UNPKG

@coinbase/agentkit

Version:

Coinbase AgentKit core primitives

297 lines (291 loc) 15.3 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.erc20ActionProvider = exports.ERC20ActionProvider = void 0; const zod_1 = require("zod"); const actionProvider_1 = require("../actionProvider"); const actionDecorator_1 = require("../actionDecorator"); const schemas_1 = require("./schemas"); const constants_1 = require("./constants"); const utils_1 = require("./utils"); const viem_1 = require("viem"); const wallet_providers_1 = require("../../wallet-providers"); /** * ERC20ActionProvider is an action provider for ERC20 tokens. */ class ERC20ActionProvider extends actionProvider_1.ActionProvider { /** * Constructor for the ERC20ActionProvider. */ constructor() { super("erc20", []); /** * Checks if the ERC20 action provider supports the given network. * * @param network - The network to check. * @returns True if the ERC20 action provider supports the network, false otherwise. */ this.supportsNetwork = (network) => network.protocolFamily === "evm"; } /** * Gets the balance of an ERC20 token. * * @param walletProvider - The wallet provider to get the balance from. * @param args - The input arguments for the action. * @returns A message containing the balance. */ async getBalance(walletProvider, args) { const address = args.address || walletProvider.getAddress(); const tokenDetails = await (0, utils_1.getTokenDetails)(walletProvider, args.tokenAddress, args.address); if (!tokenDetails) { return `Error: Could not fetch token details for ${args.tokenAddress}`; } return `Balance of ${tokenDetails.name} (${args.tokenAddress}) at address ${address} is ${tokenDetails.formattedBalance}`; } /** * Transfers a specified amount of an ERC20 token to a destination onchain. * * @param walletProvider - The wallet provider to transfer the asset from. * @param args - The input arguments for the action. * @returns A message containing the transfer details. */ async transfer(walletProvider, args) { try { // Check token details const tokenAddress = (0, viem_1.getAddress)(args.tokenAddress); const tokenDetails = await (0, utils_1.getTokenDetails)(walletProvider, args.tokenAddress); if (!tokenDetails) { return `Error: Could not fetch token details for ${args.tokenAddress}. Please verify the token address is correct.`; } // Check token balance const amountInWei = (0, viem_1.parseUnits)(String(args.amount), tokenDetails.decimals); if (tokenDetails.balance < amountInWei) { return `Error: Insufficient ${tokenDetails.name} (${args.tokenAddress}) token balance. Requested to send ${args.amount} of ${tokenDetails.name} (${args.tokenAddress}), but only ${tokenDetails.formattedBalance} is available.`; } // Guardrails to prevent loss of funds if (args.tokenAddress === args.destinationAddress) { return "Error: Transfer destination is the token contract address. Refusing transfer to prevent loss of funds."; } if ((await walletProvider .getPublicClient() .getCode({ address: args.destinationAddress })) !== "0x") { // If destination address is a contract, check if its an ERC20 token // This assumes if the contract implements name, balance and decimals functions, it is an ERC20 token const destinationTokenDetails = await (0, utils_1.getTokenDetails)(walletProvider, args.destinationAddress); if (destinationTokenDetails) { return "Error: Transfer destination is an ERC20 token contract. Refusing to transfer to prevent loss of funds."; } // If contract but not an ERC20 token (e.g a smart wallet), allow the transfer } // Check if we can do gasless transfer const isLegacyCdpWallet = walletProvider.getName() === "legacy_cdp_wallet_provider"; const network = walletProvider.getNetwork(); const canDoGasless = isLegacyCdpWallet && ((network.networkId === "base-mainnet" && constants_1.BaseTokenToAssetId.has(tokenAddress)) || (network.networkId === "base-sepolia" && constants_1.BaseSepoliaTokenToAssetId.has(tokenAddress))); if (canDoGasless) { // Cast to LegacyCdpWalletProvider to access erc20Transfer const cdpWallet = walletProvider; const assetId = network.networkId === "base-mainnet" ? constants_1.BaseTokenToAssetId.get(tokenAddress) : constants_1.BaseSepoliaTokenToAssetId.get(tokenAddress); const hash = await cdpWallet.gaslessERC20Transfer(assetId, args.destinationAddress, BigInt(args.amount)); await walletProvider.waitForTransactionReceipt(hash); return `Transferred ${args.amount} of ${args.tokenAddress} to ${args.destinationAddress} using gasless transfer.\nTransaction hash: ${hash}`; } // Fallback to regular transfer const hash = await walletProvider.sendTransaction({ to: args.tokenAddress, data: (0, viem_1.encodeFunctionData)({ abi: viem_1.erc20Abi, functionName: "transfer", args: [args.destinationAddress, amountInWei], }), }); await walletProvider.waitForTransactionReceipt(hash); return `Transferred ${args.amount} of ${tokenDetails?.name} (${args.tokenAddress}) to ${args.destinationAddress}.\nTransaction hash for the transfer: ${hash}`; } catch (error) { return `Error transferring the asset: ${error}`; } } /** * Approves a spender to transfer a specified amount of tokens. * * @param walletProvider - The wallet provider to approve from. * @param args - The input arguments for the action. * @returns A message containing the approval details. */ async approve(walletProvider, args) { try { // Get token details for better error messages and validation const tokenAddress = (0, viem_1.getAddress)(args.tokenAddress); const tokenDetails = await (0, utils_1.getTokenDetails)(walletProvider, args.tokenAddress); if (!tokenDetails) { return `Error: Could not fetch token details for ${args.tokenAddress}. Please verify the token address is correct.`; } // Convert amount to wei using token decimals const amountInWei = (0, viem_1.parseUnits)(String(args.amount), tokenDetails.decimals); const hash = await walletProvider.sendTransaction({ to: tokenAddress, data: (0, viem_1.encodeFunctionData)({ abi: viem_1.erc20Abi, functionName: "approve", args: [args.spenderAddress, amountInWei], }), }); await walletProvider.waitForTransactionReceipt(hash); return `Approved ${args.amount} ${tokenDetails.name} (${args.tokenAddress}) for spender ${args.spenderAddress}.\nTransaction hash: ${hash}`; } catch (error) { return `Error approving tokens: ${error}`; } } /** * Checks the allowance for a spender of an ERC20 token. * * @param walletProvider - The wallet provider to check the allowance from. * @param args - The input arguments containing tokenAddress and spender. * @returns A message containing the allowance amount for the spender. */ async getAllowance(walletProvider, args) { try { // Get token details for proper formatting const tokenDetails = await (0, utils_1.getTokenDetails)(walletProvider, args.tokenAddress); if (!tokenDetails) { return `Error: Could not fetch token details for ${args.tokenAddress}. Please verify the token address is correct.`; } const allowance = await walletProvider.readContract({ address: args.tokenAddress, abi: viem_1.erc20Abi, functionName: "allowance", args: [walletProvider.getAddress(), args.spenderAddress], }); // Format the allowance using token decimals const formattedAllowance = (0, viem_1.formatUnits)(allowance, tokenDetails.decimals); return `Allowance for ${args.spenderAddress} to spend ${tokenDetails.name} (${args.tokenAddress}) is ${formattedAllowance} tokens`; } catch (error) { return `Error checking allowance: ${error}`; } } /** * Gets the contract address for a token symbol on the current network. * * @param walletProvider - The wallet provider to get the network from. * @param args - The input arguments for the action. * @returns A message containing the token address or an error if not found. */ async getTokenAddress(walletProvider, args) { const network = walletProvider.getNetwork(); const networkTokens = constants_1.TOKEN_ADDRESSES_BY_SYMBOLS[network.networkId ?? ""]; const tokenAddress = networkTokens?.[args.symbol]; if (tokenAddress) { return `Token address for ${args.symbol} on ${network.networkId}: ${tokenAddress}`; } // Get available token symbols for the current network const availableSymbols = networkTokens ? Object.keys(networkTokens) : []; const availableSymbolsText = availableSymbols.length > 0 ? ` Available token symbols on ${network.networkId}: ${availableSymbols.join(", ")}` : ` No token symbols are configured for ${network.networkId}`; return `Error: Token symbol "${args.symbol}" not found on ${network.networkId}.${availableSymbolsText}`; } } exports.ERC20ActionProvider = ERC20ActionProvider; __decorate([ (0, actionDecorator_1.CreateAction)({ name: "get_balance", 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 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 `, schema: schemas_1.GetBalanceSchema, }), __metadata("design:type", Function), __metadata("design:paramtypes", [wallet_providers_1.EvmWalletProvider, void 0]), __metadata("design:returntype", Promise) ], ERC20ActionProvider.prototype, "getBalance", null); __decorate([ (0, actionDecorator_1.CreateAction)({ name: "transfer", description: ` 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 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 `, schema: schemas_1.TransferSchema, }), __metadata("design:type", Function), __metadata("design:paramtypes", [wallet_providers_1.EvmWalletProvider, void 0]), __metadata("design:returntype", Promise) ], ERC20ActionProvider.prototype, "transfer", null); __decorate([ (0, actionDecorator_1.CreateAction)({ name: "approve", description: ` This tool will approve a spender to transfer ERC20 tokens from the wallet. 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 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 `, schema: schemas_1.ApproveSchema, }), __metadata("design:type", Function), __metadata("design:paramtypes", [wallet_providers_1.EvmWalletProvider, void 0]), __metadata("design:returntype", Promise) ], ERC20ActionProvider.prototype, "approve", null); __decorate([ (0, actionDecorator_1.CreateAction)({ name: "get_allowance", description: ` This tool will get the allowance amount for a spender of an ERC20 token. It takes the following inputs: - tokenAddress: The contract address of the token to check allowance for - spenderAddress: The address to check allowance for 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 `, schema: schemas_1.AllowanceSchema, }), __metadata("design:type", Function), __metadata("design:paramtypes", [wallet_providers_1.EvmWalletProvider, void 0]), __metadata("design:returntype", Promise) ], ERC20ActionProvider.prototype, "getAllowance", null); __decorate([ (0, actionDecorator_1.CreateAction)({ name: "get_erc20_token_address", description: ` This tool will get the contract address for frequently used ERC20 tokens on different networks. It takes the following input: - symbol: The token symbol (e.g. USDC, EURC, CBBTC) `, schema: schemas_1.GetTokenAddressSchema, }), __metadata("design:type", Function), __metadata("design:paramtypes", [wallet_providers_1.EvmWalletProvider, void 0]), __metadata("design:returntype", Promise) ], ERC20ActionProvider.prototype, "getTokenAddress", null); const erc20ActionProvider = () => new ERC20ActionProvider(); exports.erc20ActionProvider = erc20ActionProvider;