Online fitness coaching subscriptions paid over the Stellar blockchain (testnet MVP). A full-stack demo for a real coaching brand: the client picks a plan, pays in XLM, and access is granted only after the payment is cryptographically verified on-chain — the server trusts the ledger, never the client. Brand, palette, and plan structure reuse the pre-built Maco Gym Coach landing (a separate project, not included in this repo).
- Three subscription tiers priced in XLM (Base 25 / Coaching 1:1 50 / Élite 100 per 30 days)
- Memo-matched on-chain verification against Horizon: tx successful + exact memo + destination + amount ≥ price
- Freighter wallet checkout: server builds an unsigned payment XDR, the user signs in the wallet
- One-click demo payment and live client onboarding (friendbot-funded account created and charged on-chain)
- Double-charge and anti-replay protection: each checkout binds to a unique
FIT-XXXXXXXXmemo, and every redeemed transaction hash is recorded single-use — a settled payment stays valid on-chain forever, so the memo alone would not stop it being re-submitted - Deployable to Vercel as a serverless function (
vercel.json+api/index.js)
Tech stack: Node.js · Express · @stellar/stellar-sdk · Horizon (testnet) · vanilla HTML/JS frontend · Freighter wallet API · Vercel.
┌───────────────────────────── BUILD TIME ─────────────────────────────┐
│ AI Labs / Claude Code ──► Stellar Raven MCP (raven.stellar.org/mcp) │
│ The AI queries Raven's Search (283-entry catalog: docs, SDKs, │
│ Horizon/RPC refs) + sandboxed Execute to generate/verify this code. │
│ Raven is a dev-time copilot layer — NOT a runtime dependency. │
└────────────────────────────────────────────────────────────────────────┘
┌────────────────────────────── RUNTIME ───────────────────────────────┐
│ Browser (public/index.html) │
│ ├─ tier selection → POST /api/checkout (order + unique memo) │
│ ├─ Freighter wallet: /api/build-tx → sign in wallet → /api/submit │
│ └─ Demo payer: /api/pay-demo (server-signed, same rails) │
│ Express API (server.js) │
│ └─ verifyAndActivate(): loads tx from Horizon, checks │
│ tx.successful + memo match + native payment op with │
│ to == merchant && amount >= tier price → grants subscription │
│ Stellar testnet (horizon-testnet.stellar.org) │
│ └─ source of truth: access is only granted from confirmed ledger │
│ state, never from client claims │
└────────────────────────────────────────────────────────────────────────┘
Why memo-based verification: each checkout gets a unique FIT-XXXXXXXX text memo.
The payment is valid only if the on-chain transaction carries that exact memo, pays the
merchant account, and covers the tier price. This is the standard Stellar
deposit-matching pattern (same one exchanges use) — no smart contract needed, which is
what makes it shippable in 30 minutes while remaining cryptographically verifiable.
| Min | Action |
|---|---|
| 0–3 | npm install |
| 3–5 | npm run setup — generates + friendbot-funds merchant & demo payer accounts, writes .env |
| 5–6 | npm start → open http://localhost:3000 |
| 6–9 | Smoke test: pick Coaching 1:1 → ⚡ Pago demo 1-clic → verify ACCESS card + tx on Stellar Expert |
| 9–14 | Freighter test (optional): install extension, switch to Testnet, fund via friendbot, pay a tier |
| 14–20 | Pitch polish: open the Stellar Expert tx tab, rehearse the flow, screenshot the access card |
| 20–25 | Buffer for wifi/testnet hiccups (re-run npm run setup if friendbot rate-limits) |
| 25–30 | Live demo: tiers → checkout → on-chain payment → access confirmation |
npm install
npm run setup # one-time: creates funded testnet accounts → .env
npm start # http://localhost:3000All configuration lives in .env (gitignored — start by copying .env.example).
Everything is Stellar TESTNET only: the accounts hold test XLM with no real-world
value, and no real funds are ever involved.
| Variable | What it is | Where to get it |
|---|---|---|
MERCHANT_PUBLIC |
Public key (G…) of the coach/merchant account that receives every subscription payment. Required — the server exits without it. |
Generated by npm run setup, or create a keypair manually (see below) |
MERCHANT_SECRET |
Secret key (S…) of the merchant account. Written by setup.js for your reference; the server never reads it (it only receives payments). |
Same keypair as above |
DEMO_PAYER_SECRET |
Secret key (S…) of a pre-funded testnet account that powers the one-click ⚡ Pago demo button. Optional — leave empty and the demo button is hidden. |
Second generated keypair |
PORT |
Local HTTP port (default 3000). |
— |
Easiest: run npm run setup. It generates both keypairs, funds each with 10,000 test
XLM via friendbot, and writes .env for you.
Manual alternative:
- Generate two keypairs (e.g. in the Stellar Lab account
creator, or
Keypair.random()from@stellar/stellar-sdk). - Fund each one on testnet: open
https://friendbot.stellar.org?addr=<PUBLIC_KEY>. - Copy
.env.exampleto.env; put the first account's public key inMERCHANT_PUBLIC(and its secret inMERCHANT_SECRET), and the second account's secret inDEMO_PAYER_SECRET.
For Vercel, set the same variables in the project's Environment Variables settings
(at minimum MERCHANT_PUBLIC; add DEMO_PAYER_SECRET if you want the demo button).
| Endpoint | What it does |
|---|---|
GET /api/config |
network, merchant address, demo availability |
GET /api/tiers |
subscription tiers (Base 25 / Coaching 50 / Élite 100 XLM) |
POST /api/checkout {tier} |
creates pending order with unique memo |
POST /api/build-tx {orderId, payerPublic} |
unsigned payment XDR for wallet signing |
POST /api/submit {orderId, signedXdr} |
submits signed tx, verifies, activates |
POST /api/pay-demo {orderId} |
server-side demo payment (real testnet tx) |
POST /api/verify {orderId, txHash} |
verifies an externally-paid tx |
GET /api/subscription/:account |
access check for a Stellar account |
These let a visitor try the full flow without installing a wallet. They generate and accept Stellar secret keys over HTTP, which is acceptable only because every account here is a friendbot-funded testnet account holding no real value. Remove these routes before pointing this code at mainnet.
| Endpoint | What it does |
|---|---|
POST /api/create-account |
generates a fresh testnet keypair, funds it via friendbot, returns it to the browser for instant onboarding |
POST /api/pay-client |
signs a payment server-side using a secret supplied in the request body |
GET /api/balances |
reads XLM balances for the demo accounts |
- Testnet only. Secrets live in
.env(gitignored). For mainnet: remove the demo payer, keep onlyMERCHANT_PUBLICon the server, switch Horizon URL + passphrase. db.jsonis MVP-grade persistence (auto-created on first run, gitignored); swap for a real DB after the hackathon..mcp.jsonpoints at the official Stellar Raven MCP (https://raven.stellar.org/mcp) — the dev-time copilot used to build this project with Claude Code. Cloning the repo shares that config with any teammate; it is a build-time tool only, never a runtime dependency.PITCH.mdis the demo runbook, andpublic/slides.htmlthe build-process slide deck.
A compact (~3k line) but complete blockchain payments slice: frontend -> API -> ledger verification -> access grant.
| Languages | JavaScript (Node), vanilla HTML/CSS/JS frontend |
| Backend | Express REST API, deployable as a Vercel serverless function |
| Blockchain | @stellar/stellar-sdk against Horizon (testnet) - builds unsigned payment XDRs, submits signed transactions, and reads the ledger as the source of truth |
| The core idea | The server never trusts the client. Access is granted only after Horizon confirms a transaction that is successful, carries the order's unique memo, pays the merchant account, and covers the tier price. |
| Payment integrity | Unique per-order memos, a single-use redemption ledger for transaction hashes (a settled payment stays valid on-chain forever, so the memo alone cannot prevent replay), and server-recomputed prices so a client can never lower the amount. |
| Wallet integration | Freighter browser wallet - sign-in-wallet flow, plus a server-signed demo path for visitors without an extension |
Skills demonstrated: blockchain/Web3 integration, cryptographic payment verification, REST API design, serverless deployment, wallet UX, and reasoning about replay and double-spend.
- Stellar TESTNET only — this is a demo, not a payment product. Every account here is funded by friendbot with test XLM that has no monetary value. Do not point it at mainnet as-is.
- The demo endpoints hand out secret keys on purpose.
POST /api/create-accountreturns a freshly generated secret andPOST /api/pay-clientaccepts one in the request body, so a visitor can try the flow without a wallet. That is only acceptable because the funds are worthless. Delete those routes before any mainnet adaptation (see the API section). - Not audited for production payments. Order state is a JSON file, there is no account system, and nothing here has had a financial-grade security review. Treat it as a reference implementation of on-chain payment verification, not as a payment gateway.
- Not financial advice, and not affiliated with the Stellar Development Foundation, Freighter, or any wallet or exchange.
- Brand. The storefront uses a real coaching brand as demo content, published with the owner's permission. If you fork this, replace that content with your own.
- No warranty. Provided "as is", without warranty of any kind (see LICENSE).
MIT — see LICENSE. Copyright (c) 2026 Diego Ostertag.