ElizaOS EVM Plugin Sent ETH in USDC Transfer Test

Tests of ElizaOS’s EVM plugin found that requests to send USDC produced native ETH transactions because the published transfer code ignores the token field.

Tests of @elizaos/plugin-evm found that its built-in transfer action sends native ETH when an agent is asked to send USDC. The published code uses the requested amount but ignores the token field, the ERC-20 transfer function and the token contract address.

The test covered version 1.0.13, the stable npm release, and version 2.0.0-alpha.8. It was conducted on Oct. 8, 2026, with a throwaway wallet on Base and a local JSON-RPC mock. No real funds moved.

An instruction to send 25 USDC produced a signed transaction with a value of 25 ETH. The transaction went directly to the recipient, contained no token contract address and had empty calldata. Its 21,000 gas limit matched a native-coin transfer rather than an ERC-20 transaction.

The transfer action collects the chain, amount, recipient, token and calldata. Its executor passes the amount to viem’s parseEther function and calls sendTransaction with a native value. The same behavior was found in version 2.0.0-alpha.8, although that package changes the action’s name and parameter handling.

A USDC transfer requires a call to the token contract’s transfer function. On Base, the USDC contract address is 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913. Because USDC uses six decimals, 25 USDC is represented by 25,000,000 base units. The 1.0.13 bundle contained no reference to the ERC-20 transfer selector or the Base USDC address.

If an agent wallet has enough ETH, a request for USDC can send the same numerical amount in ETH and report success. If it lacks enough ETH, the transaction fails for insufficient funds. The built-in success message describes the amount as “tokens” without naming the asset.

ElizaOS documentation describes transfers of native coins and ERC-20 tokens and includes USDC examples. The npm README describes native transfers, which matches the published JavaScript bundle. The documentation uses an ENS name such as vitalik.eth in an example, while the tested transfer action rejected that value because it required a 0x address.

A replacement action tested with the plugin removes the built-in transfer action and calls the USDC contract directly. The design pins a USDC contract address to each supported chain, accepts decimal amounts as strings, converts them with parseUnits at six decimals and restricts payments to checksummed addresses on an allowlist.

The action also sets per-transaction and daily limits. In testing, it used simulateContract before signing, waited for the transaction receipt and recorded the transaction hash, amount, recipient and originating agent message. A 25 USDC payment produced a call to the USDC contract with zero ETH value and transfer data for 25,000,000 units.

The guard rejected a 300 USDC payment above a 250 USDC per-transaction limit, a 5 USDC payment to an unapproved address, an ENS recipient and an amount with more than six decimal places. Daily spending data must be stored outside temporary memory so an agent restart does not reset the limit.

The test path began on Base Sepolia with testnet USDC and a small amount of Sepolia ETH for transaction fees. After contract calls and limits are checked, operators can switch to Base mainnet and monitor transfers through the USDC contract’s records.

The plugin’s swap and bridge actions read token decimals on-chain when preparing transactions. The bridge code can use 18 decimals if that read fails. The separate Solana plugin can fall back to SOL when a token mint is missing, uses floating-point amount calculations and assumes nine decimals when a mint lookup fails.

Articles by this author