diff --git a/packages/plugin-cosmos/src/actions/fetch-balance/index.ts b/packages/plugin-cosmos/src/actions/fetch-balance/index.ts new file mode 100644 index 00000000000..894354c09ea --- /dev/null +++ b/packages/plugin-cosmos/src/actions/fetch-balance/index.ts @@ -0,0 +1,181 @@ +import { HandlerCallback, IAgentRuntime, Memory, State } from "@elizaos/core"; +import { initWalletChainsData } from "../../providers/wallet/utils"; +import { cosmosTransferTemplate } from "../../templates"; +import type { + ICosmosPluginOptions, + ICosmosWalletChains, +} from "../../shared/interfaces"; +import { FetchBalancesActionService } from "./services/fetch-balance"; + +export const fetchBalancesAction = (pluginOptions: ICosmosPluginOptions) => ({ + name: "FETCH_BALANCES", + description: "Fetch balances for all connected chains", + handler: async ( + _runtime: IAgentRuntime, + _message: Memory, + state: State, + _options: { [key: string]: unknown }, + _callback?: HandlerCallback + ) => { + try { + const walletProvider: ICosmosWalletChains = + await initWalletChainsData(_runtime); + + const action = new FetchBalancesActionService(walletProvider); + + const transferResp = await action.execute( + pluginOptions?.customChainData + ); + + console.log(transferResp); + + if (_callback) { + await _callback({ + text: `Successfully fetched balances`, + content: { + success: true, + }, + }); + + const newMemory: Memory = { + userId: _message.agentId, + agentId: _message.agentId, + roomId: _message.roomId, + content: { + text: `Successfully fetched balances`, + }, + }; + + await _runtime.messageManager.createMemory(newMemory); + } + return true; + } catch (error) { + console.error("Error during token transfer:", error); + + if (_callback) { + await _callback({ + text: `Error while fetching balance: ${error.message}`, + content: { error: error.message }, + }); + } + + const newMemory: Memory = { + userId: _message.agentId, + agentId: _message.agentId, + roomId: _message.roomId, + content: { + text: `Balance fetch failed`, + }, + }; + + await _runtime.messageManager.createMemory(newMemory); + + return false; + } + }, + template: cosmosTransferTemplate, + validate: async (runtime: IAgentRuntime) => { + const mnemonic = runtime.getSetting("COSMOS_RECOVERY_PHRASE"); + const availableChains = runtime.getSetting("COSMOS_AVAILABLE_CHAINS"); + const availableChainsArray = availableChains?.split(","); + + return !(mnemonic && availableChains && availableChainsArray.length); + }, + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "Fetch balances", + action: "FETCH_BALANCES", + }, + }, + { + user: "{{user2}}", + content: { + text: "Do you confirm the transfer action?", + action: "FETCH_BALANCES", + }, + }, + { + user: "{{user1}}", + content: { + text: "Yes", + action: "FETCH_BALANCES", + }, + }, + { + user: "{{user2}}", + content: { + text: "", + action: "FETCH_BALANCES", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Display my wallet balances", + action: "FETCH_BALANCES", + }, + }, + { + user: "{{user2}}", + content: { + text: "Do you confirm the transfer action?", + action: "FETCH_BALANCES", + }, + }, + { + user: "{{user1}}", + content: { + text: "Yes", + action: "FETCH_BALANCES", + }, + }, + { + user: "{{user2}}", + content: { + text: "", + action: "FETCH_BALANCES", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Show me my wallet balances", + action: "FETCH_BALANCES", + }, + }, + { + user: "{{user2}}", + content: { + text: "Do you confirm the transfer action?", + action: "FETCH_BALANCES", + }, + }, + { + user: "{{user1}}", + content: { + text: "Yes", + action: "FETCH_BALANCES", + }, + }, + { + user: "{{user2}}", + content: { + text: "", + action: "FETCH_BALANCES", + }, + }, + ], + ], + similes: [ + "COSMOS_FETCH_ACCOUNT_BALANCE", + "COSMOS_GET_BALANCE", + "COSMOS_GET_WALLET_BALANCE", + ], +}); diff --git a/packages/plugin-cosmos/src/actions/fetch-balance/services/fetch-balance.ts b/packages/plugin-cosmos/src/actions/fetch-balance/services/fetch-balance.ts new file mode 100644 index 00000000000..8a15c6d6357 --- /dev/null +++ b/packages/plugin-cosmos/src/actions/fetch-balance/services/fetch-balance.ts @@ -0,0 +1,56 @@ +import { getChainByChainName } from "@chain-registry/utils"; +import type { + ICosmosActionService, + ICosmosPluginCustomChainData, + ICosmosWalletChains, +} from "../../../shared/interfaces.ts"; +import { assets, chains } from "chain-registry"; +import { balanceFetcher } from "../../../shared/services/balance-fetcher.ts"; +import { Coin } from "@cosmjs/proto-signing"; + +export class FetchBalancesActionService implements ICosmosActionService { + constructor(private cosmosWalletChains: ICosmosWalletChains) { + this.cosmosWalletChains = cosmosWalletChains; + } + + async execute( + customChains?: ICosmosPluginCustomChainData[] + ): Promise<{ chainName: string; balances: Coin[] }[]> { + const chainRegisteryChainsWithCustomChains = [...chains]; + const chainRegisteryAssetsWithCustomAssets = [...assets]; + + if (customChains) { + chainRegisteryChainsWithCustomChains.push( + ...customChains.map(({ chainData }) => chainData) + ); + chainRegisteryAssetsWithCustomAssets.push( + ...customChains.map(({ assets }) => assets) + ); + } + + const chainsDetails = Object.keys( + this.cosmosWalletChains.walletChainsData + ) + .map((chainName) => getChainByChainName(chains, chainName)) + .filter(Boolean); + + return Promise.all( + chainsDetails.map(async (chainDetails) => { + const addressForGivenChain = + await this.cosmosWalletChains.getWalletAddress( + chainDetails.chain_name + ); + + const balanceForGivenChain = await balanceFetcher( + chainDetails.apis.rpc[0].address, + addressForGivenChain + ); + + return { + chainName: chainDetails.chain_name, + balances: balanceForGivenChain, + }; + }) + ); + } +} diff --git a/packages/plugin-cosmos/src/index.ts b/packages/plugin-cosmos/src/index.ts index 8e3eeb9e276..1d343d6ca13 100644 --- a/packages/plugin-cosmos/src/index.ts +++ b/packages/plugin-cosmos/src/index.ts @@ -2,6 +2,7 @@ import { createTransferAction } from "./actions/transfer"; import type { Plugin } from "@ai16z/eliza"; import { createCosmosWalletProvider } from "./providers/wallet"; import { ICosmosPluginOptions } from "./shared/interfaces"; +import { fetchBalancesAction } from "./actions/fetch-balance"; export const createCosmosPlugin = ( pluginOptions?: ICosmosPluginOptions @@ -11,7 +12,10 @@ export const createCosmosPlugin = ( providers: [createCosmosWalletProvider(pluginOptions)], evaluators: [], services: [], - actions: [createTransferAction(pluginOptions)], + actions: [ + createTransferAction(pluginOptions), + fetchBalancesAction(pluginOptions), + ], }); export default createCosmosPlugin; diff --git a/packages/plugin-cosmos/src/shared/interfaces.ts b/packages/plugin-cosmos/src/shared/interfaces.ts index 13b9b003a37..7ecbdb6702d 100644 --- a/packages/plugin-cosmos/src/shared/interfaces.ts +++ b/packages/plugin-cosmos/src/shared/interfaces.ts @@ -44,3 +44,9 @@ export interface ICosmosWalletChains { export interface ICosmosWalletChainsData { [chainName: string]: ICosmosChainWallet; } + +export interface Coin { + denom: string; + amount: string; + exp?: number; + }; diff --git a/packages/plugin-cosmos/src/shared/services/balance-fetcher.ts b/packages/plugin-cosmos/src/shared/services/balance-fetcher.ts new file mode 100644 index 00000000000..db3d61df939 --- /dev/null +++ b/packages/plugin-cosmos/src/shared/services/balance-fetcher.ts @@ -0,0 +1,14 @@ +import { cosmos } from "interchain"; +import type { Coin } from "@cosmjs/proto-signing"; + +export const balanceFetcher = async (rpcEndpoint: string, address: string) => { + const client = await cosmos.ClientFactory.createRPCQueryClient({ + rpcEndpoint, + }); + + const allBalances = await client.cosmos.bank.v1beta1.allBalances({ + address, + }); + + return allBalances.balances as Coin[]; +}; diff --git a/packages/plugin-cosmos/src/templates/index.ts b/packages/plugin-cosmos/src/templates/index.ts index 44fdf98aa0a..788e73706b8 100644 --- a/packages/plugin-cosmos/src/templates/index.ts +++ b/packages/plugin-cosmos/src/templates/index.ts @@ -37,3 +37,26 @@ Example reponse for the input: "Make transfer 0.0001 OM to mantra1pcnw46km8m5amv Now respond with a JSON markdown block containing only the extracted values. `; + +export const cosmosFetchBalancesTemplate = `Given the recent messages and cosmos wallet information below: +{{recentMessages}} +{{walletInfo}} +Extract the following information about the requested transfer: +1. **Chain name**: + - Identify the chain mentioned in the instruction where the transfer will take place (e.g., carbon, axelar, cosmoshub). + - Provide this as a string. + +Respond with a JSON markdown block containing only the extracted values. All fields except 'token' are required: +\`\`\`json +{ + "chainName": string // The chain name. +\`\`\` + +Example reponse for the input: "Fetch my balance on axelar chain", the response should be: +\`\`\`json +{ + "chainName": "axelar" +\`\`\` + +Now respond with a JSON markdown block containing only the extracted values. +`; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dde34c9e572..aba000fc685 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1206,12 +1206,19 @@ importers: chain-registry: specifier: ^1.69.68 version: 1.69.86 + interchain: + specifier: ^1.10.4 + version: 1.10.4(bufferutil@4.0.9)(utf-8-validate@5.0.10) tsup: specifier: 8.3.5 version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) zod: specifier: 3.23.8 version: 3.23.8 + devDependencies: + '@chain-registry/types': + specifier: ^0.50.44 + version: 0.50.45 packages/plugin-cronoszkevm: dependencies: @@ -3499,6 +3506,7 @@ packages: '@confio/ics23@0.6.8': resolution: {integrity: sha512-wB6uo+3A50m0sW/EWcU64xpV/8wShZ6bMTa7pF8eYsTrSkQA7oLUIJcs/wb8g4y2Oyq701BaGiO6n/ak5WXO1w==} + deprecated: Unmaintained. The codebase for this package was moved to https://github.com/cosmos/ics23 but then the JS implementation was removed in https://github.com/cosmos/ics23/pull/353. Please consult the maintainers of https://github.com/cosmos for further assistance. '@coral-xyz/anchor-errors@0.30.1': resolution: {integrity: sha512-9Mkradf5yS5xiLWrl9WrpjqOrAV+/W2RQHDlbnAZBivoGpOs1ECjoDCkVk4aRG8ZdiFiB8zQEVlxf+8fKkmSfQ==} @@ -3550,6 +3558,9 @@ packages: peerDependencies: '@solana/web3.js': ^1.68.0 + '@cosmjs/amino@0.32.2': + resolution: {integrity: sha512-lcK5RCVm4OfdAooxKcF2+NwaDVVpghOq6o/A40c2mHXDUzUoRZ33VAHjVJ9Me6vOFxshrw/XEFn1f4KObntjYA==} + '@cosmjs/amino@0.32.4': resolution: {integrity: sha512-zKYOt6hPy8obIFtLie/xtygCkH9ZROiQ12UHfKsOkWaZfPQUvVbtgmu6R4Kn1tFLI/SRkw7eqhaogmW/3NYu/Q==} @@ -3568,24 +3579,36 @@ packages: '@cosmjs/math@0.32.4': resolution: {integrity: sha512-++dqq2TJkoB8zsPVYCvrt88oJWsy1vMOuSOKcdlnXuOA/ASheTJuYy4+oZlTQ3Fr8eALDLGGPhJI02W2HyAQaw==} + '@cosmjs/proto-signing@0.32.2': + resolution: {integrity: sha512-UV4WwkE3W3G3s7wwU9rizNcUEz2g0W8jQZS5J6/3fiN0mRPwtPKQ6EinPN9ASqcAJ7/VQH4/9EPOw7d6XQGnqw==} + '@cosmjs/proto-signing@0.32.4': resolution: {integrity: sha512-QdyQDbezvdRI4xxSlyM1rSVBO2st5sqtbEIl3IX03uJ7YiZIQHyv6vaHVf1V4mapusCqguiHJzm4N4gsFdLBbQ==} '@cosmjs/socket@0.32.4': resolution: {integrity: sha512-davcyYziBhkzfXQTu1l5NrpDYv0K9GekZCC9apBRvL1dvMc9F/ygM7iemHjUA+z8tJkxKxrt/YPjJ6XNHzLrkw==} + '@cosmjs/stargate@0.32.2': + resolution: {integrity: sha512-AsJa29fT7Jd4xt9Ai+HMqhyj7UQu7fyYKdXj/8+/9PD74xe6lZSYhQPcitUmMLJ1ckKPgXSk5Dd2LbsQT0IhZg==} + '@cosmjs/stargate@0.32.4': resolution: {integrity: sha512-usj08LxBSsPRq9sbpCeVdyLx2guEcOHfJS9mHGCLCXpdAPEIEQEtWLDpEUc0LEhWOx6+k/ChXTc5NpFkdrtGUQ==} '@cosmjs/stream@0.32.4': resolution: {integrity: sha512-Gih++NYHEiP+oyD4jNEUxU9antoC0pFSg+33Hpp0JlHwH0wXhtD3OOKnzSfDB7OIoEbrzLJUpEjOgpCp5Z+W3A==} + '@cosmjs/tendermint-rpc@0.32.2': + resolution: {integrity: sha512-DXyJHDmcAfCix4H/7/dKR0UMdshP01KxJOXHdHxBCbLIpck94BsWD3B2ZTXwfA6sv98so9wOzhp7qGQa5malxg==} + '@cosmjs/tendermint-rpc@0.32.4': resolution: {integrity: sha512-MWvUUno+4bCb/LmlMIErLypXxy7ckUuzEmpufYYYd9wgbdCXaTaO08SZzyFM5PI8UJ/0S2AmUrgWhldlbxO8mw==} '@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'} @@ -13587,6 +13610,9 @@ packages: int64-buffer@0.1.10: resolution: {integrity: sha512-v7cSY1J8ydZ0GyjUHqF+1bshJ6cnEVLo9EnjB8p+4HDRPZc9N5jjmvUV7NvEsqQOKyH0pmIBFWXVQbiS0+OBbA==} + interchain@1.10.4: + resolution: {integrity: sha512-tyJ3mfcuYqwLb3iZyuXDMOwMjWYptgiZrl6tu50pSSYoWrPN/9B6ztEC4IkYT1oKmWVOAiacNYuSRNmMUuWsmA==} + internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -23137,6 +23163,13 @@ snapshots: bn.js: 5.2.1 buffer-layout: 1.2.2 + '@cosmjs/amino@0.32.2': + dependencies: + '@cosmjs/crypto': 0.32.4 + '@cosmjs/encoding': 0.32.4 + '@cosmjs/math': 0.32.4 + '@cosmjs/utils': 0.32.4 + '@cosmjs/amino@0.32.4': dependencies: '@cosmjs/crypto': 0.32.4 @@ -23186,6 +23219,15 @@ snapshots: dependencies: bn.js: 5.2.1 + '@cosmjs/proto-signing@0.32.2': + dependencies: + '@cosmjs/amino': 0.32.4 + '@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/proto-signing@0.32.4': dependencies: '@cosmjs/amino': 0.32.4 @@ -23205,6 +23247,23 @@ snapshots: - bufferutil - utf-8-validate + '@cosmjs/stargate@0.32.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + dependencies: + '@confio/ics23': 0.6.8 + '@cosmjs/amino': 0.32.4 + '@cosmjs/encoding': 0.32.4 + '@cosmjs/math': 0.32.4 + '@cosmjs/proto-signing': 0.32.4 + '@cosmjs/stream': 0.32.4 + '@cosmjs/tendermint-rpc': 0.32.4(bufferutil@4.0.9)(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/stargate@0.32.4(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: '@confio/ics23': 0.6.8 @@ -23226,6 +23285,23 @@ snapshots: dependencies: xstream: 11.14.0 + '@cosmjs/tendermint-rpc@0.32.2(bufferutil@4.0.9)(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.9)(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/tendermint-rpc@0.32.4(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: '@cosmjs/crypto': 0.32.4 @@ -23245,6 +23321,12 @@ snapshots: '@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 @@ -36319,7 +36401,7 @@ snapshots: extract-zip@2.0.1: dependencies: - debug: 4.3.4 + debug: 4.4.0(supports-color@8.1.1) get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -37906,6 +37988,18 @@ snapshots: int64-buffer@0.1.10: {} + interchain@1.10.4(bufferutil@4.0.9)(utf-8-validate@5.0.10): + dependencies: + '@cosmjs/amino': 0.32.2 + '@cosmjs/proto-signing': 0.32.2 + '@cosmjs/stargate': 0.32.2(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@cosmjs/tendermint-rpc': 0.32.2(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@cosmology/lcd': 0.13.5 + transitivePeerDependencies: + - bufferutil + - debug + - utf-8-validate + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -39827,7 +39921,7 @@ snapshots: md5.js@1.3.5: dependencies: - hash-base: 3.0.5 + hash-base: 3.1.0 inherits: 2.0.4 safe-buffer: 5.2.1