diff --git a/.env.example b/.env.example index 1fdd2432..7cfc33a6 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,3 @@ PRIVATE_KEY= -BUNDLER_URL=https://bundler.biconomy.io/api/v2/80001/nJPK7B3ru.dd7f7861-190d-41bd-af80-6877f74b8f44 \ No newline at end of file +BUNDLER_URL= +PAYMASTER_URL= \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 65a1c364..e775712c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,7 @@ name: Build on: pull_request: - + types: [opened, reopened, synchronize, ready_for_review] jobs: build: name: build diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 8c9b526a..d84ea290 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,6 +1,7 @@ name: coverage on: pull_request: + types: [opened, reopened, synchronize, ready_for_review] jobs: coverage: name: coverage @@ -15,4 +16,5 @@ jobs: run: bun run test:ci env: PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} - BUNDLER_URL: ${{ secrets.BUNDLER_URL }} \ No newline at end of file + BUNDLER_URL: ${{ secrets.BUNDLER_URL }} + PAYMASTER_URL: ${{ secrets.PAYMASTER_URL }} \ No newline at end of file diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index 5103ba2a..2d2ce5cb 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint.yml @@ -1,6 +1,7 @@ name: pr lint on: pull_request: + types: [opened, reopened, synchronize, ready_for_review] jobs: enforce_title: name: pr lint diff --git a/.github/workflows/size-report.yml b/.github/workflows/size-report.yml new file mode 100644 index 00000000..03eaa27b --- /dev/null +++ b/.github/workflows/size-report.yml @@ -0,0 +1,27 @@ +name: size report +on: + pull_request: + types: [opened, reopened, synchronize, ready_for_review] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + size-report: + name: size report + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - name: Clone repository + uses: actions/checkout@v3 + + - name: Install dependencies + uses: ./.github/actions/install-dependencies + + - name: Report bundle size + uses: andresz1/size-limit-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + package_manager: bun \ No newline at end of file diff --git a/.size-limit.json b/.size-limit.json index b15afc33..01dd9552 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -11,9 +11,21 @@ "limit": "10 kB" }, { - "name": "core (tree-shaking)", + "name": "smartAccount (tree-shaking)", "path": "./dist/_esm/index.js", "limit": "5 kB", "import": "{ createSmartAccount }" + }, + { + "name": "bundler (tree-shaking)", + "path": "./dist/_esm/bundler/index.js", + "limit": "5 kB", + "import": "{ createBundlerClient }" + }, + { + "name": "paymaster (tree-shaking)", + "path": "./dist/_esm/paymaster/index.js", + "limit": "5 kB", + "import": "{ createPaymasterClient }" } ] diff --git a/bun.lockb b/bun.lockb index 0a917b11..83cd918c 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 87bb005d..694a5624 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "typings": "./dist/_types/index.d.ts", "homepage": "https://biconomy.io", "sideEffects": false, - "name": "@biconomy/core", + "name": "@biconomy/sdk", "author": "Biconomy", "version": "0.0.1", "description": "sdk for account abstraction, smart accounts, erc-4337 and erc-7579.", @@ -54,7 +54,7 @@ "build:cjs": "tsc --project ./tsconfig/tsconfig.cjs.json && tsc-alias -p ./tsconfig/tsconfig.cjs.json && echo > ./dist/_cjs/package.json '{\"type\":\"commonjs\"}'", "build:esm": "tsc --project ./tsconfig/tsconfig.esm.json && tsc-alias -p ./tsconfig/tsconfig.esm.json && echo > ./dist/_esm/package.json '{\"type\": \"module\",\"sideEffects\":false}'", "build:types": "tsc --project ./tsconfig/tsconfig.types.json && tsc-alias -p ./tsconfig/tsconfig.types.json", - "clean": "rimraf ./dist/_esm ./dist/_cjs ./dist/_types ./src/tsconfig", + "clean": "rimraf ./dist/_esm ./dist/_cjs ./dist/_types ./dist/tsconfig", "test": "vitest dev -c ./tests/vitest.config.ts", "test:ci": "CI=true vitest -c ./tests/vitest.config.ts --coverage", "size": "size-limit", @@ -78,6 +78,7 @@ "simple-git-hooks": "^2.9.0", "size-limit": "^11", "tsc-alias": "^1.8.8", + "tslib": "^2.6.2", "typedoc": "^0.25.9", "vitest": "^1.3.1" }, diff --git a/src/paymaster/actions/index.ts b/src/paymaster/actions/index.ts new file mode 100644 index 00000000..b2e19593 --- /dev/null +++ b/src/paymaster/actions/index.ts @@ -0,0 +1,17 @@ +import { sponsorUserOperation } from "./sponsorUserOperation.js" + +export type PaymasterRpcSchema = [ + { + Method: "eth_chainId" + Params: [] + ReturnType: bigint + } +] + +export type PaymasterActions = Record< + "sponsorUserOperation", + () => Promise +> +export const paymasterActions = () => (): PaymasterActions => ({ + sponsorUserOperation: () => sponsorUserOperation() +}) diff --git a/src/paymaster/actions/sponsorUserOp.ts b/src/paymaster/actions/sponsorUserOp.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/paymaster/actions/sponsorUserOperation.ts b/src/paymaster/actions/sponsorUserOperation.ts new file mode 100644 index 00000000..0c587cd2 --- /dev/null +++ b/src/paymaster/actions/sponsorUserOperation.ts @@ -0,0 +1,3 @@ +export const sponsorUserOperation = async () => { + return 1 +} diff --git a/src/paymaster/createPaymasterClient.ts b/src/paymaster/createPaymasterClient.ts index e69de29b..b68346f0 100644 --- a/src/paymaster/createPaymasterClient.ts +++ b/src/paymaster/createPaymasterClient.ts @@ -0,0 +1,56 @@ +import { + type Account, + type Chain, + type Client, + type PublicClientConfig, + type Transport, + createClient +} from "viem" +import { + type PaymasterActions, + type PaymasterRpcSchema, + paymasterActions +} from "./actions" + +export type PaymasterClient< + TChain extends Chain | undefined = Chain | undefined +> = Client< + Transport, + TChain, + Account | undefined, + PaymasterRpcSchema, + PaymasterActions +> + +/** + * Creates a Paymaster Client with a given [Transport](https://viem.sh/docs/clients/intro.html) configured for a [Chain](https://viem.sh/docs/clients/chains.html). + * + * A Paymaster Client is an interface to "paymaster endpoints" [JSON-RPC API](https://docs..io/reference/verifying-paymaster/endpoints) methods such as sponsoring user operation, etc through Paymaster Actions. + * + * @param config - {@link PublicClientConfig} + * @returns A Paymaster Client. {@link PaymasterClient} + * + * @example + * import { createPublicClient, http } from 'viem' + * import { polygonMumbai } from 'viem/chains' + * + * const PaymasterClient = createPaymasterClient({ + * chain: polygonMumbai, + * transport: http("https://paymaster.biconomy.io/api/v1/80001/YOUR_API_KEY_HERE"), + * }) + */ +export const createPaymasterClient = < + transport extends Transport = Transport, + chain extends Chain | undefined = undefined +>( + parameters: PublicClientConfig +): PaymasterClient => { + const { key = "public", name = "Paymaster Client" } = parameters + const client = createClient({ + ...parameters, + key, + name, + type: "PaymasterClient" + }) + return client.extend(paymasterActions()) +} diff --git a/src/paymaster/index.ts b/src/paymaster/index.ts new file mode 100644 index 00000000..70417b0e --- /dev/null +++ b/src/paymaster/index.ts @@ -0,0 +1,3 @@ +export type { PaymasterRpcSchema, PaymasterActions } from "./actions/index.js" +export { paymasterActions } from "./actions/index.js" +export { createPaymasterClient } from "./createPaymasterClient.js" diff --git a/src/paymaster/utils/helpers.ts b/src/paymaster/utils/helpers.ts new file mode 100644 index 00000000..cf72437d --- /dev/null +++ b/src/paymaster/utils/helpers.ts @@ -0,0 +1,18 @@ +/** + * Extracts the chain ID from a paymaster URL. + * @param url - The bundler URL. + * @returns The extracted chain ID. + * @throws Error if the chain ID cannot be extracted or is invalid. + */ +export const extractChainIdFromPaymasterUrl = (url: string): number => { + try { + const regex = /\/api\/v\d+\/(\d+)\// + const match = regex.exec(url) + if (!match) { + throw new Error("Invalid URL format") + } + return Number.parseInt(match[1]) + } catch (error) { + throw new Error("Invalid chain id") + } +} diff --git a/tests/account.test.ts b/tests/account.test.ts index 0a6b9a46..810a46f3 100644 --- a/tests/account.test.ts +++ b/tests/account.test.ts @@ -64,10 +64,8 @@ describe("Biconomy Smart Account core tests", () => { test("Should get account address + nonce", async () => { const address = smartAccount.address - console.log("Smart Account Address: ", address) - const nonce = await smartAccount.getNonce() - console.log("Smart Account Nonce: ", nonce) + expect(address).toBeTypeOf("string") }) test("Should sign a user operation", async () => { diff --git a/tests/paymaster.test.ts b/tests/paymaster.test.ts new file mode 100644 index 00000000..8205ff13 --- /dev/null +++ b/tests/paymaster.test.ts @@ -0,0 +1,33 @@ +import { http } from "viem" +import { describe, expect, it } from "vitest" + +import { privateKeyToAccount } from "viem/accounts" +import { getChain } from "../src/account/utils/helpers.js" +import { createPaymasterClient } from "../src/paymaster/createPaymasterClient.js" +import { extractChainIdFromPaymasterUrl } from "../src/paymaster/utils/helpers.js" + +describe("Paymaster tests", () => { + const paymasterUrl = process.env.PAYMASTER_URL ?? "" + const chainId = extractChainIdFromPaymasterUrl(paymasterUrl) + const chain = getChain(chainId) + const account = privateKeyToAccount(`0x${process.env.PRIVATE_KEY}`) + + it("Should have the properties of a viem client", async () => { + const paymasterClient = createPaymasterClient({ + chain, + transport: http(paymasterUrl) + }) + expect(paymasterClient.uid).toBeDefined() + expect(paymasterClient?.chain?.id).toBe(chainId) + expect(paymasterClient.pollingInterval).toBeDefined() + }) + + it("Should have a paymaster specific action", async () => { + const paymasterClient = createPaymasterClient({ + chain, + transport: http(paymasterUrl) + }) + const result = await paymasterClient.sponsorUserOperation() + expect(result).toBeTruthy() + }) +}) diff --git a/tests/utils.ts b/tests/utils.ts index dc971c54..84983283 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -1,5 +1,5 @@ export const envCheck = () => { - const fields = ["BUNDLER_URL", "PRIVATE_KEY"] + const fields = ["BUNDLER_URL", "PRIVATE_KEY", "PAYMASTER_URL"] const errorFields = fields.filter((field) => !process.env[field]) if (errorFields.length) { throw new Error(