UNPKG

@coinbase/agentkit

Version:

Coinbase AgentKit core primitives

282 lines (281 loc) 12 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const privyEvmDelegatedEmbeddedWalletProvider_1 = require("./privyEvmDelegatedEmbeddedWalletProvider"); global.fetch = jest.fn().mockImplementation(async (url, init) => { if (!init?.headers?.["privy-authorization-signature"]) { throw new Error("Missing privy-authorization-signature header"); } if (!init?.headers?.["privy-app-id"]) { throw new Error("Missing privy-app-id header"); } const body = JSON.parse(init.body); if (url.includes("wallets/rpc")) { if (body.method === "personal_sign") { return Promise.resolve({ ok: true, json: () => Promise.resolve({ data: { signature: "0x1234" } }), }); } if (body.method === "eth_signTypedData_v4") { return Promise.resolve({ ok: true, json: () => Promise.resolve({ signature: "0x1234" }), }); } if (body.method === "eth_signTransaction") { return Promise.resolve({ ok: true, json: () => Promise.resolve({ data: { signed_transaction: "0x1234" } }), }); } if (body.method === "eth_sendTransaction") { return Promise.resolve({ ok: true, json: () => Promise.resolve({ data: { hash: "0xef01" } }), }); } } return Promise.resolve({ ok: true, json: () => Promise.resolve({}), }); }); jest.mock("../analytics", () => ({ sendAnalyticsEvent: jest.fn().mockImplementation(() => Promise.resolve()), })); const MOCK_ADDRESS = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"; const MOCK_WALLET_ID = "test-wallet-id"; const MOCK_TRANSACTION_HASH = "0xef01"; const MOCK_SIGNATURE = "0x1234"; jest.mock("../network", () => { const chain = { id: 84532, name: "Base Sepolia", rpcUrls: { default: { http: ["https://sepolia.base.org"] }, }, nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18, }, }; return { getNetwork: jest.fn().mockReturnValue({ protocolFamily: "evm", chainId: "84532", networkId: "base-sepolia", }), getChain: jest.fn().mockReturnValue(chain), NETWORK_ID_TO_CHAIN_ID: { "base-sepolia": "84532", }, }; }); jest.mock("viem", () => { const originalModule = jest.requireActual("viem"); return { ...originalModule, createPublicClient: jest.fn().mockReturnValue({ getBalance: jest.fn().mockResolvedValue(BigInt(1000000000000000000)), waitForTransactionReceipt: jest.fn().mockResolvedValue({ transactionHash: "0xef01", status: "success", }), readContract: jest.fn().mockResolvedValue("mock_result"), }), parseEther: jest.fn().mockReturnValue(BigInt(1000000000000000000)), }; }); jest.mock("./privyShared", () => ({ createPrivyClient: jest.fn().mockReturnValue({ getUser: jest.fn().mockResolvedValue({ linkedAccounts: [ { type: "wallet", walletClientType: "privy", address: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e", }, ], }), }), })); jest.mock("canonicalize", () => { const mockFn = jest.fn().mockImplementation((obj) => { const replacer = (key, value) => { if (typeof value === "bigint") { return value.toString(); } return value; }; return JSON.stringify(obj, replacer); }); return mockFn; }); jest.mock("crypto", () => ({ createPrivateKey: jest.fn().mockImplementation(() => ({})), sign: jest.fn().mockImplementation(() => Buffer.from("mock-signature")), })); describe("PrivyEvmDelegatedEmbeddedWalletProvider", () => { const MOCK_CONFIG = { appId: "test-app-id", appSecret: "test-app-secret", authorizationPrivateKey: "wallet-auth:test-auth-key", walletId: MOCK_WALLET_ID, networkId: "base-sepolia", walletType: "embedded", }; beforeEach(() => { jest.clearAllMocks(); }); describe("configureWithWallet", () => { it("should configure with required configuration", async () => { const provider = await privyEvmDelegatedEmbeddedWalletProvider_1.PrivyEvmDelegatedEmbeddedWalletProvider.configureWithWallet(MOCK_CONFIG); expect(provider).toBeInstanceOf(privyEvmDelegatedEmbeddedWalletProvider_1.PrivyEvmDelegatedEmbeddedWalletProvider); expect(provider.getNetwork()).toEqual({ protocolFamily: "evm", chainId: "84532", networkId: "base-sepolia", }); }); it("should throw error if walletId is missing", async () => { const { walletId: _walletId, ...configWithoutWalletId } = MOCK_CONFIG; await expect(privyEvmDelegatedEmbeddedWalletProvider_1.PrivyEvmDelegatedEmbeddedWalletProvider.configureWithWallet(configWithoutWalletId)).rejects.toThrow("walletId is required"); }); it("should throw error if appId or appSecret is missing", async () => { const { appId: _appId, ...configWithoutAppId } = MOCK_CONFIG; await expect(privyEvmDelegatedEmbeddedWalletProvider_1.PrivyEvmDelegatedEmbeddedWalletProvider.configureWithWallet(configWithoutAppId)).rejects.toThrow("appId and appSecret are required"); }); it("should throw error if authorizationPrivateKey is missing", async () => { const { authorizationPrivateKey: _authKey, ...configWithoutAuthKey } = MOCK_CONFIG; await expect(privyEvmDelegatedEmbeddedWalletProvider_1.PrivyEvmDelegatedEmbeddedWalletProvider.configureWithWallet(configWithoutAuthKey)).rejects.toThrow("authorizationPrivateKey is required"); }); }); describe("wallet methods", () => { let provider; beforeEach(async () => { provider = await privyEvmDelegatedEmbeddedWalletProvider_1.PrivyEvmDelegatedEmbeddedWalletProvider.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: "84532", networkId: "base-sepolia", }); }); it("should get the provider name", () => { expect(provider.getName()).toBe("privy_evm_embedded_wallet_provider"); }); it("should get the wallet balance", async () => { const balance = await provider.getBalance(); expect(balance).toBe(BigInt(1000000000000000000)); }); it("should sign a message", async () => { const result = await provider.signMessage("Hello, world!"); expect(result).toBe(MOCK_SIGNATURE); }); it("should sign typed data", async () => { const typedData = { domain: { name: "Test" }, types: { Test: [{ name: "test", type: "string" }] }, primaryType: "Test", message: { test: "test" }, }; const result = await provider.signTypedData(typedData); expect(result).toBe(MOCK_SIGNATURE); }); it("should sign a transaction", async () => { const transaction = { to: "0x1234567890123456789012345678901234567890", value: BigInt(1000000000000000000), }; const result = await provider.signTransaction(transaction); expect(result).toBe(MOCK_SIGNATURE); }); it("should send a transaction", async () => { const transaction = { to: "0x1234567890123456789012345678901234567890", value: BigInt(1000000000000000000), }; const result = await provider.sendTransaction(transaction); expect(result).toBe(MOCK_TRANSACTION_HASH); }); it("should wait for transaction receipt", async () => { const receipt = await provider.waitForTransactionReceipt(MOCK_TRANSACTION_HASH); expect(receipt).toEqual({ transactionHash: MOCK_TRANSACTION_HASH, status: "success", }); }); it("should transfer native tokens", async () => { const result = await provider.nativeTransfer("0x1234567890123456789012345678901234567890", "1.0"); expect(result).toBe(MOCK_TRANSACTION_HASH); }); it("should export wallet data", () => { const exportData = provider.exportWallet(); expect(exportData).toEqual({ walletId: MOCK_WALLET_ID, authorizationPrivateKey: MOCK_CONFIG.authorizationPrivateKey, networkId: "base-sepolia", chainId: "84532", }); }); 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_ADDRESS], }); expect(result).toBe("mock_result"); }); describe("request signing", () => { it("should include required headers in requests", async () => { const transaction = { to: "0x1234567890123456789012345678901234567890", value: BigInt(1000000000000000000), }; await provider.sendTransaction(transaction); expect(global.fetch).toHaveBeenCalled(); const lastCall = global.fetch.mock.calls[global.fetch.mock.calls.length - 1]; const [_url, init] = lastCall; expect(init.headers).toBeDefined(); expect(init.headers["privy-authorization-signature"]).toBeDefined(); expect(init.headers["privy-app-id"]).toBe("test-app-id"); expect(init.headers["Authorization"]).toBeDefined(); }); it("should handle signature generation errors", async () => { const canonicalize = jest.requireMock("canonicalize"); canonicalize.mockImplementationOnce(() => null); // Force canonicalization failure const transaction = { to: "0x1234567890123456789012345678901234567890", value: BigInt(1000000000000000000), }; await expect(provider.sendTransaction(transaction)).rejects.toThrow("Error generating Privy authorization signature"); }); it("should handle HTTP errors", async () => { global.fetch.mockImplementationOnce(() => Promise.resolve({ ok: false, status: 400, })); const transaction = { to: "0x1234567890123456789012345678901234567890", value: BigInt(1000000000000000000), }; await expect(provider.sendTransaction(transaction)).rejects.toThrow("Privy request failed: HTTP error! status: 400"); }); }); }); });