UNPKG

@coinbase/agentkit

Version:

Coinbase AgentKit core primitives

403 lines (396 loc) 20.9 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.baseAccountActionProvider = exports.BaseAccountActionProvider = void 0; const zod_1 = require("zod"); const viem_1 = require("viem"); const actionProvider_1 = require("../actionProvider"); const actionDecorator_1 = require("../actionDecorator"); const wallet_providers_1 = require("../../wallet-providers"); const utils_1 = require("../erc20/utils"); const schemas_1 = require("./schemas"); const utils_2 = require("./utils"); const utils_3 = require("../../utils"); const spend_permission_1 = require("@base-org/account/spend-permission"); /** * Fetch spend permissions for a user account from a spender address * * @param userAccount - The user account address * @param spenderAccount - The spender account address * @param tokenAddress - Optional token address to filter by. If not provided, returns all permissions * @returns Promise resolving to array of fetched permissions */ async function fetchUserSpendPermissions(userAccount, spenderAccount, tokenAddress) { try { const permissions = await (0, spend_permission_1.fetchPermissions)({ account: userAccount, chainId: 8453, spender: spenderAccount, }); if (tokenAddress) { return permissions.filter(p => p.permission?.token?.toLowerCase() === tokenAddress.toLowerCase()); } return permissions; } catch (error) { console.error("❌ Failed to fetch spend permissions:", error); return []; } } /** * BaseAccountActionProvider provides actions to interact with Base Account spend permissions. */ class BaseAccountActionProvider extends actionProvider_1.ActionProvider { /** * Constructor for the BaseAccountActionProvider. */ constructor() { super("base_account", []); /** * Checks if the Base Account action provider supports the given network. * * @param network - The network to check. * @returns True if the Base Account action provider supports the network, false otherwise. */ this.supportsNetwork = (network) => { // Base Account spend permissions are only supported on Base mainnet return network.networkId === "base-mainnet"; }; } /** * Lists spend permissions that have been granted to the current wallet by a Base Account. * * @param walletProvider - The wallet provider to use for listing permissions. * @param args - The input arguments for listing spend permissions. * @returns A JSON string with the list of spend permissions. */ async listBaseAccountSpendPermissions(walletProvider, args) { try { // Validate and normalize addresses const baseAccount = (0, viem_1.getAddress)(args.baseAccount); const spenderAddress = (0, viem_1.getAddress)(walletProvider.getAddress()); // Fetch permissions with retry logic const permissions = await (0, utils_3.retryWithExponentialBackoff)(() => fetchUserSpendPermissions(baseAccount, spenderAddress), 3, // Max 3 retries 1000); if (permissions.length === 0) { return JSON.stringify({ success: false, error: "No spend permissions found for Base Account. The user needs to grant spend permissions first through the UI.", baseAccount: baseAccount, spender: spenderAddress, permissionsCount: 0, }); } // Format the permissions with token details const formattedPermissions = await Promise.all(permissions.map(async (permission, index) => { const tokenAddress = permission.permission?.token || ""; const allowanceRaw = permission.permission?.allowance || 0; // Get token details for proper formatting let tokenName = "Unknown Token"; let allowanceFormatted; if (tokenAddress) { const tokenDetails = await (0, utils_1.getTokenDetails)(walletProvider, tokenAddress); if (tokenDetails) { tokenName = tokenDetails.name; allowanceFormatted = (0, viem_1.formatUnits)(allowanceRaw, tokenDetails.decimals); } } return { permissionIndex: index + 1, token: tokenAddress, tokenName: tokenName, allowance: allowanceFormatted || allowanceRaw.toString(), createdAt: permission.createdAt ? (0, utils_2.formatTimestamp)(permission.createdAt) : "Unknown", period: permission.permission?.period ? (0, utils_2.formatPeriod)(permission.permission.period) : "Unknown", start: permission.permission?.start ? (0, utils_2.formatTimestamp)(permission.permission.start) : "Unknown", end: permission.permission?.end ? (0, utils_2.formatTimestamp)(permission.permission.end) : "Unknown", }; })); return JSON.stringify({ success: true, permissionsCount: permissions.length, baseAccount: baseAccount, spender: spenderAddress, permissions: formattedPermissions, }); } catch (error) { console.error("Error listing spend permissions:", error); return JSON.stringify({ success: false, error: `Error listing spend permissions: ${error instanceof Error ? error.message : "Unknown error"}`, baseAccount: args.baseAccount, spender: walletProvider.getAddress(), }); } } /** * Uses a spend permission to transfer tokens from a Base Account to the current wallet. * * @param walletProvider - The wallet provider to use for the spend operation. * @param args - The input arguments for using the spend permission. * @returns A JSON string with the spend operation results. */ async spendFromBaseAccountPermission(walletProvider, args) { try { // Validate and normalize addresses const baseAccount = (0, viem_1.getAddress)(args.baseAccount); const spenderAddress = (0, viem_1.getAddress)(walletProvider.getAddress()); const tokenAddress = args.tokenAddress ? (0, viem_1.getAddress)(args.tokenAddress) : undefined; // Fetch permissions to find a valid one with retry logic const permissions = await (0, utils_3.retryWithExponentialBackoff)(() => fetchUserSpendPermissions(baseAccount, spenderAddress, tokenAddress), 3, // Max 3 retries 1000); if (permissions.length === 0) { const errorMsg = tokenAddress ? `No spend permissions found for Base Account with token ${tokenAddress}.` : "No spend permissions found for Base Account."; return JSON.stringify({ success: false, error: `${errorMsg} The user needs to grant spend permissions first through the UI.`, baseAccount: baseAccount, spender: spenderAddress, tokenAddress: tokenAddress, }); } // Determine which permission to use const permissionIndex = args.permissionIndex || 1; if (permissionIndex > permissions.length) { return JSON.stringify({ success: false, error: `Permission index ${permissionIndex} is out of range. Only ${permissions.length} permission(s) available.`, baseAccount: baseAccount, spender: spenderAddress, availablePermissions: permissions.length, }); } // Use the specified permission (convert to 0-based index) const _permission = permissions[permissionIndex - 1]; const permissionTokenAddress = _permission.permission?.token; if (!permissionTokenAddress) { return JSON.stringify({ success: false, error: "Permission does not have a valid token address", baseAccount: baseAccount, spender: spenderAddress, }); } // Get token details for proper conversion const tokenDetails = await (0, utils_1.getTokenDetails)(walletProvider, permissionTokenAddress); if (!tokenDetails) { return JSON.stringify({ success: false, error: `Failed to get token details for address: ${permissionTokenAddress}`, baseAccount: baseAccount, spender: spenderAddress, tokenAddress: permissionTokenAddress, }); } // Check permission status and prepare spend call data const status = await (0, utils_3.retryWithExponentialBackoff)( // eslint-disable-next-line @typescript-eslint/no-explicit-any () => (0, spend_permission_1.getPermissionStatus)(_permission), 3, // Max 3 retries 1000, // 1s base delay 2000); // Determine amount to spend - use provided amount or full remaining allowance const amountInAtomicUnits = args.amount ? (0, viem_1.parseUnits)(args.amount.toString(), tokenDetails.decimals) : status.remainingSpend; if (status.remainingSpend < amountInAtomicUnits) { const requestedAmountFormatted = args.amount?.toString() || (0, viem_1.formatUnits)(amountInAtomicUnits, tokenDetails.decimals); return JSON.stringify({ success: false, error: `Insufficient remaining allowance. Requested: ${requestedAmountFormatted} ${tokenDetails.name}, Available: ${(0, viem_1.formatUnits)(status.remainingSpend, tokenDetails.decimals)} ${tokenDetails.name}`, baseAccount: baseAccount, spender: spenderAddress, tokenAddress: permissionTokenAddress, tokenName: tokenDetails.name, requestedAmount: requestedAmountFormatted, availableAmount: (0, viem_1.formatUnits)(status.remainingSpend, tokenDetails.decimals), }); } // Prepare the spend transaction - returns an array of calls const spendCalls = await (0, utils_3.retryWithExponentialBackoff)( // eslint-disable-next-line @typescript-eslint/no-explicit-any () => (0, spend_permission_1.prepareSpendCallData)(_permission, amountInAtomicUnits), 3, // Max 3 retries 1000, // 1s base delay 2000); // Take the first call from the array (there should be one for spend operations) if (!spendCalls || spendCalls.length === 0) { throw new Error("No transaction calls returned from prepareSpendCallData"); } const callData = spendCalls[0]; // Execute the spend transaction const txHash = await walletProvider.sendTransaction({ to: callData.to, data: callData.data, value: BigInt(callData.value || "0x0"), }); const amountSpentFormatted = (0, viem_1.formatUnits)(amountInAtomicUnits, tokenDetails.decimals); return JSON.stringify({ success: true, transactionHash: txHash, amountSpent: amountSpentFormatted, tokenAddress: permissionTokenAddress, tokenName: tokenDetails.name, baseAccount: baseAccount, spender: spenderAddress, permissionIndex: permissionIndex, remainingAllowance: (0, viem_1.formatUnits)(status.remainingSpend - amountInAtomicUnits, tokenDetails.decimals), }); } catch (error) { console.error("Error using spend permission:", error); return JSON.stringify({ success: false, error: `Error using spend permission: ${error instanceof Error ? error.message : "Unknown error"}`, baseAccount: args.baseAccount, spender: walletProvider.getAddress(), amountRequested: args.amount?.toString() || "full allowance", tokenAddress: args.tokenAddress, permissionIndex: args.permissionIndex, }); } } /** * Revokes a spend permission that was previously granted by a Base Account to the current wallet. * * @param walletProvider - The wallet provider to use for the revoke operation. * @param args - The input arguments for revoking the spend permission. * @returns A JSON string with the revoke operation results. */ async revokeBaseAccountSpendPermission(walletProvider, args) { try { // Validate and normalize addresses const baseAccount = (0, viem_1.getAddress)(args.baseAccount); const spenderAddress = (0, viem_1.getAddress)(walletProvider.getAddress()); // Fetch permissions to find the one to revoke with retry logic const permissions = await (0, utils_3.retryWithExponentialBackoff)(() => fetchUserSpendPermissions(baseAccount, spenderAddress), 3, // Max 3 retries 1000); if (permissions.length === 0) { return JSON.stringify({ success: false, error: "No spend permissions found for Base Account. There are no permissions to revoke.", baseAccount: baseAccount, spender: spenderAddress, }); } // Determine which permission to revoke const permissionIndex = args.permissionIndex || 1; if (permissionIndex > permissions.length) { return JSON.stringify({ success: false, error: `Permission index ${permissionIndex} is out of range. Only ${permissions.length} permission(s) available.`, baseAccount: baseAccount, spender: spenderAddress, availablePermissions: permissions.length, }); } // Use the specified permission (convert to 0-based index) const _permission = permissions[permissionIndex - 1]; // Prepare the revoke transaction // eslint-disable-next-line @typescript-eslint/no-explicit-any const revokeCall = await (0, spend_permission_1.prepareRevokeCallData)(_permission); // Execute the revoke transaction const txHash = await walletProvider.sendTransaction({ to: revokeCall.to, data: revokeCall.data, value: BigInt(revokeCall.value || "0x0"), }); return JSON.stringify({ success: true, transactionHash: txHash, revokedPermissionIndex: permissionIndex, baseAccount: baseAccount, spender: spenderAddress, message: "Spend permission successfully revoked", }); } catch (error) { console.error("Error revoking spend permission:", error); return JSON.stringify({ success: false, error: `Error revoking spend permission: ${error instanceof Error ? error.message : "Unknown error"}`, baseAccount: args.baseAccount, spender: walletProvider.getAddress(), permissionIndex: args.permissionIndex, }); } } } exports.BaseAccountActionProvider = BaseAccountActionProvider; __decorate([ (0, actionDecorator_1.CreateAction)({ name: "list_base_account_spend_permissions", description: ` This tool lists spend permissions that have been granted to the current wallet by a Base Account. It takes the following inputs: - baseAccount: The Base Account address to check if it has granted spend permissions Important notes: - This tool is specifically designed for Base Account spend permissions - The returned period is the time duration for resetting the used allowance on a recurring basis within the start and end timestamps - The returned allowance is the maximum allowed value to spend within each period `, schema: schemas_1.ListBaseAccountSpendPermissionsSchema, }), __metadata("design:type", Function), __metadata("design:paramtypes", [wallet_providers_1.EvmWalletProvider, void 0]), __metadata("design:returntype", Promise) ], BaseAccountActionProvider.prototype, "listBaseAccountSpendPermissions", null); __decorate([ (0, actionDecorator_1.CreateAction)({ name: "spend_from_base_account_permission", description: ` This tool uses a spend permission to transfer tokens from a Base Account to the current wallet. It takes the following inputs: - baseAccount: The Base Account address that has granted the spend permission - amount: (Optional) The amount to spend in whole units of the token (e.g. 4.6 for 4.6 tokens). If not provided, will withdraw the full remaining allowance - tokenAddress: (Optional) The token contract address. If not provided, will use the first available permission token - permissionIndex: (Optional) The index of the permission to use (1-based). If not provided, the first permission will be used Important notes: - The Base Account must have previously granted a spend permission to the current agent - This tool automatically finds and uses the specified token spend permission, or the first valid permission if no token is specified - The funds are transferred from the Base Account to the current wallet after successful execution - This is specifically designed for Base Account spend permissions on Base mainnet `, schema: schemas_1.UseBaseAccountSpendPermissionSchema, }), __metadata("design:type", Function), __metadata("design:paramtypes", [wallet_providers_1.EvmWalletProvider, void 0]), __metadata("design:returntype", Promise) ], BaseAccountActionProvider.prototype, "spendFromBaseAccountPermission", null); __decorate([ (0, actionDecorator_1.CreateAction)({ name: "revoke_base_account_spend_permission", description: ` This tool revokes a spend permission that was previously granted by a Base Account to the current wallet. It takes the following inputs: - baseAccount: The Base Account address that has granted the spend permission - permissionIndex: (Optional) The index of the permission to revoke (1-based). If not provided, the first permission will be revoked Important notes: - This permanently revokes the spend permission - it cannot be undone - The Base Account must have previously granted a spend permission to the current agent - This tool automatically finds and revokes the specified token spend permission - This is specifically designed for Base Account spend permissions on Base mainnet `, schema: schemas_1.RevokeBaseAccountSpendPermissionSchema, }), __metadata("design:type", Function), __metadata("design:paramtypes", [wallet_providers_1.EvmWalletProvider, void 0]), __metadata("design:returntype", Promise) ], BaseAccountActionProvider.prototype, "revokeBaseAccountSpendPermission", null); const baseAccountActionProvider = () => new BaseAccountActionProvider(); exports.baseAccountActionProvider = baseAccountActionProvider;