From bdfafafc8069a13974cb85cb6ac63679396b9968 Mon Sep 17 00:00:00 2001 From: anilcse Date: Mon, 23 Dec 2024 12:42:58 +0530 Subject: [PATCH 1/4] add cosmos plugin --- .env.example | 4 + packages/plugin-cosmos/.npmignore | 6 + packages/plugin-cosmos/eslint.config.mjs | 3 + packages/plugin-cosmos/package.json | 29 + .../plugin-cosmos/src/actions/transfer.ts | 216 ++++++ packages/plugin-cosmos/src/environment.ts | 53 ++ packages/plugin-cosmos/src/index.ts | 14 + .../plugin-cosmos/src/providers/wallet.ts | 356 +++++++++ .../plugin-cosmos/src/tests/wallet.test.ts | 86 +++ packages/plugin-cosmos/tsconfig.json | 21 + packages/plugin-cosmos/tsup.config.ts | 12 + pnpm-lock.yaml | 731 ++++++++++++++++++ 12 files changed, 1531 insertions(+) create mode 100644 packages/plugin-cosmos/.npmignore create mode 100644 packages/plugin-cosmos/eslint.config.mjs create mode 100644 packages/plugin-cosmos/package.json create mode 100644 packages/plugin-cosmos/src/actions/transfer.ts create mode 100644 packages/plugin-cosmos/src/environment.ts create mode 100644 packages/plugin-cosmos/src/index.ts create mode 100644 packages/plugin-cosmos/src/providers/wallet.ts create mode 100644 packages/plugin-cosmos/src/tests/wallet.test.ts create mode 100644 packages/plugin-cosmos/tsconfig.json create mode 100644 packages/plugin-cosmos/tsup.config.ts diff --git a/.env.example b/.env.example index b1f05998b7..0c2b22390e 100644 --- a/.env.example +++ b/.env.example @@ -335,3 +335,7 @@ STORY_PRIVATE_KEY= # Story private key STORY_API_BASE_URL= # Story API base URL STORY_API_KEY= # Story API key PINATA_JWT= # Pinata JWT for uploading files to IPFS + +# Cosmos based networks +COSMOS_MNEMONIC= # Mnemonic to generate cosmos accounts +COSMOS_CHAIN_NAME= # chainName matching with chain-registry entries here: https://github.com/cosmos/chain-registry \ No newline at end of file diff --git a/packages/plugin-cosmos/.npmignore b/packages/plugin-cosmos/.npmignore new file mode 100644 index 0000000000..078562ecea --- /dev/null +++ b/packages/plugin-cosmos/.npmignore @@ -0,0 +1,6 @@ +* + +!dist/** +!package.json +!readme.md +!tsup.config.ts \ No newline at end of file diff --git a/packages/plugin-cosmos/eslint.config.mjs b/packages/plugin-cosmos/eslint.config.mjs new file mode 100644 index 0000000000..92fe5bbebe --- /dev/null +++ b/packages/plugin-cosmos/eslint.config.mjs @@ -0,0 +1,3 @@ +import eslintGlobalConfig from "../../eslint.config.mjs"; + +export default [...eslintGlobalConfig]; diff --git a/packages/plugin-cosmos/package.json b/packages/plugin-cosmos/package.json new file mode 100644 index 0000000000..cda377ab92 --- /dev/null +++ b/packages/plugin-cosmos/package.json @@ -0,0 +1,29 @@ +{ + "name": "@ai16z/plugin-cosmos", + "version": "0.1.0-alpha.1", + "main": "dist/index.js", + "type": "module", + "types": "dist/index.d.ts", + "dependencies": { + "@ai16z/eliza": "workspace:*", + "bignumber.js": "9.1.2", + "node-cache": "5.1.2", + "tsup": "8.3.5", + "@cosmjs/stargate": "^0.29.2", + "@cosmjs/amino": "^0.29.2", + "cosmjs-utils": "*", + "osmojs": "16.15.0", + "chain-registry": "*", + "vitest": "^0.34.1" + }, + "scripts": { + "build": "tsup --format esm,cjs --dts", + "test": "vitest run", + "test:watch": "vitest", + "lint": "eslint . --fix" + }, + "peerDependencies": { + "whatwg-url": "7.1.0", + "form-data": "4.0.1" + } + } diff --git a/packages/plugin-cosmos/src/actions/transfer.ts b/packages/plugin-cosmos/src/actions/transfer.ts new file mode 100644 index 0000000000..accb42e815 --- /dev/null +++ b/packages/plugin-cosmos/src/actions/transfer.ts @@ -0,0 +1,216 @@ +import { + ActionExample, + Content, + HandlerCallback, + IAgentRuntime, + Memory, + ModelClass, + State, + type Action, + composeContext, + generateObject, + } from "@ai16z/eliza"; + import { chains } from 'chain-registry'; + import { + getOfflineSignerProto as getOfflineSigner + } from "cosmjs-utils"; + import { SigningStargateClient } from "@cosmjs/stargate"; + import { coins, StdFee } from "@cosmjs/amino"; + + export interface TransferContent extends Content { + recipient: string; + amount: string | number; + tokenAddress?: string; // optional if we want to handle cw20 or other tokens + } + + function isTransferContent( + runtime: IAgentRuntime, + content: any + ): content is TransferContent { + return ( + typeof content.recipient === "string" && + (typeof content.amount === "string" || typeof content.amount === "number") + ); + } + + const transferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. + + Example response: + \`\`\`json + { + "recipient": "osmo1abcd1234...", + "amount": "1.5", + "tokenAddress": null + } + \`\`\` + + {{recentMessages}} + + Given the recent messages and wallet information below: + + {{walletInfo}} + + Extract the following information about the requested token transfer: + - Recipient address + - Amount to transfer + - Token contract address (null for native transfers) + + Respond with a JSON markdown block containing only the extracted values. + `; + + async function transferTokens( + runtime: IAgentRuntime, + recipient: string, + amount: string + ): Promise { + // In a real scenario, fetch from environment or runtime settings + const chainName = runtime.getSetting("COSMOS_CHAIN_NAME") || "osmosis"; + const rpc = runtime.getSetting("COSMOS_RPC_URL") || "https://rpc.osmosis.zone"; + const denom = runtime.getSetting("COSMOS_DENOM") || "uosmo"; + const decimals = Number(runtime.getSetting("COSMOS_DECIMALS") || 6); + + const mnemonic = runtime.getSetting("COSMOS_MNEMONIC"); + if (!mnemonic) { + throw new Error("COSMOS_MNEMONIC not configured"); + } + + const chain = chains.find(({ chain_name }) => chain_name === chainName); + + // get signer + const signer = await getOfflineSigner({ + mnemonic, + chain, + }); + + // connect + const stargateClient = await SigningStargateClient.connectWithSigner(rpc, signer); + + const [fromAccount] = await signer.getAccounts(); + const fromAddress = fromAccount.address; + + // Convert input amount (like 1.5) to base denom + // E.g., 1.5 OSMO => 1500000 uosmo (if 6 decimals) + const shift = Math.pow(10, decimals); + const sendAmount = String(Math.floor(Number(amount) * shift)); + + // Create send message + // If needed, you can also specify a custom fee, otherwise it uses auto. + // For demonstration: + const fee: StdFee = { + amount: coins("2000", denom), // minimal fee + gas: "200000", + }; + + const result = await stargateClient.sendTokens( + fromAddress, + recipient, + coins(sendAmount, denom), + fee + ); + + return result.transactionHash; + } + + export const executeTransfer: Action = { + name: "SEND_COSMOS", + similes: ["TRANSFER_COSMOS", "SEND_TOKENS", "TRANSFER_TOKENS", "PAY_COSMOS"], + validate: async (_runtime: IAgentRuntime, _message: Memory) => { + // Add your validation logic + return true; + }, + description: "Transfer native Cosmos tokens to another address", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: { [key: string]: unknown }, + callback?: HandlerCallback + ): Promise => { + // Initialize or update state + if (!state) { + state = (await runtime.composeState(message)) as State; + } else { + state = await runtime.updateRecentMessageState(state); + } + + // Compose context + const transferContext = composeContext({ + state, + template: transferTemplate, + }); + + // Generate content + const content = await generateObject({ + runtime, + context: transferContext, + modelClass: ModelClass.SMALL, + }); + + // Validate + if (!isTransferContent(runtime, content)) { + console.error("Invalid content for SEND_COSMOS action."); + if (callback) { + callback({ + text: "Unable to process transfer request. Invalid content provided.", + content: { error: "Invalid transfer content" }, + }); + } + return false; + } + + try { + const txHash = await transferTokens( + runtime, + content.recipient, + content.amount.toString() + ); + + if (callback) { + callback({ + text: `Successfully transferred ${content.amount} tokens to ${content.recipient}\nTransaction: ${txHash}`, + content: { + success: true, + signature: txHash, + amount: content.amount, + recipient: content.recipient, + }, + }); + } + + return true; + } catch (error) { + console.error("Error during Cosmos transfer:", error); + if (callback) { + callback({ + text: `Error transferring tokens: ${error}`, + content: { error: error }, + }); + } + return false; + } + }, + + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "Send 1.5 tokens to osmo1abcd1234...", + }, + }, + { + user: "{{user2}}", + content: { + text: "I'll send 1.5 OSMO now...", + action: "SEND_COSMOS", + }, + }, + { + user: "{{user2}}", + content: { + text: "Successfully sent 1.5 OSMO to osmo1abcd1234...\nTransaction: ABC123XYZ", + }, + }, + ], + ] as ActionExample[][], + } as Action; diff --git a/packages/plugin-cosmos/src/environment.ts b/packages/plugin-cosmos/src/environment.ts new file mode 100644 index 0000000000..d2d90ea9e6 --- /dev/null +++ b/packages/plugin-cosmos/src/environment.ts @@ -0,0 +1,53 @@ +import { IAgentRuntime } from "@elizaos/core"; +import { z } from "zod"; + +/** + * Example environment variables for Cosmos + * that mimic the NEAR example structure + */ +export const cosmosEnvSchema = z.object({ + COSMOS_MNEMONIC: z.string().min(1, "Cosmos wallet mnemonic is required"), + COSMOS_CHAIN_NAME: z.string().default("osmosis"), + COSMOS_RPC_URL: z.string().default("https://rpc.osmosis.zone"), + COSMOS_DENOM: z.string().default("uosmo"), + COSMOS_DECIMALS: z.string().default("6"), +}); + +/** + * Type for the validated config + */ +export type CosmosConfig = z.infer; + +/** + * Simple config loader that merges runtime settings with environment variables + */ +export async function validateCosmosConfig( + runtime: IAgentRuntime +): Promise { + try { + const config = { + COSMOS_MNEMONIC: + runtime.getSetting("COSMOS_MNEMONIC") || process.env.COSMOS_MNEMONIC, + COSMOS_CHAIN_NAME: + runtime.getSetting("COSMOS_CHAIN_NAME") || process.env.COSMOS_CHAIN_NAME, + COSMOS_RPC_URL: + runtime.getSetting("COSMOS_RPC_URL") || process.env.COSMOS_RPC_URL, + COSMOS_DENOM: + runtime.getSetting("COSMOS_DENOM") || process.env.COSMOS_DENOM, + COSMOS_DECIMALS: + runtime.getSetting("COSMOS_DECIMALS") || process.env.COSMOS_DECIMALS, + }; + + return cosmosEnvSchema.parse(config); + } catch (error) { + if (error instanceof z.ZodError) { + const errorMessages = error.errors + .map((err) => `${err.path.join(".")}: ${err.message}`) + .join("\n"); + throw new Error( + `Cosmos configuration validation failed:\n${errorMessages}` + ); + } + throw error; + } +} diff --git a/packages/plugin-cosmos/src/index.ts b/packages/plugin-cosmos/src/index.ts new file mode 100644 index 0000000000..2a6aea8fcb --- /dev/null +++ b/packages/plugin-cosmos/src/index.ts @@ -0,0 +1,14 @@ +import { Plugin } from "@ai16z/eliza/src/types"; +import { walletProvider } from "./providers/wallet"; +import { executeTransfer } from "./actions/transfer"; +// If you want to implement swap, custom tokens, etc., create the files similarly and import them. + +export const cosmosPlugin: Plugin = { + name: "COSMOS", + description: "Cosmos (e.g. Osmosis) Plugin for Eliza", + providers: [walletProvider], + actions: [executeTransfer], + evaluators: [], +}; + +export default cosmosPlugin; diff --git a/packages/plugin-cosmos/src/providers/wallet.ts b/packages/plugin-cosmos/src/providers/wallet.ts new file mode 100644 index 0000000000..651e45fdac --- /dev/null +++ b/packages/plugin-cosmos/src/providers/wallet.ts @@ -0,0 +1,356 @@ +import { + IAgentRuntime, + Memory, + Provider, + State + } from "@ai16z/eliza"; + import BigNumber from "bignumber.js"; + import NodeCache from "node-cache"; + import { chains } from "chain-registry"; + + import { + getOfflineSignerProto as getOfflineSigner, + } from "cosmjs-utils"; + import { SigningStargateClient } from "@cosmjs/stargate"; + import { coins, StdFee } from "@cosmjs/amino"; + + /** + * Example chain registry interface (trimmed down). + * You can mark fields as optional if some chains might omit them. + */ + interface CosmosChainInfo { + chain_name: string; + status: string; + network_type: string; + website: string; + pretty_name: string; + chain_type: string; + chain_id: string; + bech32_prefix: string; + daemon_name: string; + node_home: string; + key_algos: string[]; + slip44: number; + fees: { + fee_tokens: Array<{ + denom: string; + fixed_min_gas_price: number; + low_gas_price: number; + average_gas_price: number; + high_gas_price: number; + }>; + }; + staking: { + staking_tokens: Array<{ + denom: string; + }>; + lock_duration: { + time: string; + }; + }; + images: Array<{ + image_sync: { + chain_name: string; + base_denom: string; + }; + svg: string; + png: string; + theme: { + primary_color_hex: string; + }; + }>; + description: string; + apis: { + rpc: Array<{ + address: string; + provider: string; + }>; + rest: Array<{ + address: string; + provider: string; + }>; + grpc: Array<{ + address: string; + provider: string; + }>; + }; + + /** + * Custom convenience fields we add ourselves + * (not in the chain-registry by default) + */ + denom?: string; + decimals?: number; + } + + /** + * Example token interface + */ + export interface CosmosToken { + name: string; + symbol: string; + decimals: number; + balance: string; + uiAmount: string; + priceUsd: string; + valueUsd: string; + } + + interface WalletPortfolio { + totalUsd: string; + tokens: Array; + } + + export class WalletProvider implements Provider { + private cache: NodeCache; + private stargateClient: SigningStargateClient | null = null; + private signerAddress: string | null = null; + + constructor( + private mnemonic: string, + private chainInfo: CosmosChainInfo, + ) { + // Cache with 5-min TTL + this.cache = new NodeCache({ stdTTL: 300 }); + } + + /** + * Provides the formatted portfolio or fallback message if failing. + */ + async get( + runtime: IAgentRuntime, + _message: Memory, + _state?: State + ): Promise { + try { + return await this.getFormattedPortfolio(runtime); + } catch (error) { + console.error("Error in wallet provider:", error); + return null; + } + } + + /** + * Connects and returns the SigningStargateClient instance + */ + public async connect(runtime: IAgentRuntime): Promise { + if (this.stargateClient) return this.stargateClient; + + if (!this.mnemonic) { + throw new Error("Cosmos wallet mnemonic not provided"); + } + + // We already have our chain registry object as this.chainInfo + // We'll pick the first RPC endpoint from the list: + const rpcUrl = this.chainInfo.apis.rpc[0]?.address; + if (!rpcUrl) { + throw new Error("No RPC endpoint found in chainInfo.apis.rpc"); + } + + // Prepare signer + const signer = await getOfflineSigner({ + mnemonic: this.mnemonic, + chain: this.chainInfo // pass the entire registry object + }); + + const stargateClient = await SigningStargateClient.connectWithSigner( + rpcUrl, + signer + ); + + // Derive first address from the signer + const [firstAccount] = await signer.getAccounts(); + this.signerAddress = firstAccount.address; + this.stargateClient = stargateClient; + + return stargateClient; + } + + /** + * Retrieves account balance & constructs a single-token portfolio + * (assuming a single base token for demonstration). + */ + async fetchPortfolioValue(runtime: IAgentRuntime): Promise { + const cacheKey = `portfolio-${this.chainInfo.chain_name}`; + const cachedValue = this.cache.get(cacheKey); + + if (cachedValue) { + console.log("Cache hit for fetchPortfolioValue"); + return cachedValue; + } + + // Ensure connected + const client = await this.connect(runtime); + + if (!this.signerAddress) { + throw new Error("Signer address not available. Connect first."); + } + + // If we added .denom/.decimals to chainInfo: + const baseDenom = this.chainInfo.denom || "uosmo"; + const decimals = this.chainInfo.decimals ?? 6; + + // Fetch balances + const balances = await client.getAllBalances(this.signerAddress); + const baseTokenBalance = balances.find( + (b) => b.denom === baseDenom + ); + + // default to "0" if no balance found + const rawBalance = baseTokenBalance?.amount ?? "0"; + + // fetch token price from e.g. Coingecko + const tokenPriceUsd = await this.fetchTokenPrice(runtime) || 0; + + // Convert from minimal denom to "1" denom + const convertedBalance = new BigNumber(rawBalance).shiftedBy(-decimals); + const valueUsd = convertedBalance.multipliedBy(tokenPriceUsd).toFixed(); + + const portfolio: WalletPortfolio = { + totalUsd: valueUsd, + tokens: [ + { + name: this.chainInfo.pretty_name || "Osmosis", + symbol: "OSMO", // or set dynamically if you have that info + decimals, + balance: rawBalance, + uiAmount: convertedBalance.toString(), + priceUsd: tokenPriceUsd.toString(), + valueUsd: valueUsd, + }, + ], + }; + + this.cache.set(cacheKey, portfolio); + return portfolio; + } + + /** + * Example token price fetcher for demonstration. + * In production, you might fetch from Coingecko or similar. + */ + private async fetchTokenPrice(runtime: IAgentRuntime): Promise { + const cacheKey = `price-${this.chainInfo.chain_name}`; + const cachedPrice = this.cache.get(cacheKey); + + if (cachedPrice) { + return cachedPrice; + } + + try { + // For demonstration, we assume OSMO price from Coingecko + // If chain is not OSMO, adapt the ID or fetch from a different endpoint. + const response = await fetch( + "https://api.coingecko.com/api/v3/simple/price?ids=osmosis&vs_currencies=usd" + ); + if (!response.ok) { + throw new Error(`Error fetching token price. Status: ${response.status}`); + } + const data = await response.json(); + const price = data.osmosis?.usd ?? 0; + this.cache.set(cacheKey, price); + return price; + } catch (error) { + console.error("Error fetching token price:", error); + return 0; + } + } + + /** + * Format portfolio into a text string for display + */ + formatPortfolio( + runtime: IAgentRuntime, + portfolio: WalletPortfolio + ): string { + let output = `${runtime.character.system}\n`; + output += `Chain: ${this.chainInfo.chain_name}\n`; + + if (this.signerAddress) { + output += `Account Address: ${this.signerAddress}\n\n`; + } + + const totalUsdFormatted = new BigNumber(portfolio.totalUsd).toFixed(2); + output += `Total Value: $${totalUsdFormatted}\n\n`; + output += "Token Balances:\n"; + + for (const token of portfolio.tokens) { + const tokenValUsd = new BigNumber(token.valueUsd).toFixed(2); + output += `${token.name} (${token.symbol}): ${token.uiAmount} ($${tokenValUsd})\n`; + } + + output += "\nMarket Prices:\n"; + for (const token of portfolio.tokens) { + const tokenPriceUsd = new BigNumber(token.priceUsd).toFixed(2); + output += `${token.symbol}: $${tokenPriceUsd}\n`; + } + + return output; + } + + /** + * Convenience method to fetch + format + */ + async getFormattedPortfolio(runtime: IAgentRuntime): Promise { + try { + const portfolio = await this.fetchPortfolioValue(runtime); + return this.formatPortfolio(runtime, portfolio); + } catch (error) { + console.error("Error generating portfolio report:", error); + return "Unable to fetch wallet information. Please try again later."; + } + } + } + + /** + * Single instance of wallet provider + */ + const walletProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + _message: Memory, + _state?: State + ): Promise => { + try { + // In a real scenario, you'd load these from environment or runtime settings + const mnemonic = runtime.getSetting("COSMOS_MNEMONIC"); + const chainName = runtime.getSetting("COSMOS_CHAIN_NAME") || "osmosis"; + + if (!mnemonic) { + throw new Error("COSMOS_MNEMONIC not configured"); + } + + // 1) Look up chain data from chain-registry + const chain = chains.find((c) => c.chain_name === chainName); + if (!chain) { + throw new Error(`Chain '${chainName}' not found in chain-registry`); + } + + console.log("chaininfo", chain) + + // Convert chain-registry object to our interface + // (Optional: if you're certain the chain matches the interface, you can cast directly.) + const chainInfo: CosmosChainInfo = { + ...chain, + // We'll grab the first RPC as the "main" RPC: + // chain.apis might be undefined for certain chains, so check + apis: chain.apis || { rpc: [], rest: [], grpc: [] }, + + // For demonstration, pick the first fee_token if available + denom: chain.fees?.fee_tokens?.[0]?.denom || "uosmo", + decimals: 6, // Cosmos is typically 6 + }; + + // 2) Create the wallet provider + const provider = new WalletProvider(mnemonic, chainInfo); + + // 3) Return the formatted portfolio + return await provider.getFormattedPortfolio(runtime); + } catch (error) { + console.error("Error in wallet provider:", error); + return null; + } + }, + }; + + export { walletProvider }; diff --git a/packages/plugin-cosmos/src/tests/wallet.test.ts b/packages/plugin-cosmos/src/tests/wallet.test.ts new file mode 100644 index 0000000000..2d1a5584c9 --- /dev/null +++ b/packages/plugin-cosmos/src/tests/wallet.test.ts @@ -0,0 +1,86 @@ +import { describe, it, expect, beforeEach, vi, afterEach } from "vitest"; +import { WalletProvider } from "../providers/wallet"; +import { defaultCharacter } from "@ai16z/eliza"; +import { assets, chains } from 'chain-registry/testnet'; + +import BigNumber from "bignumber.js"; + +// Mock NodeCache so we don't actually cache or cause side effects +vi.mock("node-cache", () => { + return { + default: vi.fn().mockImplementation(() => ({ + set: vi.fn(), + get: vi.fn().mockReturnValue(null), + })), + }; +}); + +// Mock path if needed (like in your SUI example): +vi.mock("path", async () => { + const actual = await vi.importActual("path"); + return { + ...actual, + join: vi.fn().mockImplementation((...args) => args.join("/")), + }; +}); + +// Mock the ICacheManager if you’re using a custom cache manager +const mockCacheManager = { + get: vi.fn().mockResolvedValue(null), + set: vi.fn(), + delete: vi.fn(), +}; + +describe("Cosmos WalletProvider", () => { + let walletProvider: WalletProvider; + let mockedRuntime: any; + + beforeEach(() => { + vi.clearAllMocks(); + + // Example mnemonic. DO NOT use real keys in test code! + const mnemonic ="unfold client turtle either pilot stock floor glow toward bullet car science"; + const chainName = "osmosistestnet"; + + const chain = chains.find((c) => c.chain_name === chainName); + if (!chain) { + throw new Error(`Chain '${chainName}' not found in chain-registry`); + } + + // If your wallet provider requires additional chain config, pass it here: + walletProvider = new WalletProvider( + mnemonic, + chain, + mockCacheManager + ); + + // Mock runtime/character + mockedRuntime = { + character: defaultCharacter, + }; + }); + + afterEach(() => { + vi.clearAllTimers(); + }); + + describe("Wallet Integration", () => { + it("should return a formatted portfolio with wallet address", async () => { + // Retrieve the portfolio text + const result = await walletProvider.getFormattedPortfolio(mockedRuntime); + + // In a real test, you'd parse the string or validate its structure. + // For demonstration, we just check that it includes the chain_name + // and a "Account Address" label or something similar. + expect(result).toContain("osmosis"); + expect(result).toContain("Account Address:"); + }); + + it("should contain a total value in USD", async () => { + const result = await walletProvider.getFormattedPortfolio(mockedRuntime); + + // For example, check if it says "Total Value: $" + expect(result).toMatch(/Total Value: \$[\d,]+\.\d{2}/); + }); + }); +}); diff --git a/packages/plugin-cosmos/tsconfig.json b/packages/plugin-cosmos/tsconfig.json new file mode 100644 index 0000000000..95cbe371ac --- /dev/null +++ b/packages/plugin-cosmos/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "node", + "outDir": "dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "typeRoots": [ + "./node_modules/@types", + "./src/types" + ], + "declaration": true + }, + "include": [ + "src" + ] +} \ No newline at end of file diff --git a/packages/plugin-cosmos/tsup.config.ts b/packages/plugin-cosmos/tsup.config.ts new file mode 100644 index 0000000000..96d9c23733 --- /dev/null +++ b/packages/plugin-cosmos/tsup.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + format: ["esm", "cjs"], + dts: true, + splitting: false, + sourcemap: true, + clean: true, + shims: true, + treeshake: true +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 86cda46823..bde4824c82 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1473,6 +1473,45 @@ importers: specifier: 8.3.5 version: 8.3.5(@swc/core@1.10.1(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(typescript@5.6.3)(yaml@2.6.1) + packages/plugin-osmosis: + dependencies: + '@ai16z/eliza': + specifier: workspace:* + version: link:../core + '@cosmjs/amino': + specifier: ^0.29.2 + version: 0.29.5 + '@cosmjs/stargate': + specifier: ^0.29.2 + version: 0.29.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) + bignumber.js: + specifier: 9.1.2 + version: 9.1.2 + chain-registry: + specifier: '*' + version: 1.69.71 + cosmjs-utils: + specifier: '*' + version: 0.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + form-data: + specifier: 4.0.1 + version: 4.0.1 + node-cache: + specifier: 5.1.2 + version: 5.1.2 + osmojs: + specifier: 16.15.0 + version: 16.15.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + tsup: + specifier: 8.3.5 + version: 8.3.5(@swc/core@1.10.1(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(typescript@5.6.3)(yaml@2.6.1) + vitest: + specifier: ^0.34.1 + version: 0.34.6(jsdom@25.0.1(bufferutil@4.0.8)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(playwright@1.48.2)(terser@5.37.0) + whatwg-url: + specifier: 7.1.0 + version: 7.1.0 + packages/plugin-solana: dependencies: '@ai16z/eliza': @@ -2972,6 +3011,9 @@ packages: '@cfworker/json-schema@4.0.3': resolution: {integrity: sha512-ZykIcDTVv5UNmKWSTLAs3VukO6NDJkkSKxrgUTDPBkAlORVT3H9n5DbRjRl8xIotklscHdbLIa0b9+y3mQq73g==} + '@chain-registry/types@0.50.41': + resolution: {integrity: sha512-GT1FmCgtJLAkVy2fOD7VOn9SHDOMeLUshiCjtQhmXQiIEoycn3tUJk7HSser5Wf35wq3a2OqeY7f7iKs4/Tlzw==} + '@chevrotain/cst-dts-gen@11.0.3': resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==} @@ -3084,6 +3126,9 @@ packages: resolution: {integrity: sha512-gwRLBLra/Dozj2OywopeuHj2ac26gjGkz2cZ+86cTJOdtWfiRRr4+e77ZDAGc6MDWxaWheI+mAV5TLWWRwqrFg==} engines: {node: '>=v18'} + '@confio/ics23@0.6.8': + resolution: {integrity: sha512-wB6uo+3A50m0sW/EWcU64xpV/8wShZ6bMTa7pF8eYsTrSkQA7oLUIJcs/wb8g4y2Oyq701BaGiO6n/ak5WXO1w==} + '@coral-xyz/anchor-errors@0.30.1': resolution: {integrity: sha512-9Mkradf5yS5xiLWrl9WrpjqOrAV+/W2RQHDlbnAZBivoGpOs1ECjoDCkVk4aRG8ZdiFiB8zQEVlxf+8fKkmSfQ==} engines: {node: '>=10'} @@ -3108,6 +3153,87 @@ packages: peerDependencies: '@solana/web3.js': ^1.68.0 + '@cosmjs/amino@0.29.0': + resolution: {integrity: sha512-/ZUVx6nRN5YE36H3SDq9+i8g2nZ8DJQnN9fVRC8rSHQKauNkoEuK4NxTNcQ2o2EBLUT0kyYAFY2550HVsPMrgw==} + + '@cosmjs/amino@0.29.5': + resolution: {integrity: sha512-Qo8jpC0BiziTSUqpkNatBcwtKNhCovUnFul9SlT/74JUCdLYaeG5hxr3q1cssQt++l4LvlcpF+OUXL48XjNjLw==} + + '@cosmjs/amino@0.32.3': + resolution: {integrity: sha512-G4zXl+dJbqrz1sSJ56H/25l5NJEk/pAPIr8piAHgbXYw88OdAOlpA26PQvk2IbSN/rRgVbvlLTNgX2tzz1dyUA==} + + '@cosmjs/crypto@0.29.0': + resolution: {integrity: sha512-MPJoebRGh7AcZgbfR25ci7iV+XzJiKwVq4wL8n6M5P2QdrIv7DqqniyFXcBbn9dQjMLMHnOSgT9LRv+VXzUVCA==} + + '@cosmjs/crypto@0.29.5': + resolution: {integrity: sha512-2bKkaLGictaNL0UipQCL6C1afaisv6k8Wr/GCLx9FqiyFkh9ZgRHDyetD64ZsjnWV/N/D44s/esI+k6oPREaiQ==} + + '@cosmjs/crypto@0.32.4': + resolution: {integrity: sha512-zicjGU051LF1V9v7bp8p7ovq+VyC91xlaHdsFOTo2oVry3KQikp8L/81RkXmUIT8FxMwdx1T7DmFwVQikcSDIw==} + + '@cosmjs/encoding@0.29.5': + resolution: {integrity: sha512-G4rGl/Jg4dMCw5u6PEZHZcoHnUBlukZODHbm/wcL4Uu91fkn5jVo5cXXZcvs4VCkArVGrEj/52eUgTZCmOBGWQ==} + + '@cosmjs/encoding@0.32.4': + resolution: {integrity: sha512-tjvaEy6ZGxJchiizzTn7HVRiyTg1i4CObRRaTRPknm5EalE13SV+TCHq38gIDfyUeden4fCuaBVEdBR5+ti7Hw==} + + '@cosmjs/json-rpc@0.29.5': + resolution: {integrity: sha512-C78+X06l+r9xwdM1yFWIpGl03LhB9NdM1xvZpQHwgCOl0Ir/WV8pw48y3Ez2awAoUBRfTeejPe4KvrE6NoIi/w==} + + '@cosmjs/json-rpc@0.32.4': + resolution: {integrity: sha512-/jt4mBl7nYzfJ2J/VJ+r19c92mUKF0Lt0JxM3MXEJl7wlwW5haHAWtzRujHkyYMXOwIR+gBqT2S0vntXVBRyhQ==} + + '@cosmjs/math@0.29.5': + resolution: {integrity: sha512-2GjKcv+A9f86MAWYLUkjhw1/WpRl2R1BTb3m9qPG7lzMA7ioYff9jY5SPCfafKdxM4TIQGxXQlYGewQL16O68Q==} + + '@cosmjs/math@0.32.4': + resolution: {integrity: sha512-++dqq2TJkoB8zsPVYCvrt88oJWsy1vMOuSOKcdlnXuOA/ASheTJuYy4+oZlTQ3Fr8eALDLGGPhJI02W2HyAQaw==} + + '@cosmjs/proto-signing@0.29.0': + resolution: {integrity: sha512-zAdgDz5vRGAfJ5yyKYuTL7qg5UNUT7v4iV1/ZP8ZQn2fLh9QVxViAIovF4r/Y3EEI4JS5uYj/f8UeHMHQSu8hw==} + + '@cosmjs/proto-signing@0.29.5': + resolution: {integrity: sha512-QRrS7CiKaoETdgIqvi/7JC2qCwCR7lnWaUsTzh/XfRy3McLkEd+cXbKAW3cygykv7IN0VAEIhZd2lyIfT8KwNA==} + + '@cosmjs/proto-signing@0.32.3': + resolution: {integrity: sha512-kSZ0ZUY0DwcRT0NcIn2HkadH4NKlwjfZgbLj1ABwh/4l0RgeT84QCscZCu63tJYq3K6auwqTiZSZERwlO4/nbg==} + + '@cosmjs/socket@0.29.5': + resolution: {integrity: sha512-5VYDupIWbIXq3ftPV1LkS5Ya/T7Ol/AzWVhNxZ79hPe/mBfv1bGau/LqIYOm2zxGlgm9hBHOTmWGqNYDwr9LNQ==} + + '@cosmjs/socket@0.32.4': + resolution: {integrity: sha512-davcyYziBhkzfXQTu1l5NrpDYv0K9GekZCC9apBRvL1dvMc9F/ygM7iemHjUA+z8tJkxKxrt/YPjJ6XNHzLrkw==} + + '@cosmjs/stargate@0.29.0': + resolution: {integrity: sha512-BsV3iA3vMclMm/B1LYO0djBYCALr/UIvL6u9HGvM7QvpdtpQiAvskuS4PieVO/gtF9iCCBJLPqa0scwFIgvDyg==} + + '@cosmjs/stargate@0.29.5': + resolution: {integrity: sha512-hjEv8UUlJruLrYGJcUZXM/CziaINOKwfVm2BoSdUnNTMxGvY/jC1ABHKeZUYt9oXHxEJ1n9+pDqzbKc8pT0nBw==} + + '@cosmjs/stargate@0.32.3': + resolution: {integrity: sha512-OQWzO9YWKerUinPIxrO1MARbe84XkeXJAW0lyMIjXIEikajuXZ+PwftiKA5yA+8OyditVmHVLtPud6Pjna2s5w==} + + '@cosmjs/stream@0.29.5': + resolution: {integrity: sha512-TToTDWyH1p05GBtF0Y8jFw2C+4783ueDCmDyxOMM6EU82IqpmIbfwcdMOCAm0JhnyMh+ocdebbFvnX/sGKzRAA==} + + '@cosmjs/stream@0.32.4': + resolution: {integrity: sha512-Gih++NYHEiP+oyD4jNEUxU9antoC0pFSg+33Hpp0JlHwH0wXhtD3OOKnzSfDB7OIoEbrzLJUpEjOgpCp5Z+W3A==} + + '@cosmjs/tendermint-rpc@0.29.5': + resolution: {integrity: sha512-ar80twieuAxsy0x2za/aO3kBr2DFPAXDmk2ikDbmkda+qqfXgl35l9CVAAjKRqd9d+cRvbQyb5M4wy6XQpEV6w==} + + '@cosmjs/tendermint-rpc@0.32.3': + resolution: {integrity: sha512-xeprW+VR9xKGstqZg0H/KBZoUp8/FfFyS9ljIUTLM/UINjP2MhiwncANPS2KScfJVepGufUKk0/phHUeIBSEkw==} + + '@cosmjs/utils@0.29.5': + resolution: {integrity: sha512-m7h+RXDUxOzEOGt4P+3OVPX7PuakZT3GBmaM/Y2u+abN3xZkziykD/NvedYFvvCCdQo714XcGl33bwifS9FZPQ==} + + '@cosmjs/utils@0.32.4': + resolution: {integrity: sha512-D1Yc+Zy8oL/hkUkFUL/bwxvuDBzRGpc4cF7/SkdhxX4iHpSLgdOuTt1mhCh9+kl6NQREy9t7SYZ6xeW5gFe60w==} + + '@cosmology/lcd@0.13.5': + resolution: {integrity: sha512-CI8KFsJcgp0RINF8wHpv3Y9yR4Fb9ZnGucyoUICjtX2XT4NVBK+fvZuRFj5TP34km8TpEOb+WV2T7IN/pZsD7Q==} + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -7298,6 +7424,12 @@ packages: '@types/cacheable-request@6.0.3': resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} + '@types/chai-subset@1.3.5': + resolution: {integrity: sha512-c2mPnw+xHtXDoHmdtcCXGwyLMiauiAyxWMzhGpqHC4nqI/Y5G2XhTampslK2rb59kpcuHon03UH8W6iYUzw88A==} + + '@types/chai@4.3.20': + resolution: {integrity: sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==} + '@types/chrome@0.0.278': resolution: {integrity: sha512-PDIJodOu7o54PpSOYLybPW/MDZBCjM1TKgf31I3Q/qaEbNpIH09rOM3tSEH3N7Q+FAqb1933LhF8ksUPYeQLNg==} @@ -7525,6 +7657,9 @@ packages: '@types/lodash@4.17.13': resolution: {integrity: sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==} + '@types/long@4.0.2': + resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} + '@types/lru-cache@5.1.1': resolution: {integrity: sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==} @@ -7877,6 +8012,9 @@ packages: vitest: optional: true + '@vitest/expect@0.34.6': + resolution: {integrity: sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==} + '@vitest/expect@2.1.4': resolution: {integrity: sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA==} @@ -7914,24 +8052,36 @@ packages: '@vitest/pretty-format@2.1.8': resolution: {integrity: sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==} + '@vitest/runner@0.34.6': + resolution: {integrity: sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==} + '@vitest/runner@2.1.4': resolution: {integrity: sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA==} '@vitest/runner@2.1.5': resolution: {integrity: sha512-pKHKy3uaUdh7X6p1pxOkgkVAFW7r2I818vHDthYLvUyjRfkKOU6P45PztOch4DZarWQne+VOaIMwA/erSSpB9g==} + '@vitest/snapshot@0.34.6': + resolution: {integrity: sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==} + '@vitest/snapshot@2.1.4': resolution: {integrity: sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q==} '@vitest/snapshot@2.1.5': resolution: {integrity: sha512-zmYw47mhfdfnYbuhkQvkkzYroXUumrwWDGlMjpdUr4jBd3HZiV2w7CQHj+z7AAS4VOtWxI4Zt4bWt4/sKcoIjg==} + '@vitest/spy@0.34.6': + resolution: {integrity: sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==} + '@vitest/spy@2.1.4': resolution: {integrity: sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg==} '@vitest/spy@2.1.5': resolution: {integrity: sha512-aWZF3P0r3w6DiYTVskOYuhBc7EMc3jvn1TkBg8ttylFFRqNN2XGD7V5a4aQdk6QiUzZQ4klNBSpCLJgWNdIiNw==} + '@vitest/utils@0.34.6': + resolution: {integrity: sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==} + '@vitest/utils@2.1.4': resolution: {integrity: sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==} @@ -8494,6 +8644,9 @@ packages: assert@1.5.1: resolution: {integrity: sha512-zzw1uCAgLbsKwBfFc8CX78DDg+xZeBksSO3vwVIDDN5i94eOrPsSSyiVhmsSABFDM/OcpE2aagCat9dnWQLG1A==} + assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -9123,10 +9276,17 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@4.5.0: + resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} + engines: {node: '>=4'} + chai@5.1.2: resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} engines: {node: '>=12'} + chain-registry@1.69.71: + resolution: {integrity: sha512-2at89COqTRrSfJHLWFQgjuqOi6FX2V8EwE4O9JNgV8tPNcYjoIi4PcZcSofxFj4sgniKqITrzTkuSG9Sa7/NDA==} + chalk@1.1.3: resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} engines: {node: '>=0.10.0'} @@ -9169,6 +9329,9 @@ packages: charm@0.1.2: resolution: {integrity: sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==} + check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + check-error@2.1.1: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} @@ -9697,6 +9860,18 @@ packages: typescript: optional: true + cosmjs-types@0.5.1: + resolution: {integrity: sha512-NcC58xUIVLlKdIimWWQAmSlmCjiMrJnuHf4i3LiD8PCextfHR0fT3V5/WlXZZreyMgdmh6ML1zPUfGTbbo3Z5g==} + + cosmjs-types@0.5.2: + resolution: {integrity: sha512-zxCtIJj8v3Di7s39uN4LNcN3HIE1z0B9Z0SPE8ZNQR0oSzsuSe1ACgxoFkvhkS7WBasCAFcglS11G2hyfd5tPg==} + + cosmjs-types@0.9.0: + resolution: {integrity: sha512-MN/yUe6mkJwHnCFfsNPeCfXVhyxHYW6c/xDUzrSbBycYzw++XvWDMJArXp2pLdgD6FQ8DW79vkPjeNKVrXaHeQ==} + + cosmjs-utils@0.1.0: + resolution: {integrity: sha512-Ao2YhVXN+FqvbKqNeVN6I4njzRsCe3LVuPiLerMrAr6xr4MMABzvZbnY13AK/CYPrFJFJpmBCssocthAxOi59g==} + crc-32@1.2.2: resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} engines: {node: '>=0.8'} @@ -10223,6 +10398,10 @@ packages: babel-plugin-macros: optional: true + deep-eql@4.1.4: + resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} + engines: {node: '>=6'} + deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -11437,6 +11616,9 @@ packages: resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==} engines: {node: '>=18'} + get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + get-intrinsic@1.2.6: resolution: {integrity: sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==} engines: {node: '>= 0.4'} @@ -13167,6 +13349,12 @@ packages: resolution: {integrity: sha512-26zzwoBNAvX9AWOPiqqF6FG4HrSCPsHFkQm7nT+xU1ggAujL/eae81RnCv4CJ2In9q9fh10B88sYSzKCUh/Ghg==} engines: {node: ^16.14.0 || >=18.0.0} + libsodium-sumo@0.7.15: + resolution: {integrity: sha512-5tPmqPmq8T8Nikpm1Nqj0hBHvsLFCXvdhBFV7SGOitQPZAA6jso8XoL0r4L7vmfKXr486fiQInvErHtEvizFMw==} + + libsodium-wrappers-sumo@0.7.15: + resolution: {integrity: sha512-aSWY8wKDZh5TC7rMvEdTHoyppVq/1dTSAeAR7H6pzd6QRT3vQWcT5pGwCotLcpPEOLXX6VvqihSPkpEhYAjANA==} + libsodium-wrappers@0.7.15: resolution: {integrity: sha512-E4anqJQwcfiC6+Yrl01C1m8p99wEhLmJSs0VQqST66SbQXXBoaJY0pF4BNjRYa/sOQAxx6lXAaAFIlx+15tXJQ==} @@ -13254,6 +13442,10 @@ packages: resolution: {integrity: sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==} engines: {node: '>= 12.13.0'} + local-pkg@0.4.3: + resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} + engines: {node: '>=14'} + local-pkg@0.5.1: resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} engines: {node: '>=14'} @@ -13378,6 +13570,9 @@ packages: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} + long@4.0.0: + resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} + long@5.2.3: resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} @@ -13391,6 +13586,9 @@ packages: lossless-json@4.0.2: resolution: {integrity: sha512-+z0EaLi2UcWi8MZRxA5iTb6m4Ys4E80uftGY+yG5KNFJb5EceQXOhdW/pWJZ8m97s26u7yZZAYMcKWNztSZssA==} + loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + loupe@3.1.2: resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} @@ -14556,6 +14754,9 @@ packages: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} + osmojs@16.15.0: + resolution: {integrity: sha512-ERIXRzSF+EkS+RNFSzhTurr/EfWnpNfV6b1onf0MXd+YA3X3t8WbkboXg9+/ol61HDEGjugEGzRtz6sFvwaC3w==} + otpauth@9.3.6: resolution: {integrity: sha512-eIcCvuEvcAAPHxUKC9Q4uCe0Fh/yRc5jv9z+f/kvyIF2LPrhgAOuLB7J9CssGYhND/BL8M9hlHBTFmffpoQlMQ==} @@ -14853,6 +15054,9 @@ packages: pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + pathval@2.0.0: resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} engines: {node: '>= 14.16'} @@ -15859,6 +16063,10 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + protobufjs@6.11.4: + resolution: {integrity: sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==} + hasBin: true + protobufjs@7.4.0: resolution: {integrity: sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==} engines: {node: '>=12.0.0'} @@ -16243,6 +16451,9 @@ packages: readline@1.3.0: resolution: {integrity: sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==} + readonly-date@1.0.0: + resolution: {integrity: sha512-tMKIV7hlk0h4mO3JTmmVuIlJVXjKk3Sep9Bf5OH0O+758ruuVkUy2J9SttDLm91IEX/WHlXPSpxMGjPj4beMIQ==} + real-require@0.1.0: resolution: {integrity: sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg==} engines: {node: '>= 12.13.0'} @@ -17230,6 +17441,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-literal@1.3.0: + resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} + strnum@1.0.5: resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} @@ -17326,6 +17540,10 @@ packages: peerDependencies: vue: '>=3.2.26 < 4' + symbol-observable@2.0.3: + resolution: {integrity: sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA==} + engines: {node: '>=0.10'} + symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -17513,6 +17731,10 @@ packages: engines: {node: '>= 12.10.0', npm: '>= 6.12.0', yarn: '>= 1.20.0'} hasBin: true + tinypool@0.7.0: + resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==} + engines: {node: '>=14.0.0'} + tinypool@1.0.2: resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -17525,6 +17747,10 @@ packages: resolution: {integrity: sha512-CvvMFgecnQMyg59nOnAD5O4lV83cVj2ooDniJ3j2bYvMajqlK4wQ13k6OUHfA+J5nkInTxbSGJv2olUJIiAtJg==} engines: {node: '>= 18'} + tinyspy@2.2.1: + resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} + engines: {node: '>=14.0.0'} + tinyspy@3.0.2: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} @@ -17815,6 +18041,10 @@ packages: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} + type-detect@4.1.0: + resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} + engines: {node: '>=4'} + type-fest@0.18.1: resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} engines: {node: '>=10'} @@ -18372,6 +18602,11 @@ packages: typescript: optional: true + vite-node@0.34.6: + resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==} + engines: {node: '>=v14.18.0'} + hasBin: true + vite-node@2.1.4: resolution: {integrity: sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg==} engines: {node: ^18.0.0 || >=20.0.0} @@ -18423,6 +18658,37 @@ packages: terser: optional: true + vitest@0.34.6: + resolution: {integrity: sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==} + engines: {node: '>=v14.18.0'} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@vitest/browser': '*' + '@vitest/ui': '*' + happy-dom: '*' + jsdom: '*' + playwright: '*' + safaridriver: '*' + webdriverio: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + playwright: + optional: true + safaridriver: + optional: true + webdriverio: + optional: true + vitest@2.1.4: resolution: {integrity: sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ==} engines: {node: ^18.0.0 || >=20.0.0} @@ -18931,6 +19197,9 @@ packages: xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + xstream@11.14.0: + resolution: {integrity: sha512-1bLb+kKKtKPbgTK6i/BaoAn03g47PpFstlbe1BA+y3pNS/LfvcaghS5BFf9+EE1J+KwSQsEpfJvFN5GqFtiNmw==} + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -20953,6 +21222,8 @@ snapshots: '@cfworker/json-schema@4.0.3': {} + '@chain-registry/types@0.50.41': {} + '@chevrotain/cst-dts-gen@11.0.3': dependencies: '@chevrotain/gast': 11.0.3 @@ -21139,6 +21410,11 @@ snapshots: dependencies: chalk: 4.1.2 + '@confio/ics23@0.6.8': + dependencies: + '@noble/hashes': 1.6.1 + protobufjs: 6.11.4 + '@coral-xyz/anchor-errors@0.30.1': {} '@coral-xyz/anchor@0.29.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)': @@ -21196,6 +21472,243 @@ snapshots: bn.js: 5.2.1 buffer-layout: 1.2.2 + '@cosmjs/amino@0.29.0': + dependencies: + '@cosmjs/crypto': 0.29.0 + '@cosmjs/encoding': 0.29.5 + '@cosmjs/math': 0.29.5 + '@cosmjs/utils': 0.29.5 + + '@cosmjs/amino@0.29.5': + dependencies: + '@cosmjs/crypto': 0.29.5 + '@cosmjs/encoding': 0.29.5 + '@cosmjs/math': 0.29.5 + '@cosmjs/utils': 0.29.5 + + '@cosmjs/amino@0.32.3': + dependencies: + '@cosmjs/crypto': 0.32.4 + '@cosmjs/encoding': 0.32.4 + '@cosmjs/math': 0.32.4 + '@cosmjs/utils': 0.32.4 + + '@cosmjs/crypto@0.29.0': + dependencies: + '@cosmjs/encoding': 0.29.5 + '@cosmjs/math': 0.29.5 + '@cosmjs/utils': 0.29.5 + '@noble/hashes': 1.6.1 + bn.js: 5.2.1 + elliptic: 6.6.1 + libsodium-wrappers: 0.7.15 + + '@cosmjs/crypto@0.29.5': + dependencies: + '@cosmjs/encoding': 0.29.5 + '@cosmjs/math': 0.29.5 + '@cosmjs/utils': 0.29.5 + '@noble/hashes': 1.6.1 + bn.js: 5.2.1 + elliptic: 6.6.1 + libsodium-wrappers: 0.7.15 + + '@cosmjs/crypto@0.32.4': + dependencies: + '@cosmjs/encoding': 0.32.4 + '@cosmjs/math': 0.32.4 + '@cosmjs/utils': 0.32.4 + '@noble/hashes': 1.6.1 + bn.js: 5.2.1 + elliptic: 6.6.1 + libsodium-wrappers-sumo: 0.7.15 + + '@cosmjs/encoding@0.29.5': + dependencies: + base64-js: 1.5.1 + bech32: 1.1.4 + readonly-date: 1.0.0 + + '@cosmjs/encoding@0.32.4': + dependencies: + base64-js: 1.5.1 + bech32: 1.1.4 + readonly-date: 1.0.0 + + '@cosmjs/json-rpc@0.29.5': + dependencies: + '@cosmjs/stream': 0.29.5 + xstream: 11.14.0 + + '@cosmjs/json-rpc@0.32.4': + dependencies: + '@cosmjs/stream': 0.32.4 + xstream: 11.14.0 + + '@cosmjs/math@0.29.5': + dependencies: + bn.js: 5.2.1 + + '@cosmjs/math@0.32.4': + dependencies: + bn.js: 5.2.1 + + '@cosmjs/proto-signing@0.29.0': + dependencies: + '@cosmjs/amino': 0.29.5 + '@cosmjs/crypto': 0.29.0 + '@cosmjs/encoding': 0.29.5 + '@cosmjs/math': 0.29.5 + '@cosmjs/utils': 0.29.5 + cosmjs-types: 0.5.1 + long: 4.0.0 + + '@cosmjs/proto-signing@0.29.5': + dependencies: + '@cosmjs/amino': 0.29.5 + '@cosmjs/crypto': 0.29.5 + '@cosmjs/encoding': 0.29.5 + '@cosmjs/math': 0.29.5 + '@cosmjs/utils': 0.29.5 + cosmjs-types: 0.5.2 + long: 4.0.0 + + '@cosmjs/proto-signing@0.32.3': + dependencies: + '@cosmjs/amino': 0.32.3 + '@cosmjs/crypto': 0.32.4 + '@cosmjs/encoding': 0.32.4 + '@cosmjs/math': 0.32.4 + '@cosmjs/utils': 0.32.4 + cosmjs-types: 0.9.0 + + '@cosmjs/socket@0.29.5(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + dependencies: + '@cosmjs/stream': 0.29.5 + isomorphic-ws: 4.0.1(ws@7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + ws: 7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10) + xstream: 11.14.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@cosmjs/socket@0.32.4(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + dependencies: + '@cosmjs/stream': 0.32.4 + isomorphic-ws: 4.0.1(ws@7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + ws: 7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10) + xstream: 11.14.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@cosmjs/stargate@0.29.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + dependencies: + '@confio/ics23': 0.6.8 + '@cosmjs/amino': 0.29.5 + '@cosmjs/encoding': 0.29.5 + '@cosmjs/math': 0.29.5 + '@cosmjs/proto-signing': 0.29.0 + '@cosmjs/stream': 0.29.5 + '@cosmjs/tendermint-rpc': 0.29.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@cosmjs/utils': 0.29.5 + cosmjs-types: 0.5.1 + long: 4.0.0 + protobufjs: 6.11.4 + xstream: 11.14.0 + transitivePeerDependencies: + - bufferutil + - debug + - utf-8-validate + + '@cosmjs/stargate@0.29.5(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + dependencies: + '@confio/ics23': 0.6.8 + '@cosmjs/amino': 0.29.5 + '@cosmjs/encoding': 0.29.5 + '@cosmjs/math': 0.29.5 + '@cosmjs/proto-signing': 0.29.5 + '@cosmjs/stream': 0.29.5 + '@cosmjs/tendermint-rpc': 0.29.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@cosmjs/utils': 0.29.5 + cosmjs-types: 0.5.2 + long: 4.0.0 + protobufjs: 6.11.4 + xstream: 11.14.0 + transitivePeerDependencies: + - bufferutil + - debug + - utf-8-validate + + '@cosmjs/stargate@0.32.3(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + dependencies: + '@confio/ics23': 0.6.8 + '@cosmjs/amino': 0.32.3 + '@cosmjs/encoding': 0.32.4 + '@cosmjs/math': 0.32.4 + '@cosmjs/proto-signing': 0.32.3 + '@cosmjs/stream': 0.32.4 + '@cosmjs/tendermint-rpc': 0.32.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@cosmjs/utils': 0.32.4 + cosmjs-types: 0.9.0 + xstream: 11.14.0 + transitivePeerDependencies: + - bufferutil + - debug + - utf-8-validate + + '@cosmjs/stream@0.29.5': + dependencies: + xstream: 11.14.0 + + '@cosmjs/stream@0.32.4': + dependencies: + xstream: 11.14.0 + + '@cosmjs/tendermint-rpc@0.29.5(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + dependencies: + '@cosmjs/crypto': 0.29.5 + '@cosmjs/encoding': 0.29.5 + '@cosmjs/json-rpc': 0.29.5 + '@cosmjs/math': 0.29.5 + '@cosmjs/socket': 0.29.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@cosmjs/stream': 0.29.5 + '@cosmjs/utils': 0.29.5 + axios: 0.21.4 + readonly-date: 1.0.0 + xstream: 11.14.0 + transitivePeerDependencies: + - bufferutil + - debug + - utf-8-validate + + '@cosmjs/tendermint-rpc@0.32.3(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + dependencies: + '@cosmjs/crypto': 0.32.4 + '@cosmjs/encoding': 0.32.4 + '@cosmjs/json-rpc': 0.32.4 + '@cosmjs/math': 0.32.4 + '@cosmjs/socket': 0.32.4(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@cosmjs/stream': 0.32.4 + '@cosmjs/utils': 0.32.4 + axios: 1.7.9(debug@4.4.0) + readonly-date: 1.0.0 + xstream: 11.14.0 + transitivePeerDependencies: + - bufferutil + - debug + - utf-8-validate + + '@cosmjs/utils@0.29.5': {} + + '@cosmjs/utils@0.32.4': {} + + '@cosmology/lcd@0.13.5': + dependencies: + axios: 1.7.4 + transitivePeerDependencies: + - debug + '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 @@ -27276,6 +27789,12 @@ snapshots: '@types/node': 20.17.9 '@types/responselike': 1.0.3 + '@types/chai-subset@1.3.5': + dependencies: + '@types/chai': 4.3.20 + + '@types/chai@4.3.20': {} + '@types/chrome@0.0.278': dependencies: '@types/filesystem': 0.0.36 @@ -27559,6 +28078,8 @@ snapshots: '@types/lodash@4.17.13': {} + '@types/long@4.0.2': {} + '@types/lru-cache@5.1.1': {} '@types/mdast@4.0.4': @@ -27992,6 +28513,12 @@ snapshots: typescript: 5.6.3 vitest: 2.1.5(@types/node@22.10.2)(jsdom@25.0.1(bufferutil@4.0.8)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) + '@vitest/expect@0.34.6': + dependencies: + '@vitest/spy': 0.34.6 + '@vitest/utils': 0.34.6 + chai: 4.5.0 + '@vitest/expect@2.1.4': dependencies: '@vitest/spy': 2.1.4 @@ -28034,6 +28561,12 @@ snapshots: dependencies: tinyrainbow: 1.2.0 + '@vitest/runner@0.34.6': + dependencies: + '@vitest/utils': 0.34.6 + p-limit: 4.0.0 + pathe: 1.1.2 + '@vitest/runner@2.1.4': dependencies: '@vitest/utils': 2.1.4 @@ -28044,6 +28577,12 @@ snapshots: '@vitest/utils': 2.1.5 pathe: 1.1.2 + '@vitest/snapshot@0.34.6': + dependencies: + magic-string: 0.30.17 + pathe: 1.1.2 + pretty-format: 29.7.0 + '@vitest/snapshot@2.1.4': dependencies: '@vitest/pretty-format': 2.1.4 @@ -28056,6 +28595,10 @@ snapshots: magic-string: 0.30.17 pathe: 1.1.2 + '@vitest/spy@0.34.6': + dependencies: + tinyspy: 2.2.1 + '@vitest/spy@2.1.4': dependencies: tinyspy: 3.0.2 @@ -28064,6 +28607,12 @@ snapshots: dependencies: tinyspy: 3.0.2 + '@vitest/utils@0.34.6': + dependencies: + diff-sequences: 29.6.3 + loupe: 2.3.7 + pretty-format: 29.7.0 + '@vitest/utils@2.1.4': dependencies: '@vitest/pretty-format': 2.1.4 @@ -28908,6 +29457,8 @@ snapshots: object.assign: 4.1.5 util: 0.10.4 + assertion-error@1.1.0: {} + assertion-error@2.0.1: {} ast-types@0.13.4: @@ -29819,6 +30370,16 @@ snapshots: ccount@2.0.1: {} + chai@4.5.0: + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.3 + deep-eql: 4.1.4 + get-func-name: 2.0.2 + loupe: 2.3.7 + pathval: 1.1.1 + type-detect: 4.1.0 + chai@5.1.2: dependencies: assertion-error: 2.0.1 @@ -29827,6 +30388,10 @@ snapshots: loupe: 3.1.2 pathval: 2.0.0 + chain-registry@1.69.71: + dependencies: + '@chain-registry/types': 0.50.41 + chalk@1.1.3: dependencies: ansi-styles: 2.2.1 @@ -29866,6 +30431,10 @@ snapshots: charm@0.1.2: {} + check-error@1.0.3: + dependencies: + get-func-name: 2.0.2 + check-error@2.1.1: {} cheerio-select@2.1.0: @@ -30451,6 +31020,31 @@ snapshots: optionalDependencies: typescript: 5.6.3 + cosmjs-types@0.5.1: + dependencies: + long: 4.0.0 + protobufjs: 6.11.4 + + cosmjs-types@0.5.2: + dependencies: + long: 4.0.0 + protobufjs: 6.11.4 + + cosmjs-types@0.9.0: {} + + cosmjs-utils@0.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10): + dependencies: + '@babel/runtime': 7.26.0 + '@cosmjs/amino': 0.29.0 + '@cosmjs/crypto': 0.29.0 + '@cosmjs/proto-signing': 0.29.0 + '@cosmjs/stargate': 0.29.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + cosmjs-types: 0.5.1 + transitivePeerDependencies: + - bufferutil + - debug + - utf-8-validate + crc-32@1.2.2: {} create-ecdh@4.0.4: @@ -31106,6 +31700,10 @@ snapshots: dedent@1.5.3: {} + deep-eql@4.1.4: + dependencies: + type-detect: 4.1.0 + deep-eql@5.0.2: {} deep-extend@0.6.0: {} @@ -32735,6 +33333,8 @@ snapshots: get-east-asian-width@1.3.0: {} + get-func-name@2.0.2: {} + get-intrinsic@1.2.6: dependencies: call-bind-apply-helpers: 1.0.1 @@ -35217,6 +35817,12 @@ snapshots: transitivePeerDependencies: - supports-color + libsodium-sumo@0.7.15: {} + + libsodium-wrappers-sumo@0.7.15: + dependencies: + libsodium-sumo: 0.7.15 + libsodium-wrappers@0.7.15: dependencies: libsodium: 0.7.15 @@ -35342,6 +35948,8 @@ snapshots: loader-utils@3.3.1: {} + local-pkg@0.4.3: {} + local-pkg@0.5.1: dependencies: mlly: 1.7.3 @@ -35448,6 +36056,8 @@ snapshots: strip-ansi: 7.1.0 wrap-ansi: 9.0.0 + long@4.0.0: {} + long@5.2.3: {} longest-streak@3.1.0: {} @@ -35458,6 +36068,10 @@ snapshots: lossless-json@4.0.2: {} + loupe@2.3.7: + dependencies: + get-func-name: 2.0.2 + loupe@3.1.2: {} lowdb@7.0.1: @@ -37183,6 +37797,18 @@ snapshots: os-tmpdir@1.0.2: {} + osmojs@16.15.0(bufferutil@4.0.8)(utf-8-validate@5.0.10): + dependencies: + '@cosmjs/amino': 0.32.3 + '@cosmjs/proto-signing': 0.32.3 + '@cosmjs/stargate': 0.32.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@cosmjs/tendermint-rpc': 0.32.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@cosmology/lcd': 0.13.5 + transitivePeerDependencies: + - bufferutil + - debug + - utf-8-validate + otpauth@9.3.6: dependencies: '@noble/hashes': 1.6.1 @@ -37495,6 +38121,8 @@ snapshots: pathe@1.1.2: {} + pathval@1.1.1: {} + pathval@2.0.0: {} pbkdf2@3.1.2: @@ -38525,6 +39153,22 @@ snapshots: proto-list@1.2.4: {} + protobufjs@6.11.4: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/long': 4.0.2 + '@types/node': 20.17.9 + long: 4.0.0 + protobufjs@7.4.0: dependencies: '@protobufjs/aspromise': 1.1.2 @@ -39040,6 +39684,8 @@ snapshots: readline@1.3.0: {} + readonly-date@1.0.0: {} + real-require@0.1.0: {} rechoir@0.6.2: @@ -40291,6 +40937,10 @@ snapshots: strip-json-comments@3.1.1: {} + strip-literal@1.3.0: + dependencies: + acorn: 8.14.0 + strnum@1.0.5: {} strong-log-transformer@2.1.0: @@ -40402,6 +41052,8 @@ snapshots: dependencies: vue: 3.5.13(typescript@5.6.3) + symbol-observable@2.0.3: {} + symbol-tree@3.2.4: {} symbol.inspect@1.0.1: {} @@ -40619,12 +41271,16 @@ snapshots: tinyld@1.3.4: {} + tinypool@0.7.0: {} + tinypool@1.0.2: {} tinyrainbow@1.2.0: {} tinyspawn@1.3.3: {} + tinyspy@2.2.1: {} + tinyspy@3.0.2: {} tldts-core@6.1.68: {} @@ -40967,6 +41623,8 @@ snapshots: type-detect@4.0.8: {} + type-detect@4.1.0: {} + type-fest@0.18.1: {} type-fest@0.20.2: {} @@ -41545,6 +42203,25 @@ snapshots: - utf-8-validate - zod + vite-node@0.34.6(@types/node@20.17.9)(terser@5.37.0): + dependencies: + cac: 6.7.14 + debug: 4.4.0(supports-color@8.1.1) + mlly: 1.7.3 + pathe: 1.1.2 + picocolors: 1.1.1 + vite: 5.4.11(@types/node@20.17.9)(terser@5.37.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vite-node@2.1.4(@types/node@22.10.2)(terser@5.37.0): dependencies: cac: 6.7.14 @@ -41612,6 +42289,16 @@ snapshots: dependencies: vite: link:client/@tanstack/router-plugin/vite + vite@5.4.11(@types/node@20.17.9)(terser@5.37.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.4.49 + rollup: 4.28.1 + optionalDependencies: + '@types/node': 20.17.9 + fsevents: 2.3.3 + terser: 5.37.0 + vite@5.4.11(@types/node@22.10.2)(terser@5.37.0): dependencies: esbuild: 0.21.5 @@ -41632,6 +42319,45 @@ snapshots: fsevents: 2.3.3 terser: 5.37.0 + vitest@0.34.6(jsdom@25.0.1(bufferutil@4.0.8)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(playwright@1.48.2)(terser@5.37.0): + dependencies: + '@types/chai': 4.3.20 + '@types/chai-subset': 1.3.5 + '@types/node': 20.17.9 + '@vitest/expect': 0.34.6 + '@vitest/runner': 0.34.6 + '@vitest/snapshot': 0.34.6 + '@vitest/spy': 0.34.6 + '@vitest/utils': 0.34.6 + acorn: 8.14.0 + acorn-walk: 8.3.4 + cac: 6.7.14 + chai: 4.5.0 + debug: 4.4.0(supports-color@8.1.1) + local-pkg: 0.4.3 + magic-string: 0.30.17 + pathe: 1.1.2 + picocolors: 1.1.1 + std-env: 3.8.0 + strip-literal: 1.3.0 + tinybench: 2.9.0 + tinypool: 0.7.0 + vite: 5.4.11(@types/node@20.17.9)(terser@5.37.0) + vite-node: 0.34.6(@types/node@20.17.9)(terser@5.37.0) + why-is-node-running: 2.3.0 + optionalDependencies: + jsdom: 25.0.1(bufferutil@4.0.8)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10) + playwright: 1.48.2 + transitivePeerDependencies: + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vitest@2.1.4(@types/node@22.10.2)(jsdom@25.0.1(bufferutil@4.0.8)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0): dependencies: '@vitest/expect': 2.1.4 @@ -42438,6 +43164,11 @@ snapshots: xmlchars@2.2.0: {} + xstream@11.14.0: + dependencies: + globalthis: 1.0.4 + symbol-observable: 2.0.3 + xtend@4.0.2: {} y18n@4.0.3: {} From 2437b94761840d115cbca995da25dc1cb7f627e9 Mon Sep 17 00:00:00 2001 From: anilcse Date: Mon, 23 Dec 2024 13:09:18 +0530 Subject: [PATCH 2/4] Fix lint --- .../plugin-cosmos/src/actions/transfer.ts | 140 ++++++++++++------ .../plugin-cosmos/src/providers/wallet.ts | 5 +- .../plugin-cosmos/src/tests/wallet.test.ts | 4 +- 3 files changed, 98 insertions(+), 51 deletions(-) diff --git a/packages/plugin-cosmos/src/actions/transfer.ts b/packages/plugin-cosmos/src/actions/transfer.ts index accb42e815..554cf9ea29 100644 --- a/packages/plugin-cosmos/src/actions/transfer.ts +++ b/packages/plugin-cosmos/src/actions/transfer.ts @@ -10,17 +10,15 @@ import { composeContext, generateObject, } from "@ai16z/eliza"; - import { chains } from 'chain-registry'; - import { - getOfflineSignerProto as getOfflineSigner - } from "cosmjs-utils"; + import { chains } from "chain-registry"; + import { getOfflineSignerProto as getOfflineSigner } from "cosmjs-utils"; import { SigningStargateClient } from "@cosmjs/stargate"; import { coins, StdFee } from "@cosmjs/amino"; export interface TransferContent extends Content { recipient: string; amount: string | number; - tokenAddress?: string; // optional if we want to handle cw20 or other tokens + tokenAddress?: string; // optional for cw20 tokens } function isTransferContent( @@ -58,54 +56,109 @@ import { Respond with a JSON markdown block containing only the extracted values. `; + /** + * Quickly fetches /status on an RPC endpoint. + * Returns true if the call succeeds (2xx), false otherwise. + */ + async function canGetStatus(rpcUrl: string): Promise { + try { + const url = rpcUrl.endsWith("/") ? rpcUrl + "status" : `${rpcUrl}/status`; + const response = await fetch(url, { method: "GET" }); + if (!response.ok) { + throw new Error(`RPC /status responded with HTTP ${response.status}`); + } + // Optionally parse or check contents here if needed + return true; + } catch { + return false; + } + } + + /** + * Iterates over a list of possible RPC endpoints and + * returns the first one that responds successfully to /status. + */ + async function getWorkingRpcUrl(rpcUrls: string[]): Promise { + for (const url of rpcUrls) { + if (await canGetStatus(url)) { + return url; + } + } + return null; + } + async function transferTokens( runtime: IAgentRuntime, recipient: string, amount: string ): Promise { - // In a real scenario, fetch from environment or runtime settings - const chainName = runtime.getSetting("COSMOS_CHAIN_NAME") || "osmosis"; - const rpc = runtime.getSetting("COSMOS_RPC_URL") || "https://rpc.osmosis.zone"; - const denom = runtime.getSetting("COSMOS_DENOM") || "uosmo"; - const decimals = Number(runtime.getSetting("COSMOS_DECIMALS") || 6); + // 1) Identify chain + mnemonic + const chainName = runtime.getSetting("COSMOS_CHAIN_NAME"); + if (!chainName) throw new Error("COSMOS_CHAIN_NAME not configured"); const mnemonic = runtime.getSetting("COSMOS_MNEMONIC"); - if (!mnemonic) { - throw new Error("COSMOS_MNEMONIC not configured"); + if (!mnemonic) throw new Error("COSMOS_MNEMONIC not configured"); + + // 2) Lookup chain in registry + const chain = chains.find((c) => c.chain_name === chainName); + if (!chain) { + throw new Error(`Chain '${chainName}' not found in chain-registry`); } - const chain = chains.find(({ chain_name }) => chain_name === chainName); + // 3) Gather candidate RPC endpoints (plus a fallback) + const registryRpcs = chain.apis?.rpc?.map((r) => r.address) ?? []; - // get signer - const signer = await getOfflineSigner({ - mnemonic, - chain, - }); + const workingRpc = await getWorkingRpcUrl(registryRpcs); + if (!workingRpc) { + throw new Error(`No working RPC endpoint found for '${chainName}'`); + } - // connect - const stargateClient = await SigningStargateClient.connectWithSigner(rpc, signer); + // 4) Prepare denom/decimals from chain or env + const chainFees = chain.fees?.fee_tokens?.[0]; + const defaultDenom = chainFees?.denom || "uosmo"; + const denom = runtime.getSetting("COSMOS_DENOM") || defaultDenom; + const decimals = Number(runtime.getSetting("COSMOS_DECIMALS") || 6); + const averageGasPrice = chainFees?.average_gas_price ?? 0.025; // base denom + + // 5) Create signer + connect + const signer = await getOfflineSigner({ mnemonic, chain }); + const stargateClient = await SigningStargateClient.connectWithSigner( + workingRpc, + signer + ); + // 6) Build the transaction const [fromAccount] = await signer.getAccounts(); const fromAddress = fromAccount.address; - - // Convert input amount (like 1.5) to base denom - // E.g., 1.5 OSMO => 1500000 uosmo (if 6 decimals) - const shift = Math.pow(10, decimals); + const shift = 10 ** decimals; const sendAmount = String(Math.floor(Number(amount) * shift)); - // Create send message - // If needed, you can also specify a custom fee, otherwise it uses auto. - // For demonstration: + const msg = { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: { + fromAddress, + toAddress: recipient, + amount: coins(sendAmount, denom), + }, + }; + const messages = [msg]; + const memo = ""; + + // 7) Estimate gas usage + compute fee + const gasEstimated = await stargateClient.simulate(fromAddress, messages, memo); + const feeAmount = Math.floor(gasEstimated * averageGasPrice).toString(); + const fee: StdFee = { - amount: coins("2000", denom), // minimal fee - gas: "200000", + amount: coins(feeAmount, denom), + gas: gasEstimated.toString(), }; - const result = await stargateClient.sendTokens( + // 8) Sign & broadcast + const result = await stargateClient.signAndBroadcast( fromAddress, - recipient, - coins(sendAmount, denom), - fee + messages, + fee, + memo ); return result.transactionHash; @@ -115,7 +168,6 @@ import { name: "SEND_COSMOS", similes: ["TRANSFER_COSMOS", "SEND_TOKENS", "TRANSFER_TOKENS", "PAY_COSMOS"], validate: async (_runtime: IAgentRuntime, _message: Memory) => { - // Add your validation logic return true; }, description: "Transfer native Cosmos tokens to another address", @@ -126,27 +178,27 @@ import { _options: { [key: string]: unknown }, callback?: HandlerCallback ): Promise => { - // Initialize or update state + // 1) Ensure state is up-to-date if (!state) { state = (await runtime.composeState(message)) as State; } else { state = await runtime.updateRecentMessageState(state); } - // Compose context + // 2) Compose context for LLM extraction const transferContext = composeContext({ state, template: transferTemplate, }); - // Generate content + // 3) Extract the JSON object with recipient + amount const content = await generateObject({ runtime, context: transferContext, modelClass: ModelClass.SMALL, }); - // Validate + // 4) Validate content if (!isTransferContent(runtime, content)) { console.error("Invalid content for SEND_COSMOS action."); if (callback) { @@ -159,12 +211,10 @@ import { } try { - const txHash = await transferTokens( - runtime, - content.recipient, - content.amount.toString() - ); + // 5) Execute the transfer + const txHash = await transferTokens(runtime, content.recipient, content.amount.toString()); + // 6) Callback success if (callback) { callback({ text: `Successfully transferred ${content.amount} tokens to ${content.recipient}\nTransaction: ${txHash}`, @@ -176,20 +226,20 @@ import { }, }); } - return true; } catch (error) { console.error("Error during Cosmos transfer:", error); if (callback) { callback({ text: `Error transferring tokens: ${error}`, - content: { error: error }, + content: { error }, }); } return false; } }, + // Example usage examples: [ [ { diff --git a/packages/plugin-cosmos/src/providers/wallet.ts b/packages/plugin-cosmos/src/providers/wallet.ts index 651e45fdac..8dfef5059b 100644 --- a/packages/plugin-cosmos/src/providers/wallet.ts +++ b/packages/plugin-cosmos/src/providers/wallet.ts @@ -12,7 +12,6 @@ import { getOfflineSignerProto as getOfflineSigner, } from "cosmjs-utils"; import { SigningStargateClient } from "@cosmjs/stargate"; - import { coins, StdFee } from "@cosmjs/amino"; /** * Example chain registry interface (trimmed down). @@ -133,7 +132,7 @@ import { /** * Connects and returns the SigningStargateClient instance */ - public async connect(runtime: IAgentRuntime): Promise { + public async connect(_runtime: IAgentRuntime): Promise { if (this.stargateClient) return this.stargateClient; if (!this.mnemonic) { @@ -229,7 +228,7 @@ import { * Example token price fetcher for demonstration. * In production, you might fetch from Coingecko or similar. */ - private async fetchTokenPrice(runtime: IAgentRuntime): Promise { + private async fetchTokenPrice(_runtime: IAgentRuntime): Promise { const cacheKey = `price-${this.chainInfo.chain_name}`; const cachedPrice = this.cache.get(cacheKey); diff --git a/packages/plugin-cosmos/src/tests/wallet.test.ts b/packages/plugin-cosmos/src/tests/wallet.test.ts index 2d1a5584c9..302c62432f 100644 --- a/packages/plugin-cosmos/src/tests/wallet.test.ts +++ b/packages/plugin-cosmos/src/tests/wallet.test.ts @@ -1,9 +1,7 @@ import { describe, it, expect, beforeEach, vi, afterEach } from "vitest"; import { WalletProvider } from "../providers/wallet"; import { defaultCharacter } from "@ai16z/eliza"; -import { assets, chains } from 'chain-registry/testnet'; - -import BigNumber from "bignumber.js"; +import { chains } from 'chain-registry/testnet'; // Mock NodeCache so we don't actually cache or cause side effects vi.mock("node-cache", () => { From 8d0eeef08df8bd1a05a9e4f0b1238877d908817a Mon Sep 17 00:00:00 2001 From: anilcse Date: Mon, 23 Dec 2024 15:17:52 +0530 Subject: [PATCH 3/4] Add local chain config for cosmos chains --- .env.example | 6 +- .../plugin-cosmos/src/actions/transfer.ts | 88 +++-- .../plugin-cosmos/src/providers/wallet.ts | 321 +++++++++--------- .../plugin-cosmos/src/tests/wallet.test.ts | 165 ++++++--- 4 files changed, 336 insertions(+), 244 deletions(-) diff --git a/.env.example b/.env.example index 0c2b22390e..13a05dc665 100644 --- a/.env.example +++ b/.env.example @@ -338,4 +338,8 @@ PINATA_JWT= # Pinata JWT for uploading files to IPFS # Cosmos based networks COSMOS_MNEMONIC= # Mnemonic to generate cosmos accounts -COSMOS_CHAIN_NAME= # chainName matching with chain-registry entries here: https://github.com/cosmos/chain-registry \ No newline at end of file +COSMOS_CHAIN_NAME= # chainName matching with chain-registry entries here: https://github.com/cosmos/chain-registry +COSMOS_RPC_URL= # (optional) rpc url for the chain. ex: https://rpc.osmosis.zone:443 +COSMOS_CHAIN_DENOM= # (optional) the base token denom +COSMOS_CHAIN_DECIMALS=6 # (optional) the decimals for token actions. default is 6 +COSMOS_COINGECKO_ID=osmosis # the coingecko id of the token \ No newline at end of file diff --git a/packages/plugin-cosmos/src/actions/transfer.ts b/packages/plugin-cosmos/src/actions/transfer.ts index 554cf9ea29..5620beee05 100644 --- a/packages/plugin-cosmos/src/actions/transfer.ts +++ b/packages/plugin-cosmos/src/actions/transfer.ts @@ -18,7 +18,7 @@ import { export interface TransferContent extends Content { recipient: string; amount: string | number; - tokenAddress?: string; // optional for cw20 tokens + tokenAddress?: string; // optional if we want to handle cw20 or other tokens } function isTransferContent( @@ -57,8 +57,8 @@ import { `; /** - * Quickly fetches /status on an RPC endpoint. - * Returns true if the call succeeds (2xx), false otherwise. + * Quickly checks if an RPC endpoint is reachable by fetching /status. + * Return true if ok, false if not. */ async function canGetStatus(rpcUrl: string): Promise { try { @@ -67,17 +67,12 @@ import { if (!response.ok) { throw new Error(`RPC /status responded with HTTP ${response.status}`); } - // Optionally parse or check contents here if needed return true; } catch { return false; } } - /** - * Iterates over a list of possible RPC endpoints and - * returns the first one that responds successfully to /status. - */ async function getWorkingRpcUrl(rpcUrls: string[]): Promise { for (const url of rpcUrls) { if (await canGetStatus(url)) { @@ -87,17 +82,20 @@ import { return null; } + /** + * Transfer tokens, preferring env-based RPC/DENOM/DECIMALS, else chain-registry. + */ async function transferTokens( runtime: IAgentRuntime, recipient: string, amount: string ): Promise { // 1) Identify chain + mnemonic - const chainName = runtime.getSetting("COSMOS_CHAIN_NAME"); - if (!chainName) throw new Error("COSMOS_CHAIN_NAME not configured"); - + const chainName = runtime.getSetting("COSMOS_CHAIN_NAME") || "osmosis"; const mnemonic = runtime.getSetting("COSMOS_MNEMONIC"); - if (!mnemonic) throw new Error("COSMOS_MNEMONIC not configured"); + if (!mnemonic) { + throw new Error("COSMOS_MNEMONIC not configured"); + } // 2) Lookup chain in registry const chain = chains.find((c) => c.chain_name === chainName); @@ -105,29 +103,52 @@ import { throw new Error(`Chain '${chainName}' not found in chain-registry`); } - // 3) Gather candidate RPC endpoints (plus a fallback) + // 3) Build a candidate RPC list + // First, check env-based RPC + const candidateRpcs: string[] = []; + const envRpc = runtime.getSetting("COSMOS_RPC_URL"); + if (envRpc) { + candidateRpcs.push(envRpc); + } + // Then add chain-registry RPC endpoints const registryRpcs = chain.apis?.rpc?.map((r) => r.address) ?? []; + candidateRpcs.push(...registryRpcs); + // Fallback if all else fails + candidateRpcs.push("https://rpc.osmosis.zone"); - const workingRpc = await getWorkingRpcUrl(registryRpcs); + // 4) Find a working RPC by checking /status + const workingRpc = await getWorkingRpcUrl(candidateRpcs); if (!workingRpc) { throw new Error(`No working RPC endpoint found for '${chainName}'`); } - // 4) Prepare denom/decimals from chain or env + // 5) Determine denom & decimals + // - If env is set, prefer that + // - else fallback to chain.fees const chainFees = chain.fees?.fee_tokens?.[0]; + const envDenom = runtime.getSetting("COSMOS_DENOM"); + const envDecimals = runtime.getSetting("COSMOS_DECIMALS"); + const defaultDenom = chainFees?.denom || "uosmo"; - const denom = runtime.getSetting("COSMOS_DENOM") || defaultDenom; - const decimals = Number(runtime.getSetting("COSMOS_DECIMALS") || 6); - const averageGasPrice = chainFees?.average_gas_price ?? 0.025; // base denom + const denom = envDenom || defaultDenom; + const decimals = envDecimals ? Number(envDecimals) : 6; // or read from chain data + + // average gas price + const averageGasPrice = chainFees?.average_gas_price ?? 0.025; + + // 6) Create offline signer + const signer = await getOfflineSigner({ + mnemonic, + chain, + }); - // 5) Create signer + connect - const signer = await getOfflineSigner({ mnemonic, chain }); + // 7) Connect Stargate client w/ signer const stargateClient = await SigningStargateClient.connectWithSigner( workingRpc, signer ); - // 6) Build the transaction + // 8) Build the transaction const [fromAccount] = await signer.getAccounts(); const fromAddress = fromAccount.address; const shift = 10 ** decimals; @@ -144,7 +165,7 @@ import { const messages = [msg]; const memo = ""; - // 7) Estimate gas usage + compute fee + // 9) Estimate gas usage const gasEstimated = await stargateClient.simulate(fromAddress, messages, memo); const feeAmount = Math.floor(gasEstimated * averageGasPrice).toString(); @@ -153,7 +174,7 @@ import { gas: gasEstimated.toString(), }; - // 8) Sign & broadcast + // 10) Sign & broadcast const result = await stargateClient.signAndBroadcast( fromAddress, messages, @@ -168,6 +189,7 @@ import { name: "SEND_COSMOS", similes: ["TRANSFER_COSMOS", "SEND_TOKENS", "TRANSFER_TOKENS", "PAY_COSMOS"], validate: async (_runtime: IAgentRuntime, _message: Memory) => { + // Add your validation logic if needed return true; }, description: "Transfer native Cosmos tokens to another address", @@ -178,27 +200,27 @@ import { _options: { [key: string]: unknown }, callback?: HandlerCallback ): Promise => { - // 1) Ensure state is up-to-date + // 1) Ensure up-to-date state if (!state) { state = (await runtime.composeState(message)) as State; } else { state = await runtime.updateRecentMessageState(state); } - // 2) Compose context for LLM extraction + // 2) Compose transfer context const transferContext = composeContext({ state, template: transferTemplate, }); - // 3) Extract the JSON object with recipient + amount + // 3) Generate JSON from user conversation const content = await generateObject({ runtime, context: transferContext, modelClass: ModelClass.SMALL, }); - // 4) Validate content + // 4) Validate if (!isTransferContent(runtime, content)) { console.error("Invalid content for SEND_COSMOS action."); if (callback) { @@ -211,10 +233,14 @@ import { } try { - // 5) Execute the transfer - const txHash = await transferTokens(runtime, content.recipient, content.amount.toString()); + // 5) Transfer + const txHash = await transferTokens( + runtime, + content.recipient, + content.amount.toString() + ); - // 6) Callback success + // 6) If successful if (callback) { callback({ text: `Successfully transferred ${content.amount} tokens to ${content.recipient}\nTransaction: ${txHash}`, @@ -239,7 +265,7 @@ import { } }, - // Example usage + // 7) Example usage examples: [ [ { diff --git a/packages/plugin-cosmos/src/providers/wallet.ts b/packages/plugin-cosmos/src/providers/wallet.ts index 8dfef5059b..e03184f3ab 100644 --- a/packages/plugin-cosmos/src/providers/wallet.ts +++ b/packages/plugin-cosmos/src/providers/wallet.ts @@ -4,86 +4,35 @@ import { Provider, State } from "@ai16z/eliza"; + import { chains } from "chain-registry"; import BigNumber from "bignumber.js"; import NodeCache from "node-cache"; - import { chains } from "chain-registry"; - - import { - getOfflineSignerProto as getOfflineSigner, - } from "cosmjs-utils"; + import { getOfflineSignerProto as getOfflineSigner } from "cosmjs-utils"; import { SigningStargateClient } from "@cosmjs/stargate"; /** - * Example chain registry interface (trimmed down). - * You can mark fields as optional if some chains might omit them. + * Minimal CosmosChainInfo shape for demonstration. + * Extend as needed to match your usage. */ interface CosmosChainInfo { chain_name: string; - status: string; - network_type: string; - website: string; - pretty_name: string; - chain_type: string; - chain_id: string; + denom?: string; + decimals?: number; + coingecko_id: string; bech32_prefix: string; - daemon_name: string; - node_home: string; - key_algos: string[]; - slip44: number; - fees: { - fee_tokens: Array<{ - denom: string; - fixed_min_gas_price: number; - low_gas_price: number; - average_gas_price: number; - high_gas_price: number; - }>; + apis?: { + rpc?: Array<{ address: string }>; }; - staking: { - staking_tokens: Array<{ + fees?: { + fee_tokens?: Array<{ denom: string; - }>; - lock_duration: { - time: string; - }; - }; - images: Array<{ - image_sync: { - chain_name: string; - base_denom: string; - }; - svg: string; - png: string; - theme: { - primary_color_hex: string; - }; - }>; - description: string; - apis: { - rpc: Array<{ - address: string; - provider: string; - }>; - rest: Array<{ - address: string; - provider: string; - }>; - grpc: Array<{ - address: string; - provider: string; + average_gas_price?: number; }>; }; - - /** - * Custom convenience fields we add ourselves - * (not in the chain-registry by default) - */ - denom?: string; - decimals?: number; } /** - * Example token interface + * Basic token interface for the portfolio */ export interface CosmosToken { name: string; @@ -95,6 +44,9 @@ import { valueUsd: string; } + /** + * Portfolio interface showing total USD plus an array of tokens + */ interface WalletPortfolio { totalUsd: string; tokens: Array; @@ -107,14 +59,15 @@ import { constructor( private mnemonic: string, - private chainInfo: CosmosChainInfo, + private chainInfo: CosmosChainInfo ) { - // Cache with 5-min TTL - this.cache = new NodeCache({ stdTTL: 300 }); + // console.log("WalletProvider instantiated with chainInfo:", chainInfo); + this.cache = new NodeCache({ stdTTL: 300 }); // 5-min TTL } /** - * Provides the formatted portfolio or fallback message if failing. + * The Eliza framework calls this method to "get" data from the provider. + * Here we simply fetch the user's formatted portfolio. */ async get( runtime: IAgentRuntime, @@ -124,98 +77,109 @@ import { try { return await this.getFormattedPortfolio(runtime); } catch (error) { - console.error("Error in wallet provider:", error); + console.error("Error in wallet provider get:", error); return null; } } /** - * Connects and returns the SigningStargateClient instance + * Connect once, returning a SigningStargateClient */ - public async connect(_runtime: IAgentRuntime): Promise { + public async connect(runtime: IAgentRuntime): Promise { if (this.stargateClient) return this.stargateClient; - if (!this.mnemonic) { throw new Error("Cosmos wallet mnemonic not provided"); } - // We already have our chain registry object as this.chainInfo - // We'll pick the first RPC endpoint from the list: - const rpcUrl = this.chainInfo.apis.rpc[0]?.address; + // Grab the first RPC in chainInfo.apis + const rpcUrl = this.chainInfo.apis?.rpc?.[0]?.address; if (!rpcUrl) { - throw new Error("No RPC endpoint found in chainInfo.apis.rpc"); + throw new Error("No RPC endpoint specified in chainInfo"); } - // Prepare signer const signer = await getOfflineSigner({ mnemonic: this.mnemonic, - chain: this.chainInfo // pass the entire registry object + chain: this.chainInfo, }); - const stargateClient = await SigningStargateClient.connectWithSigner( - rpcUrl, - signer - ); - - // Derive first address from the signer - const [firstAccount] = await signer.getAccounts(); - this.signerAddress = firstAccount.address; + const stargateClient = await SigningStargateClient.connectWithSigner(rpcUrl, signer); + const [account] = await signer.getAccounts(); + this.signerAddress = account.address; this.stargateClient = stargateClient; + console.log("Agent init with signer address: ", this.signerAddress) return stargateClient; } /** - * Retrieves account balance & constructs a single-token portfolio - * (assuming a single base token for demonstration). + * Retrieves balance for a single token, fetches price, calculates portfolio + * with additional checks to handle NaN or invalid values. */ async fetchPortfolioValue(runtime: IAgentRuntime): Promise { const cacheKey = `portfolio-${this.chainInfo.chain_name}`; const cachedValue = this.cache.get(cacheKey); - if (cachedValue) { console.log("Cache hit for fetchPortfolioValue"); return cachedValue; } - // Ensure connected + // Connect if not already const client = await this.connect(runtime); - if (!this.signerAddress) { - throw new Error("Signer address not available. Connect first."); + throw new Error("Signer address not available after connect"); } - // If we added .denom/.decimals to chainInfo: - const baseDenom = this.chainInfo.denom || "uosmo"; - const decimals = this.chainInfo.decimals ?? 6; + // 1) Safely determine denom & decimals + const denom = this.chainInfo.denom || "uosmo"; + + // parse the decimals as an integer + let decimals = parseInt(String(this.chainInfo.decimals), 10); + if (isNaN(decimals) || decimals < 1) { + console.warn( + `Invalid or missing decimals (${this.chainInfo.decimals}), defaulting to 1` + ); + decimals = 1; + } - // Fetch balances + // 2) Fetch all balances from the chain const balances = await client.getAllBalances(this.signerAddress); - const baseTokenBalance = balances.find( - (b) => b.denom === baseDenom - ); + const baseTokenBalance = balances.find((b) => b.denom === denom); + let rawBalance = baseTokenBalance?.amount ?? "0"; + + // 3) Coingecko ID from chainInfo or fallback + const cgID = this.chainInfo.coingecko_id || "osmosis"; - // default to "0" if no balance found - const rawBalance = baseTokenBalance?.amount ?? "0"; + let tokenPriceUsd = await this.fetchTokenPrice(runtime, cgID); - // fetch token price from e.g. Coingecko - const tokenPriceUsd = await this.fetchTokenPrice(runtime) || 0; + // 4) Ensure rawBalance is a valid number + let balanceBN = new BigNumber(rawBalance); + if (!balanceBN.isFinite()) { + console.warn(`Invalid raw balance value: ${rawBalance}, defaulting to 0.`); + balanceBN = new BigNumber(0); + } + + // Also ensure tokenPriceUsd is numeric + if (isNaN(tokenPriceUsd) || !tokenPriceUsd) { + console.warn(`Invalid token price: ${tokenPriceUsd}, defaulting to 0.`); + tokenPriceUsd = 0; + } - // Convert from minimal denom to "1" denom - const convertedBalance = new BigNumber(rawBalance).shiftedBy(-decimals); + // 5) Convert minimal denom -> "1" denom + const convertedBalance = balanceBN.shiftedBy(-decimals); const valueUsd = convertedBalance.multipliedBy(tokenPriceUsd).toFixed(); + // Construct a simple portfolio const portfolio: WalletPortfolio = { totalUsd: valueUsd, tokens: [ { - name: this.chainInfo.pretty_name || "Osmosis", - symbol: "OSMO", // or set dynamically if you have that info + name: this.chainInfo.chain_name ?? "Cosmos Chain", + symbol: denom.toUpperCase(), decimals, - balance: rawBalance, + balance: balanceBN.toFixed(), // store the validated balance uiAmount: convertedBalance.toString(), - priceUsd: tokenPriceUsd.toString(), - valueUsd: valueUsd, + priceUsd: String(tokenPriceUsd), + valueUsd, }, ], }; @@ -225,28 +189,25 @@ import { } /** - * Example token price fetcher for demonstration. - * In production, you might fetch from Coingecko or similar. + * Fetch price from Coingecko (or 0 if fails) */ - private async fetchTokenPrice(_runtime: IAgentRuntime): Promise { - const cacheKey = `price-${this.chainInfo.chain_name}`; + private async fetchTokenPrice(runtime: IAgentRuntime, cgID: string): Promise { + const cacheKey = `price-${cgID}`; const cachedPrice = this.cache.get(cacheKey); - - if (cachedPrice) { + if (cachedPrice !== undefined && cachedPrice) { return cachedPrice; } try { - // For demonstration, we assume OSMO price from Coingecko - // If chain is not OSMO, adapt the ID or fetch from a different endpoint. - const response = await fetch( - "https://api.coingecko.com/api/v3/simple/price?ids=osmosis&vs_currencies=usd" - ); + // For example: fetch OSMO price if cgID = "osmosis" + const url = `https://api.coingecko.com/api/v3/simple/price?ids=${cgID}&vs_currencies=usd`; + const response = await fetch(url); if (!response.ok) { - throw new Error(`Error fetching token price. Status: ${response.status}`); + throw new Error(`Error fetching price for ${cgID}. Status: ${response.status}`); } const data = await response.json(); - const price = data.osmosis?.usd ?? 0; + + const price = data[cgID]?.usd ?? 0; this.cache.set(cacheKey, price); return price; } catch (error) { @@ -256,29 +217,25 @@ import { } /** - * Format portfolio into a text string for display + * Format the portfolio into a text string */ - formatPortfolio( - runtime: IAgentRuntime, - portfolio: WalletPortfolio - ): string { - let output = `${runtime.character.system}\n`; + formatPortfolio(runtime: IAgentRuntime, portfolio: WalletPortfolio): string { + let output = ``; output += `Chain: ${this.chainInfo.chain_name}\n`; if (this.signerAddress) { output += `Account Address: ${this.signerAddress}\n\n`; } - const totalUsdFormatted = new BigNumber(portfolio.totalUsd).toFixed(2); - output += `Total Value: $${totalUsdFormatted}\n\n`; - output += "Token Balances:\n"; + const totalUsd = new BigNumber(portfolio.totalUsd).toFixed(2); + output += `Total Value: $${totalUsd}\n\nToken Balances:\n`; for (const token of portfolio.tokens) { - const tokenValUsd = new BigNumber(token.valueUsd).toFixed(2); - output += `${token.name} (${token.symbol}): ${token.uiAmount} ($${tokenValUsd})\n`; + const valUsd = new BigNumber(token.valueUsd).toFixed(2); + output += `${token.name} (${token.symbol}): ${token.uiAmount} ($${valUsd})\n`; } - output += "\nMarket Prices:\n"; + output += `\nMarket Prices:\n`; for (const token of portfolio.tokens) { const tokenPriceUsd = new BigNumber(token.priceUsd).toFixed(2); output += `${token.symbol}: $${tokenPriceUsd}\n`; @@ -302,49 +259,75 @@ import { } /** - * Single instance of wallet provider + * Single exported provider. + * If COSMOS_RPC_URL is set, we create a local chainInfo. + * Otherwise, we load chainInfo from chain-registry for chain_name. */ const walletProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - _message: Memory, - _state?: State - ): Promise => { + get: async (runtime, message, state) => { try { - // In a real scenario, you'd load these from environment or runtime settings + // 1) Pull settings from environment or .env const mnemonic = runtime.getSetting("COSMOS_MNEMONIC"); - const chainName = runtime.getSetting("COSMOS_CHAIN_NAME") || "osmosis"; - if (!mnemonic) { throw new Error("COSMOS_MNEMONIC not configured"); } + const coingeckoID = runtime.getSetting("COSMOS_COINGECKO_ID") || "osmosis"; + const chainName = runtime.getSetting("COSMOS_CHAIN_NAME") || "osmosis"; - // 1) Look up chain data from chain-registry - const chain = chains.find((c) => c.chain_name === chainName); - if (!chain) { - throw new Error(`Chain '${chainName}' not found in chain-registry`); + // 2) Check if user provided a custom RPC via COSMOS_RPC_URL + const customRpc = runtime.getSetting("COSMOS_RPC_URL"); + if (customRpc) { + // Possibly read denom, decimals, and bech32_prefix from env or use defaults + const customDenom = runtime.getSetting("COSMOS_CHAIN_DENOM") || "uosmo"; + // We'll parse the env decimals as integer, min 1 is enforced inside fetchPortfolioValue + const customDecimals = Number(runtime.getSetting("COSMOS_CHAIN_DECIMALS") || 6); + const bech32Prefix = runtime.getSetting("COSMOS_BECH32_PREFIX") || "osmo"; + + // Example fallback average gas price + const averageGasPrice = 0.025; + + // 2A) Construct a minimal chainInfo object from environment + const localChainInfo: CosmosChainInfo = { + chain_name: chainName, + bech32_prefix: bech32Prefix, + coingecko_id: coingeckoID, + apis: { rpc: [{ address: customRpc }] }, + fees: { + fee_tokens: [ + { + denom: customDenom, + average_gas_price: averageGasPrice + } + ] + }, + denom: customDenom, + decimals: customDecimals + }; + + const provider = new WalletProvider(mnemonic, localChainInfo); + return provider.getFormattedPortfolio(runtime); + } else { + // 2B) Otherwise, load chainInfo from chain-registry + const chainData = chains.find(c => c.chain_name === chainName); + if (!chainData) { + throw new Error(`Chain '${chainName}' not found in chain-registry`); + } + + // Optionally store denom/decimals from chainData or env + const chainDenom = chainData.fees?.fee_tokens?.[0]?.denom || "uosmo"; + // We'll parse from chainData, but min 1 is enforced inside fetchPortfolioValue + const chainDecimals = chainData.decimals || 6; + + chainData.denom = chainDenom; + chainData.decimals = chainDecimals; + + if (!chainData.coingecko_id) { + chainData.coingecko_id = coingeckoID; // fallback + } + + const provider = new WalletProvider(mnemonic, chainData as CosmosChainInfo); + return provider.getFormattedPortfolio(runtime); } - - console.log("chaininfo", chain) - - // Convert chain-registry object to our interface - // (Optional: if you're certain the chain matches the interface, you can cast directly.) - const chainInfo: CosmosChainInfo = { - ...chain, - // We'll grab the first RPC as the "main" RPC: - // chain.apis might be undefined for certain chains, so check - apis: chain.apis || { rpc: [], rest: [], grpc: [] }, - - // For demonstration, pick the first fee_token if available - denom: chain.fees?.fee_tokens?.[0]?.denom || "uosmo", - decimals: 6, // Cosmos is typically 6 - }; - - // 2) Create the wallet provider - const provider = new WalletProvider(mnemonic, chainInfo); - - // 3) Return the formatted portfolio - return await provider.getFormattedPortfolio(runtime); } catch (error) { console.error("Error in wallet provider:", error); return null; diff --git a/packages/plugin-cosmos/src/tests/wallet.test.ts b/packages/plugin-cosmos/src/tests/wallet.test.ts index 302c62432f..1864e49db6 100644 --- a/packages/plugin-cosmos/src/tests/wallet.test.ts +++ b/packages/plugin-cosmos/src/tests/wallet.test.ts @@ -1,9 +1,11 @@ -import { describe, it, expect, beforeEach, vi, afterEach } from "vitest"; -import { WalletProvider } from "../providers/wallet"; +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import { walletProvider } from "../providers/wallet"; import { defaultCharacter } from "@ai16z/eliza"; +import { ModelClass, State } from "@ai16z/eliza"; import { chains } from 'chain-registry/testnet'; +import { executeTransfer } from "../actions/transfer"; -// Mock NodeCache so we don't actually cache or cause side effects +// Mock NodeCache so there's no actual caching side effects vi.mock("node-cache", () => { return { default: vi.fn().mockImplementation(() => ({ @@ -13,7 +15,7 @@ vi.mock("node-cache", () => { }; }); -// Mock path if needed (like in your SUI example): +// Mock path if needed vi.mock("path", async () => { const actual = await vi.importActual("path"); return { @@ -22,39 +24,46 @@ vi.mock("path", async () => { }; }); -// Mock the ICacheManager if you’re using a custom cache manager -const mockCacheManager = { - get: vi.fn().mockResolvedValue(null), - set: vi.fn(), - delete: vi.fn(), -}; +// For demonstration, we mock the entire stargate client so no real network calls +vi.mock("@cosmjs/stargate", () => { + return { + // Partial mock with the classes we need + SigningStargateClient: { + connectWithSigner: vi.fn().mockResolvedValue({ + getAllBalances: vi.fn().mockResolvedValue([ + { denom: "uosmo", amount: "1230000" }, // 1.23 OSMO + ]), + // Example account with minimal data + getSignerAccounts: vi.fn().mockResolvedValue([ + { address: "osmo1mock..." }, + ]), + }), + }, + }; +}); -describe("Cosmos WalletProvider", () => { - let walletProvider: WalletProvider; +// (Optional) If you're testing price fetch from Coingecko, you can also mock fetch +vi.mock(globalThis.fetch ? 'node-fetch' : 'cross-fetch', () => ({ + __esModule: true, + default: vi.fn().mockImplementation(() => ({ + ok: true, + json: () => + Promise.resolve({ + osmosis: { usd: 0.9 }, // Example price for OSMO + }), + })), +})); + +describe("Cosmos WalletProvider (getFormattedPortfolio)", () => { let mockedRuntime: any; + let callbackFn: any; beforeEach(() => { vi.clearAllMocks(); - - // Example mnemonic. DO NOT use real keys in test code! - const mnemonic ="unfold client turtle either pilot stock floor glow toward bullet car science"; - const chainName = "osmosistestnet"; - - const chain = chains.find((c) => c.chain_name === chainName); - if (!chain) { - throw new Error(`Chain '${chainName}' not found in chain-registry`); - } - - // If your wallet provider requires additional chain config, pass it here: - walletProvider = new WalletProvider( - mnemonic, - chain, - mockCacheManager - ); - - // Mock runtime/character + // Default runtime mock mockedRuntime = { character: defaultCharacter, + getSetting: vi.fn(), }; }); @@ -62,23 +71,93 @@ describe("Cosmos WalletProvider", () => { vi.clearAllTimers(); }); - describe("Wallet Integration", () => { - it("should return a formatted portfolio with wallet address", async () => { - // Retrieve the portfolio text - const result = await walletProvider.getFormattedPortfolio(mockedRuntime); + it("uses environment variables for RPC, DENOM, and DECIMALS if set", async () => { + // Set environment-based overrides + mockedRuntime.getSetting.mockImplementation((key: string) => { + switch (key) { + case "COSMOS_MNEMONIC": + return "unfold client turtle either pilot stock floor glow toward bullet car science"; + case "COSMOS_CHAIN_NAME": + return "osmosis"; + case "COSMOS_RPC_URL": + return "https://custom.env.rpc/"; + case "COSMOS_CHAIN_DENOM": + return "uenvdenom"; + case "COSMOS_CHAIN_DECIMALS": + return "4"; + case "COSMOS_BECH32_PREFIX": + return "osmo1mock"; + default: + return undefined; + } + }); + + // Execute the provider + const result = await walletProvider.get(mockedRuntime, {} as any); + + // Should mention chain and account address + expect(result).toContain("Chain: osmosis"); + expect(result).toContain("Account Address: osmo1mock"); + + // Should have "Token Balances:" + expect(result).toContain("Token Balances:"); - // In a real test, you'd parse the string or validate its structure. - // For demonstration, we just check that it includes the chain_name - // and a "Account Address" label or something similar. - expect(result).toContain("osmosis"); - expect(result).toContain("Account Address:"); + // Symbol uppercase from the code => "UENVDENOM" + expect(result).toContain("UENVDENOM"); + + // Should show a total value line (like "Total Value: $1.10" or similar) + // We'll just check the pattern: + expect(result).toMatch(/Total Value: \$[\d,]*\.\d{2}/); + + // Optional: If you want to see the entire result in test logs + // console.log("Portfolio result with env overrides:\n", result); + }); + + it("falls back to chain-registry if env variables are not set", async () => { + // Minimal environment: just mnemonic + chain name + mockedRuntime.getSetting.mockImplementation((key: string) => { + switch (key) { + case "COSMOS_MNEMONIC": + return "unfold client turtle either pilot stock floor glow toward bullet car science"; + case "COSMOS_CHAIN_NAME": + return "osmosistestnet"; + // No COSMOS_RPC_URL, COSMOS_CHAIN_DENOM, COSMOS_CHAIN_DECIMALS + default: + return undefined; + } }); - it("should contain a total value in USD", async () => { - const result = await walletProvider.getFormattedPortfolio(mockedRuntime); + // Confirm chain-registry has 'osmosis' + const chain = chains.find((c) => c.chain_name === "osmosistestnet"); + expect(chain).toBeDefined(); + + // Execute the provider + const result = await walletProvider.get(mockedRuntime, {} as any); + + // Should mention chain and account address + expect(result).toContain("Chain: osmosis"); + expect(result).toContain("Account Address: osmo1"); - // For example, check if it says "Total Value: $" - expect(result).toMatch(/Total Value: \$[\d,]+\.\d{2}/); + // Should have "Token Balances:" + expect(result).toContain("Token Balances:"); + + // In fallback, the code sets denom to "uosmo", thus symbol => "UOSMO" + expect(result).toContain("UOSMO"); + + // Check total value + expect(result).toMatch(/Total Value: \$[\d,]*\.\d{2}/); + }); + + it("returns null if COSMOS_MNEMONIC is not set", async () => { + // We intentionally do not provide a mnemonic + mockedRuntime.getSetting.mockImplementation((key: string) => { + if (key === "COSMOS_CHAIN_NAME") return "osmosis"; + return undefined; }); + + const result = await walletProvider.get(mockedRuntime, {} as any); + + // Should return null since the mnemonic is missing + expect(result).toBeNull(); }); }); From 7b53ffb1c28a887c1f518eb543a34fd15eb59bbd Mon Sep 17 00:00:00 2001 From: Anil Date: Tue, 24 Dec 2024 13:57:18 +0530 Subject: [PATCH 4/4] remove hardcoded rpc --- packages/plugin-cosmos/src/actions/transfer.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/plugin-cosmos/src/actions/transfer.ts b/packages/plugin-cosmos/src/actions/transfer.ts index 5620beee05..0eaa53ec3e 100644 --- a/packages/plugin-cosmos/src/actions/transfer.ts +++ b/packages/plugin-cosmos/src/actions/transfer.ts @@ -113,8 +113,6 @@ import { // Then add chain-registry RPC endpoints const registryRpcs = chain.apis?.rpc?.map((r) => r.address) ?? []; candidateRpcs.push(...registryRpcs); - // Fallback if all else fails - candidateRpcs.push("https://rpc.osmosis.zone"); // 4) Find a working RPC by checking /status const workingRpc = await getWorkingRpcUrl(candidateRpcs);