diff --git a/typescript/.changeset/loud-turtles-visit.md b/typescript/.changeset/loud-turtles-visit.md new file mode 100644 index 000000000..c6cd3b739 --- /dev/null +++ b/typescript/.changeset/loud-turtles-visit.md @@ -0,0 +1,7 @@ +--- +"@coinbase/agentkit": minor +--- + +Added ZeroDevWalletProvider powered by ZeroDev smart account + +This change introduced a new wallet provider, `ZeroDevWalletProvider` which allows AgentKit to use ZeroDev's chain-abstracted smart account with any EvmWalletProvider like cdpWalletProvider or privyEvmWalletProvider as the signer. diff --git a/typescript/agentkit/README.md b/typescript/agentkit/README.md index e80216763..d18693d79 100644 --- a/typescript/agentkit/README.md +++ b/typescript/agentkit/README.md @@ -31,6 +31,10 @@ AgentKit is a framework for easily enabling AI agents to take actions onchain. I - [Authorization Keys](#authorization-keys) - [Exporting Privy Wallet information](#exporting-privy-wallet-information) - [SmartWalletProvider](#smartwalletprovider) + - [ZeroDevWalletProvider](#zerodevwalletprovider) + - [Configuring from CdpWalletProvider](#configuring-from-cdpwalletprovider) + - [Configuring from PrivyWalletProvider](#configuring-from-privywalletprovider) + - [Configuring from ViemWalletProvider](#configuring-from-viemwalletprovider) - [SVM Wallet Providers](#svm-wallet-providers) - [SolanaKeypairWalletProvider](#solanakeypairwalletprovider) - [Network Configuration](#solana-network-configuration) @@ -411,6 +415,15 @@ const agent = createReactAgent({ +
+ZeroDev Wallet + + + + + +
getCABRetrieves chain abstracted balances (CAB) for specified tokens across multiple networks.
+
## Creating an Action Provider @@ -527,6 +540,7 @@ EVM: - [CdpWalletProvider](https://github.com/coinbase/agentkit/blob/main/typescript/agentkit/src/wallet-providers/cdpWalletProvider.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) ### CdpWalletProvider @@ -783,6 +797,84 @@ 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. + +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. + +#### Configuring from CdpWalletProvider + +```typescript +import { ZeroDevWalletProvider, CdpWalletProvider } from "@coinbase/agentkit"; + +// First create a CDP wallet provider as the signer +const cdpWalletProvider = await CdpWalletProvider.configureWithWallet({ + apiKeyName: "CDP API KEY NAME", + apiKeyPrivate: "CDP API KEY PRIVATE KEY", + 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", +}); +``` + +#### Configuring from PrivyWalletProvider + +```typescript +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 +}); + +// 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", +}); +``` + +#### Configuring from ViemWalletProvider + +```typescript +import { ZeroDevWalletProvider, ViemWalletProvider } from "@coinbase/agentkit"; +import { privateKeyToAccount } from "viem/accounts"; +import { base } from "viem/chains"; +import { createWalletClient, http } from "viem"; + +// First create a Viem wallet provider as the signer +const account = privateKeyToAccount("PRIVATE_KEY"); + +const viemWalletProvider = new ViemWalletProvider( + createWalletClient({ + 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", +}); +``` + ## SVM Wallet Providers SVM: diff --git a/typescript/agentkit/package.json b/typescript/agentkit/package.json index 209184cdd..8479c13d5 100644 --- a/typescript/agentkit/package.json +++ b/typescript/agentkit/package.json @@ -51,6 +51,9 @@ "canonicalize": "^2.1.0", "decimal.js": "^10.5.0", "ethers": "^6.13.5", + "@zerodev/ecdsa-validator": "^5.4.5", + "@zerodev/intent": "^0.0.24", + "@zerodev/sdk": "^5.4.28", "md5": "^2.3.0", "opensea-js": "^7.1.18", "reflect-metadata": "^0.2.2", diff --git a/typescript/agentkit/src/action-providers/index.ts b/typescript/agentkit/src/action-providers/index.ts index f4b31c953..e41de185b 100644 --- a/typescript/agentkit/src/action-providers/index.ts +++ b/typescript/agentkit/src/action-providers/index.ts @@ -27,3 +27,4 @@ export * from "./allora"; export * from "./flaunch"; export * from "./onramp"; export * from "./vaultsfyi"; +export * from "./zerodev"; diff --git a/typescript/agentkit/src/action-providers/zerodev/README.md b/typescript/agentkit/src/action-providers/zerodev/README.md new file mode 100644 index 000000000..68397a431 --- /dev/null +++ b/typescript/agentkit/src/action-providers/zerodev/README.md @@ -0,0 +1,54 @@ +# ZeroDev Action Provider + +This directory contains the **ZeroDevWalletActionProvider** implementation, which provides actions to interact with the **ZeroDev** wallet services and chain abstraction features. + +## Directory Structure + +``` +zerodev/ +├── zeroDevWalletActionProvider.ts # Provider for ZeroDev Wallet operations +├── zeroDevWalletActionProvider.test.ts # Tests for ZeroDev Wallet provider +├── schemas.ts # Action schemas for ZeroDev operations +├── index.ts # Main exports +└── README.md # This file +``` + +## Actions + +### ZeroDev Wallet Actions + +- `get_cab`: Retrieves chain abstracted balances (CAB) for specified tokens across multiple networks + + - Input parameters: + - `tokenTickers`: Array of token symbols (e.g., ["ETH", "USDC"]) + - `networks` (optional): Array of network IDs to check balances on + - `networkType` (optional): Filter networks by type ("mainnet" or "testnet") + - Returns aggregated balances across all specified networks for each token ticker + +## Adding New Actions + +To add new ZeroDev actions: + +1. Define your action schema in `schemas.ts` +2. Implement the action in `zeroDevWalletActionProvider.ts` +3. Add corresponding tests in `zeroDevWalletActionProvider.test.ts` + +## Network Support + +The ZeroDev provider supports all EVM-compatible networks, including: + +- Ethereum (Mainnet) +- Base (Mainnet) +- Optimism (Mainnet) +- Arbitrum (Mainnet) +- Polygon (Mainnet) +- Bsc (Mainnet) +- Avalanche (Mainnet) + +## Notes + +- The ZeroDev provider requires proper configuration of the ZeroDevWalletProvider +- Chain abstraction features work across all supported EVM networks +- The `get_cab` action provides a unified view of token balances across multiple chains + +For more information on **ZeroDev**, visit the [ZeroDev Documentation](https://docs.zerodev.app/). diff --git a/typescript/agentkit/src/action-providers/zerodev/index.ts b/typescript/agentkit/src/action-providers/zerodev/index.ts new file mode 100644 index 000000000..d95ebb235 --- /dev/null +++ b/typescript/agentkit/src/action-providers/zerodev/index.ts @@ -0,0 +1 @@ +export * from "./zeroDevWalletActionProvider"; diff --git a/typescript/agentkit/src/action-providers/zerodev/schemas.ts b/typescript/agentkit/src/action-providers/zerodev/schemas.ts new file mode 100644 index 000000000..7c7df38a2 --- /dev/null +++ b/typescript/agentkit/src/action-providers/zerodev/schemas.ts @@ -0,0 +1,19 @@ +import { z } from "zod"; + +/** + * Input schema for get CAB action. + */ +export const GetCABSchema = z.discriminatedUnion("type", [ + z.object({ + type: z.literal("networkFilter"), + tokenTickers: z.array(z.string()).optional(), + networks: z.array(z.number()).describe("The networks to get the balance for."), + }), + z.object({ + type: z.literal("networkType"), + tokenTickers: z.array(z.string()).optional(), + networkType: z + .enum(["mainnet", "testnet"]) + .describe("The type of network to get the balance for."), + }), +]); diff --git a/typescript/agentkit/src/action-providers/zerodev/zeroDevWalletActionProvider.test.ts b/typescript/agentkit/src/action-providers/zerodev/zeroDevWalletActionProvider.test.ts new file mode 100644 index 000000000..e3e54bcc8 --- /dev/null +++ b/typescript/agentkit/src/action-providers/zerodev/zeroDevWalletActionProvider.test.ts @@ -0,0 +1,139 @@ +import { ZeroDevWalletProvider } from "../../wallet-providers"; +import { ZeroDevWalletActionProvider } from "./zeroDevWalletActionProvider"; +import { GetCABSchema } from "./schemas"; +import { type GetCABResult } from "@zerodev/intent"; + +describe("ZeroDev Wallet Action Provider Input Schemas", () => { + describe("Get CAB Schema", () => { + it("should successfully parse valid input with network type", () => { + const validInput = { + type: "networkType" as const, + tokenTickers: ["ETH", "USDC"], + networkType: "mainnet" as const, + }; + + const result = GetCABSchema.safeParse(validInput); + + expect(result.success).toBe(true); + expect(result.data).toEqual(validInput); + }); + + it("should successfully parse valid input with network filter", () => { + const validInput = { + type: "networkFilter" as const, + tokenTickers: ["ETH", "USDC"], + networks: [1, 8453], // ethereum and base network IDs + }; + + const result = GetCABSchema.safeParse(validInput); + + expect(result.success).toBe(true); + expect(result.data).toEqual(validInput); + }); + + it("should successfully parse input with optional tokenTickers omitted", () => { + const validInput = { + type: "networkType" as const, + networkType: "mainnet" as const, + }; + + const result = GetCABSchema.safeParse(validInput); + + expect(result.success).toBe(true); + expect(result.data).toEqual(validInput); + }); + + it("should fail parsing empty input", () => { + const emptyInput = {}; + const result = GetCABSchema.safeParse(emptyInput); + + expect(result.success).toBe(false); + }); + }); +}); + +describe("ZeroDev Wallet Action Provider", () => { + let actionProvider: ZeroDevWalletActionProvider; + let mockWallet: jest.Mocked; + + beforeEach(() => { + // Reset all mocks before each test + jest.clearAllMocks(); + + actionProvider = new ZeroDevWalletActionProvider(); + mockWallet = { + getCAB: jest.fn(), + getAddress: jest.fn().mockReturnValue("0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83"), + getNetwork: jest.fn().mockReturnValue({ networkId: "ethereum-mainnet" }), + } as unknown as jest.Mocked; + }); + + describe("getCAB", () => { + const MOCK_BALANCES = { + balance: BigInt(1000000000000000000), + tokenAddress: "0x1234567890123456789012345678901234567890" as `0x${string}`, + tokens: { + ETH: { + ethereum: "1.5", + base: "0.5", + }, + USDC: { + ethereum: "1000", + base: "500", + }, + }, + } as unknown as GetCABResult; + + beforeEach(() => { + mockWallet.getCAB.mockResolvedValue(MOCK_BALANCES); + }); + + it("should successfully get chain abstracted balances with network type", async () => { + const args = { + type: "networkType" as const, + tokenTickers: ["ETH", "USDC"], + networkType: "mainnet" as const, + }; + + const result = await actionProvider.getCAB(mockWallet, args); + + expect(mockWallet.getCAB).toHaveBeenCalledWith(args); + expect(result).toEqual(MOCK_BALANCES); + }); + + it("should successfully get chain abstracted balances with network filter", async () => { + const args = { + type: "networkFilter" as const, + tokenTickers: ["ETH", "USDC"], + networks: [1, 8453], // ethereum and base network IDs + }; + + const result = await actionProvider.getCAB(mockWallet, args); + + expect(mockWallet.getCAB).toHaveBeenCalledWith(args); + expect(result).toEqual(MOCK_BALANCES); + }); + + it("should handle errors when getting balances", async () => { + const args = { + type: "networkType" as const, + networkType: "mainnet" as const, + }; + + const error = new Error("Failed to get balances"); + mockWallet.getCAB.mockRejectedValue(error); + + await expect(actionProvider.getCAB(mockWallet, args)).rejects.toThrow(error); + }); + }); + + describe("supportsNetwork", () => { + it("should return true when protocolFamily is evm", () => { + expect(actionProvider.supportsNetwork({ protocolFamily: "evm" })).toBe(true); + }); + + it("should return false when protocolFamily is not evm", () => { + expect(actionProvider.supportsNetwork({ protocolFamily: "solana" })).toBe(false); + }); + }); +}); diff --git a/typescript/agentkit/src/action-providers/zerodev/zeroDevWalletActionProvider.ts b/typescript/agentkit/src/action-providers/zerodev/zeroDevWalletActionProvider.ts new file mode 100644 index 000000000..d35a4958b --- /dev/null +++ b/typescript/agentkit/src/action-providers/zerodev/zeroDevWalletActionProvider.ts @@ -0,0 +1,52 @@ +import { ZeroDevWalletProvider } from "../../wallet-providers"; +import { ActionProvider } from "../actionProvider"; +import { Network } from "../../network"; +import { CreateAction } from "../actionDecorator"; +import { GetCABSchema } from "./schemas"; +import { z } from "zod"; + +/** + * ZeroDevWalletActionProvider is an action provider for ZeroDevWalletProvider. + * + * This provider is used for any action that requires a ZeroDevWalletProvider. + */ +export class ZeroDevWalletActionProvider extends ActionProvider { + /** + * Constructs a new ZeroDevWalletActionProvider. + */ + constructor() { + super("zeroDevWallet", []); + } + + /** + * Gets the chain abstracted balance for the wallet. + * + * @param walletProvider - The wallet provider to get the balance for. + * @param args - The arguments for the action. + * @returns The chain abstracted balance for the wallet. + */ + @CreateAction({ + name: "get_cab", + description: `This tool retrieves the chain abstracted balance (CAB) for specified tokens across multiple networks. +It takes the following inputs: +- tokenTickers: An array of token symbols (e.g., ["ETH", "USDC"]) to get balances for +- networks (optional): An array of network IDs to check balances on. If not provided, will check all supported networks +- networkType (optional): Filter networks by type ("mainnet" or "testnet"). If not provided, will check both types + +The tool will return the aggregated balance across all specified networks for each token ticker. This is useful for getting a complete picture of a wallet's holdings across different chains in a single call.`, + schema: GetCABSchema, + }) + async getCAB(walletProvider: ZeroDevWalletProvider, args: z.infer) { + return walletProvider.getCAB(args); + } + + /** + * Checks if the ZeroDevWalletActionProvider supports the given network. + * + * @param network - The network to check. + * @returns True if the ZeroDevWalletActionProvider supports the network, false otherwise. + */ + supportsNetwork = (network: Network) => network.protocolFamily === "evm"; +} + +export const zeroDevWalletActionProvider = () => new ZeroDevWalletActionProvider(); diff --git a/typescript/agentkit/src/wallet-providers/cdpWalletProvider.test.ts b/typescript/agentkit/src/wallet-providers/cdpWalletProvider.test.ts index c7744c66d..2f59e5ff2 100644 --- a/typescript/agentkit/src/wallet-providers/cdpWalletProvider.test.ts +++ b/typescript/agentkit/src/wallet-providers/cdpWalletProvider.test.ts @@ -62,6 +62,7 @@ jest.mock("viem", () => { parseEther: jest.fn((_value: string) => BigInt(1000000000000000000)), keccak256: jest.fn((_value: string) => "0xmockhash"), serializeTransaction: jest.fn((_tx: string) => "0xserialized"), + hashMessage: jest.fn((_message: string) => "0xmockhashmessage"), }; }); diff --git a/typescript/agentkit/src/wallet-providers/cdpWalletProvider.ts b/typescript/agentkit/src/wallet-providers/cdpWalletProvider.ts index 133097ed6..8518066ee 100644 --- a/typescript/agentkit/src/wallet-providers/cdpWalletProvider.ts +++ b/typescript/agentkit/src/wallet-providers/cdpWalletProvider.ts @@ -16,6 +16,7 @@ import { ContractFunctionArgs, Address, Hex, + hashMessage, } from "viem"; import { EvmWalletProvider } from "./evmWalletProvider"; import { Network } from "../network"; @@ -29,7 +30,6 @@ import { Wallet, WalletData, hashTypedDataMessage, - hashMessage, } from "@coinbase/coinbase-sdk"; import { NETWORK_ID_TO_CHAIN_ID, NETWORK_ID_TO_VIEM_CHAIN } from "../network/network"; import { applyGasMultiplier } from "../utils"; diff --git a/typescript/agentkit/src/wallet-providers/evmWalletProvider.ts b/typescript/agentkit/src/wallet-providers/evmWalletProvider.ts index 216ec72b2..44e236beb 100644 --- a/typescript/agentkit/src/wallet-providers/evmWalletProvider.ts +++ b/typescript/agentkit/src/wallet-providers/evmWalletProvider.ts @@ -1,6 +1,7 @@ // TODO: Improve type safety /* eslint-disable @typescript-eslint/no-explicit-any */ +import { toAccount } from "viem/accounts"; import { WalletProvider } from "./walletProvider"; import { TransactionRequest, @@ -9,6 +10,8 @@ import { ContractFunctionName, Abi, ContractFunctionArgs, + Address, + Account, } from "viem"; /** @@ -17,6 +20,26 @@ import { * @abstract */ export abstract class EvmWalletProvider extends WalletProvider { + /** + * Convert the wallet provider to a Signer. + * + * @returns The signer. + */ + toSigner(): Account { + return toAccount({ + address: this.getAddress() as Address, + signMessage: async ({ message }) => { + return this.signMessage(message as string | Uint8Array); + }, + signTransaction: async transaction => { + return this.signTransaction(transaction as TransactionRequest); + }, + signTypedData: async typedData => { + return this.signTypedData(typedData); + }, + }); + } + /** * Sign a message. * diff --git a/typescript/agentkit/src/wallet-providers/index.ts b/typescript/agentkit/src/wallet-providers/index.ts index ad79f9fe4..53bbe1975 100644 --- a/typescript/agentkit/src/wallet-providers/index.ts +++ b/typescript/agentkit/src/wallet-providers/index.ts @@ -9,3 +9,4 @@ export * from "./privyWalletProvider"; export * from "./privyEvmWalletProvider"; export * from "./privySvmWalletProvider"; export * from "./privyEvmDelegatedEmbeddedWalletProvider"; +export * from "./zeroDevWalletProvider"; diff --git a/typescript/agentkit/src/wallet-providers/walletProvider.test.ts b/typescript/agentkit/src/wallet-providers/walletProvider.test.ts index 27e7b5541..503961dc7 100644 --- a/typescript/agentkit/src/wallet-providers/walletProvider.test.ts +++ b/typescript/agentkit/src/wallet-providers/walletProvider.test.ts @@ -73,6 +73,24 @@ describe("WalletProvider", () => { nativeTransfer(_to: string, _value: string): Promise { return Promise.resolve("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"); } + /** + * Converts the wallet provider to a signer. + * + * @returns The signer object. + */ + toSigner(): { + address: string; + signMessage: (message: string) => Promise; + signTransaction: (transaction: unknown) => Promise; + signTypedData: (typedData: unknown) => Promise; + } { + return { + address: this.getAddress(), + signMessage: async (_message: string) => "0xsigned", + signTransaction: async (_transaction: unknown) => "0xsigned", + signTypedData: async (_typedData: unknown) => "0xsigned", + }; + } } beforeEach(() => { @@ -121,4 +139,15 @@ describe("WalletProvider", () => { }, 0), ); }); + + it("should convert wallet provider to signer", () => { + const provider = new MockWalletProvider(); + const signer = provider.toSigner(); + + expect(signer).toBeDefined(); + expect(signer.address).toBe(MOCK_ADDRESS); + expect(signer.signMessage).toBeDefined(); + expect(signer.signTransaction).toBeDefined(); + expect(signer.signTypedData).toBeDefined(); + }); }); diff --git a/typescript/agentkit/src/wallet-providers/zeroDevWalletProvider.test.ts b/typescript/agentkit/src/wallet-providers/zeroDevWalletProvider.test.ts new file mode 100644 index 000000000..7767022ee --- /dev/null +++ b/typescript/agentkit/src/wallet-providers/zeroDevWalletProvider.test.ts @@ -0,0 +1,569 @@ +import { ZeroDevWalletProvider } from "./zeroDevWalletProvider"; +import { EvmWalletProvider } from "./evmWalletProvider"; +import { Network } from "../network"; +import { + PublicClient, + TransactionRequest, + ReadContractParameters, + TransactionReceipt, + Address, + zeroAddress, + Hex, + Account, +} from "viem"; +import { createKernelAccount, KernelSmartAccountImplementation } from "@zerodev/sdk"; +import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator"; +import { + createIntentClient, + type SendUserIntentResult, + type IntentExecutionReceipt, + type GetCABParameters, +} from "@zerodev/intent"; +import { SmartAccount } from "viem/account-abstraction"; + +// ========================================================= +// Consts +// ========================================================= + +const MOCK_ADDRESS = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"; +const MOCK_NETWORK_ID = "mainnet"; +const MOCK_PROJECT_ID = "project-1234"; +const MOCK_TRANSACTION_HASH = "0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba"; +const MOCK_SIGNATURE = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b"; +const MOCK_NETWORK: Network = { + protocolFamily: "evm", + networkId: MOCK_NETWORK_ID, + chainId: "1", +}; + +const mockPublicClient = { + readContract: jest.fn(), + waitForTransactionReceipt: jest.fn(), + getBalance: jest.fn(), +} as unknown as jest.Mocked; + +// ========================================================= +// Mocks +// ========================================================= + +// Mock Viem +jest.mock("viem", () => { + return { + createPublicClient: jest.fn(() => mockPublicClient), + http: jest.fn(), + zeroAddress: "0x0000000000000000000000000000000000000000", + toAccount: jest.fn(), + }; +}); + +// Mock ZeroDev SDK +jest.mock("@zerodev/sdk", () => ({ + createKernelAccount: jest.fn(), + KERNEL_V3_2: "v3.2", + getEntryPoint: jest.fn().mockReturnValue("0xENTRYPOINT"), +})); + +// Mock ECDSA Validator +jest.mock("@zerodev/ecdsa-validator", () => ({ + signerToEcdsaValidator: jest.fn(), +})); + +// Mock Intent +jest.mock("@zerodev/intent", () => ({ + createIntentClient: jest.fn(), + installIntentExecutor: jest.fn(), + INTENT_V0_3: "v0.3", +})); + +jest.mock("../network", () => ({ + NETWORK_ID_TO_VIEM_CHAIN: { + mainnet: { id: 1, name: "Ethereum" }, + sepolia: { id: 11155111, name: "Sepolia" }, + }, +})); + +jest.mock("../analytics", () => ({ + sendAnalyticsEvent: jest.fn().mockImplementation(() => Promise.resolve()), +})); + +// ========================================================= +// Mock Implementations +// ========================================================= + +const mockKernelAccount = { + address: MOCK_ADDRESS, + signMessage: jest.fn(), + signTypedData: jest.fn(), +} as unknown as jest.Mocked>; + +const mockIntentClient = { + sendUserIntent: jest.fn(), + waitForUserIntentExecutionReceipt: jest.fn(), +} as unknown as jest.Mocked>; + +const mockEvmWalletProvider = { + getAddress: jest.fn().mockReturnValue(MOCK_ADDRESS), + signMessage: jest.fn(), + signTransaction: jest.fn(), + signTypedData: jest.fn(), + getNetwork: jest.fn().mockReturnValue(MOCK_NETWORK), + toSigner: jest.fn().mockReturnValue({ + address: MOCK_ADDRESS, + signMessage: jest.fn(), + signTransaction: jest.fn(), + signTypedData: jest.fn(), + } as unknown as Account), +} as unknown as jest.Mocked; + +// ========================================================= +// Test Suite +// ========================================================= + +describe("ZeroDevWalletProvider", () => { + let provider: ZeroDevWalletProvider; + + beforeEach(async () => { + jest.clearAllMocks(); + + // Configure mock implementations + (createKernelAccount as jest.Mock).mockResolvedValue(mockKernelAccount); + (createIntentClient as jest.Mock).mockResolvedValue(mockIntentClient); + (signerToEcdsaValidator as jest.Mock).mockResolvedValue({ validator: "mock-validator" }); + + mockPublicClient.readContract.mockResolvedValue("mock_result"); + mockPublicClient.waitForTransactionReceipt.mockResolvedValue({ + transactionHash: MOCK_TRANSACTION_HASH, + } as unknown as TransactionReceipt); + mockPublicClient.getBalance.mockResolvedValue(BigInt(1000000000000000000)); + + mockKernelAccount.signMessage.mockResolvedValue(MOCK_SIGNATURE as `0x${string}`); + mockKernelAccount.signTypedData.mockResolvedValue(MOCK_SIGNATURE as `0x${string}`); + + // Mock Intent Client behavior + mockIntentClient.sendUserIntent.mockResolvedValue({ + outputUiHash: { uiHash: "0xmockUiHash" as Hex }, + inputsUiHash: { uiHash: "0xmockUiHash" as Hex }, + } as unknown as SendUserIntentResult); + mockIntentClient.waitForUserIntentExecutionReceipt.mockResolvedValue({ + intentHash: "0xmockIntentHash" as `0x${string}`, + sender: MOCK_ADDRESS as `0x${string}`, + relayer: MOCK_ADDRESS as `0x${string}`, + executionChainId: "0x1" as `0x${string}`, + logs: [], + receipt: { transactionHash: MOCK_TRANSACTION_HASH } as unknown as TransactionReceipt, + }); + + // Create provider instance + provider = await ZeroDevWalletProvider.configureWithWallet({ + signer: mockEvmWalletProvider.toSigner(), + projectId: MOCK_PROJECT_ID, + networkId: MOCK_NETWORK_ID, + entryPointVersion: "0.7", + }); + }); + + // ========================================================= + // Initialization Tests + // ========================================================= + + describe("initialization", () => { + it("should initialize with a signer", async () => { + expect(provider).toBeInstanceOf(ZeroDevWalletProvider); + expect(provider.getAddress()).toBe(MOCK_ADDRESS); + expect(provider.getNetwork()).toEqual(MOCK_NETWORK); + expect(createKernelAccount).toHaveBeenCalled(); + expect(createIntentClient).toHaveBeenCalled(); + }); + + it("should throw error when signer is not provided", async () => { + await expect( + ZeroDevWalletProvider.configureWithWallet({ + signer: undefined as unknown as Account, + projectId: MOCK_PROJECT_ID, + networkId: MOCK_NETWORK_ID, + }), + ).rejects.toThrow("Signer is required"); + }); + + it("should throw error when project ID is not provided", async () => { + await expect( + ZeroDevWalletProvider.configureWithWallet({ + signer: mockEvmWalletProvider.toSigner(), + projectId: "", + networkId: MOCK_NETWORK_ID, + }), + ).rejects.toThrow("ZeroDev project ID is required"); + }); + + it("should initialize with a specific address if provided", async () => { + const customAddress = "0xCustomAddress"; + await ZeroDevWalletProvider.configureWithWallet({ + signer: mockEvmWalletProvider.toSigner(), + projectId: MOCK_PROJECT_ID, + networkId: MOCK_NETWORK_ID, + address: customAddress, + }); + + expect(createKernelAccount).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + address: customAddress as `0x${string}`, + }), + ); + }); + }); + + // ========================================================= + // Basic Wallet Methods Tests + // ========================================================= + + describe("basic wallet methods", () => { + it("should get the address", () => { + expect(provider.getAddress()).toBe(MOCK_ADDRESS); + }); + + it("should get the network", () => { + expect(provider.getNetwork()).toEqual(MOCK_NETWORK); + }); + + it("should get the name", () => { + expect(provider.getName()).toBe("zerodev_wallet_provider"); + }); + + it("should get the balance", async () => { + const balance = await provider.getBalance(); + expect(balance).toBe(BigInt(1000000000000000000)); + expect(mockPublicClient.getBalance).toHaveBeenCalledWith({ + address: MOCK_ADDRESS as `0x${string}`, + }); + }); + + it("should get the kernel account", () => { + expect(provider.getKernelAccount()).toBe(mockKernelAccount); + }); + + it("should get the intent client", () => { + expect(provider.getIntentClient()).toBe(mockIntentClient); + }); + }); + + // ========================================================= + // Signing Operations Tests + // ========================================================= + + describe("signing operations", () => { + it("should sign messages", async () => { + const message = "Hello, world!"; + const signature = await provider.signMessage(message); + + expect(mockKernelAccount.signMessage).toHaveBeenCalledWith({ + message: message, + }); + expect(signature).toBe(MOCK_SIGNATURE); + }); + + it("should sign Uint8Array messages", async () => { + const messageBytes = new TextEncoder().encode("Hello, world!"); + const signature = await provider.signMessage(messageBytes); + + expect(mockKernelAccount.signMessage).toHaveBeenCalledWith({ + message: "Hello, world!", + }); + expect(signature).toBe(MOCK_SIGNATURE); + }); + + it("should sign typed data", async () => { + const typedData = { + domain: { name: "Example" }, + types: { Test: [{ name: "test", type: "string" }] }, + message: { test: "example" }, + primaryType: "Test", + }; + + const signature = await provider.signTypedData(typedData); + + expect(mockKernelAccount.signTypedData).toHaveBeenCalledWith(typedData); + expect(signature).toBe(MOCK_SIGNATURE); + }); + + it("should throw error when signing transactions directly", async () => { + const tx = { + to: "0x1234567890123456789012345678901234567890" as `0x${string}`, + value: BigInt(1000000000000000000), + }; + + await expect(provider.signTransaction(tx)).rejects.toThrow( + "signTransaction is not supported for ZeroDev Wallet Provider", + ); + }); + }); + + // ========================================================= + // Transaction Operations Tests + // ========================================================= + + describe("transaction operations", () => { + it("should send native token transfer transactions", async () => { + const to = "0x1234567890123456789012345678901234567890" as `0x${string}`; + const value = BigInt(1000000000000000000); + + const transaction: TransactionRequest = { + to, + value, + data: "0x", + }; + + const txHash = await provider.sendTransaction(transaction); + + expect(mockIntentClient.sendUserIntent).toHaveBeenCalledWith({ + calls: [ + { + to, + value, + data: "0x", + }, + ], + outputTokens: [ + { + address: zeroAddress, + chainId: 1, + amount: value, + }, + ], + }); + + expect(mockIntentClient.waitForUserIntentExecutionReceipt).toHaveBeenCalled(); + expect(txHash).toBe(MOCK_TRANSACTION_HASH); + }); + + it("should send contract interaction transactions", async () => { + const to = "0x1234567890123456789012345678901234567890" as `0x${string}`; + const data = "0xabcdef" as `0x${string}`; + + const transaction: TransactionRequest = { + to, + data, + }; + + const txHash = await provider.sendTransaction(transaction); + + expect(mockIntentClient.sendUserIntent).toHaveBeenCalledWith({ + calls: [ + { + to, + value: BigInt(0), + data, + }, + ], + chainId: 1, + }); + + expect(mockIntentClient.waitForUserIntentExecutionReceipt).toHaveBeenCalled(); + expect(txHash).toBe(MOCK_TRANSACTION_HASH); + }); + + it("should handle native transfers using nativeTransfer method", async () => { + const to = "0x1234567890123456789012345678901234567890"; + const value = "1.0"; + + const valueInWei = BigInt(parseFloat(value) * 10 ** 18); + + const txHash = await provider.nativeTransfer(to, value); + + expect(mockIntentClient.sendUserIntent).toHaveBeenCalledWith({ + calls: [ + { + to: to as Address, + value: valueInWei, + data: "0x", + }, + ], + outputTokens: [ + { + address: zeroAddress, + chainId: 1, + amount: valueInWei, + }, + ], + }); + + expect(mockIntentClient.waitForUserIntentExecutionReceipt).toHaveBeenCalled(); + expect(txHash).toBe(MOCK_TRANSACTION_HASH); + }); + + it("should wait for transaction receipt", async () => { + const hash = + "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" as `0x${string}`; + + await provider.waitForTransactionReceipt(hash); + + expect(mockPublicClient.waitForTransactionReceipt).toHaveBeenCalledWith({ hash }); + }); + + it("should handle transaction failures", async () => { + mockIntentClient.sendUserIntent.mockRejectedValueOnce(new Error("Transaction failed")); + + const transaction: TransactionRequest = { + to: "0x1234567890123456789012345678901234567890" as `0x${string}`, + value: BigInt(1000000000000000000), + }; + + await expect(provider.sendTransaction(transaction)).rejects.toThrow("Transaction failed"); + }); + + it("should handle receipt waiting failures", async () => { + mockIntentClient.waitForUserIntentExecutionReceipt.mockRejectedValueOnce( + new Error("Receipt waiting failed"), + ); + + const transaction: TransactionRequest = { + to: "0x1234567890123456789012345678901234567890" as `0x${string}`, + value: BigInt(1000000000000000000), + }; + + await expect(provider.sendTransaction(transaction)).rejects.toThrow("Receipt waiting failed"); + }); + + it("should handle missing transaction hash in receipt", async () => { + mockIntentClient.waitForUserIntentExecutionReceipt.mockResolvedValueOnce({ + receipt: {} as unknown as TransactionReceipt, + } as unknown as IntentExecutionReceipt); + + const transaction: TransactionRequest = { + to: "0x1234567890123456789012345678901234567890" as `0x${string}`, + value: BigInt(1000000000000000000), + }; + + const txHash = await provider.sendTransaction(transaction); + + expect(txHash).toBe("0x"); + }); + }); + + // ========================================================= + // 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_ADDRESS as `0x${string}`], + } as unknown as ReadContractParameters); + + expect(result).toBe("mock_result"); + expect(mockPublicClient.readContract).toHaveBeenCalled(); + }); + + it("should handle contract read failures", async () => { + mockPublicClient.readContract.mockRejectedValueOnce(new Error("Contract read failed")); + + const abi = [ + { + name: "balanceOf", + type: "function", + inputs: [{ name: "account", type: "address" }], + outputs: [{ name: "balance", type: "uint256" }], + stateMutability: "view", + }, + ] as const; + + await expect( + provider.readContract({ + address: "0x1234567890123456789012345678901234567890" as `0x${string}`, + abi, + functionName: "balanceOf", + args: [MOCK_ADDRESS as `0x${string}`], + } as unknown as ReadContractParameters), + ).rejects.toThrow("Contract read failed"); + }); + }); + + // ========================================================= + // Chain Abstracted Balance Tests + // ========================================================= + + describe("chain abstracted balance", () => { + it("should get chain abstracted balance", async () => { + const mockCABResult = { + balance: BigInt(1000000000000000000), + tokenAddress: "0x1234567890123456789012345678901234567890" as `0x${string}`, + }; + + mockIntentClient.getCAB = jest.fn().mockResolvedValue(mockCABResult); + + const options: GetCABParameters = { + tokenTickers: ["USDC"], + networks: [1], + }; + + const result = await provider.getCAB(options); + + expect(mockIntentClient.getCAB).toHaveBeenCalledWith(options); + expect(result).toEqual(mockCABResult); + }); + + it("should handle getCAB failures", async () => { + mockIntentClient.getCAB = jest.fn().mockRejectedValue(new Error("CAB check failed")); + + const options: GetCABParameters = { + tokenTickers: ["USDC"], + networks: [1234], + }; + + await expect(provider.getCAB(options)).rejects.toThrow("CAB check failed"); + }); + }); + + // ========================================================= + // Error Handling Tests + // ========================================================= + + describe("error handling", () => { + it("should handle intent client initialization failures", async () => { + (createIntentClient as jest.Mock).mockRejectedValueOnce( + new Error("Intent client initialization failed"), + ); + + await expect( + ZeroDevWalletProvider.configureWithWallet({ + signer: mockEvmWalletProvider.toSigner(), + projectId: MOCK_PROJECT_ID, + networkId: MOCK_NETWORK_ID, + }), + ).rejects.toThrow("Intent client initialization failed"); + }); + + it("should handle kernel account initialization failures", async () => { + (createKernelAccount as jest.Mock).mockRejectedValueOnce( + new Error("Kernel account initialization failed"), + ); + + await expect( + ZeroDevWalletProvider.configureWithWallet({ + signer: mockEvmWalletProvider.toSigner(), + projectId: MOCK_PROJECT_ID, + networkId: MOCK_NETWORK_ID, + }), + ).rejects.toThrow("Kernel account initialization failed"); + }); + + it("should handle balance check failures", async () => { + mockPublicClient.getBalance.mockRejectedValueOnce(new Error("Balance check failed")); + + await expect(provider.getBalance()).rejects.toThrow("Balance check failed"); + }); + }); +}); diff --git a/typescript/agentkit/src/wallet-providers/zeroDevWalletProvider.ts b/typescript/agentkit/src/wallet-providers/zeroDevWalletProvider.ts new file mode 100644 index 000000000..982de9d5e --- /dev/null +++ b/typescript/agentkit/src/wallet-providers/zeroDevWalletProvider.ts @@ -0,0 +1,394 @@ +import { createKernelAccount, KernelSmartAccountImplementation } from "@zerodev/sdk"; +import { KERNEL_V3_2, getEntryPoint } from "@zerodev/sdk/constants"; +import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator"; +import { + createIntentClient, + type GetCABParameters, + type GetCABResult, + installIntentExecutor, + INTENT_V0_3, +} from "@zerodev/intent"; +import { + Abi, + Address, + ContractFunctionArgs, + ContractFunctionName, + createPublicClient, + http, + PublicClient, + ReadContractParameters, + ReadContractReturnType, + TransactionRequest, + Hex, + zeroAddress, + Hash, + Account, +} from "viem"; +import { SmartAccount } from "viem/account-abstraction"; +import { EvmWalletProvider } from "./evmWalletProvider"; +import { NETWORK_ID_TO_VIEM_CHAIN, type Network } from "../network"; +import { Signer } from "@zerodev/sdk/types"; + +/** + * Configuration options for the ZeroDev Wallet Provider. + */ +export interface ZeroDevWalletProviderConfig { + /** + * The underlying EVM wallet provider to use as a signer. + */ + signer: Account; + + /** + * The ZeroDev project ID. + */ + projectId: string; + + /** + * The EntryPoint version ("0.6" or "0.7"). + * Defaults to "0.7". + */ + entryPointVersion?: "0.6" | "0.7"; + + /** + * The network ID of the wallet. + */ + networkId?: string; + + /** + * The address of the wallet. + * If not provided, it will be computed from the signer. + */ + address?: Address; +} + +/** + * A wallet provider that uses ZeroDev's account abstraction. + */ +export class ZeroDevWalletProvider extends EvmWalletProvider { + #signer: Account; + #projectId: string; + #network: Network; + #address: Address; + #publicClient: PublicClient; + #kernelAccount: SmartAccount; + #intentClient: Awaited>; + + /** + * Constructs a new ZeroDevWalletProvider. + * + * @param config - The configuration options for the ZeroDevWalletProvider. + * @param kernelAccount - The kernel account. + * @param intentClient - The intent client. + */ + private constructor( + config: ZeroDevWalletProviderConfig, + kernelAccount: SmartAccount, + intentClient: Awaited>, + ) { + super(); + + this.#signer = config.signer; + this.#projectId = config.projectId; + this.#network = { + protocolFamily: "evm", + networkId: config.networkId!, + chainId: NETWORK_ID_TO_VIEM_CHAIN[config.networkId!].id.toString(), + }; + this.#address = kernelAccount.address; + this.#kernelAccount = kernelAccount; + this.#intentClient = intentClient; + + // Create public client + this.#publicClient = createPublicClient({ + chain: NETWORK_ID_TO_VIEM_CHAIN[this.#network.networkId!], + transport: http(), + }); + } + + /** + * Configures a new ZeroDevWalletProvider with an existing wallet provider as the signer. + * + * @param config - The configuration options for the ZeroDevWalletProvider. + * @returns A Promise that resolves to a new ZeroDevWalletProvider instance. + */ + public static async configureWithWallet( + config: ZeroDevWalletProviderConfig, + ): Promise { + if (!config.signer) { + throw new Error("Signer is required"); + } + + if (!config.projectId) { + throw new Error("ZeroDev project ID is required"); + } + const networkId = config.networkId || "base-sepolia"; + + const chain = NETWORK_ID_TO_VIEM_CHAIN[networkId]; + const bundlerRpc = `https://rpc.zerodev.app/api/v3/bundler/${config.projectId}`; + + // Create public client + const publicClient = createPublicClient({ + chain, + transport: http(), + }); + + // Create ECDSA validator + const entryPoint = getEntryPoint(config.entryPointVersion || "0.7"); + const ecdsaValidator = await signerToEcdsaValidator(publicClient, { + signer: config.signer as Signer, + entryPoint, + kernelVersion: KERNEL_V3_2, + }); + + // Create kernel account with intent executor + const kernelAccount = await createKernelAccount(publicClient, { + plugins: { + sudo: ecdsaValidator, + }, + entryPoint, + kernelVersion: KERNEL_V3_2, + address: config.address as Address | undefined, + initConfig: [installIntentExecutor(INTENT_V0_3)], + }); + + // Create intent client + const intentClient = await createIntentClient({ + account: kernelAccount, + chain, + bundlerTransport: http(bundlerRpc), + version: INTENT_V0_3, + }); + + return new ZeroDevWalletProvider( + config, + kernelAccount as SmartAccount, + intentClient, + ); + } + + /** + * Signs a message using the Kernel account. + * + * @param message - The message to sign. + * @returns The signed message. + */ + async signMessage(message: string | Uint8Array): Promise { + // Convert Uint8Array to string if needed + const messageStr = typeof message === "string" ? message : new TextDecoder().decode(message); + + return this.#kernelAccount.signMessage({ + message: messageStr, + }); + } + + /** + * Signs a typed data object using the Kernel 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.#kernelAccount.signTypedData(typedData); + } + + /** + * Signs a transaction using the Kernel account. + * + * @param _transaction - The transaction to sign. + * @returns The signed transaction. + */ + async signTransaction(_transaction: TransactionRequest): Promise { + throw new Error("signTransaction is not supported for ZeroDev Wallet Provider"); + } + + /** + * Sends a transaction using ZeroDev's Intent system. + * + * @param transaction - The transaction to send. + * @returns The hash of the transaction. + */ + async sendTransaction(transaction: TransactionRequest): Promise { + // Get the chain ID from the network + const chainId = parseInt(this.#network.chainId!); + + // Determine if this is a native token transfer + const isNativeTransfer = + transaction.value && + BigInt(transaction.value) > 0 && + (!transaction.data || transaction.data === "0x"); + + // For native token transfers, use ETH as the output token + if (isNativeTransfer) { + const intent = await this.#intentClient.sendUserIntent({ + calls: [ + { + to: transaction.to as Address, + value: BigInt(transaction.value || 0), + data: (transaction.data as Hex) || "0x", + }, + ], + outputTokens: [ + { + address: zeroAddress, + chainId, + amount: BigInt(transaction.value || 0), + }, + ], + }); + + const receipt = await this.#intentClient.waitForUserIntentExecutionReceipt({ + uiHash: intent.outputUiHash.uiHash, + }); + + return (receipt?.receipt.transactionHash as Hash) || "0x"; + } + + const intent = await this.#intentClient.sendUserIntent({ + calls: [ + { + to: transaction.to as Address, + value: BigInt(transaction.value || 0), + data: (transaction.data as Hex) || "0x", + }, + ], + chainId: chainId, + }); + + const receipt = await this.#intentClient.waitForUserIntentExecutionReceipt({ + uiHash: intent.outputUiHash.uiHash, + }); + + return (receipt?.receipt.transactionHash as Hash) || "0x"; + } + + /** + * Waits for a transaction receipt. + * + * @param txHash - The hash of the transaction to wait for. + * @returns The transaction receipt. + */ + async waitForTransactionReceipt(txHash: Hash): Promise { + return this.#publicClient.waitForTransactionReceipt({ hash: txHash }); + } + + /** + * 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); + } + + /** + * Gets the address of the wallet. + * + * @returns The address of the wallet. + */ + getAddress(): Address { + return this.#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 "zerodev_wallet_provider"; + } + + /** + * Gets the balance of the wallet. + * + * @returns The balance of the wallet in wei. + */ + async getBalance(): Promise { + return this.#publicClient.getBalance({ + address: this.#address as Address, + }); + } + + /** + * Transfer the native asset of the network. + * + * @param to - The destination address. + * @param value - The amount to transfer in whole units (e.g. ETH). + * @returns The transaction hash. + */ + async nativeTransfer(to: string, value: string): Promise { + // Convert value to wei (assuming value is in whole units) + const valueInWei = BigInt(parseFloat(value) * 10 ** 18); + + // Get the chain ID from the network + const chainId = parseInt(this.#network.chainId || "1"); + + const intent = await this.#intentClient.sendUserIntent({ + calls: [ + { + to: to as Address, + value: valueInWei, + data: "0x", + }, + ], + outputTokens: [ + { + address: zeroAddress, + chainId, + amount: valueInWei, + }, + ], + }); + + const receipt = await this.#intentClient.waitForUserIntentExecutionReceipt({ + uiHash: intent.outputUiHash.uiHash, + }); + + return receipt?.receipt.transactionHash || ""; + } + + /** + * Gets the ZeroDev Kernel account. + * + * @returns The ZeroDev Kernel account. + */ + getKernelAccount(): SmartAccount { + return this.#kernelAccount; + } + + /** + * Gets the ZeroDev Intent client. + * + * @returns The ZeroDev Intent client. + */ + getIntentClient(): Awaited> { + return this.#intentClient; + } + + /** + * Gets chain abstracted balance. + * + * @param options - The options for the get CAB. + * @returns The chain abstracted balance. + */ + async getCAB(options: GetCABParameters): Promise { + return this.#intentClient.getCAB(options); + } +} diff --git a/typescript/examples/langchain-zerodev-chatbot/.env-local b/typescript/examples/langchain-zerodev-chatbot/.env-local new file mode 100644 index 000000000..4e42d38a5 --- /dev/null +++ b/typescript/examples/langchain-zerodev-chatbot/.env-local @@ -0,0 +1,19 @@ +# OpenAI API Key +OPENAI_API_KEY= + +# CDP API Keys +CDP_API_KEY_NAME= +CDP_API_KEY_PRIVATE_KEY= + +# ZeroDev Project ID +ZERODEV_PROJECT_ID= + +# Optional: Network ID (defaults to base-mainnet if not set) +NETWORK_ID=base-mainnet + +# Optional: Private Key for Viem Wallet +PRIVATE_KEY= + +# Privy App ID and Secret +PRIVY_APP_ID= +PRIVY_APP_SECRET= \ No newline at end of file diff --git a/typescript/examples/langchain-zerodev-chatbot/.eslintrc.json b/typescript/examples/langchain-zerodev-chatbot/.eslintrc.json new file mode 100644 index 000000000..91571ba7a --- /dev/null +++ b/typescript/examples/langchain-zerodev-chatbot/.eslintrc.json @@ -0,0 +1,4 @@ +{ + "parser": "@typescript-eslint/parser", + "extends": ["../../.eslintrc.base.json"] +} diff --git a/typescript/examples/langchain-zerodev-chatbot/.prettierignore b/typescript/examples/langchain-zerodev-chatbot/.prettierignore new file mode 100644 index 000000000..20de531f4 --- /dev/null +++ b/typescript/examples/langchain-zerodev-chatbot/.prettierignore @@ -0,0 +1,7 @@ +docs/ +dist/ +coverage/ +.github/ +src/client +**/**/*.json +*.md diff --git a/typescript/examples/langchain-zerodev-chatbot/.prettierrc b/typescript/examples/langchain-zerodev-chatbot/.prettierrc new file mode 100644 index 000000000..ffb416b74 --- /dev/null +++ b/typescript/examples/langchain-zerodev-chatbot/.prettierrc @@ -0,0 +1,11 @@ +{ + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": false, + "trailingComma": "all", + "bracketSpacing": true, + "arrowParens": "avoid", + "printWidth": 100, + "proseWrap": "never" +} diff --git a/typescript/examples/langchain-zerodev-chatbot/README.md b/typescript/examples/langchain-zerodev-chatbot/README.md new file mode 100644 index 000000000..2b8a0d0df --- /dev/null +++ b/typescript/examples/langchain-zerodev-chatbot/README.md @@ -0,0 +1,98 @@ +# ZeroDev Wallet Provider with EVM Wallet Example + +This example demonstrates an agent setup using the ZeroDevWalletProvider with a EVM wallet as the signer. It shows how to: + +1. Initialize a Viem/PrivyEvm/CDP wallet provider +2. Use the EVM wallet as a signer for the ZeroDev wallet provider +3. Create an AgentKit instance with the ZeroDev wallet provider +4. Set up LangChain tools using the AgentKit instance +5. Create a LangChain agent that uses those tools +6. Run the agent in either chat mode or autonomous mode + +## Ask the agent to engage in the Web3 ecosystem! + +- "Transfer a portion of your ETH to a random address" +- "What is the price of BTC?" +- "Deploy an NFT that will go super viral!" +- "Deploy an ERC-20 token with total supply 1 billion" + +## Prerequisites + +### 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: + +```bash +node --version +``` + +If you don't have the correct version, you can install it using [nvm](https://github.com/nvm-sh/nvm): + +```bash +nvm install node +``` + +This will automatically install and use the latest version of Node. + +### API Keys + +You'll need the following API keys: +- [OpenAI API Key](https://platform.openai.com/docs/quickstart#create-and-export-an-api-key) - For the LLM +- [CDP API Key](https://portal.cdp.coinbase.com/access/api) - For the CDP wallet +- [ZeroDev Project ID](https://docs.zerodev.app/getting-started) - For the ZeroDev wallet provider + +Once you have them, rename the `.env-local` file to `.env` and make sure you set the API keys to their corresponding environment variables: + +- "OPENAI_API_KEY" +- "CDP_API_KEY_NAME" +- "CDP_API_KEY_PRIVATE_KEY" +- "ZERODEV_PROJECT_ID" + +## Running the example + +From the root directory, run: + +```bash +npm install +npm run build +``` + +This will install the dependencies and build the packages locally. The example uses the local `@coinbase/agentkit` and `@coinbase/agentkit-langchain` 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 example. + +Now from the `typescript/examples/langchain-zerodev-chatbot` directory, run: + +```bash +npm install +npm start +``` + +Select "1. chat mode" and start telling your Agent to do things onchain! + +## How it works + +The example demonstrates how to use a EVM wallet provider as the signer for a ZeroDev wallet provider. This allows you to leverage the benefits of both wallet providers: + +- CDP wallet provides secure key management through Coinbase's MPC infrastructure +- ZeroDev wallet provides account abstraction features like batched transactions, sponsored gas, and more + +The key part of the example is the configuration of the ZeroDev wallet provider with the privateKey Privy CDP wallet as the signer: + +```typescript +// Configure ZeroDev Wallet Provider with CDP Wallet as signer +const zeroDevConfig = { + signer: evmWalletProvider.toSigner(), + projectId: process.env.ZERODEV_PROJECT_ID!, + entryPointVersion: "0.7" as const, + // Use the same network as the CDP wallet + networkId: process.env.NETWORK_ID || "base-mainnet", +}; + +// Initialize ZeroDev Wallet Provider +const zeroDevWalletProvider = await ZeroDevWalletProvider.configureWithWallet(zeroDevConfig); +``` + +The agent is then initialized with the ZeroDev wallet provider, allowing it to use ZeroDev's account abstraction features while still leveraging CDP's secure key management. + +## License + +Apache-2.0 diff --git a/typescript/examples/langchain-zerodev-chatbot/chatbot.ts b/typescript/examples/langchain-zerodev-chatbot/chatbot.ts new file mode 100644 index 000000000..fbe432437 --- /dev/null +++ b/typescript/examples/langchain-zerodev-chatbot/chatbot.ts @@ -0,0 +1,365 @@ +import { + AgentKit, + CdpWalletProvider, + wethActionProvider, + walletActionProvider, + erc20ActionProvider, + erc721ActionProvider, + cdpApiActionProvider, + cdpWalletActionProvider, + pythActionProvider, + ViemWalletProvider, + NETWORK_ID_TO_VIEM_CHAIN, + EvmWalletProvider, + PrivyEvmWalletProvider, + zeroDevWalletActionProvider, + ZeroDevWalletProvider, +} from "@coinbase/agentkit"; +import { getLangChainTools } from "@coinbase/agentkit-langchain"; +import { HumanMessage } from "@langchain/core/messages"; +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"; +import { privateKeyToAccount } from "viem/accounts"; +import { type Hex, createWalletClient, http } from "viem"; +dotenv.config(); + +/** + * Validates that required environment variables are set + * + * @throws {Error} - If required environment variables are missing + * @returns {void} + */ +function validateEnvironment(): void { + const missingVars: string[] = []; + + // Check required variables + const requiredVars = [ + "OPENAI_API_KEY", + "ZERODEV_PROJECT_ID", + "CDP_API_KEY_NAME", + "CDP_API_KEY_PRIVATE_KEY", + ]; + + requiredVars.forEach(varName => { + if (!process.env[varName]) { + missingVars.push(varName); + } + }); + + // Exit if any required variables are missing + if (missingVars.length > 0) { + console.error("Error: Required environment variables are not set"); + missingVars.forEach(varName => { + console.error(`${varName}=your_${varName.toLowerCase()}_here`); + }); + process.exit(1); + } + + // Warn about optional NETWORK_ID + if (!process.env.NETWORK_ID) { + console.warn("Warning: NETWORK_ID not set, defaulting to base-mainnet testnet"); + } +} + +// 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"; + +/** + * Initialize the agent with ZeroDev Wallet Provider using CDP Wallet as signer + * + * @returns Agent executor and config + */ +async function initializeAgent() { + try { + // Initialize LLM + const llm = new ChatOpenAI({ + 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 + } + } + + // Get network ID from environment variable + const networkId = process.env.NETWORK_ID || "base-mainnet"; + + // Initialize Viem/CDP Wallet Provider + let evmWalletProvider: EvmWalletProvider; + let cdpWalletProvider: CdpWalletProvider | undefined; + + if (process.env.PRIVATE_KEY) { + // Configure Viem Wallet Provider + evmWalletProvider = new ViemWalletProvider( + createWalletClient({ + chain: NETWORK_ID_TO_VIEM_CHAIN[networkId], + account: privateKeyToAccount(process.env.PRIVATE_KEY as Hex), + transport: http(), + }), + ); + } else if (process.env.PRIVY_APP_ID && process.env.PRIVY_APP_SECRET) { + // Configure Privy Wallet Provider + const privyConfig = { + appId: process.env.PRIVY_APP_ID!, + appSecret: process.env.PRIVY_APP_SECRET!, + chainId: NETWORK_ID_TO_VIEM_CHAIN[networkId].id.toString(), + }; + evmWalletProvider = await PrivyEvmWalletProvider.configureWithWallet(privyConfig); + } else if (process.env.CDP_API_KEY_NAME && process.env.CDP_API_KEY_PRIVATE_KEY) { + // Configure CDP Wallet Provider + const cdpConfig = { + apiKeyName: process.env.CDP_API_KEY_NAME!, + apiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY!, + cdpWalletData: walletDataStr || undefined, + networkId: networkId, + }; + cdpWalletProvider = evmWalletProvider = + await CdpWalletProvider.configureWithWallet(cdpConfig); + } else { + throw new Error("No wallet provider configured"); + } + console.log(`EVM Wallet Address: ${evmWalletProvider.getAddress()}`); + + // Configure ZeroDev Wallet Provider with CDP Wallet as signer + const zeroDevConfig = { + signer: evmWalletProvider.toSigner(), + projectId: process.env.ZERODEV_PROJECT_ID!, + entryPointVersion: "0.7" as const, + // Use the same network as the CDP wallet + networkId: evmWalletProvider.getNetwork().networkId, + }; + + // Initialize ZeroDev Wallet Provider + const zeroDevWalletProvider = await ZeroDevWalletProvider.configureWithWallet(zeroDevConfig); + console.log(`ZeroDev Wallet Address: ${zeroDevWalletProvider.getAddress()}`); + + // Initialize AgentKit with ZeroDev Wallet Provider + const agentkit = await AgentKit.from({ + walletProvider: zeroDevWalletProvider, + actionProviders: [ + wethActionProvider(), + pythActionProvider(), + walletActionProvider(), + erc20ActionProvider(), + erc721ActionProvider(), + cdpApiActionProvider({ + apiKeyName: process.env.CDP_API_KEY_NAME!, + apiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY!, + }), + cdpWalletActionProvider({ + apiKeyName: process.env.CDP_API_KEY_NAME!, + apiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY!, + }), + zeroDevWalletActionProvider(), + ], + }); + + const tools = await getLangChainTools(agentkit); + + // Store buffered conversation history in memory + const memory = new MemorySaver(); + const agentConfig = { configurable: { thread_id: "ZeroDev CDP Wallet Example!" } }; + + // Create React Agent using the LLM and AgentKit tools + const agent = createReactAgent({ + llm, + tools, + checkpointSaver: memory, + messageModifier: ` + You are a helpful agent that can interact onchain using ZeroDev's account abstraction with a CDP wallet as the signer. + You are empowered to interact onchain using your tools. If you ever need funds, you can request them from the + faucet if you are on network ID 'base-sepolia'. If not, you can provide your wallet details and request + funds from the user. Before executing your first action, get the wallet details to see what network + you're on. If there is a 5XX (internal) HTTP error code, ask the user to try again later. + + You are using a ZeroDev wallet provider with a CDP wallet as the signer. This means you have the security + of CDP's MPC wallet combined with ZeroDev's account abstraction and chain abstraction features like batched transactions, + sponsored gas, and paying gas with USDC and USDT. + + Don't use **ERC20ActionProvider_get_balance** and don't check account's balance when transferring tokens and send the tx even if the balance is low. + If tx fail with "inputAmount is too low", ask the user to deposit token to the wallet. When reading the USDC/USDT/ETH balance of the wallet, use the **ZeroDevWalletActionProvider_getCAB** tool. + + Be concise and helpful with your responses. Refrain from restating your tools' descriptions unless it is + explicitly requested. + `, + }); + + // Save wallet data + if (cdpWalletProvider) { + const exportedWallet = await cdpWalletProvider.exportWallet(); + fs.writeFileSync(WALLET_DATA_FILE, JSON.stringify(exportedWallet)); + } + + return { agent, config: agentConfig }; + } catch (error) { + console.error("Failed to initialize agent:", error); + throw error; // Re-throw to be handled by caller + } +} + +/** + * Run the agent autonomously with specified intervals + * + * @param agent - The agent executor + * @param config - Agent configuration + * @param interval - Time interval between actions in seconds + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +async function runAutonomousMode(agent: any, config: any, interval = 10) { + console.log("Starting autonomous mode..."); + + // eslint-disable-next-line no-constant-condition + while (true) { + try { + const thought = + "Be creative and do something interesting on the blockchain. " + + "Choose an action or set of actions and execute it that highlights your abilities " + + "as a ZeroDev wallet with CDP signer."; + + const stream = await agent.stream({ messages: [new HumanMessage(thought)] }, config); + + for await (const chunk of stream) { + if ("agent" in chunk) { + console.log(chunk.agent.messages[0].content); + } else if ("tools" in chunk) { + console.log(chunk.tools.messages[0].content); + } + console.log("-------------------"); + } + + await new Promise(resolve => setTimeout(resolve, interval * 1000)); + } catch (error) { + if (error instanceof Error) { + console.error("Error:", error.message); + } + process.exit(1); + } + } +} + +/** + * Run the agent interactively based on user input + * + * @param agent - The agent executor + * @param config - Agent configuration + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +async function runChatMode(agent: any, config: any) { + console.log("Starting chat mode... Type 'exit' to end."); + + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + const question = (prompt: string): Promise => + new Promise(resolve => rl.question(prompt, resolve)); + + try { + // eslint-disable-next-line no-constant-condition + while (true) { + const userInput = await question("\nPrompt: "); + + if (userInput.toLowerCase() === "exit") { + break; + } + + const stream = await agent.stream({ messages: [new HumanMessage(userInput)] }, config); + + for await (const chunk of stream) { + if ("agent" in chunk) { + console.log(chunk.agent.messages[0].content); + } else if ("tools" in chunk) { + console.log(chunk.tools.messages[0].content); + } + console.log("-------------------"); + } + } + } catch (error) { + if (error instanceof Error) { + console.error("Error:", error.message); + } + process.exit(1); + } finally { + rl.close(); + } +} + +/** + * Choose whether to run in autonomous or chat mode based on user input + * + * @returns Selected mode + */ +async function chooseMode(): Promise<"chat" | "auto"> { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + const question = (prompt: string): Promise => + new Promise(resolve => rl.question(prompt, resolve)); + + // eslint-disable-next-line no-constant-condition + while (true) { + console.log("\nAvailable modes:"); + console.log("1. chat - Interactive chat mode"); + console.log("2. auto - Autonomous action mode"); + + const choice = (await question("\nChoose a mode (enter number or name): ")) + .toLowerCase() + .trim(); + + if (choice === "1" || choice === "chat") { + rl.close(); + return "chat"; + } else if (choice === "2" || choice === "auto") { + rl.close(); + return "auto"; + } + console.log("Invalid choice. Please try again."); + } +} + +/** + * Start the agent + */ +async function main() { + try { + console.log("Initializing ZeroDev Wallet with CDP Signer..."); + const { agent, config } = await initializeAgent(); + const mode = await chooseMode(); + + if (mode === "chat") { + await runChatMode(agent, config); + } else { + await runAutonomousMode(agent, config); + } + } catch (error) { + if (error instanceof Error) { + console.error("Error:", error.message); + } + process.exit(1); + } +} + +if (require.main === module) { + console.log("Starting Agent with ZeroDev Wallet (CDP Signer)..."); + main().catch(error => { + console.error("Fatal error:", error); + process.exit(1); + }); +} diff --git a/typescript/examples/langchain-zerodev-chatbot/package.json b/typescript/examples/langchain-zerodev-chatbot/package.json new file mode 100644 index 000000000..476c47db7 --- /dev/null +++ b/typescript/examples/langchain-zerodev-chatbot/package.json @@ -0,0 +1,35 @@ +{ + "name": "@coinbase/langchain-zerodev-chatbot-example", + "description": "ZeroDev Agentkit LangChain Extension Chatbot Example", + "version": "1.0.0", + "private": true, + "author": "Coinbase Inc.", + "license": "Apache-2.0", + "scripts": { + "start": "NODE_OPTIONS='--no-warnings' ts-node ./index.ts", + "dev": "nodemon ./index.ts", + "lint": "eslint -c .eslintrc.json *.ts", + "lint:fix": "eslint -c .eslintrc.json *.ts --fix", + "format": "prettier --write \"**/*.{ts,js,cjs,json,md}\"", + "format:check": "prettier -c .prettierrc --check \"**/*.{ts,js,cjs,json,md}\"" + }, + "dependencies": { + "@coinbase/agentkit": "workspace:*", + "@coinbase/agentkit-langchain": "workspace:*", + "@langchain/core": "^0.3.19", + "@langchain/openai": "^0.3.14", + "@langchain/langgraph": "^0.2.21", + "dotenv": "^16.4.5", + "zod": "^3.23.8", + "reflect-metadata": "^0.2.2" + }, + "devDependencies": { + "@types/node": "^18.11.18", + "nodemon": "^3.1.0", + "ts-node": "^10.9.2", + "typescript": "^5.7.2" + }, + "peerDependencies": { + "viem": "2.23.15" + } +} diff --git a/typescript/examples/langchain-zerodev-chatbot/tsconfig.json b/typescript/examples/langchain-zerodev-chatbot/tsconfig.json new file mode 100644 index 000000000..a37da3664 --- /dev/null +++ b/typescript/examples/langchain-zerodev-chatbot/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "preserveSymlinks": true, + "outDir": "./dist", + "rootDir": ".", + "module": "Node16" + }, + "include": ["*.ts"] +} diff --git a/typescript/pnpm-lock.yaml b/typescript/pnpm-lock.yaml index 42f162bce..9eae66619 100644 --- a/typescript/pnpm-lock.yaml +++ b/typescript/pnpm-lock.yaml @@ -86,6 +86,15 @@ importers: '@solana/web3.js': specifier: ^1.98.0 version: 1.98.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@zerodev/ecdsa-validator': + specifier: ^5.4.5 + version: 5.4.5(@zerodev/sdk@5.4.28(viem@2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2)))(viem@2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2)) + '@zerodev/intent': + specifier: ^0.0.24 + version: 0.0.24(@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)))(viem@2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2)) + '@zerodev/sdk': + specifier: ^5.4.28 + version: 5.4.28(viem@2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2)) bs58: specifier: ^4.0.1 version: 4.0.1 @@ -140,7 +149,7 @@ importers: version: 14.1.1 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)) + version: 29.7.0(@types/node@20.17.27)(ts-node@10.9.2(@types/node@20.17.27)(typescript@5.8.2)) mock-fs: specifier: ^5.2.0 version: 5.5.0 @@ -158,7 +167,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@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(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@20.17.27)(ts-node@10.9.2(@types/node@20.17.27)(typescript@5.8.2)))(typescript@5.8.2) tsd: specifier: ^0.31.2 version: 0.31.2 @@ -560,6 +569,49 @@ importers: specifier: ^5.3.3 version: 5.8.2 + examples/langchain-zerodev-chatbot: + dependencies: + '@coinbase/agentkit': + specifier: workspace:* + version: link:../../agentkit + '@coinbase/agentkit-langchain': + specifier: workspace:* + 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)) + '@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)) + '@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)) + dotenv: + specifier: ^16.4.5 + version: 16.4.7 + reflect-metadata: + specifier: ^0.2.2 + version: 0.2.2 + viem: + specifier: 2.23.15 + version: 2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2) + zod: + specifier: ^3.23.8 + version: 3.24.2 + devDependencies: + '@types/node': + specifier: ^18.11.18 + version: 18.19.83 + nodemon: + specifier: ^3.1.0 + version: 3.1.9 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@18.19.83)(typescript@5.8.2) + typescript: + specifier: ^5.7.2 + version: 5.8.2 + examples/model-context-protocol-smart-wallet-server: dependencies: '@coinbase/agentkit': @@ -1778,6 +1830,22 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@simplewebauthn/browser@8.3.7': + resolution: {integrity: sha512-ZtRf+pUEgOCvjrYsbMsJfiHOdKcrSZt2zrAnIIpfmA06r0FxBovFYq0rJ171soZbe13KmWzAoLKjSxVW7KxCdQ==} + + '@simplewebauthn/browser@9.0.1': + resolution: {integrity: sha512-wD2WpbkaEP4170s13/HUxPcAV5y4ZXaKo1TfNklS5zDefPinIgXOpgz1kpEvobAsaLPa2KeH7AKKX/od1mrBJw==} + + '@simplewebauthn/types@12.0.0': + resolution: {integrity: sha512-q6y8MkoV8V8jB4zzp18Uyj2I7oFp2/ONL8c3j8uT06AOWu3cIChc1au71QYHrP2b+xDapkGTiv+9lX7xkTlAsA==} + + '@simplewebauthn/types@9.0.1': + resolution: {integrity: sha512-tGSRP1QvsAvsJmnOlRQyw/mvK9gnPtjEc5fg2+m8n+QUa+D7rvrKkOYyfpy42GTs90X3RDOnqJgfHt+qO67/+w==} + + '@simplewebauthn/typescript-types@8.3.4': + resolution: {integrity: sha512-38xtca0OqfRVNloKBrFB5LEM6PN5vzFbJG6rAutPVrtGHFYxPdiV3btYWq0eAZAZmP+dqFPYJxJWeJrGfmYHng==} + deprecated: This package has been renamed to @simplewebauthn/types. Please install @simplewebauthn/types instead to ensure you receive future updates. + '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -2290,6 +2358,34 @@ packages: '@xmtp/proto@3.78.0': resolution: {integrity: sha512-JHkkvbdyqpWo1YOg88uOrc2GAsPnfXhIWaUKyIjXQdt4LltR4iAMgXcr1CFN+xjUmOBkAwwJ2JywUYv/uA4yZA==} + '@zerodev/ecdsa-validator@5.4.5': + resolution: {integrity: sha512-B9QsqqNZBevny+KzTQOiOGlFC1NreU1dlEW2ghZzCpYLVLgpQ7iOpRoOMmFyfsTVJoQQoSNEfgkoE9gzh2V8fw==} + peerDependencies: + '@zerodev/sdk': ^5.4.13 + viem: ^2.23.15 + + '@zerodev/intent@0.0.24': + resolution: {integrity: sha512-s/Isov82/bfIIUSUcG3ywB18gGj5sy3lu8Boa/JtcEdhldOI+7TC9x8Iv5ItYzIwPfwrd2oM9rE+Xen5Fu6Qwg==} + peerDependencies: + viem: ^2.21.40 + + '@zerodev/multi-chain-ecdsa-validator@5.4.4': + resolution: {integrity: sha512-+AWsgHehWlIpktiakoc2hgzybayf3FhLM0bonBv6F0FiQSjfE5tJO5Tmc0CEQTMlkgSyXrbkj7BbPKtpvhg3Tw==} + peerDependencies: + '@zerodev/sdk': ^5.4.0 + '@zerodev/webauthn-key': ^5.4.0 + viem: ^2.23.15 + + '@zerodev/sdk@5.4.28': + resolution: {integrity: sha512-gnD9gEmLUHy3nt8XfRJvGivu17XbwJdLzw8/keidr/stLqYpUJ/cWASrB48YeA1EJk0/Vy3Fd/tULi3sl7JUYA==} + peerDependencies: + viem: ^2.23.15 + + '@zerodev/webauthn-key@5.4.3': + resolution: {integrity: sha512-z2AKZMZjXL9psq3e8DgG7XS3FX/2rotNe0OXp2DnJq4TrDzhBm1xQ8AeIZOQYO4T7EceXGDYsWYIcQgT4uxHGQ==} + peerDependencies: + viem: ^2.23.15 + JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true @@ -2568,6 +2664,9 @@ packages: bl@5.1.0: resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==} + bn.js@4.11.6: + resolution: {integrity: sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==} + bn.js@4.12.1: resolution: {integrity: sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==} @@ -3385,6 +3484,9 @@ packages: eth-rpc-errors@4.0.3: resolution: {integrity: sha512-Z3ymjopaoft7JDoxZcEb3pwdGh7yiYMhOwm2doUt6ASXlMavpNlK6Cre0+IMl2VSGyEU9rkiperQhp5iRxn5Pg==} + ethereum-bloom-filters@1.2.0: + resolution: {integrity: sha512-28hyiE7HVsWubqhpVLVmZXFd4ITeHi+BUu05o9isf0GUpMtzBUi+8/gFrGaGYzvGAJQmJ3JKj77Mk9G98T84rA==} + ethereum-cryptography@2.2.1: resolution: {integrity: sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==} @@ -3392,6 +3494,10 @@ packages: resolution: {integrity: sha512-+knKNieu5EKRThQJWwqaJ10a6HE9sSehGeqWN65//wE7j47ZpFhKAnHB/JJFibwwg61I/koxaPsXbXpD/skNOQ==} engines: {node: '>=14.0.0'} + ethjs-unit@0.1.6: + resolution: {integrity: sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==} + engines: {node: '>=6.5.0', npm: '>=3'} + event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -3943,6 +4049,10 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-hex-prefixed@1.0.0: + resolution: {integrity: sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==} + engines: {node: '>=6.5.0', npm: '>=3'} + is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} @@ -4521,6 +4631,10 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + merkletreejs@0.3.11: + resolution: {integrity: sha512-LJKTl4iVNTndhL+3Uz/tfkjD0klIWsHlUzgtuNnNrsf7bAlXR30m+xYB7lHr5Z/l6e/yAIsr26Dabx6Buo4VGQ==} + engines: {node: '>= 7.6.0'} + merkletreejs@0.4.1: resolution: {integrity: sha512-W2VSHeGTdAnWtedee+pgGn7SHvncMdINnMeHAaXrfarSaMNLff/pm7RCr/QXYxN6XzJFgJZY+28ejO0lAosW4A==} engines: {node: '>= 7.6.0'} @@ -4783,6 +4897,10 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} + number-to-bn@1.7.0: + resolution: {integrity: sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==} + engines: {node: '>=6.5.0', npm: '>=3'} + nunjucks@3.2.4: resolution: {integrity: sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==} engines: {node: '>= 6.9.0'} @@ -5248,6 +5366,9 @@ packages: radix3@1.1.2: resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -5707,6 +5828,10 @@ packages: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} + strip-hex-prefix@1.0.0: + resolution: {integrity: sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==} + engines: {node: '>=6.5.0', npm: '>=3'} + strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -6227,6 +6352,9 @@ packages: resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==} engines: {node: '>=6.14.2'} + utf8@3.0.0: + resolution: {integrity: sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==} + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -6323,6 +6451,10 @@ packages: resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} engines: {node: '>= 14'} + web3-utils@1.10.4: + resolution: {integrity: sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A==} + engines: {node: '>=8.0.0'} + webextension-polyfill@0.10.0: resolution: {integrity: sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g==} @@ -7261,41 +7393,6 @@ 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 @@ -8088,6 +8185,20 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} + '@simplewebauthn/browser@8.3.7': + dependencies: + '@simplewebauthn/typescript-types': 8.3.4 + + '@simplewebauthn/browser@9.0.1': + dependencies: + '@simplewebauthn/types': 9.0.1 + + '@simplewebauthn/types@12.0.0': {} + + '@simplewebauthn/types@9.0.1': {} + + '@simplewebauthn/typescript-types@8.3.4': {} + '@sinclair/typebox@0.27.8': {} '@sinonjs/commons@3.0.1': @@ -8994,6 +9105,41 @@ snapshots: rxjs: 7.8.2 undici: 5.29.0 + '@zerodev/ecdsa-validator@5.4.5(@zerodev/sdk@5.4.28(viem@2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2)))(viem@2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2))': + dependencies: + '@zerodev/sdk': 5.4.28(viem@2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2)) + viem: 2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2) + + '@zerodev/intent@0.0.24(@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)))(viem@2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2))': + dependencies: + '@zerodev/ecdsa-validator': 5.4.5(@zerodev/sdk@5.4.28(viem@2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2)))(viem@2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2)) + '@zerodev/multi-chain-ecdsa-validator': 5.4.4(@zerodev/sdk@5.4.28(viem@2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2)))(@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)))(viem@2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2)) + '@zerodev/sdk': 5.4.28(viem@2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2)) + viem: 2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2) + transitivePeerDependencies: + - '@zerodev/webauthn-key' + + '@zerodev/multi-chain-ecdsa-validator@5.4.4(@zerodev/sdk@5.4.28(viem@2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2)))(@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)))(viem@2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2))': + dependencies: + '@simplewebauthn/browser': 9.0.1 + '@simplewebauthn/typescript-types': 8.3.4 + '@zerodev/sdk': 5.4.28(viem@2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2)) + '@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)) + merkletreejs: 0.3.11 + viem: 2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2) + + '@zerodev/sdk@5.4.28(viem@2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2))': + dependencies: + semver: 7.7.1 + viem: 2.23.15(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2) + + '@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 + '@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) + JSONStream@1.3.5: dependencies: jsonparse: 1.3.1 @@ -9307,6 +9453,8 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + bn.js@4.11.6: {} + bn.js@4.12.1: {} bn.js@5.2.1: {} @@ -9596,21 +9744,6 @@ 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-fetch@3.2.0: @@ -10281,6 +10414,10 @@ snapshots: dependencies: fast-safe-stringify: 2.1.1 + ethereum-bloom-filters@1.2.0: + dependencies: + '@noble/hashes': 1.7.1 + ethereum-cryptography@2.2.1: dependencies: '@noble/curves': 1.4.2 @@ -10301,6 +10438,11 @@ snapshots: - bufferutil - utf-8-validate + ethjs-unit@0.1.6: + dependencies: + bn.js: 4.11.6 + number-to-bn: 1.7.0 + event-target-shim@5.0.1: {} eventemitter2@6.4.9: {} @@ -10923,6 +11065,8 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-hex-prefixed@1.0.0: {} + is-hexadecimal@2.0.1: {} is-interactive@2.0.0: {} @@ -11155,25 +11299,6 @@ 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 @@ -11205,68 +11330,6 @@ 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 @@ -11494,18 +11557,6 @@ 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 - jiti@1.21.7: {} jose@4.15.9: {} @@ -11858,6 +11909,14 @@ snapshots: merge2@1.4.1: {} + merkletreejs@0.3.11: + dependencies: + bignumber.js: 9.1.2 + buffer-reverse: 1.0.1 + crypto-js: 4.2.0 + treeify: 1.1.0 + web3-utils: 1.10.4 + merkletreejs@0.4.1: dependencies: buffer-reverse: 1.0.1 @@ -12170,6 +12229,11 @@ snapshots: dependencies: path-key: 3.1.1 + number-to-bn@1.7.0: + dependencies: + bn.js: 4.11.6 + strip-hex-prefix: 1.0.0 + nunjucks@3.2.4(chokidar@3.6.0): dependencies: a-sync-waterfall: 1.0.1 @@ -12673,6 +12737,10 @@ snapshots: radix3@1.1.2: {} + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + range-parser@1.2.1: {} raw-body@3.0.0: @@ -13279,6 +13347,10 @@ snapshots: strip-final-newline@2.0.0: {} + strip-hex-prefix@1.0.0: + dependencies: + is-hex-prefixed: 1.0.0 + strip-indent@3.0.0: dependencies: min-indent: 1.0.1 @@ -13343,7 +13415,7 @@ snapshots: svix@1.62.0: dependencies: '@stablelib/base64': 1.0.1 - '@types/node': 22.13.13 + '@types/node': 22.13.14 es6-promise: 4.2.8 fast-sha256: 1.3.0 svix-fetch: 3.0.0 @@ -13487,25 +13559,23 @@ 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): + ts-node@10.9.2(@types/node@18.19.83)(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 + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 18.19.83 + acorn: 8.14.1 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.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) + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 ts-node@10.9.2(@types/node@20.17.27)(typescript@5.8.2): dependencies: @@ -13820,6 +13890,8 @@ snapshots: dependencies: node-gyp-build: 4.8.4 + utf8@3.0.0: {} + util-deprecate@1.0.2: {} util@0.12.5: @@ -13966,6 +14038,17 @@ snapshots: web-streams-polyfill@4.0.0-beta.3: {} + web3-utils@1.10.4: + dependencies: + '@ethereumjs/util': 8.1.0 + bn.js: 5.2.1 + ethereum-bloom-filters: 1.2.0 + ethereum-cryptography: 2.2.1 + ethjs-unit: 0.1.6 + number-to-bn: 1.7.0 + randombytes: 2.1.0 + utf8: 3.0.0 + webextension-polyfill@0.10.0: {} webidl-conversions@3.0.1: {}