Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugin-evm tests and multichain support #864

Closed
wants to merge 19 commits into from

Conversation

nicky-ru
Copy link
Contributor

@nicky-ru nicky-ru commented Dec 5, 2024

Relates to:

#303

Risks

Low, dependent on viem chains.

Background

What does this PR do?

This PR initiates support for testing the EVM plugin, which was previously unsupported. Added multichain support, the current version of the plugin only supported mainnet and Base, and there have been several requests for additional EVM chain support.

  • The plugin lacked test coverage, so we initiated tests alongside the feature development, starting with WalletProvider.
  • Refactored the plugin to make it more test-friendly and modular.
  • Added multichain support (for viem chains)

What kind of change is this?

  • Improvements: Refactoring, cleaning, and testing of the existing plugin.
  • Features: Non-breaking changes to add EVM multichain support. The previous character configuration even though accepted evm chain config array, but in fact it was not implemented

Why are we doing this? Any context or related work?

Initially, the goal was to add IoTeX EVM chain support. However, the scope was expanded to include general multichain support, given the demand for broader compatibility. Transfers are not yet fully functional, as noted in Issue #735. This will be addressed in a follow-up PR.

Documentation changes needed?

No.
Currently, the project documentation does not cover blockchain configurations, including EVM plugin setup. No changes are required at this time.

Testing

How was it tested

  • Ran test scripts
  • Verified functionality through agent interactions:
Screenshot 2024-12-06 at 2 42 13 AM

Where should a reviewer start?

File: packages/plugin-evm/src/tests/wallet.test.ts

Detailed testing steps

No manual testing is required; automated tests have been implemented and cover the new functionality.

Discord username

nikita_zhou

@0xaguspunk
Copy link
Contributor

Hey @nicky-ru! :)

This is cool!

One thing tho is that transfer, swap and bridge do not work in this plugin, check issue #735

There's also the GOAT plugin which you could add multi-chain support to if you want to take a stab at it. The GOAT library supports all EVM chains (and Solana):

https://github.com/ai16z/eliza/tree/main/packages/plugin-goat

@nicky-ru
Copy link
Contributor Author

nicky-ru commented Dec 5, 2024

Hey @nicky-ru! :)

This is cool!

One thing tho is that transfer, swap and bridge do not work in this plugin, check issue #735

There's also the GOAT plugin which you could add multi-chain support to if you want to take a stab at it. The GOAT library supports all EVM chains (and Solana):

https://github.com/ai16z/eliza/tree/main/packages/plugin-goat

Hey @0xaguspunk!
Thanks for the feedback and for pointing out the GOAT plugin as an alternative!

I noticed the issues with transfer, swap, and bridge functionality (#735) and mentioned in the PR that they will be addressed in a follow-up. This PR focuses on enabling multichain support and making the plugin more test-friendly via TDD + a foundation to extend the plugin with more features.

The GOAT plugin does look interesting, and I’ll take a closer look to see if it aligns with the goals here.

Let me know if you think it’s better to pivot entirely to GOAT, or if continuing with this plugin (and fixing the mentioned issues) is a good path forward!

@0xaguspunk
Copy link
Contributor

Ah got it! Thanks for the context! :) Then it makes sense knowing it will be fixed on another PR.

No, no need to pivot to goat. PR is fine, neat having the tests there too! 🙏

@nicky-ru
Copy link
Contributor Author

nicky-ru commented Dec 6, 2024

Ah got it! Thanks for the context! :) Then it makes sense knowing it will be fixed on another PR.

No, no need to pivot to goat. PR is fine, neat having the tests there too! 🙏

Hi @0xaguspunk ,
Thanks for the feedback and confirmation! 😊 Glad to hear the PR aligns well. 🙏

Just to clarify—are there any additional steps or changes you’d like me to make before the PR is ready for merging? It would be great to have it merged as a base to continue working on improvements and fixes for Issue #735. Let me know if there’s anything else I should address!

Thanks again!

Copy link
Contributor

@0xaguspunk 0xaguspunk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a small comment on RPC config! Otherwise, lgtm! 🙂

this.currentChain = chain;
};
private createHttpTransport = (chain: SupportedChain) => {
return http(this.chains[chain].rpcUrls.default.http[0]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concern with this is that the public RPCs can be quite unreliable. Here I'm not able to override the RPC with one I want to use (for instance, from Alchemy).

Maybe you can infer the RPC from the env variables through the chain name having the env variable be ETHEREUM_PROVIDER_<CHAIN_NAME> (e.g if I have base as a supported chain, I will look for an ETHEREUM_PROVIDER_BASE env variable to set up the RPC)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@0xaguspunk totally agree, let me push some fixes in a bit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@0xaguspunk This has been fixed in commit ee6663e.

You can now configure the mainnet custom RPC URL by setting the EVM_PROVIDER_URL value. For other chains, use the template ETHEREUM_PROVIDER_<CHAIN_NAME>.

Additionally, the current version supports adding custom chains, even if they are not part of viem/chains.

const walletProvider = new WalletProvider(
privateKey as `0x${string}`,
chainNames
);
const address = walletProvider.getAddress();
const balance = await walletProvider.getWalletBalance();
return `EVM Wallet Address: ${address}\nBalance: ${balance} ETH`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'd add the chain you are getting the balance for

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'd add the chain you are getting the balance for

The current behavior defaults to Ethereum mainnet unless another chain is provided, in which case it uses the first chain in the list (see tests on lines 24-29 and 36-40). Next, we plan to add a switch chain action and a separate action to fetch the balance for a specific chain.

Does this approach make sense?

Copy link
Contributor

@0xaguspunk 0xaguspunk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat, lgtm! 🙂

FYI, I'm not a core contributor so you'd be missing the review from one of them 🙌

@odilitime odilitime added the Plugin_new Mark PRs that are a new plugin label Dec 10, 2024
@nicky-ru
Copy link
Contributor Author

Hi @odilitime,

Thanks for visiting the PR! I noticed it was marked as “new plugin,” but just to clarify, this PR is actually an improvement to the existing plugin, not a new one. The changes add multichain support, refactor the code for better testability, and introduce tests that were previously missing.

Additionally, my latest commits address Issue #735 and fix the transfer functionality that was mentioned there as seen on the next screenshot:

Screenshot 2024-12-11 at 3 42 34 AM

Let me know if there’s anything else needed for this PR or if further clarification is required! 😊

Thanks again for your time!

@odilitime odilitime removed the Plugin_new Mark PRs that are a new plugin label Dec 11, 2024
@0xCardinalError
Copy link
Contributor

think this is on good track, using viem chains for chain data, also what was missing for transfer to work (generating object with LLM) is here. I was thinking to do the same, but found out this was already here, so no point in repeating this.

currently I had some problems running this, there was RPC error, not sure why and now both claud and openai are timing out for response so I cant test it, will try it out more as I want to use it for transferring eth/tokens and other EVM things.

Will report back asap

@nicky-ru
Copy link
Contributor Author

nicky-ru commented Dec 11, 2024

think this is on good track, using viem chains for chain data, also what was missing for transfer to work (generating object with LLM) is here. I was thinking to do the same, but found out this was already here, so no point in repeating this.

currently I had some problems running this, there was RPC error, not sure why and now both claud and openai are timing out for response so I cant test it, will try it out more as I want to use it for transferring eth/tokens and other EVM things.

Will report back asap

Thanks for your feedback! Yeah the rpc url is a headache if the fromChain is not in viem/chain list. I've added a console log for transfer for better debugging. Also since ethereum mainnet is just "mainnet" in viem/chains most probably the issue originated from that, also pushed a fix for the template.

We'll need to make it more robust in the next iteration 😁

@nicky-ru nicky-ru force-pushed the plugin-evm-multichain branch from 1cf47be to 8c15447 Compare December 11, 2024 18:17
@0xCardinalError
Copy link
Contributor

think this is on good track, using viem chains for chain data, also what was missing for transfer to work (generating object with LLM) is here. I was thinking to do the same, but found out this was already here, so no point in repeating this.
currently I had some problems running this, there was RPC error, not sure why and now both claud and openai are timing out for response so I cant test it, will try it out more as I want to use it for transferring eth/tokens and other EVM things.
Will report back asap

Thanks for your feedback! Yeah the rpc url is a headache if the fromChain is not in viem/chain list. I've added a console log for transfer for better debugging. Also since ethereum mainnet is just "mainnet" in viem/chains most probably the issue originated from that, also pushed a fix for the template.

We'll need to make it more robust in the next iteration 😁

having 504 today all the time with my installations. not sure what is happening, eager to test this but just cant :(

actually my problem before was on sepolia but good point about "mainnet" nobody would write that in a message.

as its very important chain, should be some casting, if someone writes ETh or ethereum or even L1 should load "mainnet" chain from viem IMO.

Also wonder how would code pick between arbitrum and arbitrum nova, maybe there would be needed another LLM call that would compare message and find proper chain name from list of all the chains....

I tried this regex, didn't implement it but please check this kind of pattern, could help In matching

import * as viemChains from "viem/chains";

type ChainKey = keyof typeof viemChains;

/**
 * Normalizes a string for chain name comparison by:
 * - Converting to lowercase
 * - Removing spaces, hyphens, and underscores
 * - Removing "network", "chain", "mainnet", "testnet" common suffixes
 */
function normalizeChainName(name: string): string {
  return name
    .toLowerCase()
    .replace(/[-_\s]/g, '')
    .replace(/(network|chain|mainnet|testnet)$/g, '');
}

/**
 * Matches user input against available chain names, returning the best match if found
 * @param userInput - The user's chain name input
 * @returns The matching chain key or null if no match found
 */
export function matchChainName(userInput: string): ChainKey | null {
  const normalizedInput = normalizeChainName(userInput);
  
  // Get all available chain keys
  const chainKeys = Object.keys(viemChains) as ChainKey[];
  
  // First try exact match after normalization
  const exactMatch = chainKeys.find(
    key => normalizeChainName(key) === normalizedInput
  );
  if (exactMatch) return exactMatch;
  
  // Then try starts-with match
  const startsWithMatch = chainKeys.find(
    key => normalizeChainName(key).startsWith(normalizedInput) ||
           normalizedInput.startsWith(normalizeChainName(key))
  );
  if (startsWithMatch) return startsWithMatch;
  
  // Finally try includes match
  const includesMatch = chainKeys.find(
    key => normalizeChainName(key).includes(normalizedInput) ||
           normalizedInput.includes(normalizeChainName(key))
  );
  if (includesMatch) return includesMatch;
  
  return null;
}

/**
 * Examples of usage:
 * matchChainName("ethereum") → "mainnet"
 * matchChainName("eth") → "mainnet"
 * matchChainName("bsc") → "bsc"
 * matchChainName("binance smart chain") → "bsc"
 * matchChainName("polygon network") → "polygon"
 * matchChainName("invalid chain") → null
 */

@nicky-ru
Copy link
Contributor Author

Hey @n00b21337 ,

Thanks for sharing your solution! The regex and normalization approach makes a lot of sense.

I ended up taking a slightly different route by limiting the options to just the supported chains that were intentionally included. This way, I'm only checking a predefined list instead of scanning through all the viem chains. Here's a snippet of what I did:

const chains = Object.keys(wp.chains);

// Replace the placeholder with supported chain names
const contextWithChains = context.replace("SUPPORTED_CHAINS", chains.toString());

Maybe a mix of both approaches could make this even better. What do you think?

@nicky-ru nicky-ru marked this pull request as draft December 12, 2024 09:59
@0xCardinalError
Copy link
Contributor

Hey @n00b21337 ,

Thanks for sharing your solution! The regex and normalization approach makes a lot of sense.

I ended up taking a slightly different route by limiting the options to just the supported chains that were intentionally included. This way, I'm only checking a predefined list instead of scanning through all the viem chains. Here's a snippet of what I did:

const chains = Object.keys(wp.chains);

// Replace the placeholder with supported chain names
const contextWithChains = context.replace("SUPPORTED_CHAINS", chains.toString());

Maybe a mix of both approaches could make this even better. What do you think?

ok, sounds good.
I am still kind of not sure how does template thing work, maybe you can enlighten me, where does it go into the flow? (as you added "fromChain": SUPPORTED_CHAINS"
?

@nicky-ru
Copy link
Contributor Author

@n00b21337 Yeah, no problem! The template basically provides an instruction and an example of how the returned data should look. It gets combined with the current state to create a context, which the model uses to generate the transferDetails object.

The "fromChain": SUPPORTED_CHAINS part is a placeholder in the template. Before passing the context to the model, I replace SUPPORTED_CHAINS with the actual list of connected chains (from wp.chains). This way, the model knows which chains to consider when generating the output.

so in short, the template shows the structure, and by swapping in the real chain list, the model sticks to that structure and uses the valid chains.

let me know if it helps!

@0xCardinalError
Copy link
Contributor

@n00b21337 Yeah, no problem! The template basically provides an instruction and an example of how the returned data should look. It gets combined with the current state to create a context, which the model uses to generate the transferDetails object.

The "fromChain": SUPPORTED_CHAINS part is a placeholder in the template. Before passing the context to the model, I replace SUPPORTED_CHAINS with the actual list of connected chains (from wp.chains). This way, the model knows which chains to consider when generating the output.

so in short, the template shows the structure, and by swapping in the real chain list, the model sticks to that structure and uses the valid chains.

let me know if it helps!

thnx for nice explanation.

so what I see as another problem is this. think the provider is checking wallet on ethereum and its funds with get_balance but i have a wallet with 0 funds on ethereum but I do have on sepolia, so this happens all the time. Think the provider also needs to get proper context from message, select that chain and then check balance there. After that it can go to actions

   can you send 0.001 ETH  on sepolia to 0xF14D804DCf86Da1236B58e4F41B5b004B19b1695 

Error getting wallet balance: ResourceNotFoundRpcError: Requested resource not found.

URL: https://cloudflare-eth.com
Request body: {"method":"eth_getBalance","params":["0xF14D804DCf86Da1236B58e4F41B5b004B19b1695","latest"]}

Details: Resource not found.
Version: viem@2.21.53
    at withRetry.delay.count.count (file:///home/marko/Experiments/eliza-evm/node_modules/viem/_esm/utils/buildRequest.js:45:31)
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
    at async attemptRetry (file:///home/marko/Experiments/eliza-evm/node_modules/viem/_esm/utils/promise/withRetry.js:12:30) {
  details: 'Resource not found.',
  docsPath: undefined,
  metaMessages: [
    'URL: https://cloudflare-eth.com',
    'Request body: {"method":"eth_getBalance","params":["0xF44D804DCf86Da1236B58e4F48B5b004B19b1695","latest"]}'
  ],
  shortMessage: 'Requested resource not found.',
  version: '2.21.53',
  code: -32001,
  [cause]: RpcRequestError: RPC Request failed.
  
  URL: https://cloudflare-eth.com
  Request body: {"method":"eth_getBalance","params":["0xF44D804DCf86Da1236B58e4F48B5b004B19b1695","latest"]}

@nicky-ru
Copy link
Contributor Author

closing this in favor of #1009

@nicky-ru nicky-ru closed this Dec 12, 2024
@nicky-ru
Copy link
Contributor Author

@n00b21337 weird.., just tried it on my side both mainnet and sepolia work. I never created a doc, but the character json should include this:

"chains": {
            "evm": [
                "sepolia"
            ]
        }

@0xCardinalError
Copy link
Contributor

0xCardinalError commented Dec 12, 2024

@n00b21337 weird.., just tried it on my side both mainnet and sepolia work. I never created a doc, but the character json should include this:

"chains": {
            "evm": [
                "sepolia"
            ]
        }

I just literally made a new installation, made a check out of this branch and run Tate bot with anthropic.

I didn't know I need to add chains, like this I belive?

    "plugins": [],
    "chains": {
        "evm": [
            "sepolia"
        ]
    },
    "bio": [
?


but even with that I have the problems
    
      send 0.001 eth to 0xB1620c0547741DeDD30F40a863c09D1964532F8C on sepolia 

Error getting wallet balance: ResourceNotFoundRpcError: Requested resource not found.

URL: https://cloudflare-eth.com
Request body: {"method":"eth_getBalance","params":["0xa6B04AFfC92BA83D4B6FFAded0A58412892CF381","latest"]}

Details: Resource not found.
Version: viem@2.21.53
    at withRetry.delay.count.count (file:///home/marko/Experiments/eliza-evm/node_modules/viem/_esm/utils/buildRequest.js:45:31)
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
    at async attemptRetry (file:///home/marko/Experiments/eliza-evm/node_modules/viem/_esm/utils/promise/withRetry.js:12:30) {
  details: 'Resource not found.',
  docsPath: undefined,
  metaMessages: [
    'URL: https://cloudflare-eth.com',
    'Request body: {"method":"eth_getBalance","params":["0xa6B04AFfC92BA83D4B6FFAded0A58412892CF381","latest"]}'
  ],
  shortMessage: 'Requested resource not found.',
  version: '2.21.53',
  code: -32001,
  [cause]: RpcRequestError: RPC Request failed.
  
  URL: https://cloudflare-eth.com
  Request body: {"method":"eth_getBalance","params":["0xa6B04AFfC92BA83D4B6FFAded0A58412892CF381","latest"]}
  
  Details: Resource not found.
  Version: viem@2.21.53
      at request (file:///home/marko/Experiments/eliza-evm/node_modules/viem/_esm/clients/transports/http.js:51:27)
      at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
      at async withRetry.delay.count.count (file:///home/marko/Experiments/eliza-evm/node_modules/viem/_esm/utils/buildRequest.js:20:24)
      at async attemptRetry (file:///home/marko/Experiments/eliza-evm/node_modules/viem/_esm/utils/promise/withRetry.js:12:30) {
    details: 'Resource not found.',
    docsPath: undefined,
    metaMessages: [
      'URL: https://cloudflare-eth.com',
      'Request body: {"method":"eth_getBalance","params":["0xa6B04AFfC92BA83D4B6FFAded0A58412892CF381","latest"]}'
    ],
    shortMessage: 'RPC Request failed.',
    version: '2.21.53',
    code: -32001,
    [cause]: { code: -32001, message: 'Resource not found.' }
  }
}

and

 ["◎ Agent: Error transferring tokens: The chain sepolia not configured yet. Add the chain or choose one from configured: mainnet"] 
 

you sure you are running on this same branch here? :) I don't see what I could have be do wrong here anymore

Two notes on

  1. I don't think we should be adding chains to bot config, we have transfer and bridge etc its not bot like to configure that first. We should be multichain by default so all chains should work out of the box
  2. Provider is not fetching chain from message, we have "initWalletProvider" this should also be using LLM and according to the chain in message check balance, right?

@nicky-ru
Copy link
Contributor Author

nicky-ru commented Dec 13, 2024

@n00b21337 its the same as in the previous version, but instead of passing chain config, only need to pass chain name:

"settings": {
        "secrets": {},
        "voice": {
            "model": "en_US-male-medium"
        },
       "chains": {
            "evm": [
                "sepolia"
            ]
        }
    }

then

  1. I think the best approach would be something in the middle, like wallet providers do, by default include ~10 most popular chains and the rest let the agent creator configure in the character according to his needs. And add an action for adding new chains.
  2. If we'd like to ask the balance for the chain other than the default one it sounds like a use case for another action "check balance".

@0xCardinalError
Copy link
Contributor

yes, this works. is this added anywhere in docs or how can ppl know they need to set it up there in character?

yes, you are right, think actions should be modular, but how to chain them then? is it possible that AI first finds check balance action, runs that, then uses output context and insert it into next action like transfer?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants