Hook System
Three configurable hooks that separate custody from policy in the RaycashWrapper.
The hook system separates the RaycashWrapper's custody logic (immutable — holds tokens, manages balances) from its policy logic (configurable — KYC, compliance). Hooks are external contracts set by the DEFAULT_ADMIN_ROLE holder and can be swapped without redeploying the wrapper.
Hook Lifecycle
Hook Lifecycle Matrix
| Hook | Interface | Called During | Can Revert? | Parameters | Return Value |
|---|---|---|---|---|---|
| Transfer | IOnTransferHook | _update (mint + P2P, not burn) | Yes | (from, to) | none |
| Unwrap Requested | IOnUnwrapRequestedHook | initUnwrap | No (try/catch) | (account, handle) | none |
| Unwrap Finalized | IOnUnwrapFinalizedHook | finalizeUnwrap | No (try/catch) | (destination, amount, handle) | none |
Entry vs Exit Hooks
This is the most important safety property in the hook system:
- Entry hooks (transfer) can revert and block the operation. This is how KYC enforcement works — a transfer to an unverified address gets rejected.
- Exit hooks (unwrap requested, unwrap finalized) use try/catch and can never block the operation. If they fail, an event is emitted but the unwrap proceeds.
This ensures the "exit always callable" invariant: users can always leave the encrypted domain, even if the hook contract is broken, paused, or their KYC was revoked.
The transfer hook is skipped for burns (to == address(0)). Since initUnwrap internally burns tokens, this ensures the burn path is never blocked.
Hook Interfaces
IOnTransferHook
interface IOnTransferHook {
function onTransfer(address from, address to) external;
}Called on mint and P2P transfers (never on burn). If it reverts, the transfer reverts with TransferHookReverted.
IOnUnwrapRequestedHook
interface IOnUnwrapRequestedHook {
function onUnwrapRequested(address account, bytes32 handle) external;
}Called during initUnwrap via try/catch. Failure emits UnwrapRequestedHookFailed but does not block.
IOnUnwrapFinalizedHook
interface IOnUnwrapFinalizedHook {
function onUnwrapFinalized(
address destination,
uint64 amount,
bytes32 handle
) external;
}Called during finalizeUnwrap via try/catch. Failure emits UnwrapFinalizedHookFailed but does not block.
Implementations
RaycashKycHook
A transfer hook implementing IOnTransferHook. It enforces KYC verification by delegating to RaycashKycAttesterResolver.hasValidKycAttestation().
| Hook | Who is Checked | Special Cases |
|---|---|---|
onTransfer | Both from and to | Skips from check on mint (from == address(0)) — deposit KYC is enforced off-chain |
The function is view — the hook performs read-only KYC checks with no state mutation. This means it can be shared across multiple wrapper instances.
Does not implement unwrap hooks — KYC revocation intentionally cannot block withdrawals.
Access Control
The wrapper uses OZ AccessControl. Only the DEFAULT_ADMIN_ROLE holder can update hook contracts via the setter functions:
| Setter | Hook Slot |
|---|---|
setTransferHook(address) | transferHook |
setUnwrapRequestedHook(address) | unwrapRequestedHook |
setUnwrapFinalizedHook(address) | unwrapFinalizedHook |
Setting a hook to address(0) disables it. Each hook slot is independently configurable. A view transfer hook (like RaycashKycHook) can be shared across multiple wrapper instances since it mutates no state. The admin can also grant/revoke the OPERATOR_ROLE (used for initWrap) via grantRole() / revokeRole().
Was this page helpful?