Skip to content
Open
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
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,8 @@ agentkit/
│ │ └── wallet-providers/
│ │ ├── cdp/
│ │ ├── privy/
│ │ └── viem/
│ │ ├── viem/
│ │ └── waap/
│ ├── create-onchain-agent/
│ ├── framework-extensions/
│ │ ├── langchain/
Expand All @@ -158,10 +159,12 @@ agentkit/
│ ├── langchain-privy-chatbot/
│ ├── langchain-solana-chatbot/
│ ├── langchain-twitter-chatbot/
│ ├── langchain-waap-chatbot/
│ ├── langchain-xmtp-chatbot/
│ ├── langchain-zerodev-chatbot/
│ ├── model-context-protocol-smart-wallet-server/
│ └── vercel-ai-sdk-smart-wallet-chatbot/
│ └── vercel-ai-sdk-waap-chatbot/
├── python/
│ ├── coinbase-agentkit/
│ │ └── coinbase_agentkit/
Expand Down Expand Up @@ -279,6 +282,7 @@ AgentKit is proud to have support for the following protocols, frameworks, walle
<a href="https://coinbase.com" target="_blank"><img src="./assets/wallets/coinbase.svg" width="100" height="auto" alt="Coinbase"></a>
<a href="https://privy.io" target="_blank"><img src="./assets/wallets/privy.svg" width="100" height="auto" alt="Privy"></a>
<a href="https://viem.sh" target="_blank"><img src="./assets/wallets/viem.svg" width="100" height="auto" alt="ViEM"></a>
<a href="https://waap.xyz" target="_blank"><img src="./assets/wallets/waap.svg" width="100" height="auto" alt="WaaP"></a>

### Protocols

Expand Down
56 changes: 56 additions & 0 deletions assets/wallets/waap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions typescript/.changeset/add-waap-wallet-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@coinbase/agentkit": patch
---

Added WaaP wallet provider with 2PC split-custody key management
19 changes: 19 additions & 0 deletions typescript/agentkit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ AgentKit is a framework for easily enabling AI agents to take actions onchain. I
- [Configuring from CdpWalletProvider](#configuring-from-cdpwalletprovider)
- [Configuring from PrivyWalletProvider](#configuring-from-privywalletprovider)
- [Configuring from ViemWalletProvider](#configuring-from-viemwalletprovider)
- [WaapWalletProvider](#waapwalletprovider)
- [SVM Wallet Providers](#svm-wallet-providers)
- [CdpV2SolanaWalletProvider](#cdpv2solanawalletprovider)
- [Basic Configuration](#basic-configuration-2)
Expand Down Expand Up @@ -981,6 +982,7 @@ EVM:
- [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)
- [WaapWalletProvider](https://github.com/coinbase/agentkit/blob/main/typescript/agentkit/src/wallet-providers/waapWalletProvider.ts)

### CdpEvmWalletProvider

Expand Down Expand Up @@ -1417,6 +1419,23 @@ const walletProvider = await ZeroDevWalletProvider.configureWithWallet({
});
```

### WaapWalletProvider

The `WaapWalletProvider` is an EVM wallet provider that uses the `waap-cli` binary. WaaP (Wallet as a Protocol) manages your private keys securely using two-party computation on the server-side, meaning that raw private keys never hit your local environment. The provider shells out to the `waap-cli` executable for all signing operations.

```typescript
import { WaapWalletProvider } from "@coinbase/agentkit";

// Configures the wallet synchronously and logs in the CLI.
// Requires waap-cli to be installed and available in the local PATH.
const walletProvider = WaapWalletProvider.configureWithWallet({
email: "your_email@example.com", // Optional, for auto-login
password: "password", // Optional, for auto-login
chainId: "11155111", // e.g., Ethereum Sepolia (11155111)
rpcUrl: "https://ethereum-sepolia-rpc.publicnode.com", // Optional overridable RPC node URL
});
```

## SVM Wallet Providers

Wallet providers give an agent access to a wallet. AgentKit currently supports the following wallet providers:
Expand Down
1 change: 1 addition & 0 deletions typescript/agentkit/src/wallet-providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export * from "./privyEvmWalletProvider";
export * from "./privySvmWalletProvider";
export * from "./privyEvmDelegatedEmbeddedWalletProvider";
export * from "./zeroDevWalletProvider";
export * from "./waapWalletProvider";
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
/**
* Integration tests for WaapWalletProvider.
*
* These tests require a real waap-cli installation and valid credentials.
* They are skipped by default. To run them, set the following environment
* variables and remove the `.skip` from each `describe` block:
*
* WAAP_CLI_PATH - Path to the waap-cli binary (default: "waap-cli")
* WAAP_EMAIL - WaaP account email
* WAAP_PASSWORD - WaaP account password
* WAAP_CHAIN_ID - EVM chain ID (default: "84532" for Base Sepolia)
* WAAP_RPC_URL - RPC URL for the chain (optional)
*
* Run:
* WAAP_EMAIL=you@example.com WAAP_PASSWORD=secret
* npx jest --testMatch "**\/*.integration.test.ts" --no-cache
*/

import { WaapWalletProvider, WaapWalletProviderConfig } from "./waapWalletProvider";

const WAAP_CLI_PATH = process.env.WAAP_CLI_PATH ?? "waap-cli";
const WAAP_EMAIL = process.env.WAAP_EMAIL;
const WAAP_PASSWORD = process.env.WAAP_PASSWORD;
const WAAP_CHAIN_ID = process.env.WAAP_CHAIN_ID ?? "84532";
const WAAP_RPC_URL = process.env.WAAP_RPC_URL;

const canRun = Boolean(WAAP_EMAIL && WAAP_PASSWORD);

const describeIntegration = canRun ? describe : describe.skip;

describeIntegration("WaapWalletProvider integration", () => {
let provider: WaapWalletProvider;

const config: WaapWalletProviderConfig = {
cliPath: WAAP_CLI_PATH,
chainId: WAAP_CHAIN_ID,
rpcUrl: WAAP_RPC_URL,
email: WAAP_EMAIL,
password: WAAP_PASSWORD,
};

// =========================================================
// authentication & setup
// =========================================================

describe("authentication & setup", () => {
it("should log in and create a provider via configureWithWallet", () => {
provider = WaapWalletProvider.configureWithWallet(config);
expect(provider).toBeInstanceOf(WaapWalletProvider);
});
});

// =========================================================
// wallet identity
// =========================================================

describe("wallet identity", () => {
beforeAll(() => {
provider = WaapWalletProvider.configureWithWallet(config);
});

it("should return a valid EVM address from getAddress", () => {
const address = provider.getAddress();
expect(address).toMatch(/^0x[0-9a-fA-F]{40}$/);
});

it("should return the correct network", () => {
const network = provider.getNetwork();
expect(network.protocolFamily).toBe("evm");
expect(network.chainId).toBe(WAAP_CHAIN_ID);
});

it("should return the provider name", () => {
expect(provider.getName()).toBe("waap_wallet_provider");
});
});

// =========================================================
// balance
// =========================================================

describe("balance", () => {
beforeAll(() => {
provider = WaapWalletProvider.configureWithWallet(config);
});

it("should fetch balance (>= 0)", async () => {
const balance = await provider.getBalance();
expect(balance).toBeGreaterThanOrEqual(BigInt(0));
});
});

// =========================================================
// signing operations
// =========================================================

describe("signing", () => {
beforeAll(() => {
provider = WaapWalletProvider.configureWithWallet(config);
});

it("should sign a message and return a valid signature", async () => {
const signature = await provider.signMessage("Hello from AgentKit integration test");
expect(signature).toMatch(/^0x[0-9a-fA-F]+$/);
// ECDSA signature is 65 bytes = 130 hex chars + "0x" prefix
expect(signature.length).toBe(132);
});

it("should sign typed data (EIP-712)", async () => {
const typedData = {
domain: {
name: "AgentKit Integration Test",
version: "1",
chainId: Number(WAAP_CHAIN_ID),
},
types: {
Greeting: [{ name: "text", type: "string" }],
},
primaryType: "Greeting",
message: { text: "Hello" },
};

const signature = await provider.signTypedData(typedData);
expect(signature).toMatch(/^0x[0-9a-fA-F]+$/);
expect(signature.length).toBe(132);
});

it("should sign a raw hash", async () => {
const hash =
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" as `0x${string}`;
const signature = await provider.sign(hash);
expect(signature).toMatch(/^0x[0-9a-fA-F]+$/);
});
});

// =========================================================
// transaction flow (testnet only)
// =========================================================

describe("transaction flow", () => {
beforeAll(() => {
provider = WaapWalletProvider.configureWithWallet(config);
});

it("should sign a transaction without broadcasting", async () => {
const address = provider.getAddress() as `0x${string}`;
const signedTx = await provider.signTransaction({
to: address, // self-transfer
value: BigInt(0),
});
expect(signedTx).toMatch(/^0x[0-9a-fA-F]+$/);
});

// This test sends a real 0-value transaction on testnet.
// It requires the wallet to have gas funds on the configured chain.
it(
"should send a 0-value self-transfer and get a receipt",
async () => {
const address = provider.getAddress() as `0x${string}`;
const balance = await provider.getBalance();

// Skip if wallet has no gas
if (balance === BigInt(0)) {
console.warn("Skipping send-tx test: wallet has no balance for gas.");
return;
}

const txHash = await provider.sendTransaction({
to: address,
value: BigInt(0),
});
expect(txHash).toMatch(/^0x[0-9a-fA-F]{64}$/);

const receipt = await provider.waitForTransactionReceipt(txHash);
expect(receipt).toBeDefined();
expect(receipt.transactionHash).toBe(txHash);
},
60_000,
);
});

// =========================================================
// multi-agent isolation
// =========================================================

describe("multi-agent isolation via + email notation", () => {
it("should create a separate wallet for a + alias email", () => {
if (!WAAP_EMAIL) return;
const [localPart, domain] = WAAP_EMAIL.split("@");
const agentEmail = `${localPart}+agent-integration-test@${domain}`;

try {
const agentProvider = WaapWalletProvider.configureWithWallet({
...config,
email: agentEmail,
});

const mainAddress = provider.getAddress();
const agentAddress = agentProvider.getAddress();

// Each + alias gets its own wallet, so addresses should differ
expect(agentAddress).toMatch(/^0x[0-9a-fA-F]{40}$/);
expect(agentAddress).not.toBe(mainAddress);
} catch (error) {
// Skip if the + alias account is not registered on the server
const msg = (error as Error).message || "";
if (msg.includes("401") || msg.includes("Invalid email")) {
console.warn("Skipping: + alias account not registered on server");
return;
}
throw error;
}
});
});

// =========================================================
// contract reads
// =========================================================

describe("contract reads", () => {
beforeAll(() => {
provider = WaapWalletProvider.configureWithWallet(config);
});

it("should return a PublicClient for read-only operations", () => {
const client = provider.getPublicClient();
expect(client).toBeDefined();
});
});
});
Loading
Loading