Beneficiary ids, stealth keys and relayers
Beneficiary ids
A beneficiary in zk-pad is a bytes32 beneficiaryId in the FeeVault.
It is a hash of the information needed to claim:
id = keccak256(abi.encode(ID_TAG, kind, keyHash, fallbackRecipient, fallbackDelay))
| Kind | keyHash | Who can claim |
|---|---|---|
Stealth key (kind = 1) | keccak256(abi.encode(stealthOwner, salt)) | Whoever holds the stealth private key |
Social-account escrow (kind = 2) | a salted handleCommitment to a platform account | The account owner, after an attestor bind (not possible until attestors are registered) |
Because the id is a hash with a random salt, nobody can work out the owner or the key from it. The fallback settings are inside the hash too, so nobody can attach a different fallback to someone else's id. Exact encodings are in beneficiary id derivation.
Registration is lazy. Fees can be credited to an id before anyone registers it. The id is registered (its preimage revealed to the vault) only when it is first used, usually in the same relayed transaction as the first claim.
Stealth keys and claim links
A stealth key is an ordinary secp256k1 key pair generated in the browser by the SDK when the creator launches a coin. It is never funded and never sends a transaction.
The creator receives a claim kit: a claim link and a printable QR code.
https://zkpad.family/claim#v1.<base64url(json)>
- The key material is only in the URL fragment (after
#). Browsers do not send the fragment to servers, so the web server hosting the claim page never sees it. - Whoever has the link can claim. Treat it like cash. The creator passes it to the beneficiary through a private channel. A "myself" launch keeps it with the creator.
- The beneficiary should rotate the key after receiving it, so the creator no longer holds a working key.
For a stealth-key beneficiary, the creator generated the key and could have kept a copy. Until the beneficiary rotates, the creator can claim. Rotating moves control to a key only the beneficiary has.
Signed actions
The stealth key (the account's current owner) controls the account only through EIP-712
signatures:
| Action | What it does |
|---|---|
Claim | Withdraw an amount of one asset (or the whole balance, amount = 2^256 − 1) to a destination (direct address or adapter), paying a relayer fee |
RotateOwner | Replace the owner key. Also discards every unused shield template |
Ping | Prove the beneficiary is still active (resets the inactivity clock) |
CancelBind | Cancel a pending rebind of a social-escrow account |
Consolidate | Swap a balance into USDT with the beneficiary's own minOut |
RegisterShieldTemplates | Pre-register single-use Railgun destinations for creator-triggered shields |
ClearShieldTemplates | Discard every unused template |
Every signature carries a per-account nonce and a deadline, and is bound to the vault's chain and address. The type strings are in EIP-712 typed data. The owner can also be a smart-contract account that validates signatures with ERC-1271.
Relayers
A relayer is a service that receives a signed action, checks it, simulates it, and submits it on-chain, paying the gas.
- The
Claimnames the relayer and its fee. The fee (at most the claimed amount) is paid in the claimed asset, to the relayer, in the same transaction. - Other owner actions (consolidate, register templates, rotate, ping) are paid with a
fee-only claim: a second signed
Claimthat pays only the relayer's fee and runs in the same transaction, so the action and its payment succeed or fail together. Bind actions are free. - With
relayer = address(0)anyone may submit, and the fee goes to whoever submits. - A relayer cannot change anything in a signed claim: not the amount, the destination, the adapter payload (signed by hash) or its own fee.
- The FeeVault supports
multicall, so a relayer can register an id and claim in one transaction.
What a relayer does learn: your IP address (unless you use Tor or a similar network), the
timing of your request, and the claim contents, which become public on-chain anyway. The SDK
fetches account data, including the nonce to sign with, from a fetch-everything endpoint,
downloading every id's balance and filtering locally, so neither the relayer nor a public RPC
learns which id you are looking at. Fee quotes carry no amount and transaction status is polled
with a POST, so nothing account-specific lands in an access-logged URL.
Inactivity fallback
A creator can give a beneficiary an optional fallback recipient, typically an NGO address, and a fallback delay of at least 180 days.
- If the account shows no owner activity (any signed action: claim, consolidate, template
registration, rotate, ping, cancel-bind; or a finalized bind) for
max(fallbackDelay, 180 days), anyone can callsweepToFallbackto send its balances to the fallback recipient. Creator-triggered consolidations are not owner activity. - The clock is set at the first deposit and restarted at registration. Since a sweep needs a registered id, in practice the delay runs from registration (or from the last owner action). Fee deposits from trading do not reset it; only owner actions do.
- The id must be registered for a sweep to work, because the vault only learns the fallback recipient from the registration. Registration is permissionless but needs the id's preimage, so a creator who sets a fallback should register the id right after launch (the launch success page offers a relayed registration). Registering a stealth id reveals the stealth owner's address and salt, which is harmless because that key is never funded.
- A sweep is blocked while a social-escrow bind is pending, so a beneficiary who shows up at the
last moment cannot be raced. A veto of a pending bind keeps the sweep closed for at least
2 × bindDelay, so a vetoed beneficiary has time to be re-attested. - The fallback is committed inside the id, so it cannot be changed later.
With a fallback set and the id registered, donation money is never stranded if the beneficiary never claims. Without a fallback, an unclaimed balance stays in the vault indefinitely.