Skip to content

Commit

Permalink
feat: Add onchain kit paymaster utilities (#515)
Browse files Browse the repository at this point in the history
  • Loading branch information
cpcramer committed Jun 13, 2024
1 parent 462e848 commit 0540295
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 9 deletions.
42 changes: 42 additions & 0 deletions template/web/app/api/paymaster-proxy/route.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { isValidAAEntrypoint, isWalletASmartWallet } from '@coinbase/onchainkit/wallet';
import { NextRequest } from 'next/server';
import { paymasterClient } from '@/utils/paymasterClient';
import { POST } from './route';

jest.mock('@coinbase/onchainkit/wallet', () => ({
isValidAAEntrypoint: jest.fn(),
isWalletASmartWallet: jest.fn(),
}));
jest.mock('../../../src/utils/paymasterClient');

describe('POST function', () => {
Expand All @@ -10,6 +15,8 @@ describe('POST function', () => {
});

it('should return 200 and the result on success for pm_getPaymasterStubData', async () => {
(isValidAAEntrypoint as jest.Mock).mockReturnValue(true);
(isWalletASmartWallet as jest.Mock).mockResolvedValue(true);
(paymasterClient.getPaymasterStubData as jest.Mock).mockResolvedValue('stub data result');

const req = {
Expand All @@ -27,6 +34,8 @@ describe('POST function', () => {
});

it('should return 200 and the result on success for pm_getPaymasterData', async () => {
(isValidAAEntrypoint as jest.Mock).mockReturnValue(true);
(isWalletASmartWallet as jest.Mock).mockResolvedValue(true);
(paymasterClient.getPaymasterData as jest.Mock).mockResolvedValue('paymaster data result');

const req = {
Expand All @@ -42,4 +51,37 @@ describe('POST function', () => {
expect(response.status).toBe(200);
expect(json.result).toBe('paymaster data result');
});
it('should return 400 and error message if isValidAAEntrypoint returns false', async () => {
(isValidAAEntrypoint as jest.Mock).mockReturnValue(false);

const req = {
json: jest.fn().mockResolvedValue({
method: 'someMethod',
params: [{}, 'entrypoint', '1'],
}),
} as unknown as NextRequest;

const response = await POST(req);
const json = (await response.json()) as { error: string };

expect(response.status).toBe(400);
expect(json.error).toBe('invalid entrypoint');
});
it('should return 400 and error message if isWalletASmartWallet returns false', async () => {
(isValidAAEntrypoint as jest.Mock).mockReturnValue(true);
(isWalletASmartWallet as jest.Mock).mockResolvedValue(false);

const req = {
json: jest.fn().mockResolvedValue({
method: 'someMethod',
params: [{}, 'entrypoint', '1'],
}),
} as unknown as NextRequest;

const response = await POST(req);
const json = (await response.json()) as { error: string };

expect(response.status).toBe(400);
expect(json.error).toBe('invalid wallet');
});
});
24 changes: 22 additions & 2 deletions template/web/app/api/paymaster-proxy/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { isValidAAEntrypoint, isWalletASmartWallet } from '@coinbase/onchainkit/wallet';
import { NextRequest, NextResponse } from 'next/server';
import { UserOperation } from 'permissionless';
import { paymasterClient } from '@/utils/paymasterClient';
import { client, paymasterClient } from '@/utils/paymasterClient';
import type {
IsValidAAEntrypointOptions,
IsWalletASmartWalletOptions,
} from '@coinbase/onchainkit/wallet';

type PaymasterRequest = {
method: string;
Expand All @@ -16,7 +21,22 @@ type PaymasterRequest = {
export async function POST(req: NextRequest): Promise<NextResponse> {
const reqBody: PaymasterRequest = (await req.json()) as PaymasterRequest;
const { method, params } = reqBody;
const [userOp] = params;
const [userOp, entrypoint] = params;

// Verify the entrypoint address
if (!isValidAAEntrypoint({ entrypoint } as IsValidAAEntrypointOptions)) {
return NextResponse.json({ error: 'invalid entrypoint' }, { status: 400 });
}

// Validate the User Operation by checking if the sender address is a proxy with the expected bytecode.
if (
!(await isWalletASmartWallet({
client,
userOp,
} as IsWalletASmartWalletOptions))
) {
return NextResponse.json({ error: 'invalid wallet' }, { status: 400 });
}

try {
let result;
Expand Down
1 change: 1 addition & 0 deletions template/web/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const customJestConfig = {
coverageReporters: ['clover', 'json', 'lcov', 'text'],
moduleNameMapper: {
'rehype-pretty-code': '<rootDir>/node_modules/rehype-pretty-code',
'@coinbase/onchainkit/wallet': '<rootDir>/node_modules/@coinbase/onchainkit/esm/wallet',
},
roots: ['<rootDir>/src', '<rootDir>/app/api/paymaster-proxy'],
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
Expand Down
2 changes: 1 addition & 1 deletion template/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"test:coverage:open": "yarn test:coverage && open coverage/lcov-report/index.html"
},
"dependencies": {
"@coinbase/onchainkit": "0.16.10",
"@coinbase/onchainkit": "^0.19.7",
"@coinbase/wallet-sdk": "4.0.0",
"@farcaster/hub-nodejs": "^0.11.9",
"@radix-ui/react-dropdown-menu": "^2.0.6",
Expand Down
7 changes: 6 additions & 1 deletion template/web/src/utils/paymasterClient.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { ENTRYPOINT_ADDRESS_V06 } from 'permissionless';
import { paymasterActionsEip7677 } from 'permissionless/experimental';
import { createClient, http } from 'viem';
import { createClient, http, createPublicClient } from 'viem';
import { baseSepolia } from 'viem/chains';

const paymasterService = process.env.NEXT_PUBLIC_PAYMASTER_URL ?? '';

export const client = createPublicClient({
chain: baseSepolia,
transport: http(),
});

export const paymasterClient = createClient({
chain: baseSepolia,
transport: http(paymasterService),
Expand Down
11 changes: 6 additions & 5 deletions template/web/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1532,7 +1532,7 @@ __metadata:
"@babel/preset-env": "npm:^7.23.5"
"@babel/preset-react": "npm:^7.23.3"
"@babel/preset-typescript": "npm:^7.23.3"
"@coinbase/onchainkit": "npm:0.16.10"
"@coinbase/onchainkit": "npm:^0.19.7"
"@coinbase/wallet-sdk": "npm:4.0.0"
"@farcaster/hub-nodejs": "npm:^0.11.9"
"@jest/globals": "npm:^29.7.0"
Expand Down Expand Up @@ -1617,20 +1617,21 @@ __metadata:
languageName: unknown
linkType: soft

"@coinbase/onchainkit@npm:0.16.10":
version: 0.16.10
resolution: "@coinbase/onchainkit@npm:0.16.10"
"@coinbase/onchainkit@npm:^0.19.7":
version: 0.19.7
resolution: "@coinbase/onchainkit@npm:0.19.7"
peerDependencies:
"@coinbase/wallet-sdk": ^4
"@tanstack/react-query": ^5
"@xmtp/frames-validator": ^0.6.0
graphql: ^14 || ^15 || ^16
graphql-request: ^6.1.0
permissionless: ^0.1.29
react: ^18
react-dom: ^18
viem: ^2
wagmi: ^2
checksum: 10c0/4cacee8f7ed3c59f3234f6db6a2d8ff5491dbce435128053c3ef712da110a528c74ad337b67ea2c54d8578f323c3e419cd994c863f950b32fde970dee8065e6e
checksum: 10c0/aab1679e4eea240886632aefe9405db9b4b95a9e474838151079ac0e72212e638d7d7aa35aa6fdb2df8ced38e29bee8cba795bb078764ae28f8e783f9073f0c8
languageName: node
linkType: hard

Expand Down

0 comments on commit 0540295

Please sign in to comment.