Skip to main content

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.
  • deposit is permissionless and credits the measured balance delta. Lockers credit through depositFromLaunch, 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.
  • multicall lets a relayer registerStealth + 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)​

FunctionDescription
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)​

FunctionSigned typeNotes
claim(Claim c, bytes adapterData, bytes signature)ClaimSee below
rotateOwner(id, newOwner, deadline, signature)RotateOwnerDiscards every unused shield template
ping(id, deadline, signature)PingResets the inactivity clock
cancelBind(id, deadline, signature)CancelBindCurrent 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:

  • relayerFee goes to msg.sender;
  • adapter == address(0): adapterData = abi.encode(address recipient, bool unwrapNative); unwrapNative is only valid for WBNB;
  • otherwise the net amount is transferred to the adapter, then IWithdrawalAdapter(adapter).onWithdraw(asset, net, adapterData) is called.

Social binds​

FunctionAccessDescription
proposeBind(id, newOwner, deadline, bytes[] attestorSignatures)anyoneNeeds ≥ 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 timelockSets the owner; counts as activity; discards the previous owner's shield templates
vetoBind(id)guardian or any single attestorClears the pending bind; never moves funds; keeps the fallback sweep closed for at least 2 × bindDelay

Fallback​

FunctionAccessDescription
sweepToFallback(id, address[] assets)anyoneAfter 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)​

FunctionBound
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).