Actalink ERC20 Paymaster
A service for sponsoring ERC20 token payments, and scheduling token transfers using Actalink Account Abstraction infrastructure.
Overview
The @actalink/erc20-paymaster
enables smart contract wallets to pay gas fees using ERC20 tokens instead of native ETH. This package provides a paymaster service that can be integrated with Account Abstraction infrastructure to allow for a more flexible user experience.
Architecture
The ERC20 Paymaster implements a modular service architecture for handling token-based gas fee payments:

Key Components
-
PaymasterService: The core class that orchestrates all components and manages the paymaster lifecycle.
- Creates and configures the Express application server
- Sets up authentication (SIWE based E2E Auth) and API routes
- Spawns and manages background worker threads
- Provides access to stored user operations to extension developers
PaymasterService Architecture
The PaymasterService follows microservice architecture to better manage the complexity of the system. The service is divided into several components, each responsible for a specific task:
-
Express Application: Handles HTTP requests and provides API endpoints for:
- Sponsor User Operations
- User operation scheduling
- User operation cancellation
- User operation listing
- User operation status checking
- Nonce key retrieval
-
API Routes: Endpoints for client interaction, mounted at /api/*
.
-
Worker Thread: Background process for:
- Monitoring pending user operations
- Processing and submitting transactions to the blockchain
- Managing transaction status updates
-
SQLite Database: Persistent storage designed to run within microservices for:
- Storing signed user operations and their current status
- Transaction history and status updates
Service Lifecycle
- Initialize: Constructs the service with configuration options
- Setup Middleware & Routes: Configures Express with middleware and API routes
- Start HTTP Server: Begins listening for client requests
- Create Worker: Spawns background worker for transaction processing (if enabled)
Authentication Flow
The ERC20 Paymaster service uses Sign-In with Ethereum (SIWE) for authentication. This provides a secure, web3-native way to authenticate users using their Ethereum wallets.

Authentication Process
-
Request Challenge
- Client sends a POST request to
/api/challenge-siwe
with their wallet address
- PaymasterService generates a unique SIWE message as challenge
- Message includes service details, nonce, and timestamp
-
Message Signing
- Client receives SIWE message
- Wallet signs the message using private key
- Returns signature to client
-
Authentication
- Client makes authenticated requests by including:
- Base64 encoded SIWE message in
x-siwe-message
header
- Signature in
x-siwe-signature
header
- PaymasterService verifies signature matches wallet address
- If valid, grants access to protected resources
Usage Example
const challengeResponse = await fetch('/api/challenge-siwe', {
method: 'POST',
body: JSON.stringify({ walletAddress: account.address })
});
const { message } = await challengeResponse.json();
const signature = await wallet.signMessage(message);
const response = await fetch('/api/protected-endpoint', {
headers: {
'x-siwe-message': Buffer.from(message).toString('base64'),
'x-siwe-signature': signature
}
});
Installation
npm install @actalink/erc20-paymaster
Quick Start
import { ERC20PaymasterService } from '@actalink/erc20-paymaster';
const service = new PaymasterService({
port: 4000,
enableLuciaAuth: false,
enableSIWEe2eAuth: true,
enableWorker: true,
bodyLimit: "1024kb",
logFormat: "dev",
onUserOpSuccess: (userOp: UserOperationAndProps) => {
const { userOpHash, transactionHash } = userOp;
console.log(`UserOperation ${userOpHash} succeeded with transaction ${transactionHash}`);
}
});
await service.start();
API Documentation
The service exposes the following REST API endpoints:
ERC20 Paymaster Endpoints
POST /api/scheduleops
Schedules user operations for future execution. This endpoint allows you to schedule one or more user operations to be executed at a specific time.
Request Body:
[
{
"userOp": {
"sender": "0x...",
"nonce": "0x...",
},
"userOpHash": "0x...",
"entryPoint": "0x...",
"executionTime": 1234567890000,
"signerAddress": "0x..."
}
]
Validation:
- Execution time must be at least 2 minutes in the future
- Valid merkle signature in the UserOperation
- Valid entry point address
Response:
- Success (200):
"N UserOperations scheduled successfully!"
where N is the number of operations scheduled
- Error (404): Error message with details
Notes:
- Operations are stored with initial status "pending"
- A background worker processes pending operations near their execution time
- Operations can be cancelled using the
/cancelops
endpoint before execution
- No authentication required for this endpoint
POST /api/challenge-siwe
Generates a Sign-In with Ethereum (SIWE) challenge message for authentication.
Request Body:
{
"walletAddress": "0x..."
}
Response:
{
"message": "service.acta.link wants you to sign in with your Ethereum account:\n0x...\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: ...\nIssued At: ..."
}
Status Codes:
- 200: Challenge message generated successfully
- 400: Invalid wallet address
- 500: Server error
Usage:
- Call this endpoint to get a SIWE challenge message
- Sign the message using the user's wallet
- Use the signed message and signature in subsequent authenticated requests
POST /api/sign/v2
Sponsors a user operation with ERC20 token payment.
Request Body:
{
"method": "pm_sponsorUserOperation",
"params": [
userOperation,
entryPointAddress,
{
"type": "sponsor"
}
]
}
Response:
{
"result": {
...userOperation,
"paymasterData": "0x...",
"paymasterVerificationGasLimit": "0x...",
"paymasterPostOpGasLimit": "0x..."
}
}
POST /api/cancelops
Cancels pending user operations for an authenticated user. Requires SIWE (Sign-In with Ethereum) authentication.
Headers:
x-siwe-message: <base64 encoded SIWE message>
x-siwe-signature: <signature>
Response:
[
"0x...",
"0x..."
]
Authentication:
This endpoint requires SIWE authentication. To authenticate:
- Get a SIWE challenge message using
/api/challenge-siwe
- Sign the message with your wallet
- Include the base64 encoded message and signature in the request headers
Status Codes:
- 200: Successfully cancelled operations
- 403: Invalid authentication or signature
- 500: Server error
POST /api/listops
Lists all scheduled user operations for a given smart wallet address.
Request Body:
{
"validators": ["0x..."],
"salt": "0x..."
}
Response:
[
{
"userOpHash": "0x...",
"signerAddress": "0x...",
"userOp": {
},
"entryPoint": "0x...",
"executionTime": 1234567890000,
"status": "pending",
"transactionHash": ""
}
]
Authentication:
Requires user authentication. The endpoint verifies that the authenticated user owns the smart wallet.
Status Codes:
- 200: Successfully retrieved operations
- 403: Invalid user or address
- 500: Server error
GET /api/listPendingOps
Lists all pending user operations for a given smart wallet address.
Request Body:
{
"validators": ["0x..."],
"salt": "0x..."
}
Response:
[
{
"userOpHash": "0x...",
"signerAddress": "0x...",
"userOp": {
},
"entryPoint": "0x...",
"executionTime": 1234567890000,
"status": "pending",
"transactionHash": ""
}
]
Notes:
- Only returns operations with "pending" status
- Useful for monitoring operations that haven't been executed yet
- Can be used in conjunction with
/cancelops
to manage pending operations
POST /api/getPendingNonceKeys
Retrieves a list of unique nonce keys from pending user operations for a given smart wallet address. This is useful for checking which nonce slots are currently in use by pending operations.
Request Body:
{
"validators": ["0x..."],
"salt": "0x..."
}
Response:
[
"0x1234...",
"0x5678..."
]
Notes:
- Only considers operations with "pending" status
- Nonce keys are extracted from the first 42 characters of each operation's nonce
- Duplicates are automatically filtered out
- Useful for nonce management and preventing nonce conflicts
Service Management
The PaymasterService class provides the following methods:
start()
: Starts the HTTP server and worker thread (if enabled)
stop()
: Stops the service and terminates the worker thread
getApp()
: Returns the Express application instance for custom configuration
For detailed API documentation including TypeScript types and interfaces, run:
npm run docs
Then open docs/index.html
in your browser.
Configuration
The PaymasterService accepts the following configuration options:
port | number | Port number for the HTTP server | 4000 |
enableSIWEe2eAuth | boolean | Enable Sign-In with Ethereum authentication | true |
enableWorker | boolean | Enable background worker for transaction processing | true |
workerPath | string | Path to worker thread script | "build/workers/scheduler.js" |
bodyLimit | string | Request body size limit | "1024kb" |
logFormat | string | Morgan logging format | "dev" |
onUserOpSuccess | (userOp: UserOperationAndProps) => void | Optional callback function executed by the background worker when a scheduled UserOperation is successfully mined on-chain. The function receives a single argument of type UserOperationAndProps , which includes the original userOp object, userOpHash , the final transactionHash , the updated status , and other details. | undefined |
app | express.Application | Provide an existing Express application instance | express() |
Environment Variables
The service requires the following environment variables:
PAYMASTER_PORT=4000
PVT_KEY=
PROVIDER=
CHAIN_ID=
Supported Networks
The paymaster service currently supports the following networks:
- Ethereum Mainnet (Chain ID: 1)
- Polygon (Chain ID: 137)
- Linea (Chain ID: 59144)
- Hedera (Chain ID: 295)
Examples
See the examples directory for integration examples.
Advanced Usage
Extending the API
The underlying Express router used by the service is also exported as paymasterRouter
. You can use this to add custom routes or middleware to the service instance:
import { PaymasterService, paymasterRouter } from '@actalink/erc20-paymaster';
import express from 'express';
const service = new PaymasterService();
const app = service.getApp();
app.use('/api', (req, res, next) => {
console.log('Custom middleware for API routes');
next();
});
app.get('/custom-status', (req, res) => {
res.json({ custom: 'status OK' });
});
paymasterRouter.get('/custom-paymaster-route', (req, res) => {
res.json({ message: 'Custom route within paymaster router' });
});
await service.start();
Exported Utility Functions
The package also exports several utility functions that might be useful for advanced integrations or direct database interaction. Use these with caution as they might expose internal implementation details.
-
getSignerFromMerkleSignature(root: Hex, proof: Hex[], leaf: Hex): Address
- Recovers the signer address from a Merkle signature, root, proof, and leaf.
- Useful for verifying Merkle-based signatures off-chain.
-
getScheduledUserOpsByHash(hashes: string[]): UserOperationAndProps[]
- Retrieves scheduled user operations from the database based on their userOpHashes.
-
getScheduledUserOps(address: Address): UserOperationAndProps[]
- Retrieves all scheduled user operations associated with a specific smart wallet address.
-
removeUserOpByHash(hash: string): void
- Removes a specific user operation from the scheduling database using its userOpHash.
- Note: This directly modifies the database state.
-
getInsertQuery(): Database.Statement
- Returns the prepared SQLite statement used for inserting new scheduled user operations.
- Provides direct access to the database insertion mechanism.
License
MIT