UNPKG

near-safe-test

Version:

An SDK for controlling Ethereum Smart Accounts via ERC4337 from a Near Account.

109 lines (108 loc) 4.17 kB
import { createPublicClient, http, rpcSchema, RpcError, HttpRequestError, } from "viem"; import { PLACEHOLDER_SIG } from "../util"; function bundlerUrl(chainId, apikey) { return `https://api.pimlico.io/v2/${chainId}/rpc?apikey=${apikey}`; } export class Erc4337Bundler { client; entryPointAddress; apiKey; chainId; constructor(entryPointAddress, apiKey, chainId) { this.entryPointAddress = entryPointAddress; this.apiKey = apiKey; this.chainId = chainId; this.client = createPublicClient({ transport: http(bundlerUrl(chainId, this.apiKey)), rpcSchema: rpcSchema(), }); } async getPaymasterData(rawUserOp, sponsorshipPolicy) { const userOp = { ...rawUserOp, signature: PLACEHOLDER_SIG }; if (sponsorshipPolicy) { console.log("Requesting paymaster data..."); return handleRequest(() => this.client.request({ method: "pm_sponsorUserOperation", params: [ userOp, this.entryPointAddress, { sponsorshipPolicyId: sponsorshipPolicy }, ], })); } console.log("Estimating user operation gas..."); return handleRequest(() => this.client.request({ method: "eth_estimateUserOperationGas", params: [userOp, this.entryPointAddress], })); } async sendUserOperation(userOp) { return handleRequest(() => this.client.request({ method: "eth_sendUserOperation", params: [userOp, this.entryPointAddress], })); // throw new Error(`Failed to send user op with: ${error.message}`); } async getGasPrice() { return handleRequest(() => this.client.request({ method: "pimlico_getUserOperationGasPrice", params: [], })); } async getUserOpReceipt(userOpHash) { let userOpReceipt = null; while (!userOpReceipt) { // Wait 2 seconds before checking the status again await new Promise((resolve) => setTimeout(resolve, 2000)); userOpReceipt = await this._getUserOpReceiptInner(userOpHash); } return userOpReceipt; } async _getUserOpReceiptInner(userOpHash) { return handleRequest(() => this.client.request({ method: "eth_getUserOperationReceipt", params: [userOpHash], })); } // New method to query sponsorship policies async getSponsorshipPolicies() { const url = `https://api.pimlico.io/v2/account/sponsorship_policies?apikey=${this.apiKey}`; const allPolocies = await handleRequest(async () => { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}: ${response.statusText}`); } return response.json(); }); return allPolocies.data.filter((p) => p.chain_ids.allowlist.includes(this.chainId)); } } async function handleRequest(clientMethod) { try { return await clientMethod(); } catch (error) { const message = stripApiKey(error); if (error instanceof HttpRequestError) { if (error.status === 401) { throw new Error("Unauthorized request. Please check your Pimlico API key."); } else { throw new Error(`Pimlico: ${message}`); } } else if (error instanceof RpcError) { throw new Error(`Failed to send user op with: ${message}`); } throw new Error(`Bundler Request: ${message}`); } } export function stripApiKey(error) { const message = error instanceof Error ? error.message : String(error); return message.replace(/(apikey=)[^\s&]+/, "$1***"); // Could also do this with slicing. // const keyStart = message.indexOf("apikey=") + 7; // // If no apikey in the message, return it as is. // if (keyStart === -1) return message; // return `${message.slice(0, keyStart)}***${message.slice(keyStart + 36)}`; }