Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7ae3493
chore: implement privy delegated wallet provider for evm
njokuScript Mar 17, 2025
0f34809
chore: rename provider to be evm specific
njokuScript Mar 17, 2025
88c2f7c
add unused func to fix inheritance error
njokuScript Mar 17, 2025
f8802eb
chore: add tests for privy evm embedded wallet provider
njokuScript Mar 17, 2025
1004b44
update readme and changeset
njokuScript Mar 17, 2025
c07155a
fix onchain spelling
njokuScript Mar 17, 2025
b9fd76a
chore: Changed the parameter structure to accept a single config obje…
njokuScript Mar 18, 2025
2437b8b
chore: export privy client from shared privy class
njokuScript Mar 18, 2025
b025494
chore: fix native assets transfer
njokuScript Mar 19, 2025
da79528
uncomment analytics event
njokuScript Mar 19, 2025
545a85c
remove logs and use viem's Adress type
njokuScript Mar 24, 2025
caa9717
use viem's Hex type
njokuScript Mar 24, 2025
2d8f074
lookup chain ID using global NETWORK_ID_TO_CHAIN_ID
njokuScript Mar 24, 2025
d99dd89
remove unused chainid mapping fnc
njokuScript Mar 24, 2025
ca0666d
fix(wallet-providers): Fix type casting in PrivyWalletProvider
njokuScript Mar 24, 2025
9716235
remove unnecessary comments
njokuScript Mar 24, 2025
580b619
delete lock files
njokuScript Mar 25, 2025
0ab2b60
chore: remove logs
njokuScript Mar 26, 2025
df143be
chore: await response from privy provider variant
njokuScript Mar 26, 2025
2321067
chore: rename spelling to privyEvmDelegatedEmbeddedWalletProvider
njokuScript Mar 26, 2025
41a1036
udpate chatbot example to use new naming and add env.local file
njokuScript Mar 26, 2025
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
13 changes: 13 additions & 0 deletions typescript/.changeset/privy-evm-embedded-wallet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@coinbase/agentkit": minor
---

Add support for Privy Evm embedded wallets with delegation.

This change introduces a new wallet provider, `PrivyEvmEmbeddedWalletProvider`, which allows AgentKit to use Privy's embedded wallets that have been delegated to a server. This enables autonomous agents to perform onchain actions on behalf of users who have delegated transaction signing authority to the agent.

Key changes:
- Add `PrivyEvmEmbeddedWalletProvider` class extending the `EvmWalletProvider` base class
- Update the `PrivyWalletProvider` factory to support embedded wallets via a new `walletType` option
- Add comprehensive test coverage for the new provider
- Add documentation on how to use the embedded wallet provider
58 changes: 51 additions & 7 deletions typescript/agentkit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -624,24 +624,61 @@ const walletProvider = new ViemWalletProvider(client, {

### PrivyWalletProvider

The `PrivyWalletProvider` is a wallet provider that uses [Privy Server Wallets](https://docs.privy.io/guide/server-wallets/). This implementation extends the `ViemWalletProvider`.
The `PrivyWalletProvider` is a wallet provider that uses [Privy Server Wallets](https://docs.privy.io/guide/server-wallets/) or [Privy Embedded Wallets](https://docs.privy.io/guide/embedded-wallets/). This implementation extends the `EvmWalletProvider`.

#### Server Wallet Configuration

```typescript
import { PrivyWalletProvider, PrivyWalletConfig } from "@coinbase/agentkit";
import { PrivyWalletProvider } from "@coinbase/agentkit";

// Configure Wallet Provider
const config: PrivyWalletConfig = {
// Configure Server Wallet Provider
const config = {
appId: "PRIVY_APP_ID",
appSecret: "PRIVY_APP_SECRET",
chainId: "84532", // base-sepolia
walletId: "PRIVY_WALLET_ID", // optional, otherwise a new wallet will be created
authorizationPrivateKey: PRIVY_WALLET_AUTHORIZATION_PRIVATE_KEY, // optional, required if your account is using authorization keys
authorizationKeyId: PRIVY_WALLET_AUTHORIZATION_KEY_ID, // optional, only required to create a new wallet if walletId is not provided
authorizationPrivateKey: "PRIVY_WALLET_AUTHORIZATION_PRIVATE_KEY", // optional, required if your account is using authorization keys
authorizationKeyId: "PRIVY_WALLET_AUTHORIZATION_KEY_ID", // optional, only required to create a new wallet if walletId is not provided
};

const walletProvider = await PrivyWalletProvider.configureWithWallet(config);
```

#### Delegated Embedded Wallet Configuration

You can also use Privy's embedded wallets with delegation for agent actions. This allows your agent to use wallets that have been delegated transaction signing authority by users.

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

// Configure Embedded Wallet Provider
const config = {
appId: "PRIVY_APP_ID",
appSecret: "PRIVY_APP_SECRET",
authorizationPrivateKey: "PRIVY_WALLET_AUTHORIZATION_PRIVATE_KEY",
walletId: "PRIVY_DELEGATED_WALLET_ID", // The ID of the wallet that was delegated to your server
networkId: "base-mainnet", // or any supported network
walletType: "embedded" // Specify "embedded" to use the embedded wallet provider
};

const walletProvider = await PrivyWalletProvider.configureWithWallet(config);
```

### Prerequisites

Before using this wallet provider, you need to:

1. Set up Privy in your application
2. Enable server delegated actions
3. Have users delegate permissions to your server
4. Obtain the delegated wallet ID

For more information on setting up Privy and enabling delegated actions, see [Privy's documentation](https://docs.privy.io/guide/embedded/server-delegated-actions).

### Supported Operations

The `PrivyEvmEmbeddedWalletProvider` supports all standard wallet operations including transaction signing, message signing, and native transfers, using the wallet that was delegated to your server.

#### Authorization Keys

Privy offers the option to use authorization keys to secure your server wallets.
Expand All @@ -657,12 +694,19 @@ The `PrivyWalletProvider` can export wallet information by calling the `exportWa
```typescript
const walletData = await walletProvider.exportWallet();

// walletData will be in the following format:
// For server wallets, walletData will be in the following format:
{
walletId: string;
authorizationKey: string | undefined;
chainId: string | undefined;
}

// For embedded wallets, walletData will be in the following format:
{
walletId: string;
networkId: string;
chainId: string | undefined;
}
```

### SmartWalletProvider
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 @@ -8,3 +8,4 @@ export * from "./solanaKeypairWalletProvider";
export * from "./privyWalletProvider";
export * from "./privyEvmWalletProvider";
export * from "./privySvmWalletProvider";
export * from "./privyEvmDelegatedEmbeddedWalletProvider";
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import axios from "axios";
import { PrivyEvmDelegatedEmbeddedWalletProvider } from "./privyEvmDelegatedEmbeddedWalletProvider";

// Mock axios
jest.mock("axios");
const mockedAxios = axios as jest.Mocked<typeof axios>;

describe("PrivyEvmDelegatedEmbeddedWalletProvider", () => {
const mockConfig = {
appId: "test-app-id",
appSecret: "test-app-secret",
authorizationPrivateKey: "test-auth-key",
walletId: "test-wallet-id",
networkId: "base-sepolia",
};

const mockWalletAddress = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266";

beforeEach(() => {
jest.clearAllMocks();

// Mock the wallet fetch API call
mockedAxios.get.mockResolvedValue({
data: {
address: mockWalletAddress,
},
});

// Mock all other API calls
mockedAxios.post.mockResolvedValue({
data: {
data: {
signature: "0xsignature",
hash: "0xhash",
result: "0x1000",
},
},
});
});

describe("configureWithWallet", () => {
it("should fetch wallet address and create provider", async () => {
const provider =
await PrivyEvmDelegatedEmbeddedWalletProvider.configureWithWallet(mockConfig);

expect(mockedAxios.get).toHaveBeenCalledWith(
`https://api.privy.io/v1/wallets/${mockConfig.walletId}`,
expect.any(Object),
);

expect(provider.getAddress()).toBe(mockWalletAddress);
expect(provider.getNetwork().networkId).toBe(mockConfig.networkId);
expect(provider.getName()).toBe("privy_embedded_wallet_provider");
});

it("should throw an error if wallet address cannot be fetched", async () => {
mockedAxios.get.mockResolvedValueOnce({ data: {} });

await expect(
PrivyEvmDelegatedEmbeddedWalletProvider.configureWithWallet(mockConfig),
).rejects.toThrow("Could not find wallet address for wallet ID");
});
});

describe("wallet operations", () => {
let provider: PrivyEvmDelegatedEmbeddedWalletProvider;

beforeEach(async () => {
provider = await PrivyEvmDelegatedEmbeddedWalletProvider.configureWithWallet(mockConfig);
});

it("should sign messages", async () => {
const signature = await provider.signMessage("Hello, world!");

expect(mockedAxios.post).toHaveBeenCalledWith(
"https://api.privy.io/v1/wallets/rpc",
expect.objectContaining({
address: mockWalletAddress,
chain_type: "ethereum",
method: "personal_sign",
params: expect.objectContaining({
message: "Hello, world!",
}),
}),
expect.any(Object),
);

expect(signature).toBe("0xsignature");
});

it("should send transactions", async () => {
const transaction = {
to: "0xrecipient" as `0x${string}`,
value: BigInt(1000),
data: "0xdata" as `0x${string}`,
};

const hash = await provider.sendTransaction(transaction);

expect(mockedAxios.post).toHaveBeenCalledWith(
"https://api.privy.io/v1/wallets/rpc",
expect.objectContaining({
address: mockWalletAddress,
chain_type: "ethereum",
method: "eth_sendTransaction",
params: expect.objectContaining({
transaction: expect.objectContaining({
to: transaction.to,
value: transaction.value,
data: transaction.data,
}),
}),
}),
expect.any(Object),
);

expect(hash).toBe("0xhash");
});

it("should get balance", async () => {
const balance = await provider.getBalance();

expect(mockedAxios.post).toHaveBeenCalledWith(
"https://api.privy.io/v1/wallets/rpc",
expect.objectContaining({
address: mockWalletAddress,
chain_type: "ethereum",
method: "eth_getBalance",
params: [mockWalletAddress, "latest"],
}),
expect.any(Object),
);

expect(balance).toBe(BigInt("0x1000"));
});

it("should perform native transfers", async () => {
const hash = await provider.nativeTransfer("0xrecipient", "1000");

expect(mockedAxios.post).toHaveBeenCalledWith(
"https://api.privy.io/v1/wallets/rpc",
expect.objectContaining({
address: mockWalletAddress,
chain_type: "ethereum",
method: "eth_sendTransaction",
params: expect.objectContaining({
transaction: expect.objectContaining({
to: "0xrecipient",
value: "0x3e8", // hex for 1000
}),
}),
}),
expect.any(Object),
);

expect(hash).toBe("0xhash");
});

it("should export wallet data", () => {
const walletData = provider.exportWallet();

expect(walletData).toEqual({
walletId: mockConfig.walletId,
networkId: mockConfig.networkId,
chainId: expect.any(String),
});
});
});
});
Loading