Skip to content

Commit e34b5c1

Browse files
0xRAGkylexqian
authored andcommitted
fix: format erc20 balance (coinbase#467)
1 parent 3f3ffc6 commit e34b5c1

File tree

6 files changed

+89
-8
lines changed

6 files changed

+89
-8
lines changed

python/coinbase-agentkit/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### Fixed
6+
7+
- Fixed erc20 `get_balance` action to format erc20 balance with correct number of decimals.
8+
59
## [0.1.3] - 2025-02-21
610

711
- Fixed Morpho `deposit` and `withdraw` function headers to conform to the Action Provider Paradigm.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# ERC20 Action Provider
2+
3+
This directory contains the **ERC20ActionProvider** implementation, which provides actions to interact with **ERC20 tokens** on EVM-compatible networks.
4+
5+
## Directory Structure
6+
7+
```
8+
erc20/
9+
├── erc20_action_provider.py # Main provider with ERC20 token functionality
10+
├── constants.py # Constants including ERC20 ABI
11+
├── schemas.py # Pydantic schemas for action inputs
12+
├── validators.py # Input validation utilities
13+
├── __init__.py # Package exports
14+
└── README.md # This file
15+
```
16+
17+
## Actions
18+
19+
### ERC20 Token Actions
20+
21+
- `get_balance`: Get the balance of an ERC20 token
22+
- Returns the **balance** of the token in the wallet
23+
- Formats the balance with the correct number of decimals
24+
- Takes a contract address as input
25+
26+
- `transfer`: Transfer ERC20 tokens to another address
27+
- Takes amount, contract address, and destination as inputs
28+
- Constructs and sends the transfer transaction
29+
- Returns the **transaction hash** upon success
30+
- Handles decimal formatting automatically
31+
32+
## Adding New Actions
33+
34+
To add new ERC20 actions:
35+
36+
1. Define your action schema in `schemas.py`. See [Defining the input schema](https://github.com/coinbase/agentkit/blob/main/CONTRIBUTING-PYTHON.md#defining-the-input-schema) for more information.
37+
2. Implement the action in `erc20_action_provider.py`
38+
3. Implement tests in `tests/action_providers/erc20/test_erc20_action_provider.py`
39+
40+
## Notes
41+
42+
For more information on the **ERC20 token standard**, visit [ERC20 Token Standard](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/).

python/coinbase-agentkit/coinbase_agentkit/action_providers/erc20/constants.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,15 @@
3737
},
3838
],
3939
},
40+
{
41+
"type": "function",
42+
"name": "decimals",
43+
"stateMutability": "view",
44+
"inputs": [],
45+
"outputs": [
46+
{
47+
"type": "uint8",
48+
},
49+
],
50+
},
4051
]

python/coinbase-agentkit/coinbase_agentkit/action_providers/erc20/erc20_action_provider.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,14 @@ def get_balance(self, wallet_provider: EvmWalletProvider, args: dict[str, Any])
4747
args=[wallet_provider.get_address()],
4848
)
4949

50-
return f"Balance of {validated_args.contract_address} is {balance}"
50+
decimals = wallet_provider.read_contract(
51+
contract_address=validated_args.contract_address,
52+
abi=ERC20_ABI,
53+
function_name="decimals",
54+
args=[],
55+
)
56+
57+
return f"Balance of {validated_args.contract_address} is {balance / 10 ** decimals}"
5158
except Exception as e:
5259
return f"Error getting balance: {e!s}"
5360

python/coinbase-agentkit/tests/action_providers/erc20/conftest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from coinbase_agentkit.wallet_providers.evm_wallet_provider import EvmWalletProvider
88

99
MOCK_AMOUNT = "1000000000000000000"
10+
MOCK_DECIMALS = 6
1011
MOCK_CONTRACT_ADDRESS = "0x1234567890123456789012345678901234567890"
1112
MOCK_DESTINATION = "0x9876543210987654321098765432109876543210"
1213
MOCK_ADDRESS = "0x1234567890123456789012345678901234567890"
@@ -17,5 +18,5 @@ def mock_wallet():
1718
"""Create a mock wallet provider."""
1819
mock = Mock(spec=EvmWalletProvider)
1920
mock.get_address.return_value = MOCK_ADDRESS
20-
mock.read_contract.return_value = MOCK_AMOUNT
21+
mock.read_contract.side_effect = [int(MOCK_AMOUNT), MOCK_DECIMALS]
2122
return mock

python/coinbase-agentkit/tests/action_providers/erc20/test_erc20_action_provider.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Tests for the ERC20 action provider."""
22

3+
from unittest.mock import call
4+
35
import pytest
46
from web3 import Web3
57

@@ -13,6 +15,7 @@
1315
from .conftest import (
1416
MOCK_AMOUNT,
1517
MOCK_CONTRACT_ADDRESS,
18+
MOCK_DECIMALS,
1619
MOCK_DESTINATION,
1720
)
1821

@@ -37,13 +40,26 @@ def test_get_balance_success(mock_wallet):
3740

3841
response = provider.get_balance(mock_wallet, args)
3942

40-
mock_wallet.read_contract.assert_called_once_with(
41-
contract_address=MOCK_CONTRACT_ADDRESS,
42-
abi=ERC20_ABI,
43-
function_name="balanceOf",
44-
args=[mock_wallet.get_address()],
43+
mock_wallet.read_contract.assert_has_calls(
44+
[
45+
call(
46+
contract_address=MOCK_CONTRACT_ADDRESS,
47+
abi=ERC20_ABI,
48+
function_name="balanceOf",
49+
args=[mock_wallet.get_address()],
50+
),
51+
call(
52+
contract_address=MOCK_CONTRACT_ADDRESS,
53+
abi=ERC20_ABI,
54+
function_name="decimals",
55+
args=[],
56+
),
57+
]
58+
)
59+
assert (
60+
f"Balance of {MOCK_CONTRACT_ADDRESS} is {int(MOCK_AMOUNT) / 10 ** MOCK_DECIMALS}"
61+
in response
4562
)
46-
assert f"Balance of {MOCK_CONTRACT_ADDRESS} is {MOCK_AMOUNT}" in response
4763

4864

4965
def test_get_balance_error(mock_wallet):

0 commit comments

Comments
 (0)