# OTC private swap

> Swap shielded assets between two parties, each submitting their own leg without a shared relayer.

Two parties want to swap shielded assets: Alice sends ETH-in-pool to Bob, and Bob sends USDC-in-pool to Alice. This guide follows the patterns established by the [OTC PoC](https://github.com/0xbow-io/OTC-PoC) repo.

## Architecture overview

Each party generates their leg of the swap as a private [transfer](/operations/transfer). A coordinator (the OTC server, or a paired browser session) sequences the submissions so they land back-to-back.

## The flow

1.  **Alice posts a maker order.** She specifies "I'll send 0.005 ETH for 50 USDC."
2.  **Bob accepts.** He picks the order from the order book.
3.  **Both prepare their legs in their own browsers.** Alice's leg spends an ETH [note](/concepts/notes) with Bob as the recipient, and Bob's leg spends a USDC note with Alice as the recipient. Each side generates its own proof client-side.
4.  **Both POST the prepared `relayOptions[0]` to the coordinator.** The coordinator forwards each payload to the [relayer](/operations/relaying) in sequence.
5.  **Both txs land back-to-back.** There is no atomicity, so one leg can land even if the other reverts.

## Code sketch (Alice's side)

```ts
const toHex = (value: bigint): `0x${string}` => `0x${value.toString(16)}`;
const prepared = await session.prepareTransfer({
    inputCommitments: [aliceEthNote.commitment],
    amount: toHex(parseEther("0.005")),
    tokenId: NATIVE_ETH,
    recipientDiscoveryData: { evmAddress: bobAddress },
});

// Capture the recipient note for a receipt back to Bob.
const recipientNote = prepared.executeOptions.recipientPendingNotes[0];

// Hand the prepared payload to the OTC coordinator.
await api.post(`/api/orders/${order.id}/submit-maker`, {
    relayParams: prepared.relayOptions[0],
    recipientCommitment: recipientNote.commitment,
    recipientNoteSecret: recipientNote.noteSecret,
});
```

## Coordinator's role

The coordinator's role is narrow:

-   It owns the order book: it creates orders, lists them, and updates their status as they move through `open`, `maker_ready`, `settling`, and `complete`.
-   It forwards each prepared payload to the relayer's `/v1/relay/...` endpoint, in sequence.
-   It polls for both [receipts](/operations/receipts-and-viewing-keys) and updates the order status.
-   It never holds any user keys.

## Partial-landing window

The maker's leg can confirm while the taker's reverts, because the protocol doesn't provide atomicity across separate transact calls. There are a few ways to manage that window:

-   Show a UI banner: "Sequential, same-block target. Manual unwind required if taker reverts after maker lands."
-   If the taker's leg reverts, surface a `partial_settled` state. The maker still holds a recipient note they don't owe back, so the broken swap settles in their favor.
-   For an atomic alternative, wrap both transactions in `Multicall3.aggregate3(requireSuccess=true)`. This requires building both proofs with the Multicall3 contract as the `processor`, which lets the `msg.sender == processor` check pass.

## Auto-import after settlement

After settlement, a client can call `importReceivedNote` for the note it received, so the new shielded balance appears immediately instead of waiting for the next [discovery](/operations/manage-notes) sync. The OTC PoC is a single-wallet demo where one session plays both sides, so its coordinator does this for the maker-leg and taker-leg notes at once.
