From ab94f8b46afb7f210cd51d46832ae1ea848d56eb Mon Sep 17 00:00:00 2001 From: Benjamin Smith Date: Mon, 2 Dec 2024 15:10:20 +0100 Subject: [PATCH 1/5] ERC20: Only Return MetaTransaction --- packages/agent-sdk/src/evm/erc20.ts | 59 +++++++++------------ packages/agent-sdk/tests/evm/erc20.spec.ts | 60 +++++++--------------- 2 files changed, 41 insertions(+), 78 deletions(-) diff --git a/packages/agent-sdk/src/evm/erc20.ts b/packages/agent-sdk/src/evm/erc20.ts index b03a403..02966ca 100644 --- a/packages/agent-sdk/src/evm/erc20.ts +++ b/packages/agent-sdk/src/evm/erc20.ts @@ -1,7 +1,6 @@ import { erc20Abi } from "viem"; import { encodeFunctionData, type Address } from "viem"; -import { signRequestFor } from ".."; -import { getClient, type SignRequestData } from "near-safe"; +import { getClient, type MetaTransaction } from "near-safe"; import type { TokenInfo } from "./types"; const MAX_APPROVAL = BigInt( @@ -9,50 +8,38 @@ const MAX_APPROVAL = BigInt( ); export async function erc20Transfer(params: { - chainId: number; token: Address; to: Address; amount: bigint; -}): Promise { - const { chainId, token, to, amount } = params; - return signRequestFor({ - chainId, - metaTransactions: [ - { - to: token, - value: "0x", - data: encodeFunctionData({ - abi: erc20Abi, - functionName: "transfer", - args: [to, amount], - }), - }, - ], - }); +}): Promise { + const { token, to, amount } = params; + return { + to: token, + value: "0x0", + data: encodeFunctionData({ + abi: erc20Abi, + functionName: "transfer", + args: [to, amount], + }), + }; } export async function erc20Approve(params: { - chainId: number; token: Address; spender: Address; // If not provided, the maximum amount will be approved. amount?: bigint; -}): Promise { - const { chainId, token, spender, amount } = params; - return signRequestFor({ - chainId, - metaTransactions: [ - { - to: token, - value: "0x", - data: encodeFunctionData({ - abi: erc20Abi, - functionName: "approve", - args: [spender, amount ?? MAX_APPROVAL], - }), - }, - ], - }); +}): Promise { + const { token, spender, amount } = params; + return { + to: token, + value: "0x0", + data: encodeFunctionData({ + abi: erc20Abi, + functionName: "approve", + args: [spender, amount ?? MAX_APPROVAL], + }), + }; } export async function checkAllowance( diff --git a/packages/agent-sdk/tests/evm/erc20.spec.ts b/packages/agent-sdk/tests/evm/erc20.spec.ts index 59f1d0e..87eaaa5 100644 --- a/packages/agent-sdk/tests/evm/erc20.spec.ts +++ b/packages/agent-sdk/tests/evm/erc20.spec.ts @@ -20,25 +20,17 @@ describe("ERC20 Utilities", () => { describe("erc20Transfer", () => { it("creates correct transfer transaction", async () => { const params = { - chainId: mockChainId, token: mockAddress, to: mockAddress, amount: mockAmount, }; - const signRequest = await erc20Transfer(params); - - expect(signRequest).toEqual({ - chainId: 1, - method: "eth_sendTransaction", - params: [ - { - data: "0xa9059cbb000000000000000000000000123456789012345678901234567890123456789000000000000000000000000000000000000000000000000000000000000003e8", - from: "0x0000000000000000000000000000000000000000", - to: "0x1234567890123456789012345678901234567890", - value: "0x", - }, - ], + const tx = await erc20Transfer(params); + + expect(tx).toEqual({ + data: "0xa9059cbb000000000000000000000000123456789012345678901234567890123456789000000000000000000000000000000000000000000000000000000000000003e8", + to: "0x1234567890123456789012345678901234567890", + value: "0x0", }); }); }); @@ -46,48 +38,32 @@ describe("ERC20 Utilities", () => { describe("erc20Approve", () => { it("creates approval transaction with specific amount", async () => { const params = { - chainId: mockChainId, token: mockAddress, spender: mockAddress, amount: mockAmount, }; - const signRequest = await erc20Approve(params); - - expect(signRequest).toEqual({ - chainId: 1, - method: "eth_sendTransaction", - params: [ - { - data: "0x095ea7b3000000000000000000000000123456789012345678901234567890123456789000000000000000000000000000000000000000000000000000000000000003e8", - from: "0x0000000000000000000000000000000000000000", - to: "0x1234567890123456789012345678901234567890", - value: "0x", - }, - ], + const tx = await erc20Approve(params); + + expect(tx).toEqual({ + data: "0x095ea7b3000000000000000000000000123456789012345678901234567890123456789000000000000000000000000000000000000000000000000000000000000003e8", + to: "0x1234567890123456789012345678901234567890", + value: "0x0", }); }); it("creates approval transaction with max amount when amount not specified", async () => { const params = { - chainId: mockChainId, token: mockAddress, spender: mockAddress, }; - const signRequest = await erc20Approve(params); - - expect(signRequest).toEqual({ - chainId: 1, - method: "eth_sendTransaction", - params: [ - { - data: "0x095ea7b30000000000000000000000001234567890123456789012345678901234567890ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - from: "0x0000000000000000000000000000000000000000", - to: "0x1234567890123456789012345678901234567890", - value: "0x", - }, - ], + const tx = await erc20Approve(params); + + expect(tx).toEqual({ + data: "0x095ea7b30000000000000000000000001234567890123456789012345678901234567890ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + to: "0x1234567890123456789012345678901234567890", + value: "0x0", }); }); }); From 612d9c2475ef8dddf4bd4d115cb3d3f1ee29095d Mon Sep 17 00:00:00 2001 From: Benjamin Smith Date: Mon, 2 Dec 2024 15:15:16 +0100 Subject: [PATCH 2/5] remove unnecessary async --- packages/agent-sdk/examples/erc20transfer.ts | 13 +++++++------ packages/agent-sdk/src/evm/erc20.ts | 8 ++++---- packages/agent-sdk/tests/evm/erc20.spec.ts | 16 +++++----------- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/packages/agent-sdk/examples/erc20transfer.ts b/packages/agent-sdk/examples/erc20transfer.ts index 92998dc..217fcb2 100644 --- a/packages/agent-sdk/examples/erc20transfer.ts +++ b/packages/agent-sdk/examples/erc20transfer.ts @@ -7,6 +7,7 @@ import { numberField, validateInput, type FieldParser, + signRequestFor, } from "../src"; interface Input { @@ -34,14 +35,14 @@ export async function GET(req: Request): Promise { parsers, ); const decimals = await getTokenDecimals(chainId, token); + const tx = erc20Transfer({ + token, + to: recipient, + amount: parseUnits(amount.toString(), decimals), + }); return Response.json( { - transaction: erc20Transfer({ - chainId, - token, - to: recipient, - amount: parseUnits(amount.toString(), decimals), - }), + transaction: signRequestFor({ chainId, metaTransactions: [tx] }), }, { status: 200 }, ); diff --git a/packages/agent-sdk/src/evm/erc20.ts b/packages/agent-sdk/src/evm/erc20.ts index 02966ca..88f9c92 100644 --- a/packages/agent-sdk/src/evm/erc20.ts +++ b/packages/agent-sdk/src/evm/erc20.ts @@ -7,11 +7,11 @@ const MAX_APPROVAL = BigInt( "115792089237316195423570985008687907853269984665640564039457584007913129639935", ); -export async function erc20Transfer(params: { +export function erc20Transfer(params: { token: Address; to: Address; amount: bigint; -}): Promise { +}): MetaTransaction { const { token, to, amount } = params; return { to: token, @@ -24,12 +24,12 @@ export async function erc20Transfer(params: { }; } -export async function erc20Approve(params: { +export function erc20Approve(params: { token: Address; spender: Address; // If not provided, the maximum amount will be approved. amount?: bigint; -}): Promise { +}): MetaTransaction { const { token, spender, amount } = params; return { to: token, diff --git a/packages/agent-sdk/tests/evm/erc20.spec.ts b/packages/agent-sdk/tests/evm/erc20.spec.ts index 87eaaa5..812ae0b 100644 --- a/packages/agent-sdk/tests/evm/erc20.spec.ts +++ b/packages/agent-sdk/tests/evm/erc20.spec.ts @@ -18,16 +18,14 @@ describe("ERC20 Utilities", () => { const mockAmount = 1000n; describe("erc20Transfer", () => { - it("creates correct transfer transaction", async () => { + it("creates correct transfer transaction", () => { const params = { token: mockAddress, to: mockAddress, amount: mockAmount, }; - const tx = await erc20Transfer(params); - - expect(tx).toEqual({ + expect(erc20Transfer(params)).toEqual({ data: "0xa9059cbb000000000000000000000000123456789012345678901234567890123456789000000000000000000000000000000000000000000000000000000000000003e8", to: "0x1234567890123456789012345678901234567890", value: "0x0", @@ -43,24 +41,20 @@ describe("ERC20 Utilities", () => { amount: mockAmount, }; - const tx = await erc20Approve(params); - - expect(tx).toEqual({ + expect(erc20Approve(params)).toEqual({ data: "0x095ea7b3000000000000000000000000123456789012345678901234567890123456789000000000000000000000000000000000000000000000000000000000000003e8", to: "0x1234567890123456789012345678901234567890", value: "0x0", }); }); - it("creates approval transaction with max amount when amount not specified", async () => { + it("creates approval transaction with max amount when amount not specified", () => { const params = { token: mockAddress, spender: mockAddress, }; - const tx = await erc20Approve(params); - - expect(tx).toEqual({ + expect(erc20Approve(params)).toEqual({ data: "0x095ea7b30000000000000000000000001234567890123456789012345678901234567890ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", to: "0x1234567890123456789012345678901234567890", value: "0x0", From 0043e08a2cdce25e5e808a55f2502535c531ad79 Mon Sep 17 00:00:00 2001 From: Benjamin Smith Date: Mon, 2 Dec 2024 15:53:23 +0100 Subject: [PATCH 3/5] Testing evm/index --- packages/agent-sdk/src/evm/index.ts | 3 +- packages/agent-sdk/tests/evm/index.spec.ts | 162 +++++++++++++++++++++ 2 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 packages/agent-sdk/tests/evm/index.spec.ts diff --git a/packages/agent-sdk/src/evm/index.ts b/packages/agent-sdk/src/evm/index.ts index 47a3fe3..ff13f36 100644 --- a/packages/agent-sdk/src/evm/index.ts +++ b/packages/agent-sdk/src/evm/index.ts @@ -45,7 +45,8 @@ export function createResponse( return { json: (_data, responseInit) => ({ data: responseData, - ...(responseInit ?? init), + ...init, + ...responseInit, }), }; } diff --git a/packages/agent-sdk/tests/evm/index.spec.ts b/packages/agent-sdk/tests/evm/index.spec.ts new file mode 100644 index 0000000..eb77b53 --- /dev/null +++ b/packages/agent-sdk/tests/evm/index.spec.ts @@ -0,0 +1,162 @@ +import { signRequestFor, createResponse, validateRequest } from "../../src/evm"; +import { zeroAddress } from "viem"; +import type { BaseRequest } from "../../src/evm"; + +// Mock external dependencies +jest.mock("viem", () => ({ + getAddress: jest.fn().mockImplementation((address) => address), + zeroAddress: "0x0000000000000000000000000000000000000000" +})); + +jest.mock("near-safe", () => ({ + NearSafe: { + create: jest.fn().mockImplementation(async () => ({ + address: "0x123" + })) + } +})); + +describe("evm/index", () => { + describe("signRequestFor", () => { + it("creates a sign request with default from address", () => { + const metaTransactions = [ + { to: "0x123", value: "0x0", data: "0xabc" } + ]; + + const result = signRequestFor({ + chainId: 1, + metaTransactions + }); + + expect(result).toEqual({ + method: "eth_sendTransaction", + chainId: 1, + params: [ + { + from: zeroAddress, + to: "0x123", + value: "0x0", + data: "0xabc" + } + ] + }); + }); + + it("creates a sign request with specified from address", () => { + const metaTransactions = [ + { to: "0x123", value: "0x0", data: "0xabc" } + ]; + + const result = signRequestFor({ + from: "0x456", + chainId: 1, + metaTransactions + }); + + expect(result).toEqual({ + method: "eth_sendTransaction", + chainId: 1, + params: [ + { + from: "0x456", + to: "0x123", + value: "0x0", + data: "0xabc" + } + ] + }); + }); + }); + + describe("createResponse", () => { + it("creates a response with default status", () => { + const responseData = { message: "Success" }; + + const response = createResponse(responseData); + + expect(response.json({}, {})).toEqual({ + data: responseData + }); + }); + + it("creates a response with specified status", () => { + const responseData = { message: "Error" }; + + const response = createResponse(responseData, { status: 400 }); + expect(response.json({}, {})).toEqual({ + data: responseData, + status: 400 + }); + }); + }); + + describe("validateRequest", () => { + const mockAddress = "0x1111111111111111111111111111111111111111"; + const mockGetAdapterAddress = jest.fn(); + jest.mock("../../src/evm", () => ({ + ...jest.requireActual("../../src/evm"), + getAdapterAddress: mockGetAdapterAddress + })); + + it("returns null for valid request", async () => { + const req = { + headers: { + get: jest.fn().mockReturnValue(JSON.stringify({ + accountId: "testAccount", + evmAddress: "0x123" + })) + } + } as BaseRequest; + + mockGetAdapterAddress.mockResolvedValue("0x123"); + + const result = await validateRequest(req, "safeSaltNonce"); + + expect(result).toBeNull(); + }); + + it("returns error response for missing accountId or evmAddress", async () => { + const req = { + headers: { + get: jest.fn().mockReturnValue("{}") + } + } as BaseRequest; + + const result = await validateRequest(req, "safeSaltNonce"); + + expect(result).toEqual({ + json: expect.any(Function) + }); + + const jsonResponse = result?.json({}, {}); + expect(jsonResponse).toEqual({ + data: { error: "Missing accountId or evmAddress in metadata" }, + status: 400 + }); + }); + + it("returns error response for invalid safeAddress", async () => { + const req = { + headers: { + get: jest.fn().mockReturnValue(JSON.stringify({ + accountId: "testAccount", + evmAddress: mockAddress + })) + } + } as BaseRequest; + + mockGetAdapterAddress.mockResolvedValue(mockAddress); + + const result = await validateRequest(req, "0"); + expect(result).toEqual({ + json: expect.any(Function) + }); + + const jsonResponse = result?.json({}, {}); + expect(jsonResponse).toEqual({ + data: { error: `Invalid safeAddress in metadata: 0x123 !== ${mockAddress}` }, + status: 401 + }); + }); + }); +}); From 4a98c8397243bef6ab82a6f7ade934cdd491a5c0 Mon Sep 17 00:00:00 2001 From: Benjamin Smith Date: Mon, 2 Dec 2024 15:54:21 +0100 Subject: [PATCH 4/5] format test file --- packages/agent-sdk/tests/evm/index.spec.ts | 78 +++++++++++----------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/packages/agent-sdk/tests/evm/index.spec.ts b/packages/agent-sdk/tests/evm/index.spec.ts index eb77b53..5856342 100644 --- a/packages/agent-sdk/tests/evm/index.spec.ts +++ b/packages/agent-sdk/tests/evm/index.spec.ts @@ -5,27 +5,25 @@ import type { BaseRequest } from "../../src/evm"; // Mock external dependencies jest.mock("viem", () => ({ getAddress: jest.fn().mockImplementation((address) => address), - zeroAddress: "0x0000000000000000000000000000000000000000" + zeroAddress: "0x0000000000000000000000000000000000000000", })); jest.mock("near-safe", () => ({ NearSafe: { create: jest.fn().mockImplementation(async () => ({ - address: "0x123" - })) - } + address: "0x123", + })), + }, })); describe("evm/index", () => { describe("signRequestFor", () => { it("creates a sign request with default from address", () => { - const metaTransactions = [ - { to: "0x123", value: "0x0", data: "0xabc" } - ]; + const metaTransactions = [{ to: "0x123", value: "0x0", data: "0xabc" }]; const result = signRequestFor({ chainId: 1, - metaTransactions + metaTransactions, }); expect(result).toEqual({ @@ -36,21 +34,19 @@ describe("evm/index", () => { from: zeroAddress, to: "0x123", value: "0x0", - data: "0xabc" - } - ] + data: "0xabc", + }, + ], }); }); it("creates a sign request with specified from address", () => { - const metaTransactions = [ - { to: "0x123", value: "0x0", data: "0xabc" } - ]; + const metaTransactions = [{ to: "0x123", value: "0x0", data: "0xabc" }]; const result = signRequestFor({ from: "0x456", chainId: 1, - metaTransactions + metaTransactions, }); expect(result).toEqual({ @@ -61,9 +57,9 @@ describe("evm/index", () => { from: "0x456", to: "0x123", value: "0x0", - data: "0xabc" - } - ] + data: "0xabc", + }, + ], }); }); }); @@ -75,7 +71,7 @@ describe("evm/index", () => { const response = createResponse(responseData); expect(response.json({}, {})).toEqual({ - data: responseData + data: responseData, }); }); @@ -85,7 +81,7 @@ describe("evm/index", () => { const response = createResponse(responseData, { status: 400 }); expect(response.json({}, {})).toEqual({ data: responseData, - status: 400 + status: 400, }); }); }); @@ -95,17 +91,19 @@ describe("evm/index", () => { const mockGetAdapterAddress = jest.fn(); jest.mock("../../src/evm", () => ({ ...jest.requireActual("../../src/evm"), - getAdapterAddress: mockGetAdapterAddress + getAdapterAddress: mockGetAdapterAddress, })); it("returns null for valid request", async () => { const req = { headers: { - get: jest.fn().mockReturnValue(JSON.stringify({ - accountId: "testAccount", - evmAddress: "0x123" - })) - } + get: jest.fn().mockReturnValue( + JSON.stringify({ + accountId: "testAccount", + evmAddress: "0x123", + }), + ), + }, } as BaseRequest; mockGetAdapterAddress.mockResolvedValue("0x123"); @@ -118,44 +116,48 @@ describe("evm/index", () => { it("returns error response for missing accountId or evmAddress", async () => { const req = { headers: { - get: jest.fn().mockReturnValue("{}") - } + get: jest.fn().mockReturnValue("{}"), + }, } as BaseRequest; const result = await validateRequest(req, "safeSaltNonce"); expect(result).toEqual({ - json: expect.any(Function) + json: expect.any(Function), }); const jsonResponse = result?.json({}, {}); expect(jsonResponse).toEqual({ data: { error: "Missing accountId or evmAddress in metadata" }, - status: 400 + status: 400, }); }); it("returns error response for invalid safeAddress", async () => { const req = { headers: { - get: jest.fn().mockReturnValue(JSON.stringify({ - accountId: "testAccount", - evmAddress: mockAddress - })) - } + get: jest.fn().mockReturnValue( + JSON.stringify({ + accountId: "testAccount", + evmAddress: mockAddress, + }), + ), + }, } as BaseRequest; mockGetAdapterAddress.mockResolvedValue(mockAddress); const result = await validateRequest(req, "0"); expect(result).toEqual({ - json: expect.any(Function) + json: expect.any(Function), }); const jsonResponse = result?.json({}, {}); expect(jsonResponse).toEqual({ - data: { error: `Invalid safeAddress in metadata: 0x123 !== ${mockAddress}` }, - status: 401 + data: { + error: `Invalid safeAddress in metadata: 0x123 !== ${mockAddress}`, + }, + status: 401, }); }); }); From f569b8b43e8522a9803ed93811a78795270affc7 Mon Sep 17 00:00:00 2001 From: Benjamin Smith Date: Mon, 2 Dec 2024 16:06:46 +0100 Subject: [PATCH 5/5] remove wrap unwrap signRequest --- packages/agent-sdk/src/evm/weth.ts | 23 +------------- packages/agent-sdk/tests/evm/weth.spec.ts | 38 ----------------------- 2 files changed, 1 insertion(+), 60 deletions(-) diff --git a/packages/agent-sdk/src/evm/weth.ts b/packages/agent-sdk/src/evm/weth.ts index df91376..aced099 100644 --- a/packages/agent-sdk/src/evm/weth.ts +++ b/packages/agent-sdk/src/evm/weth.ts @@ -1,4 +1,4 @@ -import { Network, type MetaTransaction, type SignRequestData } from "near-safe"; +import { Network, type MetaTransaction } from "near-safe"; import { type Address, encodeFunctionData, @@ -7,7 +7,6 @@ import { parseEther, toHex, } from "viem"; -import { signRequestFor } from "."; type NativeAsset = { address: Address; @@ -51,16 +50,6 @@ export function validateWethInput(params: URLSearchParams): { }; } -export function unwrapSignRequest( - chainId: number, - amount: bigint, -): SignRequestData { - return signRequestFor({ - chainId, - metaTransactions: [unwrapMetaTransaction(chainId, amount)], - }); -} - export const unwrapMetaTransaction = ( chainId: number, amount: bigint, @@ -76,16 +65,6 @@ export const unwrapMetaTransaction = ( }; }; -export function wrapSignRequest( - chainId: number, - amount: bigint, -): SignRequestData { - return signRequestFor({ - chainId, - metaTransactions: [wrapMetaTransaction(chainId, amount)], - }); -} - export const wrapMetaTransaction = ( chainId: number, amount: bigint, diff --git a/packages/agent-sdk/tests/evm/weth.spec.ts b/packages/agent-sdk/tests/evm/weth.spec.ts index 248d0fb..6644999 100644 --- a/packages/agent-sdk/tests/evm/weth.spec.ts +++ b/packages/agent-sdk/tests/evm/weth.spec.ts @@ -2,8 +2,6 @@ import { Network } from "near-safe"; import { parseEther } from "viem"; import { validateWethInput, - unwrapSignRequest, - wrapSignRequest, getNativeAsset, unwrapMetaTransaction, wrapMetaTransaction, @@ -96,42 +94,6 @@ describe("evm/weth", () => { }); }); - describe("unwrapSignRequest", () => { - it("creates correct unwrap sign request", () => { - const signRequest = unwrapSignRequest(100, 25n); - expect(signRequest).toEqual({ - method: "eth_sendTransaction", - chainId: 100, - params: [ - { - from: "0x0000000000000000000000000000000000000000", - to: "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d", - value: "0x0", - data: "0x2e1a7d4d0000000000000000000000000000000000000000000000000000000000000019", - }, - ], - }); - }); - }); - - describe("wrapSignRequest", () => { - it("creates correct wrap sign request", () => { - const signRequest = wrapSignRequest(100, 25n); - expect(signRequest).toEqual({ - method: "eth_sendTransaction", - chainId: 100, - params: [ - { - from: "0x0000000000000000000000000000000000000000", - to: "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d", - value: "0x19", - data: "0xd0e30db0", - }, - ], - }); - }); - }); - describe("getNativeAsset", () => { it("returns correct native asset info for known chain", () => { const result = getNativeAsset(100);