Skip to content

Commit 91c9f60

Browse files
authored
Add fetch permission RPC method (#101)
* Add fetch permission RPC method and public utility * Apply formatting fixes * align fetchPermissions * typecheck
1 parent 1387ecd commit 91c9f60

File tree

8 files changed

+274
-86
lines changed

8 files changed

+274
-86
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { SpendPermission } from './coinbase_fetchSpendPermissions.js';
2+
3+
export type FetchPermissionRequest = {
4+
method: 'coinbase_fetchPermission';
5+
params: [{ permissionHash: string }];
6+
};
7+
8+
export type FetchPermissionResponse = {
9+
permission: SpendPermission;
10+
};
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
export * from './methods/fetchPermissions.node.js';
1+
export * from './methods/fetchPermission.js'; // Same as browser environment.
2+
export * from './methods/fetchPermissions.js'; // Same as browser environment.
23
export * from './methods/getPermissionStatus.node.js';
34
export * from './methods/prepareRevokeCallData.js'; // Same as browser environment.
45
export * from './methods/prepareSpendCallData.node.js';

packages/account-sdk/src/interface/public-utilities/spend-permission/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from './methods/fetchPermission.js';
12
export * from './methods/fetchPermissions.js';
23
export * from './methods/getHash.js';
34
export * from './methods/getPermissionStatus.js';
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { CB_WALLET_RPC_URL } from ':core/constants.js';
2+
import { ProviderInterface } from ':core/provider/interface.js';
3+
import { FetchPermissionResponse } from ':core/rpc/coinbase_fetchPermission.js';
4+
import { SpendPermission } from ':core/rpc/coinbase_fetchSpendPermissions.js';
5+
import { fetchRPCRequest } from ':util/provider.js';
6+
import { withTelemetry } from '../withTelemetry.js';
7+
8+
type FetchPermissionType = {
9+
permissionHash: string;
10+
provider?: ProviderInterface;
11+
};
12+
13+
/**
14+
* Fetches a single spend permission by its hash.
15+
*
16+
* This helper method retrieves a specific spend permission using its unique hash identifier.
17+
* This is useful when you have a permission hash and need to retrieve the full permission details.
18+
*
19+
* The method uses coinbase_fetchPermission RPC method to query the permission
20+
* from the backend service. If a provider is supplied, it will use that provider to make the request.
21+
* Otherwise, it will make a direct RPC request to the Coinbase Wallet RPC endpoint.
22+
*
23+
* @param params - The parameters for the fetchPermission method.
24+
* @param params.permissionHash - The hash of the permission to fetch.
25+
* @param params.provider - Optional provider interface used to make the coinbase_fetchPermission request.
26+
*
27+
* @returns A promise that resolves to a SpendPermission object.
28+
*
29+
* @example
30+
* ```typescript
31+
* import { fetchPermission } from '@base-org/account/spend-permission';
32+
*
33+
* // Fetch a specific permission by its hash (with provider)
34+
* const permission = await fetchPermission({
35+
* provider, // Base Account Provider
36+
* permissionHash: '0x71319cd488f8e4f24687711ec5c95d9e0c1bacbf5c1064942937eba4c7cf2984'
37+
* });
38+
*
39+
* // Fetch a specific permission by its hash (without provider)
40+
* const permission = await fetchPermission({
41+
* permissionHash: '0x71319cd488f8e4f24687711ec5c95d9e0c1bacbf5c1064942937eba4c7cf2984'
42+
* });
43+
*
44+
* console.log(`Permission for token: ${permission.permission.token}`);
45+
* console.log(`Spender: ${permission.permission.spender}`);
46+
* console.log(`Allowance: ${permission.permission.allowance}`);
47+
* ```
48+
*/
49+
const fetchPermissionFn = async ({
50+
provider,
51+
permissionHash,
52+
}: FetchPermissionType): Promise<SpendPermission> => {
53+
let response: FetchPermissionResponse;
54+
55+
if (provider) {
56+
response = (await provider.request({
57+
method: 'coinbase_fetchPermission',
58+
params: [
59+
{
60+
permissionHash,
61+
},
62+
],
63+
})) as FetchPermissionResponse;
64+
} else {
65+
response = (await fetchRPCRequest(
66+
{
67+
method: 'coinbase_fetchPermission',
68+
params: [
69+
{
70+
permissionHash,
71+
},
72+
],
73+
},
74+
CB_WALLET_RPC_URL
75+
)) as FetchPermissionResponse;
76+
}
77+
78+
return response.permission;
79+
};
80+
81+
export const fetchPermission = withTelemetry(fetchPermissionFn);

packages/account-sdk/src/interface/public-utilities/spend-permission/methods/fetchPermissions.node.test.ts

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import { CB_WALLET_RPC_URL } from ':core/constants.js';
2+
import { ProviderInterface } from ':core/provider/interface.js';
23
import {
34
FetchPermissionsResponse,
45
SpendPermission,
56
} from ':core/rpc/coinbase_fetchSpendPermissions.js';
67
import { fetchRPCRequest } from ':util/provider.js';
78
import { beforeEach, describe, expect, it, vi } from 'vitest';
8-
import { fetchPermissions } from './fetchPermissions.node.js';
9+
import { fetchPermissions } from './fetchPermissions.js';
910

1011
// Mock the provider utility
1112
vi.mock(':util/provider.js');
1213

1314
const mockFetchRPCRequest = vi.mocked(fetchRPCRequest);
1415

15-
describe('fetchPermissions (node)', () => {
16+
describe('fetchPermissions (without provider)', () => {
1617
beforeEach(() => {
1718
vi.clearAllMocks();
1819
});
@@ -203,3 +204,118 @@ describe('fetchPermissions (node)', () => {
203204
});
204205
});
205206
});
207+
208+
describe('fetchPermissions (with provider)', () => {
209+
const mockProvider = {
210+
request: vi.fn(),
211+
} as unknown as ProviderInterface;
212+
213+
beforeEach(() => {
214+
vi.clearAllMocks();
215+
});
216+
217+
describe('successful requests', () => {
218+
it('should fetch permissions successfully using provider', async () => {
219+
const mockPermissions: SpendPermission[] = [
220+
{
221+
createdAt: 1234567890,
222+
permissionHash: '0xabcdef123456',
223+
signature: '0x987654321fedcba',
224+
chainId: 8453,
225+
permission: {
226+
account: '0x1234567890abcdef1234567890abcdef12345678',
227+
spender: '0x5678901234567890abcdef1234567890abcdef12',
228+
token: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
229+
allowance: '1000000000000000000',
230+
period: 86400,
231+
start: 1234567890,
232+
end: 1234654290,
233+
salt: '123456789',
234+
extraData: '0x',
235+
},
236+
},
237+
];
238+
239+
const mockResponse: FetchPermissionsResponse = {
240+
permissions: mockPermissions,
241+
};
242+
243+
vi.mocked(mockProvider.request).mockResolvedValue(mockResponse);
244+
245+
const result = await fetchPermissions({
246+
provider: mockProvider,
247+
account: '0x1234567890abcdef1234567890abcdef12345678',
248+
chainId: 8453,
249+
spender: '0x5678901234567890abcdef1234567890abcdef12',
250+
});
251+
252+
expect(result).toEqual(mockPermissions);
253+
expect(mockProvider.request).toHaveBeenCalledWith({
254+
method: 'coinbase_fetchPermissions',
255+
params: [
256+
{
257+
account: '0x1234567890abcdef1234567890abcdef12345678',
258+
chainId: '0x2105',
259+
spender: '0x5678901234567890abcdef1234567890abcdef12',
260+
},
261+
],
262+
});
263+
// Ensure fetchRPCRequest was NOT called when provider is provided
264+
expect(mockFetchRPCRequest).not.toHaveBeenCalled();
265+
});
266+
});
267+
268+
describe('parameter handling', () => {
269+
it('should convert chainId to hex format correctly with provider', async () => {
270+
const mockResponse: FetchPermissionsResponse = {
271+
permissions: [],
272+
};
273+
274+
vi.mocked(mockProvider.request).mockResolvedValue(mockResponse);
275+
276+
const testCases = [
277+
{ input: 1, expected: '0x1' },
278+
{ input: 8453, expected: '0x2105' },
279+
{ input: 137, expected: '0x89' },
280+
];
281+
282+
for (const testCase of testCases) {
283+
vi.mocked(mockProvider.request).mockClear();
284+
285+
await fetchPermissions({
286+
provider: mockProvider,
287+
account: '0x1234567890abcdef1234567890abcdef12345678',
288+
chainId: testCase.input,
289+
spender: '0x5678901234567890abcdef1234567890abcdef12',
290+
});
291+
292+
expect(mockProvider.request).toHaveBeenCalledWith({
293+
method: 'coinbase_fetchPermissions',
294+
params: [
295+
{
296+
account: '0x1234567890abcdef1234567890abcdef12345678',
297+
chainId: testCase.expected,
298+
spender: '0x5678901234567890abcdef1234567890abcdef12',
299+
},
300+
],
301+
});
302+
}
303+
});
304+
});
305+
306+
describe('error handling', () => {
307+
it('should propagate provider errors', async () => {
308+
const errorMessage = 'Provider error';
309+
vi.mocked(mockProvider.request).mockRejectedValue(new Error(errorMessage));
310+
311+
await expect(
312+
fetchPermissions({
313+
provider: mockProvider,
314+
account: '0x1234567890abcdef1234567890abcdef12345678',
315+
chainId: 8453,
316+
spender: '0x5678901234567890abcdef1234567890abcdef12',
317+
})
318+
).rejects.toThrow(errorMessage);
319+
});
320+
});
321+
});

packages/account-sdk/src/interface/public-utilities/spend-permission/methods/fetchPermissions.node.ts

Lines changed: 0 additions & 69 deletions
This file was deleted.

0 commit comments

Comments
 (0)