This is a simple API that signs userOps so that their fees are covered by EtherspotPaymaster contract.
The API gives paymasterAndData
and verificationGasLimit
as a response.
paymasterAndData
is a bytes array with the following content
- address of the EtherspotPaymaster contract
validUntil
andvalidAfter
- the expiration date ofpaymasterAndData
- Signature of the Paymaster - a Paymaster signs the
hash of a userOp || validUntil || validAfter
. This signature is later used to identify the Paymaster address in EtherspotPaymaster contract.
Please have an AWS setup with secrets manager ready and create api access key. Then make sure that the key has enough permissions to access AWS secrets manager
Also create a secret manager folder with prefix as arka_
and concatenate any string of your choice which can act as an api_key for calling the endpoints. For eg. arka_devTest
(api_key would be devTest
)
Inside each folder in the secrets manager in our case arka_devTest
the necessary key values are as follows
- PRIVATE_KEY - the wallet from which you wish to sponsor from
- SUPPORTED_NETWORKS - the networks you wish to support. The structure should follow this file config.json which again needs to be converted into
base64
value - ERC20_PAYMASTERS - the custom deployed pimlico erc20 paymaster contract addresses. The structure should be as follows
{
"10": {
"USDC": "0x99fB8d618F52a42049776899D5c07241D344a8A4",
"DAI": "0x3bE5380ec8cfe159f0525d16d11E9Baba516C40c",
"USDT": "0x9102889001d0901b3d9123651d492e52ce772C6b"
},
"420": {
"LINK": "0x53F48579309f8dBfFE4edE921C50200861C2482a"
},
"421613": {
"LINK": "0x0a6Aa1Bd30D6954cA525315287AdeeEcbb6eFB59"
}
} which also needs to be converted into
base64
value
npm install
npm run dev
You need to create a custom middleware for this paymaster:
import { UserOperationMiddlewareFn } from "userop";
import { OpToJSON } from "userop/dist/utils";
interface EtherspotPaymasterResult {
paymasterAndData: string;
verificationGasLimit: string;
preVerificationGas: string;
callGasLimit: string;
}
export const etherspotPaymaster =
(paymasterRpc: string, context: any): UserOperationMiddlewareFn =>
async (ctx) => {
const apiUrl = new URL('/', paymasterRpc).href;
const pm: EtherspotPaymasterResult = await fetch(apiUrl, {
method: 'POST',
body: JSON.stringify({
userOp: OpToJSON(ctx.op),
entryPoint: ctx.entryPoint,
context: ctx.context,
chainId: 80001
})
}).then(res => res.json());
if (!pm.paymasterAndData) {
throw new Error("No paymaster and data");
}
ctx.op.paymasterAndData = pm.paymasterAndData;
ctx.op.verificationGasLimit = pm.verificationGasLimit;
ctx.op.preVerificationGas = pm.preVerificationGas;
ctx.op.callGasLimit = pm.callGasLimit;
};
Config file should look like this:
{
"rpcUrl": "http://127.0.0.1:14337/80001",
"signingKey": "0x...",
"entryPoint": "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
"EtherspotWalletFactory": "0x27f11918740060bd9Be146086F6836e18eedBB8C",
"paymaster": {
"rpcUrl": "http://127.0.0.1:5050?apiKey=apiKey&chainId=80001",
"context": {
"mode": "sponsor",
}
}
}
Usage of this middleware with a simple transfer example from erc-4337-examples repo:
import { ethers } from "ethers";
import { Client } from "userop";
import { CLIOpts } from "../../src";
import { etherspotPaymaster } from '../middlewares/paymaster';
// @ts-ignore
import config from "../../config.json";
import { EtherspotAccount } from "../builder/etherspotAccount";
export default async function main(t: string, amt: string, opts: CLIOpts) {
const paymaster = opts.withPM
? etherspotPaymaster(
config.paymaster.rpcUrl,
config.paymaster.context
)
: undefined;
const simpleAccount = await EtherspotAccount.init(
new ethers.Wallet(config.signingKey),
config.rpcUrl,
config.entryPoint,
config.EtherspotWalletFactory,
paymaster
);
const client = await Client.init(config.rpcUrl, config.entryPoint);
const target = ethers.utils.getAddress(t);
const value = ethers.utils.parseEther(amt);
const res = await client.sendUserOperation(
simpleAccount.execute(target, value, "0x"),
{
dryRun: opts.dryRun,
onBuild: (op) => console.log("Signed UserOperation:", op),
}
);
console.log(`UserOpHash: ${res.userOpHash}`);
console.log("Waiting for transaction...");
const ev = await res.wait();
console.log(`Transaction hash: ${ev?.transactionHash ?? null}`);
}
If you want to contribute,
- Create a branch from master
- Do the changes you wish to include
2.1 For adding a new paymaster, add the desired route in the src/routes/index.ts file and add validators. Add the logic of getting a paymasterAndData in src/paymaster/index.ts
2.2 For adding a new env var, add the desired variable name in the plugins/config.ts under ConfigSchema such as
And use it in routes/index.ts as server.config.newEnvVar inside routes variable
const ConfigSchema = Type.Strict( Type.Object({ ..., newEnvVar: Type.String() // change the variable name and type of the desired env var }) );
- Test on your local machine
- Submit the PR for merging the changes to master and notify us.
- Also write the description of the changes made and do tell us why do you think this change is necessary and specify the env vars if needed to add
-
/
- This url accepts three parameters in body as array and returns the paymasterData, verificationGasLimit, callGasLimit and preVerificationGas Parameters:- userOp object itself in JSON format
- entryPointAddress
- context object which has one required parameter mode and three optional parameter
- mode which accepts "erc20" | "sponsor"
- token (if mode is "erc20") which accepts symbol i.e "USDC"
- validAfter - timestamp in milliseconds only applicable with mode as "sponsor" used for defining the start of the paymaster validity
- validUntil - timestamp in milliseconds only applicable with mode as "sponsor" used for defining the end of the paymaster validity
-
/pimlicoAddress
- This url accepts two parameters in body and returns the address of the deployed erc20 paymaster if exists Parameters:- entryPointAddress
- context object with token symbol i.e { token: "USDC" }
-
/whitelist
- This url accepts one parameter and returns the submitted transaction hash if successful. This url is used to whitelist an array of addresses thats needed to be whitelisted for sponsorship. Please note that all addresses needs to be addresses that wasn't been whitelisted before.- address - an array of addresses (max. 10 per request)
-
/checkWhitelist
- This url accepts two parameters in body and returns if the address has been whitelisted or not- sponsorAddress - The address of the sponsorer
- accountAddress - The address which needs to be checked
-
/deposit
- This url accepts one parameter and returns the submitted transaction hash if successful. This url is used to deposit some funds to the entryPointAddress from the sponsor wallet- amount - The amount to be deposited in ETH
-
/whitelist/v2
- This url accepts one parameter and returns a message indicating whether the offchain whitelist was successful. This url is used to whitelist an array of addresses thats needed to be whitelisted for sponsorship. If all the addresses were already whitelisted, an error message will be thrown. If some of the addresses were already whitelisted the rest of the addresses will be whitelisted.- address - an array of addresses (max. 10 per request)
-
/removeWhitelist/v2
- This url accepts one parameter and returns a message indicating whether the offchain whitelist removal was successful. This url is used to remove whitelist of an array of addresses. If all the addresses were not whitelisted, an error message will be thrown. If some of the addresses were not whitelisted the rest of the addresses will be removed from whitelist.- address - an array of addresses (max. 10 per request)
-
/checkWhitelist/v2
- This url accepts one parameter and returns if the address has been whitelisted or not- address - The address which needs to be checked.
- policyId - Optional policy id.
-
/deposit/v2
- This url accepts one parameter and returns the submitted transaction hash if successful. This url is used to deposit some funds to the entryPointAddress from the sponsor wallet- amount - The amount to be deposited in ETH.
-
/getAllWhitelist/v2
- This url accepts optionally one parameter and returns all the addresses which are whitelisted for the apiKey/policyId.- policyId - Optional policy id.
-
/saveKey
- This url is used to save a new api key. This url uses content type astext/plain
.- apiKey - The new api key to be created.
- supportedNetworks - Base64 encoded string which follows config.json.default structure.
- erc20Paymasters - Base64 encoded string which represents the list of custom ERC20 paymasters.
- multiTokenPaymasters - Base64 encoded string which represents the list of custom multiToken paymasters.
- multiTokenOracles - Base64 encoded string which represents the list of custom multiToken oracles.
- sponsorName - Name of the sponsorer.
- logoUrl - Url of the logo.
- transactionLimit - Limit for number of transactions.
- noOfTransactionsInAMonth - Number of transaction allowed in a month.
- indexerEndpoint - Endpoint for indexer, defaults to DEFAULT_INDEXER_ENDPOINT environment property.
-
/deleteKey
- This url accepts one parameter and deletes the api key record. This url uses content type astext/plain
.- apiKey - The api key to be deleted.
- Follow steps for local network setup in main readme.MD file in project root-directory project-readme-steps