The problem
A sender wants to pay +2348012345678, who has never used Numpay. That person has no Web3Auth key yet, so there is no smart-account address to send to.
The solution
The backend creates a claimable wallet (called a pregenerated wallet in the code): a Kernel smart account controlled by a key the backend generates and stores encrypted. The sender pays it like any other wallet. When the recipient logs in for real, the backend sweeps the full USDC balance to their own smart account and deletes the stored key.
Lifecycle
Creation
Key encryption
Claim and sweep
Failure handling
- The sweep is attempted twice in the same request, to ride out a flaky bundler or RPC call.
- If both attempts fail, the database is left unchanged: the claimable wallet and its key stay in place, and the API returns the existing record.
- The app notices that the returned wallet differs from its own and retries the sync once after 3 seconds in the background. Every later session restore calls
/user/register again, so the claim completes without another OTP.
The backend reads the claimable wallet’s balance on the same chain and USDC contract as the app. If they ever disagreed, the backend would see a zero balance, mark the wallet claimed and delete the key, losing the funds.
Trust model
Claimable wallets are the one place Numpay is custodial: between a payment to a new identity and that person’s first login, the backend holds the key. Exposure is limited because:
- Only identities that have never logged in get a claimable wallet; registered users are never overwritten.
- The key is encrypted at rest and never leaves the backend.
- The key is deleted as soon as the balance is swept.