RaycashDocs
Protocol

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

HookInterfaceCalled DuringCan Revert?ParametersReturn Value
TransferIOnTransferHook_update (mint + P2P, not burn)Yes(from, to)none
Unwrap RequestedIOnUnwrapRequestedHookinitUnwrapNo (try/catch)(account, handle)none
Unwrap FinalizedIOnUnwrapFinalizedHookfinalizeUnwrapNo (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().

HookWho is CheckedSpecial Cases
onTransferBoth from and toSkips 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:

SetterHook 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?

On this page