@coinbase/agentkit
Version:
Coinbase AgentKit core primitives
288 lines (287 loc) • 12.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const cdp_sdk_1 = require("@coinbase/cdp-sdk");
const cdpSmartWalletProvider_1 = require("./cdpSmartWalletProvider");
// =========================================================
// consts
// =========================================================
const mockPublicClient = {
readContract: jest.fn(),
getBalance: jest.fn(),
};
// =========================================================
// mocks
// =========================================================
jest.mock("../analytics", () => ({
sendAnalyticsEvent: jest.fn().mockImplementation(() => Promise.resolve()),
}));
jest.mock("../../package.json", () => ({
version: "1.0.0",
}));
jest.mock("viem", () => {
return {
createPublicClient: jest.fn(() => mockPublicClient),
http: jest.fn(),
};
});
jest.mock("../network", () => {
return {
NETWORK_ID_TO_CHAIN_ID: {
"base-mainnet": "8453",
"base-sepolia": "84532",
},
NETWORK_ID_TO_VIEM_CHAIN: {
"base-mainnet": { id: 8453 },
"base-sepolia": { id: 84532 },
},
};
});
// Mock CdpClient
jest.mock("@coinbase/cdp-sdk", () => {
const MOCK_ADDRESS = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e";
const MOCK_SMART_ADDRESS = "0x1234567890123456789012345678901234567890";
const MOCK_SIGNATURE = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b01";
const MOCK_USER_OP_HASH = "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890";
const mockCreateAccount = jest.fn().mockImplementation(() => Promise.resolve({
address: MOCK_ADDRESS,
signMessage: jest.fn().mockResolvedValue(MOCK_SIGNATURE),
signTypedData: jest.fn().mockResolvedValue(MOCK_SIGNATURE),
}));
const mockGetSmartAccount = jest.fn().mockImplementation(() => Promise.resolve({
address: MOCK_SMART_ADDRESS,
}));
const mockCreateSmartAccount = jest.fn().mockImplementation(() => Promise.resolve({
address: MOCK_SMART_ADDRESS,
}));
const mockGetOrCreateSmartAccount = jest.fn().mockImplementation(() => Promise.resolve({
address: MOCK_SMART_ADDRESS,
}));
const mockSendUserOperation = jest
.fn()
.mockImplementation(async () => ({ userOpHash: MOCK_USER_OP_HASH }));
const mockEvmClient = {
createAccount: mockCreateAccount,
getAccount: jest.fn(),
createSmartAccount: mockCreateSmartAccount,
getOrCreateSmartAccount: mockGetOrCreateSmartAccount,
getSmartAccount: mockGetSmartAccount,
sendUserOperation: mockSendUserOperation,
};
return {
CdpClient: jest.fn().mockImplementation(() => ({
evm: mockEvmClient,
})),
EvmServerAccount: jest.fn().mockImplementation(() => ({
address: MOCK_ADDRESS,
signMessage: jest.fn().mockResolvedValue(MOCK_SIGNATURE),
signTypedData: jest.fn().mockResolvedValue(MOCK_SIGNATURE),
})),
EvmSmartAccount: jest.fn().mockImplementation(() => ({
address: MOCK_SMART_ADDRESS,
})),
};
});
// =========================================================
// test constants
// =========================================================
const MOCK_ADDRESS = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e";
const MOCK_SMART_ADDRESS = "0x1234567890123456789012345678901234567890";
const MOCK_NETWORK_ID = "base-sepolia";
const MOCK_USER_OP_HASH = "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890";
const MOCK_SIGNATURE = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b01";
const MOCK_BALANCE = 1000000000000000000n;
const MOCK_NETWORK = {
protocolFamily: "evm",
networkId: MOCK_NETWORK_ID,
chainId: "84532",
};
describe("CdpSmartWalletProvider", () => {
let provider;
let mockCdpClient;
let mockSmartAccount;
let mockOwnerAccount;
beforeEach(async () => {
jest.clearAllMocks();
mockCdpClient = new cdp_sdk_1.CdpClient({
apiKeyId: "test-key-id",
apiKeySecret: "test-key-secret",
walletSecret: "test-wallet-secret",
});
mockOwnerAccount = {
address: MOCK_ADDRESS,
signMessage: jest.fn().mockResolvedValue(MOCK_SIGNATURE),
signTypedData: jest.fn().mockResolvedValue(MOCK_SIGNATURE),
};
mockSmartAccount = {
address: MOCK_SMART_ADDRESS,
};
// Set up the mock smart account for the provider
mockCdpClient.evm.createAccount.mockResolvedValue(mockOwnerAccount);
mockCdpClient.evm.createSmartAccount.mockResolvedValue(mockSmartAccount);
mockPublicClient.readContract.mockResolvedValue("mock_result");
mockPublicClient.getBalance.mockResolvedValue(MOCK_BALANCE);
provider = await cdpSmartWalletProvider_1.CdpSmartWalletProvider.configureWithWallet({
apiKeyId: "test-key-id",
apiKeySecret: "test-key-secret",
walletSecret: "test-wallet-secret",
networkId: MOCK_NETWORK_ID,
});
});
// =========================================================
// initialization tests
// =========================================================
describe("initialization", () => {
it("should initialize with API keys", async () => {
const provider = await cdpSmartWalletProvider_1.CdpSmartWalletProvider.configureWithWallet({
apiKeyId: "test-key-id",
apiKeySecret: "test-key-secret",
walletSecret: "test-wallet-secret",
networkId: MOCK_NETWORK_ID,
});
expect(provider.getAddress()).toBe(MOCK_SMART_ADDRESS);
expect(provider.getNetwork()).toEqual(MOCK_NETWORK);
});
it("should throw error for non-Base networks", async () => {
await expect(cdpSmartWalletProvider_1.CdpSmartWalletProvider.configureWithWallet({
apiKeyId: "test-key-id",
apiKeySecret: "test-key-secret",
walletSecret: "test-wallet-secret",
networkId: "ethereum-sepolia",
})).rejects.toThrow("Smart wallets are only supported on Base networks");
});
it("should create smart account with name", async () => {
await cdpSmartWalletProvider_1.CdpSmartWalletProvider.configureWithWallet({
apiKeyId: "test-key-id",
apiKeySecret: "test-key-secret",
walletSecret: "test-wallet-secret",
networkId: MOCK_NETWORK_ID,
smartAccountName: "MySmartWallet",
});
expect(mockCdpClient.evm.getSmartAccount).toHaveBeenCalledWith({
name: "MySmartWallet",
owner: mockOwnerAccount,
});
});
});
// =========================================================
// basic wallet method tests
// =========================================================
describe("basic wallet methods", () => {
it("should get the smart wallet address", () => {
expect(provider.getAddress()).toBe(MOCK_SMART_ADDRESS);
});
it("should get the network", () => {
expect(provider.getNetwork()).toEqual(MOCK_NETWORK);
});
it("should get the name", () => {
expect(provider.getName()).toBe("cdp_smart_wallet_provider");
});
it("should get the balance", async () => {
const balance = await provider.getBalance();
expect(balance).toBe(MOCK_BALANCE);
expect(mockPublicClient.getBalance).toHaveBeenCalledWith({
address: MOCK_SMART_ADDRESS,
});
});
});
// =========================================================
// signing operation tests
// =========================================================
describe("signing operations", () => {
it("should throw error for direct message signing", async () => {
await expect(provider.signMessage("Hello, world!")).rejects.toThrow("Direct message signing not supported for smart wallets. Use sendTransaction instead.");
});
it("should sign typed data using smart account", async () => {
const typedData = {
domain: {
name: "Example",
version: "1",
chainId: 1,
verifyingContract: "0x1234567890123456789012345678901234567890",
},
types: {
Person: [
{ name: "name", type: "string" },
{ name: "wallet", type: "address" },
],
},
primaryType: "Person",
message: {
name: "Bob",
wallet: "0x1234567890123456789012345678901234567890",
},
};
// Mock the smart account's signTypedData method
mockSmartAccount.signTypedData = jest.fn().mockResolvedValue(MOCK_SIGNATURE);
const signature = await provider.signTypedData(typedData);
// Verify the smart account was called with the correct parameters
expect(mockSmartAccount.signTypedData).toHaveBeenCalledWith({
domain: typedData.domain,
types: typedData.types,
primaryType: typedData.primaryType,
message: typedData.message,
network: MOCK_NETWORK.networkId,
});
expect(signature).toBe(MOCK_SIGNATURE);
});
it("should throw error for direct transaction signing", async () => {
const tx = {
to: "0x1234567890123456789012345678901234567890",
value: BigInt(1000000000000000000),
};
await expect(provider.signTransaction(tx)).rejects.toThrow("Direct transaction signing not supported for smart wallets. Use sendTransaction instead.");
});
});
// =========================================================
// user operation tests
// =========================================================
describe("user operations", () => {
it("should send user operations", async () => {
const transaction = {
to: "0x1234567890123456789012345678901234567890",
value: BigInt(1000000000000000000),
};
const userOpHash = await provider.sendTransaction(transaction);
expect(mockCdpClient.evm.sendUserOperation).toHaveBeenCalledWith({
smartAccount: mockSmartAccount,
network: "base-sepolia",
calls: [
{
to: transaction.to,
value: BigInt(transaction.value.toString()),
data: "0x",
},
],
});
expect(userOpHash).toBe(MOCK_USER_OP_HASH);
});
it("should handle native transfers", async () => {
const userOpHash = await provider.nativeTransfer("0x1234567890123456789012345678901234567890", "1000000000000000000");
expect(userOpHash).toBe(MOCK_USER_OP_HASH);
});
});
// =========================================================
// 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",
},
];
const result = await provider.readContract({
address: "0x1234567890123456789012345678901234567890",
abi,
functionName: "balanceOf",
args: [MOCK_SMART_ADDRESS],
});
expect(result).toBe("mock_result");
expect(mockPublicClient.readContract).toHaveBeenCalled();
});
});
});