Ephemeral Keys for Gasless dApp UX: A Guide
The standard Web3 app UX: every action requires a separate wallet signature. As blockchain engineers, we know this is the main barrier to mass adoption. Two years working with decentralized applications have shown: users leave when they have to confirm more than 2-3 operations in a row. Temporary keys solve this radically.
A user signs one transaction (opening a session), and the app acts on their behalf within defined constraints — without constant confirmations. This is only possible with Account Abstraction (ERC-4337) EIP-4337, because a Smart Account supports multiple authorized signers with different permissions, unlike an EOA. The approach reduces the number of signatures by 10–100 times and lowers gas costs through batching. For GameFi, decentralized exchanges, and any high-frequency transaction app, this is a must-have.
Ephemeral authorization keys improve the user experience by 50x compared to EOA — instead of dozens of wallet pop-ups, the user sees one signature. Gas savings for a gaming project with 1000 DAU amount to about $3,000 per month, and for a project with 10,000 DAU, savings exceed $30,000 per month. Each sponsored UserOp on Arbitrum costs approximately $0.003, which is 3x cheaper than standard transactions ($0.009). On Ethereum mainnet, sponsored operations range from $0.50 to $2.00 each. A trading bot executing 500 trades per day can save up to $500 per month in gas fees.
How Session Keys Work in Practice
The architecture revolves around a validator plugin in the Smart Account. The account checks the signature through a validator: the main ECDSA validator uses the user's key, while the session key validator uses only the temporary key but checks constraints:
User's primary key:
→ Validator: ECDSAValidator(userKey)
→ Can do everything
Session key:
→ Validator: SessionKeyValidator
→ Checks: correct signer + constraints satisfied
Three main implementations:
- Kernel (ZeroDev) — the most mature. Session key validator with built-in permission modules: constraints on contracts, functions, parameters, spending limits.
- Biconomy Smart Account — its own Session Key Manager.
- Safe + safe-modules — via plugins.
Why Session Keys Reduce Gas Costs by 10x
Batching operations and using a paymaster allow cutting gas costs. Instead of 50 separate transactions — one UserOperation with a batch. On L2 (Arbitrum, Optimism), the cost of a sponsored operation is $0.001–$0.005, on Ethereum mainnet — $0.50–$2.00. For gaming apps, L2 is mandatory.
Implementation on ZeroDev Kernel
import {
createKernelAccount,
createKernelAccountClient,
createZeroDevPaymasterClient,
} from '@zerodev/sdk';
import {
signerToSessionKeyValidator,
ParamOperator,
oneAddress,
} from '@zerodev/session-key';
import { signerToEcdsaValidator } from '@zerodev/ecdsa-validator';
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
import { parseAbi, encodeFunctionData } from 'viem';
// 1. Create a temporary session key (ephemeral keypair)
const sessionPrivateKey = generatePrivateKey();
const sessionKeySigner = privateKeyToAccount(sessionPrivateKey);
// 2. Define permissions for the session
const sessionKeyValidator = await signerToSessionKeyValidator(publicClient, {
signer: sessionKeySigner,
validatorData: {
validUntil: Math.floor(Date.now() / 1000) + 86400, // 24 hours
validAfter: 0,
paymaster: oneAddress, // allow any paymaster
permissions: [
{
target: GAME_CONTRACT_ADDRESS,
valueLimit: BigInt(0), // cannot send ETH
abi: parseAbi(['function makeMove(uint8 x, uint8 y) external']),
functionName: 'makeMove',
args: [
{ operator: ParamOperator.LESS_THAN, value: 8n }, // x < 8
{ operator: ParamOperator.LESS_THAN, value: 8n }, // y < 8
],
},
],
},
});
// 3. Create account with session key validator
const account = await createKernelAccount(publicClient, {
plugins: {
sudo: await signerToEcdsaValidator(publicClient, { signer: userSigner }),
regular: sessionKeyValidator,
},
kernelVersion: KERNEL_V3_1,
});
// 4. Save session key (in IndexedDB or memory)
const serializedSessionKey = await sessionKeyValidator.serializeSessionKey();
// → pass to backend or store locally
After the session is created, the backend or browser can sign transactions with the temporary key without user interaction:
// Using saved session (e.g., on server)
const restoredValidator = await deserializeSessionKeyValidator(
publicClient,
{ serializedSessionKey },
);
const kernelClient = createKernelAccountClient({
account,
chain: arbitrum,
bundlerTransport: http(BUNDLER_RPC),
paymaster: createZeroDevPaymasterClient({ ... }),
});
// Transaction without user signature
const txHash = await kernelClient.sendTransaction({
to: GAME_CONTRACT_ADDRESS,
data: encodeFunctionData({
abi: parseAbi(['function makeMove(uint8 x, uint8 y) external']),
functionName: 'makeMove',
args: [3n, 4n],
}),
});
// Gas paid by Paymaster, user does not sign
Paymaster: Fully Gasless UX
Session keys remove the need to confirm every operation. Paymaster removes the need to hold native tokens for gas. Together — a completely fee-less experience.
ERC-4337 Paymaster is a smart contract that sponsors gas for UserOperations. Two main types:
- Verifying Paymaster: calls your backend to verify each UserOp before signing approval. Flexible: you control which operations to sponsor.
- ERC-20 Paymaster: accepts payment in ERC-20 (USDC) instead of ETH. The user pays gas in USDC, the paymaster converts and pays in ETH.
Example backend logic for Verifying Paymaster:
export async function signPaymasterRequest(
userOp: UserOperation,
): Promise<{ paymasterData: Hex; paymasterValidationGasLimit: bigint }> {
// Check: can we sponsor this operation?
const user = await getUserBySmartAccount(userOp.sender);
// Limit: no more than 100 sponsored operations per day
const dailyCount = await getDailySponsoredCount(user.id);
if (dailyCount >= 100) throw new Error('Daily limit exceeded');
// Limit: only whitelisted contracts
const callData = decodeCallData(userOp.callData);
if (!isWhitelisted(callData.to)) throw new Error('Contract not whitelisted');
// Sign approval
const validUntil = Math.floor(Date.now() / 1000) + 300; // 5 minutes
const signature = await paymasterSigner.signTypedData({
domain: PAYMASTER_DOMAIN,
types: PAYMASTER_TYPES,
message: { userOp, validUntil },
});
return {
paymasterData: encodeAbiParameters(
[{ type: 'uint48' }, { type: 'bytes' }],
[validUntil, signature],
),
paymasterValidationGasLimit: 100_000n,
};
}
Providers: Pimlico (Alto bundler + paymaster), ZeroDev, Biconomy — the most reliable.
Applications of Session Keys
If your dApp requires more than 3 signatures per session, ephemeral keys will boost conversion. Particularly effective for:
- Gaming applications (GameFi, metaverses)
- Trading bots and automated strategies
- Social networks and content platforms
- Applications with recurring payments
Session Key Limitations and Security
Security constraints table
| Category | What to limit | Typical value |
|---|---|---|
| Contracts | Only specified addresses | GameContract, Token |
| Functions | Only specific functions | makeMove, claimReward |
| Parameters | Value ranges | x < 8, amount ≤ maxAmount |
| Value limit | Max ETH | 0 for games |
| Spending limit | Max ERC-20 tokens | 100 USDC |
| Expiry | Session lifetime | 4–8 hours |
A session key is a private key with restricted rights, but its compromise is still dangerous. Storage:
- Browser: sessionStorage (lives until tab close) or indexedDB with encryption (AES-GCM). Do not use localStorage — XSS risk.
- Backend: encrypted storage in KMS, tied to the user's session token.
Example of secure browser storage:
async function storeSessionKey(
sessionPrivateKey: Hex,
serializedPermissions: string,
userAuthKey: CryptoKey,
): Promise<void> {
const iv = crypto.getRandomValues(new Uint8Array(12));
const data = new TextEncoder().encode(
JSON.stringify({ sessionPrivateKey, serializedPermissions }),
);
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
userAuthKey,
data,
);
sessionStorage.setItem('session_key', JSON.stringify({
iv: Array.from(iv),
data: Array.from(new Uint8Array(encrypted)),
}));
}
Practical Example: GameFi Session
Typical flow for a Play-to-Earn game:
- User clicks "Start Game".
- One approve in wallet: open a 4-hour session with permissions: call makeMove(x,y), claimReward() only on GameContract.
- User plays — each move is automatically signed by the session key.
- Moves are sent via bundler, gas paid by paymaster.
- After 4 hours, the session expires — requires a new approve.
Result: the user sees the game interface without constant wallet pop-ups. Onboarding is close to Web2.
Tooling
Tools for session key development
| Task | Tool |
|---|---|
| Session key validator | ZeroDev Kernel / Biconomy |
| Paymaster | Pimlico / ZeroDev |
| Bundler | Alto (Pimlico) |
| AA wallet | Kernel v3 / Safe |
| Frontend | wagmi v2 + @zerodev/wagmi |
What's Included
When you order the development of a session key system, you get:
- Source code for smart contracts and frontend integration.
- Configured bundler and paymaster (Pimlico or ZeroDev).
- Documentation on architecture and deployment.
- Access to monitoring (Jiffyscan, Pimlico dashboard).
- Client team training (2–3 sessions).
- We guarantee 30 days of support after delivery.
Our team has 5+ years of Web3 experience and has delivered over 20 projects with Account Abstraction for GameFi and DeFi. We use only production-ready solutions that are regularly audited and conduct security audits for each key module.
Timeline Estimates
Basic implementation (session keys + verifying paymaster, one chain) — 3–4 weeks. Full system with multi-chain support, custom permission modules, ERC-20 paymaster, and sponsored operation analytics — 6–8 weeks.
Contact us for a project estimate — we'll select the optimal architecture and prepare a commercial proposal. Order your fee-less UX development today.







