Skip to main content

Smart Contracts (V3)

This page summarizes the V3 on-chain contracts and links to detailed pages for each component.

Orchestrator

  • Purpose: lifecycle for intents (signal, cancel, fulfill), protocol/referral/manager fees, routing to verifiers, one per-deposit pre-intent hook, a snapshotted global lifecycle hook, and post-intent hooks.
  • Key entry points
    • signalIntent(SignalIntentParams)
      • Inputs: escrow, depositId, amount, to, paymentMethod (bytes32), fiatCurrency (bytes32), conversionRate (1e18 fixed), referralFees[] (multi-recipient), gatingServiceSignature, signatureExpiration, optional postIntentHook (IPostIntentHookV2), preIntentHookData, data.
      • The deposit's optional generic pre-intent hook executes first. The active lifecycle hook then runs onIntentSignaled before fund locking; a rejection reverts the whole transaction.
      • Manager fee is snapshotted from EscrowV2.getManagerFee(depositId).
      • Emits IntentSignaled(intentHash, escrow, depositId, paymentMethod, owner, to, amount, fiatCurrency, conversionRate, timestamp).
    • fulfillIntent(FulfillIntentParams)
      • Inputs: paymentProof (ABI-encoded PaymentAttestation), intentHash, optional verificationData, optional postIntentHookData.
      • Routes to the configured IPaymentVerifier, unlocks funds, distributes fees (protocol → manager → referral), and transfers net to to or post-intent hook.
      • Accepts verifier-authorized partial fulfillment amounts, including amounts below the deposit's signal-time minimum; unused locked liquidity returns to the deposit.
      • Emits IntentFulfilled.

UnifiedPaymentVerifier

  • Purpose: canonical on-chain verifier for off-chain attestations from buyer zkTLS, Buyer TEE Verification, and Seller Autopilot.
  • V3 uses UnifiedPaymentVerifierV2 (0x46A58Dc65587D4D7B8198C6A25eEdf5b2535Da94).
  • Typed data
    • Type: PaymentAttestation(bytes32 intentHash,uint256 releaseAmount,bytes32 dataHash)
    • Domain: name UnifiedPaymentVerifier, version 1, chainId, verifyingContract.
  • Attestation payload
    • intentHash: binds the attestation to an on-chain intent.
    • releaseAmount: token amount to release on-chain (after FX, before fees). Capped to intent.amount during verification.
    • dataHash: hash of the data blob.
    • signatures[]: witness signatures checked by AttestationVerifier.
    • data: ABI-encoded (PaymentDetails, IntentSnapshot).
    • metadata: optional attribution bytes. Current service responses encode buyer-zktls, buyer-tee, or seller-tee; this field is not signed or digested.
      • PaymentDetails:
        • method: bytes32 (payment method, e.g., keccak256("venmo"))
        • payeeId: bytes32 (hashed off-chain recipient id)
        • amount: uint256 (smallest fiat unit, e.g., cents)
        • currency: bytes32 (fiat currency code hash)
        • timestamp: uint256 (ms)
        • paymentId: bytes32 (hashed provider transaction id)
      • IntentSnapshot:
        • intentHash, amount, paymentMethod, fiatCurrency, payeeDetails, conversionRate, signalTimestamp, timestampBuffer

Verification rules (key checks)

  • EIP-712 signature validates over (intentHash, releaseAmount, dataHash) and the domain separator.
  • keccak256(data) == dataHash to prevent tampering.
  • Snapshot must match on-chain intent fields at fulfillment time.
  • Nullifier: keccak256(paymentMethod || paymentId) must be unused; it is recorded to prevent reuse.
  • Release capping: if releaseAmount > intent.amount, the verifier reduces to intent.amount.

AttestationVerifier

  • Purpose: witness management and threshold signature verification for the attestation digest.
  • The active implementation on Base is MultiAttestationVerifier, which supports a governed witness set with a signature threshold. SimpleAttestationVerifier (single witness, threshold 1) remains deployed but is not wired to the live UnifiedPaymentVerifierV2.

Escrow

  • EscrowV2 holds deposits and tracks payment methods + currencies per deposit.
  • Supports oracle-driven rate floors, delegated rate management (RateManagerV1), dust sweeping, and third-party funded deposits (depositTo).
  • Authorized Orchestrators (via OrchestratorRegistry) call into Escrow to lock/unlock and transfer funds.

OrchestratorRegistry

  • Simple allowlist authorizing orchestrator contracts on EscrowV2.
  • addOrchestrator(address) / removeOrchestrator(address) — Owner-only.
  • isOrchestrator(address) — Returns whether an address is authorized.
  • Replaces the single orchestrator address used in the legacy Escrow.

RateManagerV1

  • Pure rate registry for delegated rate management.
  • Managers create configs (createRateManager), set rates per deposit/method/currency.
  • Exposes getRate(rateManagerId, escrow, depositId, paymentMethod, currencyCode) and getFee(rateManagerId).
  • EscrowV2 enforces the effective rate floor as max(fixedRate, oracleRate, delegatedRate).
  • Manager fee (capped at 5%) is snapshotted at intent signal time and distributed at fulfillment.

ChainlinkOracleAdapter

  • Wraps Chainlink price feeds implementing IOracleAdapter.
  • getRate(normalizedConfig)(isValid, marketRate, updatedAt).
  • validateConfig(rawConfig) — Validates and normalizes adapter-specific config for storage.
  • Adapters are view-only (no state mutation) and normalize rates to 1e18 preciseUnits.
  • Spread is applied by EscrowV2, not the adapter: oracleRate = marketRate * (10_000 + spreadBps) / 10_000.

Pre-Intent Hooks

  • One optional generic hook slot per deposit on OrchestratorV3.
  • Runs during signalIntent before state changes and can only revert to reject.
  • SignatureGatingPreIntentHook is the built-in signature-gating implementation.
  • See Pre-Intent Hooks for details.

Lifecycle Hook and Access Policy

  • Governance selects one lifecycle hook on OrchestratorV3, and the selected address is snapshotted onto each new intent.
  • Production uses WhitelistLifecycleHook. On signal, it reads the deposit's WhitelistPolicy; public policies pass, while restricted policies require the taker to be directly allowed or a member of one of the deposit's allowed Groups.
  • WhitelistPolicy is depositor-owned. It supports direct wallet addresses plus up to ten Groups from AddressGroupRegistry per deposit.
  • Cancellation and settlement callbacks use the snapshotted lifecycle hook, so a later governance update does not change an existing intent's callback target.

Events (selected)

  • IntentSignaled, IntentFulfilled, IntentPruned, IntentReferralFeeDistributed, IntentManagerFeeSnapshotted
  • DepositPreIntentHookSet, LifecycleHookUpdated, IntentLifecycleHookSnapshotted
  • PaymentMethodAdded/Removed, AttestationVerifierUpdated.

Notes

  • All string-like identifiers (payment method, currency code, payee id, payment id) are keccak256-hashed to bytes32 on-chain.
  • conversionRate uses 1e18 precision (same as PRECISE_UNIT).