ConfidentialPaymentRouterV2 lets you send any ERC-20 token where the transfer amount is hidden on Ethereum. You pay with plain ERC-20; the router wraps it into a cToken, transfers it encrypted, then unwraps it — the receiver gets standard ERC-20 with no FHE interaction required.
Mainnet address: 0x087D50Bb21a4C7A5E9394E9739809cB3AA6576Fa
Constants
| Constant | Value |
|---|---|
MIN_RELAYER_FEE | 0.00005 ETH |
PAYMENT_TIMEOUT | 1 day |
Payment flow
send
amount of token before calling, and attach at least MIN_RELAYER_FEE in ETH.
The ERC-20 token to send. A wrapper must exist in WrapperFactory (or the router will revert with
WrapperNotFound).Who receives the plain ERC-20 after finalization.
Amount in the token’s native decimals.
Human-readable note attached to the payment.
paymentId used to track and finalize the payment. Estimated gas: ~1,500,000.
finalize
The payment ID returned by
send().FHE ciphertext handles decrypted by the KMS.
ABI-encoded decrypted values (the
canUnwrap boolean).Threshold KMS signatures verified via
FHE.checkSignatures().cancel
PAYMENT_TIMEOUT (1 day) if it has not been finalized. Only the original sender can cancel. The ERC-20 is returned and the ETH fee refunded.
getPayment
Payment struct fields
Address that initiated the payment.
Address that will receive the plain ERC-20.
The ConfidentialWrapper used for this payment.
Amount in 6-decimal cToken units.
The unwrap request ID in the ConfidentialWrapper contract.
The
ebool ciphertext handle for canUnwrap.ETH fee paid to the relayer on finalization.
Human-readable note.
Block timestamp of payment creation.
Whether the payment has been finalized.
Whether the payment was cancelled.
Events
| Event | Parameters |
|---|---|
PaymentCreated | paymentId, sender, receiver, wrapper, amount, memo |
PaymentUnwrapRequested | paymentId, unwrapRequestId, handle |
PaymentFinalized | paymentId, receiver, amount, success |
PaymentCancelled | paymentId, sender |
Custom errors
| Error | When thrown |
|---|---|
ZeroAddress | receiver is address(0) |
ZeroAmount | amount is 0 |
InsufficientRelayerFee | msg.value < MIN_RELAYER_FEE |
PaymentNotFound | paymentId does not exist |
PaymentAlreadyFinalized | Payment already finalized or cancelled |
WrapperNotFound | No wrapper exists for the given token |
NotSender | Caller is not the payment sender (for cancel) |
TooEarly | Timeout not yet elapsed (for cancel) |
Confidential Payments
How to use confidential payments from the frontend.
ConfidentialWrapper
The wrapper contract used internally by the router.