UNPKG

@coinbase/agentkit

Version:

Coinbase AgentKit core primitives

743 lines (742 loc) 35.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const legacyCdpWalletProvider_1 = require("./legacyCdpWalletProvider"); const coinbase_sdk_1 = require("@coinbase/coinbase-sdk"); const decimal_js_1 = require("decimal.js"); global.fetch = jest.fn(() => Promise.resolve({ ok: true, json: () => Promise.resolve({}), })); // ========================================================= // consts // ========================================================= const mockPublicClient = { waitForTransactionReceipt: jest.fn(), readContract: jest.fn(), getTransactionCount: jest.fn(), estimateFeesPerGas: jest.fn(), estimateGas: 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), createWalletClient: jest.fn(), http: jest.fn(), zeroAddress: "0x0000000000000000000000000000000000000000", parseEther: jest.fn((_value) => BigInt(1000000000000000000)), keccak256: jest.fn((_value) => "0xmockhash"), serializeTransaction: jest.fn((_tx) => "0xserialized"), hashMessage: jest.fn((_message) => "0xmockhashmessage"), }; }); jest.mock("../network", () => { return { NETWORK_ID_TO_CHAIN_ID: { mainnet: "1", "base-sepolia": "84532", }, }; }); // Mock Coinbase SDK jest.mock("@coinbase/coinbase-sdk", () => { const mockWalletAddressFn = jest.fn(); mockWalletAddressFn.mockImplementation((model) => { const typedModel = model; return { address_id: typedModel.address_id, wallet_id: typedModel.wallet_id, network_id: typedModel.network_id, public_key: typedModel.public_key, index: typedModel.index, getId: jest.fn().mockReturnValue(typedModel.address_id), }; }); // Create a mock for ExternalAddress const mockExternalAddress = jest.fn().mockImplementation(() => { return { broadcastExternalTransaction: jest.fn().mockImplementation(async () => ({ transactionHash: "0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba", })), }; }); return { Wallet: { import: jest.fn(), create: jest.fn(), }, Coinbase: { assets: { Usdc: "USDC", Cbbtc: "CBBTC", Eurc: "EURC", Eth: "ETH", }, configure: jest.fn(), configureFromJson: jest.fn(), networks: { BaseSepolia: "base-sepolia", }, }, assets: { Usdc: "USDC", Cbbtc: "CBBTC", Eurc: "EURC", Eth: "ETH", }, hashTypedDataMessage: jest.fn(), hashMessage: jest.fn(), WalletAddress: mockWalletAddressFn, ExternalAddress: mockExternalAddress, }; }); const mockWalletObj = { getDefaultAddress: jest.fn(), getNetworkId: jest.fn(), getBalance: jest.fn(), createPayloadSignature: jest.fn(), createTransfer: jest.fn(), createTrade: jest.fn(), deployToken: jest.fn(), deployContract: jest.fn(), deployNFT: jest.fn(), export: jest.fn(), }; // ========================================================= // test constants // ========================================================= const MOCK_ADDRESS = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"; const MOCK_NETWORK_ID = "mainnet"; const MOCK_PRIVATE_KEY = "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"; const MOCK_TRANSACTION_HASH = "0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba"; const MOCK_SIGNATURE = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b01"; const MOCK_BALANCE = 1000000000000000000n; const MOCK_NETWORK = { protocolFamily: "evm", networkId: MOCK_NETWORK_ID, }; const MOCK_TRANSACTION_RECEIPT = { transactionHash: MOCK_TRANSACTION_HASH, }; describe("LegacyCdpWalletProvider", () => { let provider; beforeEach(async () => { jest.clearAllMocks(); const mockWalletAddress = { wallet_id: "mock-wallet-id", network_id: MOCK_NETWORK_ID, public_key: "mock-public-key", address_id: MOCK_ADDRESS, index: 0, getId: jest.fn().mockReturnValue(MOCK_ADDRESS), }; mockWalletObj.getDefaultAddress.mockResolvedValue(mockWalletAddress); mockWalletObj.getNetworkId.mockReturnValue(MOCK_NETWORK_ID); mockWalletObj.getBalance.mockResolvedValue(new decimal_js_1.Decimal(1)); const mockPayloadSignature = { getStatus: jest.fn().mockReturnValue("completed"), getSignature: jest.fn().mockReturnValue(MOCK_SIGNATURE), }; mockWalletObj.createPayloadSignature.mockResolvedValue(mockPayloadSignature); const mockTransferResult = { getTransactionHash: jest.fn().mockReturnValue(MOCK_TRANSACTION_HASH), wait: jest.fn(), }; mockTransferResult.wait.mockResolvedValue(mockTransferResult); mockWalletObj.createTransfer.mockResolvedValue(mockTransferResult); mockWalletObj.export.mockReturnValue({ seed: MOCK_PRIVATE_KEY, networkId: MOCK_NETWORK_ID, }); jest.spyOn(coinbase_sdk_1.Wallet, "import").mockResolvedValue(mockWalletObj); jest.spyOn(coinbase_sdk_1.Wallet, "create").mockResolvedValue(mockWalletObj); mockPublicClient.waitForTransactionReceipt.mockResolvedValue(MOCK_TRANSACTION_RECEIPT); mockPublicClient.readContract.mockResolvedValue("mock_result"); mockPublicClient.getTransactionCount.mockResolvedValue(1); mockPublicClient.estimateFeesPerGas.mockResolvedValue({ maxFeePerGas: BigInt(100000000), maxPriorityFeePerGas: BigInt(10000000), }); mockPublicClient.estimateGas.mockResolvedValue(BigInt(21000)); provider = await legacyCdpWalletProvider_1.LegacyCdpWalletProvider.configureWithWallet({ wallet: mockWalletObj, networkId: MOCK_NETWORK_ID, }); }); // ========================================================= // initialization tests // ========================================================= describe("initialization", () => { it("should initialize with wallet data", async () => { const walletData = JSON.stringify({ seed: MOCK_PRIVATE_KEY, networkId: MOCK_NETWORK_ID, }); const provider = await legacyCdpWalletProvider_1.LegacyCdpWalletProvider.configureWithWallet({ cdpWalletData: walletData, networkId: MOCK_NETWORK_ID, }); expect(coinbase_sdk_1.Wallet.import).toHaveBeenCalled(); expect(provider.getAddress()).toBe(MOCK_ADDRESS); expect(provider.getNetwork()).toEqual(MOCK_NETWORK); }); it("should initialize with mnemonic phrase", async () => { const mnemonicPhrase = "test test test test test test test test test test test junk"; const provider = await legacyCdpWalletProvider_1.LegacyCdpWalletProvider.configureWithWallet({ mnemonicPhrase, networkId: MOCK_NETWORK_ID, }); expect(coinbase_sdk_1.Wallet.import).toHaveBeenCalledWith({ mnemonicPhrase }, MOCK_NETWORK_ID); expect(provider.getAddress()).toBe(MOCK_ADDRESS); expect(provider.getNetwork()).toEqual(MOCK_NETWORK); }); it("should initialize with API keys", async () => { const apiKeyId = "test-key"; const apiKeySecret = "private-key"; const provider = await legacyCdpWalletProvider_1.LegacyCdpWalletProvider.configureWithWallet({ apiKeyId, apiKeySecret, networkId: MOCK_NETWORK_ID, }); expect(coinbase_sdk_1.Coinbase.configure).toHaveBeenCalledWith({ apiKeyName: apiKeyId, privateKey: apiKeySecret, source: "agentkit", sourceVersion: "1.0.0", }); expect(provider.getAddress()).toBe(MOCK_ADDRESS); expect(provider.getNetwork()).toEqual(MOCK_NETWORK); }); it("should initialize with an existing wallet", async () => { const provider = await legacyCdpWalletProvider_1.LegacyCdpWalletProvider.configureWithWallet({ wallet: mockWalletObj, networkId: MOCK_NETWORK_ID, }); expect(provider.getAddress()).toBe(MOCK_ADDRESS); expect(provider.getNetwork()).toEqual(MOCK_NETWORK); }); it("should default to base-sepolia if network not provided", async () => { mockWalletObj.getNetworkId.mockReturnValueOnce("base-sepolia"); const provider = await legacyCdpWalletProvider_1.LegacyCdpWalletProvider.configureWithWallet({ wallet: mockWalletObj, }); expect(provider.getNetwork().networkId).toBe("base-sepolia"); }); it("should handle initialization failures gracefully", async () => { jest.spyOn(coinbase_sdk_1.Wallet, "create").mockRejectedValueOnce(new Error("Failed to create wallet")); await expect(legacyCdpWalletProvider_1.LegacyCdpWalletProvider.configureWithWallet({ networkId: MOCK_NETWORK_ID, })).rejects.toThrow("Failed to create wallet"); }); }); // ========================================================= // basic wallet method tests // ========================================================= describe("basic wallet methods", () => { it("should get the address", () => { expect(provider.getAddress()).toBe(MOCK_ADDRESS); }); it("should get the network", () => { expect(provider.getNetwork()).toEqual(MOCK_NETWORK); }); it("should get the name", () => { expect(provider.getName()).toBe("legacy_cdp_wallet_provider"); }); it("should get the balance", async () => { const balance = await provider.getBalance(); expect(balance).toBe(MOCK_BALANCE); expect(mockWalletObj.getBalance).toHaveBeenCalled(); }); it("should handle connection errors during balance check", async () => { mockWalletObj.getBalance.mockRejectedValueOnce(new Error("Network connection error")); await expect(provider.getBalance()).rejects.toThrow("Network connection error"); }); it("should handle timeout errors during balance check", async () => { mockWalletObj.getBalance.mockRejectedValueOnce(new Error("Request timed out")); await expect(provider.getBalance()).rejects.toThrow("Request timed out"); }); }); // ========================================================= // signing operation tests // ========================================================= describe("signing operations", () => { it("should sign a hash", async () => { const testHash = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; const signature = await provider.sign(testHash); expect(mockWalletObj.createPayloadSignature).toHaveBeenCalledWith(testHash); expect(signature).toBe(MOCK_SIGNATURE); }); it("should sign messages", async () => { const signature = await provider.signMessage("Hello, world!"); expect(mockWalletObj.createPayloadSignature).toHaveBeenCalled(); expect(signature).toBe(MOCK_SIGNATURE); }); it("should sign typed data", async () => { const typedData = { domain: { name: "Example" }, types: { Test: [{ name: "test", type: "string" }] }, message: { test: "example" }, primaryType: "Test", }; const signature = await provider.signTypedData(typedData); expect(mockWalletObj.createPayloadSignature).toHaveBeenCalled(); expect(signature).toBe(MOCK_SIGNATURE); }); it("should sign transactions", async () => { const tx = { to: "0x1234567890123456789012345678901234567890", value: BigInt(1000000000000000000), }; const signedTx = await provider.signTransaction(tx); expect(mockWalletObj.createPayloadSignature).toHaveBeenCalled(); expect(signedTx).toBe(MOCK_SIGNATURE); }); it("should handle signing failures", async () => { mockWalletObj.createPayloadSignature.mockRejectedValueOnce(new Error("Signing failed")); const message = "Hello, world!"; await expect(provider.signMessage(message)).rejects.toThrow("Signing failed"); }); }); // ========================================================= // transaction operation tests // ========================================================= describe("transaction operations", () => { it("should send transactions", async () => { const validSignature = "0x" + "1".repeat(64) + "2".repeat(64) + "01"; // r, s, v format (130 chars + 0x prefix) const mockSignature = { model: {}, getId: jest.fn().mockReturnValue("signature-id"), getWalletId: jest.fn().mockReturnValue("mock-wallet-id"), getAddressId: jest.fn().mockReturnValue(MOCK_ADDRESS), getNetworkId: jest.fn().mockReturnValue(MOCK_NETWORK_ID), getSignature: jest.fn().mockReturnValue(validSignature), getPayload: jest.fn().mockReturnValue("0xpayload"), getEncodedPayload: jest.fn().mockReturnValue("0xencodedpayload"), getStatus: jest.fn().mockReturnValue("completed"), }; mockWalletObj.createPayloadSignature.mockResolvedValue(mockSignature); const transaction = { to: "0x1234567890123456789012345678901234567890", value: BigInt(1000000000000000000), }; const txHash = await provider.sendTransaction(transaction); expect(mockWalletObj.createPayloadSignature).toHaveBeenCalled(); expect(txHash).toBe(MOCK_TRANSACTION_HASH); }); it("should wait for the first transfer before sending another one", async () => { const validSignature = "0x" + "1".repeat(64) + "2".repeat(64) + "01"; // r, s, v format (130 chars + 0x prefix) const mockSignature = { model: {}, getId: jest.fn().mockReturnValue("signature-id"), getWalletId: jest.fn().mockReturnValue("mock-wallet-id"), getAddressId: jest.fn().mockReturnValue(MOCK_ADDRESS), getNetworkId: jest.fn().mockReturnValue(MOCK_NETWORK_ID), getSignature: jest.fn().mockReturnValue(validSignature), getPayload: jest.fn().mockReturnValue("0xpayload"), getEncodedPayload: jest.fn().mockReturnValue("0xencodedpayload"), getStatus: jest.fn().mockReturnValue("completed"), }; mockWalletObj.createPayloadSignature.mockResolvedValue(mockSignature); const transaction = { to: "0x1234567890123456789012345678901234567890", value: BigInt(1000000000000000000), }; mockPublicClient.waitForTransactionReceipt.mockImplementationOnce(async () => { await new Promise(resolve => setTimeout(resolve, 20)); return MOCK_TRANSACTION_RECEIPT; }); const txHash1 = await provider.sendTransaction(transaction); expect(mockWalletObj.createPayloadSignature).toHaveBeenCalledTimes(1); expect(txHash1).toBe(MOCK_TRANSACTION_HASH); expect(mockPublicClient.waitForTransactionReceipt).toHaveBeenCalledTimes(1); const awaitReceiptPromise = mockPublicClient.waitForTransactionReceipt.mock.results[0] .value; const secondTxHashPromise = provider.sendTransaction(transaction); const race = Promise.race([awaitReceiptPromise, secondTxHashPromise]); expect(await race).toStrictEqual(MOCK_TRANSACTION_RECEIPT); expect(await secondTxHashPromise).toBe(MOCK_TRANSACTION_HASH); }); it("should execute a native transfer", async () => { const to = "0x1234567890123456789012345678901234567890"; const value = "1.0"; const txHash = await provider.nativeTransfer(to, value); expect(mockWalletObj.createTransfer).toHaveBeenCalledWith({ assetId: "ETH", destination: to, amount: new decimal_js_1.Decimal(1), gasless: false, }); expect(txHash).toBe(MOCK_TRANSACTION_HASH); }); it("should handle transaction failures during send", async () => { mockWalletObj.createPayloadSignature.mockRejectedValueOnce(new Error("Transaction signing failed")); const transaction = { to: "0x1234567890123456789012345678901234567890", value: BigInt(1000000000000000000), }; await expect(provider.sendTransaction(transaction)).rejects.toThrow("Transaction signing failed"); }); it("should handle network errors during transfers", async () => { mockWalletObj.createTransfer.mockRejectedValueOnce(new Error("Network connection error")); const to = "0x1234567890123456789012345678901234567890"; const value = "1.0"; await expect(provider.nativeTransfer(to, value)).rejects.toThrow("Network connection error"); }); it("should handle receipt timeout errors", async () => { mockPublicClient.waitForTransactionReceipt.mockRejectedValueOnce(new Error("Timed out")); const hash = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; await expect(provider.waitForTransactionReceipt(hash)).rejects.toThrow("Timed out"); }); it("should handle transaction timeout during sending", async () => { mockWalletObj.createPayloadSignature.mockRejectedValueOnce(new Error("Transaction timed out")); const transaction = { to: "0x1234567890123456789012345678901234567890", value: BigInt(1000000000000000000), }; await expect(provider.sendTransaction(transaction)).rejects.toThrow("Transaction timed out"); }); it("should handle receipt waiting timeout with specific error", async () => { mockPublicClient.waitForTransactionReceipt.mockRejectedValueOnce(new Error("Transaction receipt waiting timed out")); const hash = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; await expect(provider.waitForTransactionReceipt(hash)).rejects.toThrow("Transaction receipt waiting timed out"); }); it("should handle transaction with invalid address", async () => { const mockError = new Error("Invalid address format"); mockWalletObj.createTransfer.mockRejectedValueOnce(mockError); const invalidAddress = "not_a_valid_address"; const value = "1.0"; await expect(provider.nativeTransfer(invalidAddress, value)).rejects.toThrow("Invalid address format"); }); }); // ========================================================= // 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_ADDRESS], }); expect(result).toBe("mock_result"); expect(mockPublicClient.readContract).toHaveBeenCalled(); }); it("should handle network errors during contract reads", async () => { mockPublicClient.readContract.mockRejectedValueOnce(new Error("Contract read error")); const abi = [ { name: "balanceOf", type: "function", inputs: [{ name: "account", type: "address" }], outputs: [{ name: "balance", type: "uint256" }], stateMutability: "view", }, ]; await expect(provider.readContract({ address: "0x1234567890123456789012345678901234567890", abi, functionName: "balanceOf", args: [MOCK_ADDRESS], })).rejects.toThrow("Contract read error"); }); it("should handle invalid ABI format in contract reads", async () => { mockPublicClient.readContract.mockRejectedValueOnce(new TypeError("Invalid ABI format")); const invalidAbi = "not_a_valid_abi"; await expect(provider.readContract({ address: "0x1234567890123456789012345678901234567890", abi: invalidAbi, functionName: "balanceOf", args: [MOCK_ADDRESS], })).rejects.toThrow("Invalid ABI format"); }); it("should handle contract execution reverts", async () => { mockPublicClient.readContract.mockRejectedValueOnce(new Error("execution reverted: Insufficient funds")); const abi = [ { name: "transfer", type: "function", inputs: [ { name: "to", type: "address" }, { name: "amount", type: "uint256" }, ], outputs: [{ name: "success", type: "bool" }], stateMutability: "nonpayable", }, ]; await expect(provider.readContract({ address: "0x1234567890123456789012345678901234567890", abi, functionName: "transfer", args: ["0x1234567890123456789012345678901234567890", 1000n], })).rejects.toThrow("execution reverted: Insufficient funds"); }); }); // ========================================================= // trading operations // ========================================================= describe("trading operations", () => { it("should create a trade", async () => { const mockTradeTransaction = { isTerminalState: jest.fn().mockReturnValue(true), getTransactionHash: jest.fn().mockReturnValue(MOCK_TRANSACTION_HASH), getTransactionLink: jest .fn() .mockReturnValue(`https://etherscan.io/tx/${MOCK_TRANSACTION_HASH}`), getStatus: jest.fn().mockReturnValue("completed"), }; const mockTradeResult = { model: {}, getId: jest.fn().mockReturnValue("trade-id"), getNetworkId: jest.fn().mockReturnValue(MOCK_NETWORK_ID), getWalletId: jest.fn().mockReturnValue("mock-wallet-id"), getFromAmount: jest.fn().mockReturnValue(new decimal_js_1.Decimal(1)), getToAmount: jest.fn().mockReturnValue(new decimal_js_1.Decimal(100)), getFromAssetId: jest.fn().mockReturnValue("ETH"), getToAssetId: jest.fn().mockReturnValue("USDC"), getStatus: jest.fn().mockReturnValue("completed"), setModel: jest.fn(), to_amount: "100", transaction: { transaction_hash: MOCK_TRANSACTION_HASH }, getTransaction: jest.fn().mockReturnValue(mockTradeTransaction), getAddressId: jest.fn().mockReturnValue(MOCK_ADDRESS), reload: jest.fn(), getApproveTransaction: jest.fn(), sign: jest.fn(), broadcast: jest.fn(), wait: jest.fn(), }; mockTradeResult.wait.mockResolvedValue(mockTradeResult); mockTradeResult.reload.mockResolvedValue(mockTradeResult); mockWalletObj.createTrade.mockResolvedValue(mockTradeResult); const options = { fromAssetId: "ETH", toAssetId: "USDC", amount: new decimal_js_1.Decimal("1.0"), }; const trade = await provider.createTrade(options); expect(mockWalletObj.createTrade).toHaveBeenCalledWith(options); expect(trade).toBe(mockTradeResult); }); it("should handle trade creation failures", async () => { mockWalletObj.createTrade.mockRejectedValueOnce(new Error("Trade creation failed")); const options = { fromAssetId: "ETH", toAssetId: "USDC", amount: new decimal_js_1.Decimal("1.0"), }; await expect(provider.createTrade(options)).rejects.toThrow("Trade creation failed"); }); it("should handle trade state checking", async () => { const mockTradeTransaction = { isTerminalState: jest.fn().mockReturnValue(true), getTransactionHash: jest.fn().mockReturnValue(MOCK_TRANSACTION_HASH), getStatus: jest.fn().mockReturnValue("completed"), }; const mockTradeResult = { model: {}, getId: jest.fn().mockReturnValue("trade-id"), getStatus: jest.fn().mockReturnValueOnce("pending").mockReturnValueOnce("completed"), getTransaction: jest.fn().mockReturnValue(mockTradeTransaction), reload: jest.fn().mockImplementation(() => Promise.resolve(mockTradeResult)), wait: jest.fn().mockImplementation(() => Promise.resolve(mockTradeResult)), }; jest.spyOn(mockTradeResult, "reload"); jest.spyOn(mockTradeResult, "wait"); mockWalletObj.createTrade.mockResolvedValue(mockTradeResult); const options = { fromAssetId: "ETH", toAssetId: "USDC", amount: new decimal_js_1.Decimal("1.0"), }; const trade = await provider.createTrade(options); await trade.reload(); await trade.wait(); expect(mockTradeResult.reload).toHaveBeenCalled(); expect(mockTradeResult.wait).toHaveBeenCalled(); }); }); // ========================================================= // token & contract deployment operations // ========================================================= describe("token and contract deployment", () => { it("should deploy a token", async () => { const mockTokenResult = { model: {}, isExternal: false, getId: jest.fn().mockReturnValue("token-id"), getNetworkId: jest.fn().mockReturnValue(MOCK_NETWORK_ID), getWalletId: jest.fn().mockReturnValue("mock-wallet-id"), getAddress: jest.fn().mockReturnValue("0xtoken"), address: "0xtoken", }; mockWalletObj.deployToken.mockResolvedValue(mockTokenResult); const options = { name: "Test Token", symbol: "TEST", totalSupply: new decimal_js_1.Decimal("1000000"), }; const token = await provider.deployToken(options); expect(mockWalletObj.deployToken).toHaveBeenCalledWith(options); expect(token).toBe(mockTokenResult); }); it("should handle token deployment failures", async () => { mockWalletObj.deployToken.mockRejectedValueOnce(new Error("Token deployment failed")); const options = { name: "Test Token", symbol: "TEST", totalSupply: new decimal_js_1.Decimal("1000000"), }; await expect(provider.deployToken(options)).rejects.toThrow("Token deployment failed"); }); it("should deploy a contract", async () => { const options = { solidityVersion: "0.8.0", solidityInputJson: "{}", contractName: "TestContract", constructorArgs: { _name: "Test" }, }; const mockContractResult = { model: {}, isExternal: false, getId: jest.fn().mockReturnValue("contract-id"), getNetworkId: jest.fn().mockReturnValue(MOCK_NETWORK_ID), getWalletId: jest.fn().mockReturnValue("mock-wallet-id"), getAddress: jest.fn().mockReturnValue("0xcontract"), address: "0xcontract", }; mockWalletObj.deployContract.mockResolvedValue(mockContractResult); const contract = await provider.deployContract(options); expect(mockWalletObj.deployContract).toHaveBeenCalledWith(options); expect(contract).toBe(mockContractResult); }); it("should handle contract deployment failures", async () => { mockWalletObj.deployContract.mockRejectedValueOnce(new Error("Contract deployment failed")); const options = { solidityVersion: "0.8.0", solidityInputJson: "{}", contractName: "TestContract", constructorArgs: { _name: "Test" }, }; await expect(provider.deployContract(options)).rejects.toThrow("Contract deployment failed"); }); it("should deploy an NFT", async () => { const options = { name: "Test NFT", symbol: "TNFT", baseURI: "https://example.com/nft/", }; const mockNftResult = { model: {}, isExternal: false, getId: jest.fn().mockReturnValue("nft-id"), getNetworkId: jest.fn().mockReturnValue(MOCK_NETWORK_ID), getWalletId: jest.fn().mockReturnValue("mock-wallet-id"), getAddress: jest.fn().mockReturnValue("0xnft"), address: "0xnft", }; mockWalletObj.deployNFT.mockResolvedValue(mockNftResult); const nft = await provider.deployNFT(options); expect(mockWalletObj.deployNFT).toHaveBeenCalledWith(options); expect(nft).toBe(mockNftResult); }); it("should handle NFT deployment failures", async () => { mockWalletObj.deployNFT.mockRejectedValueOnce(new Error("NFT deployment failed")); const options = { name: "Test NFT", symbol: "TNFT", baseURI: "https://example.com/nft/", }; await expect(provider.deployNFT(options)).rejects.toThrow("NFT deployment failed"); }); }); // ========================================================= // transfer operations // ========================================================= describe("transfer operations", () => { it("should execute a native transfer", async () => { const to = "0x1234567890123456789012345678901234567890"; const value = "1.0"; const txHash = await provider.nativeTransfer(to, value); expect(mockWalletObj.createTransfer).toHaveBeenCalledWith({ assetId: "ETH", destination: to, amount: new decimal_js_1.Decimal(1), gasless: false, }); expect(txHash).toBe(MOCK_TRANSACTION_HASH); }); it("should handle native transfer failures", async () => { mockWalletObj.createTransfer.mockRejectedValueOnce(new Error("Network connection error")); const to = "0x1234567890123456789012345678901234567890"; const value = "1.0"; await expect(provider.nativeTransfer(to, value)).rejects.toThrow("Network connection error"); }); it("should execute gasless ERC20 transfer", async () => { const destination = "0x1234567890123456789012345678901234567890"; const amount = 1000000000n; const assetId = "USDC"; const hash = await provider.gaslessERC20Transfer(assetId, destination, amount); expect(hash).toBe(MOCK_TRANSACTION_HASH); expect(mockWalletObj.createTransfer).toHaveBeenCalledWith({ amount, assetId, destination, gasless: true, }); }); it("should handle gasless ERC20 transfer failures", async () => { mockWalletObj.createTransfer.mockRejectedValueOnce(new Error("Gasless transfer failed")); const destination = "0x1234567890123456789012345678901234567890"; const amount = 1000000000n; const assetId = "USDC"; await expect(provider.gaslessERC20Transfer(assetId, destination, amount)).rejects.toThrow("Gasless transfer failed"); }); it("should handle transfer for amounts below minimum", async () => { const to = "0x1234567890123456789012345678901234567890"; const value = "0.000000001"; const hash = await provider.nativeTransfer(to, value); expect(hash).toBe(MOCK_TRANSACTION_HASH); expect(mockWalletObj.createTransfer).toHaveBeenCalled(); }); }); // ========================================================= // wallet management // ========================================================= describe("wallet management", () => { it("should export wallet data", async () => { const data = await provider.exportWallet(); expect(data).toEqual({ seed: MOCK_PRIVATE_KEY, networkId: MOCK_NETWORK_ID, }); expect(mockWalletObj.export).toHaveBeenCalled(); }); it("should handle wallet export failures", async () => { mockWalletObj.export.mockImplementationOnce(() => { throw new Error("Export failed"); }); await expect(provider.exportWallet()).rejects.toThrow("Export failed"); }); it("should get wallet network ID", () => { expect(provider.getNetwork().networkId).toBe(MOCK_NETWORK_ID); }); }); });