From 25a4f0c45e6e66a30b45b6d2a61a18d707ef0ac6 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Fri, 11 Apr 2025 21:56:35 +0100 Subject: [PATCH 1/2] Update types --- src/methods/wallet-get-calls-status.ts | 1 + src/methods/wallet-send-calls.test.ts | 6 +++++- src/methods/wallet-send-calls.ts | 11 +++++++---- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/methods/wallet-get-calls-status.ts b/src/methods/wallet-get-calls-status.ts index d014746..4ee079c 100644 --- a/src/methods/wallet-get-calls-status.ts +++ b/src/methods/wallet-get-calls-status.ts @@ -28,6 +28,7 @@ export type GetCallsStatusResult = { id: Hex; chainId: Hex; status: number; + atomic: boolean; receipts?: { logs: { address: Hex; diff --git a/src/methods/wallet-send-calls.test.ts b/src/methods/wallet-send-calls.test.ts index 22c6ba6..e92b591 100644 --- a/src/methods/wallet-send-calls.test.ts +++ b/src/methods/wallet-send-calls.test.ts @@ -21,6 +21,7 @@ const REQUEST_MOCK = { version: '1.0', from: ADDRESS_MOCK, chainId: HEX_MOCK, + atomicRequired: true, calls: [ { to: ADDRESS_MOCK, @@ -123,12 +124,13 @@ describe('wallet_sendCalls', () => { params[0].from = undefined as never; params[0].chainId = undefined as never; params[0].calls = undefined as never; + params[0].atomicRequired = undefined as never; await expect(callMethod()).rejects.toMatchInlineSnapshot(` [Error: Invalid params - 0 > from - Expected a string, but received: undefined 0 > chainId - Expected a string, but received: undefined + 0 > atomicRequired - Expected a value of type \`boolean\`, but received: \`undefined\` 0 > calls - Expected an array value, but received: undefined] `); }); @@ -139,6 +141,7 @@ describe('wallet_sendCalls', () => { params[0].chainId = 123 as never; params[0].calls = '123' as never; params[0].capabilities = '123' as never; + params[0].atomicRequired = 123 as never; await expect(callMethod()).rejects.toMatchInlineSnapshot(` [Error: Invalid params @@ -146,6 +149,7 @@ describe('wallet_sendCalls', () => { 0 > id - Expected a string, but received: 123 0 > from - Expected a string matching \`/^0x[0-9a-fA-F]{40}$/\` but received "123" 0 > chainId - Expected a string, but received: 123 + 0 > atomicRequired - Expected a value of type \`boolean\`, but received: \`123\` 0 > calls - Expected an array value, but received: "123" 0 > capabilities - Expected an object, but received: "123"] `); diff --git a/src/methods/wallet-send-calls.ts b/src/methods/wallet-send-calls.ts index e161ceb..9953033 100644 --- a/src/methods/wallet-send-calls.ts +++ b/src/methods/wallet-send-calls.ts @@ -35,8 +35,9 @@ const SendCallsStruct = tuple([ object({ version: nonempty(string()), id: optional(StrictHexStruct), - from: HexChecksumAddressStruct, + from: optional(HexChecksumAddressStruct), chainId: StrictHexStruct, + atomicRequired: boolean(), calls: array( object({ to: optional(HexChecksumAddressStruct), @@ -81,9 +82,11 @@ export async function walletSendCalls( const params = req.params[0]; - const from = await validateAndNormalizeKeyholder(params.from, req, { - getAccounts, - }); + const from = params.from + ? await validateAndNormalizeKeyholder(params.from, req, { + getAccounts, + }) + : undefined; const sendCalls: SendCalls = { ...params, From cc12ca140cb2513b7cc0b0df74fe7cfd6da5fd3e Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Fri, 11 Apr 2025 22:43:59 +0100 Subject: [PATCH 2/2] Validate getCapabilities address --- src/methods/wallet-get-capabilities.test.ts | 18 +++++++++++++++++- src/methods/wallet-get-capabilities.ts | 12 +++++++++++- src/utils/validation.ts | 2 +- src/wallet.ts | 2 +- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/methods/wallet-get-capabilities.test.ts b/src/methods/wallet-get-capabilities.test.ts index 3a579a9..4863b8c 100644 --- a/src/methods/wallet-get-capabilities.test.ts +++ b/src/methods/wallet-get-capabilities.test.ts @@ -7,6 +7,9 @@ import type { GetCapabilitiesResult, } from './wallet-get-capabilities'; import { walletGetCapabilities } from './wallet-get-capabilities'; +import type { WalletMiddlewareOptions } from '../wallet'; + +type GetAccounts = WalletMiddlewareOptions['getAccounts']; const ADDRESS_MOCK = '0x123abc123abc123abc123abc123abc123abc123a'; const CHAIN_ID_MOCK = '0x1'; @@ -26,10 +29,12 @@ describe('wallet_getCapabilities', () => { let request: JsonRpcRequest; let params: GetCapabilitiesParams; let response: PendingJsonRpcResponse; + let getAccountsMock: jest.MockedFn; let getCapabilitiesMock: jest.MockedFunction; async function callMethod() { return walletGetCapabilities(request, response, { + getAccounts: getAccountsMock, getCapabilities: getCapabilitiesMock, }); } @@ -41,6 +46,7 @@ describe('wallet_getCapabilities', () => { params = request.params as GetCapabilitiesParams; response = {} as PendingJsonRpcResponse; + getAccountsMock = jest.fn().mockResolvedValue([ADDRESS_MOCK]); getCapabilitiesMock = jest.fn().mockResolvedValue(RESULT_MOCK); }); @@ -72,7 +78,9 @@ describe('wallet_getCapabilities', () => { it('throws if no hook', async () => { await expect( - walletGetCapabilities(request, response, {}), + walletGetCapabilities(request, response, { + getAccounts: getAccountsMock, + }), ).rejects.toMatchInlineSnapshot(`[Error: Method not supported.]`); }); @@ -115,4 +123,12 @@ describe('wallet_getCapabilities', () => { 0 - Expected a string matching \`/^0x[0-9a-fA-F]{40}$/\` but received "0x123"] `); }); + + it('throws if from is not in accounts', async () => { + getAccountsMock.mockResolvedValueOnce([]); + + await expect(callMethod()).rejects.toMatchInlineSnapshot( + `[Error: The requested account and/or method has not been authorized by the user.]`, + ); + }); }); diff --git a/src/methods/wallet-get-capabilities.ts b/src/methods/wallet-get-capabilities.ts index c1bbbd5..ac6c246 100644 --- a/src/methods/wallet-get-capabilities.ts +++ b/src/methods/wallet-get-capabilities.ts @@ -9,7 +9,10 @@ import type { } from '@metamask/utils'; import { StrictHexStruct, HexChecksumAddressStruct } from '@metamask/utils'; -import { validateParams } from '../utils/validation'; +import { + validateAndNormalizeKeyholder, + validateParams, +} from '../utils/validation'; const GetCapabilitiesStruct = tuple([ HexChecksumAddressStruct, @@ -29,8 +32,10 @@ export async function walletGetCapabilities( req: JsonRpcRequest, res: PendingJsonRpcResponse, { + getAccounts, getCapabilities, }: { + getAccounts: (req: JsonRpcRequest) => Promise; getCapabilities?: GetCapabilitiesHook; }, ): Promise { @@ -42,6 +47,11 @@ export async function walletGetCapabilities( const address = req.params[0]; const chainIds = req.params[1]; + + await validateAndNormalizeKeyholder(address, req, { + getAccounts, + }); + const capabilities = await getCapabilities(address, chainIds, req); res.result = capabilities; diff --git a/src/utils/validation.ts b/src/utils/validation.ts index 9600a39..0913574 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -42,7 +42,7 @@ export function validateParams( const [error] = validate(value, struct); if (error) { - throw rpcErrors.invalidInput( + throw rpcErrors.invalidParams( formatValidationError(error, `Invalid params`), ); } diff --git a/src/wallet.ts b/src/wallet.ts index 4ef664f..6f44309 100644 --- a/src/wallet.ts +++ b/src/wallet.ts @@ -138,7 +138,7 @@ WalletMiddlewareOptions): JsonRpcMiddleware { // EIP-5792 wallet_getCapabilities: createAsyncMiddleware(async (params, req) => - walletGetCapabilities(params, req, { getCapabilities }), + walletGetCapabilities(params, req, { getAccounts, getCapabilities }), ), wallet_sendCalls: createAsyncMiddleware(async (params, req) => walletSendCalls(params, req, { getAccounts, processSendCalls }),