@actalink/erc20-paymaster v0.0.2-alpha.14
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
- Client sends a POST request to
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
- Base64 encoded SIWE message in
- PaymasterService verifies signature matches wallet address
- If valid, grants access to protected resources
- Client makes authenticated requests by including:
Usage Example
// 1. Get SIWE challenge
const challengeResponse = await fetch('/api/challenge-siwe', {
method: 'POST',
body: JSON.stringify({ walletAddress: account.address })
});
const { message } = await challengeResponse.json();
// 2. Sign message with wallet
const signature = await wallet.signMessage(message);
// 3. Make authenticated request
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';
// Initialize the paymaster
const service = new PaymasterService({
// Configuration options
port: 4000, // Port to run the service on
enableLuciaAuth: false, // Enable Lucia authentication
enableSIWEe2eAuth: true, // Enable Sign-In with Ethereum authentication
enableWorker: true, // Enable background worker for transaction processing
bodyLimit: "1024kb", // Request body size limit
logFormat: "dev", // Morgan logging format
onUserOpSuccess: (userOp: UserOperationAndProps) => {
const { userOpHash, transactionHash } = userOp;
console.log(`UserOperation ${userOpHash} succeeded with transaction ${transactionHash}`);
// Add custom logic here (e.g., update database, notify user)
}
});
// Start the service
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": {
// UserOperation object (RpcUserOperation<'0.7'>)
"sender": "0x...",
"nonce": "0x...",
// ... other UserOperation fields
},
"userOpHash": "0x...",
"entryPoint": "0x...",
"executionTime": 1234567890000, // Unix timestamp in milliseconds
"signerAddress": "0x..."
}
// ... more operations
]
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..." // The wallet address to authenticate
}
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: 1. Call this endpoint to get a SIWE challenge message 2. Sign the message using the user's wallet 3. 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, // UserOperation object
entryPointAddress, // Entry point contract address
{
"type": "sponsor" // Sponsorship type
}
]
}
Response:
{
"result": {
...userOperation,
"paymasterData": "0x...", // Signed paymaster data
"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...", // Array of cancelled userOpHashes
"0x..."
]
Authentication:
This endpoint requires SIWE authentication. To authenticate:
1. Get a SIWE challenge message using /api/challenge-siwe
2. Sign the message with your wallet
3. 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..."], // Array of validator addresses
"salt": "0x..." // Salt for smart wallet address calculation
}
Response:
[
{
"userOpHash": "0x...",
"signerAddress": "0x...",
"userOp": {
// UserOperation object
},
"entryPoint": "0x...",
"executionTime": 1234567890000,
"status": "pending",
"transactionHash": ""
}
// ... more operations
]
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..."], // Array of validator addresses
"salt": "0x..." // Salt for smart wallet address calculation
}
Response:
[
{
"userOpHash": "0x...",
"signerAddress": "0x...",
"userOp": {
// UserOperation object
},
"entryPoint": "0x...",
"executionTime": 1234567890000,
"status": "pending",
"transactionHash": ""
}
// ... more operations
]
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..."], // Array of validator addresses
"salt": "0x..." // Salt for smart wallet address calculation
}
Response:
[
"0x1234...", // Array of unique nonce keys (first 42 characters of the nonce)
"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 threadgetApp()
: 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:
Option | Type | Description | Default |
---|---|---|---|
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 # Service port (optional, defaults to 4000)
PVT_KEY= # Private key for the paymaster account
PROVIDER= # RPC provider URL
CHAIN_ID= # Chain ID for the network (e.g. 137 for Polygon)
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(); // Get the Express app instance
// Add custom middleware
app.use('/api', (req, res, next) => {
console.log('Custom middleware for API routes');
next();
});
// Add a custom route
app.get('/custom-status', (req, res) => {
res.json({ custom: 'status OK' });
});
// You can also add routes directly to the exported router
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
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago