Initial escrow + lot lifecycle
The first public release of the FairFoundry Fabric. Three roles, one escrow, one lot lifecycle. Everything later releases ship — staking, dispute resolution, governance timelock, the per-service Service Fabric — extends the primitives that landed here.
Three roles, one contract
Every contract instance is initialized with three addresses, each with non-overlapping authority:
| Role | Authority |
|---|---|
| OEM | Deposits escrow, withdraws unused escrow, executes settlement |
| Factory | Creates lots against escrowed funds; receives payment on approval |
| QA | Commits test metadata, signs off on lot, updates final unit counts |
The role separation is enforced by Soroban's require_auth() primitive. Every state-changing function is gated on a specific caller; cross-role grants are not possible without re-initialization — and re-initialization itself was hardened in v0.4.1.
Escrow
Escrow is a single OEM-controlled balance held inside the contract. The OEM funds it ahead of production; the contract pays it out as lots get approved.
pub fn deposit_escrow(env: Env, from: Address, amount: i128)
pub fn withdraw_escrow(env: Env, oem: Address, amount: i128)
Withdrawal is constrained by min_escrow_lots — the OEM cannot drain escrow if it would leave the balance below the floor needed to cover currently-open lots. This is the contract's solvency guarantee: a factory creating a lot today knows the funds to settle it are committed and cannot be pulled mid-flight.
Lot lifecycle
A “lot” is a batch of manufactured units. The factory creates it; the QA tests it; the contract pays for it. The whole journey is one state machine.
State diagram
Open
↓ qa_commit (QA submits test metadata)
InQA
↓ qa_update_counts (QA finalizes pass/fail counts)
Approved
↓ execute_payment (payment > 0) ↓ execute_payment (payment ≤ 0)
Paid Closed
Disputed and Refunded were reserved as LotStatus variants in v0.1.0 but not exercised — they're the entry points consumed by the dispute path that lands in v0.3.0.
Mutators
| Function | Caller | Transition |
|---|---|---|
| create_lot | Factory | (none) → Open |
| qa_commit | QA | Open / InQA → InQA |
| qa_commit_serials | QA | Adds per-unit serial-number commitments |
| qa_commit_attestation | QA | Adds testbench attestation (equipment identity) |
| qa_commit_full | QA | Combined metadata + serials + attestation in one call |
| qa_update_counts | QA | InQA → Approved (when all units accounted for) |
| execute_payment | OEM | Approved → Paid or Closed |
Init parameters
The contract is configured at init; every parameter that matters for the lifecycle is set then. The relevant subset for v0.1.0:
| Parameter | Type | Purpose |
|---|---|---|
| oem / factory / qa | Address | The three roles, all required to authorize init |
| pay_asset | PaymentAsset | Which token settles payouts (Stellar native, USDC, etc.) |
| pricing | Pricing | price_per_unit + per-failure penalties |
| ers | ERS | Hash of the Engineering Requirement Spec the lot is built against |
| min_escrow_lots | u32 | Solvency floor; escrow cannot drop below this many open lots |
| min_qa_stake | i128 | Reserved for v0.2.0 staking; set to 0 in v0.1.0 deployments |
| oem_bond | i128 | Reserved future field |
| dispute_window_secs | u64 | Floor: 1800 seconds (30 minutes); used by the v0.3.0 dispute path |
Both oem.require_auth() and factory.require_auth() and qa.require_auth() must succeed for init to land. No role can deploy a contract that binds the others without their on-chain consent.
Tests
30 tests pass. Coverage:
- Init happy paths (Cloud-hosted + Customer-hosted deployment models)
- Init parameter validation (negative price, negative stake, undersized dispute window)
- Escrow deposit / withdraw / solvency floor enforcement
- Lot create / qa_commit / qa_commit_full / qa_update_counts / execute_payment happy paths
- Status-machine violations (qa_commit on a Paid lot, execute_payment from non-Approved)
- Authorization violations (factory tries to deposit, OEM tries to qa_commit, etc.)
- Payment math: per-unit price minus failure penalties;
Closedpath when penalties exceed lot value
Compiles cleanly for wasm32-unknown-unknown. Full suite runs in well under a second.
What's not here yet
- QA collateral → v0.2.0 adds
stake_qa+ slashing primitives - Disputes → v0.3.0 adds re-inspection + governance timelock
- Per-service settlement → v0.4.0 adds the Service Fabric on top of the lot pipeline
References
Source: github.com/fairb-dev/Fairfoundry. Architecture diagrams + the full contract API table live on the platform page. The technical narrative on what the contract actually settles is in the blog post The smart contract behind a 48-hour settlement.