The policy and state engine behind Unbound: issuer verification, signed redemption policies, exchange intents, redemptions and reconciliation.
Part of Unbound - turn stranded value into something usable.
Store credit is usually stuck inside one retailer. Unbound lets a person exchange store credit that a retailer has genuinely issued and agreed to honor for other Stellar assets, at a real order-book quote that they sign on their own device, or redeem it with the issuer.
- Issuer-authorized only. A verified merchant signs a redemption policy with its Stellar issuer key and must authorize every holder's trustline. No issuer, no usable credit; arbitrary gift cards are out of scope.
- No fake liquidity. Quotes come from real Stellar path payments, expire and carry a minimum-output floor. No route means no quote. Unbound never promises a price, liquidity or fiat cash-out.
- Ledger-truthful. Nothing is shown as settled until the ledger confirms it; unknown outcomes reconcile by hash.
- Testnet only today. Mainnet needs an external security review and legal/compliance work that has not happened.
Unbound-core is the system of record. It decides whether a credit is eligible (verified issuer, valid signed
policy, not revoked or expired), runs the exchange-intent state machine (quote -> intent -> signature -> submit ->
confirmed / failed / reconciling) with idempotency keys, handles redemptions and disputes, and consumes
reconciliation events from Unbound-stellar. It never builds or parses Stellar transactions and never sees a user's
secret key.
Unbound is four independently tested repositories with one owner per boundary. This one is highlighted.
flowchart LR
M["Unbound-mobile<br/>Flutter wallet"]
S["Unbound-site<br/>landing + merchant console"]
C["Unbound-core<br/>HTTP API v1, policy, intents, audit"]
G["Unbound-stellar<br/>SDK, gateway service, chain worker"]
N[("Stellar Testnet<br/>assets, trustlines, DEX")]
M -- "quotes, intents, signed XDR" --> C
S -- "merchant auth, redemption queue" --> C
C -- "quote / build / submit" --> G
G -- "Horizon" --> N
G -- "reconciliation events" --> C
style C fill:#7537F5,stroke:#10092E,color:#ffffff
Clients never build Stellar transactions, and core never reimplements ledger logic. Details:
docs/ARCHITECTURE.md and docs/STELLAR_PROTOCOL.md.
Requires Node 22+. Everything below runs offline with the clearly labelled FakeStellarGateway (no Stellar network).
npm ci
npm run check # lint + typecheck + tests + build
# Local dev server with durable SQLite and a synthetic seed (choose your own dev-only passwords, >= 12 chars)
mkdir -p data
export DATABASE_FILE=./data/dev.db \
BOOTSTRAP_ADMIN_EMAIL=admin@example.invalid BOOTSTRAP_ADMIN_PASSWORD='change-me-admin-123' \
SEED_MERCHANT_EMAIL=shop@example.invalid SEED_MERCHANT_PASSWORD='change-me-shop-1234'
npm run seed # verified merchant + SHOP credit program + USDC target (synthetic)
npm start # http://127.0.0.1:3001
curl http://127.0.0.1:3001/v1/targetsTo use a real Unbound-stellar gateway instead of the fake, also set STELLAR_GATEWAY_URL and STELLAR_GATEWAY_TOKEN
(see .env.example). Then point Unbound-site at this server and sign in
as the seeded merchant to see the console. Useful dev switches: CORS_ALLOWED_ORIGINS=http://localhost:3000 (browser
on another origin), STELLAR_ESCROW_ENABLED=true (escrowed redemptions; the fake gateway emulates the contract),
JOBS_ENABLED=false (no background jobs). Password-reset emails are printed to the server log, never sent.
Legend: Built = implemented and covered by automated tests in this repo. Not yet = not started or not verified.
| Capability | Status |
|---|---|
| Merchant apply, admin verify (evidence required), revoke; revocation suspends programs immediately | Built |
| Signed redemption policy (ed25519 by the issuer key, strict schema, expiry, explicit tokenization + transfer permission) | Built |
Durable SQLite storage behind a Storage port, with migrations and restart tests |
Built (interim store, not for scale) |
| Auth: wallet challenge/verify for consumers; scrypt password + lockout for merchants and admins | Built |
| Exchange quote -> intent -> submit state machine (idempotency, write-ahead submit, RECONCILING on unknown outcome) | Built, incl. concurrent duplicates |
| Reconciliation consumer (contract v1.0, fail-closed, dedupe; discrepancies pause sensitive actions) | Built |
| Redemptions (confirm only after ledger-confirmed return; rejection flags a refund) and disputes | Built |
Escrowed redemptions through the Soroban escrow (via StellarGateway.escrow): issuer-signed confirm/reject, holder timeout refund, EXPIRED state, reconcileEscrows; native return as fallback (ADR 0002) |
Built; run end to end on Stellar Testnet against the deployed escrow (confirm, reject, holder timeout refund; hashes in Unbound-stellar/docs/DEPLOYMENT.md); FakeEscrow remains the test adapter |
CORS (CORS_ALLOWED_ORIGINS, exact origins, no credentials) |
Built |
| Scheduled maintenance jobs (stuck intents, overdue disputes, escrow reconciliation) with backoff and admin status / run-now | Built; with SqliteJobLock several processes sharing one SQLite file keep each job single-flight (lease table, expiry on crash) |
| Password reset (single-use hashed tokens, 30 min, throttled, ends sessions) | Built; email goes through a Mailer port: dev OutboxMailer (prints) by default, ResendMailer when MAIL_PROVIDER=resend (mocked-fetch tested only; never sent through the live provider) |
| Admin merchant review queue and revoke endpoints | Built |
GET /v1/portfolio classification; OpenAPI 3.1 snapshot; audit trail |
Built |
HttpStellarGateway + cross-repo tests against a real Unbound-stellar gateway on its simulator |
Built (runs when the sibling checkout exists) |
| Docker / docker-compose | Written; only syntax-checked, never run |
Admin TOTP MFA (RFC 6238; enrol / activate / disable, replay-safe, lockout, secrets sealed with MFA_ENCRYPTION_KEY) and POST /v1/admin/targets |
Built and unit tested; not used in a live browser session |
| Live Testnet: escrow redemption flows, DEX exchange SHOP->USDC (quote, intent, signed settlement, confirmed), gateway worker against Horizon | Run 2026-11-04 (see Unbound-stellar/docs/DEPLOYMENT.md) |
| PostgreSQL, queues, rate limiting beyond login lockout, email verification, production deployment, external audit | Not yet |
openapi/openapi.json- HTTP v1 contract (snapshot-tested).fixtures/reconciliation-event.*.v1.json- copies of the producer's (Unbound-stellar) golden fixture and schema; a test fails on drift.fixtures/gateway/*.json- gateway service contract (incl. the escrow endpoints), byte-identical toUnbound-stellar's copy.docs/adr/0002-redemption-escrow-mode.md- escrow mode and the native fallback.- Idempotency:
Idempotency-Keyheader onPOST /v1/exchange/intents,.../submitandPOST /v1/redemptions.
| Repository | Owns | Stack | CI |
|---|---|---|---|
| Unbound-core (this repo) | Backend API: issuers, policies, intents, redemptions, audit | TypeScript, Node 22, SQLite | |
| Unbound-stellar | Stellar SDK, gateway service, chain worker, Soroban redemption escrow | TypeScript, @stellar/stellar-sdk, Rust/Soroban | |
| Unbound-site | Landing page, partner docs and merchant console | Next.js 16, React 19, Tailwind 4 | |
| Unbound-mobile | Flutter consumer wallet | Flutter, Dart, Riverpod |
Contributions are welcome. Start with CONTRIBUTING.md, then pick from the scoped, testable ideas in
docs/CONTRIBUTOR_ISSUES.md. Unbound intends to take part in
Drips Wave; that is subject to Drips' review and
limits, and no rewards are promised. Wave issues are opened only once their dependencies and acceptance tests are ready.
Everyone agrees to the Code of Conduct.
Please report vulnerabilities privately (Security tab, Report a vulnerability) as described in
SECURITY.md. The threat model is in docs/SECURITY.md. Testnet only: never use
real funds, real customer data or Mainnet keys with this code.
docs/PRD.md (product) · docs/SRD.md (requirements, API, states) ·
docs/ARCHITECTURE.md · docs/TECHNICAL_BUILD.md ·
docs/ROADMAP.md · docs/BRAND.md · AGENTS.md (rules for coding agents).
The canonical copy of the shared docs lives in Unbound-core.
MIT. The Unbound logo in brand/ is the project's approved identity and is shared by all four
repositories; it has not been trademark-cleared.