@coinbase/agentkit
Version:
Coinbase AgentKit core primitives
353 lines (352 loc) • 18.3 kB
JavaScript
;
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.cdpEvmWalletActionProvider = exports.CdpEvmWalletActionProvider = void 0;
const zod_1 = require("zod");
const wallet_providers_1 = require("../../wallet-providers");
const cdpShared_1 = require("../../wallet-providers/cdpShared");
const cdpEvmWalletProvider_1 = require("../../wallet-providers/cdpEvmWalletProvider");
const actionDecorator_1 = require("../actionDecorator");
const actionProvider_1 = require("../actionProvider");
const schemas_1 = require("./schemas");
const spendPermissionUtils_1 = require("./spendPermissionUtils");
const swapUtils_1 = require("./swapUtils");
const viem_1 = require("viem");
const utils_1 = require("../../utils");
/**
* CdpEvmWalletActionProvider is an action provider for CDP EVM Wallet specific actions.
*
* This provider is scoped specifically to EVM wallets and provides actions
* that are optimized for EVM functionality, including spend permission usage.
*/
class CdpEvmWalletActionProvider extends actionProvider_1.ActionProvider {
/**
* Constructor for the CdpEvmWalletActionProvider class.
*/
constructor() {
super("cdp_evm_wallet", []);
/**
* Checks if the EVM wallet action provider supports the given network.
*
* @param network - The network to check.
* @returns True if the EVM wallet action provider supports the network, false otherwise.
*/
this.supportsNetwork = (network) => {
// EVM wallets support EVM networks in general
return network.protocolFamily === "evm";
};
}
/**
* Lists spend permissions for a smart account.
*
* @param walletProvider - The server wallet provider to use for listing permissions.
* @param args - The input arguments for listing spend permissions.
* @returns A list of spend permissions available to the current wallet.
*/
async listSpendPermissions(walletProvider, args) {
const network = walletProvider.getNetwork();
if ((0, cdpShared_1.isWalletProviderWithClient)(walletProvider)) {
if (network.protocolFamily === "evm") {
return await (0, spendPermissionUtils_1.listSpendPermissionsForSpender)(walletProvider.getClient(), args.smartAccountAddress, walletProvider.getAddress());
}
else {
return "Spend permissions are currently only supported on EVM networks.";
}
}
else {
return "Wallet provider is not a CDP Wallet Provider.";
}
}
/**
* Uses a spend permission to transfer tokens from a smart account to the current EVM wallet.
*
* @param walletProvider - The EVM wallet provider to use for the spend operation.
* @param args - The input arguments for using the spend permission.
* @returns A confirmation message with transaction details.
*/
async useSpendPermission(walletProvider, args) {
const network = walletProvider.getNetwork();
const cdpNetwork = walletProvider.getCdpSdkNetwork();
if ((0, cdpShared_1.isWalletProviderWithClient)(walletProvider)) {
if (network.protocolFamily === "evm") {
try {
const spenderAddress = walletProvider.getAddress();
const permission = await (0, spendPermissionUtils_1.findLatestSpendPermission)(walletProvider.getClient(), args.smartAccountAddress, spenderAddress);
const account = await walletProvider.getClient().evm.getAccount({
address: spenderAddress,
});
const spendResult = await account.useSpendPermission({
spendPermission: permission,
value: BigInt(args.value),
network: cdpNetwork,
});
return `Successfully spent ${args.value} tokens using spend permission. Transaction hash: ${spendResult.transactionHash}`;
}
catch (error) {
return `Failed to use spend permission: ${error}`;
}
}
else {
return "Spend permissions are currently only supported on EVM networks.";
}
}
else {
return "Wallet provider is not a CDP Wallet Provider.";
}
}
/**
* Gets a price quote for swapping tokens using the CDP Swap API.
*
* @param walletProvider - The EVM wallet provider to get the quote for.
* @param args - The input arguments for the swap price action.
* @returns A JSON string with detailed swap price quote information.
*/
async getSwapPrice(walletProvider, args) {
// Get CDP SDK network
const network = walletProvider.getNetwork();
const networkId = network.networkId;
const cdpNetwork = walletProvider.getCdpSdkNetwork();
// Check if the network is supported
if (networkId !== "base-mainnet" && networkId !== "ethereum-mainnet")
return JSON.stringify({
success: false,
error: "CDP Swap API is currently only supported on 'base-mainnet' or 'ethereum-mainnet'.",
});
try {
// Get token details
const { fromTokenDecimals, toTokenDecimals, fromTokenName, toTokenName } = await (0, swapUtils_1.getTokenDetails)(walletProvider, args.fromToken, args.toToken);
// Get swap price quote
const swapPrice = (await walletProvider.getClient().evm.getSwapPrice({
fromToken: args.fromToken,
toToken: args.toToken,
fromAmount: (0, viem_1.parseUnits)(args.fromAmount, fromTokenDecimals),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
network: cdpNetwork,
taker: walletProvider.getAddress(),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}));
const formattedResponse = {
success: true,
fromAmount: args.fromAmount,
fromTokenName: fromTokenName,
fromToken: args.fromToken,
toAmount: (0, viem_1.formatUnits)(swapPrice.toAmount, toTokenDecimals),
minToAmount: (0, viem_1.formatUnits)(swapPrice.minToAmount, toTokenDecimals),
toTokenName: toTokenName,
toToken: args.toToken,
slippageBps: args.slippageBps,
liquidityAvailable: swapPrice.liquidityAvailable,
balanceEnough: swapPrice.issues.balance === undefined,
priceOfBuyTokenInSellToken: (Number(args.fromAmount) / Number((0, viem_1.formatUnits)(swapPrice.toAmount, toTokenDecimals))).toString(),
priceOfSellTokenInBuyToken: (Number((0, viem_1.formatUnits)(swapPrice.toAmount, toTokenDecimals)) / Number(args.fromAmount)).toString(),
};
return JSON.stringify(formattedResponse);
}
catch (error) {
return JSON.stringify({
success: false,
error: `Error fetching swap price: ${error}`,
});
}
}
/**
* Swaps tokens using the CDP client.
*
* @param walletProvider - The EVM wallet provider to perform the swap with.
* @param args - The input arguments for the swap action.
* @returns A JSON string with detailed swap execution information.
*/
async swap(walletProvider, args) {
// Get CDP SDK network
const network = walletProvider.getNetwork();
const networkId = network.networkId;
const cdpNetwork = walletProvider.getCdpSdkNetwork();
// Check if the network is supported
if (networkId !== "base-mainnet" && networkId !== "ethereum-mainnet")
return JSON.stringify({
success: false,
error: "CDP Swap API is currently only supported on 'base-mainnet' or 'ethereum-mainnet'.",
});
try {
// Get token details
const { fromTokenDecimals, fromTokenName, toTokenName, toTokenDecimals } = await (0, swapUtils_1.getTokenDetails)(walletProvider, args.fromToken, args.toToken);
// Get the account
const account = await walletProvider.getClient().evm.getAccount({
address: walletProvider.getAddress(),
});
// Estimate swap price first to check liquidity, token balance and permit2 approval status
const swapPrice = await walletProvider.getClient().evm.getSwapPrice({
fromToken: args.fromToken,
toToken: args.toToken,
fromAmount: (0, viem_1.parseUnits)(args.fromAmount, fromTokenDecimals),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
network: cdpNetwork,
taker: account.address,
});
// Check if liquidity is available
if (!swapPrice.liquidityAvailable) {
return JSON.stringify({
success: false,
error: `No liquidity available to swap ${args.fromAmount} ${fromTokenName} (${args.fromToken}) to ${toTokenName} (${args.toToken})`,
});
}
// Check if balance is enough
if (swapPrice.issues.balance) {
return JSON.stringify({
success: false,
error: `Balance is not enough to perform swap. Required: ${args.fromAmount} ${fromTokenName}, but only have ${(0, viem_1.formatUnits)(swapPrice.issues.balance.currentBalance, fromTokenDecimals)} ${fromTokenName} (${args.fromToken})`,
});
}
// Check if allowance is enough
let approvalTxHash = null;
if (swapPrice.issues.allowance) {
try {
approvalTxHash = await walletProvider.sendTransaction({
to: args.fromToken,
data: (0, viem_1.encodeFunctionData)({
abi: viem_1.erc20Abi,
functionName: "approve",
args: [swapUtils_1.PERMIT2_ADDRESS, viem_1.maxUint256],
}),
});
const receipt = await walletProvider.waitForTransactionReceipt(approvalTxHash);
if (receipt.status !== "success") {
return JSON.stringify({
success: false,
error: `Approval transaction failed`,
});
}
}
catch (error) {
return JSON.stringify({
success: false,
error: `Error approving token: ${error}`,
});
}
}
// Execute swap using the all-in-one pattern with retry logic
const swapResult = await (0, utils_1.retryWithExponentialBackoff)(async () => {
return (await account.swap({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
network: cdpNetwork,
fromToken: args.fromToken,
toToken: args.toToken,
fromAmount: (0, viem_1.parseUnits)(args.fromAmount, fromTokenDecimals),
slippageBps: args.slippageBps,
signerAddress: account.address,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}));
}, 3, 5000); // Max 3 retries with 5s base delay
// Check if swap was successful
const swapReceipt = await walletProvider.waitForTransactionReceipt(swapResult.transactionHash);
if (swapReceipt.status !== "success") {
return JSON.stringify({
success: false,
error: `Swap transaction failed`,
});
}
// Format the successful response
const formattedResponse = {
success: true,
...(approvalTxHash ? { approvalTxHash } : {}),
transactionHash: swapResult.transactionHash,
fromAmount: args.fromAmount,
fromTokenName: fromTokenName,
fromToken: args.fromToken,
toAmount: (0, viem_1.formatUnits)(swapPrice.toAmount, toTokenDecimals),
minToAmount: (0, viem_1.formatUnits)(swapPrice.minToAmount, toTokenDecimals),
toTokenName: toTokenName,
toToken: args.toToken,
slippageBps: args.slippageBps,
network: networkId,
};
return JSON.stringify(formattedResponse);
}
catch (error) {
return JSON.stringify({
success: false,
error: `Swap failed: ${error}`,
});
}
}
}
exports.CdpEvmWalletActionProvider = CdpEvmWalletActionProvider;
__decorate([
(0, actionDecorator_1.CreateAction)({
name: "list_spend_permissions",
description: `This tool lists spend permissions that have been granted to the current EVM wallet by a smart account.
It takes a smart account address and returns spend permissions where the current EVM wallet is the spender.
This is useful to see what spending allowances have been granted before using them.
This action is specifically designed for EVM wallets.`,
schema: schemas_1.ListSpendPermissionsSchema,
}),
__metadata("design:type", Function),
__metadata("design:paramtypes", [wallet_providers_1.WalletProvider, void 0]),
__metadata("design:returntype", Promise)
], CdpEvmWalletActionProvider.prototype, "listSpendPermissions", null);
__decorate([
(0, actionDecorator_1.CreateAction)({
name: "use_spend_permission",
description: `This tool uses a spend permission to spend tokens on behalf of a smart account that the current EVM wallet has permission to spend.
It automatically finds the latest valid spend permission granted by the smart account to the current EVM wallet and uses it to spend the specified amount.
The smart account must have previously granted a spend permission to the current EVM wallet using createSpendPermission.
This action is specifically designed for EVM wallets and uses the EVM wallet for spend permission execution.`,
schema: schemas_1.UseSpendPermissionSchema,
}),
__metadata("design:type", Function),
__metadata("design:paramtypes", [cdpEvmWalletProvider_1.CdpEvmWalletProvider, void 0]),
__metadata("design:returntype", Promise)
], CdpEvmWalletActionProvider.prototype, "useSpendPermission", null);
__decorate([
(0, actionDecorator_1.CreateAction)({
name: "get_swap_price",
description: `
This tool fetches a price quote for swapping (trading) between two tokens using the CDP Swap API but does not execute a swap.
It takes the following inputs:
- fromToken: The contract address of the token to sell
- toToken: The contract address of the token to buy
- fromAmount: The amount of fromToken to swap in whole units (e.g. 1 ETH or 10.5 USDC)
- slippageBps: (Optional) Maximum allowed slippage in basis points (100 = 1%)
Important notes:
- The contract address for native ETH is "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
- Use fromAmount units exactly as provided, do not convert to wei or any other units
- Never assume token or address, they have to be provided as inputs. If only token symbol is provided, use the get_token_address tool if available to get the token address first
`,
schema: schemas_1.SwapSchema,
}),
__metadata("design:type", Function),
__metadata("design:paramtypes", [cdpEvmWalletProvider_1.CdpEvmWalletProvider, void 0]),
__metadata("design:returntype", Promise)
], CdpEvmWalletActionProvider.prototype, "getSwapPrice", null);
__decorate([
(0, actionDecorator_1.CreateAction)({
name: "swap",
description: `
This tool executes a token swap (trade) using the CDP Swap API.
It takes the following inputs:
- fromToken: The contract address of the token to sell
- toToken: The contract address of the token to buy
- fromAmount: The amount of fromToken to swap in whole units (e.g. 1 ETH or 10.5 USDC)
- slippageBps: (Optional) Maximum allowed slippage in basis points (100 = 1%)
Important notes:
- The contract address for native ETH is "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
- If needed, it will automatically approve the permit2 contract to spend the fromToken
- Use fromAmount units exactly as provided, do not convert to wei or any other units.
- Never assume token or address, they have to be provided as inputs. If only token symbol is provided, use the get_token_address tool if available to get the token address first
`,
schema: schemas_1.SwapSchema,
}),
__metadata("design:type", Function),
__metadata("design:paramtypes", [cdpEvmWalletProvider_1.CdpEvmWalletProvider, void 0]),
__metadata("design:returntype", Promise)
], CdpEvmWalletActionProvider.prototype, "swap", null);
const cdpEvmWalletActionProvider = () => new CdpEvmWalletActionProvider();
exports.cdpEvmWalletActionProvider = cdpEvmWalletActionProvider;