Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions typescript/agentkit/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Fixed

- Fixed erc20 `get_balance` action to format erc20 balance with correct number of decimals.

## [0.2.2] - 2025-02-19

### Added
Expand Down
40 changes: 39 additions & 1 deletion typescript/agentkit/src/action-providers/erc20/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,39 @@
# ERC20 Action Provider
# ERC20 Action Provider

This directory contains the **ERC20ActionProvider** implementation, which provides actions to interact with **ERC20 tokens** on EVM-compatible networks.

## Directory Structure

```
erc20/
├── erc20ActionProvider.ts # Main provider with ERC20 token functionality
├── erc20ActionProvider.test.ts # Test file for ERC20 provider
├── constants.ts # Constants for ERC20 provider
├── schemas.ts # Token action schemas
├── index.ts # Main exports
└── README.md # This file
```

## Actions

### ERC20 Token Actions

- `get_balance`: Get the balance of an ERC20 token
- Returns the **balance** of the token in the wallet
- Formats the balance with the correct number of decimals

- `transfer`: Transfer ERC20 tokens to another address
- Constructs and sends the transfer transaction
- Returns the **transaction hash** upon success

## Adding New Actions

To add new ERC20 actions:

1. Define your action schema in `schemas.ts`. See [Defining the input schema](https://github.com/coinbase/agentkit/blob/main/CONTRIBUTING-TYPESCRIPT.md#defining-the-input-schema) for more information.
2. Implement the action in `erc20ActionProvider.ts`
3. Implement tests in `erc20ActionProvider.test.ts`

## Notes

For more information on the **ERC20 token standard**, visit [ERC20 Token Standard](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/).
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { encodeFunctionData, Hex } from "viem";
import { abi } from "./constants";

const MOCK_AMOUNT = 15;
const MOCK_DECIMALS = 6;
const MOCK_CONTRACT_ADDRESS = "0x1234567890123456789012345678901234567890";
const MOCK_DESTINATION = "0x9876543210987654321098765432109876543210";
const MOCK_ADDRESS = "0x1234567890123456789012345678901234567890";
Expand Down Expand Up @@ -40,11 +41,12 @@ describe("Get Balance Action", () => {
getAddress: jest.fn().mockReturnValue(MOCK_ADDRESS),
readContract: jest.fn(),
} as unknown as jest.Mocked<EvmWalletProvider>;

mockWallet.readContract.mockResolvedValue(MOCK_AMOUNT);
});

it("should successfully respond", async () => {
mockWallet.readContract.mockResolvedValueOnce(MOCK_AMOUNT);
mockWallet.readContract.mockResolvedValueOnce(MOCK_DECIMALS);

const args = {
contractAddress: MOCK_CONTRACT_ADDRESS,
};
Expand All @@ -57,7 +59,9 @@ describe("Get Balance Action", () => {
functionName: "balanceOf",
args: [mockWallet.getAddress()],
});
expect(response).toContain(`Balance of ${MOCK_CONTRACT_ADDRESS} is ${MOCK_AMOUNT}`);
expect(response).toContain(
`Balance of ${MOCK_CONTRACT_ADDRESS} is ${MOCK_AMOUNT / 10 ** MOCK_DECIMALS}`,
);
});

it("should fail with an error", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Network } from "../../network";
import { CreateAction } from "../actionDecorator";
import { GetBalanceSchema, TransferSchema } from "./schemas";
import { abi } from "./constants";
import { encodeFunctionData, Hex } from "viem";
import { encodeFunctionData, formatUnits, Hex } from "viem";
import { EvmWalletProvider } from "../../wallet-providers";

/**
Expand Down Expand Up @@ -41,10 +41,17 @@ export class ERC20ActionProvider extends ActionProvider<EvmWalletProvider> {
address: args.contractAddress as Hex,
abi,
functionName: "balanceOf",
args: [walletProvider.getAddress()],
args: [walletProvider.getAddress() as Hex],
});

return `Balance of ${args.contractAddress} is ${balance}`;
const decimals = await walletProvider.readContract({
address: args.contractAddress as Hex,
abi,
functionName: "decimals",
args: [],
});

return `Balance of ${args.contractAddress} is ${formatUnits(balance, decimals)}`;
} catch (error) {
return `Error getting balance: ${error}`;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ It takes the following inputs:
address: args.contractAddress as Hex,
abi: ERC721_ABI,
functionName: "balanceOf",
args: [address],
args: [address as Hex],
});

return `Balance of NFTs for contract ${args.contractAddress} at address ${address} is ${balance}`;
Expand Down
13 changes: 11 additions & 2 deletions typescript/agentkit/src/wallet-providers/cdpWalletProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import {
keccak256,
Signature,
PublicClient,
Abi,
ContractFunctionName,
ContractFunctionArgs,
} from "viem";
import { EvmWalletProvider } from "./evmWalletProvider";
import { Network } from "../network";
Expand Down Expand Up @@ -425,8 +428,14 @@ export class CdpWalletProvider extends EvmWalletProvider {
* @param params - The parameters to read the contract.
* @returns The response from the contract.
*/
async readContract(params: ReadContractParameters): Promise<ReadContractReturnType> {
return this.#publicClient!.readContract(params);
async readContract<
const abi extends Abi | readonly unknown[],
functionName extends ContractFunctionName<abi, "pure" | "view">,
const args extends ContractFunctionArgs<abi, "pure" | "view", functionName>,
>(
params: ReadContractParameters<abi, functionName, args>,
): Promise<ReadContractReturnType<abi, functionName, args>> {
return this.#publicClient!.readContract<abi, functionName, args>(params);
}

/**
Expand Down
17 changes: 15 additions & 2 deletions typescript/agentkit/src/wallet-providers/evmWalletProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@
/* eslint-disable @typescript-eslint/no-explicit-any */

import { WalletProvider } from "./walletProvider";
import { TransactionRequest, ReadContractParameters, ReadContractReturnType } from "viem";
import {
TransactionRequest,
ReadContractParameters,
ReadContractReturnType,
ContractFunctionName,
Abi,
ContractFunctionArgs,
} from "viem";

/**
* EvmWalletProvider is the abstract base class for all EVM wallet providers.
Expand Down Expand Up @@ -56,5 +63,11 @@ export abstract class EvmWalletProvider extends WalletProvider {
* @param params - The parameters to read the contract.
* @returns The response from the contract.
*/
abstract readContract(params: ReadContractParameters): Promise<ReadContractReturnType>;
abstract readContract<
const abi extends Abi | readonly unknown[],
functionName extends ContractFunctionName<abi, "pure" | "view">,
const args extends ContractFunctionArgs<abi, "pure" | "view", functionName>,
>(
params: ReadContractParameters<abi, functionName, args>,
): Promise<ReadContractReturnType<abi, functionName, args>>;
}
13 changes: 11 additions & 2 deletions typescript/agentkit/src/wallet-providers/viemWalletProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import {
ReadContractParameters,
ReadContractReturnType,
parseEther,
Abi,
ContractFunctionName,
ContractFunctionArgs,
} from "viem";
import { EvmWalletProvider } from "./evmWalletProvider";
import { Network } from "../network";
Expand Down Expand Up @@ -214,8 +217,14 @@ export class ViemWalletProvider extends EvmWalletProvider {
* @param params - The parameters to read the contract.
* @returns The response from the contract.
*/
async readContract(params: ReadContractParameters): Promise<ReadContractReturnType> {
return this.#publicClient.readContract(params);
async readContract<
const abi extends Abi | readonly unknown[],
functionName extends ContractFunctionName<abi, "pure" | "view">,
const args extends ContractFunctionArgs<abi, "pure" | "view", functionName>,
>(
params: ReadContractParameters<abi, functionName, args>,
): Promise<ReadContractReturnType<abi, functionName, args>> {
return this.#publicClient.readContract<abi, functionName, args>(params);
}

/**
Expand Down
Loading