@coinbase/agentkit
Version:
Coinbase AgentKit core primitives
389 lines (388 loc) • 17.4 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const coinbaseSdk = __importStar(require("@coinbase/coinbase-sdk"));
global.fetch = jest.fn(() => Promise.resolve({
ok: true,
json: () => Promise.resolve({}),
}));
jest.mock("../analytics", () => ({
sendAnalyticsEvent: jest.fn().mockImplementation(() => Promise.resolve()),
}));
// =========================================================
// constants
// =========================================================
const MOCK_ADDRESS = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e";
const MOCK_CHAIN_ID = "1";
const MOCK_NETWORK_ID = "mainnet";
const MOCK_TRANSACTION_HASH = "0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba";
const MOCK_BALANCE = BigInt(1000000000000000000);
const mockPublicClient = {
waitForTransactionReceipt: jest.fn(),
readContract: jest.fn(),
};
var UserOperationStatus;
(function (UserOperationStatus) {
UserOperationStatus["CREATED"] = "created";
UserOperationStatus["PENDING"] = "pending";
UserOperationStatus["COMPLETE"] = "complete";
})(UserOperationStatus || (UserOperationStatus = {}));
// =========================================================
// mocks
// =========================================================
jest.mock("viem", () => {
return {
createPublicClient: jest.fn(() => mockPublicClient),
http: jest.fn(),
parseEther: jest.fn((_value) => MOCK_BALANCE),
};
});
jest.mock("../network", () => {
return {
NETWORK_ID_TO_CHAIN_ID: {
mainnet: "1",
"base-sepolia": "84532",
},
NETWORK_ID_TO_VIEM_CHAIN: {
mainnet: {},
"base-sepolia": {},
},
};
});
jest.mock("@coinbase/coinbase-sdk", () => {
return {
CHAIN_ID_TO_NETWORK_ID: {
"1": "mainnet",
"84532": "base-sepolia",
},
NETWORK_ID_TO_CHAIN_ID: {
mainnet: "1",
"base-sepolia": "84532",
},
NETWORK_ID_TO_VIEM_CHAIN: {
mainnet: {},
"base-sepolia": {},
},
Coinbase: {
configure: jest.fn(),
configureFromJson: jest.fn(),
networks: {
BaseSepolia: "base-sepolia",
},
},
waitForUserOperation: jest.fn(),
createSmartWallet: jest.fn(),
};
});
// =========================================================
// tests
// =========================================================
describe("LegacyCdpSmartWalletProvider", () => {
let provider;
let mockNetworkScopedWallet;
let mockWaitForUserOperation;
beforeEach(() => {
jest.clearAllMocks();
const mockGetBalance = jest.fn();
mockGetBalance.mockResolvedValue(MOCK_BALANCE);
const mockSendTransaction = jest.fn();
mockSendTransaction.mockResolvedValue(MOCK_TRANSACTION_HASH);
const mockSendUserOperation = jest.fn();
mockNetworkScopedWallet = {
address: MOCK_ADDRESS,
getBalance: mockGetBalance,
sendTransaction: mockSendTransaction,
sendUserOperation: mockSendUserOperation,
};
const mockUserOperationWait = jest.fn();
mockUserOperationWait.mockResolvedValue({
status: UserOperationStatus.COMPLETE,
transactionHash: MOCK_TRANSACTION_HASH,
});
const mockUserOperation = {
hash: MOCK_TRANSACTION_HASH,
wait: mockUserOperationWait,
};
mockNetworkScopedWallet.sendUserOperation.mockResolvedValue(mockUserOperation);
mockPublicClient.waitForTransactionReceipt.mockResolvedValue({
transactionHash: MOCK_TRANSACTION_HASH,
});
mockPublicClient.readContract.mockResolvedValue("mock_result");
provider = {
sendTransaction: jest.fn(),
sendUserOperation: jest.fn(),
waitForTransactionReceipt: jest.fn(),
signMessage: jest.fn(),
signTypedData: jest.fn(),
signTransaction: jest.fn(),
getAddress: jest.fn(),
getNetwork: jest.fn(),
getName: jest.fn(),
getBalance: jest.fn(),
readContract: jest.fn(),
nativeTransfer: jest.fn(),
_smartWallet: mockNetworkScopedWallet,
};
provider.getAddress.mockReturnValue(MOCK_ADDRESS);
provider.getNetwork.mockReturnValue({
protocolFamily: "evm",
networkId: MOCK_NETWORK_ID,
chainId: MOCK_CHAIN_ID,
});
provider.getName.mockReturnValue("legacy_cdp_smart_wallet_provider");
provider.getBalance.mockResolvedValue(MOCK_BALANCE);
provider.sendTransaction.mockImplementation(async (tx) => {
const _result = await mockNetworkScopedWallet.sendUserOperation({ calls: [tx] });
const waitResult = await _result.wait();
if (waitResult.status === "failed") {
throw new Error(`Transaction failed with status ${waitResult.status}`);
}
return _result.hash;
});
provider.sendUserOperation.mockImplementation(async (op) => {
const _result = await mockNetworkScopedWallet.sendUserOperation(op);
return _result.hash;
});
provider.waitForTransactionReceipt.mockImplementation((hash) => mockPublicClient.waitForTransactionReceipt({ hash }));
provider.readContract.mockImplementation(async (params) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return mockPublicClient.readContract(params);
});
provider.nativeTransfer.mockImplementation(async (to, _value) => {
await mockNetworkScopedWallet.sendUserOperation({
calls: [
{
to,
value: BigInt(1000000000000000000),
},
],
});
return MOCK_TRANSACTION_HASH;
});
const notImplementedError = new Error("Not implemented");
provider.signMessage.mockRejectedValue(notImplementedError);
provider.signTypedData.mockRejectedValue(notImplementedError);
provider.signTransaction.mockRejectedValue(notImplementedError);
mockWaitForUserOperation = jest.fn();
mockWaitForUserOperation.mockImplementation((operation) => {
return operation.wait();
});
jest
.spyOn(coinbaseSdk, "waitForUserOperation")
.mockImplementation(mockWaitForUserOperation);
});
// =========================================================
// transaction operations
// =========================================================
describe("transaction operations", () => {
it("should send transactions", async () => {
const transaction = {
to: "0x1234567890123456789012345678901234567890",
value: BigInt(1000000000000000000),
};
const txHash = await provider.sendTransaction(transaction);
expect(txHash).toBe(MOCK_TRANSACTION_HASH);
expect(mockNetworkScopedWallet.sendUserOperation).toHaveBeenCalled();
});
it("should send a user operation", async () => {
const calls = [
{
to: "0x1234567890123456789012345678901234567890",
data: "0xabcdef",
value: 0n,
},
];
const txHash = await provider.sendUserOperation({ calls });
expect(txHash).toBe(MOCK_TRANSACTION_HASH);
expect(mockNetworkScopedWallet.sendUserOperation).toHaveBeenCalledWith({ calls });
});
it("should wait for transaction receipts", async () => {
await provider.waitForTransactionReceipt(MOCK_TRANSACTION_HASH);
expect(mockPublicClient.waitForTransactionReceipt).toHaveBeenCalled();
});
it("should handle transaction failures", async () => {
mockWaitForUserOperation.mockRejectedValueOnce(new Error("Failed to send transaction"));
mockNetworkScopedWallet.sendUserOperation.mockRejectedValueOnce(new Error("Failed to send transaction"));
await expect(provider.sendTransaction({
to: MOCK_ADDRESS,
value: MOCK_BALANCE,
})).rejects.toThrow("Failed to send transaction");
});
it("should handle network errors in transactions", async () => {
mockNetworkScopedWallet.sendUserOperation.mockRejectedValueOnce(new Error("Network connection error"));
await expect(provider.sendTransaction({
to: MOCK_ADDRESS,
value: MOCK_BALANCE,
})).rejects.toThrow("Network connection error");
});
it("should handle invalid address errors", async () => {
mockNetworkScopedWallet.sendUserOperation.mockRejectedValueOnce(new Error("Invalid address format"));
const invalidAddressHex = "0xinvalid";
await expect(provider.sendTransaction({
to: invalidAddressHex,
value: MOCK_BALANCE,
})).rejects.toThrow("Invalid address format");
});
it("should handle receipt retrieval failures", async () => {
mockPublicClient.waitForTransactionReceipt.mockRejectedValueOnce(new Error("Receipt retrieval failed"));
await expect(provider.waitForTransactionReceipt(MOCK_TRANSACTION_HASH)).rejects.toThrow("Receipt retrieval failed");
});
it("should handle operation failures when sending transactions", async () => {
const mockUserOperationWait = jest.fn();
mockUserOperationWait.mockResolvedValue({
status: "failed",
transactionHash: MOCK_TRANSACTION_HASH,
});
const failedOperation = {
hash: MOCK_TRANSACTION_HASH,
wait: mockUserOperationWait,
};
mockNetworkScopedWallet.sendUserOperation.mockResolvedValueOnce(failedOperation);
const transaction = {
to: "0x1234567890123456789012345678901234567890",
value: BigInt(1000000000000000000),
};
await expect(provider.sendTransaction(transaction)).rejects.toThrow("Transaction failed with status failed");
});
it("should handle exceptions when sending user operations", async () => {
mockNetworkScopedWallet.sendUserOperation.mockRejectedValueOnce(new Error("Failed to send"));
const calls = [
{
to: "0x1234567890123456789012345678901234567890",
data: "0xabcdef",
value: BigInt(0),
},
];
await expect(provider.sendUserOperation({ calls })).rejects.toThrow("Failed to send");
});
it("should handle send user operation timeout", async () => {
mockNetworkScopedWallet.sendUserOperation.mockRejectedValueOnce(new Error("User operation timed out"));
const calls = [
{
to: "0x1234567890123456789012345678901234567890",
data: "0xabcdef",
value: 0n,
},
];
await expect(provider.sendUserOperation({ calls })).rejects.toThrow("User operation timed out");
});
});
// =========================================================
// native token transfer operations
// =========================================================
describe("native token operations", () => {
it("should transfer native tokens", async () => {
const to = "0x1234567890123456789012345678901234567890";
const value = "1";
const txHash = await provider.nativeTransfer(to, value);
expect(mockNetworkScopedWallet.sendUserOperation).toHaveBeenCalled();
expect(txHash).toBe(MOCK_TRANSACTION_HASH);
});
it("should handle operation failures when transferring native tokens", async () => {
provider.nativeTransfer.mockRejectedValueOnce(new Error("Transfer failed with status failed"));
const to = "0x1234567890123456789012345678901234567890";
const value = "1";
await expect(provider.nativeTransfer(to, value)).rejects.toThrow("Transfer failed with status failed");
});
it("should handle invalid address format in native transfer", async () => {
provider.nativeTransfer.mockRejectedValueOnce(new Error("Invalid address format"));
const invalidAddress = "not_a_valid_address";
await expect(provider.nativeTransfer(invalidAddress, "1")).rejects.toThrow("Invalid address format");
});
it("should handle network errors in native token transfers", async () => {
provider.nativeTransfer.mockRejectedValueOnce(new Error("Network error"));
await expect(provider.nativeTransfer("0x1234567890123456789012345678901234567890", "1")).rejects.toThrow("Network error");
});
});
// =========================================================
// contract interaction methods
// =========================================================
describe("contract interactions", () => {
it("should read from contracts", async () => {
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 errors in contract reads", async () => {
mockPublicClient.readContract.mockRejectedValueOnce(new Error("Contract read failed"));
await expect(provider.readContract({
address: "0x1234567890123456789012345678901234567890",
abi: [],
functionName: "balanceOf",
args: [MOCK_ADDRESS],
})).rejects.toThrow("Contract read failed");
});
it("should handle read contract with invalid ABI", async () => {
const invalidAbi = "not_an_abi";
const params = {
address: "0x1234567890123456789012345678901234567890",
abi: invalidAbi,
functionName: "balanceOf",
args: ["0x742d35Cc6634C0532925a3b844Bc454e4438f44e"],
};
mockPublicClient.readContract.mockImplementationOnce(() => {
throw new TypeError("Invalid ABI format");
});
await expect(provider.readContract(params)).rejects.toThrow("Invalid ABI format");
});
});
// =========================================================
// signing methods (unsupported operations)
// =========================================================
describe("unsupported operations", () => {
it("should throw error on sign message", async () => {
await expect(provider.signMessage("test")).rejects.toThrow("Not implemented");
});
it("should throw error on sign typed data", async () => {
await expect(provider.signTypedData({
domain: {},
types: {},
primaryType: "",
message: {},
})).rejects.toThrow("Not implemented");
});
it("should throw error on sign transaction", async () => {
await expect(provider.signTransaction({
to: MOCK_ADDRESS,
value: MOCK_BALANCE,
})).rejects.toThrow("Not implemented");
});
});
});