Skip to main content

Overview

An intent represents a buyer’s commitment to pay off-chain fiat currency in exchange for on-chain tokens. This page walks through the complete lifecycle from signal to fulfillment, showing all contract interactions and state changes.
The intent lifecycle is the core workflow of the zkp2p protocol, coordinating between Orchestrator, Escrow, payment verifiers, and registries.

Lifecycle Stages

Stage 1: Discovery

The buyer browses available deposits (off-chain indexing or on-chain queries) to find one matching their requirements:

Stage 2: Gating Service (Optional)

If the deposit has a intentGatingService, the buyer must obtain a signature:

Request Signature

Gating Service Validation

The gating service may:
  • Perform KYC/AML checks
  • Verify the buyer is on an allowlist
  • Check reputation scores
  • Rate-limit signaling frequency
  • Enforce geographic restrictions
If the deposit has no gating service (intentGatingService == address(0)), this stage is skipped.

Stage 3: Signal Intent

The buyer calls signalIntent() on the Orchestrator.

Transaction Flow

Code Example

State Changes

Orchestrator:
  • Creates Intent struct with all parameters
  • Adds intent hash to accountIntents[buyer]
  • Stores intentMinAtSignal[intentHash] (deposit’s min amount at signal time)
  • Increments intentCounter
Escrow:
  • Validates deposit is accepting intents
  • Validates amount is within deposit’s intentAmountRange
  • Prunes expired intents if liquidity is needed
  • Moves amount from remainingDeposits to outstandingIntentAmount
  • Creates Intent struct with expiryTime = timestamp + intentExpirationPeriod
  • Adds intent hash to depositIntentHashes[depositId]

Validation Checks

The Orchestrator performs extensive validation:
  1. Multiple Intent Check: Buyer must be whitelisted relayer OR allowMultipleIntents == true
  2. Zero Address Check: to address must not be zero
  3. Fee Validation: Referrer fee ≤ 50%, and must be 0 if no referrer
  4. Hook Validation: If post-intent hook set, must be whitelisted
  5. Escrow Validation: Must be whitelisted OR registry accepts all escrows
  6. Payment Method: Must exist in PaymentVerifierRegistry and be active on deposit
  7. Currency: Must be supported by payment method with non-zero min rate
  8. Conversion Rate: Must be ≥ deposit’s min conversion rate
  9. Gating Signature: If deposit has gating service, signature must be valid and not expired

Stage 4: Awaiting Payment

After signaling, the buyer has intentExpirationPeriod (configured on Escrow) to complete the off-chain payment.

Buyer Actions

  1. Make Off-Chain Payment: Send fiat to the depositor’s payment account (Venmo username, PayPal email, etc.)
  2. Include Intent Hash: Some payment methods allow notes/memos - buyer should include intent hash
  3. Wait for Confirmation: Payment service confirms transaction

Intent Status

Stage 5: Payment Attestation

Once the off-chain payment is confirmed, an attestation service generates a cryptographic proof.

Attestation Service Flow

Constructing the Attestation

Stage 6: Fulfill Intent

Anyone can submit the attestation to fulfill the intent.

Transaction Flow

Code Example

State Changes

Verifier:
  • Validates attestation signatures
  • Adds payment ID to NullifierRegistry
  • Emits PaymentVerified event
Orchestrator:
  • Deletes Intent from storage
  • Removes intent hash from accountIntents
  • Deletes intentMinAtSignal
  • Emits IntentPruned and IntentFulfilled events
Escrow:
  • Moves amount from outstandingIntentAmount back to available balance
  • Deletes Intent struct
  • Removes intent hash from depositIntentHashes
  • Transfers tokens to Orchestrator
  • May close deposit if dust threshold reached
  • Emits FundsUnlockedAndTransferred event

Fee Distribution

Alternative Paths

Path A: Cancellation

If the buyer changes their mind before making payment:
Flow:
  1. Orchestrator validates caller is intent owner
  2. Orchestrator deletes intent from storage
  3. Orchestrator calls escrow.unlockFunds(depositId, intentHash)
  4. Escrow returns amount to remainingDeposits
  5. Escrow deletes intent from storage
Use Cases:
  • Buyer found better rate elsewhere
  • Payment service is down
  • Buyer doesn’t have sufficient fiat balance

Path B: Expiration

If the intent is not fulfilled before expiryTime:
Flow:
  1. Escrow iterates through depositIntentHashes[depositId]
  2. For each expired intent (block.timestamp > expiryTime):
    • Return amount to remainingDeposits
    • Delete intent from storage
    • Remove from depositIntentHashes
    • Emit FundsUnlocked event
  3. Escrow calls orchestrator.pruneIntents(expiredIntents)
  4. Orchestrator deletes each intent from storage
Automatic Pruning: Expired intents are automatically pruned when:
  • Depositor calls removeFunds() or withdrawDeposit()
  • New intent is signaled and liquidity is needed
  • Anyone calls pruneExpiredIntents()

Path C: Manual Release

If there’s a dispute or special arrangement, the depositor can manually release funds:
Flow:
  1. Orchestrator validates caller is the deposit’s depositor
  2. Orchestrator deletes intent from storage
  3. Orchestrator calls escrow.unlockAndTransferFunds() with full intent amount
  4. Escrow transfers tokens to Orchestrator
  5. Orchestrator calculates and transfers fees
  6. Orchestrator transfers net amount to buyer
  7. Emit IntentFulfilled(intentHash, to, netAmount, isManualRelease: true)
Security: Manual release still charges protocol and referrer fees. Depositors cannot bypass fee collection.

Intent States Summary

Timeline Example

Best Practices

Monitor Expiry

Buyers should complete payment well before expiryTime to account for attestation delays

Include Intent Hash

If payment service supports memos, include intent hash for faster attestation matching

Use Relayers

Buyers can outsource fulfillment submission to relayers to save gas and improve UX

Verify Deposit

Before signaling, verify deposit has sufficient liquidity and acceptable parameters