Session keys and spending caps
How to delegate scoped spending power to an agent without giving up the SCW's owner key.
In the canonical aa-native flow the SCW's owner key signs every UserOp. For an agent that needs to spend autonomously, this means either keeping the owner key online indefinitely (full delegation) or using a different account contract that supports scoped delegation.
ERC-4337 supports session keys at the account-contract layer. The aa-native wire format is unaffected by which account contract you use; only the on-chain validation logic changes.
What a session key buys you
A session key is a separate keypair the SCW's owner authorizes for a constrained subset of operations. Constraints are enforced by the account contract during validateUserOp. Typical constraints:
- Time-bounded. "Valid until
expirytimestamp." - Method-bounded. "May only call
SettlementContract.settle(...)." - Amount-bounded. "May spend at most 10 USDT/day."
- Counterparty-bounded. "May only target merchants in this allowlist."
When the constraints are exceeded, the account contract rejects validation and the bundler refuses to submit the UserOp. The owner can revoke a session key at any time by sending a single transaction.
Why this is not in the canonical SDK
The canonical SimpleAccount contract (the factory the reference SDK targets at 0x9406Cc6185a346906296840746125a0E44976454) does not implement session-key validation. Adding it requires deploying a different account contract.
Several account contracts in the ERC-4337 ecosystem do support session keys:
- Kernel (ZeroDev) — validator plugins, including session-key validators with policies.
- Safe with
Safe4337Module— Safe accounts as ERC-4337 wallets, with module-based delegation. - Biconomy Smart Account — pluggable validation modules, session-key module included.
Each comes with its own factory and account-contract address. The wire-format implications for aa-native are zero: the verifier ABI-decodes userOp.callData to a SettlementContract.settle(...) call regardless of which account contract is the userOp.sender.
Plugging an alternative account contract into the SDK
@nerochain/x402-aa's aaNativeSigner accepts entryPoint and accountFactory options. Override them to use a different account contract:
const signer = aaNativeSigner({
signer: ownerOrSessionKeyWallet,
rpcUrl, bundlerUrl, paymasterUrl, paymasterApiKey,
settlementContract,
entryPoint: "0x...", // EntryPoint v0.6 for the chain
accountFactory: "0x...", // Kernel / Safe / Biconomy factory
});The SDK builds the UserOp via the userop SDK, which respects whatever factory you point it at. For account contracts that need additional middleware (signature shape changes, validator selection, etc.), you'll likely want to drop down from aaNativeSigner to a custom PaymentSigner implementation that knows about your account contract's specifics.
A future @nerochain/x402-aa-session
A planned package, @nerochain/x402-aa-session, will package up the session-key flow for one or more popular account contracts as a configuration option. Expected surface:
const signer = aaNativeSessionSigner({
ownerSigner, // the SCW's owner — used to sign session-key authorizations
sessionKey: { ... }, // generated or imported session key
scopes: {
spendCap: parseUnits("10", 6), // 10 USDT/day
targets: [SETTLEMENT_CONTRACT],
expiry: Date.now() + 24 * 3600 * 1000,
},
rpcUrl, bundlerUrl, paymasterUrl, paymasterApiKey,
settlementContract,
});This is a future deliverable, not something shipped today. The ephemeral SCW pattern (see Delegated agent) covers most demo and trusted-environment use cases without it.
When you actually need session keys
- Multi-user platforms where the operator runs LLM agents on behalf of users and cannot hold each user's owner key online.
- Long-running autonomous agents that should keep operating across user-session boundaries.
- Cost-capped agent budgets where a runaway prompt cannot drain the wallet.
For one-off scripts or single-user demos, the ephemeral-SCW pattern is enough.