> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/zkp2p/zkp2p-contracts/llms.txt
> Use this file to discover all available pages before exploring further.

# Orchestrator Contract

> Intent lifecycle management, payment verification coordination, and P2P trading orchestration

## Overview

The **Orchestrator** is the intent coordination hub of the zkp2p protocol. It manages the complete lifecycle of intents from signal to fulfillment, coordinates with the Escrow for liquidity management, and integrates with payment verifiers to validate off-chain payments.

<Info>
  The Orchestrator acts as the "order book" for P2P fiat-to-crypto trades, matching buyers (who signal intents) with sellers (who provide liquidity via deposits).
</Info>

## Key Responsibilities

* **Intent Signaling**: Capture buyer commitment to pay off-chain for on-chain assets
* **Intent Verification**: Validate gating service signatures and deposit parameters
* **Fulfillment Coordination**: Verify payment proofs and release funds to buyers
* **Fee Management**: Collect protocol fees and referrer fees
* **Hook Execution**: Execute post-intent actions (bridging, swapping, etc.)

## Core Data Structures

### Intent Struct

Every intent signaled on the Orchestrator is represented by this comprehensive struct (from `contracts/interfaces/IOrchestrator.sol:12`):

```solidity theme={null}
struct Intent {
    address owner;                   // Address of the intent owner  
    address to;                      // Address to forward funds to (can be same as owner)
    address escrow;                  // Address of the escrow contract holding the deposit
    uint256 depositId;               // ID of the deposit the intent is associated with
    uint256 amount;                  // Amount of deposit.token the owner wants to take
    uint256 timestamp;               // Timestamp of the intent
    bytes32 paymentMethod;           // Payment method to be used for offchain payment
    bytes32 fiatCurrency;            // Currency code that the owner is paying in offchain
    uint256 conversionRate;          // Conversion rate of deposit token to fiat at time of intent
    bytes32 payeeId;                 // Hashed payee identifier to whom the owner will pay offchain
    address referrer;                // Address of the referrer who brought this intent (if any)
    uint256 referrerFee;             // Fee to be paid to the referrer in preciseUnits (1e16 = 1%)
    IPostIntentHook postIntentHook;  // Address of the post-intent hook that will execute actions
    bytes data;                      // Additional data to be passed to the post-intent hook
}
```

<Note>
  **Intent Immutability**: Once signaled, intent parameters are frozen on-chain. This gives strong guarantees to both buyer (locked exchange rate) and seller (payment details).
</Note>

### SignalIntentParams

When signaling an intent, users provide these parameters (from `contracts/interfaces/IOrchestrator.sol:29`):

```solidity theme={null}
struct SignalIntentParams {
    address escrow;                      // The escrow contract where the deposit is held
    uint256 depositId;                   // The ID of the deposit the taker intends to use
    uint256 amount;                      // The amount of deposit.token the user wants to take
    address to;                          // Address to forward funds to
    bytes32 paymentMethod;               // Payment method to be used for offchain payment
    bytes32 fiatCurrency;                // Currency code for offchain payment
    uint256 conversionRate;              // The conversion rate agreed offchain
    address referrer;                    // Address of the referrer (address(0) if no referrer)
    uint256 referrerFee;                 // Fee to be paid to the referrer
    bytes gatingServiceSignature;        // Signature from the deposit's gating service
    uint256 signatureExpiration;         // Timestamp when the gating service signature expires
    IPostIntentHook postIntentHook;      // Optional post-intent hook (address(0) for no hook)
    bytes data;                          // Additional data for the intent
}
```

## Intent Lifecycle

```mermaid theme={null}
stateDiagram-v2
    [*] --> Signaled: signalIntent()
    Signaled --> Cancelled: cancelIntent()
    Signaled --> Fulfilled: fulfillIntent()
    Signaled --> Released: releaseFundsToPayer()
    Signaled --> Expired: Time > expiryTime
    Expired --> Pruned: pruneIntents()
    Cancelled --> [*]
    Fulfilled --> [*]
    Released --> [*]
    Pruned --> [*]
```

## Signaling Intents

Buyers signal intent to purchase on-chain assets by paying off-chain (from `contracts/Orchestrator.sol:102`):

```solidity theme={null}
function signalIntent(SignalIntentParams calldata _params)
    external
    whenNotPaused
```

### Signal Validation

The `_validateSignalIntent()` function performs extensive checks (from `contracts/Orchestrator.sol:390`):

1. **Multiple Intent Check**: Verify user can have multiple active intents (whitelisted relayer or `allowMultipleIntents == true`)
2. **Address Validation**: Ensure `to` address is not zero
3. **Fee Validation**:
   * Referrer fee must be ≤ 50% (`MAX_REFERRER_FEE`)
   * If no referrer, fee must be 0
4. **Post-Intent Hook Validation**: If set, must be whitelisted in `postIntentHookRegistry`
5. **Escrow Validation**: Must be whitelisted or registry accepts all escrows
6. **Payment Method Validation**: Must exist in `paymentVerifierRegistry` and be active on the deposit
7. **Currency Validation**: Must be supported by the payment method with non-zero min conversion rate
8. **Conversion Rate Validation**: Must meet or exceed deposit's min conversion rate
9. **Gating Service Signature**: If deposit has a gating service, validate EIP-712 signature

### Intent Hash Calculation

Intent hashes are deterministically generated (from `contracts/Orchestrator.sol:444`):

```solidity theme={null}
function _calculateIntentHash() internal view returns (bytes32 intentHash) {
    uint256 intermediateHash = uint256(
        keccak256(
            abi.encodePacked(
                address(this),    // Orchestrator address for global uniqueness
                intentCounter     // Unique counter within this orchestrator
            )
        ));
    intentHash = bytes32(intermediateHash % CIRCOM_PRIME_FIELD);
}
```

<Tip>
  The hash is modded by `CIRCOM_PRIME_FIELD` (254-bit prime) to ensure compatibility with circom-based ZK circuits.
</Tip>

### What Happens on Signal

1. Validate all parameters via `_validateSignalIntent()`
2. Generate unique `intentHash` via `_calculateIntentHash()`
3. Fetch deposit and payment method data from Escrow
4. Store snapshot of deposit's min intent amount (`intentMinAtSignal`)
5. Create and store the full Intent struct
6. Add intent hash to `accountIntents[msg.sender]`
7. Increment `intentCounter`
8. Emit `IntentSignaled` event
9. Call `IEscrow(escrow).lockFunds(depositId, intentHash, amount)`

<Warning>
  If `lockFunds()` on the Escrow reverts (insufficient liquidity, deposit not accepting intents, etc.), the entire transaction reverts.
</Warning>

## Cancelling Intents

Intent owners can cancel their own intents before fulfillment (from `contracts/Orchestrator.sol:161`):

```solidity theme={null}
function cancelIntent(bytes32 _intentHash) external
```

**Process**:

1. Verify intent exists (`timestamp != 0`)
2. Verify caller is intent owner
3. Prune intent (delete from storage, remove from `accountIntents`)
4. Call `IEscrow(escrow).unlockFunds(depositId, intentHash)` to release liquidity
5. Emit `IntentPruned` event

## Fulfilling Intents

Anyone can submit a fulfillment with valid payment proof (from `contracts/Orchestrator.sol:184`):

```solidity theme={null}
function fulfillIntent(FulfillIntentParams calldata _params) external nonReentrant whenNotPaused
```

### FulfillIntentParams

```solidity theme={null}
struct FulfillIntentParams {
    bytes paymentProof;           // Payment proof (attestation, ZK proof, etc.)
    bytes32 intentHash;           // Identifier of intent being fulfilled
    bytes verificationData;       // Additional data for payment verifier
    bytes postIntentHookData;     // Additional data for post intent hook
}
```

### Fulfillment Flow

```mermaid theme={null}
sequenceDiagram
    participant User
    participant Orchestrator
    participant Verifier
    participant Escrow
    participant Hook
    
    User->>Orchestrator: fulfillIntent()
    Orchestrator->>Verifier: verifyPayment()
    Verifier-->>Orchestrator: PaymentVerificationResult
    Orchestrator->>Orchestrator: Validate min-at-signal
    Orchestrator->>Orchestrator: _pruneIntent()
    Orchestrator->>Escrow: unlockAndTransferFunds()
    Escrow-->>Orchestrator: Transfer tokens
    Orchestrator->>Orchestrator: _calculateAndTransferFees()
    alt Has PostIntentHook
        Orchestrator->>Hook: execute()
    else No Hook
        Orchestrator->>User: Transfer net amount
    end
    Orchestrator->>Orchestrator: Emit IntentFulfilled
```

### Payment Verification

The Orchestrator calls the payment verifier registered for the intent's payment method (from `contracts/Orchestrator.sol:194`):

```solidity theme={null}
address verifier = paymentVerifierRegistry.getVerifier(intent.paymentMethod);

IPaymentVerifier.PaymentVerificationResult memory verificationResult = 
    IPaymentVerifier(verifier).verifyPayment(
        IPaymentVerifier.VerifyPaymentData({
            intentHash: _params.intentHash,
            paymentProof: _params.paymentProof,
            data: _params.verificationData
        })
    );
```

The verifier returns:

```solidity theme={null}
struct PaymentVerificationResult {
    bool success;              // Whether verification succeeded
    bytes32 intentHash;        // The intent hash (must match)
    uint256 releaseAmount;     // The amount to release (may be partial)
}
```

### Min-At-Signal Enforcement

To prevent sub-minimum partial fulfillments, the Orchestrator enforces the deposit's min intent amount **at the time of signal** (from `contracts/Orchestrator.sol:205`):

```solidity theme={null}
uint256 minAtSignal = intentMinAtSignal[_params.intentHash];
if (minAtSignal > 0 && verificationResult.releaseAmount < minAtSignal) {
    revert AmountBelowMin(verificationResult.releaseAmount, minAtSignal);
}
```

<Info>
  This prevents a depositor from raising their min intent amount after an intent is signaled, then getting a partial fulfillment below the original minimum.
</Info>

## Fee Management

### Protocol Fee

The protocol charges a fee on the **release amount** (from `contracts/Orchestrator.sol:485`):

```solidity theme={null}
if (protocolFeeRecipient != address(0) && protocolFee > 0) {
    protocolFeeAmount = (_releaseAmount * protocolFee) / PRECISE_UNIT;
    _token.safeTransfer(protocolFeeRecipient, protocolFeeAmount);
}
```

* Max protocol fee: **10%** (`MAX_PROTOCOL_FEE = 1e17` at line 41)
* Fee is in `preciseUnits` (1e16 = 1%)

### Referrer Fee

Referrers can earn a fee for bringing users to the protocol (from `contracts/Orchestrator.sol:490`):

```solidity theme={null}
if (_intent.referrer != address(0) && _intent.referrerFee > 0) {
    referrerFeeAmount = (_releaseAmount * _intent.referrerFee) / PRECISE_UNIT;
    _token.safeTransfer(_intent.referrer, referrerFeeAmount);
}
```

* Max referrer fee: **50%** (`MAX_REFERRER_FEE = 5e17` at line 40)
* Paid from the release amount

### Net Amount Calculation

```solidity theme={null}
uint256 netFees = protocolFeeAmount + referrerFeeAmount;
uint256 netAmount = releaseAmount - netFees;
```

The `netAmount` is what the buyer receives (or is passed to the post-intent hook).

## Post-Intent Hooks

Post-intent hooks enable custom actions after fulfillment, such as:

* Bridging tokens to another chain (Across, Stargate)
* Swapping tokens (Uniswap, 1inch)
* Depositing to yield protocols
* Multi-step DeFi operations

### Hook Execution

If an intent has a `postIntentHook` set (from `contracts/Orchestrator.sol:534`):

```solidity theme={null}
if (address(_intent.postIntentHook) != address(0)) {
    // Grant exact allowance to the hook
    _token.safeApprove(address(_intent.postIntentHook), 0);
    _token.safeApprove(address(_intent.postIntentHook), netAmount);
    
    // Execute the hook
    _intent.postIntentHook.execute(_intent, netAmount, _postIntentHookData);
    
    // Enforce exact consumption
    uint256 postBalance = _token.balanceOf(address(this));
    require(postBalance <= preBalance, "PostIntentHook: unexpected balance increase");
    uint256 spent = preBalance - postBalance;
    require(spent == netAmount, "PostIntentHook: must pull exact netAmount");
    
    // Reset allowance
    _token.safeApprove(address(_intent.postIntentHook), 0);
}
```

<Warning>
  **Security**: The hook must pull **exactly** `netAmount` from the Orchestrator. Any deviation causes the transaction to revert. This prevents hooks from leaving stranded funds or draining extra tokens.
</Warning>

## Manual Release by Depositor

Depositors can manually release funds to the buyer in case of disputes or off-chain arrangements (from `contracts/Orchestrator.sol:232`):

```solidity theme={null}
function releaseFundsToPayer(bytes32 _intentHash) external nonReentrant
```

**Process**:

1. Verify caller is the depositor for the intent's deposit
2. Prune intent from storage
3. Call `unlockAndTransferFunds()` with **full intent amount** (no partial)
4. Calculate and transfer fees
5. Transfer net amount to `intent.to`
6. Emit `IntentFulfilled(intentHash, to, netAmount, isManualRelease: true)`

<Note>
  Manual releases still charge protocol and referrer fees.
</Note>

## Intent Pruning

Expired intents are pruned by the Escrow contract, which calls back to the Orchestrator (from `contracts/Orchestrator.sol:257`):

```solidity theme={null}
function pruneIntents(bytes32[] calldata _intents) external
```

Only the Escrow that owns the intent can prune it:

```solidity theme={null}
if (
    intent.timestamp != 0 &&           // Intent exists
    intent.escrow == msg.sender        // Caller is the owning escrow
) {
    _pruneIntent(intentHash);
}
```

## Gating Service Signatures

Deposits can require buyers to obtain a signature from a **gating service** (KYC provider, allowlist manager, etc.) before signaling intents.

### Signature Validation

The signature covers (from `contracts/Orchestrator.sol:578`):

```solidity theme={null}
bytes memory message = abi.encodePacked(
    address(this),              // Orchestrator address
    _intent.escrow, 
    _intent.depositId, 
    _intent.amount, 
    _intent.to, 
    _intent.paymentMethod, 
    _intent.fiatCurrency, 
    _intent.conversionRate, 
    _intent.signatureExpiration,
    chainId
);

bytes32 verifierPayload = keccak256(message).toEthSignedMessageHash();
return _intentGatingService.isValidSignatureNow(verifierPayload, _intent.gatingServiceSignature);
```

<Tip>
  The signature includes `signatureExpiration` to prevent replay attacks. Signatures must be used before expiry.
</Tip>

## State Variables

| Variable                  | Description                                   | Location |
| ------------------------- | --------------------------------------------- | -------- |
| `chainId`                 | Immutable chain identifier                    | Line 45  |
| `intents`                 | Mapping of intentHash to Intent struct        | Line 47  |
| `accountIntents`          | Mapping of account to array of intent hashes  | Line 48  |
| `intentMinAtSignal`       | Snapshot of min intent amount at signal time  | Line 52  |
| `escrowRegistry`          | Registry of whitelisted escrows               | Line 55  |
| `paymentVerifierRegistry` | Registry of payment verifiers                 | Line 56  |
| `postIntentHookRegistry`  | Registry of whitelisted hooks                 | Line 57  |
| `relayerRegistry`         | Registry of whitelisted relayers              | Line 58  |
| `protocolFee`             | Protocol fee in preciseUnits (1e16 = 1%)      | Line 61  |
| `protocolFeeRecipient`    | Recipient of protocol fees                    | Line 62  |
| `allowMultipleIntents`    | Whether all users can have multiple intents   | Line 64  |
| `intentCounter`           | Incrementing nonce for intent hash generation | Line 66  |

## Access Control

### Owner (Governance)

* Set escrow registry
* Set protocol fee (max 10%)
* Set protocol fee recipient
* Set allow multiple intents
* Set post-intent hook registry
* Set relayer registry
* Pause/unpause orchestrator

### Users

* Signal intents (with valid gating signature if required)
* Cancel own intents
* Fulfill any intent (with valid payment proof)

### Depositors

* Manually release funds to buyers

### Escrows

* Prune expired intents

## Key Events

```solidity theme={null}
event IntentSignaled(
    bytes32 indexed intentHash, 
    address indexed escrow,
    uint256 indexed depositId, 
    bytes32 paymentMethod, 
    address owner, 
    address to, 
    uint256 amount, 
    bytes32 fiatCurrency, 
    uint256 conversionRate, 
    uint256 timestamp
);

event IntentPruned(bytes32 indexed intentHash);

event IntentFulfilled(
    bytes32 indexed intentHash,
    address indexed fundsTransferredTo,   // Can be intent.to or hook address
    uint256 amount,
    bool isManualRelease
);
```

## Security Features

<CardGroup cols={2}>
  <Card title="Reentrancy Protection" icon="shield">
    `fulfillIntent` and `releaseFundsToPayer` use `nonReentrant` guard
  </Card>

  <Card title="Intent Immutability" icon="lock">
    Once signaled, intent parameters cannot be changed
  </Card>

  <Card title="Min-At-Signal" icon="gauge">
    Prevents depositors from raising minimums after intent signal
  </Card>

  <Card title="Hook Safety" icon="shield-check">
    Post-intent hooks must consume exact allowance or revert
  </Card>
</CardGroup>

## Related Contracts

* [Escrow](/concepts/escrow) - Manages deposits and locks liquidity
* [Payment Verification](/concepts/payment-verification) - Verifies off-chain payment proofs
* [Intent Lifecycle](/concepts/intent-lifecycle) - Complete intent flow diagram
* [Registry System](/concepts/registry-system) - Whitelisting and authorization
