EIP-712 typed data
Every owner and attestor action on the FeeVault is an EIP-712
signature. The strings below are exact; any difference produces a different digest and the
vault rejects the signature.
Domain
| Field | Value |
|---|---|
name | "ZkPadFeeVault" |
version | "1" |
chainId | the chain id (56 BSC, 97 BSC testnet, 31337 local) |
verifyingContract | the FeeVault address |
EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)
The vault exposes DOMAIN_SEPARATOR() and hashClaim / hashRotateOwner / hashPing /
hashCancelBind / hashBindOwner so clients can check their digests on-chain.
Types
| Type | Signed by | Nonce |
|---|---|---|
Claim | account owner | nonces(id) |
RotateOwner | account owner | nonces(id) |
Ping | account owner | nonces(id) |
CancelBind | account owner | nonces(id) |
BindOwner | each attestor | bindNonces(id) |
Consolidate | account owner | nonces(id) |
RegisterShieldTemplates | account owner | nonces(id) |
ClearShieldTemplates | account owner | nonces(id) |
Owner actions share one nonce sequence per account, consumed in order. Every message has a
deadline (unix seconds); the vault rejects it after that time.
Type strings and typehashes
Claim(bytes32 beneficiaryId,address asset,uint256 amount,address adapter,bytes32 dataHash,address relayer,uint256 relayerFee,uint256 nonce,uint256 deadline)
0x45a74d21aea80eff47faf64f7ab938e4f40e6da037183a57a0e733f7393ec1c1
RotateOwner(bytes32 beneficiaryId,address newOwner,uint256 nonce,uint256 deadline)
0xc157ea48fda20f57af23ed2a8a45b61ef8ce2db3d05f9f2081f848a9557705af
Ping(bytes32 beneficiaryId,uint256 nonce,uint256 deadline)
0xd3e64a2eac63a84951d5998ccfdd00bec16f495f877caf678bef81668d86a300
CancelBind(bytes32 beneficiaryId,uint256 nonce,uint256 deadline)
0x2f8a11d99cfc6b95a1774c7471a6e6bc1a27f9b4a445f7a6166d23309f4b7e53
BindOwner(bytes32 beneficiaryId,address newOwner,uint256 bindNonce,uint256 deadline)
0xe60ce31d79e3ded52631f772232b52e697c0a22d8294df4c9effda89bf550872
Consolidate(bytes32 beneficiaryId,address assetIn,uint256 amountIn,uint256 minOut,uint256 nonce,uint256 deadline)
0x8326daafef3ea9a1e67573b536d462b863e98d810fbcce7cad228554eae56835
RegisterShieldTemplates(bytes32 beneficiaryId,bytes32 templatesHash,uint256 nonce,uint256 deadline)
0x705410f832f84d01467d08adf008c769a9c235ebc759ee870107cab679cb1acf
ClearShieldTemplates(bytes32 beneficiaryId,uint256 nonce,uint256 deadline)
0x1b3d72506a98dc545eb910f4b3d53f4cd831e608e79aa4f293d3f3cd3abe9b07
templatesHash = keccak256(keccak256(t_0) ‖ … ‖ keccak256(t_n-1)), which is also EIP-712's
encoding of a bytes[] member.
The typehashes are keccak256 of the type strings, computed for this page. The FeeVault exposes
them as CLAIM_TYPEHASH, ROTATE_OWNER_TYPEHASH, PING_TYPEHASH, CANCEL_BIND_TYPEHASH,
BIND_OWNER_TYPEHASH, CONSOLIDATE_TYPEHASH, REGISTER_SHIELD_TEMPLATES_TYPEHASH and
CLEAR_SHIELD_TEMPLATES_TYPEHASH, plus hashConsolidate, hashRegisterShieldTemplates,
hashClearShieldTemplates and templatesHash views. The SDK tests check every digest against a
deployed FeeVault.
Claim fields
| Field | Meaning |
|---|---|
beneficiaryId | The account |
asset | ERC20 to withdraw |
amount | Total debited from the account, including the relayer fee. 2^256 − 1 means "the whole balance at execution" (it must be non-zero and cover relayerFee); Claimed reports the amount actually taken |
adapter | address(0) for a direct transfer, or an allow-listed adapter |
dataHash | keccak256(adapterData) |
relayer | The only address allowed to submit, or address(0) for anyone (fee then goes to the submitter) |
relayerFee | Paid to the submitter in asset; ≤ amount |
nonce | Must equal nonces(beneficiaryId) |
deadline | Unix seconds |
A fee-only claim pays a relayer for a non-claim action (consolidate, register templates,
rotate, ping): adapter = address(0), amount == relayerFee > 0, dataHash = keccak256(0x),
relayer = that relayer, and nonce = nonces(id) + 1, because it runs right after the action in
the same multicall (SDK buildFeeClaim). For RotateOwner it is signed by the new owner.
Consolidate amounts
Consolidate.amountIn is either an exact amount or one of two whole-balance forms:
amountIn | Swaps | minOut |
|---|---|---|
| exact amount | that amount | absolute |
2^255 | quotedAmountIn (SDK consolidateAllAmountIn) | the whole balance at execution | scaled by balance / quotedAmountIn (rounded up) when less than quoted is left, so the signed rate holds |
2^256 − 1 | the whole balance at execution | absolute (a creator consolidation larger than the slippage margin landing first makes it revert) |
Clients use the quoted form; effectiveConsolidateMinOut computes the bound the vault will
apply. Exception: the FeeVault of the superseded 2026-10-04 deployment (which never held funds)
predates the quoted form and reads 2^255 | quotedAmountIn as a literal amount (the request
reverts), so messages for that vault would use 2^256 − 1 or an exact amount.
Adapter data
| Destination | adapterData |
|---|---|
| Direct | abi.encode(address recipient, bool unwrapNative) (unwrapNative only for WBNB) |
| Railgun | abi.encode(bytes32 beneficiaryId, ShieldRequest req) |
Example (viem / SDK)
import {buildClaim, encodeDirectData, DIRECT_ADAPTER, signFeeVaultMessage, hashFeeVaultMessage} from '@zk-pad/sdk';
import {privateKeyToAccount} from 'viem/accounts';
const {claim, data} = buildClaim({
beneficiaryId: id,
asset: USDT,
amount: 100n * 10n ** 18n, // total debited, fee included
adapter: DIRECT_ADAPTER, // address(0)
data: encodeDirectData(freshAddress),
relayer: relayerAddress,
relayerFee: BigInt(quote.relayerFee),
nonce, // nonces(id), from RelayerClient.accountNonces(id) (no RPC read)
deadline: BigInt(Math.floor(Date.now() / 1000) + 600),
});
const stealth = privateKeyToAccount(kit.privateKey);
const signature = await signFeeVaultMessage(stealth, 'Claim', claim, 56, feeVault);
// Optional: compare with FeeVault.hashClaim(claim) on-chain
const digest = hashFeeVaultMessage('Claim', claim, 56, feeVault);
The exact SDK signatures are on the SDK page.
BindOwner signatures
proposeBind requires at least attestorThreshold signatures, each from a current attestor, with
recovered signers in strictly ascending address order (duplicates are rejected). Attestor
signatures are verified with ECDSA recovery (EOA keys only).