From fb39eabad8fd031155931d10e2e19e334eecca34 Mon Sep 17 00:00:00 2001 From: Axiom Bot <0xAxiom@users.noreply.github.com> Date: Tue, 19 May 2026 17:24:53 -0700 Subject: [PATCH 1/2] docs(erc20): add output format, error states, and workflow context to action descriptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #1066 — audit flagged erc20 actions scoring C/C+ due to missing output documentation and error state coverage. Changes per action: - get_balance: add Returns section (success format + error case), note EVM-only constraint - transfer: add Returns section, document gas requirement for native token (agents need to know ERC20 transfers still cost ETH), list guardrail refusal conditions - approve: add workflow context (use BEFORE calling a protocol; flow is approve → protocol call; without allowance transferFrom reverts), add Returns section, document overwrite behaviour and malicious-spender risk - get_allowance: add workflow context (call before approve to avoid redundant tx; allowance of 0 blocks transferFrom), add Returns section, clarify owner is always the wallet address Applied to both TypeScript and Python implementations. --- .../erc20/erc20_action_provider.py | 43 ++++++++++++--- .../erc20/erc20ActionProvider.ts | 53 +++++++++++++++---- 2 files changed, 77 insertions(+), 19 deletions(-) diff --git a/python/coinbase-agentkit/coinbase_agentkit/action_providers/erc20/erc20_action_provider.py b/python/coinbase-agentkit/coinbase_agentkit/action_providers/erc20/erc20_action_provider.py index 04f52664e..5c324864c 100644 --- a/python/coinbase-agentkit/coinbase_agentkit/action_providers/erc20/erc20_action_provider.py +++ b/python/coinbase-agentkit/coinbase_agentkit/action_providers/erc20/erc20_action_provider.py @@ -35,8 +35,13 @@ def __init__(self) -> None: - contract_address: The contract address of the token to get the balance for - address: (Optional) The address to check the balance for. If not provided, uses the wallet's address + Returns: + - On success: "Balance of () at address
is " + - On error: An error message if the token contract cannot be read (e.g. invalid address or non-ERC20 contract) + Important notes: - Never assume token or address, they have to be provided as inputs. If only token symbol is provided, use the get_erc20_token_address tool to get the token address first + - Only works on EVM-compatible networks; will fail on non-EVM chains """, schema=GetBalanceSchema, ) @@ -78,10 +83,14 @@ def get_balance(self, wallet_provider: EvmWalletProvider, args: dict[str, Any]) - contract_address: The contract address of the token to transfer - destination_address: The address to send the tokens to + Returns: + - On success: Confirmation with the transferred amount, token name, destination address, and transaction hash + - On error: An error message explaining the failure (e.g. insufficient token balance, invalid address, or unsafe destination) + Important notes: - Never assume token or destination addresses, they have to be provided as inputs. If only token symbol is provided, use the get_erc20_token_address tool to get the token address first - - Ensure sufficient balance of the input asset before transferring - - When sending native assets (e.g. 'eth' on base-mainnet), ensure there is sufficient balance for the transfer itself AND the gas cost of this transfer + - ERC20 transfers require native token (e.g. ETH on Base/Ethereum) to pay for gas — always ensure the wallet holds sufficient native token balance even when the asset being transferred is not native + - Refuses to transfer to the token's own contract address or to another ERC20 contract to prevent permanent loss of funds """, schema=TransferSchema, ) @@ -160,15 +169,24 @@ def transfer(self, wallet_provider: EvmWalletProvider, args: dict[str, Any]) -> description=""" This tool will approve a spender to transfer ERC20 tokens from the wallet. + Use this BEFORE interacting with a DeFi protocol or contract that needs to move tokens on your behalf + (e.g. a DEX swap, lending deposit, or bridge). The typical flow is: approve → call the protocol. + If no allowance exists, the protocol's transferFrom call will revert and the interaction will fail. + Use get_allowance first to check whether approval is already in place before calling this. + It takes the following inputs: - amount: The amount to approve in whole units (e.g. 100 for 100 USDC) - contract_address: The contract address of the token to approve - - spender_address: The spender address to approve + - spender_address: The spender address to approve (typically a protocol contract, not a user wallet) + + Returns: + - On success: Confirmation with the approved amount, token name, spender address, and transaction hash + - On error: An error message if the token address is invalid or the transaction fails Important notes: - - This will overwrite any existing allowance - - To revoke an allowance, set the amount to 0 - - Ensure you trust the spender address before approving + - This overwrites any existing allowance for the spender — set amount to 0 to revoke + - Approving requires native token (e.g. ETH) for gas + - A malicious spender can drain up to the approved amount — only approve addresses you trust - Never assume token addresses, they have to be provided as inputs. If only token symbol is provided, use the get_erc20_token_address tool to get the token address first """, schema=ApproveSchema, @@ -225,13 +243,22 @@ def approve(self, wallet_provider: EvmWalletProvider, args: dict[str, Any]) -> s @create_action( name="get_allowance", description=""" - This tool will get the allowance amount for a spender of an ERC20 token. + This tool will get the current allowance that a spender is authorized to use from the wallet's ERC20 token balance. + + Call this before approve to check whether a protocol already has sufficient allowance — if it does, + you can skip the approve step. An allowance of 0 means the spender cannot move any tokens; you must + call approve before the protocol can execute transferFrom on your behalf. It takes the following inputs: - contract_address: The contract address of the token to check allowance for - - spender_address: The address to check allowance for + - spender_address: The spender address to check allowance for (usually a protocol contract) + + Returns: + - On success: "Allowance for to spend () is tokens" + - On error: An error message if the token address is invalid or the contract cannot be read Important notes: + - Always checks allowance from the wallet's own address as the owner - Never assume token addresses, they have to be provided as inputs. If only token symbol is provided, use the get_erc20_token_address tool to get the token address first """, schema=AllowanceSchema, diff --git a/typescript/agentkit/src/action-providers/erc20/erc20ActionProvider.ts b/typescript/agentkit/src/action-providers/erc20/erc20ActionProvider.ts index db03f8db7..e68298585 100644 --- a/typescript/agentkit/src/action-providers/erc20/erc20ActionProvider.ts +++ b/typescript/agentkit/src/action-providers/erc20/erc20ActionProvider.ts @@ -35,12 +35,18 @@ export class ERC20ActionProvider extends ActionProvider { @CreateAction({ name: "get_balance", description: ` - This tool will get the balance of an ERC20 token for a given address. + This tool will get the balance of an ERC20 token for a given address. It takes the following inputs: - tokenAddress: The contract address of the token to get the balance for - address: (Optional) The address to check the balance for. If not provided, uses the wallet's address + + Returns: + - On success: "Balance of () at address
is " + - On error: An error message if the token contract cannot be read (e.g. invalid address or non-ERC20 contract) + Important notes: - - Never assume token or address, they have to be provided as inputs. If only token symbol is provided, use the get_token_address tool to get the token address first + - Never assume token or address, they have to be provided as inputs. If only token symbol is provided, use the get_erc20_token_address tool to get the token address first + - Only works on EVM-compatible networks; will fail on non-EVM chains `, schema: GetBalanceSchema, }) @@ -74,8 +80,15 @@ It takes the following inputs: - amount: The amount to transfer in whole units (e.g. 10.5 USDC) - tokenAddress: The contract address of the token to transfer - destinationAddress: The address to send the funds to + +Returns: +- On success: Confirmation with the transferred amount, token name, destination address, and transaction hash +- On error: An error message explaining the failure (e.g. insufficient token balance, invalid address, or unsafe destination) + Important notes: -- Never assume token or destination addresses, they have to be provided as inputs. If only token symbol is provided, use the get_token_address tool to get the token address first +- Never assume token or destination addresses, they have to be provided as inputs. If only token symbol is provided, use the get_erc20_token_address tool to get the token address first +- ERC20 transfers require native token (e.g. ETH on Base/Ethereum) to pay for gas — always ensure the wallet holds sufficient native token balance even when the asset being transferred is not native +- Refuses to transfer to the token's own contract address or to another ERC20 contract to prevent permanent loss of funds `, schema: TransferSchema, }) @@ -147,16 +160,25 @@ Important notes: description: ` This tool will approve a spender to transfer ERC20 tokens from the wallet. +Use this BEFORE interacting with a DeFi protocol or contract that needs to move tokens on your behalf +(e.g. a DEX swap, lending deposit, or bridge). The typical flow is: approve → call the protocol. +If no allowance exists, the protocol's transferFrom call will revert and the interaction will fail. +Use get_allowance first to check whether approval is already in place before calling this. + It takes the following inputs: - amount: The amount to approve in whole units (e.g. 100 for 100 USDC) - tokenAddress: The contract address of the token to approve -- spenderAddress: The spender address to approve +- spenderAddress: The spender address to approve (typically a protocol contract, not a user wallet) + +Returns: +- On success: Confirmation with the approved amount, token name, spender address, and transaction hash +- On error: An error message if the token address is invalid or the transaction fails Important notes: -- This will overwrite any existing allowance -- To revoke an allowance, set the amount to 0 -- Ensure you trust the spender address before approving -- Never assume token addresses, they have to be provided as inputs. If only token symbol is provided, use the get_token_address tool to get the token address first +- This overwrites any existing allowance for the spender — set amount to 0 to revoke +- Approving requires native token (e.g. ETH) for gas +- A malicious spender can drain up to the approved amount — only approve addresses you trust +- Never assume token addresses, they have to be provided as inputs. If only token symbol is provided, use the get_erc20_token_address tool to get the token address first `, schema: ApproveSchema, }) @@ -201,14 +223,23 @@ Important notes: @CreateAction({ name: "get_allowance", description: ` -This tool will get the allowance amount for a spender of an ERC20 token. +This tool will get the current allowance that a spender is authorized to use from the wallet's ERC20 token balance. + +Call this before approve to check whether a protocol already has sufficient allowance — if it does, +you can skip the approve step. An allowance of 0 means the spender cannot move any tokens; you must +call approve before the protocol can execute transferFrom on your behalf. It takes the following inputs: - tokenAddress: The contract address of the token to check allowance for -- spenderAddress: The address to check allowance for +- spenderAddress: The spender address to check allowance for (usually a protocol contract) + +Returns: +- On success: "Allowance for to spend () is tokens" +- On error: An error message if the token address is invalid or the contract cannot be read Important notes: -- Never assume token addresses, they have to be provided as inputs. If only token symbol is provided, use the get_token_address tool to get the token address first +- Always checks allowance from the wallet's own address as the owner +- Never assume token addresses, they have to be provided as inputs. If only token symbol is provided, use the get_erc20_token_address tool to get the token address first `, schema: AllowanceSchema, }) From 63ff5c6db4d78adea18234c8c0a2b009b6b2c29f Mon Sep 17 00:00:00 2001 From: Axiom Bot <0xAxiom@users.noreply.github.com> Date: Sun, 7 Jun 2026 07:46:11 -0700 Subject: [PATCH 2/2] docs(erc20): define arg types and formats in action descriptions (addresses review feedback) Add explicit type annotations (str/string, 0x hex address, decimal number, optional) to each arg in the "It takes the following inputs:" sections across all four ERC20 actions in both TypeScript and Python, per MVPuknowme's "Define args" review comment. Co-Authored-By: Claude Sonnet 4.6 --- .../erc20/erc20_action_provider.py | 20 +++++++++---------- .../erc20/erc20ActionProvider.ts | 20 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/python/coinbase-agentkit/coinbase_agentkit/action_providers/erc20/erc20_action_provider.py b/python/coinbase-agentkit/coinbase_agentkit/action_providers/erc20/erc20_action_provider.py index 5c324864c..32b478042 100644 --- a/python/coinbase-agentkit/coinbase_agentkit/action_providers/erc20/erc20_action_provider.py +++ b/python/coinbase-agentkit/coinbase_agentkit/action_providers/erc20/erc20_action_provider.py @@ -32,8 +32,8 @@ def __init__(self) -> None: description=""" This tool will get the balance of an ERC20 token for a given address. It takes the following inputs: - - contract_address: The contract address of the token to get the balance for - - address: (Optional) The address to check the balance for. If not provided, uses the wallet's address + - contract_address (str, 0x hex address): The contract address of the ERC20 token to get the balance for + - address (str, 0x hex address, optional): The address to check the balance for. If not provided, uses the wallet's address Returns: - On success: "Balance of () at address
is " @@ -79,9 +79,9 @@ def get_balance(self, wallet_provider: EvmWalletProvider, args: dict[str, Any]) This tool will transfer an ERC20 token from the wallet to another onchain address. It takes the following inputs: - - amount: The amount to transfer in whole units (e.g. 10.5 USDC) - - contract_address: The contract address of the token to transfer - - destination_address: The address to send the tokens to + - amount (str, decimal number): The amount to transfer in whole units (e.g. 10.5 USDC) + - contract_address (str, 0x hex address): The contract address of the token to transfer + - destination_address (str, 0x hex address): The address to send the tokens to Returns: - On success: Confirmation with the transferred amount, token name, destination address, and transaction hash @@ -175,9 +175,9 @@ def transfer(self, wallet_provider: EvmWalletProvider, args: dict[str, Any]) -> Use get_allowance first to check whether approval is already in place before calling this. It takes the following inputs: - - amount: The amount to approve in whole units (e.g. 100 for 100 USDC) - - contract_address: The contract address of the token to approve - - spender_address: The spender address to approve (typically a protocol contract, not a user wallet) + - amount (str, decimal number): The amount to approve in whole units (e.g. 100 for 100 USDC); use "0" to revoke + - contract_address (str, 0x hex address): The contract address of the token to approve + - spender_address (str, 0x hex address): The spender address to approve (typically a protocol contract, not a user wallet) Returns: - On success: Confirmation with the approved amount, token name, spender address, and transaction hash @@ -250,8 +250,8 @@ def approve(self, wallet_provider: EvmWalletProvider, args: dict[str, Any]) -> s call approve before the protocol can execute transferFrom on your behalf. It takes the following inputs: - - contract_address: The contract address of the token to check allowance for - - spender_address: The spender address to check allowance for (usually a protocol contract) + - contract_address (str, 0x hex address): The contract address of the token to check allowance for + - spender_address (str, 0x hex address): The spender address to check allowance for (usually a protocol contract) Returns: - On success: "Allowance for to spend () is tokens" diff --git a/typescript/agentkit/src/action-providers/erc20/erc20ActionProvider.ts b/typescript/agentkit/src/action-providers/erc20/erc20ActionProvider.ts index e68298585..f60af7be6 100644 --- a/typescript/agentkit/src/action-providers/erc20/erc20ActionProvider.ts +++ b/typescript/agentkit/src/action-providers/erc20/erc20ActionProvider.ts @@ -37,8 +37,8 @@ export class ERC20ActionProvider extends ActionProvider { description: ` This tool will get the balance of an ERC20 token for a given address. It takes the following inputs: - - tokenAddress: The contract address of the token to get the balance for - - address: (Optional) The address to check the balance for. If not provided, uses the wallet's address + - tokenAddress (string, 0x hex address): The contract address of the ERC20 token to get the balance for + - address (string, 0x hex address, optional): The address to check the balance for. If not provided, uses the wallet's address Returns: - On success: "Balance of () at address
is " @@ -77,9 +77,9 @@ export class ERC20ActionProvider extends ActionProvider { This tool will transfer (send) an ERC20 token from the wallet to another onchain address. It takes the following inputs: -- amount: The amount to transfer in whole units (e.g. 10.5 USDC) -- tokenAddress: The contract address of the token to transfer -- destinationAddress: The address to send the funds to +- amount (string, decimal number): The amount to transfer in whole units (e.g. 10.5 USDC) +- tokenAddress (string, 0x hex address): The contract address of the token to transfer +- destinationAddress (string, 0x hex address): The address to send the funds to Returns: - On success: Confirmation with the transferred amount, token name, destination address, and transaction hash @@ -166,9 +166,9 @@ If no allowance exists, the protocol's transferFrom call will revert and the int Use get_allowance first to check whether approval is already in place before calling this. It takes the following inputs: -- amount: The amount to approve in whole units (e.g. 100 for 100 USDC) -- tokenAddress: The contract address of the token to approve -- spenderAddress: The spender address to approve (typically a protocol contract, not a user wallet) +- amount (string, decimal number): The amount to approve in whole units (e.g. 100 for 100 USDC); use "0" to revoke +- tokenAddress (string, 0x hex address): The contract address of the token to approve +- spenderAddress (string, 0x hex address): The spender address to approve (typically a protocol contract, not a user wallet) Returns: - On success: Confirmation with the approved amount, token name, spender address, and transaction hash @@ -230,8 +230,8 @@ you can skip the approve step. An allowance of 0 means the spender cannot move a call approve before the protocol can execute transferFrom on your behalf. It takes the following inputs: -- tokenAddress: The contract address of the token to check allowance for -- spenderAddress: The spender address to check allowance for (usually a protocol contract) +- tokenAddress (string, 0x hex address): The contract address of the token to check allowance for +- spenderAddress (string, 0x hex address): The spender address to check allowance for (usually a protocol contract) Returns: - On success: "Allowance for to spend () is tokens"