FeeVault
Source: contracts/src/vault/FeeVault.sol · Interface: IFeeVault (frozen: additions
allowed, changes not) · MIT. Ownable2Step, ReentrancyGuard, OZ EIP712, OZ Multicall.
Purpose
Holds fee balances for opaque beneficiary ids. A beneficiary is never identified by a public wallet. Owners act only through EIP-712 signatures that anyone (normally a relayer) submits, so the owner key never needs gas or on-chain history.
- Ledgers are per
(id, asset). A freeze of one asset never blocks other assets or ids. depositis permissionless and credits the measured balance delta. Lockers credit throughdepositFromLaunch, which also books the amount as that launch's credit.- Registration is idempotent: registering an already-registered id returns it and changes nothing, so a copied relayer call landing first cannot break a relayed multicall.
multicalllets a relayerregisterStealth+claim(and, with consolidation,consolidate) in one transaction.
Account
struct Account {
uint8 kind; // 0 unregistered, 1 stealth, 2 handle
address owner; // current signer (0 for an unbound handle escrow)
address fallbackRecipient;
uint64 fallbackDelay;
uint64 activityAt; // last owner activity (or first registration / credit)
address pendingOwner; // bind / rebind waiting for the timelock
uint64 pendingReadyAt;
}
Id derivation: beneficiary ids. Typed data: EIP-712.
Functions
Funding and registration (permissionless)
| Function | Description |
|---|---|
deposit(bytes32 id, address asset, uint256 amount) returns (uint256 credited) | Pull amount, credit the received delta to id. Works for unregistered ids; the first credit starts the activity clock. |
depositFromLaunch(launchToken, id, asset, amount) returns (uint256 credited) | Same; when msg.sender is the factory-recorded locker of launchToken whose beneficiary is id, also adds credited to launchCredit(launchToken, asset). Anyone else makes a plain deposit. |
registerStealth(owner, salt, fallbackRecipient, fallbackDelay) returns (bytes32 id) | Only the true preimage reproduces the id. Starts the activity clock. |
registerHandle(handleCommitment, fallbackRecipient, fallbackDelay) returns (bytes32 id) | Registers an unbound social escrow (owner = 0). |
Owner actions (EIP-712, submitted by anyone)
| Function | Signed type | Notes |
|---|---|---|
claim(Claim c, bytes adapterData, bytes signature) | Claim | See below |
rotateOwner(id, newOwner, deadline, signature) | RotateOwner | Discards every unused shield template |
ping(id, deadline, signature) | Ping | Resets the inactivity clock |
cancelBind(id, deadline, signature) | CancelBind | Current owner cancels a pending rebind |
All of them consume the account's shared nonces(id) in order and update activityAt. Signatures
are checked with OZ SignatureChecker, so the owner may be an EOA or an ERC-1271 contract.
claim checks, in order: registered, owner set, deadline, relayer binding
(c.relayer == 0 || c.relayer == msg.sender), nonce, 0 < amount, relayerFee <= amount,
keccak256(adapterData) == c.dataHash, adapter allow-listed (or 0 for direct), owner signature,
balance. c.amount == type(uint256).max claims the whole balance at execution (which must be
non-zero and cover relayerFee), so a creator consolidation landing first cannot break a
full-balance claim; Claimed reports the amount taken. Effects happen before any transfer
(including shrinking the launch credits pro rata). Then:
relayerFeegoes tomsg.sender;adapter == address(0):adapterData = abi.encode(address recipient, bool unwrapNative);unwrapNativeis only valid for WBNB;- otherwise the net amount is transferred to the adapter, then
IWithdrawalAdapter(adapter).onWithdraw(asset, net, adapterData)is called.
Social binds
| Function | Access | Description |
|---|---|---|
proposeBind(id, newOwner, deadline, bytes[] attestorSignatures) | anyone | Needs ≥ attestorThreshold BindOwner signatures from current attestors, signers strictly ascending. Consumes bindNonces(id). Timelock bindDelay (first bind) or 2 × bindDelay (rebind). |
finalizeBind(id) | anyone, after the timelock | Sets the owner; counts as activity; discards the previous owner's shield templates |
vetoBind(id) | guardian or any single attestor | Clears the pending bind; never moves funds; keeps the fallback sweep closed for at least 2 × bindDelay |
Fallback
| Function | Access | Description |
|---|---|---|
sweepToFallback(id, address[] assets) | anyone | After fallbackReadyAt(id) = activityAt + max(fallbackDelay, MIN_FALLBACK_DELAY); blocked while a bind is pending; skips zero balances; shrinks the launch credits like any owner-side debit |
Views
balanceOf(id, asset), accountOf(id), nonces(id), bindNonces(id), isAdapter,
isAttestor, attestorCount, attestorThreshold, guardian, bindDelay, wbnb,
totalBalance(asset), fallbackReadyAt(id), computeId(...), stealthKeyHash(owner, salt),
DOMAIN_SEPARATOR(), hashClaim, hashRotateOwner, hashPing, hashCancelBind,
hashBindOwner, and the constants ID_TAG, KIND_STEALTH, KIND_HANDLE,
MIN_FALLBACK_DELAY, MIN_BIND_DELAY, MAX_BIND_DELAY, MAX_TEMPLATES_PER_CALL, *_TYPEHASH.
Consolidation views: consolidationRegistry, usdt, launchFactory, shieldAdapter,
minCreatorShield, launchCredit(launchToken, asset), shieldTemplateCount,
shieldTemplateAt, templatesHash, hashConsolidate, hashRegisterShieldTemplates,
hashClearShieldTemplates.
Admin (owner)
| Function | Bound |
|---|---|
setAdapter(adapter, allowed) | |
setAttestor(attestor, allowed) | |
setAttestorThreshold(uint8) | 1 ≤ threshold ≤ attestorCount (InvalidConfig otherwise). It is 0 only before first configuration, which blocks proposeBind |
setGuardian(address) | |
setBindDelay(uint64) | [1 days, 30 days] |
recoverSurplus(asset, to) returns (uint256) | Only the excess over totalBalance(asset); beneficiary balances are untouchable |
setConsolidationConfig(registry, usdt, factory) | registry.usdt() must equal usdt. The first call sets USDT, the launch factory and minCreatorShield (1 USDT); later calls may only replace the registry |
setShieldAdapter(adapter) | New templates are pinned to it; address(0) turns template shielding off |
setMinCreatorShield(amount) | Minimum oracle-bounded output of a creator consolidation and minimum template shield |
Events
Deposited(id, asset, from, amount), Registered(id, kind, fallbackRecipient, fallbackDelay),
Claimed(id, asset, adapter, amount, relayer, relayerFee), OwnerRotated(id, newOwner),
Pinged(id), BindProposed(id, newOwner, readyAt), BindFinalized(id, newOwner),
BindCancelled(id, by), SweptToFallback(id, asset, recipient, amount),
AdapterSet(adapter, allowed), AttestorSet(attestor, allowed),
AttestorThresholdSet(threshold), GuardianSet(guardian), BindDelaySet(delay),
SurplusRecovered(asset, to, amount).
Events never include anything not already derivable from calldata.
Errors
From IFeeVault: ZeroAddress, ZeroId, InvalidKind, AlreadyRegistered(id),
NotRegistered(id), NoOwner(id), InvalidSignature, SignatureExpired, InvalidNonce,
InsufficientBalance, InvalidAmount, RelayerMismatch, AdapterNotAllowed(adapter),
NotHandleAccount(id), InsufficientAttestations, NoPendingBind(id), BindNotReady(id),
NotAuthorized, FallbackDisabled(id), FallbackNotReady(id), InvalidConfig,
NativeTransferFailed.
Additions in FeeVault: DataHashMismatch, InvalidUnwrap, BindPending(id),
AttestorsNotSorted, NotAttestor(signer), NativeNotAccepted.
Consolidation additions
The FeeVault also implements IFeeVaultConsolidation (additive; IFeeVault is unchanged):
depositFromLaunch, consolidate, creatorConsolidate, creatorConsolidateAndShield,
registerShieldTemplates, clearShieldTemplates, the views and admin setters above. Its events
are Consolidated, ShieldTemplatesRegistered, ShieldTemplatesCleared,
ShieldedFromTemplate, ConsolidationConfigSet, ShieldAdapterSet and MinCreatorShieldSet;
its errors are ConsolidationNotConfigured, InvalidAsset, SlippageExceeded,
ExecutorInputMismatch, NotCreator, InvalidTemplate, TooManyTemplates,
ShieldAdapterNotSet, ExceedsLaunchCredit, ShieldAmountTooSmall and
CreatorAmountTooSmall. See consolidation.
Runtime size is 24,289 bytes at 1,500 optimizer runs, 287 bytes under the EIP-170 limit
(forge build --sizes).