Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions typescript/.changeset/loud-turtles-visit.md
Original file line number Diff line number Diff line change
@@ -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.
92 changes: 92 additions & 0 deletions typescript/agentkit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -411,6 +415,15 @@ const agent = createReactAgent({
</tr>
</table>
</details>
<details>
<summary><strong>ZeroDev Wallet</strong></summary>
<table width="100%">
<tr>
Comment thread
CarsonRoscoe marked this conversation as resolved.
<td width="200"><code>getCAB</code></td>
<td width="768">Retrieves chain abstracted balances (CAB) for specified tokens across multiple networks.</td>
</tr>
</table>
</details>

## Creating an Action Provider

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions typescript/agentkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions typescript/agentkit/src/action-providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ export * from "./allora";
export * from "./flaunch";
export * from "./onramp";
export * from "./vaultsfyi";
export * from "./zerodev";
54 changes: 54 additions & 0 deletions typescript/agentkit/src/action-providers/zerodev/README.md
Original file line number Diff line number Diff line change
@@ -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/).
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./zeroDevWalletActionProvider";
19 changes: 19 additions & 0 deletions typescript/agentkit/src/action-providers/zerodev/schemas.ts
Original file line number Diff line number Diff line change
@@ -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."),
}),
]);
Original file line number Diff line number Diff line change
@@ -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<ZeroDevWalletProvider>;

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<ZeroDevWalletProvider>;
});

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);
});
});
});
Loading