Lightweight stateless API for upgrading Biconomy V2 accounts to Nexus via signed UserOperations.
- Install dependencies:
npm install- Create
.envfile:
# Private key for the bundler/executor account (with 0x prefix)
PRIVATE_KEY=0x...
# RPC URL for the target chain
RPC_URL=https://polygon-mainnet.g.alchemy.com/v2/YOUR_API_KEY
# Optional: Server port (defaults to 3000)
PORT=3000- Run the server:
# Development (with hot reload)
npm run dev
# Production
npm run build
npm startThe private key in your .env file corresponds to an EOA (Externally Owned Account) that acts as the bundler. This account must be funded with native gas tokens (e.g., MATIC on Polygon) to pay for transaction fees when calling the EntryPoint.
The bundler EOA pays for:
- The
handleOpstransaction to the EntryPoint contract - All gas costs for executing the UserOperation on-chain
Make sure to:
- Check the bundler address logged on server startup
- Fund it with sufficient gas tokens for your expected volume
- Monitor the balance to avoid failed transactions
This API should NOT be exposed publicly without access control. Anyone who can call /execute can drain your bundler's gas funds by submitting operations.
Implement access control before deploying to production:
- API Key Authentication: Add middleware to validate API keys in request headers
- IP Allowlisting: Restrict access to known client IPs
- JWT/Session Auth: Integrate with your existing user authentication system
- Rate Limiting: Prevent abuse by limiting requests per user/IP
Example: Add API key middleware in src/index.ts:
app.use('*', async (c, next) => {
const apiKey = c.req.header('X-API-Key');
if (apiKey !== process.env.API_KEY) {
return c.json({ error: 'Unauthorized' }, 401);
}
await next();
});Health check endpoint.
Response:
{
"status": "ok",
"message": "Upgrade API is running"
}Generates an unsigned UserOperation for upgrading a smart account.
Request:
{
"smartAccountAddress": "0x1234...5678",
"ownerAddress": "0xabcd...efgh"
}Response:
{
"userOp": {
"sender": "0x1234...5678",
"nonce": "0",
"initCode": "0x",
"callData": "0x...",
"callGasLimit": "800000",
"verificationGasLimit": "500000",
"preVerificationGas": "100000",
"maxFeePerGas": "20000000",
"maxPriorityFeePerGas": "1000000",
"paymasterAndData": "0x",
"signature": "0x"
},
"userOpHash": "0x..."
}Executes a signed UserOperation via the EntryPoint contract.
Request:
{
"userOp": {
"sender": "0x1234...5678",
"nonce": "0",
"initCode": "0x",
"callData": "0x...",
"callGasLimit": "800000",
"verificationGasLimit": "500000",
"preVerificationGas": "100000",
"maxFeePerGas": "20000000",
"maxPriorityFeePerGas": "1000000",
"paymasterAndData": "0x",
"signature": "0x..."
}
}Response:
{
"transactionHash": "0x..."
}- Client calls
/generate-useropwith the smart account address and owner address - Client receives the unsigned
userOpanduserOpHash - Client signs the
userOpHashusing their smart account's signing mechanism - Client attaches the signature to
userOp.signature - Client calls
/executewith the signeduserOp - Server submits the UserOp to the EntryPoint and returns the transaction hash
Contract addresses can be modified in src/config.ts:
implementationAddress: Nexus implementation contractbootStrapAddress: Bootstrap contract for initializationENTRY_POINT_ADDRESS: ERC-4337 EntryPoint v0.6
Gas limits can be adjusted in GAS_CONFIG within the same file.
This API is stateless - no database or session storage is needed. The client is responsible for:
- Storing the generated
userOpbetween calls - Signing the
userOpHash - Submitting the complete signed
userOpto execute
The server simply generates the upgrade calldata and acts as a bundler to submit signed operations.