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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ agentkit/
│ │ └── wallet-providers/
│ │ ├── cdp/
│ │ ├── privy/
| | ├── dynamic/
│ │ └── viem/
│ │ └── scripts/generate-action-provider/ # use this to create new actions
│ ├── create-onchain-agent/
Expand All @@ -156,6 +157,7 @@ agentkit/
│ ├── langchain-farcaster-chatbot/
│ ├── langchain-legacy-cdp-chatbot/
│ ├── langchain-privy-chatbot/
| ├── langchain-dynamic-chatbot/
│ ├── langchain-solana-chatbot/
│ ├── langchain-twitter-chatbot/
│ ├── langchain-xmtp-chatbot/
Expand Down Expand Up @@ -273,6 +275,7 @@ AgentKit is proud to have support for the following protocols, frameworks, walle
### Wallets

<a href="https://coinbase.com" target="_blank"><img src="./assets/wallets/coinbase.svg" width="100" height="auto" alt="Coinbase"></a>
<a href="https://dynamic.xyz" target="_blank"><img src="./assets/wallets/dynamic.svg" width="100" height="auto" alt="Dynamic"></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>

Expand Down
15 changes: 15 additions & 0 deletions assets/wallets/dynamic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions typescript/.changeset/small-banks-reply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"langchain-dynamic-chatbot": minor
"@coinbase/agentkit": minor
---

Adds Dynamic as wallet provider
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor -> patch
Adds -> Added

5 changes: 4 additions & 1 deletion typescript/agentkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,19 @@
"@alloralabs/allora-sdk": "^0.1.0",
"@coinbase/cdp-sdk": "^1.34.0",
"@coinbase/coinbase-sdk": "^0.20.0",
"@dynamic-labs-wallet/node-evm": "0.0.0-preview.160.0",
"@dynamic-labs-wallet/node-svm": "0.0.0-preview.160.0",
"@dynamic-labs-wallet/node": "0.0.0-preview.160.0",
"@jup-ag/api": "^6.0.39",
"@privy-io/public-api": "2.18.5",
"@privy-io/server-auth": "1.18.4",
"@solana/spl-token": "^0.4.12",
"@solana/web3.js": "^1.98.1",
"@zerodev/ecdsa-validator": "^5.4.5",
"@zerodev/intent": "^0.0.24",
"@zerodev/sdk": "^5.4.28",
"@zoralabs/coins-sdk": "^0.2.8",
"axios": "^1.9.0",
"@solana/web3.js": "^1.98.2",
"bs58": "^4.0.1",
"canonicalize": "^2.1.0",
"decimal.js": "^10.5.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import { DynamicEvmWalletProvider } from "./dynamicEvmWalletProvider";
import { DynamicEvmWalletClient } from "@dynamic-labs-wallet/node-evm";
import { ThresholdSignatureScheme } from "@dynamic-labs-wallet/node";
import type { Address, Hex } from "viem";
import { createWalletClient, http } from "viem";
import { getChain } from "../network/network";
import { createDynamicWallet } from "./dynamicShared";

jest.mock("@dynamic-labs-wallet/node-evm");
jest.mock("viem");
jest.mock("../network/network");
jest.mock("./dynamicShared");

const MOCK_ADDRESS = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e";
const MOCK_TRANSACTION_HASH = "0xef01";
const MOCK_SIGNATURE_HASH = "0x1234";

describe("DynamicEvmWalletProvider", () => {
const MOCK_CONFIG = {
authToken: "test-auth-token",
environmentId: "test-environment-id",
baseApiUrl: "https://app.dynamicauth.com",
baseMPCRelayApiUrl: "relay.dynamicauth.com",
chainId: "84532",
chainType: "ethereum" as const,
thresholdSignatureScheme: ThresholdSignatureScheme.TWO_OF_TWO,
};

const mockWallet = {
accountAddress: MOCK_ADDRESS,
publicKeyHex: "0x123",
};

const mockDynamicClient = {
createViemPublicClient: jest.fn().mockReturnValue({
getBalance: jest.fn(),
getTransactionCount: jest.fn(),
}),
signMessage: jest.fn().mockResolvedValue(MOCK_SIGNATURE_HASH),
exportPrivateKey: jest.fn().mockResolvedValue({ derivedPrivateKey: "0xprivate" }),
importPrivateKey: jest.fn().mockResolvedValue({
accountAddress: MOCK_ADDRESS,
publicKeyHex: "0x123",
}),
};

const mockWalletClient = {
account: {
address: MOCK_ADDRESS,
type: "json-rpc",
},
chain: {
id: 84532,
name: "Base Goerli",
rpcUrls: {
default: { http: ["https://goerli.base.org"] },
},
nativeCurrency: {
name: "Ether",
symbol: "ETH",
decimals: 18,
},
},
signMessage: jest.fn().mockResolvedValue(MOCK_SIGNATURE_HASH),
signTypedData: jest.fn().mockResolvedValue(MOCK_SIGNATURE_HASH),
signTransaction: jest.fn().mockResolvedValue(MOCK_SIGNATURE_HASH),
sendTransaction: jest.fn().mockResolvedValue(MOCK_TRANSACTION_HASH),
};

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

// Mock DynamicEvmWalletClient
(DynamicEvmWalletClient as jest.Mock).mockImplementation(() => mockDynamicClient);

// Mock getChain
(getChain as jest.Mock).mockReturnValue({
id: 84532,
name: "Base Goerli",
rpcUrls: {
default: { http: ["https://goerli.base.org"] },
},
nativeCurrency: {
name: "Ether",
symbol: "ETH",
decimals: 18,
},
});

// Mock createWalletClient
(createWalletClient as jest.Mock).mockReturnValue(mockWalletClient);

// Mock createDynamicWallet
(createDynamicWallet as jest.Mock).mockResolvedValue({
wallet: mockWallet,
dynamic: mockDynamicClient,
});
});

describe("configureWithWallet", () => {
it("should create a new wallet with Dynamic client", async () => {
const provider = await DynamicEvmWalletProvider.configureWithWallet(MOCK_CONFIG);

expect(createDynamicWallet).toHaveBeenCalledWith({
...MOCK_CONFIG,
chainType: "ethereum",
});

expect(mockDynamicClient.createViemPublicClient).toHaveBeenCalledWith({
chain: expect.any(Object),
});

expect(getChain).toHaveBeenCalledWith(MOCK_CONFIG.chainId);
expect(createWalletClient).toHaveBeenCalled();
});

it("should throw error when wallet creation fails", async () => {
(createDynamicWallet as jest.Mock).mockRejectedValue(new Error("Failed to create wallet"));

await expect(DynamicEvmWalletProvider.configureWithWallet(MOCK_CONFIG)).rejects.toThrow(
"Failed to create wallet",
);
});

it("should throw error when chain is not found", async () => {
(getChain as jest.Mock).mockReturnValue(null);

await expect(DynamicEvmWalletProvider.configureWithWallet(MOCK_CONFIG)).rejects.toThrow(
`Chain with ID ${MOCK_CONFIG.chainId} not found`,
);
});

it("should use default chain ID when not provided", async () => {
const { chainId, ...configWithoutChainId } = MOCK_CONFIG;

await DynamicEvmWalletProvider.configureWithWallet(configWithoutChainId);

expect(getChain).toHaveBeenCalledWith("84532");
});
});

describe("wallet methods", () => {
let provider: DynamicEvmWalletProvider;

beforeEach(async () => {
provider = await DynamicEvmWalletProvider.configureWithWallet(MOCK_CONFIG);
});

it("should get the wallet address", () => {
expect(provider.getAddress()).toBe(MOCK_ADDRESS);
});

it("should get the network information", () => {
expect(provider.getNetwork()).toEqual({
protocolFamily: "evm",
chainId: MOCK_CONFIG.chainId,
networkId: "base-sepolia",
});
});

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

it("should sign a message using Dynamic client", async () => {
const result = await provider.signMessage("Hello, world!");
expect(result).toBe(MOCK_SIGNATURE_HASH);
expect(mockDynamicClient.signMessage).toHaveBeenCalledWith({
message: "Hello, world!",
accountAddress: MOCK_ADDRESS,
});
});

it("should export private key", async () => {
const result = await provider.exportPrivateKey();
expect(result).toBe("0xprivate");
expect(mockDynamicClient.exportPrivateKey).toHaveBeenCalledWith({
accountAddress: MOCK_ADDRESS,
});
});

it("should import private key", async () => {
const result = await provider.importPrivateKey("0xprivate");
expect(result).toEqual({
accountAddress: MOCK_ADDRESS,
publicKeyHex: "0x123",
});
expect(mockDynamicClient.importPrivateKey).toHaveBeenCalledWith({
privateKey: "0xprivate",
chainName: "EVM",
thresholdSignatureScheme: "TWO_OF_TWO",
});
});

it("should export wallet information", async () => {
const result = await provider.exportWallet();
expect(result).toEqual({
walletId: MOCK_ADDRESS,
chainId: MOCK_CONFIG.chainId,
networkId: "base-sepolia",
});
});
});
});
Loading
Loading