Skip to content

Commit

Permalink
Merge branch 'feat-paymaster'
Browse files Browse the repository at this point in the history
  • Loading branch information
roushou committed Jun 21, 2024
2 parents 4623528 + f57da62 commit 475d44d
Show file tree
Hide file tree
Showing 11 changed files with 442 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/cyan-crabs-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@coinbase-platform/paymaster": minor
---

feat: add `paymaster` package
53 changes: 53 additions & 0 deletions packages/paymaster/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "@coinbase-platform/paymaster",
"version": "0.0.0",
"description": "Coinbase platform Paymaster package",
"author": "roushou <roushou9@gmail.com>",
"license": "MIT",
"homepage": "https://github.com/roushou/coinbase-platform#readme",
"type": "module",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"typings": "./dist/index.d.ts",
"repository": {
"type": "git",
"url": "git+https://github.com/roushou/coinbase-platform.git",
"directory": "packages/paymaster"
},
"bugs": {
"url": "https://github.com/roushou/coinbase-platform/issues"
},
"publishConfig": {
"access": "public"
},
"keywords": ["coinbase", "coinbase platform", "paymaster"],
"scripts": {
"build": "pnpm clean && tsup",
"clean": "rimraf ./dist",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@coinbase-platform/utils": "workspace:*"
},
"peerDependencies": {
"typescript": ">=5.0.4"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
},
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"./package.json": "./package.json"
},
"files": ["src", "dist"],
"engine": {
"node": "^18.0.0 || >=20.0.0"
}
}
2 changes: 2 additions & 0 deletions packages/paymaster/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { createRpcClient } from "./rpc";
export type { RpcClient, RpcClientConfig } from "./rpc";
85 changes: 85 additions & 0 deletions packages/paymaster/src/mocks/handlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { RPC_URL } from "@coinbase-platform/utils/constants";
import { http, HttpResponse } from "msw";
import type { DefaultBodyType, PathParams } from "msw";
import { withRpcMethod } from "./predicates";

export const handlers = [
http.post<PathParams, { method: string }, DefaultBodyType>(
`${RPC_URL}/API_KEY`,
withRpcMethod({ method: "eth_supportedEntryPoints" }, async () => {
return HttpResponse.json({
id: 1,
jsonrpc: "2.0",
result: ["0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"],
});
}),
),
http.post<PathParams, { method: string }, DefaultBodyType>(
`${RPC_URL}/API_KEY`,
withRpcMethod({ method: "eth_getUserOperationByHash" }, async () => {
return HttpResponse.json({
id: 1,
jsonrpc: "2.0",
// TODO: set value
result: [],
});
}),
),
http.post<PathParams, { method: string }, DefaultBodyType>(
`${RPC_URL}/API_KEY`,
withRpcMethod({ method: "eth_getUserOperationReceipt" }, async () => {
return HttpResponse.json({
id: 1,
jsonrpc: "2.0",
result: {
userOpHash:
"0x13574b2256b73bdc33fb121052f64b3803161e5ec602a6dc9e56177ba387e700",
entryPoint: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
sender: "0x023fEF87894773DF227587d9B29af8D17b4dBB5A",
nonce: "0x1",
paymaster: null,
actualGasCost: "0x6f75ef8d",
actualGasUsed: "0x329af",
success: true,
reason: "",
logs: [
{
address: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
topics: [
"0xbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f972",
],
data: "0x",
blockNumber: "0x27fb22e",
transactionHash:
"0x0f9b0e5868beaf345d8d55895c8037ae85adb91c422c00badcdcae8a0bf247a1",
transactionIndex: "0x4",
blockHash:
"0x965e08190b1093c078bde81f67362203834784e34cf499d516f1a7b9c7a7b29e",
logIndex: "0x13",
removed: false,
},
],
receipt: {
blockHash:
"0x965e08190b1093c078bde81f67362203834784e34cf499d516f1a7b9c7a7b29e",
blockNumber: "0x27fb22e",
from: "0x425d190ef5F561aFc8728593cA13EAf2FD9E3380",
to: "0x25aD59adbe00C2d80c86d01e2E05e1294DA84823",
cumulativeGasUsed: "0xe13e1",
gasUsed: "0x329af",
contractAddress: null,
logs: [null],
logsBloom:
"0x000000010000000000000000800000000000000000000008000000000200000000080000020000020002080100010000001080000000000000100210000000000000000000000008000000000000808010000000000000000001000000000000000000000e000000000000000000080000002200000000408880000000000040000020000000000001000000080000002040000000040000000000000008000020000000000100000040000000000000000000000000000000000220000000400000000000000000000100000010000044000000800020000a100000010020000000000040000081000000000000000000000000000000400000000000100000",
status: 1,
type: "0x2",
transactionHash:
"0x0f9b0e5868beaf345d8d55895c8037ae85adb91c422c00badcdcae8a0bf247a1",
transactionIndex: "0x4",
effectiveGasPrice: "0x6f75ef8d",
},
},
});
}),
),
];
4 changes: 4 additions & 0 deletions packages/paymaster/src/mocks/node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { setupServer } from "msw/node";
import { handlers } from "./handlers";

export const server = setupServer(...handlers);
35 changes: 35 additions & 0 deletions packages/paymaster/src/mocks/predicates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { DefaultBodyType, HttpResponseResolver, PathParams } from "msw";
import type { RpcRequestConfig } from "../rpc";

// https://mswjs.io/docs/best-practices/custom-request-predicate

export function withRpcMethod<
Params extends PathParams,
RequestBodyType extends DefaultBodyType,
ResponseBodyType extends DefaultBodyType,
>(
expectedBody: { method: RpcRequestConfig["method"] },
resolver: HttpResponseResolver<Params, RequestBodyType, ResponseBodyType>,
): HttpResponseResolver<Params, RequestBodyType, ResponseBodyType> {
return async (args) => {
const { request } = args;

// Ignore requests that have a non-JSON body.
const contentType = request.headers.get("Content-Type") || "";
if (!contentType.includes("application/json")) {
return;
}

// Ignore requests from handlers that don't specify the rpc method
if (!expectedBody || !("method" in expectedBody)) {
return;
}

const body = await request.clone().json();
if (body?.method !== expectedBody.method) {
return;
}

return resolver(args);
};
}
50 changes: 50 additions & 0 deletions packages/paymaster/src/rpc.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { RPC_URL } from "@coinbase-platform/utils/constants";
import { describe, expect, test } from "vitest";
import { createRpcClient } from "./rpc";

describe("rpc", () => {
test("should set RPC url", async () => {
const rpc = createRpcClient({
apiKey: "API_KEY",
url: "https://coinbase.com/other/rpc",
});
expect(rpc.__url).toEqual("https://coinbase.com/other/rpc");
});

test(`should default RPC url to ${RPC_URL}`, async () => {
const rpc = createRpcClient({ apiKey: "API_KEY" });
expect(rpc.__url).toEqual(RPC_URL);
});

test("should get supported entrypoints", async () => {
const rpc = createRpcClient({ apiKey: "API_KEY" });
const response = await rpc.request({
method: "eth_supportedEntryPoints",
});
expect(response).toEqual({
id: 1,
jsonrpc: "2.0",
result: ["0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"],
});
});

test("should get user operation by hash", async () => {
const rpc = createRpcClient({ apiKey: "API_KEY" });
await rpc.request({
method: "eth_getUserOperationByHash",
parameters: [
"0x77c0b560eb0b042902abc5613f768d2a6b2d67481247e9663bf4d68dec0ca122",
],
});
});

test("should get user operation receipt", async () => {
const rpc = createRpcClient({ apiKey: "API_KEY" });
await rpc.request({
method: "eth_getUserOperationReceipt",
parameters: [
"0x77c0b560eb0b042902abc5613f768d2a6b2d67481247e9663bf4d68dec0ca122",
],
});
});
});
Loading

0 comments on commit 475d44d

Please sign in to comment.