Wrapping
Two-phase wrap flow -- depositing cleartext tokens into the confidential domain.
Wrapping converts cleartext ERC-20 tokens into FHE-encrypted confidential tokens. The RaycashWrapper (which extends the abstract ERC7984AsyncWrapper base) uses a two-phase design to preserve privacy while supporting compliance.
Wrap Flow
Phase 1: initWrap
The operator (holder of OPERATOR_ROLE) calls initWrap(DeployParams) to initiate a deposit. There is no external deposit() function — all deposits go through the operator-gated initWrap() flow.
The wrapper:
- Verifies the FHE-encrypted recipient via
FHE.fromExternal - Uses the encrypted recipient handle as the CREATE2 salt
- Deploys a
RaycashDepositorat the deterministic address - Sweeps the expected amount from the depositor to the wrapper
- Compresses the amount to
euint64viarate() - Creates a
Depositrecord with the encrypted recipient - Commits the encrypted recipient for future withdrawal verification
Encrypted Recipients and Proof Binding
The recipient is an FHE-encrypted address (externalEaddress). The inputProof in DeployParams is bound to the wrapper's contract address — it is verified inside the wrapper via FHE.fromExternal(encryptedRecipient, inputProof). Integrators must encrypt the recipient using the wrapper address as the contract context (not the depositor or any other address).
This means the wrapper doesn't know who the tokens are for until finalization. finalizeWrap(depositIndices, recipient) is permissionless — anyone can call it on behalf of any recipient. The FHE.eq check determines whether a given deposit's encrypted amount is credited to the recipient or treated as zero (decoy). Non-matching recipients mint confidential zero, indistinguishable from a real mint.
Deposit Record
struct Deposit {
address depositor; // CREATE2 depositor address (cleartext)
uint256 originalAmount; // cleartext amount for off-chain tracking
euint64 amount; // encrypted amount, consumed by finalizeWrap
eaddress recipient; // encrypted recipient
}Phase 2: finalizeWrap
The recipient (or anyone on their behalf) calls finalizeWrap with a list of deposit indices and the recipient address.
The wrapper:
- For each index, computes
isMatch = FHE.eq(deposit.recipient, recipient) - Accumulates
sum = Σ FHE.select(isMatch, deposit.amount, 0)— non-matching deposits contribute zero - Zeroes matched deposits to prevent double-claiming
- Mints
sumto the recipient
finalizeWrap is permissionless — anyone can call it with valid deposit indices and a recipient address.
Decoy Mechanism
The caller must include at least minDecoys deposit indices. Non-matching deposits are processed but contribute zero to the sum via FHE.select. This hides which specific deposits belong to the recipient — an observer sees a batch of indices but cannot determine which ones were actually claimed.
Was this page helpful?