Skip to content

determined-001/Quay

Stellar Checkout

Non-custodial stablecoin checkout + payment links on Stellar, with a deliberately swappable off-ramp seam so the seller can later cash out to local currency without a rewrite.

The loop, end to end:

  1. A seller creates a payment link in the dashboard (title + amount + asset).
  2. The buyer opens the checkout page, scans a QR (or taps a wallet deep-link), and pays USDC straight to the seller's own Stellar wallet — nothing is custodied in between.
  3. A backend worker watches the ledger, matches the incoming payment to the link by memo, marks it paid, and fires any registered webhooks.
  4. When the seller wants cash, they trigger a seller-initiated cash-out to local currency through the off-ramp adapter.

This is the non-custodial version of a hosted checkout (think Stripe-style PaymentIntent), built on the chain whose anchor network can actually settle to local rails.


Why it's shaped this way

The link + checkout + on-chain payment is the easy, commodity part. The off-ramp is the hard 80% and the whole moat — and it isn't a step you bolt on, it's a corridor walking back in: FX rate risk in flight, KYC on the payout, reconciliation that proves local currency landed, recovery when the anchor is down.

So two deliberate boundaries are baked into the architecture:

  • Off-ramp runs seller_initiated, not inline. The seller receives the stablecoin to a wallet they control and cashes out as a separate, authorized action. Custody stays at the edges. inline mode (value routed through the anchor mid-flight, seller receives local currency directly) is what merchants ultimately want — and it is the mode that puts you in the money-transmission / custody box. The OffRampPort already models both modes; do not flip to inline until a licensed anchor relationship and a compliance story are real.

  • Ports-and-adapters everywhere. The domain never imports a chain SDK. RailPort, WatcherPort, and OffRampPort are the seams. Today: a Stellar (SEP-7 + Horizon) rail and a mock anchor. Tomorrow: the same PaymentIntent spine behind an adapter-gateway (Arc/Circle) or a different chain — without touching the domain or the worker.


Monorepo layout

packages/
  core/        Domain brain — entities, status machine, money math, SEP-7 builder,
               the pure payment matcher, port interfaces, zod schemas.  (29 unit tests)
  stellar/     Stellar adapter — SEP-7 rail + Horizon polling watcher (RailPort/WatcherPort).
  offramp/     Off-ramp adapter — MockAnchorOffRamp (OffRampPort, seller_initiated).  *** mock ***
apps/
  api/         Hono API + Drizzle (libSQL) + the ledger-watching worker.
  web/         Next.js (App Router) seller dashboard + buyer checkout page.

core is the only package with business logic worth unit-testing in isolation, and it is: money is compared in integer stroops (never floats), the status machine rejects illegal transitions, the SEP-7 builder is spec-checked, and the matcher is exhaustively tested for paid / overpaid / underpaid / wrong-asset / no-memo / unknown-reference.


Run it locally

Requirements: Node 20+ and pnpm 9.

pnpm install
cp .env.example .env

Two processes (two terminals):

# 1) API + ledger watcher  →  http://localhost:8787
pnpm --filter @checkout/api dev

# 2) Web dashboard + checkout  →  http://localhost:3000
pnpm --filter @checkout/web dev

On first boot with no DEFAULT_SELLER_WALLET set, the API generates a throwaway testnet keypair, prints it, and gives you a Friendbot link to fund it. Set DEFAULT_SELLER_WALLET in .env to a wallet you control to reuse a stable address across restarts.

Then: open the dashboard, create a link, open its checkout page, and pay the displayed amount of USDC with the shown memo from any Stellar testnet wallet. Within a poll interval the dashboard flips the link to paid; hit Cash out to NGN to exercise the off-ramp seam.

Useful scripts (from the repo root):

pnpm typecheck   # all packages
pnpm test        # core unit tests
pnpm build       # builds the web app

What's real vs. stubbed

Piece Status
SEP-7 payment-request URIs Real, spec-correct (native vs issued asset, memo ≤28 bytes, %20 encoding, network passphrase).
Horizon payment watching + memo matching Real logic against the Stellar SDK v16 API. Polling (restart-safe), idempotent via persisted cursor + processed-tx ledger.
Status lifecycle, webhooks (HMAC-SHA256 signed) Real.
Persistence Real, libSQL/SQLite for zero-config local dev (swap the DATABASE_URL for Turso/Postgres). Tables self-initialize on boot.
Off-ramp (@checkout/offramp) MOCK. MockAnchorOffRamp simulates an FX quote and a payout with a fake rate. It exists so the seam is exercisable end-to-end; it moves no money.
Auth Not implemented. Single hard-coded demo seller, no API keys / login. Fine for a demo, not for production.

Before you go live (the parts code can't do)

  1. Verify the USDC issuer. .env.example ships placeholder Circle issuers for testnet and public. Confirm the current issuer for your network before relying on it — a wrong issuer silently matches nothing (or the wrong asset).
  2. Get a real anchor relationship first. A checkout that dead-ends in USDC isn't the product. Replace MockAnchorOffRamp with an adapter implementing the same OffRampPort against a licensed Nigerian anchor's SEP endpoints (quote → SEP-38, initiate → SEP-24/31, status → poll to settlement). Validate the anchor will actually onboard you and pay out before building further.
  3. Don't enable inline off-ramp without legal review. See the boundary note above.
  4. Add auth (API keys per seller + a real login) before anyone but you touches it.
  5. Multiple sellers / scale: the watcher polls per active destination account; for many sellers you may want a streaming WatcherPort implementation (the interface already allows it).

This README is engineering guidance, not legal advice. Money transmission is the box you do not want to back into by accident.


Docs & contributing

Licensed under the Apache License 2.0.

About

No description, website, or topics provided.

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors