diff --git a/typescript/.changeset/poor-tigers-happen.md b/typescript/.changeset/poor-tigers-happen.md new file mode 100644 index 000000000..8b14775b9 --- /dev/null +++ b/typescript/.changeset/poor-tigers-happen.md @@ -0,0 +1,6 @@ +--- +"create-onchain-agent": minor +"@coinbase/agentkit": minor +--- + +Migrate to CDP Wallet API v2 as the default when using CDP wallets diff --git a/typescript/agentkit/README.md b/typescript/agentkit/README.md index 8caa670e7..7d75b2749 100644 --- a/typescript/agentkit/README.md +++ b/typescript/agentkit/README.md @@ -18,14 +18,27 @@ AgentKit is a framework for easily enabling AI agents to take actions onchain. I - [Adding Actions to your Action Provider that use a Wallet Provider](#adding-actions-to-your-action-provider-that-use-a-wallet-provider) - [Adding an Action Provider to your AgentKit instance](#adding-an-action-provider-to-your-agentkit-instance) - [EVM Wallet Providers](#evm-wallet-providers) - - [CdpV2EvmWalletProvider](#cdpv2evmwalletprovider) - - [CdpWalletProvider](#cdpwalletprovider) + - [CdpEvmWalletProvider](#cdpevmwalletprovider) + - [Basic Configuration](#basic-configuration) + - [Using an existing wallet](#using-an-existing-wallet) + - [Creating a new wallet](#creating-a-new-wallet) + - [Environment Variables](#environment-variables) + - [Exporting a wallet](#exporting-a-wallet) + - [CdpSmartWalletProvider](#cdpsmartwalletprovider) + - [Basic Configuration](#basic-configuration-1) + - [Using an Existing Smart Wallet](#using-an-existing-smart-wallet) + - [Specifying an Owner Account](#specifying-an-owner-account) + - [Creating a New Smart Wallet](#creating-a-new-smart-wallet) + - [Environment Variables](#environment-variables-1) + - [Exporting Smart Wallet Information](#exporting-smart-wallet-information) + - [Key Differences from Regular Wallets](#key-differences-from-regular-wallets) + - [LegacyCdpWalletProvider](#legacycdpwalletprovider) - [Network Configuration](#network-configuration) - [Configuring from an existing CDP API Wallet](#configuring-from-an-existing-cdp-api-wallet) - [Configuring from a mnemonic phrase](#configuring-from-a-mnemonic-phrase) - [Exporting a wallet](#exporting-a-wallet) - [Importing a wallet from WalletData JSON string](#importing-a-wallet-from-walletdata-json-string) - - [Configuring gas parameters](#configuring-cdpwalletprovider-gas-parameters) + - [Configuring gas parameters](#configuring-legacycdpwalletprovider-gas-parameters) - [ViemWalletProvider](#viemwalletprovider) - [Configuring gas parameters](#configuring-viemwalletprovider-gas-parameters) - [PrivyWalletProvider](#privywalletprovider) @@ -37,7 +50,7 @@ AgentKit is a framework for easily enabling AI agents to take actions onchain. I - [Configuring from PrivyWalletProvider](#configuring-from-privywalletprovider) - [Configuring from ViemWalletProvider](#configuring-from-viemwalletprovider) - [SVM Wallet Providers](#svm-wallet-providers) - - [CdpV2SolanaWalletProvider](#cdpv2solanawalletprovider) + - [CdpSolanaWalletProvider](#cdpsolanawalletprovider) - [SolanaKeypairWalletProvider](#solanakeypairwalletprovider) - [Network Configuration](#solana-network-configuration) - [RPC URL Configuration](#rpc-url-configuration) @@ -49,7 +62,8 @@ AgentKit is a framework for easily enabling AI agents to take actions onchain. I ## Getting Started -*Prerequisites*: +_Prerequisites_: + - [Node.js 18+](https://nodejs.org/en/download/) - [CDP Secret API Key](https://docs.cdp.coinbase.com/get-started/docs/cdp-api-keys#creating-secret-api-keys) @@ -87,13 +101,13 @@ const agentKit = await AgentKit.from({ import { CdpWalletProvider } from "@coinbase/agentkit"; const walletProvider = await CdpWalletProvider.configureWithWallet({ - apiKeyId: "CDP API KEY NAME", - apiKeyPrivate: "CDP API KEY SECRET", - networkId: "base-mainnet", + apiKeyId: "CDP API KEY NAME", + apiKeyPrivate: "CDP API KEY SECRET", + networkId: "base-mainnet", }); const agentKit = await AgentKit.from({ - walletProvider, + walletProvider, }); ``` @@ -103,20 +117,21 @@ const agentKit = await AgentKit.from({ import { cdpApiActionProvider, pythActionProvider } from "@coinbase/agentkit"; const agentKit = await AgentKit.from({ - walletProvider, - actionProviders: [ - cdpApiActionProvider({ - apiKeyId: "CDP API KEY NAME", - apiKeyPrivate: "CDP API KEY SECRET", - }), - pythActionProvider(), - ], + walletProvider, + actionProviders: [ + cdpApiActionProvider({ + apiKeyId: "CDP API KEY NAME", + apiKeyPrivate: "CDP API KEY SECRET", + }), + pythActionProvider(), + ], }); ``` ### Use the agent's actions with a framework extension. For example, using LangChain + OpenAI. -*Prerequisites*: +_Prerequisites_: + - [OpenAI API Key](https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key) - Set `OPENAI_API_KEY` environment variable. @@ -132,16 +147,17 @@ import { ChatOpenAI } from "@langchain/openai"; const tools = await getLangChainTools(agentKit); const llm = new ChatOpenAI({ - model: "gpt-4o-mini", + model: "gpt-4o-mini", }); const agent = createReactAgent({ - llm, - tools, + llm, + tools, }); ``` ## Action Providers +
Across @@ -165,27 +181,6 @@ const agent = createReactAgent({
-CDP Wallet - - - - - - - - - - - - - - - - - -
deploy_contractDeploys a custom smart contract using specified Solidity version and constructor arguments.
deploy_nftDeploys a standard ERC-721 NFT contract with configurable name, symbol, and metadata URI.
deploy_tokenDeploys a standard ERC-20 token contract with configurable name, symbol, and initial supply.
tradeExecutes a token swap between two assets at current market rates on mainnet networks.
-
-
Compound @@ -454,12 +449,12 @@ import { ActionProvider, WalletProvider, Network } from "@coinbase/agentkit"; // Define an action provider that uses a wallet provider. class MyActionProvider extends ActionProvider { - constructor() { - super("my-action-provider", []); - } + constructor() { + super("my-action-provider", []); + } - // Define if the action provider supports the given network - supportsNetwork = (network: Network) => true; + // Define if the action provider supports the given network + supportsNetwork = (network: Network) => true; } ``` @@ -473,11 +468,11 @@ Creating actions with the `@CreateAction` decorator requires the following compi ```json { - "compilerOptions": { - "experimentalDecorators": true, - "emitDecoratorMetadata": true - } -} + "compilerOptions": { + "experimentalDecorators": true, + "emitDecoratorMetadata": true + } +} ``` #### Steps to create an action @@ -498,20 +493,20 @@ export const MyActionSchema = z.object({ import { ActionProvider, WalletProvider, Network, CreateAction } from "@coinbase/agentkit"; class MyActionProvider extends ActionProvider { - constructor() { - super("my-action-provider", []); - } - - @CreateAction({ - name: "my-action", - description: "My action description", - schema: MyActionSchema, - }) - async myAction(args: z.infer): Promise { - return args.myField; - } - - supportsNetwork = (network: Network) => true; + constructor() { + super("my-action-provider", []); + } + + @CreateAction({ + name: "my-action", + description: "My action description", + schema: MyActionSchema, + }) + async myAction(args: z.infer): Promise { + return args.myField; + } + + supportsNetwork = (network: Network) => true; } export const myActionProvider = () => new MyActionProvider(); @@ -523,24 +518,27 @@ Actions that use a wallet provider can be defined as instance methods on the act ```typescript class MyActionProvider extends ActionProvider { - constructor() { - super("my-action-provider", []); - } - - @CreateAction({ - name: "my-action", - description: "My action description", - schema: MyActionSchema, - }) - async myAction(walletProvider: WalletProvider, args: z.infer): Promise { - return walletProvider.signMessage(args.myField); - } - - supportsNetwork = (network: Network) => true; + constructor() { + super("my-action-provider", []); + } + + @CreateAction({ + name: "my-action", + description: "My action description", + schema: MyActionSchema, + }) + async myAction( + walletProvider: WalletProvider, + args: z.infer, + ): Promise { + return walletProvider.signMessage(args.myField); + } + + supportsNetwork = (network: Network) => true; } ``` -### Adding an Action Provider to your AgentKit instance. +### Adding an Action Provider to your AgentKit instance. This gives your agent access to the actions defined in the action provider. @@ -557,26 +555,27 @@ const agentKit = new AgentKit({ Wallet providers give an agent access to a wallet. AgentKit currently supports the following wallet providers: EVM: -- [CdpV2EvmWalletProvider](https://github.com/coinbase/agentkit/blob/main/typescript/agentkit/src/wallet-providers/cdpV2EvmWalletProvider.ts) -- [CdpWalletProvider](https://github.com/coinbase/agentkit/blob/main/typescript/agentkit/src/wallet-providers/cdpWalletProvider.ts) + +- [CdpEvmWalletProvider](https://github.com/coinbase/agentkit/blob/main/typescript/agentkit/src/wallet-providers/cdpEvmWalletProvider.ts) +- [CdpSmartWalletProvider](https://github.com/coinbase/agentkit/blob/main/typescript/agentkit/src/wallet-providers/cdpSmartWalletProvider.ts) - [ViemWalletProvider](https://github.com/coinbase/agentkit/blob/main/typescript/agentkit/src/wallet-providers/viemWalletProvider.ts) - [PrivyWalletProvider](https://github.com/coinbase/agentkit/blob/main/typescript/agentkit/src/wallet-providers/privyWalletProvider.ts) - [ZeroDevWalletProvider](https://github.com/coinbase/agentkit/blob/main/typescript/agentkit/src/wallet-providers/zeroDevWalletProvider.ts) -### CdpV2EvmWalletProvider +### CdpEvmWalletProvider -The `CdpV2EvmWalletProvider` is a wallet provider that uses the Coinbase Developer Platform (CDP) V2 API. It provides a more modern and streamlined interface for interacting with CDP wallets. +The `CdpEvmWalletProvider` is a wallet provider that uses the Coinbase Developer Platform (CDP) [v2 Wallet API](https://docs.cdp.coinbase.com/wallet-api/v2/introduction/welcome). It provides a more modern and streamlined interface for interacting with CDP wallets. #### Basic Configuration ```typescript -import { CdpV2EvmWalletProvider } from "@coinbase/agentkit"; +import { CdpEvmWalletProvider } from "@coinbase/agentkit"; -const walletProvider = await CdpV2EvmWalletProvider.configureWithWallet({ - apiKeyId: "CDP_API_KEY_ID", - apiKeySecret: "CDP_API_KEY_SECRET", - walletSecret: "CDP_WALLET_SECRET", - networkId: "base-sepolia", // Optional, defaults to "base-sepolia" +const walletProvider = await CdpEvmWalletProvider.configureWithWallet({ + apiKeyId: "CDP_API_KEY_ID", + apiKeySecret: "CDP_API_KEY_SECRET", + walletSecret: "CDP_WALLET_SECRET", + networkId: "base-sepolia", // Optional, defaults to "base-sepolia" }); ``` @@ -585,14 +584,14 @@ const walletProvider = await CdpV2EvmWalletProvider.configureWithWallet({ You can configure the provider with an existing wallet by providing the wallet's address: ```typescript -import { CdpV2EvmWalletProvider } from "@coinbase/agentkit"; - -const walletProvider = await CdpV2EvmWalletProvider.configureWithWallet({ - apiKeyId: "CDP_API_KEY_ID", - apiKeySecret: "CDP_API_KEY_SECRET", - walletSecret: "CDP_WALLET_SECRET", - address: "0x...", // The address of an existing wallet - networkId: "base-sepolia", +import { CdpEvmWalletProvider } from "@coinbase/agentkit"; + +const walletProvider = await CdpEvmWalletProvider.configureWithWallet({ + apiKeyId: "CDP_API_KEY_ID", + apiKeySecret: "CDP_API_KEY_SECRET", + walletSecret: "CDP_WALLET_SECRET", + address: "0x...", // The address of an existing wallet + networkId: "base-sepolia", }); ``` @@ -601,14 +600,124 @@ const walletProvider = await CdpV2EvmWalletProvider.configureWithWallet({ To create a new wallet, you can provide an idempotency key. The same idempotency key will always generate the same wallet address, and these keys are valid for 24 hours: ```typescript -import { CdpV2EvmWalletProvider } from "@coinbase/agentkit"; - -const walletProvider = await CdpV2EvmWalletProvider.configureWithWallet({ - apiKeyId: "CDP_API_KEY_ID", - apiKeySecret: "CDP_API_KEY_SECRET", - walletSecret: "CDP_WALLET_SECRET", - idempotencyKey: "unique-key-123", // Optional, if not provided a new wallet will be created - networkId: "base-sepolia", +import { CdpEvmWalletProvider } from "@coinbase/agentkit"; + +const walletProvider = await CdpEvmWalletProvider.configureWithWallet({ + apiKeyId: "CDP_API_KEY_ID", + apiKeySecret: "CDP_API_KEY_SECRET", + walletSecret: "CDP_WALLET_SECRET", + idempotencyKey: "unique-key-123", // Optional, if not provided a new wallet will be created + networkId: "base-sepolia", +}); +``` + +#### Environment Variables + +The provider can also be configured using environment variables: + +```typescript +// Environment variables: +// CDP_API_KEY_ID=your_api_key_id +// CDP_API_KEY_SECRET=your_api_key_secret +// CDP_WALLET_SECRET=your_wallet_secret +// NETWORK_ID=base-sepolia (optional) +// IDEMPOTENCY_KEY=unique-key-123 (optional) + +const walletProvider = await CdpEvmWalletProvider.configureWithWallet(); +``` + +#### Exporting a wallet + +The `CdpEvmWalletProvider` can export a wallet by calling the `exportWallet` method: + +```typescript +const walletData = await walletProvider.exportWallet(); +``` + +### CdpSmartWalletProvider + +The `CdpSmartWalletProvider` is a wallet provider that uses the Coinbase Developer Platform (CDP) [Smart Wallets](https://docs.cdp.coinbase.com/wallet-api/docs/smart-wallets). Smart wallets are ERC-4337 compliant smart contract wallets that provide enhanced features like gasless transactions, batch operations, and account recovery. + +**Note:** Smart wallets are currently only supported on Base networks (base-sepolia and base-mainnet). + +#### Basic Configuration + +```typescript +import { CdpSmartWalletProvider } from "@coinbase/agentkit"; + +const walletProvider = await CdpSmartWalletProvider.configureWithWallet({ + apiKeyId: "CDP_API_KEY_ID", + apiKeySecret: "CDP_API_KEY_SECRET", + walletSecret: "CDP_WALLET_SECRET", + networkId: "base-sepolia", // Optional, defaults to "base-sepolia" +}); +``` + +#### Using an Existing Smart Wallet + +You can configure the provider with an existing smart wallet by providing either the wallet's address or name: + +```typescript +import { CdpSmartWalletProvider } from "@coinbase/agentkit"; + +// Using smart wallet address +const walletProvider = await CdpSmartWalletProvider.configureWithWallet({ + apiKeyId: "CDP_API_KEY_ID", + apiKeySecret: "CDP_API_KEY_SECRET", + walletSecret: "CDP_WALLET_SECRET", + address: "0x...", // The address of an existing smart wallet + networkId: "base-sepolia", +}); + +// Or using smart wallet name +const walletProvider = await CdpSmartWalletProvider.configureWithWallet({ + apiKeyId: "CDP_API_KEY_ID", + apiKeySecret: "CDP_API_KEY_SECRET", + walletSecret: "CDP_WALLET_SECRET", + smartAccountName: "my-smart-wallet", // The name of an existing smart wallet + networkId: "base-sepolia", +}); +``` + +#### Specifying an Owner Account + +Smart wallets require an owner account. You can specify an existing owner account by providing its address or the account object itself: + +```typescript +import { CdpSmartWalletProvider } from "@coinbase/agentkit"; + +// Using owner address +const walletProvider = await CdpSmartWalletProvider.configureWithWallet({ + apiKeyId: "CDP_API_KEY_ID", + apiKeySecret: "CDP_API_KEY_SECRET", + walletSecret: "CDP_WALLET_SECRET", + owner: "0x...", // The address of the owner account + networkId: "base-sepolia", +}); + +// Using owner account object +const walletProvider = await CdpSmartWalletProvider.configureWithWallet({ + apiKeyId: "CDP_API_KEY_ID", + apiKeySecret: "CDP_API_KEY_SECRET", + walletSecret: "CDP_WALLET_SECRET", + owner: ownerAccount, // An EvmServerAccount or PrivateKeyAccount object + networkId: "base-sepolia", +}); +``` + +#### Creating a New Smart Wallet + +To create a new smart wallet, provide an idempotency key. The same idempotency key will always generate the same owner account address, and these keys are valid for 24 hours: + +```typescript +import { CdpSmartWalletProvider } from "@coinbase/agentkit"; + +const walletProvider = await CdpSmartWalletProvider.configureWithWallet({ + apiKeyId: "CDP_API_KEY_ID", + apiKeySecret: "CDP_API_KEY_SECRET", + walletSecret: "CDP_WALLET_SECRET", + idempotencyKey: "unique-key-123", // Optional, if not provided a new owner account will be created + networkId: "base-sepolia", }); ``` @@ -624,64 +733,87 @@ The provider can also be configured using environment variables: // NETWORK_ID=base-sepolia (optional) // IDEMPOTENCY_KEY=unique-key-123 (optional) -const walletProvider = await CdpV2EvmWalletProvider.configureWithWallet(); +const walletProvider = await CdpSmartWalletProvider.configureWithWallet(); ``` -### CdpWalletProvider +#### Exporting Smart Wallet Information -The `CdpWalletProvider` is a wallet provider that uses the Coinbase Developer Platform (CDP) [API Wallet](https://docs.cdp.coinbase.com/wallet-api/docs/welcome). +The `CdpSmartWalletProvider` can export wallet information by calling the `exportWallet` method: + +```typescript +const walletData = await walletProvider.exportWallet(); + +// walletData will be in the following format: +{ + name: string | undefined; // The smart wallet name (if set) + address: string; // The smart wallet address + ownerAddress: string; // The owner account address +} +``` + +#### Key Differences from Regular Wallets + +1. **User Operations**: Smart wallets use ERC-4337 user operations instead of regular transactions +2. **No Direct Transaction Signing**: Smart wallets cannot sign transactions directly; all operations go through the user operation flow +3. **Gasless Transactions**: Smart wallets can be configured to use paymasters for sponsored transactions +4. **Batch Operations**: Multiple operations can be bundled into a single user operation +5. **Base Networks Only**: Currently limited to base-sepolia and base-mainnet + +### LegacyCdpWalletProvider + +The `LegacyCdpWalletProvider` is a wallet provider that uses the Coinbase Developer Platform (CDP) [v1 Wallet API](https://docs.cdp.coinbase.com/wallet-api/v1/introduction/welcome). #### Network Configuration -The `CdpWalletProvider` can be configured to use a specific network by passing the `networkId` parameter to the `configureWithWallet` method. The `networkId` is the ID of the network you want to use. You can find a list of [supported networks on the CDP API docs](https://docs.cdp.coinbase.com/cdp-apis/docs/networks). +The `LegacyCdpWalletProvider` can be configured to use a specific network by passing the `networkId` parameter to the `configureWithWallet` method. The `networkId` is the ID of the network you want to use. You can find a list of [supported networks on the CDP API docs](https://docs.cdp.coinbase.com/cdp-apis/docs/networks). ```typescript -import { CdpWalletProvider } from "@coinbase/agentkit"; +import { LegacyCdpWalletProvider } from "@coinbase/agentkit"; const walletProvider = await CdpWalletProvider.configureWithWallet({ - apiKeyId: "CDP API KEY NAME", - apiKeyPrivate: "CDP API KEY SECRET", - networkId: "base-mainnet", + apiKeyId: "CDP API KEY NAME", + apiKeyPrivate: "CDP API KEY SECRET", + networkId: "base-mainnet", }); ``` #### Configuring from an existing CDP API Wallet -If you already have a CDP API Wallet, you can configure the `CdpWalletProvider` by passing the `wallet` parameter to the `configureWithWallet` method. +If you already have a CDP API Wallet, you can configure the `LegacyCdpWalletProvider` by passing the `wallet` parameter to the `configureWithWallet` method. ```typescript -import { CdpWalletProvider } from "@coinbase/agentkit"; +import { LegacyCdpWalletProvider } from "@coinbase/agentkit"; import { Wallet } from "@coinbase/coinbase-sdk"; -const walletProvider = await CdpWalletProvider.configureWithWallet({ - wallet, - apiKeyId: "CDP API KEY NAME", - apiKeyPrivate: "CDP API KEY SECRET", +const walletProvider = await LegacyCdpWalletProvider.configureWithWallet({ + wallet, + apiKeyId: "CDP API KEY NAME", + apiKeyPrivate: "CDP API KEY SECRET", }); ``` #### Configuring from a mnemonic phrase -The `CdpWalletProvider` can be configured from a mnemonic phrase by passing the `mnemonicPhrase` and `networkId` parameters to the `configureWithWallet` method. If `networkId` is not defined, the `CdpWalletProvider` will fall back to the env var `NETWORK_ID`, and if that is not defined, it will default to `base-sepolia`. +The `LegacyCdpWalletProvider` can be configured from a mnemonic phrase by passing the `mnemonicPhrase` and `networkId` parameters to the `configureWithWallet` method. If `networkId` is not defined, the `LegacyCdpWalletProvider` will fall back to the env var `NETWORK_ID`, and if that is not defined, it will default to `base-sepolia`. ```typescript -import { CdpWalletProvider } from "@coinbase/agentkit"; +import { LegacyCdpWalletProvider } from "@coinbase/agentkit"; -const walletProvider = await CdpWalletProvider.configureWithWallet({ - mnemonicPhrase: "MNEMONIC PHRASE", - networkId: "base-sepolia", +const walletProvider = await LegacyCdpWalletProvider.configureWithWallet({ + mnemonicPhrase: "MNEMONIC PHRASE", + networkId: "base-sepolia", }); ``` #### Exporting a wallet -The `CdpWalletProvider` can export a wallet by calling the `exportWallet` method. +The `LegacyCdpWalletProvider` can export a wallet by calling the `exportWallet` method. ```typescript -import { CdpWalletProvider } from "@coinbase/agentkit"; +import { LegacyCdpWalletProvider } from "@coinbase/agentkit"; -const walletProvider = await CdpWalletProvider.configureWithWallet({ - mnemonicPhrase: "MNEMONIC PHRASE", - networkId: "base-sepolia", +const walletProvider = await LegacyCdpWalletProvider.configureWithWallet({ + mnemonicPhrase: "MNEMONIC PHRASE", + networkId: "base-sepolia", }); const walletData = await walletProvider.exportWallet(); @@ -689,39 +821,38 @@ const walletData = await walletProvider.exportWallet(); #### Importing a wallet from `WalletData` JSON string -The `CdpWalletProvider` can import a wallet from a `WalletData` JSON string by passing the `cdpWalletData` parameter to the `configureWithWallet` method. +The `LegacyCdpWalletProvider` can import a wallet from a `WalletData` JSON string by passing the `cdpWalletData` parameter to the `configureWithWallet` method. ```typescript -import { CdpWalletProvider } from "@coinbase/agentkit"; +import { LegacyCdpWalletProvider } from "@coinbase/agentkit"; -const walletProvider = await CdpWalletProvider.configureWithWallet({ - cdpWalletData: "WALLET DATA JSON STRING", - apiKeyId: "CDP API KEY NAME", - apiKeyPrivate: "CDP API KEY SECRET", +const walletProvider = await LegacyCdpWalletProvider.configureWithWallet({ + cdpWalletData: "WALLET DATA JSON STRING", + apiKeyId: "CDP API KEY NAME", + apiKeyPrivate: "CDP API KEY SECRET", }); ``` -#### Configuring CdpWalletProvider gas parameters +#### Configuring LegacyCdpWalletProvider gas parameters -The `CdpWalletProvider` also exposes parameters for effecting the gas calculations. +The `LegacyCdpWalletProvider` also exposes parameters for effecting the gas calculations. ```typescript -import { CdpWalletProvider } from "@coinbase/agentkit"; - -const walletProvider = await CdpWalletProvider.configureWithWallet({ - cdpWalletData: "WALLET DATA JSON STRING", - apiKeyId: "CDP API KEY NAME", - apiKeyPrivate: "CDP API KEY SECRET", - gas: { - gasLimitMultiplier: 2.0, // Adjusts gas limit estimation - feePerGasMultiplier: 2.0, // Adjusts max fee per gas - } +import { LegacyCdpWalletProvider } from "@coinbase/agentkit"; + +const walletProvider = await LegacyCdpWalletProvider.configureWithWallet({ + cdpWalletData: "WALLET DATA JSON STRING", + apiKeyId: "CDP API KEY NAME", + apiKeyPrivate: "CDP API KEY SECRET", + gas: { + gasLimitMultiplier: 2.0, // Adjusts gas limit estimation + feePerGasMultiplier: 2.0, // Adjusts max fee per gas + }, }); ``` **Note**: Gas parameters only impact the `walletProvider.sendTransaction` behavior. Actions that do not rely on direct transaction calls, such as `deploy_token`, `deploy_contract`, and `native_transfer`, remain unaffected. - ### ViemWalletProvider The `ViemWalletProvider` is a wallet provider that uses the [Viem library](https://viem.sh/docs/getting-started). It is useful for interacting with any EVM-compatible chain. @@ -768,8 +899,8 @@ const client = createWalletClient({ }); const walletProvider = new ViemWalletProvider(client, { - gasLimitMultiplier: 2.0, // Adjusts gas limit estimation - feePerGasMultiplier: 2.0, // Adjusts max fee per gas + gasLimitMultiplier: 2.0, // Adjusts gas limit estimation + feePerGasMultiplier: 2.0, // Adjusts max fee per gas }); ``` @@ -784,12 +915,12 @@ import { PrivyWalletProvider } from "@coinbase/agentkit"; // Configure Server Wallet Provider const config = { - appId: "PRIVY_APP_ID", - appSecret: "PRIVY_APP_SECRET", - chainId: "84532", // base-sepolia - walletId: "PRIVY_WALLET_ID", // optional, otherwise a new wallet will be created - authorizationPrivateKey: "PRIVY_WALLET_AUTHORIZATION_PRIVATE_KEY", // optional, required if your account is using authorization keys - authorizationKeyId: "PRIVY_WALLET_AUTHORIZATION_KEY_ID", // optional, only required to create a new wallet if walletId is not provided + appId: "PRIVY_APP_ID", + appSecret: "PRIVY_APP_SECRET", + chainId: "84532", // base-sepolia + walletId: "PRIVY_WALLET_ID", // optional, otherwise a new wallet will be created + authorizationPrivateKey: "PRIVY_WALLET_AUTHORIZATION_PRIVATE_KEY", // optional, required if your account is using authorization keys + authorizationKeyId: "PRIVY_WALLET_AUTHORIZATION_KEY_ID", // optional, only required to create a new wallet if walletId is not provided }; const walletProvider = await PrivyWalletProvider.configureWithWallet(config); @@ -804,12 +935,12 @@ import { PrivyWalletProvider } from "@coinbase/agentkit"; // Configure Embedded Wallet Provider const config = { - appId: "PRIVY_APP_ID", - appSecret: "PRIVY_APP_SECRET", - authorizationPrivateKey: "PRIVY_WALLET_AUTHORIZATION_PRIVATE_KEY", - walletId: "PRIVY_DELEGATED_WALLET_ID", // The ID of the wallet that was delegated to your server - networkId: "base-mainnet", // or any supported network - walletType: "embedded" // Specify "embedded" to use the embedded wallet provider + appId: "PRIVY_APP_ID", + appSecret: "PRIVY_APP_SECRET", + authorizationPrivateKey: "PRIVY_WALLET_AUTHORIZATION_PRIVATE_KEY", + walletId: "PRIVY_DELEGATED_WALLET_ID", // The ID of the wallet that was delegated to your server + networkId: "base-mainnet", // or any supported network + walletType: "embedded", // Specify "embedded" to use the embedded wallet provider }; const walletProvider = await PrivyWalletProvider.configureWithWallet(config); @@ -840,23 +971,23 @@ When using authorization keys, you must provide the `authorizationPrivateKey` an #### Exporting Privy Wallet information -The `PrivyWalletProvider` can export wallet information by calling the `exportWallet` method. +The `PrivyWalletProvider` can export wallet information by calling the `exportWallet` method. ```typescript const walletData = await walletProvider.exportWallet(); // For server wallets, walletData will be in the following format: { - walletId: string; - authorizationKey: string | undefined; - chainId: string | undefined; + walletId: string; + authorizationKey: string | undefined; + chainId: string | undefined; } // For embedded wallets, walletData will be in the following format: { - walletId: string; - networkId: string; - chainId: string | undefined; + walletId: string; + networkId: string; + chainId: string | undefined; } ``` @@ -884,11 +1015,11 @@ const walletProvider = await SmartWalletProvider.configureWithWallet({ ### ZeroDevWalletProvider -The `ZeroDevWalletProvider` is a wallet provider that uses [ZeroDev](https://docs.zerodev.app/) smart accounts. It supports features like chain abstraction, gasless transactions, batched transactions, and more. +The `ZeroDevWalletProvider` is a wallet provider that uses [ZeroDev](https://docs.zerodev.app/) smart accounts. It supports features like chain abstraction, gasless transactions, batched transactions, and more. -In the context of Agent Kit, "chain abstraction" means that the agent can spend funds across chains without explicitly bridging. For example, if you send funds to the agent's address on Base, the agent will be able to spend the funds on any supported EVM chains such as Arbitrum and Optimism. +In the context of Agent Kit, "chain abstraction" means that the agent can spend funds across chains without explicitly bridging. For example, if you send funds to the agent's address on Base, the agent will be able to spend the funds on any supported EVM chains such as Arbitrum and Optimism. -The ZeroDev wallet provider does not itself manage keys. Rather, it can be used with any EVM wallet provider (e.g. CDP/Privy/Viem) which serves as the "signer" for the ZeroDev smart account. +The ZeroDev wallet provider does not itself manage keys. Rather, it can be used with any EVM wallet provider (e.g. CDP/Privy/Viem) which serves as the "signer" for the ZeroDev smart account. #### Configuring from CdpWalletProvider @@ -897,17 +1028,17 @@ import { ZeroDevWalletProvider, CdpWalletProvider } from "@coinbase/agentkit"; // First create a CDP wallet provider as the signer const cdpWalletProvider = await CdpWalletProvider.configureWithWallet({ - apiKeyId: "CDP API KEY NAME", - apiKeyPrivate: "CDP API KEY SECRET", - networkId: "base-mainnet", + apiKeyId: "CDP API KEY NAME", + apiKeyPrivate: "CDP API KEY SECRET", + networkId: "base-mainnet", }); // Configure ZeroDev Wallet Provider with CDP signer const walletProvider = await ZeroDevWalletProvider.configureWithWallet({ - signer: cdpWalletProvider.toSigner(), - projectId: "ZERODEV_PROJECT_ID", - entryPointVersion: "0.7" as const, - networkId: "base-mainnet", + signer: cdpWalletProvider.toSigner(), + projectId: "ZERODEV_PROJECT_ID", + entryPointVersion: "0.7" as const, + networkId: "base-mainnet", }); ``` @@ -918,17 +1049,17 @@ import { ZeroDevWalletProvider, PrivyWalletProvider } from "@coinbase/agentkit"; // First create a Privy wallet provider as the signer const privyWalletProvider = await PrivyWalletProvider.configureWithWallet({ - appId: "PRIVY_APP_ID", - appSecret: "PRIVY_APP_SECRET", - chainId: "8453", // base-mainnet + appId: "PRIVY_APP_ID", + appSecret: "PRIVY_APP_SECRET", + chainId: "8453", // base-mainnet }); // Configure ZeroDev Wallet Provider with Privy signer const walletProvider = await ZeroDevWalletProvider.configureWithWallet({ - signer: privyWalletProvider.toSigner(), - projectId: "ZERODEV_PROJECT_ID", - entryPointVersion: "0.7" as const, - networkId: "base-mainnet", + signer: privyWalletProvider.toSigner(), + projectId: "ZERODEV_PROJECT_ID", + entryPointVersion: "0.7" as const, + networkId: "base-mainnet", }); ``` @@ -948,15 +1079,15 @@ const viemWalletProvider = new ViemWalletProvider( account, chain: base, transport: http(), - }) + }), ); // Configure ZeroDev Wallet Provider with Viem signer const walletProvider = await ZeroDevWalletProvider.configureWithWallet({ - signer: viemWalletProvider.toSigner(), - projectId: "ZERODEV_PROJECT_ID", - entryPointVersion: "0.7" as const, - networkId: "base-mainnet", + signer: viemWalletProvider.toSigner(), + projectId: "ZERODEV_PROJECT_ID", + entryPointVersion: "0.7" as const, + networkId: "base-mainnet", }); ``` @@ -965,6 +1096,7 @@ const walletProvider = await ZeroDevWalletProvider.configureWithWallet({ Wallet providers give an agent access to a wallet. AgentKit currently supports the following wallet providers: SVM: + - [CdpV2SolanaWalletProvider](https://github.com/coinbase/agentkit/blob/main/typescript/agentkit/src/wallet-providers/cdpV2SolanaWalletProvider.ts) - [SolanaKeypairWalletProvider](https://github.com/coinbase/agentkit/blob/main/typescript/agentkit/src/wallet-providers/solanaKeypairWalletProvider.ts) - [PrivyWalletProvider](https://github.com/coinbase/agentkit/blob/main/typescript/agentkit/src/wallet-providers/privySvmWalletProvider.ts) @@ -979,10 +1111,10 @@ The `CdpV2SolanaWalletProvider` is a wallet provider that uses the Coinbase Deve import { CdpV2SolanaWalletProvider } from "@coinbase/agentkit"; const walletProvider = await CdpV2SolanaWalletProvider.configureWithWallet({ - apiKeyId: "CDP_API_KEY_ID", - apiKeySecret: "CDP_API_KEY_SECRET", - walletSecret: "CDP_WALLET_SECRET", - networkId: "solana-devnet", // Optional, defaults to "solana-devnet" + apiKeyId: "CDP_API_KEY_ID", + apiKeySecret: "CDP_API_KEY_SECRET", + walletSecret: "CDP_WALLET_SECRET", + networkId: "solana-devnet", // Optional, defaults to "solana-devnet" }); ``` @@ -994,11 +1126,11 @@ You can configure the provider with an existing wallet by providing the wallet's import { CdpV2SolanaWalletProvider } from "@coinbase/agentkit"; const walletProvider = await CdpV2SolanaWalletProvider.configureWithWallet({ - apiKeyId: "CDP_API_KEY_ID", - apiKeySecret: "CDP_API_KEY_SECRET", - walletSecret: "CDP_WALLET_SECRET", - address: "your-solana-address", // The address of an existing wallet - networkId: "solana-devnet", + apiKeyId: "CDP_API_KEY_ID", + apiKeySecret: "CDP_API_KEY_SECRET", + walletSecret: "CDP_WALLET_SECRET", + address: "your-solana-address", // The address of an existing wallet + networkId: "solana-devnet", }); ``` @@ -1010,11 +1142,11 @@ To create a new wallet, you can provide an idempotency key. The same idempotency import { CdpV2SolanaWalletProvider } from "@coinbase/agentkit"; const walletProvider = await CdpV2SolanaWalletProvider.configureWithWallet({ - apiKeyId: "CDP_API_KEY_ID", - apiKeySecret: "CDP_API_KEY_SECRET", - walletSecret: "CDP_WALLET_SECRET", - idempotencyKey: "unique-key-123", // Optional, if not provided a new wallet will be created - networkId: "solana-devnet", + apiKeyId: "CDP_API_KEY_ID", + apiKeySecret: "CDP_API_KEY_SECRET", + walletSecret: "CDP_WALLET_SECRET", + idempotencyKey: "unique-key-123", // Optional, if not provided a new wallet will be created + networkId: "solana-devnet", }); ``` @@ -1036,6 +1168,7 @@ const walletProvider = await CdpV2SolanaWalletProvider.configureWithWallet(); #### Supported Networks The `CdpV2SolanaWalletProvider` supports the following Solana networks: + - `solana-mainnet` - `solana-devnet` - `solana-testnet` @@ -1051,6 +1184,7 @@ NOTE: It is highly recommended to use a dedicated RPC provider. See [here](https The `SolanaKeypairWalletProvider` can be configured to use a specific network by passing the `networkId` parameter to the `fromNetwork` method. The `networkId` is the ID of the Solana network you want to use. Valid values are `solana-mainnet`, `solana-devnet` and `solana-testnet`. The default RPC endpoints for each network are as follows: + - `solana-mainnet`: `https://api.mainnet-beta.solana.com` - `solana-devnet`: `https://api.devnet.solana.com` - `solana-testnet`: `https://api.testnet.solana.com` @@ -1088,14 +1222,14 @@ import { PrivyWalletProvider, PrivyWalletConfig } from "@coinbase/agentkit"; // Configure Wallet Provider const config: PrivyWalletConfig = { - appId: "PRIVY_APP_ID", - appSecret: "PRIVY_APP_SECRET", - connection, - chainType: "solana", // optional, defaults to "evm". Make sure to set this to "solana" if you want to use Solana! - networkId: "solana-devnet", // optional, defaults to "solana-devnet" - walletId: "PRIVY_WALLET_ID", // optional, otherwise a new wallet will be created - authorizationPrivateKey: PRIVY_WALLET_AUTHORIZATION_PRIVATE_KEY, // optional, required if your account is using authorization keys - authorizationKeyId: PRIVY_WALLET_AUTHORIZATION_KEY_ID, // optional, only required to create a new wallet if walletId is not provided + appId: "PRIVY_APP_ID", + appSecret: "PRIVY_APP_SECRET", + connection, + chainType: "solana", // optional, defaults to "evm". Make sure to set this to "solana" if you want to use Solana! + networkId: "solana-devnet", // optional, defaults to "solana-devnet" + walletId: "PRIVY_WALLET_ID", // optional, otherwise a new wallet will be created + authorizationPrivateKey: PRIVY_WALLET_AUTHORIZATION_PRIVATE_KEY, // optional, required if your account is using authorization keys + authorizationKeyId: PRIVY_WALLET_AUTHORIZATION_KEY_ID, // optional, only required to create a new wallet if walletId is not provided }; const walletProvider = await PrivyWalletProvider.configureWithWallet(config); @@ -1112,14 +1246,14 @@ const connection = new Connection("YOUR_RPC_URL"); // Configure Wallet Provider const config: PrivyWalletConfig = { - appId: "PRIVY_APP_ID", - appSecret: "PRIVY_APP_SECRET", - connection, - chainType: "solana", // optional, defaults to "evm". Make sure to set this to "solana" if you want to use Solana! - networkId: "solana-devnet", // optional, defaults to "solana-devnet" - walletId: "PRIVY_WALLET_ID", // optional, otherwise a new wallet will be created - authorizationPrivateKey: PRIVY_WALLET_AUTHORIZATION_PRIVATE_KEY, // optional, required if your account is using authorization keys - authorizationKeyId: PRIVY_WALLET_AUTHORIZATION_KEY_ID, // optional, only required to create a new wallet if walletId is not provided + appId: "PRIVY_APP_ID", + appSecret: "PRIVY_APP_SECRET", + connection, + chainType: "solana", // optional, defaults to "evm". Make sure to set this to "solana" if you want to use Solana! + networkId: "solana-devnet", // optional, defaults to "solana-devnet" + walletId: "PRIVY_WALLET_ID", // optional, otherwise a new wallet will be created + authorizationPrivateKey: PRIVY_WALLET_AUTHORIZATION_PRIVATE_KEY, // optional, required if your account is using authorization keys + authorizationKeyId: PRIVY_WALLET_AUTHORIZATION_KEY_ID, // optional, only required to create a new wallet if walletId is not provided }; const walletProvider = await PrivyWalletProvider.configureWithWallet(config); @@ -1142,9 +1276,9 @@ const walletData = await walletProvider.exportWallet(); // walletData will be in the following format: { - walletId: string; - authorizationKey: string | undefined; - networkId: string | undefined; + walletId: string; + authorizationKey: string | undefined; + networkId: string | undefined; } ``` diff --git a/typescript/agentkit/package.json b/typescript/agentkit/package.json index c16280345..aaf73fd92 100644 --- a/typescript/agentkit/package.json +++ b/typescript/agentkit/package.json @@ -41,7 +41,7 @@ "dependencies": { "@across-protocol/app-sdk": "^0.2.0", "@alloralabs/allora-sdk": "^0.1.0", - "@coinbase/cdp-sdk": "^1.3.0", + "@coinbase/cdp-sdk": "^1.25.0", "@coinbase/coinbase-sdk": "^0.20.0", "@jup-ag/api": "^6.0.39", "@privy-io/public-api": "2.18.5", diff --git a/typescript/agentkit/src/action-providers/cdp-v2/README.md b/typescript/agentkit/src/action-providers/cdp-legacy/README.md similarity index 53% rename from typescript/agentkit/src/action-providers/cdp-v2/README.md rename to typescript/agentkit/src/action-providers/cdp-legacy/README.md index 11bc94eda..3a09dbac7 100644 --- a/typescript/agentkit/src/action-providers/cdp-v2/README.md +++ b/typescript/agentkit/src/action-providers/cdp-legacy/README.md @@ -1,13 +1,16 @@ -# CDP (Coinbase Developer Platform) V2 Action Provider +# CDP (Coinbase Developer Platform) Action Provider -This directory contains the **CdpV2ActionProvider** implementation, which provides actions to interact with the **Coinbase Developer Platform (CDP)** API and wallet services. +This directory contains the **CdpActionProvider** implementation, which provides actions to interact with the **Coinbase Developer Platform (CDP)** API and wallet services. ## Directory Structure ``` cdp/ -├── cdpApiV2ActionProvider.ts # Provider for CDP API interactions -├── cdpApiV2ActionProvider.test.ts # Tests for CDP API provider +├── cdpApiActionProvider.ts # Provider for CDP API interactions +├── cdpWalletActionProvider.ts # Provider for CDP Wallet operations +├── cdpApiActionProvider.test.ts # Tests for CDP API provider +├── cdpWalletActionProvider.test.ts # Tests for CDP Wallet provider +├── constants.ts # CDP contract constants and ABI ├── schemas.ts # Action schemas for CDP operations ├── index.ts # Main exports └── README.md # This file @@ -17,9 +20,20 @@ cdp/ ### CDP API Actions +- `address_reputation`: Returns onchain activity metrics + - `request_faucet_funds`: Request testnet funds from CDP faucet - - Available only on Base Sepolia, Ethereum Sepolia or Solana Devnet + - Available only on Base Sepolia + +### CDP Wallet Actions + +- `deploy_contract`: Deploy a smart contract +- `deploy_nft`: Deploy an NFT +- `deploy_token`: Deploy a token +- `trade`: Trade a token + + - Available only on mainnet networks ## Adding New Actions @@ -28,6 +42,7 @@ To add new CDP actions: 1. Define your action schema in `schemas.ts` 2. Implement the action in the appropriate provider file: - CDP API actions in `cdpApiActionProvider.ts` + - CDP Wallet actions in `cdpWalletActionProvider.ts` 3. Add corresponding tests ## Network Support diff --git a/typescript/agentkit/src/action-providers/cdp/constants.ts b/typescript/agentkit/src/action-providers/cdp-legacy/constants.ts similarity index 100% rename from typescript/agentkit/src/action-providers/cdp/constants.ts rename to typescript/agentkit/src/action-providers/cdp-legacy/constants.ts diff --git a/typescript/agentkit/src/action-providers/cdp-legacy/index.ts b/typescript/agentkit/src/action-providers/cdp-legacy/index.ts new file mode 100644 index 000000000..35f2ef108 --- /dev/null +++ b/typescript/agentkit/src/action-providers/cdp-legacy/index.ts @@ -0,0 +1,3 @@ +export * from "./schemas"; +export * from "./legacyCdpApiActionProvider"; +export * from "./legacyCdpWalletActionProvider"; diff --git a/typescript/agentkit/src/action-providers/cdp-legacy/legacyCdpApiActionProvider.test.ts b/typescript/agentkit/src/action-providers/cdp-legacy/legacyCdpApiActionProvider.test.ts new file mode 100644 index 000000000..9c6c03718 --- /dev/null +++ b/typescript/agentkit/src/action-providers/cdp-legacy/legacyCdpApiActionProvider.test.ts @@ -0,0 +1,185 @@ +import { EvmWalletProvider } from "../../wallet-providers"; +import { LegacyCdpApiActionProvider } from "./legacyCdpApiActionProvider"; +import { AddressReputationSchema, RequestFaucetFundsSchema } from "./schemas"; + +jest.mock("@coinbase/coinbase-sdk"); + +const { ExternalAddress } = jest.requireMock("@coinbase/coinbase-sdk"); + +describe("CDP API Action Provider Input Schemas", () => { + describe("Address Reputation Schema", () => { + it("should successfully parse valid input", () => { + const validInput = { + address: "0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83", + network: "base-mainnet", + }; + + const result = AddressReputationSchema.safeParse(validInput); + + expect(result.success).toBe(true); + expect(result.data).toEqual(validInput); + }); + + it("should fail parsing invalid address", () => { + const invalidInput = { + address: "invalid-address", + network: "base-mainnet", + }; + const result = AddressReputationSchema.safeParse(invalidInput); + + expect(result.success).toBe(false); + }); + }); + + describe("Request Faucet Funds Schema", () => { + it("should successfully parse with optional assetId", () => { + const validInput = { + assetId: "eth", + }; + + const result = RequestFaucetFundsSchema.safeParse(validInput); + + expect(result.success).toBe(true); + expect(result.data).toEqual(validInput); + }); + + it("should successfully parse without assetId", () => { + const validInput = {}; + const result = RequestFaucetFundsSchema.safeParse(validInput); + + expect(result.success).toBe(true); + expect(result.data).toEqual(validInput); + }); + }); +}); + +describe("CDP API Action Provider", () => { + let actionProvider: LegacyCdpApiActionProvider; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let mockExternalAddressInstance: jest.Mocked; + let mockWallet: jest.Mocked; + + beforeEach(() => { + // Reset all mocks before each test + jest.clearAllMocks(); + + actionProvider = new LegacyCdpApiActionProvider(); + mockExternalAddressInstance = { + reputation: jest.fn(), + faucet: jest.fn(), + }; + + // Mock the constructor to return our mock instance + (ExternalAddress as jest.Mock).mockImplementation(() => mockExternalAddressInstance); + + mockWallet = { + deployToken: jest.fn(), + deployContract: jest.fn(), + getAddress: jest.fn().mockReturnValue("0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83"), + getNetwork: jest.fn().mockReturnValue({ networkId: "base-sepolia" }), + } as unknown as jest.Mocked; + }); + + describe("addressReputation", () => { + it("should successfully check address reputation", async () => { + const args = { + address: "0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83", + network: "base-mainnet", + }; + + mockExternalAddressInstance.reputation.mockResolvedValue("Good reputation"); + + const result = await actionProvider.addressReputation(args); + + expect(ExternalAddress).toHaveBeenCalledWith(args.network, args.address); + expect(ExternalAddress).toHaveBeenCalledTimes(1); + expect(mockExternalAddressInstance.reputation).toHaveBeenCalled(); + expect(mockExternalAddressInstance.reputation).toHaveBeenCalledTimes(1); + expect(result).toBe("Good reputation"); + }); + + it("should handle errors when checking reputation", async () => { + const args = { + address: "0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83", + network: "base-mainnet", + }; + + const error = new Error("Reputation check failed"); + mockExternalAddressInstance.reputation.mockRejectedValue(error); + + const result = await actionProvider.addressReputation(args); + + expect(ExternalAddress).toHaveBeenCalledWith(args.network, args.address); + expect(ExternalAddress).toHaveBeenCalledTimes(1); + expect(mockExternalAddressInstance.reputation).toHaveBeenCalled(); + expect(mockExternalAddressInstance.reputation).toHaveBeenCalledTimes(1); + expect(result).toBe(`Error checking address reputation: ${error}`); + }); + + it("should return error if not on Ethereum network", async () => { + const args = { + address: "0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83", + network: "solana-devnet", + }; + + const result = await actionProvider.addressReputation(args); + + expect(result).toBe("Address reputation is only supported on Ethereum networks."); + }); + }); + + describe("faucet", () => { + beforeEach(() => { + mockExternalAddressInstance.faucet.mockResolvedValue({ + wait: jest.fn().mockResolvedValue({ + getTransactionLink: jest.fn().mockReturnValue("tx-link"), + }), + }); + }); + + it("should successfully request faucet funds with assetId", async () => { + const args = { + assetId: "eth", + }; + + const result = await actionProvider.faucet(mockWallet, args); + + expect(ExternalAddress).toHaveBeenCalledWith("base-sepolia", mockWallet.getAddress()); + expect(ExternalAddress).toHaveBeenCalledTimes(1); + expect(mockExternalAddressInstance.faucet).toHaveBeenCalledWith("eth"); + expect(mockExternalAddressInstance.faucet).toHaveBeenCalledTimes(1); + expect(result).toContain("Received eth from the faucet"); + expect(result).toContain("tx-link"); + }); + + it("should successfully request faucet funds without assetId", async () => { + const args = {}; + + const result = await actionProvider.faucet(mockWallet, args); + + expect(ExternalAddress).toHaveBeenCalledWith("base-sepolia", mockWallet.getAddress()); + expect(ExternalAddress).toHaveBeenCalledTimes(1); + expect(mockExternalAddressInstance.faucet).toHaveBeenCalledWith(undefined); + expect(mockExternalAddressInstance.faucet).toHaveBeenCalledTimes(1); + expect(result).toContain("Received ETH from the faucet"); + }); + + it("should handle faucet errors", async () => { + const args = {}; + const error = new Error("Faucet request failed"); + mockExternalAddressInstance.faucet.mockRejectedValue(error); + + const result = await actionProvider.faucet(mockWallet, args); + + expect(result).toBe(`Error requesting faucet funds: ${error}`); + }); + + it("should return error if not on base-sepolia or solana-devnet", async () => { + mockWallet.getNetwork.mockReturnValue({ networkId: "solana-mainnet", protocolFamily: "svm" }); + const args = {}; + const result = await actionProvider.faucet(mockWallet, args); + + expect(result).toBe("Faucet is only allowed on 'base-sepolia' or 'solana-devnet'."); + }); + }); +}); diff --git a/typescript/agentkit/src/action-providers/cdp-legacy/legacyCdpApiActionProvider.ts b/typescript/agentkit/src/action-providers/cdp-legacy/legacyCdpApiActionProvider.ts new file mode 100644 index 000000000..575a65597 --- /dev/null +++ b/typescript/agentkit/src/action-providers/cdp-legacy/legacyCdpApiActionProvider.ts @@ -0,0 +1,125 @@ +import { version } from "../../../package.json"; +import { Coinbase, ExternalAddress } from "@coinbase/coinbase-sdk"; +import { z } from "zod"; +import { CreateAction } from "../actionDecorator"; +import { ActionProvider } from "../actionProvider"; +import { Network } from "../../network"; +import { LegacyCdpProviderConfig, WalletProvider } from "../../wallet-providers"; +import { AddressReputationSchema, RequestFaucetFundsSchema } from "./schemas"; + +/** + * LegacyCdpApiActionProvider is a legacy action provider for CDP API using the old Coinbase SDK. + * + * This provider is used for any action that uses the CDP API, but does not require a CDP Wallet. + * + * @deprecated Use CdpApiActionProvider instead + */ +export class LegacyCdpApiActionProvider extends ActionProvider { + /** + * Constructor for the LegacyCdpApiActionProvider class. + * + * @param config - The configuration options for the LegacyCdpApiActionProvider. + */ + constructor(config: LegacyCdpProviderConfig = {}) { + super("legacy_cdp_api", []); + + if (config.apiKeyId && config.apiKeySecret) { + Coinbase.configure({ + apiKeyName: config.apiKeyId, + privateKey: config.apiKeySecret?.replace(/\\n/g, "\n"), + source: "agentkit", + sourceVersion: version, + }); + } else { + Coinbase.configureFromJson({ source: "agentkit", sourceVersion: version }); + } + } + + /** + * Check the reputation of an address. + * + * @param args - The input arguments for the action + * @returns A string containing reputation data or error message + */ + @CreateAction({ + name: "address_reputation", + description: ` +This tool checks the reputation of an address on a given network. It takes: + +- network: The network to check the address on (e.g. "base-mainnet") +- address: The Ethereum address to check +`, + schema: AddressReputationSchema, + }) + async addressReputation(args: z.infer): Promise { + if (args.network.includes("solana")) { + return "Address reputation is only supported on Ethereum networks."; + } + + try { + const address = new ExternalAddress(args.network, args.address); + const reputation = await address.reputation(); + return reputation.toString(); + } catch (error) { + return `Error checking address reputation: ${error}`; + } + } + + /** + * Requests test tokens from the faucet for the default address in the wallet. + * + * @param walletProvider - The wallet provider to request funds from. + * @param args - The input arguments for the action. + * @returns A confirmation message with transaction details. + */ + @CreateAction({ + name: "request_faucet_funds", + description: `This tool will request test tokens from the faucet for the default address in the wallet. It takes the wallet and asset ID as input. +Faucet is only allowed on 'base-sepolia' or 'solana-devnet'. +If fauceting on 'base-sepolia', user can only provide asset ID 'eth' or 'usdc', if no asset ID is provided, the faucet will default to 'eth'. +If fauceting on 'solana-devnet', user can only provide asset ID 'sol', if no asset ID is provided, the faucet will default to 'sol'. +You are not allowed to faucet with any other network or asset ID. If you are on another network, suggest that the user sends you some ETH +from another wallet and provide the user with your wallet details.`, + schema: RequestFaucetFundsSchema, + }) + async faucet( + walletProvider: WalletProvider, + args: z.infer, + ): Promise { + const network = walletProvider.getNetwork(); + + if (network.networkId !== "base-sepolia" && network.networkId !== "solana-devnet") { + return `Faucet is only allowed on 'base-sepolia' or 'solana-devnet'.`; + } + + try { + const address = new ExternalAddress( + walletProvider.getNetwork().networkId!, + walletProvider.getAddress(), + ); + + const faucetTx = await address.faucet(args.assetId || undefined); + + const result = await faucetTx.wait({ timeoutSeconds: 60 }); + + return `Received ${ + args.assetId || "ETH" + } from the faucet. Transaction: ${result.getTransactionLink()}`; + } catch (error) { + return `Error requesting faucet funds: ${error}`; + } + } + + /** + * Checks if the Cdp action provider supports the given network. + * + * NOTE: Network scoping is done at the action implementation level + * + * @param _ - The network to check. + * @returns True if the Cdp action provider supports the network, false otherwise. + */ + supportsNetwork = (_: Network) => true; +} + +export const legacyCdpApiActionProvider = (config: LegacyCdpProviderConfig = {}) => + new LegacyCdpApiActionProvider(config); diff --git a/typescript/agentkit/src/action-providers/cdp/cdpWalletActionProvider.test.ts b/typescript/agentkit/src/action-providers/cdp-legacy/legacyCdpWalletActionProvider.test.ts similarity index 94% rename from typescript/agentkit/src/action-providers/cdp/cdpWalletActionProvider.test.ts rename to typescript/agentkit/src/action-providers/cdp-legacy/legacyCdpWalletActionProvider.test.ts index 6798e572f..84d5c8e53 100644 --- a/typescript/agentkit/src/action-providers/cdp/cdpWalletActionProvider.test.ts +++ b/typescript/agentkit/src/action-providers/cdp-legacy/legacyCdpWalletActionProvider.test.ts @@ -1,5 +1,5 @@ -import { CdpWalletProvider } from "../../wallet-providers"; -import { CdpWalletActionProvider } from "./cdpWalletActionProvider"; +import { LegacyCdpWalletProvider } from "../../wallet-providers"; +import { LegacyCdpWalletActionProvider } from "./legacyCdpWalletActionProvider"; import { DeployNftSchema, DeployTokenSchema, DeployContractSchema } from "./schemas"; import { SmartContract, Trade } from "@coinbase/coinbase-sdk"; @@ -71,17 +71,17 @@ describe("CDP Wallet Action Provider Input Schemas", () => { }); }); -describe("CDP Wallet Action Provider", () => { - let actionProvider: CdpWalletActionProvider; +describe("Legacy CDP Wallet Action Provider", () => { + let actionProvider: LegacyCdpWalletActionProvider; // eslint-disable-next-line @typescript-eslint/no-explicit-any let mockExternalAddressInstance: jest.Mocked; - let mockWallet: jest.Mocked; + let mockWallet: jest.Mocked; beforeEach(() => { // Reset all mocks before each test jest.clearAllMocks(); - actionProvider = new CdpWalletActionProvider(); + actionProvider = new LegacyCdpWalletActionProvider(); mockExternalAddressInstance = { reputation: jest.fn(), faucet: jest.fn(), @@ -96,11 +96,11 @@ describe("CDP Wallet Action Provider", () => { deployContract: jest.fn(), getAddress: jest.fn().mockReturnValue("0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83"), getNetwork: jest.fn().mockReturnValue({ networkId: "base-sepolia" }), - } as unknown as jest.Mocked; + } as unknown as jest.Mocked; }); describe("deployNft", () => { - let mockWallet: jest.Mocked; + let mockWallet: jest.Mocked; const MOCK_NFT_BASE_URI = "https://www.test.xyz/metadata/"; const MOCK_NFT_NAME = "Test Token"; const MOCK_NFT_SYMBOL = "TEST"; @@ -121,7 +121,7 @@ describe("CDP Wallet Action Provider", () => { }), }), getNetwork: jest.fn().mockReturnValue({ networkId: NETWORK_ID }), - } as unknown as jest.Mocked; + } as unknown as jest.Mocked; }); it("should successfully deploy an NFT", async () => { @@ -169,7 +169,7 @@ describe("CDP Wallet Action Provider", () => { }), }), }), - } as unknown as jest.Mocked; + } as unknown as jest.Mocked; }); it("should successfully deploy a token", async () => { diff --git a/typescript/agentkit/src/action-providers/cdp/cdpWalletActionProvider.ts b/typescript/agentkit/src/action-providers/cdp-legacy/legacyCdpWalletActionProvider.ts similarity index 92% rename from typescript/agentkit/src/action-providers/cdp/cdpWalletActionProvider.ts rename to typescript/agentkit/src/action-providers/cdp-legacy/legacyCdpWalletActionProvider.ts index 2ef2899d0..5de156e9a 100644 --- a/typescript/agentkit/src/action-providers/cdp/cdpWalletActionProvider.ts +++ b/typescript/agentkit/src/action-providers/cdp-legacy/legacyCdpWalletActionProvider.ts @@ -4,7 +4,7 @@ import { version } from "../../../package.json"; import { CreateAction } from "../actionDecorator"; import { ActionProvider } from "../actionProvider"; import { Network } from "../../network"; -import { CdpWalletProvider, CdpProviderConfig } from "../../wallet-providers"; +import { LegacyCdpWalletProvider, LegacyCdpProviderConfig } from "../../wallet-providers"; import { SolidityVersions } from "./constants"; import { DeployContractSchema, DeployNftSchema, DeployTokenSchema, TradeSchema } from "./schemas"; @@ -13,14 +13,14 @@ import { DeployContractSchema, DeployNftSchema, DeployTokenSchema, TradeSchema } * * This provider is used for any action that requires a CDP Wallet. */ -export class CdpWalletActionProvider extends ActionProvider { +export class LegacyCdpWalletActionProvider extends ActionProvider { /** * Constructor for the CdpWalletActionProvider class. * * @param config - The configuration options for the CdpWalletActionProvider. */ - constructor(config: CdpProviderConfig = {}) { - super("cdp_wallet", []); + constructor(config: LegacyCdpProviderConfig = {}) { + super("legacy_cdp_wallet", []); if (config.apiKeyId && config.apiKeySecret) { Coinbase.configure({ @@ -61,7 +61,7 @@ map where the key is the arg name and the value is the arg value. Encode uint/in schema: DeployContractSchema, }) async deployContract( - walletProvider: CdpWalletProvider, + walletProvider: LegacyCdpWalletProvider, args: z.infer, ): Promise { try { @@ -98,7 +98,7 @@ map where the key is the arg name and the value is the arg value. Encode uint/in schema: DeployNftSchema, }) async deployNFT( - walletProvider: CdpWalletProvider, + walletProvider: LegacyCdpWalletProvider, args: z.infer, ): Promise { try { @@ -139,7 +139,10 @@ map where the key is the arg name and the value is the arg value. Encode uint/in The token will be deployed using the wallet's default address as the owner and initial token holder.`, schema: DeployTokenSchema, }) - async deployToken(walletProvider: CdpWalletProvider, args: z.infer) { + async deployToken( + walletProvider: LegacyCdpWalletProvider, + args: z.infer, + ) { try { const tokenContract = await walletProvider.deployToken({ name: args.name, @@ -181,7 +184,7 @@ Important notes: schema: TradeSchema, }) async trade( - walletProvider: CdpWalletProvider, + walletProvider: LegacyCdpWalletProvider, args: z.infer, ): Promise { try { @@ -214,5 +217,5 @@ Important notes: supportsNetwork = (network: Network) => network.protocolFamily === "evm"; } -export const cdpWalletActionProvider = (config: CdpProviderConfig = {}) => - new CdpWalletActionProvider(config); +export const legacyCdpWalletActionProvider = (config: LegacyCdpProviderConfig = {}) => + new LegacyCdpWalletActionProvider(config); diff --git a/typescript/agentkit/src/action-providers/cdp-legacy/schemas.ts b/typescript/agentkit/src/action-providers/cdp-legacy/schemas.ts new file mode 100644 index 000000000..362a7a73e --- /dev/null +++ b/typescript/agentkit/src/action-providers/cdp-legacy/schemas.ts @@ -0,0 +1,80 @@ +import { z } from "zod"; +import { SolidityVersions } from "./constants"; + +/** + * Input schema for address reputation check. + */ +export const AddressReputationSchema = z + .object({ + address: z + .string() + .regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format") + .describe("The Ethereum address to check"), + network: z.string().describe("The network to check the address on"), + }) + .strip() + .describe("Input schema for address reputation check"); + +/** + * Input schema for deploy contract action. + */ +export const DeployContractSchema = z + .object({ + solidityVersion: z + .enum(Object.keys(SolidityVersions) as [string, ...string[]]) + .describe("The solidity compiler version"), + solidityInputJson: z.string().describe("The input json for the solidity compiler"), + contractName: z.string().describe("The name of the contract class to be deployed"), + constructorArgs: z + .record(z.string(), z.any()) + .describe("The constructor arguments for the contract") + .optional(), + }) + .strip() + .describe("Instructions for deploying an arbitrary contract"); + +/** + * Input schema for deploy NFT action + */ +export const DeployNftSchema = z + .object({ + name: z.string().describe("The name of the NFT collection"), + symbol: z.string().describe("The symbol of the NFT collection"), + baseURI: z.string().describe("The base URI for the token metadata"), + }) + .strip() + .describe("Instructions for deploying an NFT collection"); + +/** + * Input schema for deploy token action. + */ +export const DeployTokenSchema = z + .object({ + name: z.string().describe("The name of the token"), + symbol: z.string().describe("The token symbol"), + totalSupply: z.custom().describe("The total supply of tokens to mint"), + }) + .strip() + .describe("Instructions for deploying a token"); + +/** + * Input schema for request faucet funds action. + */ +export const RequestFaucetFundsSchema = z + .object({ + assetId: z.string().optional().describe("The optional asset ID to request from faucet"), + }) + .strip() + .describe("Instructions for requesting faucet funds"); + +/** + * Input schema for trade action. + */ +export const TradeSchema = z + .object({ + amount: z.custom().describe("The amount of the from asset to trade"), + fromAssetId: z.string().describe("The from asset ID to trade"), + toAssetId: z.string().describe("The to asset ID to receive from the trade"), + }) + .strip() + .describe("Instructions for trading assets"); diff --git a/typescript/agentkit/src/action-providers/cdp-v2/cdpApiV2ActionProvider.ts b/typescript/agentkit/src/action-providers/cdp-v2/cdpApiV2ActionProvider.ts deleted file mode 100644 index 658637509..000000000 --- a/typescript/agentkit/src/action-providers/cdp-v2/cdpApiV2ActionProvider.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { z } from "zod"; -import { Network } from "../../network"; -import { WalletProvider } from "../../wallet-providers"; -import { isWalletProviderWithClient } from "../../wallet-providers/cdpV2Shared"; -import { CreateAction } from "../actionDecorator"; -import { ActionProvider } from "../actionProvider"; -import { RequestFaucetFundsV2Schema } from "./schemas"; - -/** - * CdpApiActionProvider is an action provider for CDP API. - * - * This provider is used for any action that uses the CDP API, but does not require a CDP Wallet. - */ -export class CdpApiV2ActionProvider extends ActionProvider { - /** - * Constructor for the CdpApiActionProvider class. - */ - constructor() { - super("cdp_api_v2", []); - } - - /** - * Requests test tokens from the faucet for the default address in the wallet. - * - * @param walletProvider - The wallet provider to request funds from. - * @param args - The input arguments for the action. - * @returns A confirmation message with transaction details. - */ - @CreateAction({ - name: "request_faucet_funds", - description: `This tool will request test tokens from the faucet for the default address in the wallet. It takes the wallet and asset ID as input. -Faucet is only allowed on 'base-sepolia' or 'solana-devnet'. -If fauceting on 'base-sepolia', user can only provide asset ID 'eth', 'usdc', 'eurc' or 'cbbtc', if no asset ID is provided, the faucet will default to 'eth'. -If fauceting on 'solana-devnet', user can only provide asset ID 'sol' or 'usdc', if no asset ID is provided, the faucet will default to 'sol'. -You are not allowed to faucet with any other network or asset ID. If you are on another network, suggest that the user sends you some ETH -from another wallet and provide the user with your wallet details.`, - schema: RequestFaucetFundsV2Schema, - }) - async faucet( - walletProvider: WalletProvider, - args: z.infer, - ): Promise { - const network = walletProvider.getNetwork(); - const networkId = network.networkId!; - - if (isWalletProviderWithClient(walletProvider)) { - if (network.protocolFamily === "evm") { - if (networkId !== "base-sepolia" && networkId !== "ethereum-sepolia") { - throw new Error( - "Faucet is only supported on 'base-sepolia' or 'ethereum-sepolia' evm networks.", - ); - } - - const faucetTx = await walletProvider.getClient().evm.requestFaucet({ - address: walletProvider.getAddress(), - token: (args.assetId || "eth") as "eth" | "usdc" | "eurc" | "cbbtc", - network: networkId, - }); - - return `Received ${ - args.assetId || "ETH" - } from the faucet. Transaction hash: ${faucetTx.transactionHash}`; - } else if (network.protocolFamily === "svm") { - if (networkId !== "solana-devnet") { - throw new Error("Faucet is only supported on 'solana-devnet' solana networks."); - } - - const faucetTx = await walletProvider.getClient().solana.requestFaucet({ - address: walletProvider.getAddress(), - token: (args.assetId || "sol") as "sol" | "usdc", - }); - - return `Received ${ - args.assetId || "SOL" - } from the faucet. Transaction signature hash: ${faucetTx.signature}`; - } else { - throw new Error("Faucet is only supported on Ethereum and Solana protocol families."); - } - } else { - throw new Error("Wallet provider is not a CDP Wallet Provider."); - } - } - - /** - * Checks if the Cdp action provider supports the given network. - * - * NOTE: Network scoping is done at the action implementation level - * - * @param _ - The network to check. - * @returns True if the Cdp action provider supports the network, false otherwise. - */ - supportsNetwork = (_: Network) => true; -} - -export const cdpApiV2ActionProvider = () => new CdpApiV2ActionProvider(); diff --git a/typescript/agentkit/src/action-providers/cdp-v2/index.ts b/typescript/agentkit/src/action-providers/cdp-v2/index.ts deleted file mode 100644 index 4a702d591..000000000 --- a/typescript/agentkit/src/action-providers/cdp-v2/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./schemas"; -export * from "./cdpApiV2ActionProvider"; diff --git a/typescript/agentkit/src/action-providers/cdp-v2/schemas.ts b/typescript/agentkit/src/action-providers/cdp-v2/schemas.ts deleted file mode 100644 index 4648cfe82..000000000 --- a/typescript/agentkit/src/action-providers/cdp-v2/schemas.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { z } from "zod"; - -/** - * Input schema for request faucet funds action. - */ -export const RequestFaucetFundsV2Schema = z - .object({ - assetId: z.string().optional().describe("The optional asset ID to request from faucet"), - }) - .strip() - .describe("Instructions for requesting faucet funds"); diff --git a/typescript/agentkit/src/action-providers/cdp/README.md b/typescript/agentkit/src/action-providers/cdp/README.md index 3a09dbac7..b22e3841d 100644 --- a/typescript/agentkit/src/action-providers/cdp/README.md +++ b/typescript/agentkit/src/action-providers/cdp/README.md @@ -1,16 +1,13 @@ -# CDP (Coinbase Developer Platform) Action Provider +# CDP (Coinbase Developer Platform) V2 Action Provider -This directory contains the **CdpActionProvider** implementation, which provides actions to interact with the **Coinbase Developer Platform (CDP)** API and wallet services. +This directory contains the **CdpV2ActionProvider** implementation, which provides actions to interact with the **Coinbase Developer Platform (CDP)** API and wallet services. ## Directory Structure ``` cdp/ -├── cdpApiActionProvider.ts # Provider for CDP API interactions -├── cdpWalletActionProvider.ts # Provider for CDP Wallet operations -├── cdpApiActionProvider.test.ts # Tests for CDP API provider -├── cdpWalletActionProvider.test.ts # Tests for CDP Wallet provider -├── constants.ts # CDP contract constants and ABI +├── cdpApiActionProvider.ts # Provider for CDP API interactions +├── cdpApiActionProvider.test.ts # Tests for CDP API provider ├── schemas.ts # Action schemas for CDP operations ├── index.ts # Main exports └── README.md # This file @@ -20,20 +17,9 @@ cdp/ ### CDP API Actions -- `address_reputation`: Returns onchain activity metrics - - `request_faucet_funds`: Request testnet funds from CDP faucet - - Available only on Base Sepolia - -### CDP Wallet Actions - -- `deploy_contract`: Deploy a smart contract -- `deploy_nft`: Deploy an NFT -- `deploy_token`: Deploy a token -- `trade`: Trade a token - - - Available only on mainnet networks + - Available only on Base Sepolia, Ethereum Sepolia or Solana Devnet ## Adding New Actions @@ -42,7 +28,6 @@ To add new CDP actions: 1. Define your action schema in `schemas.ts` 2. Implement the action in the appropriate provider file: - CDP API actions in `cdpApiActionProvider.ts` - - CDP Wallet actions in `cdpWalletActionProvider.ts` 3. Add corresponding tests ## Network Support diff --git a/typescript/agentkit/src/action-providers/cdp/cdpApiActionProvider.test.ts b/typescript/agentkit/src/action-providers/cdp/cdpApiActionProvider.test.ts index aff4bc182..0acfb74fe 100644 --- a/typescript/agentkit/src/action-providers/cdp/cdpApiActionProvider.test.ts +++ b/typescript/agentkit/src/action-providers/cdp/cdpApiActionProvider.test.ts @@ -1,185 +1,285 @@ -import { EvmWalletProvider } from "../../wallet-providers"; +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { CdpClient } from "@coinbase/cdp-sdk"; +import { WalletProvider } from "../../wallet-providers"; +import { WalletProviderWithClient } from "../../wallet-providers/cdpShared"; import { CdpApiActionProvider } from "./cdpApiActionProvider"; -import { AddressReputationSchema, RequestFaucetFundsSchema } from "./schemas"; +import { RequestFaucetFundsV2Schema, SwapSchema } from "./schemas"; -jest.mock("@coinbase/coinbase-sdk"); +// Mock the CDP SDK +jest.mock("@coinbase/cdp-sdk"); -const { ExternalAddress } = jest.requireMock("@coinbase/coinbase-sdk"); - -describe("CDP API Action Provider Input Schemas", () => { - describe("Address Reputation Schema", () => { - it("should successfully parse valid input", () => { - const validInput = { - address: "0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83", - network: "base-mainnet", - }; +describe("CDP API Action Provider", () => { + let actionProvider: CdpApiActionProvider; + let mockWalletProvider: jest.Mocked; + let mockCdpClient: jest.Mocked; - const result = AddressReputationSchema.safeParse(validInput); + beforeEach(() => { + jest.clearAllMocks(); - expect(result.success).toBe(true); - expect(result.data).toEqual(validInput); - }); + const mockAccount = { + swap: jest.fn(), + }; - it("should fail parsing invalid address", () => { - const invalidInput = { - address: "invalid-address", - network: "base-mainnet", - }; - const result = AddressReputationSchema.safeParse(invalidInput); + mockCdpClient = { + evm: { + requestFaucet: jest.fn() as any, + getAccount: jest.fn() as any, + }, + solana: { + requestFaucet: jest.fn() as any, + }, + } as any; + + // Set up default mock behavior + (mockCdpClient.evm.getAccount as jest.Mock).mockResolvedValue(mockAccount); + + mockWalletProvider = { + getNetwork: jest.fn(), + getAddress: jest.fn(), + getClient: jest.fn(), + } as any; - expect(result.success).toBe(false); - }); + actionProvider = new CdpApiActionProvider(); }); - describe("Request Faucet Funds Schema", () => { - it("should successfully parse with optional assetId", () => { - const validInput = { - assetId: "eth", - }; - - const result = RequestFaucetFundsSchema.safeParse(validInput); - - expect(result.success).toBe(true); - expect(result.data).toEqual(validInput); + describe("initialization", () => { + it("should initialize with correct provider name", () => { + expect(actionProvider.name).toBe("cdp_api"); }); - it("should successfully parse without assetId", () => { - const validInput = {}; - const result = RequestFaucetFundsSchema.safeParse(validInput); - - expect(result.success).toBe(true); - expect(result.data).toEqual(validInput); + it("should support all networks", () => { + const mockNetwork = { protocolFamily: "evm", networkId: "base-sepolia" }; + expect(actionProvider.supportsNetwork(mockNetwork as any)).toBe(true); }); }); -}); -describe("CDP API Action Provider", () => { - let actionProvider: CdpApiActionProvider; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let mockExternalAddressInstance: jest.Mocked; - let mockWallet: jest.Mocked; + describe("faucet", () => { + it("should request faucet funds on base-sepolia", async () => { + const mockNetwork = { protocolFamily: "evm", networkId: "base-sepolia" }; + mockWalletProvider.getNetwork.mockReturnValue(mockNetwork as any); + mockWalletProvider.getAddress.mockReturnValue("0x123456789"); + mockWalletProvider.getClient.mockReturnValue(mockCdpClient); + + (mockCdpClient.evm.requestFaucet as jest.Mock).mockResolvedValue({ + transactionHash: "0xabcdef123456", + }); - beforeEach(() => { - // Reset all mocks before each test - jest.clearAllMocks(); + const result = await actionProvider.faucet(mockWalletProvider, { assetId: "eth" }); - actionProvider = new CdpApiActionProvider(); - mockExternalAddressInstance = { - reputation: jest.fn(), - faucet: jest.fn(), - }; + expect(mockCdpClient.evm.requestFaucet).toHaveBeenCalledWith({ + address: "0x123456789", + token: "eth", + network: "base-sepolia", + }); + expect(result).toContain("Received eth from the faucet"); + expect(result).toContain("0xabcdef123456"); + }); - // Mock the constructor to return our mock instance - (ExternalAddress as jest.Mock).mockImplementation(() => mockExternalAddressInstance); + it("should request faucet funds on solana-devnet", async () => { + const mockNetwork = { protocolFamily: "svm", networkId: "solana-devnet" }; + mockWalletProvider.getNetwork.mockReturnValue(mockNetwork as any); + mockWalletProvider.getAddress.mockReturnValue("address123"); + mockWalletProvider.getClient.mockReturnValue(mockCdpClient); - mockWallet = { - deployToken: jest.fn(), - deployContract: jest.fn(), - getAddress: jest.fn().mockReturnValue("0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83"), - getNetwork: jest.fn().mockReturnValue({ networkId: "base-sepolia" }), - } as unknown as jest.Mocked; - }); + (mockCdpClient.solana.requestFaucet as jest.Mock).mockResolvedValue({ + signature: "signature123", + }); - describe("addressReputation", () => { - it("should successfully check address reputation", async () => { - const args = { - address: "0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83", - network: "base-mainnet", - }; + const result = await actionProvider.faucet(mockWalletProvider, { assetId: "sol" }); - mockExternalAddressInstance.reputation.mockResolvedValue("Good reputation"); + expect(mockCdpClient.solana.requestFaucet).toHaveBeenCalledWith({ + address: "address123", + token: "sol", + }); + expect(result).toContain("Received sol from the faucet"); + expect(result).toContain("signature123"); + }); - const result = await actionProvider.addressReputation(args); + it("should throw error for unsupported EVM network", async () => { + const mockNetwork = { protocolFamily: "evm", networkId: "ethereum-mainnet" }; + mockWalletProvider.getNetwork.mockReturnValue(mockNetwork as any); + mockWalletProvider.getClient.mockReturnValue(mockCdpClient); - expect(ExternalAddress).toHaveBeenCalledWith(args.network, args.address); - expect(ExternalAddress).toHaveBeenCalledTimes(1); - expect(mockExternalAddressInstance.reputation).toHaveBeenCalled(); - expect(mockExternalAddressInstance.reputation).toHaveBeenCalledTimes(1); - expect(result).toBe("Good reputation"); + await expect(actionProvider.faucet(mockWalletProvider, { assetId: "eth" })).rejects.toThrow( + "Faucet is only supported on 'base-sepolia' or 'ethereum-sepolia' evm networks", + ); }); - it("should handle errors when checking reputation", async () => { - const args = { - address: "0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83", - network: "base-mainnet", - }; + it("should throw error for unsupported Solana network", async () => { + const mockNetwork = { protocolFamily: "svm", networkId: "solana-mainnet" }; + mockWalletProvider.getNetwork.mockReturnValue(mockNetwork as any); + mockWalletProvider.getClient.mockReturnValue(mockCdpClient); - const error = new Error("Reputation check failed"); - mockExternalAddressInstance.reputation.mockRejectedValue(error); + await expect(actionProvider.faucet(mockWalletProvider, { assetId: "sol" })).rejects.toThrow( + "Faucet is only supported on 'solana-devnet' solana networks", + ); + }); - const result = await actionProvider.addressReputation(args); + it("should throw error for wallet provider without client", async () => { + const mockWalletWithoutClient = { + getNetwork: jest.fn().mockReturnValue({ protocolFamily: "evm", networkId: "base-sepolia" }), + } as any; - expect(ExternalAddress).toHaveBeenCalledWith(args.network, args.address); - expect(ExternalAddress).toHaveBeenCalledTimes(1); - expect(mockExternalAddressInstance.reputation).toHaveBeenCalled(); - expect(mockExternalAddressInstance.reputation).toHaveBeenCalledTimes(1); - expect(result).toBe(`Error checking address reputation: ${error}`); + await expect( + actionProvider.faucet(mockWalletWithoutClient, { assetId: "eth" }), + ).rejects.toThrow("Wallet provider is not a CDP Wallet Provider"); }); + }); - it("should return error if not on Ethereum network", async () => { - const args = { - address: "0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83", - network: "solana-devnet", - }; + describe("swap", () => { + it("should perform swap on base-sepolia", async () => { + const mockNetwork = { protocolFamily: "evm", networkId: "base-sepolia" }; + mockWalletProvider.getNetwork.mockReturnValue(mockNetwork as any); + mockWalletProvider.getAddress.mockReturnValue("0x123456789"); + mockWalletProvider.getClient.mockReturnValue(mockCdpClient); + + const mockAccount = { swap: jest.fn() }; + (mockCdpClient.evm.getAccount as jest.Mock).mockResolvedValue(mockAccount); + mockAccount.swap.mockResolvedValue({ + transactionHash: "0xswap123456", + }); - const result = await actionProvider.addressReputation(args); + const result = await actionProvider.swap(mockWalletProvider, { + fromAssetId: "eth", + toAssetId: "usdc", + amount: "0.1", + }); - expect(result).toBe("Address reputation is only supported on Ethereum networks."); + expect(mockCdpClient.evm.getAccount).toHaveBeenCalledWith({ + address: "0x123456789", + }); + expect(mockAccount.swap).toHaveBeenCalledWith({ + network: "base-sepolia", + from: "eth", + to: "usdc", + amount: "0.1", + slippageBps: 100, + }); + expect(result).toContain("Successfully swapped 0.1 ETH to USDC"); + expect(result).toContain("0xswap123456"); }); - }); - describe("faucet", () => { - beforeEach(() => { - mockExternalAddressInstance.faucet.mockResolvedValue({ - wait: jest.fn().mockResolvedValue({ - getTransactionLink: jest.fn().mockReturnValue("tx-link"), - }), + it("should perform swap on base-mainnet", async () => { + const mockNetwork = { protocolFamily: "evm", networkId: "base-mainnet" }; + mockWalletProvider.getNetwork.mockReturnValue(mockNetwork as any); + mockWalletProvider.getAddress.mockReturnValue("0x123456789"); + mockWalletProvider.getClient.mockReturnValue(mockCdpClient); + + const mockAccount = { swap: jest.fn() }; + (mockCdpClient.evm.getAccount as jest.Mock).mockResolvedValue(mockAccount); + mockAccount.swap.mockResolvedValue({ + transactionHash: "0xswap789", }); - }); - it("should successfully request faucet funds with assetId", async () => { - const args = { - assetId: "eth", - }; + const result = await actionProvider.swap(mockWalletProvider, { + fromAssetId: "usdc", + toAssetId: "eth", + amount: "100", + }); + + expect(mockAccount.swap).toHaveBeenCalledWith({ + network: "base", // Should convert base-mainnet to base + from: "usdc", + to: "eth", + amount: "100", + slippageBps: 100, + }); + expect(result).toContain("Successfully swapped 100 USDC to ETH"); + }); - const result = await actionProvider.faucet(mockWallet, args); + it("should throw error for non-EVM networks", async () => { + const mockNetwork = { protocolFamily: "svm", networkId: "solana-devnet" }; + mockWalletProvider.getNetwork.mockReturnValue(mockNetwork as any); + mockWalletProvider.getClient.mockReturnValue(mockCdpClient); - expect(ExternalAddress).toHaveBeenCalledWith("base-sepolia", mockWallet.getAddress()); - expect(ExternalAddress).toHaveBeenCalledTimes(1); - expect(mockExternalAddressInstance.faucet).toHaveBeenCalledWith("eth"); - expect(mockExternalAddressInstance.faucet).toHaveBeenCalledTimes(1); - expect(result).toContain("Received eth from the faucet"); - expect(result).toContain("tx-link"); + await expect( + actionProvider.swap(mockWalletProvider, { + fromAssetId: "sol", + toAssetId: "usdc", + amount: "1", + }), + ).rejects.toThrow("Swap is currently only supported on EVM networks"); }); - it("should successfully request faucet funds without assetId", async () => { - const args = {}; + it("should throw error for wallet provider without client", async () => { + const mockWalletWithoutClient = { + getNetwork: jest.fn().mockReturnValue({ protocolFamily: "evm", networkId: "base-sepolia" }), + } as any; - const result = await actionProvider.faucet(mockWallet, args); + await expect( + actionProvider.swap(mockWalletWithoutClient, { + fromAssetId: "eth", + toAssetId: "usdc", + amount: "0.1", + }), + ).rejects.toThrow("Wallet provider is not a CDP Wallet Provider"); + }); - expect(ExternalAddress).toHaveBeenCalledWith("base-sepolia", mockWallet.getAddress()); - expect(ExternalAddress).toHaveBeenCalledTimes(1); - expect(mockExternalAddressInstance.faucet).toHaveBeenCalledWith(undefined); - expect(mockExternalAddressInstance.faucet).toHaveBeenCalledTimes(1); - expect(result).toContain("Received ETH from the faucet"); + it("should handle swap errors", async () => { + const mockNetwork = { protocolFamily: "evm", networkId: "base-sepolia" }; + mockWalletProvider.getNetwork.mockReturnValue(mockNetwork as any); + mockWalletProvider.getAddress.mockReturnValue("0x123456789"); + mockWalletProvider.getClient.mockReturnValue(mockCdpClient); + + const mockAccount = { swap: jest.fn() }; + (mockCdpClient.evm.getAccount as jest.Mock).mockResolvedValue(mockAccount); + mockAccount.swap.mockRejectedValue(new Error("Insufficient liquidity")); + + await expect( + actionProvider.swap(mockWalletProvider, { + fromAssetId: "eth", + toAssetId: "usdc", + amount: "1000", + }), + ).rejects.toThrow("Swap failed: Error: Insufficient liquidity"); }); + }); - it("should handle faucet errors", async () => { - const args = {}; - const error = new Error("Faucet request failed"); - mockExternalAddressInstance.faucet.mockRejectedValue(error); + describe("RequestFaucetFundsV2Schema", () => { + it("should validate correct input", () => { + const validInput = { assetId: "eth" }; + const result = RequestFaucetFundsV2Schema.safeParse(validInput); + expect(result.success).toBe(true); + }); - const result = await actionProvider.faucet(mockWallet, args); + it("should allow missing assetId", () => { + const validInput = {}; + const result = RequestFaucetFundsV2Schema.safeParse(validInput); + expect(result.success).toBe(true); + }); + }); - expect(result).toBe(`Error requesting faucet funds: ${error}`); + describe("SwapSchema", () => { + it("should validate correct swap input", () => { + const validInput = { + fromAssetId: "eth", + toAssetId: "usdc", + amount: "0.1", + }; + const result = SwapSchema.safeParse(validInput); + expect(result.success).toBe(true); + expect(result.data).toEqual(validInput); }); - it("should return error if not on base-sepolia or solana-devnet", async () => { - mockWallet.getNetwork.mockReturnValue({ networkId: "solana-mainnet", protocolFamily: "svm" }); - const args = {}; - const result = await actionProvider.faucet(mockWallet, args); + it("should validate swap input with network", () => { + const validInput = { + fromAssetId: "usdc", + toAssetId: "eth", + amount: "100", + network: "base-sepolia", + }; + const result = SwapSchema.safeParse(validInput); + expect(result.success).toBe(true); + expect(result.data).toEqual(validInput); + }); - expect(result).toBe("Faucet is only allowed on 'base-sepolia' or 'solana-devnet'."); + it("should fail validation when missing required fields", () => { + const invalidInput = { + fromAssetId: "eth", + // missing toAssetId and amount + }; + const result = SwapSchema.safeParse(invalidInput); + expect(result.success).toBe(false); }); }); }); diff --git a/typescript/agentkit/src/action-providers/cdp/cdpApiActionProvider.ts b/typescript/agentkit/src/action-providers/cdp/cdpApiActionProvider.ts index 473c98e9d..697a2b8ea 100644 --- a/typescript/agentkit/src/action-providers/cdp/cdpApiActionProvider.ts +++ b/typescript/agentkit/src/action-providers/cdp/cdpApiActionProvider.ts @@ -1,11 +1,10 @@ -import { version } from "../../../package.json"; -import { Coinbase, ExternalAddress } from "@coinbase/coinbase-sdk"; import { z } from "zod"; +import { Network } from "../../network"; +import { WalletProvider } from "../../wallet-providers"; +import { isWalletProviderWithClient } from "../../wallet-providers/cdpShared"; import { CreateAction } from "../actionDecorator"; import { ActionProvider } from "../actionProvider"; -import { Network } from "../../network"; -import { CdpProviderConfig, WalletProvider } from "../../wallet-providers"; -import { AddressReputationSchema, RequestFaucetFundsSchema } from "./schemas"; +import { RequestFaucetFundsV2Schema, SwapSchema } from "./schemas"; /** * CdpApiActionProvider is an action provider for CDP API. @@ -15,52 +14,9 @@ import { AddressReputationSchema, RequestFaucetFundsSchema } from "./schemas"; export class CdpApiActionProvider extends ActionProvider { /** * Constructor for the CdpApiActionProvider class. - * - * @param config - The configuration options for the CdpApiActionProvider. */ - constructor(config: CdpProviderConfig = {}) { + constructor() { super("cdp_api", []); - - if (config.apiKeyId && config.apiKeySecret) { - Coinbase.configure({ - apiKeyName: config.apiKeyId, - privateKey: config.apiKeySecret?.replace(/\\n/g, "\n"), - source: "agentkit", - sourceVersion: version, - }); - } else { - Coinbase.configureFromJson({ source: "agentkit", sourceVersion: version }); - } - } - - /** - * Check the reputation of an address. - * - * @param args - The input arguments for the action - * @returns A string containing reputation data or error message - */ - @CreateAction({ - name: "address_reputation", - description: ` -This tool checks the reputation of an address on a given network. It takes: - -- network: The network to check the address on (e.g. "base-mainnet") -- address: The Ethereum address to check -`, - schema: AddressReputationSchema, - }) - async addressReputation(args: z.infer): Promise { - if (args.network.includes("solana")) { - return "Address reputation is only supported on Ethereum networks."; - } - - try { - const address = new ExternalAddress(args.network, args.address); - const reputation = await address.reputation(); - return reputation.toString(); - } catch (error) { - return `Error checking address reputation: ${error}`; - } } /** @@ -74,37 +30,107 @@ This tool checks the reputation of an address on a given network. It takes: name: "request_faucet_funds", description: `This tool will request test tokens from the faucet for the default address in the wallet. It takes the wallet and asset ID as input. Faucet is only allowed on 'base-sepolia' or 'solana-devnet'. -If fauceting on 'base-sepolia', user can only provide asset ID 'eth' or 'usdc', if no asset ID is provided, the faucet will default to 'eth'. -If fauceting on 'solana-devnet', user can only provide asset ID 'sol', if no asset ID is provided, the faucet will default to 'sol'. +If fauceting on 'base-sepolia', user can only provide asset ID 'eth', 'usdc', 'eurc' or 'cbbtc', if no asset ID is provided, the faucet will default to 'eth'. +If fauceting on 'solana-devnet', user can only provide asset ID 'sol' or 'usdc', if no asset ID is provided, the faucet will default to 'sol'. You are not allowed to faucet with any other network or asset ID. If you are on another network, suggest that the user sends you some ETH from another wallet and provide the user with your wallet details.`, - schema: RequestFaucetFundsSchema, + schema: RequestFaucetFundsV2Schema, }) async faucet( walletProvider: WalletProvider, - args: z.infer, + args: z.infer, ): Promise { const network = walletProvider.getNetwork(); + const networkId = network.networkId!; + + if (isWalletProviderWithClient(walletProvider)) { + if (network.protocolFamily === "evm") { + if (networkId !== "base-sepolia" && networkId !== "ethereum-sepolia") { + throw new Error( + "Faucet is only supported on 'base-sepolia' or 'ethereum-sepolia' evm networks.", + ); + } + + const faucetTx = await walletProvider.getClient().evm.requestFaucet({ + address: walletProvider.getAddress(), + token: (args.assetId || "eth") as "eth" | "usdc" | "eurc" | "cbbtc", + network: networkId, + }); - if (network.networkId !== "base-sepolia" && network.networkId !== "solana-devnet") { - return `Faucet is only allowed on 'base-sepolia' or 'solana-devnet'.`; + return `Received ${ + args.assetId || "ETH" + } from the faucet. Transaction hash: ${faucetTx.transactionHash}`; + } else if (network.protocolFamily === "svm") { + if (networkId !== "solana-devnet") { + throw new Error("Faucet is only supported on 'solana-devnet' solana networks."); + } + + const faucetTx = await walletProvider.getClient().solana.requestFaucet({ + address: walletProvider.getAddress(), + token: (args.assetId || "sol") as "sol" | "usdc", + }); + + return `Received ${ + args.assetId || "SOL" + } from the faucet. Transaction signature hash: ${faucetTx.signature}`; + } else { + throw new Error("Faucet is only supported on Ethereum and Solana protocol families."); + } + } else { + throw new Error("Wallet provider is not a CDP Wallet Provider."); } + } - try { - const address = new ExternalAddress( - walletProvider.getNetwork().networkId!, - walletProvider.getAddress(), - ); + /** + * Swaps tokens using the CDP client. + * + * @param walletProvider - The wallet provider to perform the swap with. + * @param args - The input arguments for the swap action. + * @returns A confirmation message with transaction details. + */ + @CreateAction({ + name: "swap", + description: `This tool swaps tokens using the CDP API. It takes the wallet, from asset ID, to asset ID, and amount as input. +Swaps are currently supported on EVM networks like Base and Ethereum. +Example usage: +- Swap 0.1 ETH to USDC: { fromAssetId: "eth", toAssetId: "usdc", amount: "0.1" } +- Swap 100 USDC to ETH: { fromAssetId: "usdc", toAssetId: "eth", amount: "100" }`, + schema: SwapSchema, + }) + async swap(walletProvider: WalletProvider, args: z.infer): Promise { + const network = walletProvider.getNetwork(); + const networkId = network.networkId!; + + if (isWalletProviderWithClient(walletProvider)) { + if (network.protocolFamily === "evm") { + try { + const cdpNetwork = this.#getCdpSdkNetwork(networkId); - const faucetTx = await address.faucet(args.assetId || undefined); + // Get the account for the wallet address + const account = await walletProvider.getClient().evm.getAccount({ + address: walletProvider.getAddress() as `0x${string}`, + }); - const result = await faucetTx.wait({ timeoutSeconds: 60 }); + // Execute swap using the all-in-one pattern + const swapResult = await account.swap({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + network: cdpNetwork as any, + from: args.fromAssetId, + to: args.toAssetId, + amount: args.amount, + slippageBps: 100, // 1% slippage tolerance + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any); - return `Received ${ - args.assetId || "ETH" - } from the faucet. Transaction: ${result.getTransactionLink()}`; - } catch (error) { - return `Error requesting faucet funds: ${error}`; + return `Successfully swapped ${args.amount} ${args.fromAssetId.toUpperCase()} to ${args.toAssetId.toUpperCase()}. Transaction hash: ${swapResult.transactionHash}`; + } catch (error) { + throw new Error(`Swap failed: ${error}`); + } + } else { + throw new Error("Swap is currently only supported on EVM networks."); + } + } else { + throw new Error("Wallet provider is not a CDP Wallet Provider."); } } @@ -117,7 +143,28 @@ from another wallet and provide the user with your wallet details.`, * @returns True if the Cdp action provider supports the network, false otherwise. */ supportsNetwork = (_: Network) => true; + + /** + * Converts the internal network ID to the format expected by the CDP SDK. + * + * @param networkId - The network ID to convert + * @returns The network ID in CDP SDK format + * @throws Error if the network is not supported + */ + #getCdpSdkNetwork(networkId: string): string { + switch (networkId) { + case "base-sepolia": + return "base-sepolia"; + case "base-mainnet": + return "base"; + case "ethereum-sepolia": + return "ethereum-sepolia"; + case "ethereum-mainnet": + return "ethereum"; + default: + return networkId; // For other networks, use as-is + } + } } -export const cdpApiActionProvider = (config: CdpProviderConfig = {}) => - new CdpApiActionProvider(config); +export const cdpApiActionProvider = () => new CdpApiActionProvider(); diff --git a/typescript/agentkit/src/action-providers/cdp/cdpEvmWalletActionProvider.ts b/typescript/agentkit/src/action-providers/cdp/cdpEvmWalletActionProvider.ts new file mode 100644 index 000000000..39303cece --- /dev/null +++ b/typescript/agentkit/src/action-providers/cdp/cdpEvmWalletActionProvider.ts @@ -0,0 +1,21 @@ +import { ActionProvider } from "../actionProvider"; +import { CdpEvmWalletProvider } from "../../wallet-providers"; +import { Network } from "../../network"; + +/** + * CdpEvmWalletActionProvider is an action provider for Cdp. + * + * This provider is used for any action that requires a CDP Wallet. + */ +class CdpEvmWalletActionProvider extends ActionProvider { + /** + * Constructor for the CdpEvmWalletActionProvider class. + */ + constructor() { + super("cdp_evm", []); + } + + supportsNetwork = (network: Network) => network.protocolFamily === "evm"; +} + +export const cdpEvmWalletActionProvider = () => new CdpEvmWalletActionProvider(); diff --git a/typescript/agentkit/src/action-providers/cdp/cdpSmartWalletActionProvider.ts b/typescript/agentkit/src/action-providers/cdp/cdpSmartWalletActionProvider.ts new file mode 100644 index 000000000..c9c587fa2 --- /dev/null +++ b/typescript/agentkit/src/action-providers/cdp/cdpSmartWalletActionProvider.ts @@ -0,0 +1,21 @@ +import { ActionProvider } from "../actionProvider"; +import { CdpSmartWalletProvider } from "../../wallet-providers"; +import { Network } from "../../network"; + +/** + * CdpSmartWalletActionProvider is an action provider for Cdp. + * + * This provider is used for any action that requires a CDP Wallet. + */ +class CdpSmartWalletActionProvider extends ActionProvider { + /** + * Constructor for the CdpSmartWalletActionProvider class. + */ + constructor() { + super("cdp_smart_wallet", []); + } + + supportsNetwork = (network: Network) => network.protocolFamily === "evm"; +} + +export const cdpSmartWalletActionProvider = () => new CdpSmartWalletActionProvider(); diff --git a/typescript/agentkit/src/action-providers/cdp/cdpSolanaWalletActionProvider.ts b/typescript/agentkit/src/action-providers/cdp/cdpSolanaWalletActionProvider.ts new file mode 100644 index 000000000..ff884485f --- /dev/null +++ b/typescript/agentkit/src/action-providers/cdp/cdpSolanaWalletActionProvider.ts @@ -0,0 +1,21 @@ +import { ActionProvider } from "../actionProvider"; +import { CdpSolanaWalletProvider } from "../../wallet-providers"; +import { Network } from "../../network"; + +/** + * CdpSolanaWalletActionProvider is an action provider for Cdp. + * + * This provider is used for any action that requires a CDP Wallet. + */ +class CdpSolanaWalletActionProvider extends ActionProvider { + /** + * Constructor for the CdpSolanaWalletActionProvider class. + */ + constructor() { + super("cdp_solana", []); + } + + supportsNetwork = (network: Network) => network.protocolFamily === "solana"; +} + +export const cdpSolanaWalletActionProvider = () => new CdpSolanaWalletActionProvider(); diff --git a/typescript/agentkit/src/action-providers/cdp/index.ts b/typescript/agentkit/src/action-providers/cdp/index.ts index 377149961..adfac899b 100644 --- a/typescript/agentkit/src/action-providers/cdp/index.ts +++ b/typescript/agentkit/src/action-providers/cdp/index.ts @@ -1,3 +1,5 @@ export * from "./schemas"; export * from "./cdpApiActionProvider"; -export * from "./cdpWalletActionProvider"; +export * from "./cdpEvmWalletActionProvider"; +export * from "./cdpSolanaWalletActionProvider"; +export * from "./cdpSmartWalletActionProvider"; diff --git a/typescript/agentkit/src/action-providers/cdp/schemas.ts b/typescript/agentkit/src/action-providers/cdp/schemas.ts index 362a7a73e..9d722ddfe 100644 --- a/typescript/agentkit/src/action-providers/cdp/schemas.ts +++ b/typescript/agentkit/src/action-providers/cdp/schemas.ts @@ -1,66 +1,9 @@ import { z } from "zod"; -import { SolidityVersions } from "./constants"; - -/** - * Input schema for address reputation check. - */ -export const AddressReputationSchema = z - .object({ - address: z - .string() - .regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format") - .describe("The Ethereum address to check"), - network: z.string().describe("The network to check the address on"), - }) - .strip() - .describe("Input schema for address reputation check"); - -/** - * Input schema for deploy contract action. - */ -export const DeployContractSchema = z - .object({ - solidityVersion: z - .enum(Object.keys(SolidityVersions) as [string, ...string[]]) - .describe("The solidity compiler version"), - solidityInputJson: z.string().describe("The input json for the solidity compiler"), - contractName: z.string().describe("The name of the contract class to be deployed"), - constructorArgs: z - .record(z.string(), z.any()) - .describe("The constructor arguments for the contract") - .optional(), - }) - .strip() - .describe("Instructions for deploying an arbitrary contract"); - -/** - * Input schema for deploy NFT action - */ -export const DeployNftSchema = z - .object({ - name: z.string().describe("The name of the NFT collection"), - symbol: z.string().describe("The symbol of the NFT collection"), - baseURI: z.string().describe("The base URI for the token metadata"), - }) - .strip() - .describe("Instructions for deploying an NFT collection"); - -/** - * Input schema for deploy token action. - */ -export const DeployTokenSchema = z - .object({ - name: z.string().describe("The name of the token"), - symbol: z.string().describe("The token symbol"), - totalSupply: z.custom().describe("The total supply of tokens to mint"), - }) - .strip() - .describe("Instructions for deploying a token"); /** * Input schema for request faucet funds action. */ -export const RequestFaucetFundsSchema = z +export const RequestFaucetFundsV2Schema = z .object({ assetId: z.string().optional().describe("The optional asset ID to request from faucet"), }) @@ -68,13 +11,17 @@ export const RequestFaucetFundsSchema = z .describe("Instructions for requesting faucet funds"); /** - * Input schema for trade action. + * Input schema for swap tokens action. */ -export const TradeSchema = z +export const SwapSchema = z .object({ - amount: z.custom().describe("The amount of the from asset to trade"), - fromAssetId: z.string().describe("The from asset ID to trade"), - toAssetId: z.string().describe("The to asset ID to receive from the trade"), + fromAssetId: z.string().describe("The asset ID to swap from (e.g., 'eth', 'usdc')"), + toAssetId: z.string().describe("The asset ID to swap to (e.g., 'eth', 'usdc')"), + amount: z.string().describe("The amount to swap (in the from asset's units)"), + network: z + .string() + .optional() + .describe("The network to perform the swap on (defaults to wallet's network)"), }) .strip() - .describe("Instructions for trading assets"); + .describe("Instructions for swapping tokens"); diff --git a/typescript/agentkit/src/action-providers/erc20/erc20ActionProvider.ts b/typescript/agentkit/src/action-providers/erc20/erc20ActionProvider.ts index 2c2f27b4d..8eb38cdf9 100644 --- a/typescript/agentkit/src/action-providers/erc20/erc20ActionProvider.ts +++ b/typescript/agentkit/src/action-providers/erc20/erc20ActionProvider.ts @@ -5,7 +5,7 @@ import { CreateAction } from "../actionDecorator"; import { GetBalanceSchema, TransferSchema } from "./schemas"; import { abi, BaseTokenToAssetId, BaseSepoliaTokenToAssetId } from "./constants"; import { encodeFunctionData, formatUnits, Hex, getAddress } from "viem"; -import { EvmWalletProvider, CdpWalletProvider } from "../../wallet-providers"; +import { EvmWalletProvider, LegacyCdpWalletProvider } from "../../wallet-providers"; /** * ERC20ActionProvider is an action provider for ERC20 tokens. @@ -96,8 +96,8 @@ Important notes: (network.networkId === "base-sepolia" && BaseSepoliaTokenToAssetId.has(tokenAddress))); if (canDoGasless) { - // Cast to CdpWalletProvider to access erc20Transfer - const cdpWallet = walletProvider as CdpWalletProvider; + // Cast to LegacyCdpWalletProvider to access erc20Transfer + const cdpWallet = walletProvider as LegacyCdpWalletProvider; const assetId = network.networkId === "base-mainnet" ? BaseTokenToAssetId.get(tokenAddress)! diff --git a/typescript/agentkit/src/action-providers/index.ts b/typescript/agentkit/src/action-providers/index.ts index 2ea47ab83..f9619094e 100644 --- a/typescript/agentkit/src/action-providers/index.ts +++ b/typescript/agentkit/src/action-providers/index.ts @@ -6,8 +6,8 @@ export * from "./customActionProvider"; export * from "./across"; export * from "./alchemy"; export * from "./basename"; +export * from "./cdp-legacy"; export * from "./cdp"; -export * from "./cdp-v2"; export * from "./compound"; export * from "./defillama"; export * from "./erc20"; diff --git a/typescript/agentkit/src/action-providers/jupiter/jupiterActionProvider.test.ts b/typescript/agentkit/src/action-providers/jupiter/jupiterActionProvider.test.ts index d4d948931..c6ae8f7fc 100644 --- a/typescript/agentkit/src/action-providers/jupiter/jupiterActionProvider.test.ts +++ b/typescript/agentkit/src/action-providers/jupiter/jupiterActionProvider.test.ts @@ -3,9 +3,6 @@ import { SvmWalletProvider } from "../../wallet-providers/svmWalletProvider"; import { JupiterActionProvider } from "./jupiterActionProvider"; import { createJupiterApiClient } from "@jup-ag/api"; -// Default SPL token decimals for tests -const DECIMALS = 6; - // Mock the @solana/web3.js module jest.mock("@solana/web3.js", () => ({ // Preserve the actual implementation of @solana/web3.js while overriding specific methods @@ -34,7 +31,7 @@ jest.mock("@solana/spl-token", () => ({ ...jest.requireActual("@solana/spl-token"), // Mock getMint to always return a fixed decimal value for tokens - getMint: jest.fn().mockReturnValue({ decimals: DECIMALS }), + getMint: jest.fn().mockReturnValue({ decimals: 6 }), })); // Mock the @jup-ag/api module @@ -128,7 +125,7 @@ describe("JupiterActionProvider", () => { expect(mockQuoteGet).toHaveBeenCalledWith({ inputMint: INPUT_MINT, outputMint: OUTPUT_MINT, - amount: swapArgs.amount * 10 ** DECIMALS, // Ensure correct decimal conversion + amount: swapArgs.amount * 10 ** 6, // Ensure correct decimal conversion slippageBps: swapArgs.slippageBps, }); diff --git a/typescript/agentkit/src/agentkit.ts b/typescript/agentkit/src/agentkit.ts index 5ab997420..47f0acf4a 100644 --- a/typescript/agentkit/src/agentkit.ts +++ b/typescript/agentkit/src/agentkit.ts @@ -1,4 +1,4 @@ -import { WalletProvider, CdpWalletProvider } from "./wallet-providers"; +import { WalletProvider, CdpSmartWalletProvider } from "./wallet-providers"; import { Action, ActionProvider, walletActionProvider } from "./action-providers"; /** @@ -7,6 +7,7 @@ import { Action, ActionProvider, walletActionProvider } from "./action-providers export type AgentKitOptions = { cdpApiKeyId?: string; cdpApiKeySecret?: string; + cdpWalletSecret?: string; walletProvider?: WalletProvider; actionProviders?: ActionProvider[]; }; @@ -47,15 +48,16 @@ export class AgentKit { let walletProvider: WalletProvider | undefined = config.walletProvider; if (!config.walletProvider) { - if (!config.cdpApiKeyId || !config.cdpApiKeySecret) { + if (!config.cdpApiKeyId || !config.cdpApiKeySecret || !config.cdpWalletSecret) { throw new Error( "cdpApiKeyId and cdpApiKeySecret are required if not providing a walletProvider", ); } - walletProvider = await CdpWalletProvider.configureWithWallet({ + walletProvider = await CdpSmartWalletProvider.configureWithWallet({ apiKeyId: config.cdpApiKeyId, apiKeySecret: config.cdpApiKeySecret, + walletSecret: config.cdpWalletSecret, }); } diff --git a/typescript/agentkit/src/wallet-providers/cdpV2EvmWalletProvider.test.ts b/typescript/agentkit/src/wallet-providers/cdpEvmWalletProvider.test.ts similarity index 96% rename from typescript/agentkit/src/wallet-providers/cdpV2EvmWalletProvider.test.ts rename to typescript/agentkit/src/wallet-providers/cdpEvmWalletProvider.test.ts index d5cd3cc2a..f58e626b4 100644 --- a/typescript/agentkit/src/wallet-providers/cdpV2EvmWalletProvider.test.ts +++ b/typescript/agentkit/src/wallet-providers/cdpEvmWalletProvider.test.ts @@ -9,7 +9,7 @@ import { createWalletClient, } from "viem"; import { Network } from "../network"; -import { CdpV2EvmWalletProvider } from "./cdpV2EvmWalletProvider"; +import { CdpEvmWalletProvider } from "./cdpEvmWalletProvider"; // ========================================================= // consts @@ -121,8 +121,8 @@ const MOCK_TRANSACTION_RECEIPT = { transactionHash: MOCK_TRANSACTION_HASH, } as unknown as TransactionReceipt; -describe("CdpV2EvmWalletProvider", () => { - let provider: CdpV2EvmWalletProvider; +describe("CdpEvmWalletProvider", () => { + let provider: CdpEvmWalletProvider; let mockCdpClient: jest.Mocked; let mockServerAccount: jest.Mocked; let mockSignTransaction: jest.Mock; @@ -167,7 +167,7 @@ describe("CdpV2EvmWalletProvider", () => { mockWalletClient.sendTransaction.mockResolvedValue(MOCK_TRANSACTION_HASH); - provider = await CdpV2EvmWalletProvider.configureWithWallet({ + provider = await CdpEvmWalletProvider.configureWithWallet({ apiKeyId: "test-key-id", apiKeySecret: "test-key-secret", walletSecret: "test-wallet-secret", @@ -181,7 +181,7 @@ describe("CdpV2EvmWalletProvider", () => { describe("initialization", () => { it("should initialize with API keys", async () => { - const provider = await CdpV2EvmWalletProvider.configureWithWallet({ + const provider = await CdpEvmWalletProvider.configureWithWallet({ apiKeyId: "test-key-id", apiKeySecret: "test-key-secret", walletSecret: "test-wallet-secret", @@ -193,7 +193,7 @@ describe("CdpV2EvmWalletProvider", () => { }); it("should default to base-sepolia if network not provided", async () => { - const provider = await CdpV2EvmWalletProvider.configureWithWallet({ + const provider = await CdpEvmWalletProvider.configureWithWallet({ apiKeyId: "test-key-id", apiKeySecret: "test-key-secret", walletSecret: "test-wallet-secret", @@ -223,7 +223,7 @@ describe("CdpV2EvmWalletProvider", () => { (CdpClient as jest.MockedClass).mockImplementation(() => mockCdpClient); await expect( - CdpV2EvmWalletProvider.configureWithWallet({ + CdpEvmWalletProvider.configureWithWallet({ apiKeyId: "test-key-id", apiKeySecret: "test-key-secret", walletSecret: "test-wallet-secret", @@ -247,7 +247,7 @@ describe("CdpV2EvmWalletProvider", () => { }); it("should get the name", () => { - expect(provider.getName()).toBe("cdp_v2_wallet_provider"); + expect(provider.getName()).toBe("cdp_evm_wallet_provider"); }); it("should get the balance", async () => { diff --git a/typescript/agentkit/src/wallet-providers/cdpV2EvmWalletProvider.ts b/typescript/agentkit/src/wallet-providers/cdpEvmWalletProvider.ts similarity index 87% rename from typescript/agentkit/src/wallet-providers/cdpV2EvmWalletProvider.ts rename to typescript/agentkit/src/wallet-providers/cdpEvmWalletProvider.ts index 84bb6cd21..bb27a1cc8 100644 --- a/typescript/agentkit/src/wallet-providers/cdpV2EvmWalletProvider.ts +++ b/typescript/agentkit/src/wallet-providers/cdpEvmWalletProvider.ts @@ -16,9 +16,9 @@ import { } from "viem"; import { Network, NETWORK_ID_TO_CHAIN_ID, NETWORK_ID_TO_VIEM_CHAIN } from "../network"; import { EvmWalletProvider } from "./evmWalletProvider"; -import { WalletProviderWithClient, CdpV2WalletProviderConfig } from "./cdpV2Shared"; +import { WalletProviderWithClient, CdpWalletProviderConfig } from "./cdpShared"; -interface ConfigureCdpV2EvmWalletProviderWithWalletOptions { +interface ConfigureCdpEvmWalletProviderWithWalletOptions { /** * The CDP client of the wallet. */ @@ -43,18 +43,18 @@ interface ConfigureCdpV2EvmWalletProviderWithWalletOptions { /** * A wallet provider that uses the Coinbase SDK. */ -export class CdpV2EvmWalletProvider extends EvmWalletProvider implements WalletProviderWithClient { +export class CdpEvmWalletProvider extends EvmWalletProvider implements WalletProviderWithClient { #publicClient: PublicClient; #serverAccount: EvmServerAccount; #cdp: CdpClient; #network: Network; /** - * Constructs a new CdpWalletProvider. + * Constructs a new CdpServerWalletProvider. * - * @param config - The configuration options for the CdpWalletProvider. + * @param config - The configuration options for the CdpServerWalletProvider. */ - private constructor(config: ConfigureCdpV2EvmWalletProviderWithWalletOptions) { + private constructor(config: ConfigureCdpEvmWalletProviderWithWalletOptions) { super(); this.#serverAccount = config.serverAccount; @@ -64,15 +64,15 @@ export class CdpV2EvmWalletProvider extends EvmWalletProvider implements WalletP } /** - * Configures a new CdpWalletProvider with a wallet. + * Configures a new CdpServerWalletProvider with a wallet. * * @param config - Optional configuration parameters - * @returns A Promise that resolves to a new CdpWalletProvider instance + * @returns A Promise that resolves to a new CdpServerWalletProvider instance * @throws Error if required environment variables are missing or wallet initialization fails */ public static async configureWithWallet( - config: CdpV2WalletProviderConfig = {}, - ): Promise { + config: CdpWalletProviderConfig = {}, + ): Promise { const apiKeyId = config.apiKeyId || process.env.CDP_API_KEY_ID; const apiKeySecret = config.apiKeySecret || process.env.CDP_API_KEY_SECRET; const walletSecret = config.walletSecret || process.env.CDP_WALLET_SECRET; @@ -106,7 +106,7 @@ export class CdpV2EvmWalletProvider extends EvmWalletProvider implements WalletP transport: http(), }); - return new CdpV2EvmWalletProvider({ + return new CdpEvmWalletProvider({ publicClient, cdp: cdpClient, serverAccount, @@ -114,6 +114,18 @@ export class CdpV2EvmWalletProvider extends EvmWalletProvider implements WalletP }); } + /** + * Exports the wallet. + * + * @returns The wallet's data. + */ + async exportWallet(): Promise<{ name: string | undefined; address: `0x${string}` }> { + return { + name: this.#serverAccount.name, + address: this.#serverAccount.address as `0x${string}`, + }; + } + /** * Signs a message. * @@ -211,7 +223,7 @@ export class CdpV2EvmWalletProvider extends EvmWalletProvider implements WalletP * @returns The name of the wallet provider. */ getName(): string { - return "cdp_v2_wallet_provider"; + return "cdp_evm_wallet_provider"; } /** diff --git a/typescript/agentkit/src/wallet-providers/cdpV2Shared.ts b/typescript/agentkit/src/wallet-providers/cdpShared.ts similarity index 69% rename from typescript/agentkit/src/wallet-providers/cdpV2Shared.ts rename to typescript/agentkit/src/wallet-providers/cdpShared.ts index 264dbc4b7..cbcd88ae6 100644 --- a/typescript/agentkit/src/wallet-providers/cdpV2Shared.ts +++ b/typescript/agentkit/src/wallet-providers/cdpShared.ts @@ -1,6 +1,7 @@ -import { CdpClient } from "@coinbase/cdp-sdk"; +import { CdpClient, type EvmServerAccount } from "@coinbase/cdp-sdk"; +import type { Address, PrivateKeyAccount } from "viem"; -export interface CdpV2ProviderConfig { +export interface CdpProviderConfig { /** * The CDP API Key ID. */ @@ -20,11 +21,11 @@ export interface CdpV2ProviderConfig { /** * Configuration options for the CDP Providers. */ -export interface CdpV2WalletProviderConfig extends CdpV2ProviderConfig { +export interface CdpWalletProviderConfig extends CdpProviderConfig { /** * The address of the wallet. */ - address?: string; + address?: Address; /** * The network of the wallet. @@ -37,6 +38,18 @@ export interface CdpV2WalletProviderConfig extends CdpV2ProviderConfig { idempotencyKey?: string; } +export interface CdpSmartWalletProviderConfig extends CdpWalletProviderConfig { + /** + * The owner account of the smart wallet. + */ + owner?: EvmServerAccount | PrivateKeyAccount | Address; + + /** + * The name of the smart wallet. + */ + smartAccountName?: string; +} + /** * A wallet provider that can be used to interact with the CDP. */ diff --git a/typescript/agentkit/src/wallet-providers/cdpSmartWalletProvider.test.ts b/typescript/agentkit/src/wallet-providers/cdpSmartWalletProvider.test.ts new file mode 100644 index 000000000..614ac4327 --- /dev/null +++ b/typescript/agentkit/src/wallet-providers/cdpSmartWalletProvider.test.ts @@ -0,0 +1,338 @@ +import { CdpClient, EvmSmartAccount, EvmServerAccount } from "@coinbase/cdp-sdk"; +import { PublicClient, ReadContractParameters, TransactionRequest } from "viem"; +import { Network } from "../network"; +import { CdpSmartWalletProvider } from "./cdpSmartWalletProvider"; + +// ========================================================= +// consts +// ========================================================= + +const mockPublicClient = { + readContract: jest.fn(), + getBalance: jest.fn(), +} as unknown as jest.Mocked; + +// ========================================================= +// mocks +// ========================================================= + +jest.mock("../analytics", () => ({ + sendAnalyticsEvent: jest.fn().mockImplementation(() => Promise.resolve()), +})); + +jest.mock("../../package.json", () => ({ + version: "1.0.0", +})); + +jest.mock("viem", () => { + return { + createPublicClient: jest.fn(() => mockPublicClient), + http: jest.fn(), + }; +}); + +jest.mock("../network", () => { + return { + NETWORK_ID_TO_CHAIN_ID: { + "base-mainnet": "8453", + "base-sepolia": "84532", + }, + NETWORK_ID_TO_VIEM_CHAIN: { + "base-mainnet": { id: 8453 }, + "base-sepolia": { id: 84532 }, + }, + }; +}); + +// Mock CdpClient +jest.mock("@coinbase/cdp-sdk", () => { + const MOCK_ADDRESS = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"; + const MOCK_SMART_ADDRESS = "0x1234567890123456789012345678901234567890"; + const MOCK_SIGNATURE = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b01"; + const MOCK_USER_OP_HASH = "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"; + + const mockCreateAccount = jest.fn().mockImplementation(() => + Promise.resolve({ + address: MOCK_ADDRESS, + signMessage: jest.fn().mockResolvedValue(MOCK_SIGNATURE), + signTypedData: jest.fn().mockResolvedValue(MOCK_SIGNATURE), + }), + ); + + const mockGetSmartAccount = jest.fn().mockImplementation(() => + Promise.resolve({ + address: MOCK_SMART_ADDRESS, + }), + ); + + const mockCreateSmartAccount = jest.fn().mockImplementation(() => + Promise.resolve({ + address: MOCK_SMART_ADDRESS, + }), + ); + + const mockGetOrCreateSmartAccount = jest.fn().mockImplementation(() => + Promise.resolve({ + address: MOCK_SMART_ADDRESS, + }), + ); + + const mockSendUserOperation = jest + .fn() + .mockImplementation(async () => ({ userOpHash: MOCK_USER_OP_HASH })); + + const mockEvmClient = { + createAccount: mockCreateAccount as jest.MockedFunction, + getAccount: jest.fn(), + createSmartAccount: mockCreateSmartAccount, + getOrCreateSmartAccount: mockGetOrCreateSmartAccount, + getSmartAccount: mockGetSmartAccount, + sendUserOperation: mockSendUserOperation, + }; + + return { + CdpClient: jest.fn().mockImplementation(() => ({ + evm: mockEvmClient, + })), + EvmServerAccount: jest.fn().mockImplementation(() => ({ + address: MOCK_ADDRESS, + signMessage: jest.fn().mockResolvedValue(MOCK_SIGNATURE), + signTypedData: jest.fn().mockResolvedValue(MOCK_SIGNATURE), + })), + EvmSmartAccount: jest.fn().mockImplementation(() => ({ + address: MOCK_SMART_ADDRESS, + })), + }; +}); + +// ========================================================= +// test constants +// ========================================================= + +const MOCK_ADDRESS = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"; +const MOCK_SMART_ADDRESS = "0x1234567890123456789012345678901234567890"; +const MOCK_NETWORK_ID = "base-sepolia"; +const MOCK_USER_OP_HASH = "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"; +const MOCK_SIGNATURE = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b01"; +const MOCK_BALANCE = 1000000000000000000n; +const MOCK_NETWORK: Network = { + protocolFamily: "evm", + networkId: MOCK_NETWORK_ID, + chainId: "84532", +}; + +describe("CdpSmartWalletProvider", () => { + let provider: CdpSmartWalletProvider; + let mockCdpClient: jest.Mocked; + let mockSmartAccount: jest.Mocked; + let mockOwnerAccount: jest.Mocked; + + beforeEach(async () => { + jest.clearAllMocks(); + + mockCdpClient = new CdpClient({ + apiKeyId: "test-key-id", + apiKeySecret: "test-key-secret", + walletSecret: "test-wallet-secret", + }) as jest.Mocked; + + mockOwnerAccount = { + address: MOCK_ADDRESS, + signMessage: jest.fn().mockResolvedValue(MOCK_SIGNATURE), + signTypedData: jest.fn().mockResolvedValue(MOCK_SIGNATURE), + } as unknown as jest.Mocked; + + mockSmartAccount = { + address: MOCK_SMART_ADDRESS, + } as unknown as jest.Mocked; + + // Set up the mock smart account for the provider + ( + mockCdpClient.evm.createAccount as jest.MockedFunction + ).mockResolvedValue(mockOwnerAccount); + ( + mockCdpClient.evm.createSmartAccount as jest.MockedFunction< + typeof mockCdpClient.evm.createSmartAccount + > + ).mockResolvedValue(mockSmartAccount); + + mockPublicClient.readContract.mockResolvedValue("mock_result" as string); + mockPublicClient.getBalance.mockResolvedValue(MOCK_BALANCE); + + provider = await CdpSmartWalletProvider.configureWithWallet({ + apiKeyId: "test-key-id", + apiKeySecret: "test-key-secret", + walletSecret: "test-wallet-secret", + networkId: MOCK_NETWORK_ID, + }); + }); + + // ========================================================= + // initialization tests + // ========================================================= + + describe("initialization", () => { + it("should initialize with API keys", async () => { + const provider = await CdpSmartWalletProvider.configureWithWallet({ + apiKeyId: "test-key-id", + apiKeySecret: "test-key-secret", + walletSecret: "test-wallet-secret", + networkId: MOCK_NETWORK_ID, + }); + + expect(provider.getAddress()).toBe(MOCK_SMART_ADDRESS); + expect(provider.getNetwork()).toEqual(MOCK_NETWORK); + }); + + it("should throw error for non-Base networks", async () => { + await expect( + CdpSmartWalletProvider.configureWithWallet({ + apiKeyId: "test-key-id", + apiKeySecret: "test-key-secret", + walletSecret: "test-wallet-secret", + networkId: "ethereum-sepolia", + }), + ).rejects.toThrow("Smart wallets are only supported on Base networks"); + }); + + it("should create smart account with name", async () => { + await CdpSmartWalletProvider.configureWithWallet({ + apiKeyId: "test-key-id", + apiKeySecret: "test-key-secret", + walletSecret: "test-wallet-secret", + networkId: MOCK_NETWORK_ID, + smartAccountName: "MySmartWallet", + }); + + expect(mockCdpClient.evm.getSmartAccount).toHaveBeenCalledWith({ + name: "MySmartWallet", + owner: mockOwnerAccount, + }); + }); + }); + + // ========================================================= + // basic wallet method tests + // ========================================================= + + describe("basic wallet methods", () => { + it("should get the smart wallet address", () => { + expect(provider.getAddress()).toBe(MOCK_SMART_ADDRESS); + }); + + it("should get the network", () => { + expect(provider.getNetwork()).toEqual(MOCK_NETWORK); + }); + + it("should get the name", () => { + expect(provider.getName()).toBe("cdp_smart_wallet_provider"); + }); + + it("should get the balance", async () => { + const balance = await provider.getBalance(); + expect(balance).toBe(MOCK_BALANCE); + expect(mockPublicClient.getBalance).toHaveBeenCalledWith({ + address: MOCK_SMART_ADDRESS, + }); + }); + }); + + // ========================================================= + // signing operation tests + // ========================================================= + + describe("signing operations", () => { + it("should sign messages using owner account", async () => { + const signature = await provider.signMessage("Hello, world!"); + expect(mockOwnerAccount.signMessage).toHaveBeenCalledWith({ message: "Hello, world!" }); + expect(signature).toBe(MOCK_SIGNATURE); + }); + + it("should sign typed data using owner account", async () => { + const typedData = { + domain: { name: "Example" }, + types: { Test: [{ name: "test", type: "string" }] }, + message: { test: "example" }, + primaryType: "Test", + }; + + const signature = await provider.signTypedData(typedData); + expect(mockOwnerAccount.signTypedData).toHaveBeenCalledWith(typedData); + expect(signature).toBe(MOCK_SIGNATURE); + }); + + it("should throw error for direct transaction signing", async () => { + const tx = { + to: "0x1234567890123456789012345678901234567890" as `0x${string}`, + value: BigInt(1000000000000000000), + }; + + await expect(provider.signTransaction(tx)).rejects.toThrow( + "Direct transaction signing not supported for smart wallets", + ); + }); + }); + + // ========================================================= + // user operation tests + // ========================================================= + + describe("user operations", () => { + it("should send user operations", async () => { + const transaction: TransactionRequest = { + to: "0x1234567890123456789012345678901234567890" as `0x${string}`, + value: BigInt(1000000000000000000), + }; + + const userOpHash = await provider.sendTransaction(transaction); + expect(mockCdpClient.evm.sendUserOperation).toHaveBeenCalledWith({ + smartAccount: mockSmartAccount, + network: "base-sepolia", + calls: [ + { + to: transaction.to, + value: BigInt(transaction.value!.toString()), + data: "0x", + }, + ], + }); + expect(userOpHash).toBe(MOCK_USER_OP_HASH); + }); + + it("should handle native transfers", async () => { + const userOpHash = await provider.nativeTransfer( + "0x1234567890123456789012345678901234567890" as `0x${string}`, + "1000000000000000000", + ); + expect(userOpHash).toBe(MOCK_USER_OP_HASH); + }); + }); + + // ========================================================= + // contract interaction tests + // ========================================================= + + describe("contract interactions", () => { + it("should read contract data", async () => { + const abi = [ + { + name: "balanceOf", + type: "function", + inputs: [{ name: "account", type: "address" }], + outputs: [{ name: "balance", type: "uint256" }], + stateMutability: "view", + }, + ] as const; + + const result = await provider.readContract({ + address: "0x1234567890123456789012345678901234567890" as `0x${string}`, + abi, + functionName: "balanceOf", + args: [MOCK_SMART_ADDRESS as `0x${string}`], + } as unknown as jest.Mocked); + + expect(result).toBe("mock_result"); + expect(mockPublicClient.readContract).toHaveBeenCalled(); + }); + }); +}); diff --git a/typescript/agentkit/src/wallet-providers/cdpSmartWalletProvider.ts b/typescript/agentkit/src/wallet-providers/cdpSmartWalletProvider.ts new file mode 100644 index 000000000..2f0fd4972 --- /dev/null +++ b/typescript/agentkit/src/wallet-providers/cdpSmartWalletProvider.ts @@ -0,0 +1,333 @@ +import { CdpClient, EvmSmartAccount, EvmServerAccount } from "@coinbase/cdp-sdk"; +import { + Abi, + Address, + ContractFunctionArgs, + ContractFunctionName, + createPublicClient, + Hex, + http, + PublicClient, + ReadContractParameters, + ReadContractReturnType, + TransactionRequest, + PrivateKeyAccount, +} from "viem"; +import { Network, NETWORK_ID_TO_CHAIN_ID, NETWORK_ID_TO_VIEM_CHAIN } from "../network"; +import { EvmWalletProvider } from "./evmWalletProvider"; +import { WalletProviderWithClient, CdpSmartWalletProviderConfig } from "./cdpShared"; + +interface ConfigureCdpSmartWalletProviderWithWalletOptions { + /** + * The CDP client of the wallet. + */ + cdp: CdpClient; + + /** + * The smart account of the wallet. + */ + smartAccount: EvmSmartAccount; + + /** + * The owner account of the smart wallet. + */ + ownerAccount: EvmServerAccount | PrivateKeyAccount; + + /** + * The public client of the wallet. + */ + publicClient: PublicClient; + + /** + * The network of the wallet. + */ + network: Network; +} + +/** + * A wallet provider that uses the Coinbase CDP SDK smart wallets. + */ +export class CdpSmartWalletProvider extends EvmWalletProvider implements WalletProviderWithClient { + #publicClient: PublicClient; + #smartAccount: EvmSmartAccount; + #ownerAccount: PrivateKeyAccount | EvmServerAccount; + #cdp: CdpClient; + #network: Network; + + /** + * Constructs a new CdpSmartWalletProvider. + * + * @param config - The configuration options for the CdpSmartWalletProvider. + */ + private constructor(config: ConfigureCdpSmartWalletProviderWithWalletOptions) { + super(); + + this.#smartAccount = config.smartAccount; + this.#ownerAccount = config.ownerAccount; + this.#cdp = config.cdp; + this.#publicClient = config.publicClient; + this.#network = config.network; + } + + /** + * Configures a new CdpSmartWalletProvider with a smart wallet. + * + * @param config - Optional configuration parameters + * @returns A Promise that resolves to a new CdpSmartWalletProvider instance + * @throws Error if required environment variables are missing or wallet initialization fails + */ + public static async configureWithWallet( + config: CdpSmartWalletProviderConfig = {}, + ): Promise { + const apiKeyId = config.apiKeyId || process.env.CDP_API_KEY_ID; + const apiKeySecret = config.apiKeySecret || process.env.CDP_API_KEY_SECRET; + const walletSecret = config.walletSecret || process.env.CDP_WALLET_SECRET; + const idempotencyKey = config.idempotencyKey || process.env.IDEMPOTENCY_KEY; + + if (!apiKeyId || !apiKeySecret || !walletSecret) { + throw new Error( + "Missing required environment variables. CDP_API_KEY_ID, CDP_API_KEY_SECRET, CDP_WALLET_SECRET are required.", + ); + } + + const networkId: string = config.networkId || process.env.NETWORK_ID || "base-sepolia"; + + // Smart wallets are currently only supported on Base networks + if (!networkId.startsWith("base-")) { + throw new Error(`Smart wallets are only supported on Base networks. Got: ${networkId}`); + } + + const network = { + protocolFamily: "evm" as const, + chainId: NETWORK_ID_TO_CHAIN_ID[networkId], + networkId: networkId, + }; + + const cdpClient = new CdpClient({ + apiKeyId, + apiKeySecret, + walletSecret, + }); + + // Create or get the owner account + const ownerAccount = await (() => { + if (typeof config.owner === "string") { + return cdpClient.evm.getAccount({ address: config.owner as Address }); + } + + if (typeof config.owner === "object") { + return config.owner; + } + + return cdpClient.evm.createAccount({ idempotencyKey }); + })(); + + // Create or get the smart account + const smartAccount = await (config.address || config.smartAccountName + ? cdpClient.evm.getSmartAccount({ + address: config.address, + name: config.smartAccountName, + owner: ownerAccount as EvmServerAccount, + }) + : cdpClient.evm.createSmartAccount({ + owner: ownerAccount as EvmServerAccount, + })); + + const publicClient = createPublicClient({ + chain: NETWORK_ID_TO_VIEM_CHAIN[networkId], + transport: http(), + }); + + return new CdpSmartWalletProvider({ + publicClient, + cdp: cdpClient, + smartAccount, + ownerAccount, + network, + }); + } + + /** + * Exports the wallet. + * + * @returns The wallet's data. + */ + async exportWallet(): Promise<{ + name: string | undefined; + address: Address; + ownerAddress: Address; + }> { + return { + name: this.#smartAccount.name, + address: this.#smartAccount.address as Address, + ownerAddress: this.#ownerAccount.address as Address, + }; + } + + /** + * Signs a message using the owner account. + * + * @param message - The message to sign. + * @returns The signed message. + */ + async signMessage(message: string): Promise { + return this.#ownerAccount.signMessage({ message }); + } + + /** + * Signs a typed data object using the owner account. + * + * @param typedData - The typed data object to sign. + * @returns The signed typed data object. + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async signTypedData(typedData: any): Promise { + return this.#ownerAccount.signTypedData(typedData); + } + + /** + * Signs a transaction using the owner account. + * + * @param _ - The transaction to sign. + * @returns The signed transaction. + */ + async signTransaction(_: TransactionRequest): Promise { + throw new Error( + "Direct transaction signing not supported for smart wallets. Use sendTransaction instead.", + ); + } + + /** + * Sends a user operation through the smart wallet. + * + * @param transaction - The transaction to send. + * @returns The user operation hash. + */ + async sendTransaction(transaction: TransactionRequest): Promise { + const calls = [ + { + to: transaction.to as Address, + value: transaction.value ? BigInt(transaction.value.toString()) : 0n, + data: (transaction.data as Hex) || "0x", + }, + ]; + + const userOperation = await this.#cdp.evm.sendUserOperation({ + smartAccount: this.#smartAccount, + network: this.#getCdpSdkNetwork(), + calls, + }); + + return userOperation.userOpHash as Hex; + } + + /** + * Gets the address of the smart wallet. + * + * @returns The address of the smart wallet. + */ + getAddress(): string { + return this.#smartAccount.address; + } + + /** + * Gets the network of the wallet. + * + * @returns The network of the wallet. + */ + getNetwork(): Network { + return this.#network; + } + + /** + * Gets the name of the wallet provider. + * + * @returns The name of the wallet provider. + */ + getName(): string { + return "cdp_smart_wallet_provider"; + } + + /** + * Gets the CDP client. + * + * @returns The CDP client. + */ + getClient(): CdpClient { + return this.#cdp; + } + + /** + * Gets the balance of the smart wallet. + * + * @returns The balance of the wallet in wei + */ + async getBalance(): Promise { + return await this.#publicClient.getBalance({ address: this.#smartAccount.address }); + } + + /** + * Waits for a user operation receipt. + * + * @param userOpHash - The user operation hash to wait for. + * @returns The user operation receipt. + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async waitForTransactionReceipt(userOpHash: Hex): Promise { + // For smart wallets, we need to wait for the user operation to be confirmed + // This is a simplified implementation - in practice you might want to poll + // the CDP API for user operation status + return this.#cdp.evm.waitForUserOperation({ + smartAccountAddress: this.#smartAccount.address, + userOpHash, + }); + } + + /** + * Reads a contract. + * + * @param params - The parameters to read the contract. + * @returns The response from the contract. + */ + async readContract< + const abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + const args extends ContractFunctionArgs, + >( + params: ReadContractParameters, + ): Promise> { + return this.#publicClient.readContract(params); + } + + /** + * Transfer the native asset of the network using smart wallet. + * + * @param to - The destination address. + * @param value - The amount to transfer in Wei. + * @returns The user operation hash. + */ + async nativeTransfer(to: Address, value: string): Promise { + return this.sendTransaction({ + to: to, + value: BigInt(value), + data: "0x", + }); + } + + /** + * Converts the internal network ID to the format expected by the CDP SDK. + * + * @returns The network ID in CDP SDK format ("base-sepolia" or "base") + * @throws Error if the network is not supported + */ + #getCdpSdkNetwork(): "base-sepolia" | "base" { + switch (this.#network.networkId) { + case "base-sepolia": + return "base-sepolia"; + case "base-mainnet": + return "base"; + default: + throw new Error(`Unsupported network for smart wallets: ${this.#network.networkId}`); + } + } +} diff --git a/typescript/agentkit/src/wallet-providers/cdpV2SolanaWalletProvider.test.ts b/typescript/agentkit/src/wallet-providers/cdpSolanaWalletProvider.test.ts similarity index 96% rename from typescript/agentkit/src/wallet-providers/cdpV2SolanaWalletProvider.test.ts rename to typescript/agentkit/src/wallet-providers/cdpSolanaWalletProvider.test.ts index eae1d88a7..1017b435a 100644 --- a/typescript/agentkit/src/wallet-providers/cdpV2SolanaWalletProvider.test.ts +++ b/typescript/agentkit/src/wallet-providers/cdpSolanaWalletProvider.test.ts @@ -9,7 +9,7 @@ import { VersionedTransaction, } from "@solana/web3.js"; import { Network } from "../network"; -import { CdpV2SolanaWalletProvider } from "./cdpV2SolanaWalletProvider"; +import { CdpSolanaWalletProvider } from "./cdpSolanaWalletProvider"; // ========================================================= // consts @@ -130,8 +130,8 @@ const MOCK_SIGNATURE_RESULT = { }, } as RpcResponseAndContext; -describe("CdpV2SolanaWalletProvider", () => { - let provider: CdpV2SolanaWalletProvider; +describe("CdpSolanaWalletProvider", () => { + let provider: CdpSolanaWalletProvider; let mockCdpClient: jest.Mocked; let mockServerAccount: jest.Mocked< Awaited> @@ -171,7 +171,7 @@ describe("CdpV2SolanaWalletProvider", () => { mockConnection.confirmTransaction.mockResolvedValue(MOCK_SIGNATURE_RESULT); mockConnection.sendTransaction.mockResolvedValue(MOCK_SIGNATURE); - provider = await CdpV2SolanaWalletProvider.configureWithWallet({ + provider = await CdpSolanaWalletProvider.configureWithWallet({ apiKeyId: "test-key-id", apiKeySecret: "test-key-secret", walletSecret: "test-wallet-secret", @@ -185,7 +185,7 @@ describe("CdpV2SolanaWalletProvider", () => { describe("initialization", () => { it("should initialize with API keys", async () => { - const provider = await CdpV2SolanaWalletProvider.configureWithWallet({ + const provider = await CdpSolanaWalletProvider.configureWithWallet({ apiKeyId: "test-key-id", apiKeySecret: "test-key-secret", walletSecret: "test-wallet-secret", @@ -197,7 +197,7 @@ describe("CdpV2SolanaWalletProvider", () => { }); it("should default to solana-devnet if network not provided", async () => { - const provider = await CdpV2SolanaWalletProvider.configureWithWallet({ + const provider = await CdpSolanaWalletProvider.configureWithWallet({ apiKeyId: "test-key-id", apiKeySecret: "test-key-secret", walletSecret: "test-wallet-secret", @@ -224,7 +224,7 @@ describe("CdpV2SolanaWalletProvider", () => { (CdpClient as jest.MockedClass).mockImplementation(() => mockCdpClient); await expect( - CdpV2SolanaWalletProvider.configureWithWallet({ + CdpSolanaWalletProvider.configureWithWallet({ apiKeyId: "test-key-id", apiKeySecret: "test-key-secret", walletSecret: "test-wallet-secret", @@ -248,7 +248,7 @@ describe("CdpV2SolanaWalletProvider", () => { }); it("should get the name", () => { - expect(provider.getName()).toBe("cdp_v2_solana_wallet_provider"); + expect(provider.getName()).toBe("cdp_solana_wallet_provider"); }); it("should get the balance", async () => { diff --git a/typescript/agentkit/src/wallet-providers/cdpV2SolanaWalletProvider.ts b/typescript/agentkit/src/wallet-providers/cdpSolanaWalletProvider.ts similarity index 88% rename from typescript/agentkit/src/wallet-providers/cdpV2SolanaWalletProvider.ts rename to typescript/agentkit/src/wallet-providers/cdpSolanaWalletProvider.ts index dd71273e3..132769195 100644 --- a/typescript/agentkit/src/wallet-providers/cdpV2SolanaWalletProvider.ts +++ b/typescript/agentkit/src/wallet-providers/cdpSolanaWalletProvider.ts @@ -22,10 +22,10 @@ import { SOLANA_TESTNET_NETWORK, SOLANA_TESTNET_NETWORK_ID, } from "../network/svm"; -import { WalletProviderWithClient, CdpV2WalletProviderConfig } from "./cdpV2Shared"; +import { WalletProviderWithClient, CdpWalletProviderConfig } from "./cdpShared"; import { SvmWalletProvider } from "./svmWalletProvider"; -interface ConfigureCdpV2WalletProviderWithWalletOptions { +interface ConfigureCdpSolanaWalletProviderWithWalletOptions { /** * The CDP client of the wallet. */ @@ -50,21 +50,18 @@ interface ConfigureCdpV2WalletProviderWithWalletOptions { /** * A wallet provider that uses the Coinbase SDK. */ -export class CdpV2SolanaWalletProvider - extends SvmWalletProvider - implements WalletProviderWithClient -{ +export class CdpSolanaWalletProvider extends SvmWalletProvider implements WalletProviderWithClient { #connection: Connection; #serverAccount: Awaited>; #cdp: CdpClient; #network: Network; /** - * Constructs a new CdpWalletProvider. + * Constructs a new CdpSolanaWalletProvider. * - * @param config - The configuration options for the CdpWalletProvider. + * @param config - The configuration options for the CdpSolanaWalletProvider. */ - private constructor(config: ConfigureCdpV2WalletProviderWithWalletOptions) { + private constructor(config: ConfigureCdpSolanaWalletProviderWithWalletOptions) { super(); this.#serverAccount = config.serverAccount; @@ -74,15 +71,15 @@ export class CdpV2SolanaWalletProvider } /** - * Configures a new CdpWalletProvider with a wallet. + * Configures a new CdpSolanaWalletProvider with a wallet. * * @param config - Optional configuration parameters - * @returns A Promise that resolves to a new CdpWalletProvider instance + * @returns A Promise that resolves to a new CdpSolanaWalletProvider instance * @throws Error if required environment variables are missing or wallet initialization fails */ public static async configureWithWallet( - config: CdpV2WalletProviderConfig = {}, - ): Promise { + config: CdpWalletProviderConfig = {}, + ): Promise { const apiKeyId = config.apiKeyId || process.env.CDP_API_KEY_ID; const apiKeySecret = config.apiKeySecret || process.env.CDP_API_KEY_SECRET; const walletSecret = config.walletSecret || process.env.CDP_WALLET_SECRET; @@ -127,7 +124,7 @@ export class CdpV2SolanaWalletProvider ? cdpClient.solana.getAccount({ address: config.address }) : cdpClient.solana.createAccount({ idempotencyKey })); - return new CdpV2SolanaWalletProvider({ + return new CdpSolanaWalletProvider({ connection, cdp: cdpClient, serverAccount, @@ -135,6 +132,18 @@ export class CdpV2SolanaWalletProvider }); } + /** + * Exports the wallet. + * + * @returns The wallet's data. + */ + async exportWallet(): Promise<{ name: string | undefined; address: `0x${string}` }> { + return { + name: this.#serverAccount.name, + address: this.#serverAccount.address as `0x${string}`, + }; + } + /** * Get the connection instance * @@ -177,7 +186,7 @@ export class CdpV2SolanaWalletProvider * @returns The name of the wallet provider. */ getName(): string { - return "cdp_v2_solana_wallet_provider"; + return "cdp_solana_wallet_provider"; } /** diff --git a/typescript/agentkit/src/wallet-providers/cdpV2WalletProvider.ts b/typescript/agentkit/src/wallet-providers/cdpV2WalletProvider.ts deleted file mode 100644 index 760d0d1fa..000000000 --- a/typescript/agentkit/src/wallet-providers/cdpV2WalletProvider.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { CdpV2EvmWalletProvider } from "./cdpV2EvmWalletProvider"; -import { CdpV2SolanaWalletProvider } from "./cdpV2SolanaWalletProvider"; -import { CdpV2WalletProviderConfig } from "./cdpV2Shared"; -import { SOLANA_NETWORK_ID, SOLANA_NETWORK_IDS } from "../network"; - -export type CdpV2WalletProviderVariant = CdpV2SolanaWalletProvider | CdpV2EvmWalletProvider; - -/** - * Factory class for creating chain-specific CDP V2 wallet providers - */ -export class CdpV2WalletProvider { - /** - * Creates and configures a new wallet provider instance based on the chain type. - * - * @param config - The configuration options for the CdpV2 wallet - * @returns A configured WalletProvider instance for the specified chain - * - * @example - * ```typescript - * // For EVM server wallets (default) - * const evmWallet = await CdpV2WalletProvider.configureWithWallet({ - * apiKeyId: "your-api-key-id", - * apiKeySecret: "your-api-key-secret", - * walletSecret: "your-wallet-secret", - * networkId: "base-sepolia" // or any EVM network. Defaults to "base-sepolia" - * }); - * - * // For Solana server wallets - * const solanaWallet = await CdpV2WalletProvider.configureWithWallet({ - * apiKeyId: "your-api-key-id", - * apiKeySecret: "your-api-key-secret", - * walletSecret: "your-wallet-secret", - * networkId: "solana-devnet" // or "solana-mainnet" - * }); - * ``` - */ - static async configureWithWallet( - config: T, - ): Promise { - const useSolana = - config.networkId && SOLANA_NETWORK_IDS.includes(config.networkId as SOLANA_NETWORK_ID); - - const walletProviderClass = useSolana ? CdpV2SolanaWalletProvider : CdpV2EvmWalletProvider; - - return await walletProviderClass.configureWithWallet(config); - } -} diff --git a/typescript/agentkit/src/wallet-providers/index.ts b/typescript/agentkit/src/wallet-providers/index.ts index 49fd82735..0c727b42b 100644 --- a/typescript/agentkit/src/wallet-providers/index.ts +++ b/typescript/agentkit/src/wallet-providers/index.ts @@ -1,13 +1,13 @@ export * from "./walletProvider"; export * from "./evmWalletProvider"; export * from "./viemWalletProvider"; -export * from "./cdpV2EvmWalletProvider"; -export * from "./cdpV2SolanaWalletProvider"; -export * from "./cdpV2WalletProvider"; -export * from "./cdpV2Shared"; -export * from "./cdpWalletProvider"; +export * from "./cdpShared"; +export * from "./cdpEvmWalletProvider"; +export * from "./cdpSmartWalletProvider"; +export * from "./cdpSolanaWalletProvider"; +export * from "./legacyCdpWalletProvider"; export * from "./svmWalletProvider"; -export * from "./smartWalletProvider"; +export * from "./legacyCdpSmartWalletProvider"; export * from "./solanaKeypairWalletProvider"; export * from "./privyWalletProvider"; export * from "./privyEvmWalletProvider"; diff --git a/typescript/agentkit/src/wallet-providers/smartWalletProvider.test.ts b/typescript/agentkit/src/wallet-providers/legacyCdpSmartWalletProvider.test.ts similarity index 98% rename from typescript/agentkit/src/wallet-providers/smartWalletProvider.test.ts rename to typescript/agentkit/src/wallet-providers/legacyCdpSmartWalletProvider.test.ts index 4be84ba30..769fe91e0 100644 --- a/typescript/agentkit/src/wallet-providers/smartWalletProvider.test.ts +++ b/typescript/agentkit/src/wallet-providers/legacyCdpSmartWalletProvider.test.ts @@ -1,4 +1,4 @@ -import { SmartWalletProvider } from "./smartWalletProvider"; +import { LegacyCdpSmartWalletProvider } from "./legacyCdpSmartWalletProvider"; import { TransactionRequest, Hex, @@ -118,8 +118,8 @@ jest.mock("@coinbase/coinbase-sdk", () => { // tests // ========================================================= -describe("SmartWalletProvider", () => { - let provider: jest.Mocked; +describe("LegacyCdpSmartWalletProvider", () => { + let provider: jest.Mocked; let mockNetworkScopedWallet: MockNetworkScopedSmartWallet; let mockWaitForUserOperation: jest.MockedFunction< (op: MockUserOperation) => Promise @@ -180,7 +180,7 @@ describe("SmartWalletProvider", () => { readContract: jest.fn(), nativeTransfer: jest.fn(), _smartWallet: mockNetworkScopedWallet as unknown as NetworkScopedSmartWallet, - } as unknown as jest.Mocked; + } as unknown as jest.Mocked; provider.getAddress.mockReturnValue(MOCK_ADDRESS); provider.getNetwork.mockReturnValue({ @@ -188,7 +188,7 @@ describe("SmartWalletProvider", () => { networkId: MOCK_NETWORK_ID, chainId: MOCK_CHAIN_ID, }); - provider.getName.mockReturnValue("smart_wallet_provider"); + provider.getName.mockReturnValue("legacy_cdp_smart_wallet_provider"); provider.getBalance.mockResolvedValue(MOCK_BALANCE); provider.sendTransaction.mockImplementation(async (tx: TransactionRequest): Promise => { diff --git a/typescript/agentkit/src/wallet-providers/smartWalletProvider.ts b/typescript/agentkit/src/wallet-providers/legacyCdpSmartWalletProvider.ts similarity index 95% rename from typescript/agentkit/src/wallet-providers/smartWalletProvider.ts rename to typescript/agentkit/src/wallet-providers/legacyCdpSmartWalletProvider.ts index 3ebf77b9d..b58f60c0a 100644 --- a/typescript/agentkit/src/wallet-providers/smartWalletProvider.ts +++ b/typescript/agentkit/src/wallet-providers/legacyCdpSmartWalletProvider.ts @@ -26,7 +26,7 @@ import { Network, NETWORK_ID_TO_CHAIN_ID, NETWORK_ID_TO_VIEM_CHAIN } from "../ne import { EvmWalletProvider } from "./evmWalletProvider"; import { version } from "../../package.json"; -export interface ConfigureSmartWalletOptions { +export interface ConfigureLegacyCdpSmartWalletOptions { cdpApiKeyId?: string; cdpApiKeySecret?: string; networkId?: string; @@ -35,7 +35,7 @@ export interface ConfigureSmartWalletOptions { signer: Signer; } -interface SmartWalletProviderConfig { +interface LegacyCdpSmartWalletProviderConfig { smartWallet: NetworkScopedSmartWallet; network: Required; chainId: string; @@ -44,7 +44,7 @@ interface SmartWalletProviderConfig { /** * A wallet provider that uses Smart Wallets from the Coinbase SDK. */ -export class SmartWalletProvider extends EvmWalletProvider { +export class LegacyCdpSmartWalletProvider extends EvmWalletProvider { #smartWallet: NetworkScopedSmartWallet; #network: Required; #publicClient: ViemPublicClient; @@ -54,7 +54,7 @@ export class SmartWalletProvider extends EvmWalletProvider { * * @param config - The configuration options for the CdpWalletProvider. */ - private constructor(config: SmartWalletProviderConfig) { + private constructor(config: LegacyCdpSmartWalletProviderConfig) { super(); this.#network = config.network; @@ -90,8 +90,8 @@ export class SmartWalletProvider extends EvmWalletProvider { * ``` */ public static async configureWithWallet( - config: ConfigureSmartWalletOptions, - ): Promise { + config: ConfigureLegacyCdpSmartWalletOptions, + ): Promise { const networkId = config.networkId || process.env.NETWORK_ID || Coinbase.networks.BaseSepolia; const network = { protocolFamily: "evm" as const, @@ -138,13 +138,13 @@ export class SmartWalletProvider extends EvmWalletProvider { paymasterUrl: config.paymasterUrl, }); - const smartWalletProvider = new SmartWalletProvider({ + const legacyCdpSmartWalletProvider = new LegacyCdpSmartWalletProvider({ smartWallet: networkScopedSmartWallet, network, chainId: network.chainId, }); - return smartWalletProvider; + return legacyCdpSmartWalletProvider; } /** @@ -289,7 +289,7 @@ export class SmartWalletProvider extends EvmWalletProvider { * @returns The name of the wallet provider. */ getName(): string { - return "cdp_smart_wallet_provider"; + return "legacy_cdp_smart_wallet_provider"; } /** diff --git a/typescript/agentkit/src/wallet-providers/cdpWalletProvider.test.ts b/typescript/agentkit/src/wallet-providers/legacyCdpWalletProvider.test.ts similarity index 97% rename from typescript/agentkit/src/wallet-providers/cdpWalletProvider.test.ts rename to typescript/agentkit/src/wallet-providers/legacyCdpWalletProvider.test.ts index 838e87b85..72fdd6c06 100644 --- a/typescript/agentkit/src/wallet-providers/cdpWalletProvider.test.ts +++ b/typescript/agentkit/src/wallet-providers/legacyCdpWalletProvider.test.ts @@ -1,4 +1,4 @@ -import { CdpWalletProvider } from "./cdpWalletProvider"; +import { LegacyCdpWalletProvider } from "./legacyCdpWalletProvider"; import { Network } from "../network"; import { Abi, @@ -170,8 +170,8 @@ const MOCK_TRANSACTION_RECEIPT = { transactionHash: MOCK_TRANSACTION_HASH, } as unknown as TransactionReceipt; -describe("CdpWalletProvider", () => { - let provider: CdpWalletProvider; +describe("LegacyCdpWalletProvider", () => { + let provider: LegacyCdpWalletProvider; beforeEach(async () => { jest.clearAllMocks(); @@ -220,7 +220,7 @@ describe("CdpWalletProvider", () => { } as unknown as jest.Mocked); mockPublicClient.estimateGas.mockResolvedValue(BigInt(21000)); - provider = await CdpWalletProvider.configureWithWallet({ + provider = await LegacyCdpWalletProvider.configureWithWallet({ wallet: mockWalletObj, networkId: MOCK_NETWORK_ID, }); @@ -237,7 +237,7 @@ describe("CdpWalletProvider", () => { networkId: MOCK_NETWORK_ID, }); - const provider = await CdpWalletProvider.configureWithWallet({ + const provider = await LegacyCdpWalletProvider.configureWithWallet({ cdpWalletData: walletData, networkId: MOCK_NETWORK_ID, }); @@ -250,7 +250,7 @@ describe("CdpWalletProvider", () => { it("should initialize with mnemonic phrase", async () => { const mnemonicPhrase = "test test test test test test test test test test test junk"; - const provider = await CdpWalletProvider.configureWithWallet({ + const provider = await LegacyCdpWalletProvider.configureWithWallet({ mnemonicPhrase, networkId: MOCK_NETWORK_ID, }); @@ -264,7 +264,7 @@ describe("CdpWalletProvider", () => { const apiKeyId = "test-key"; const apiKeySecret = "private-key"; - const provider = await CdpWalletProvider.configureWithWallet({ + const provider = await LegacyCdpWalletProvider.configureWithWallet({ apiKeyId, apiKeySecret, networkId: MOCK_NETWORK_ID, @@ -282,7 +282,7 @@ describe("CdpWalletProvider", () => { }); it("should initialize with an existing wallet", async () => { - const provider = await CdpWalletProvider.configureWithWallet({ + const provider = await LegacyCdpWalletProvider.configureWithWallet({ wallet: mockWalletObj as unknown as Wallet, networkId: MOCK_NETWORK_ID, }); @@ -294,7 +294,7 @@ describe("CdpWalletProvider", () => { it("should default to base-sepolia if network not provided", async () => { mockWalletObj.getNetworkId.mockReturnValueOnce("base-sepolia"); - const provider = await CdpWalletProvider.configureWithWallet({ + const provider = await LegacyCdpWalletProvider.configureWithWallet({ wallet: mockWalletObj as unknown as Wallet, }); @@ -305,7 +305,7 @@ describe("CdpWalletProvider", () => { jest.spyOn(Wallet, "create").mockRejectedValueOnce(new Error("Failed to create wallet")); await expect( - CdpWalletProvider.configureWithWallet({ + LegacyCdpWalletProvider.configureWithWallet({ networkId: MOCK_NETWORK_ID, }), ).rejects.toThrow("Failed to create wallet"); @@ -326,7 +326,7 @@ describe("CdpWalletProvider", () => { }); it("should get the name", () => { - expect(provider.getName()).toBe("cdp_wallet_provider"); + expect(provider.getName()).toBe("legacy_cdp_wallet_provider"); }); it("should get the balance", async () => { diff --git a/typescript/agentkit/src/wallet-providers/cdpWalletProvider.ts b/typescript/agentkit/src/wallet-providers/legacyCdpWalletProvider.ts similarity index 93% rename from typescript/agentkit/src/wallet-providers/cdpWalletProvider.ts rename to typescript/agentkit/src/wallet-providers/legacyCdpWalletProvider.ts index 6488754d2..157331588 100644 --- a/typescript/agentkit/src/wallet-providers/cdpWalletProvider.ts +++ b/typescript/agentkit/src/wallet-providers/legacyCdpWalletProvider.ts @@ -37,7 +37,7 @@ import { applyGasMultiplier } from "../utils"; /** * Configuration options for the CDP Providers. */ -export interface CdpProviderConfig { +export interface LegacyCdpProviderConfig { /** * The CDP API Key Name. */ @@ -50,9 +50,11 @@ export interface CdpProviderConfig { } /** - * Configuration options for the CdpActionProvider. + * Configuration options for the LegacyCdpActionProvider. + * + * @deprecated Use CdpV2WalletProviderConfig instead */ -export interface CdpWalletProviderConfig extends CdpProviderConfig { +export interface LegacyCdpWalletProviderConfig extends LegacyCdpProviderConfig { /** * The CDP Wallet. */ @@ -92,7 +94,7 @@ export interface CdpWalletProviderConfig extends CdpProviderConfig { /** * Configuration options for the CDP Agentkit with a Wallet. */ -interface ConfigureCdpAgentkitWithWalletOptions extends CdpWalletProviderConfig { +interface ConfigureLegacyCdpAgentkitWithWalletOptions extends LegacyCdpWalletProviderConfig { /** * The data of the CDP Wallet as a JSON string. */ @@ -105,9 +107,11 @@ interface ConfigureCdpAgentkitWithWalletOptions extends CdpWalletProviderConfig } /** - * A wallet provider that uses the Coinbase SDK. + * A legacy wallet provider that uses the old Coinbase SDK. + * + * @deprecated Use CdpServerWalletProvider or CdpSmartWalletProvider instead */ -export class CdpWalletProvider extends EvmWalletProvider { +export class LegacyCdpWalletProvider extends EvmWalletProvider { #cdpWallet?: Wallet; #address?: string; #network?: Network; @@ -117,11 +121,11 @@ export class CdpWalletProvider extends EvmWalletProvider { #transactionQueue: Promise | undefined; /** - * Constructs a new CdpWalletProvider. + * Constructs a new LegacyCdpWalletProvider. * - * @param config - The configuration options for the CdpWalletProvider. + * @param config - The configuration options for the LegacyCdpWalletProvider. */ - private constructor(config: CdpWalletProviderConfig) { + private constructor(config: LegacyCdpWalletProviderConfig) { super(); this.#cdpWallet = config.wallet; @@ -136,15 +140,15 @@ export class CdpWalletProvider extends EvmWalletProvider { } /** - * Configures a new CdpWalletProvider with a wallet. + * Configures a new LegacyCdpWalletProvider with a wallet. * * @param config - Optional configuration parameters - * @returns A Promise that resolves to a new CdpWalletProvider instance + * @returns A Promise that resolves to a new LegacyCdpWalletProvider instance * @throws Error if required environment variables are missing or wallet initialization fails */ public static async configureWithWallet( - config: ConfigureCdpAgentkitWithWalletOptions = {}, - ): Promise { + config: ConfigureLegacyCdpAgentkitWithWalletOptions = {}, + ): Promise { if (config.apiKeyId && config.apiKeySecret) { Coinbase.configure({ apiKeyName: config.apiKeyId, @@ -185,7 +189,7 @@ export class CdpWalletProvider extends EvmWalletProvider { networkId: networkId, }; - const cdpWalletProvider = new CdpWalletProvider({ + const cdpWalletProvider = new LegacyCdpWalletProvider({ wallet, address, network, @@ -407,7 +411,7 @@ export class CdpWalletProvider extends EvmWalletProvider { * @returns The name of the wallet provider. */ getName(): string { - return "cdp_wallet_provider"; + return "legacy_cdp_wallet_provider"; } /** diff --git a/typescript/create-onchain-agent/src/actions/initProject.ts b/typescript/create-onchain-agent/src/actions/initProject.ts index 9fd19b36e..e6e07898e 100644 --- a/typescript/create-onchain-agent/src/actions/initProject.ts +++ b/typescript/create-onchain-agent/src/actions/initProject.ts @@ -198,8 +198,8 @@ export async function initProject() { }, message: (prev, { network }) => { const walletDescriptions: Record = { - SmartWallet: "Uses Coinbase Developer Platform (CDP)'s Smart Wallet.", - CDP: "Uses Coinbase Developer Platform (CDP)'s managed wallet.", + CDPSmartWallet: "Uses Coinbase Developer Platform (CDP)'s Smart Wallet.", + CDPWallet: "Uses Coinbase Developer Platform (CDP)'s managed wallet.", Viem: "Client-side Ethereum wallet.", Privy: "Authentication and wallet infrastructure.", SolanaKeypair: "Client-side Solana wallet.", diff --git a/typescript/create-onchain-agent/src/actions/prepareAgentkit.ts b/typescript/create-onchain-agent/src/actions/prepareAgentkit.ts index a17f94c7b..0c2382c8a 100644 --- a/typescript/create-onchain-agent/src/actions/prepareAgentkit.ts +++ b/typescript/create-onchain-agent/src/actions/prepareAgentkit.ts @@ -103,8 +103,8 @@ export async function prepareAgentKit() { name: "walletProvider", message: (prev, { network }) => { const walletDescriptions: Record = { - SmartWallet: "Uses Coinbase Developer Platform (CDP)'s Smart Wallet.", - CDP: "Uses Coinbase Developer Platform (CDP)'s managed wallet.", + CDPSmartWallet: "Uses Coinbase Developer Platform (CDP)'s Smart Wallet.", + CDPWallet: "Uses Coinbase Developer Platform (CDP)'s managed wallet.", Viem: "Client-side Ethereum wallet.", Privy: "Authentication and wallet infrastructure.", SolanaKeypair: "Client-side Solana wallet.", diff --git a/typescript/create-onchain-agent/src/common/constants.ts b/typescript/create-onchain-agent/src/common/constants.ts index 9ab65aee9..8cd3750ab 100644 --- a/typescript/create-onchain-agent/src/common/constants.ts +++ b/typescript/create-onchain-agent/src/common/constants.ts @@ -25,7 +25,12 @@ export type SVMNetwork = (typeof SVM_NETWORKS)[number]; export type Network = EVMNetwork | SVMNetwork; -const CDP_SUPPORTED_EVM_WALLET_PROVIDERS = ["SmartWallet", "CDP", "Viem", "Privy"] as const; +const CDP_SUPPORTED_EVM_WALLET_PROVIDERS = [ + "CDPSmartWallet", + "CDPWallet", + "Viem", + "Privy", +] as const; const SVM_WALLET_PROVIDERS = ["SolanaKeypair", "Privy"] as const; export const NON_CDP_SUPPORTED_EVM_WALLET_PROVIDERS = ["Viem", "Privy"] as const; @@ -65,10 +70,10 @@ export const AgentkitRouteConfigurations: Record< Partial> > = { EVM: { - CDP: { + CDPWallet: { env: { topComments: ["Get keys from CDP Portal: https://portal.cdp.coinbase.com/"], - required: ["CDP_API_KEY_ID", "CDP_API_KEY_SECRET"], + required: ["CDP_API_KEY_ID", "CDP_API_KEY_SECRET", "CDP_WALLET_SECRET"], optional: [], }, prepareAgentkitRoute: "evm/cdp/prepare-agentkit.ts", @@ -102,13 +107,13 @@ export const AgentkitRouteConfigurations: Record< }, prepareAgentkitRoute: "evm/privy/prepare-agentkit.ts", }, - SmartWallet: { + CDPSmartWallet: { env: { topComments: [ "Get keys from CDP Portal: https://portal.cdp.coinbase.com/", "Optionally provide a private key, otherwise one will be generated", ], - required: ["CDP_API_KEY_ID", "CDP_API_KEY_SECRET"], + required: ["CDP_API_KEY_ID", "CDP_API_KEY_SECRET", "CDP_WALLET_SECRET"], optional: ["PRIVATE_KEY"], }, prepareAgentkitRoute: "evm/smart/prepare-agentkit.ts", @@ -196,7 +201,7 @@ export const MCPRouteConfigurations: Record< Partial> > = { EVM: { - CDP: { + CDPWallet: { getAgentkitRoute: "evm/cdp/getAgentKit.ts", configRoute: "evm/cdp/claude_desktop_config.json", }, @@ -208,7 +213,7 @@ export const MCPRouteConfigurations: Record< getAgentkitRoute: "evm/privy/getAgentKit.ts", configRoute: "evm/privy/claude_desktop_config.json", }, - SmartWallet: { + CDPSmartWallet: { getAgentkitRoute: "evm/smart/getAgentKit.ts", configRoute: "evm/smart/claude_desktop_config.json", }, @@ -236,7 +241,7 @@ export const PrepareAgentkitRouteConfigurations: Record< Partial> > = { EVM: { - CDP: { + CDPWallet: { route: "evm/cdp/prepareAgentkit.ts", }, Viem: { @@ -245,7 +250,7 @@ export const PrepareAgentkitRouteConfigurations: Record< Privy: { route: "evm/privy/prepareAgentkit.ts", }, - SmartWallet: { + CDPSmartWallet: { route: "evm/smart/prepareAgentkit.ts", }, }, diff --git a/typescript/create-onchain-agent/templates/mcp/src/agentkit/evm/cdp/getAgentKit.ts b/typescript/create-onchain-agent/templates/mcp/src/agentkit/evm/cdp/getAgentKit.ts index 8069853fa..740c16eae 100644 --- a/typescript/create-onchain-agent/templates/mcp/src/agentkit/evm/cdp/getAgentKit.ts +++ b/typescript/create-onchain-agent/templates/mcp/src/agentkit/evm/cdp/getAgentKit.ts @@ -1,8 +1,8 @@ import { AgentKit, cdpApiActionProvider, - cdpWalletActionProvider, - CdpWalletProvider, + cdpEvmWalletActionProvider, + CdpEvmWalletProvider, erc20ActionProvider, pythActionProvider, walletActionProvider, @@ -17,7 +17,7 @@ import { export async function getAgentKit(): Promise { try { // Initialize WalletProvider: https://docs.cdp.coinbase.com/agentkit/docs/wallet-management - const walletProvider = await CdpWalletProvider.configureWithWallet({ + const walletProvider = await CdpEvmWalletProvider.configureWithWallet({ apiKeyId: process.env.CDP_API_KEY_ID, apiKeySecret: process.env.CDP_API_KEY_SECRET, networkId: process.env.NETWORK_ID || "base-sepolia", @@ -31,14 +31,8 @@ export async function getAgentKit(): Promise { pythActionProvider(), walletActionProvider(), erc20ActionProvider(), - cdpApiActionProvider({ - apiKeyId: process.env.CDP_API_KEY_ID, - apiKeySecret: process.env.CDP_API_KEY_SECRET, - }), - cdpWalletActionProvider({ - apiKeyId: process.env.CDP_API_KEY_ID, - apiKeySecret: process.env.CDP_API_KEY_SECRET, - }), + cdpApiActionProvider(), + cdpEvmWalletActionProvider(), ], }); diff --git a/typescript/create-onchain-agent/templates/mcp/src/agentkit/evm/smart/getAgentKit.ts b/typescript/create-onchain-agent/templates/mcp/src/agentkit/evm/smart/getAgentKit.ts index 003c5f9ea..9ab65d938 100644 --- a/typescript/create-onchain-agent/templates/mcp/src/agentkit/evm/smart/getAgentKit.ts +++ b/typescript/create-onchain-agent/templates/mcp/src/agentkit/evm/smart/getAgentKit.ts @@ -1,9 +1,10 @@ import { AgentKit, cdpApiActionProvider, + cdpSmartWalletActionProvider, erc20ActionProvider, pythActionProvider, - SmartWalletProvider, + CdpSmartWalletProvider, walletActionProvider, wethActionProvider, } from "@coinbase/agentkit"; @@ -23,12 +24,13 @@ export async function getAgentKit(): Promise { privateKey = (process.env.PRIVATE_KEY || generatePrivateKey()) as Hex; } - const signer = privateKeyToAccount(privateKey); + const owner = privateKeyToAccount(privateKey); // Initialize WalletProvider: https://docs.cdp.coinbase.com/agentkit/docs/wallet-management - const walletProvider = await SmartWalletProvider.configureWithWallet({ + const walletProvider = await CdpSmartWalletProvider.configureWithWallet({ networkId: process.env.NETWORK_ID || "base-sepolia", - signer, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + owner: owner as any, paymasterUrl: undefined, // Sponsor transactions: https://docs.cdp.coinbase.com/paymaster/docs/welcome }); @@ -40,10 +42,8 @@ export async function getAgentKit(): Promise { pythActionProvider(), walletActionProvider(), erc20ActionProvider(), - cdpApiActionProvider({ - apiKeyId: process.env.CDP_API_KEY_ID, - apiKeySecret: process.env.CDP_API_KEY_SECRET, - }), + cdpApiActionProvider(), + cdpSmartWalletActionProvider(), ], }); diff --git a/typescript/create-onchain-agent/templates/next/app/api/agent/agentkit/evm/cdp/prepare-agentkit.ts b/typescript/create-onchain-agent/templates/next/app/api/agent/agentkit/evm/cdp/prepare-agentkit.ts index 27e6ec5d1..3b31b5289 100644 --- a/typescript/create-onchain-agent/templates/next/app/api/agent/agentkit/evm/cdp/prepare-agentkit.ts +++ b/typescript/create-onchain-agent/templates/next/app/api/agent/agentkit/evm/cdp/prepare-agentkit.ts @@ -1,8 +1,8 @@ import { AgentKit, cdpApiActionProvider, - cdpWalletActionProvider, - CdpWalletProvider, + cdpEvmWalletActionProvider, + CdpEvmWalletProvider, erc20ActionProvider, pythActionProvider, walletActionProvider, @@ -77,11 +77,11 @@ export async function prepareAgentkitAndWalletProvider(): Promise<{ } // Initialize WalletProvider: https://docs.cdp.coinbase.com/agentkit/docs/wallet-management - const walletProvider = await CdpWalletProvider.configureWithWallet({ + const walletProvider = await CdpEvmWalletProvider.configureWithWallet({ apiKeyId: process.env.CDP_API_KEY_ID, apiKeySecret: process.env.CDP_API_KEY_SECRET, networkId: process.env.NETWORK_ID || "base-sepolia", - cdpWalletData: walletDataStr || undefined, + address: walletDataStr ? JSON.parse(walletDataStr).address : undefined, }); // Initialize AgentKit: https://docs.cdp.coinbase.com/agentkit/docs/agent-actions @@ -92,14 +92,8 @@ export async function prepareAgentkitAndWalletProvider(): Promise<{ pythActionProvider(), walletActionProvider(), erc20ActionProvider(), - cdpApiActionProvider({ - apiKeyId: process.env.CDP_API_KEY_ID, - apiKeySecret: process.env.CDP_API_KEY_SECRET, - }), - cdpWalletActionProvider({ - apiKeyId: process.env.CDP_API_KEY_ID, - apiKeySecret: process.env.CDP_API_KEY_SECRET, - }), + cdpApiActionProvider(), + cdpEvmWalletActionProvider(), ], }); diff --git a/typescript/create-onchain-agent/templates/next/app/api/agent/agentkit/evm/smart/prepare-agentkit.ts b/typescript/create-onchain-agent/templates/next/app/api/agent/agentkit/evm/smart/prepare-agentkit.ts index affce09f8..848e73414 100644 --- a/typescript/create-onchain-agent/templates/next/app/api/agent/agentkit/evm/smart/prepare-agentkit.ts +++ b/typescript/create-onchain-agent/templates/next/app/api/agent/agentkit/evm/smart/prepare-agentkit.ts @@ -1,9 +1,10 @@ import { AgentKit, cdpApiActionProvider, + cdpSmartWalletActionProvider, erc20ActionProvider, pythActionProvider, - SmartWalletProvider, + CdpSmartWalletProvider, walletActionProvider, WalletProvider, wethActionProvider, @@ -92,12 +93,13 @@ export async function prepareAgentkitAndWalletProvider(): Promise<{ } try { - const signer = privateKeyToAccount(privateKey); + const owner = privateKeyToAccount(privateKey); // Initialize WalletProvider: https://docs.cdp.coinbase.com/agentkit/docs/wallet-management - const walletProvider = await SmartWalletProvider.configureWithWallet({ + const walletProvider = await CdpSmartWalletProvider.configureWithWallet({ networkId: process.env.NETWORK_ID || "base-sepolia", - signer, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + owner: owner as any, smartWalletAddress: walletData?.smartWalletAddress, paymasterUrl: undefined, // Sponsor transactions: https://docs.cdp.coinbase.com/paymaster/docs/welcome }); @@ -110,10 +112,8 @@ export async function prepareAgentkitAndWalletProvider(): Promise<{ pythActionProvider(), walletActionProvider(), erc20ActionProvider(), - cdpApiActionProvider({ - apiKeyId: process.env.CDP_API_KEY_ID, - apiKeySecret: process.env.CDP_API_KEY_SECRET, - }), + cdpApiActionProvider(), + cdpSmartWalletActionProvider(), ], }); diff --git a/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/cdp/prepareAgentkit.ts b/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/cdp/prepareAgentkit.ts index 8291a89a4..2d4d9c587 100644 --- a/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/cdp/prepareAgentkit.ts +++ b/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/cdp/prepareAgentkit.ts @@ -1,8 +1,8 @@ import { AgentKit, cdpApiActionProvider, - cdpWalletActionProvider, - CdpWalletProvider, + cdpEvmWalletActionProvider, + CdpEvmWalletProvider, erc20ActionProvider, pythActionProvider, walletActionProvider, @@ -71,11 +71,11 @@ export async function prepareAgentkitAndWalletProvider(): Promise<{ } // Initialize WalletProvider: https://docs.cdp.coinbase.com/agentkit/docs/wallet-management - const walletProvider = await CdpWalletProvider.configureWithWallet({ + const walletProvider = await CdpEvmWalletProvider.configureWithWallet({ apiKeyId: process.env.CDP_API_KEY_ID, apiKeySecret: process.env.CDP_API_KEY_SECRET, networkId: process.env.NETWORK_ID || "base-sepolia", - cdpWalletData: walletDataStr || undefined, + address: walletDataStr ? JSON.parse(walletDataStr).address : undefined, }); // Initialize AgentKit: https://docs.cdp.coinbase.com/agentkit/docs/agent-actions @@ -86,14 +86,8 @@ export async function prepareAgentkitAndWalletProvider(): Promise<{ pythActionProvider(), walletActionProvider(), erc20ActionProvider(), - cdpApiActionProvider({ - apiKeyId: process.env.CDP_API_KEY_ID, - apiKeySecret: process.env.CDP_API_KEY_SECRET, - }), - cdpWalletActionProvider({ - apiKeyId: process.env.CDP_API_KEY_ID, - apiKeySecret: process.env.CDP_API_KEY_SECRET, - }), + cdpApiActionProvider(), + cdpEvmWalletActionProvider(), ], }); diff --git a/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/smart/prepareAgentkit.ts b/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/smart/prepareAgentkit.ts index d12ee2d90..04374baef 100644 --- a/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/smart/prepareAgentkit.ts +++ b/typescript/create-onchain-agent/templates/prepareAgentkit/agentkit/evm/smart/prepareAgentkit.ts @@ -1,9 +1,10 @@ import { AgentKit, cdpApiActionProvider, + cdpSmartWalletActionProvider, erc20ActionProvider, pythActionProvider, - SmartWalletProvider, + CdpSmartWalletProvider, walletActionProvider, WalletProvider, wethActionProvider, @@ -87,12 +88,13 @@ export async function prepareAgentkitAndWalletProvider(): Promise<{ privateKey = (process.env.PRIVATE_KEY || generatePrivateKey()) as Hex; } - const signer = privateKeyToAccount(privateKey); + const owner = privateKeyToAccount(privateKey); // Initialize WalletProvider: https://docs.cdp.coinbase.com/agentkit/docs/wallet-management - const walletProvider = await SmartWalletProvider.configureWithWallet({ + const walletProvider = await CdpSmartWalletProvider.configureWithWallet({ networkId: process.env.NETWORK_ID || "base-sepolia", - signer, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + owner: owner as any, smartWalletAddress: walletData?.smartWalletAddress, paymasterUrl: undefined, // Sponsor transactions: https://docs.cdp.coinbase.com/paymaster/docs/welcome }); @@ -105,10 +107,8 @@ export async function prepareAgentkitAndWalletProvider(): Promise<{ pythActionProvider(), walletActionProvider(), erc20ActionProvider(), - cdpApiActionProvider({ - apiKeyId: process.env.CDP_API_KEY_ID, - apiKeySecret: process.env.CDP_API_KEY_SECRET, - }), + cdpApiActionProvider(), + cdpSmartWalletActionProvider(), ], }); diff --git a/typescript/examples/langchain-cdp-chatbot/.env-local b/typescript/examples/langchain-cdp-chatbot/.env-local index 2b247f0d2..704928450 100644 --- a/typescript/examples/langchain-cdp-chatbot/.env-local +++ b/typescript/examples/langchain-cdp-chatbot/.env-local @@ -1,3 +1,11 @@ +# Required OPENAI_API_KEY= CDP_API_KEY_ID= CDP_API_KEY_SECRET= +CDP_WALLET_SECRET= + +# Optional +## Used for account creation +IDEMPOTENCY_KEY= +## Used for already existing accounts +ADDRESS= \ No newline at end of file diff --git a/typescript/examples/langchain-cdp-chatbot/README.md b/typescript/examples/langchain-cdp-chatbot/README.md index e8bc9f2b0..e358306f1 100644 --- a/typescript/examples/langchain-cdp-chatbot/README.md +++ b/typescript/examples/langchain-cdp-chatbot/README.md @@ -13,7 +13,7 @@ This example demonstrates an agent setup as a terminal style chatbot with access ### Checking Node Version -Before using the example, ensure that you have the correct version of Node.js installed. The example requires Node.js 18 or higher. You can check your Node version by running: +Before using the example, ensure that you have the correct version of Node.js installed. The example requires Node.js 20 or higher. You can check your Node version by running: ```bash node --version @@ -32,11 +32,13 @@ This will automatically install and use the latest version of Node. You'll need the following API keys: - [CDP API Key](https://portal.cdp.coinbase.com/access/api) - [OpenAI API Key](https://platform.openai.com/docs/quickstart#create-and-export-an-api-key) +- [Generate Wallet Secret](https://portal.cdp.coinbase.com/products/wallet-api) Once you have them, rename the `.env-local` file to `.env` and make sure you set the API keys to their corresponding environment variables: - "CDP_API_KEY_ID" - "CDP_API_KEY_SECRET" +- "CDP_WALLET_SECRET" - "OPENAI_API_KEY" ## Running the example @@ -44,16 +46,16 @@ Once you have them, rename the `.env-local` file to `.env` and make sure you set From the root directory, run: ```bash -npm install -npm run build +pnpm install +pnpm build ``` -This will install the dependencies and build the packages locally. The chatbot example uses the local `@coinbase/agentkit-langchain` and `@coinbase/agentkit` packages. If you make changes to the packages, you can run `npm run build` from root again to rebuild the packages, and your changes will be reflected in the chatbot example. +This will install the dependencies and build the packages locally. The chatbot example uses the local `@coinbase/agentkit-langchain` and `@coinbase/agentkit` packages. If you make changes to the packages, you can run `pnpm build` from root again to rebuild the packages, and your changes will be reflected in the chatbot example. -Now from the `typescript/examples/langchain-cdp-chatbot` directory, run: +Now from the `typescript/examples/langchain-cdp-v2-chatbot` directory, run: ```bash -npm start +pnpm start ``` Select "1. chat mode" and start telling your Agent to do things onchain! diff --git a/typescript/examples/langchain-cdp-chatbot/chatbot.ts b/typescript/examples/langchain-cdp-chatbot/chatbot.ts index f6f79d2a7..168db82cc 100644 --- a/typescript/examples/langchain-cdp-chatbot/chatbot.ts +++ b/typescript/examples/langchain-cdp-chatbot/chatbot.ts @@ -1,15 +1,13 @@ import { AgentKit, - CdpWalletProvider, + CdpEvmWalletProvider, wethActionProvider, walletActionProvider, erc20ActionProvider, erc721ActionProvider, cdpApiActionProvider, - cdpWalletActionProvider, - pythActionProvider, - openseaActionProvider, - alloraActionProvider, + CdpSolanaWalletProvider, + splActionProvider, } from "@coinbase/agentkit"; import { getLangChainTools } from "@coinbase/agentkit-langchain"; import { HumanMessage } from "@langchain/core/messages"; @@ -17,7 +15,6 @@ import { MemorySaver } from "@langchain/langgraph"; import { createReactAgent } from "@langchain/langgraph/prebuilt"; import { ChatOpenAI } from "@langchain/openai"; import * as dotenv from "dotenv"; -import * as fs from "fs"; import * as readline from "readline"; dotenv.config(); @@ -32,7 +29,12 @@ function validateEnvironment(): void { const missingVars: string[] = []; // Check required variables - const requiredVars = ["OPENAI_API_KEY", "CDP_API_KEY_ID", "CDP_API_KEY_SECRET"]; + const requiredVars = [ + "OPENAI_API_KEY", + "CDP_API_KEY_ID", + "CDP_API_KEY_SECRET", + "CDP_WALLET_SECRET", + ]; requiredVars.forEach(varName => { if (!process.env[varName]) { missingVars.push(varName); @@ -57,8 +59,29 @@ function validateEnvironment(): void { // Add this right after imports and before any other code validateEnvironment(); -// Configure a file to persist the agent's CDP MPC Wallet Data -const WALLET_DATA_FILE = "wallet_data.txt"; +/** + * Type guard to check if the wallet provider is an EVM provider + * + * @param walletProvider - The wallet provider to check + * @returns True if the wallet provider is an EVM provider, false otherwise + */ +function isEvmWalletProvider( + walletProvider: CdpEvmWalletProvider, +): walletProvider is CdpEvmWalletProvider { + return walletProvider instanceof CdpEvmWalletProvider; +} + +/** + * Type guard to check if the wallet provider is a Solana provider + * + * @param walletProvider - The wallet provider to check + * @returns True if the wallet provider is a Solana provider, false otherwise + */ +function isSolanaWalletProvider( + walletProvider: CdpEvmWalletProvider | CdpSolanaWalletProvider, +): walletProvider is CdpSolanaWalletProvider { + return walletProvider instanceof CdpSolanaWalletProvider; +} /** * Initialize the agent with CDP Agentkit @@ -72,57 +95,31 @@ async function initializeAgent() { model: "gpt-4o-mini", }); - let walletDataStr: string | null = null; - - // Read existing wallet data if available - if (fs.existsSync(WALLET_DATA_FILE)) { - try { - walletDataStr = fs.readFileSync(WALLET_DATA_FILE, "utf8"); - } catch (error) { - console.error("Error reading wallet data:", error); - // Continue without wallet data - } - } - // Configure CDP Wallet Provider - const config = { + const cdpWalletConfig = { apiKeyId: process.env.CDP_API_KEY_ID, apiKeySecret: process.env.CDP_API_KEY_SECRET, - cdpWalletData: walletDataStr || undefined, - networkId: process.env.NETWORK_ID || "base-sepolia", + walletSecret: process.env.CDP_WALLET_SECRET, + idempotencyKey: process.env.IDEMPOTENCY_KEY, + address: process.env.ADDRESS as `0x${string}` | undefined, + networkId: process.env.NETWORK_ID, }; - const walletProvider = await CdpWalletProvider.configureWithWallet(config); + const walletProvider = await CdpEvmWalletProvider.configureWithWallet(cdpWalletConfig); + const actionProviders = [ + walletActionProvider(), + cdpApiActionProvider(), + ...(isEvmWalletProvider(walletProvider) + ? [wethActionProvider(), erc20ActionProvider(), erc721ActionProvider()] + : isSolanaWalletProvider(walletProvider) + ? [splActionProvider()] + : []), + ]; // Initialize AgentKit const agentkit = await AgentKit.from({ walletProvider, - actionProviders: [ - wethActionProvider(), - pythActionProvider(), - walletActionProvider(), - erc20ActionProvider(), - erc721ActionProvider(), - cdpApiActionProvider({ - apiKeyId: process.env.CDP_API_KEY_ID, - apiKeySecret: process.env.CDP_API_KEY_SECRET, - }), - cdpWalletActionProvider({ - apiKeyId: process.env.CDP_API_KEY_ID, - apiKeySecret: process.env.CDP_API_KEY_SECRET, - }), - // Only add OpenSea provider if API key is configured - ...(process.env.OPENSEA_API_KEY - ? [ - openseaActionProvider({ - apiKey: process.env.OPENSEA_API_KEY, - networkId: walletProvider.getNetwork().networkId, - privateKey: await (await walletProvider.getWallet().getDefaultAddress()).export(), - }), - ] - : []), - alloraActionProvider(), - ], + actionProviders, }); const tools = await getLangChainTools(agentkit); @@ -149,10 +146,6 @@ async function initializeAgent() { `, }); - // Save wallet data - const exportedWallet = await walletProvider.exportWallet(); - fs.writeFileSync(WALLET_DATA_FILE, JSON.stringify(exportedWallet)); - return { agent, config: agentConfig }; } catch (error) { console.error("Failed to initialize agent:", error); diff --git a/typescript/examples/langchain-cdp-chatbot/package.json b/typescript/examples/langchain-cdp-chatbot/package.json index f343a0843..3c4cb4e7b 100644 --- a/typescript/examples/langchain-cdp-chatbot/package.json +++ b/typescript/examples/langchain-cdp-chatbot/package.json @@ -1,5 +1,5 @@ { - "name": "@coinbase/cdp-langchain-chatbot-example", + "name": "@coinbase/cdp-v2-evm-langchain-chatbot-example", "description": "CDP Agentkit Node.js SDK Chatbot Example", "version": "1.0.0", "private": true, diff --git a/typescript/examples/langchain-smart-wallet-chatbot/.env-local b/typescript/examples/langchain-cdp-smart-wallet-chatbot/.env-local similarity index 100% rename from typescript/examples/langchain-smart-wallet-chatbot/.env-local rename to typescript/examples/langchain-cdp-smart-wallet-chatbot/.env-local diff --git a/typescript/examples/langchain-cdp-v2-chatbot/.eslintrc.json b/typescript/examples/langchain-cdp-smart-wallet-chatbot/.eslintrc.json similarity index 100% rename from typescript/examples/langchain-cdp-v2-chatbot/.eslintrc.json rename to typescript/examples/langchain-cdp-smart-wallet-chatbot/.eslintrc.json diff --git a/typescript/examples/langchain-cdp-v2-chatbot/.prettierignore b/typescript/examples/langchain-cdp-smart-wallet-chatbot/.prettierignore similarity index 100% rename from typescript/examples/langchain-cdp-v2-chatbot/.prettierignore rename to typescript/examples/langchain-cdp-smart-wallet-chatbot/.prettierignore diff --git a/typescript/examples/langchain-cdp-v2-chatbot/.prettierrc b/typescript/examples/langchain-cdp-smart-wallet-chatbot/.prettierrc similarity index 100% rename from typescript/examples/langchain-cdp-v2-chatbot/.prettierrc rename to typescript/examples/langchain-cdp-smart-wallet-chatbot/.prettierrc diff --git a/typescript/examples/langchain-smart-wallet-chatbot/README.md b/typescript/examples/langchain-cdp-smart-wallet-chatbot/README.md similarity index 100% rename from typescript/examples/langchain-smart-wallet-chatbot/README.md rename to typescript/examples/langchain-cdp-smart-wallet-chatbot/README.md diff --git a/typescript/examples/langchain-smart-wallet-chatbot/chatbot.ts b/typescript/examples/langchain-cdp-smart-wallet-chatbot/chatbot.ts similarity index 92% rename from typescript/examples/langchain-smart-wallet-chatbot/chatbot.ts rename to typescript/examples/langchain-cdp-smart-wallet-chatbot/chatbot.ts index c35a263be..45d737f82 100644 --- a/typescript/examples/langchain-smart-wallet-chatbot/chatbot.ts +++ b/typescript/examples/langchain-cdp-smart-wallet-chatbot/chatbot.ts @@ -3,7 +3,7 @@ import { cdpApiActionProvider, erc20ActionProvider, pythActionProvider, - SmartWalletProvider, + CdpSmartWalletProvider, walletActionProvider, wethActionProvider, } from "@coinbase/agentkit"; @@ -94,14 +94,18 @@ async function initializeAgent() { privateKey = (process.env.PRIVATE_KEY || generatePrivateKey()) as Hex; } - const signer = privateKeyToAccount(privateKey); + const owner = privateKeyToAccount(privateKey); // Configure Smart Wallet Provider - const walletProvider = await SmartWalletProvider.configureWithWallet({ + const walletProvider = await CdpSmartWalletProvider.configureWithWallet({ + apiKeyId: process.env.CDP_API_KEY_ID, + apiKeySecret: process.env.CDP_API_KEY_SECRET, + walletSecret: process.env.CDP_WALLET_SECRET, + address: walletData?.smartWalletAddress, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + owner: owner as any, networkId, - signer, - smartWalletAddress: walletData?.smartWalletAddress, - paymasterUrl: undefined, // Sponsor transactions: https://docs.cdp.coinbase.com/paymaster/docs/welcome + // paymasterUrl: undefined, // Sponsor transactions: https://docs.cdp.coinbase.com/paymaster/docs/welcome }); // Initialize AgentKit @@ -112,10 +116,7 @@ async function initializeAgent() { pythActionProvider(), walletActionProvider(), erc20ActionProvider(), - cdpApiActionProvider({ - apiKeyId: process.env.CDP_API_KEY_ID, - apiKeySecret: process.env.CDP_API_KEY_SECRET, - }), + cdpApiActionProvider(), ], }); @@ -144,12 +145,12 @@ async function initializeAgent() { }); // Save wallet data - const smartWalletAddress = await walletProvider.getAddress(); + const exportedWallet = await walletProvider.exportWallet(); fs.writeFileSync( walletDataFile, JSON.stringify({ privateKey, - smartWalletAddress, + smartWalletAddress: exportedWallet.address, } as WalletData), ); diff --git a/typescript/examples/langchain-smart-wallet-chatbot/package.json b/typescript/examples/langchain-cdp-smart-wallet-chatbot/package.json similarity index 100% rename from typescript/examples/langchain-smart-wallet-chatbot/package.json rename to typescript/examples/langchain-cdp-smart-wallet-chatbot/package.json diff --git a/typescript/examples/langchain-cdp-v2-chatbot/tsconfig.json b/typescript/examples/langchain-cdp-smart-wallet-chatbot/tsconfig.json similarity index 100% rename from typescript/examples/langchain-cdp-v2-chatbot/tsconfig.json rename to typescript/examples/langchain-cdp-smart-wallet-chatbot/tsconfig.json diff --git a/typescript/examples/langchain-cdp-v2-chatbot/.env-local b/typescript/examples/langchain-cdp-v2-chatbot/.env-local deleted file mode 100644 index 704928450..000000000 --- a/typescript/examples/langchain-cdp-v2-chatbot/.env-local +++ /dev/null @@ -1,11 +0,0 @@ -# Required -OPENAI_API_KEY= -CDP_API_KEY_ID= -CDP_API_KEY_SECRET= -CDP_WALLET_SECRET= - -# Optional -## Used for account creation -IDEMPOTENCY_KEY= -## Used for already existing accounts -ADDRESS= \ No newline at end of file diff --git a/typescript/examples/langchain-farcaster-chatbot/.env-local b/typescript/examples/langchain-farcaster-chatbot/.env-local index 2c9ab7740..c22665ce4 100644 --- a/typescript/examples/langchain-farcaster-chatbot/.env-local +++ b/typescript/examples/langchain-farcaster-chatbot/.env-local @@ -2,4 +2,5 @@ OPENAI_API_KEY= NEYNAR_API_KEY= NEYNAR_MANAGED_SIGNER= CDP_API_KEY_ID= -CDP_API_KEY_SECRET= \ No newline at end of file +CDP_API_KEY_SECRET= +CDP_WALLET_SECRET= \ No newline at end of file diff --git a/typescript/examples/langchain-farcaster-chatbot/chatbot.ts b/typescript/examples/langchain-farcaster-chatbot/chatbot.ts index fa254ecaf..26af8b652 100644 --- a/typescript/examples/langchain-farcaster-chatbot/chatbot.ts +++ b/typescript/examples/langchain-farcaster-chatbot/chatbot.ts @@ -32,6 +32,7 @@ async function initialize() { const agentkit = await AgentKit.from({ cdpApiKeyId: process.env.CDP_API_KEY_ID, cdpApiKeySecret: process.env.CDP_API_KEY_SECRET, + cdpWalletSecret: process.env.CDP_WALLET_SECRET, actionProviders: [farcasterActionProvider()], }); diff --git a/typescript/examples/langchain-legacy-cdp-chatbot/.env-local b/typescript/examples/langchain-legacy-cdp-chatbot/.env-local new file mode 100644 index 000000000..2b247f0d2 --- /dev/null +++ b/typescript/examples/langchain-legacy-cdp-chatbot/.env-local @@ -0,0 +1,3 @@ +OPENAI_API_KEY= +CDP_API_KEY_ID= +CDP_API_KEY_SECRET= diff --git a/typescript/examples/langchain-smart-wallet-chatbot/.eslintrc.json b/typescript/examples/langchain-legacy-cdp-chatbot/.eslintrc.json similarity index 100% rename from typescript/examples/langchain-smart-wallet-chatbot/.eslintrc.json rename to typescript/examples/langchain-legacy-cdp-chatbot/.eslintrc.json diff --git a/typescript/examples/langchain-smart-wallet-chatbot/.prettierignore b/typescript/examples/langchain-legacy-cdp-chatbot/.prettierignore similarity index 100% rename from typescript/examples/langchain-smart-wallet-chatbot/.prettierignore rename to typescript/examples/langchain-legacy-cdp-chatbot/.prettierignore diff --git a/typescript/examples/langchain-smart-wallet-chatbot/.prettierrc b/typescript/examples/langchain-legacy-cdp-chatbot/.prettierrc similarity index 100% rename from typescript/examples/langchain-smart-wallet-chatbot/.prettierrc rename to typescript/examples/langchain-legacy-cdp-chatbot/.prettierrc diff --git a/typescript/examples/langchain-cdp-v2-chatbot/README.md b/typescript/examples/langchain-legacy-cdp-chatbot/README.md similarity index 79% rename from typescript/examples/langchain-cdp-v2-chatbot/README.md rename to typescript/examples/langchain-legacy-cdp-chatbot/README.md index e358306f1..e8bc9f2b0 100644 --- a/typescript/examples/langchain-cdp-v2-chatbot/README.md +++ b/typescript/examples/langchain-legacy-cdp-chatbot/README.md @@ -13,7 +13,7 @@ This example demonstrates an agent setup as a terminal style chatbot with access ### Checking Node Version -Before using the example, ensure that you have the correct version of Node.js installed. The example requires Node.js 20 or higher. You can check your Node version by running: +Before using the example, ensure that you have the correct version of Node.js installed. The example requires Node.js 18 or higher. You can check your Node version by running: ```bash node --version @@ -32,13 +32,11 @@ This will automatically install and use the latest version of Node. You'll need the following API keys: - [CDP API Key](https://portal.cdp.coinbase.com/access/api) - [OpenAI API Key](https://platform.openai.com/docs/quickstart#create-and-export-an-api-key) -- [Generate Wallet Secret](https://portal.cdp.coinbase.com/products/wallet-api) Once you have them, rename the `.env-local` file to `.env` and make sure you set the API keys to their corresponding environment variables: - "CDP_API_KEY_ID" - "CDP_API_KEY_SECRET" -- "CDP_WALLET_SECRET" - "OPENAI_API_KEY" ## Running the example @@ -46,16 +44,16 @@ Once you have them, rename the `.env-local` file to `.env` and make sure you set From the root directory, run: ```bash -pnpm install -pnpm build +npm install +npm run build ``` -This will install the dependencies and build the packages locally. The chatbot example uses the local `@coinbase/agentkit-langchain` and `@coinbase/agentkit` packages. If you make changes to the packages, you can run `pnpm build` from root again to rebuild the packages, and your changes will be reflected in the chatbot example. +This will install the dependencies and build the packages locally. The chatbot example uses the local `@coinbase/agentkit-langchain` and `@coinbase/agentkit` packages. If you make changes to the packages, you can run `npm run build` from root again to rebuild the packages, and your changes will be reflected in the chatbot example. -Now from the `typescript/examples/langchain-cdp-v2-chatbot` directory, run: +Now from the `typescript/examples/langchain-cdp-chatbot` directory, run: ```bash -pnpm start +npm start ``` Select "1. chat mode" and start telling your Agent to do things onchain! diff --git a/typescript/examples/langchain-cdp-v2-chatbot/chatbot.ts b/typescript/examples/langchain-legacy-cdp-chatbot/chatbot.ts similarity index 78% rename from typescript/examples/langchain-cdp-v2-chatbot/chatbot.ts rename to typescript/examples/langchain-legacy-cdp-chatbot/chatbot.ts index 824544cc6..d84082537 100644 --- a/typescript/examples/langchain-cdp-v2-chatbot/chatbot.ts +++ b/typescript/examples/langchain-legacy-cdp-chatbot/chatbot.ts @@ -1,14 +1,15 @@ import { AgentKit, - CdpV2WalletProvider, + LegacyCdpWalletProvider, wethActionProvider, walletActionProvider, erc20ActionProvider, erc721ActionProvider, - cdpApiV2ActionProvider, - CdpV2EvmWalletProvider, - CdpV2SolanaWalletProvider, - splActionProvider, + legacyCdpApiActionProvider, + legacyCdpWalletActionProvider, + pythActionProvider, + openseaActionProvider, + alloraActionProvider, } from "@coinbase/agentkit"; import { getLangChainTools } from "@coinbase/agentkit-langchain"; import { HumanMessage } from "@langchain/core/messages"; @@ -16,6 +17,7 @@ import { MemorySaver } from "@langchain/langgraph"; import { createReactAgent } from "@langchain/langgraph/prebuilt"; import { ChatOpenAI } from "@langchain/openai"; import * as dotenv from "dotenv"; +import * as fs from "fs"; import * as readline from "readline"; dotenv.config(); @@ -30,12 +32,7 @@ function validateEnvironment(): void { const missingVars: string[] = []; // Check required variables - const requiredVars = [ - "OPENAI_API_KEY", - "CDP_API_KEY_ID", - "CDP_API_KEY_SECRET", - "CDP_WALLET_SECRET", - ]; + const requiredVars = ["OPENAI_API_KEY", "CDP_API_KEY_ID", "CDP_API_KEY_SECRET"]; requiredVars.forEach(varName => { if (!process.env[varName]) { missingVars.push(varName); @@ -60,29 +57,8 @@ function validateEnvironment(): void { // Add this right after imports and before any other code validateEnvironment(); -/** - * Type guard to check if the wallet provider is an EVM provider - * - * @param walletProvider - The wallet provider to check - * @returns True if the wallet provider is an EVM provider, false otherwise - */ -function isEvmWalletProvider( - walletProvider: CdpV2WalletProvider, -): walletProvider is CdpV2EvmWalletProvider { - return walletProvider instanceof CdpV2EvmWalletProvider; -} - -/** - * Type guard to check if the wallet provider is a Solana provider - * - * @param walletProvider - The wallet provider to check - * @returns True if the wallet provider is a Solana provider, false otherwise - */ -function isSolanaWalletProvider( - walletProvider: CdpV2WalletProvider, -): walletProvider is CdpV2SolanaWalletProvider { - return walletProvider instanceof CdpV2SolanaWalletProvider; -} +// Configure a file to persist the agent's CDP MPC Wallet Data +const WALLET_DATA_FILE = "wallet_data.txt"; /** * Initialize the agent with CDP Agentkit @@ -96,31 +72,57 @@ async function initializeAgent() { model: "gpt-4o-mini", }); + let walletDataStr: string | null = null; + + // Read existing wallet data if available + if (fs.existsSync(WALLET_DATA_FILE)) { + try { + walletDataStr = fs.readFileSync(WALLET_DATA_FILE, "utf8"); + } catch (error) { + console.error("Error reading wallet data:", error); + // Continue without wallet data + } + } + // Configure CDP Wallet Provider - const cdpWalletConfig = { + const config = { apiKeyId: process.env.CDP_API_KEY_ID, apiKeySecret: process.env.CDP_API_KEY_SECRET, - walletSecret: process.env.CDP_WALLET_SECRET, - idempotencyKey: process.env.IDEMPOTENCY_KEY, - address: process.env.ADDRESS as `0x${string}` | undefined, - networkId: process.env.NETWORK_ID, + cdpWalletData: walletDataStr || undefined, + networkId: process.env.NETWORK_ID || "base-sepolia", }; - const walletProvider = await CdpV2WalletProvider.configureWithWallet(cdpWalletConfig); - const actionProviders = [ - walletActionProvider(), - cdpApiV2ActionProvider(), - ...(isEvmWalletProvider(walletProvider) - ? [wethActionProvider(), erc20ActionProvider(), erc721ActionProvider()] - : isSolanaWalletProvider(walletProvider) - ? [splActionProvider()] - : []), - ]; + const walletProvider = await LegacyCdpWalletProvider.configureWithWallet(config); // Initialize AgentKit const agentkit = await AgentKit.from({ walletProvider, - actionProviders, + actionProviders: [ + wethActionProvider(), + pythActionProvider(), + walletActionProvider(), + erc20ActionProvider(), + erc721ActionProvider(), + legacyCdpApiActionProvider({ + apiKeyId: process.env.CDP_API_KEY_ID, + apiKeySecret: process.env.CDP_API_KEY_SECRET, + }), + legacyCdpWalletActionProvider({ + apiKeyId: process.env.CDP_API_KEY_ID, + apiKeySecret: process.env.CDP_API_KEY_SECRET, + }), + // Only add OpenSea provider if API key is configured + ...(process.env.OPENSEA_API_KEY + ? [ + openseaActionProvider({ + apiKey: process.env.OPENSEA_API_KEY, + networkId: walletProvider.getNetwork().networkId, + privateKey: await (await walletProvider.getWallet().getDefaultAddress()).export(), + }), + ] + : []), + alloraActionProvider(), + ], }); const tools = await getLangChainTools(agentkit); @@ -147,6 +149,10 @@ async function initializeAgent() { `, }); + // Save wallet data + const exportedWallet = await walletProvider.exportWallet(); + fs.writeFileSync(WALLET_DATA_FILE, JSON.stringify(exportedWallet)); + return { agent, config: agentConfig }; } catch (error) { console.error("Failed to initialize agent:", error); diff --git a/typescript/examples/langchain-cdp-v2-chatbot/package.json b/typescript/examples/langchain-legacy-cdp-chatbot/package.json similarity index 93% rename from typescript/examples/langchain-cdp-v2-chatbot/package.json rename to typescript/examples/langchain-legacy-cdp-chatbot/package.json index 3c4cb4e7b..f343a0843 100644 --- a/typescript/examples/langchain-cdp-v2-chatbot/package.json +++ b/typescript/examples/langchain-legacy-cdp-chatbot/package.json @@ -1,5 +1,5 @@ { - "name": "@coinbase/cdp-v2-evm-langchain-chatbot-example", + "name": "@coinbase/cdp-langchain-chatbot-example", "description": "CDP Agentkit Node.js SDK Chatbot Example", "version": "1.0.0", "private": true, diff --git a/typescript/examples/langchain-smart-wallet-chatbot/tsconfig.json b/typescript/examples/langchain-legacy-cdp-chatbot/tsconfig.json similarity index 100% rename from typescript/examples/langchain-smart-wallet-chatbot/tsconfig.json rename to typescript/examples/langchain-legacy-cdp-chatbot/tsconfig.json diff --git a/typescript/examples/langchain-solana-chatbot/chatbot.ts b/typescript/examples/langchain-solana-chatbot/chatbot.ts index 2e369ff96..7d25c74e5 100644 --- a/typescript/examples/langchain-solana-chatbot/chatbot.ts +++ b/typescript/examples/langchain-solana-chatbot/chatbot.ts @@ -91,15 +91,10 @@ async function initializeAgent() { // Initialize AgentKit const agentkit = await AgentKit.from({ + cdpApiKeyId: process.env.CDP_API_KEY_ID, + cdpApiKeySecret: process.env.CDP_API_KEY_SECRET, walletProvider, - actionProviders: [ - splActionProvider(), - walletActionProvider(), - cdpApiActionProvider({ - apiKeyId: process.env.CDP_API_KEY_ID, - apiKeySecret: process.env.CDP_API_KEY_SECRET, - }), - ], + actionProviders: [splActionProvider(), walletActionProvider(), cdpApiActionProvider()], }); const tools = await getLangChainTools(agentkit); diff --git a/typescript/examples/langchain-xmtp-chatbot/chatbot.ts b/typescript/examples/langchain-xmtp-chatbot/chatbot.ts index 43f7fb871..157bb4ce6 100644 --- a/typescript/examples/langchain-xmtp-chatbot/chatbot.ts +++ b/typescript/examples/langchain-xmtp-chatbot/chatbot.ts @@ -2,11 +2,11 @@ import * as dotenv from "dotenv"; import * as fs from "fs"; import { AgentKit, - CdpWalletProvider, + LegacyCdpWalletProvider, walletActionProvider, erc20ActionProvider, - cdpApiActionProvider, - cdpWalletActionProvider, + legacyCdpApiActionProvider, + cdpEvmWalletActionProvider, } from "@coinbase/agentkit"; import { getLangChainTools } from "@coinbase/agentkit-langchain"; import { HumanMessage } from "@langchain/core/messages"; @@ -190,21 +190,18 @@ async function initializeAgent(userId: string): Promise<{ agent: Agent; config: networkId: process.env.NETWORK_ID || "base-sepolia", }; - const walletProvider = await CdpWalletProvider.configureWithWallet(config); + const walletProvider = await LegacyCdpWalletProvider.configureWithWallet(config); const agentkit = await AgentKit.from({ walletProvider, actionProviders: [ walletActionProvider(), erc20ActionProvider(), - cdpApiActionProvider({ - apiKeyId: process.env.CDP_API_KEY_ID, - apiKeySecret: process.env.CDP_API_KEY_SECRET?.replace(/\\n/g, "\n"), - }), - cdpWalletActionProvider({ + legacyCdpApiActionProvider({ apiKeyId: process.env.CDP_API_KEY_ID, apiKeySecret: process.env.CDP_API_KEY_SECRET?.replace(/\\n/g, "\n"), }), + cdpEvmWalletActionProvider(), ], }); diff --git a/typescript/examples/langchain-zerodev-chatbot/chatbot.ts b/typescript/examples/langchain-zerodev-chatbot/chatbot.ts index 64c50a38c..dcda102f5 100644 --- a/typescript/examples/langchain-zerodev-chatbot/chatbot.ts +++ b/typescript/examples/langchain-zerodev-chatbot/chatbot.ts @@ -1,12 +1,12 @@ import { AgentKit, - CdpWalletProvider, + CdpEvmWalletProvider, wethActionProvider, walletActionProvider, erc20ActionProvider, erc721ActionProvider, cdpApiActionProvider, - cdpWalletActionProvider, + cdpEvmWalletActionProvider, pythActionProvider, ViemWalletProvider, NETWORK_ID_TO_VIEM_CHAIN, @@ -100,7 +100,7 @@ async function initializeAgent() { // Initialize Viem/CDP Wallet Provider let evmWalletProvider: EvmWalletProvider; - let cdpWalletProvider: CdpWalletProvider | undefined; + let cdpWalletProvider: CdpEvmWalletProvider | undefined; if (process.env.PRIVATE_KEY) { // Configure Viem Wallet Provider @@ -128,7 +128,7 @@ async function initializeAgent() { networkId: networkId, }; cdpWalletProvider = evmWalletProvider = - await CdpWalletProvider.configureWithWallet(cdpConfig); + await CdpEvmWalletProvider.configureWithWallet(cdpConfig); } else { throw new Error("No wallet provider configured"); } @@ -156,14 +156,8 @@ async function initializeAgent() { walletActionProvider(), erc20ActionProvider(), erc721ActionProvider(), - cdpApiActionProvider({ - apiKeyId: process.env.CDP_API_KEY_ID!, - apiKeySecret: process.env.CDP_API_KEY_SECRET!, - }), - cdpWalletActionProvider({ - apiKeyId: process.env.CDP_API_KEY_ID!, - apiKeySecret: process.env.CDP_API_KEY_SECRET!, - }), + cdpApiActionProvider(), + cdpEvmWalletActionProvider(), zeroDevWalletActionProvider(), ], }); diff --git a/typescript/examples/model-context-protocol-smart-wallet-server/index.ts b/typescript/examples/model-context-protocol-smart-wallet-server/index.ts index 85ca558da..89c5bf6d6 100644 --- a/typescript/examples/model-context-protocol-smart-wallet-server/index.ts +++ b/typescript/examples/model-context-protocol-smart-wallet-server/index.ts @@ -1,10 +1,10 @@ import { AgentKit, - cdpApiActionProvider, erc20ActionProvider, erc721ActionProvider, + cdpApiActionProvider, pythActionProvider, - SmartWalletProvider, + CdpSmartWalletProvider, walletActionProvider, wethActionProvider, } from "@coinbase/agentkit"; @@ -68,7 +68,7 @@ async function initializeServer() { signer, }; - const walletProvider = await SmartWalletProvider.configureWithWallet(config); + const walletProvider = await CdpSmartWalletProvider.configureWithWallet(config); if (!process.env.PRIVATE_KEY || !process.env.SMART_WALLET_ADDRESS) { console.log("Save your private key and smart wallet address to the environment variables"); @@ -85,10 +85,7 @@ async function initializeServer() { walletActionProvider(), erc20ActionProvider(), erc721ActionProvider(), - cdpApiActionProvider({ - apiKeyId: config.apiKeyId, - apiKeySecret: config.apiKeySecret, - }), + cdpApiActionProvider(), ], }); diff --git a/typescript/examples/vercel-ai-sdk-smart-wallet-chatbot/.env-local b/typescript/examples/vercel-ai-sdk-smart-wallet-chatbot/.env-local index 40f234b25..b2ae0a214 100644 --- a/typescript/examples/vercel-ai-sdk-smart-wallet-chatbot/.env-local +++ b/typescript/examples/vercel-ai-sdk-smart-wallet-chatbot/.env-local @@ -1,4 +1,4 @@ CDP_API_KEY_ID="" # Place your CDP API key id in between the quotes CDP_API_KEY_SECRET="" # Place your CDP API key secret in between the quotes -OPENAI_API_KEY="" # Place your OpenAI API key in between the quotes -PRIVATE_KEY="" # Place your private key in between the quotes \ No newline at end of file +CDP_WALLET_SECRET="" # Place your CDP Wallet Secret in between the quotes +OPENAI_API_KEY="" # Place your OpenAI API key in between the quotes \ No newline at end of file diff --git a/typescript/examples/vercel-ai-sdk-smart-wallet-chatbot/chatbot.ts b/typescript/examples/vercel-ai-sdk-smart-wallet-chatbot/chatbot.ts index a5a305b5f..a33f12b07 100644 --- a/typescript/examples/vercel-ai-sdk-smart-wallet-chatbot/chatbot.ts +++ b/typescript/examples/vercel-ai-sdk-smart-wallet-chatbot/chatbot.ts @@ -4,7 +4,7 @@ import { erc721ActionProvider, pythActionProvider, walletActionProvider, - SmartWalletProvider, + CdpSmartWalletProvider, } from "@coinbase/agentkit"; import { getVercelAITools } from "@coinbase/agentkit-vercel-ai-sdk"; import { openai } from "@ai-sdk/openai"; @@ -12,13 +12,12 @@ import { generateId, Message, streamText, ToolSet } from "ai"; import * as dotenv from "dotenv"; import * as readline from "readline"; import * as fs from "fs"; -import { Address, Hex } from "viem"; -import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { Address } from "viem"; dotenv.config(); type WalletData = { - privateKey: Hex; + smartAccountName?: string; smartWalletAddress: Address; }; @@ -79,58 +78,43 @@ async function initializeAgent() { const walletDataFile = `wallet_data_${networkId.replace(/-/g, "_")}.txt`; let walletData: WalletData | null = null; - let privateKey: Hex | null = null; + let smartAccountName: string | undefined = undefined; // Read existing wallet data if available if (fs.existsSync(walletDataFile)) { try { walletData = JSON.parse(fs.readFileSync(walletDataFile, "utf8")) as WalletData; - privateKey = walletData.privateKey; + smartAccountName = walletData.smartAccountName; } catch (error) { console.error(`Error reading wallet data for ${networkId}:`, error); // Continue without wallet data } } - if (!privateKey) { - if (walletData?.smartWalletAddress) { - throw new Error( - `Smart wallet found but no private key provided. Either provide the private key, or delete ${walletDataFile} and try again.`, - ); - } - privateKey = (process.env.PRIVATE_KEY || generatePrivateKey()) as Hex; - } - - const signer = privateKeyToAccount(privateKey); - // Configure Smart Wallet Provider - const walletProvider = await SmartWalletProvider.configureWithWallet({ + const walletProvider = await CdpSmartWalletProvider.configureWithWallet({ networkId, - signer, - smartWalletAddress: walletData?.smartWalletAddress, - paymasterUrl: undefined, // Sponsor transactions: https://docs.cdp.coinbase.com/paymaster/docs/welcome + smartAccountName, }); const agentKit = await AgentKit.from({ walletProvider, actionProviders: [ - cdpApiActionProvider({ - apiKeyId: process.env.CDP_API_KEY_ID, - apiKeySecret: process.env.CDP_API_KEY_SECRET, - }), + cdpApiActionProvider(), erc721ActionProvider(), pythActionProvider(), walletActionProvider(), ], }); + const data = await walletProvider.exportWallet(); + // Save wallet data - const smartWalletAddress = await walletProvider.getAddress(); fs.writeFileSync( walletDataFile, JSON.stringify({ - privateKey, - smartWalletAddress, + smartAccountName: data.name, + smartWalletAddress: data.address, } as WalletData), ); diff --git a/typescript/pnpm-lock.yaml b/typescript/pnpm-lock.yaml index d9411648e..a547b87f7 100644 --- a/typescript/pnpm-lock.yaml +++ b/typescript/pnpm-lock.yaml @@ -69,8 +69,8 @@ importers: specifier: ^0.1.0 version: 0.1.0 '@coinbase/cdp-sdk': - specifier: ^1.3.0 - version: 1.3.0(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2) + specifier: ^1.25.0 + version: 1.25.0(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(utf-8-validate@5.0.10) '@coinbase/coinbase-sdk': specifier: ^0.20.0 version: 0.20.0(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2) @@ -161,7 +161,7 @@ importers: version: 14.1.1 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.17.27)(ts-node@10.9.2(@types/node@20.17.27)(typescript@5.8.2)) + version: 29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)) mock-fs: specifier: ^5.2.0 version: 5.5.0 @@ -179,7 +179,7 @@ importers: version: 2.4.2 ts-jest: specifier: ^29.2.5 - version: 29.3.0(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest@29.7.0(@types/node@20.17.27)(ts-node@10.9.2(@types/node@20.17.27)(typescript@5.8.2)))(typescript@5.8.2) + version: 29.3.0(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)))(typescript@5.8.2) tsd: specifier: ^0.31.2 version: 0.31.2 @@ -258,7 +258,7 @@ importers: specifier: ^10.9.2 version: 10.9.2(@types/node@22.13.14)(typescript@5.8.2) - examples/langchain-cdp-v2-chatbot: + examples/langchain-cdp-smart-wallet-chatbot: dependencies: '@coinbase/agentkit': specifier: workspace:* @@ -268,19 +268,22 @@ importers: version: link:../../framework-extensions/langchain '@langchain/core': specifier: ^0.3.19 - version: 0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.2)) + version: 0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.56)) '@langchain/langgraph': specifier: ^0.2.21 - version: 0.2.59(@langchain/core@0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.2)))(react@18.3.1)(zod-to-json-schema@3.24.5(zod@3.24.2)) + version: 0.2.59(@langchain/core@0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.56)))(react@18.3.1)(zod-to-json-schema@3.24.5(zod@3.25.56)) '@langchain/openai': specifier: ^0.3.14 - version: 0.3.17(@langchain/core@0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.2)))(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 0.3.17(@langchain/core@0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.56)))(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10)) dotenv: specifier: ^16.4.5 version: 16.4.7 + viem: + specifier: ^2.24.1 + version: 2.24.1(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.25.56) zod: specifier: ^3.22.4 - version: 3.24.2 + version: 3.25.56 devDependencies: nodemon: specifier: ^3.1.0 @@ -320,7 +323,7 @@ importers: specifier: ^10.9.2 version: 10.9.2(@types/node@22.13.14)(typescript@5.8.2) - examples/langchain-privy-chatbot: + examples/langchain-legacy-cdp-chatbot: dependencies: '@coinbase/agentkit': specifier: workspace:* @@ -330,19 +333,19 @@ importers: version: link:../../framework-extensions/langchain '@langchain/core': specifier: ^0.3.19 - version: 0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.2)) + version: 0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.56)) '@langchain/langgraph': specifier: ^0.2.21 - version: 0.2.59(@langchain/core@0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.2)))(react@18.3.1)(zod-to-json-schema@3.24.5(zod@3.24.2)) + version: 0.2.59(@langchain/core@0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.56)))(react@18.3.1)(zod-to-json-schema@3.24.5(zod@3.25.56)) '@langchain/openai': specifier: ^0.3.14 - version: 0.3.17(@langchain/core@0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.2)))(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 0.3.17(@langchain/core@0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.56)))(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10)) dotenv: specifier: ^16.4.5 version: 16.4.7 zod: specifier: ^3.22.4 - version: 3.24.2 + version: 3.25.56 devDependencies: nodemon: specifier: ^3.1.0 @@ -351,7 +354,7 @@ importers: specifier: ^10.9.2 version: 10.9.2(@types/node@22.13.14)(typescript@5.8.2) - examples/langchain-smart-wallet-chatbot: + examples/langchain-privy-chatbot: dependencies: '@coinbase/agentkit': specifier: workspace:* @@ -371,9 +374,6 @@ importers: dotenv: specifier: ^16.4.5 version: 16.4.7 - viem: - specifier: ^2.24.1 - version: 2.24.1(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2) zod: specifier: ^3.22.4 version: 3.24.2 @@ -926,8 +926,8 @@ packages: '@changesets/write@0.4.0': resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} - '@coinbase/cdp-sdk@1.3.0': - resolution: {integrity: sha512-wG6LhIST0UsVpzrcgUy2wPPHTGeIz8Bgm0gR2Iw1gDvBNYTqx07dkR/ueMsoEZrxSnRWTr3Kbe8GqOFvgjfH+g==} + '@coinbase/cdp-sdk@1.25.0': + resolution: {integrity: sha512-8mkwiRr2jhm721G98jdfoeAtdfqrq9Qw2fI54ob8ZTnXDysX5XCc1tYnBAk97ZrzqRG3PT0Pql5REKD9ejkWlg==} '@coinbase/coinbase-sdk@0.20.0': resolution: {integrity: sha512-OoMMktKbjmeEwtwQCK3kIIoX5M+hNelxAGX5Llymvw6bmyrMDaEBZ/Myga9kaLJ+7Hi5Y4jPDy4Cy2MGxxXg6w==} @@ -1382,6 +1382,10 @@ packages: resolution: {integrity: sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==} engines: {node: ^14.21.3 || >=16} + '@noble/curves@1.9.2': + resolution: {integrity: sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g==} + engines: {node: ^14.21.3 || >=16} + '@noble/hashes@1.3.2': resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} engines: {node: '>= 16'} @@ -1394,6 +1398,10 @@ packages: resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==} engines: {node: ^14.21.3 || >=16} + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -4763,6 +4771,9 @@ packages: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} + uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + undefsafe@2.0.5: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} @@ -5437,21 +5448,24 @@ snapshots: human-id: 4.1.1 prettier: 2.8.8 - '@coinbase/cdp-sdk@1.3.0(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2)': + '@coinbase/cdp-sdk@1.25.0(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(utf-8-validate@5.0.10)': dependencies: + '@solana/spl-token': 0.4.13(@solana/web3.js@1.98.1(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(utf-8-validate@5.0.10) '@solana/web3.js': 1.98.1(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10) - abitype: 1.0.6(typescript@5.8.2)(zod@3.24.2) + abitype: 1.0.6(typescript@5.8.2)(zod@3.25.56) axios: 1.9.0 jose: 6.0.10 md5: 2.3.0 - viem: 2.24.1(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2) + uncrypto: 0.1.3 + viem: 2.24.1(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.25.56) + zod: 3.25.56 transitivePeerDependencies: - bufferutil - debug - encoding + - fastestsmallesttextencoderdecoder - typescript - utf-8-validate - - zod '@coinbase/coinbase-sdk@0.20.0(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2)': dependencies: @@ -5921,6 +5935,41 @@ snapshots: - supports-color - ts-node + '@jest/core@29.7.0(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2))': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.17.27 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@20.17.27)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + '@jest/environment@29.7.0': dependencies: '@jest/fake-timers': 29.7.0 @@ -6080,11 +6129,33 @@ snapshots: transitivePeerDependencies: - openai + '@langchain/core@0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.56))': + dependencies: + '@cfworker/json-schema': 4.1.1 + ansi-styles: 5.2.0 + camelcase: 6.3.0 + decamelize: 1.2.0 + js-tiktoken: 1.0.19 + langsmith: 0.2.15(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.56)) + mustache: 4.2.0 + p-queue: 6.6.2 + p-retry: 4.6.2 + uuid: 10.0.0 + zod: 3.24.2 + zod-to-json-schema: 3.24.5(zod@3.24.2) + transitivePeerDependencies: + - openai + '@langchain/langgraph-checkpoint@0.0.16(@langchain/core@0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.2)))': dependencies: '@langchain/core': 0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.2)) uuid: 10.0.0 + '@langchain/langgraph-checkpoint@0.0.16(@langchain/core@0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.56)))': + dependencies: + '@langchain/core': 0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.56)) + uuid: 10.0.0 + '@langchain/langgraph-sdk@0.0.60(@langchain/core@0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.2)))(react@18.3.1)': dependencies: '@types/json-schema': 7.0.15 @@ -6095,6 +6166,16 @@ snapshots: '@langchain/core': 0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.2)) react: 18.3.1 + '@langchain/langgraph-sdk@0.0.60(@langchain/core@0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.56)))(react@18.3.1)': + dependencies: + '@types/json-schema': 7.0.15 + p-queue: 6.6.2 + p-retry: 4.6.2 + uuid: 9.0.1 + optionalDependencies: + '@langchain/core': 0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.56)) + react: 18.3.1 + '@langchain/langgraph@0.2.59(@langchain/core@0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.2)))(react@18.3.1)(zod-to-json-schema@3.24.5(zod@3.24.2))': dependencies: '@langchain/core': 0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.2)) @@ -6107,6 +6188,18 @@ snapshots: transitivePeerDependencies: - react + '@langchain/langgraph@0.2.59(@langchain/core@0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.56)))(react@18.3.1)(zod-to-json-schema@3.24.5(zod@3.25.56))': + dependencies: + '@langchain/core': 0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.56)) + '@langchain/langgraph-checkpoint': 0.0.16(@langchain/core@0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.56))) + '@langchain/langgraph-sdk': 0.0.60(@langchain/core@0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.56)))(react@18.3.1) + uuid: 10.0.0 + zod: 3.24.2 + optionalDependencies: + zod-to-json-schema: 3.24.5(zod@3.25.56) + transitivePeerDependencies: + - react + '@langchain/openai@0.3.17(@langchain/core@0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.2)))(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: '@langchain/core': 0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.2)) @@ -6118,6 +6211,17 @@ snapshots: - encoding - ws + '@langchain/openai@0.3.17(@langchain/core@0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.56)))(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))': + dependencies: + '@langchain/core': 0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.56)) + js-tiktoken: 1.0.19 + openai: 4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.2) + zod: 3.24.2 + zod-to-json-schema: 3.24.5(zod@3.24.2) + transitivePeerDependencies: + - encoding + - ws + '@manypkg/find-root@1.1.0': dependencies: '@babel/runtime': 7.27.0 @@ -6144,8 +6248,8 @@ snapshots: express-rate-limit: 7.5.0(express@5.0.1) pkce-challenge: 4.1.0 raw-body: 3.0.0 - zod: 3.24.2 - zod-to-json-schema: 3.24.5(zod@3.24.2) + zod: 3.25.56 + zod-to-json-schema: 3.24.5(zod@3.25.56) transitivePeerDependencies: - supports-color @@ -6161,12 +6265,18 @@ snapshots: dependencies: '@noble/hashes': 1.7.1 + '@noble/curves@1.9.2': + dependencies: + '@noble/hashes': 1.8.0 + '@noble/hashes@1.3.2': {} '@noble/hashes@1.4.0': {} '@noble/hashes@1.7.1': {} + '@noble/hashes@1.8.0': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -6769,7 +6879,7 @@ snapshots: '@zerodev/webauthn-key@5.4.3(viem@2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2))': dependencies: - '@noble/curves': 1.8.1 + '@noble/curves': 1.9.2 '@simplewebauthn/browser': 8.3.7 '@simplewebauthn/types': 12.0.0 viem: 2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2) @@ -6781,10 +6891,10 @@ snapshots: a-sync-waterfall@1.0.1: {} - abitype@1.0.6(typescript@5.8.2)(zod@3.24.2): + abitype@1.0.6(typescript@5.8.2)(zod@3.25.56): optionalDependencies: typescript: 5.8.2 - zod: 3.24.2 + zod: 3.25.56 abitype@1.0.8(typescript@5.8.2)(zod@3.24.2): optionalDependencies: @@ -7301,6 +7411,21 @@ snapshots: - supports-color - ts-node + create-jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + create-require@1.1.1: {} cross-spawn@7.0.6: @@ -8535,6 +8660,25 @@ snapshots: - supports-color - ts-node + jest-cli@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + jest-config@29.7.0(@types/node@20.17.27)(ts-node@10.9.2(@types/node@20.17.27)(typescript@5.8.2)): dependencies: '@babel/core': 7.26.10 @@ -8566,6 +8710,68 @@ snapshots: - babel-plugin-macros - supports-color + jest-config@29.7.0(@types/node@20.17.27)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)): + dependencies: + '@babel/core': 7.26.10 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.26.10) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.17.27 + ts-node: 10.9.2(@types/node@22.13.14)(typescript@5.8.2) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-config@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)): + dependencies: + '@babel/core': 7.26.10 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.26.10) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 22.13.14 + ts-node: 10.9.2(@types/node@22.13.14)(typescript@5.8.2) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + jest-diff@29.7.0: dependencies: chalk: 4.1.2 @@ -8793,6 +8999,18 @@ snapshots: - supports-color - ts-node + jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)) + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + jose@4.15.9: {} jose@5.10.0: {} @@ -8869,6 +9087,17 @@ snapshots: optionalDependencies: openai: 4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.2) + langsmith@0.2.15(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.56)): + dependencies: + '@types/uuid': 10.0.0 + commander: 10.0.1 + p-queue: 6.6.2 + p-retry: 4.6.2 + semver: 7.7.1 + uuid: 10.0.0 + optionalDependencies: + openai: 4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.56) + leven@3.1.0: {} levn@0.4.1: @@ -9204,6 +9433,22 @@ snapshots: transitivePeerDependencies: - encoding + openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.56): + dependencies: + '@types/node': 18.19.83 + '@types/node-fetch': 2.6.12 + abort-controller: 3.0.0 + agentkeepalive: 4.6.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + optionalDependencies: + ws: 8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) + zod: 3.25.56 + transitivePeerDependencies: + - encoding + optional: true + opener@1.5.2: {} opensea-js@7.1.18(bufferutil@4.0.9)(utf-8-validate@5.0.10): @@ -9987,6 +10232,26 @@ snapshots: '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.26.10) + ts-jest@29.3.0(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)))(typescript@5.8.2): + dependencies: + bs-logger: 0.2.6 + ejs: 3.1.10 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)) + jest-util: 29.7.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.7.1 + type-fest: 4.38.0 + typescript: 5.8.2 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.26.10 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.26.10) + ts-node@10.9.2(@types/node@18.19.83)(typescript@5.8.2): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -10186,6 +10451,8 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 + uncrypto@0.1.3: {} + undefsafe@2.0.5: {} undici-types@5.26.5: {} @@ -10484,6 +10751,10 @@ snapshots: dependencies: zod: 3.24.2 + zod-to-json-schema@3.24.5(zod@3.25.56): + dependencies: + zod: 3.25.56 + zod@3.24.2: {} zod@3.25.56: {}