This guide covers the internal architecture. If you just want to make your first API call, go to Quick Start instead.
Understand OFFER-HUB's core payment orchestration architecture and how it processes transactions.
The Orchestrator is the central engine of OFFER-HUB. It coordinates every step of a transaction — from the moment a buyer deposits USDC to the moment a seller withdraws funds — enforcing rules, managing state, and communicating with the Stellar blockchain so your marketplace never has to.
This guide covers the internal architecture. If you just want to make your first API call, go to Quick Start instead.
The Orchestrator is a state machine-driven backend service that sits between your marketplace and the Stellar blockchain. It is responsible for:
Your marketplace communicates with the Orchestrator via REST API. The Orchestrator handles everything on-chain.
The Orchestrator is intentionally narrow in scope. It handles payments, escrow, and balances. Authentication, listings, and communication are your marketplace's responsibility.
The API in apps/api serves the REST API and processes BullMQ background jobs in the same service.
The separate apps/worker package is deprecated; do not deploy it as a second required process.
Marketplace requests enter the Orchestrator, which coordinates ledger, escrow, events, and idempotency work.
This view is verified against apps/api/src/modules/orders/orders.controller.ts, its dto/create-order.dto.ts, and apps/api/src/modules/events/event-catalog.ts in the Orchestrator repository; those files define the REST boundary, order input, and emitted domain events represented here.
Balance Ledger — tracks available and reserved balances for every user. All operations are atomic to prevent double-spending.
Escrow Engine — manages the lifecycle of each escrow from creation to settlement or dispute, enforcing valid state transitions.
SSE Event Stream — emits a domain event on every state change so your marketplace can react in real time.
Idempotency Key Store — caches responses to prevent duplicate operations when requests are retried.
Every transaction follows the same linear flow:
The buyer sends USDC to their assigned Stellar address. The Orchestrator detects the on-chain payment and credits the buyer's available balance.
The buyer places an order. Funds move from available to reserved in the internal ledger — no longer spendable but not yet on-chain.
The Orchestrator signs and submits a Stellar transaction that locks the USDC into a Soroban smart contract via Trustless Work. The escrow state moves to FUNDED.
At this point funds are held by the smart contract — not by OFFER-HUB. The Orchestrator cannot unilaterally move them. Only valid contract calls can release, dispute, or refund.
Your marketplace manages communication, deliverables, and reviews. The Orchestrator waits.
The buyer approves the delivery. The Orchestrator submits transactions to release USDC from the smart contract to the seller's Stellar address.
The seller requests a withdrawal. The Orchestrator signs a Stellar payment transaction sending USDC to any Stellar address.
POST /api/withdrawalsThe Orchestrator runs two distinct state machines: an Order state machine that tracks the order lifecycle, and an internal Escrow state machine that tracks the on-chain escrow contract. Invalid transitions are rejected with a 409 Conflict error. Both diagrams below match docs/architecture/state-machines.md in the orchestrator repository exactly.
| State | Description |
|---|---|
ORDER_CREATED | Order created off-chain, no funds reserved |
FUNDS_RESERVED | Buyer balance reserved (logical hold) |
ESCROW_CREATING | Creating escrow contract in Trustless Work |
ESCROW_FUNDING | Funding escrow with reserved balance |
ESCROW_FUNDED | Escrow funded, funds locked on-chain |
IN_PROGRESS | Work in progress |
RELEASE_REQUESTED | Release requested for the seller |
RELEASED | Funds released to the seller |
REFUND_REQUESTED | Refund requested for the buyer |
REFUNDED | Funds returned to the buyer |
DISPUTED | Dispute opened, flow frozen |
CLOSED | Order completed |
The escrow contract moves through its own lifecycle inside the Orchestrator while the order machine advances in parallel.
| State | Description |
|---|---|
CREATING | Creating contract in Trustless Work |
CREATED | Contract created, not funded |
FUNDING | Funding contract |
FUNDED | Funds locked in contract |
RELEASING | Releasing funds to the seller |
RELEASED | Funds released |
REFUNDING | Returning funds to the buyer |
REFUNDED | Funds returned |
DISPUTED | Active dispute |
Once an escrow reaches RELEASED or REFUNDED the contract is terminal — no further on-chain transitions are allowed. The order itself only reaches CLOSED after the escrow resolves and cleanup completes.
The Orchestrator uses standard HTTP status codes and structured error responses.
| Code | HTTP | Description |
|---|---|---|
INSUFFICIENT_BALANCE | 422 | Buyer's available balance is too low |
INVALID_STATE_TRANSITION | 409 | Transition not allowed from the current state |
DUPLICATE_IDEMPOTENCY_KEY | 200 | Already processed — cached response returned |
STELLAR_TX_FAILED | 502 | On-chain transaction rejected by Stellar |
ESCROW_NOT_FOUND | 404 | No escrow found with the given ID |
Always include an Idempotency-Key header on state-changing requests. Retrying with the same key is safe — the Orchestrator returns the cached result instead of processing twice.
Never generate a new idempotency key when retrying a failed request. A new key will create a duplicate operation.
The Orchestrator is built exclusively for USDC on Stellar. Stellar was chosen for its low fees (fractions of a cent), fast finality (3-5 seconds), and native USDC support via Circle. Smart contracts run on Soroban, managed through the Trustless Work protocol.
For local development, set STELLAR_NETWORK=testnet. You can fund test wallets using the Stellar Friendbot.
AirTM requires users to complete KYC on their platform. Crypto mode has no KYC requirement.