A real, operating footwear store — not a demo — with Stellar USDC checkout
ShoeSafari is a live online footwear store that sells products and accepts USDC payments on the Stellar network. The store has processed more than $10,000 USDC in product sales on Stellar. Customers can browse footwear, place orders, and pay from their own Freighter wallet.
This is not a demo or mock storefront. ShoeSafari sells real products to customers and processes real USDC product payments on Stellar.
ShoeSafari's storefront and Stellar integration are open source. The live store has processed more than $10,000 USDC in product sales on Stellar. Developers can study and improve the commerce implementation. Testnet instructions in this repository are for development only; never use production funds while testing.
The full design rationale — escrow, multi-token support, event indexing, and pre-flight fee simulation — lives in docs/ARCHITECTURE.md.
In short, payments are non-custodial and refundable:
buyer ──pay──▶ contract (escrow) ──dispatch──▶ merchant
│
└──refund──▶ buyer (on-chain)
- Order registry on-chain — every order records
{ buyer, amount, token, timestamp, status }and transitionsPending → Paid → Shipped/Refunded. - Multi-token — any SEP-41 token the merchant whitelists (
add_token): USDC and native XLM out of the box. - Escrow — funds stay in the contract until the merchant dispatches, or go straight back to the buyer on refund.
- Real-time indexing — the storefront polls
getEvents(cursor-paginated) and decodespay/dispatch/refundevents live (lib/stellar/indexer.ts). - Pre-flight simulation — every payment is simulated first so resource
fees are reported and failures are caught before the buyer signs
(
lib/stellar/simulate.ts). - Readiness checks — account funding, trustline, and token balance are all
verified before building the transaction (
lib/stellar/account.ts).
- Highlights
- Deep Stellar Integration
- Architecture
- Repository Layout
- Tech Stack
- Prerequisites
- Getting Started
- Smart Contract Deployment (Testnet)
- Paying with Stellar (testnet)
- Next.js Stellar integration tutorial
- Testnet wallet walkthrough
- Stellar and Soroban glossary
- Testnet checkout validation
- Environment Variables Reference
- Stellar network configuration matrix
- Security Notes
- Stellar integration API reference
- Firestore deployment notes
- Firebase App Check rollout
- Merchant dispatch and refund runbook
- Admin product catalog guide
- Drips Wave issue backlog
- Contributing
- License
- Next.js 14 App Router + Tailwind CSS storefront (catalog, cart, checkout, admin, blog).
- Soroban checkout with escrow — a Rust smart contract moves USDC (or native XLM) from the buyer's wallet into an on-chain escrow, records the order, and releases it to the merchant on dispatch or refunds it on-chain.
- Freighter wallet integration with network guard, pre-flight simulation,
readiness checks, real-time
getEventsindexing, and an explorer link on success. - Testnet development flow — brand-new accounts can be funded via friendbot, so testing takes under a minute.
- Testnet USDC + native XLM (Stellar Asset Contract) are available for development and contract testing. Testnet is separate from live purchases.
┌──────────────────────────────────────────────────────────────────┐
│ Storefront (Next.js / React / Tailwind) │
│ │
│ app/checkout/page.tsx │
│ ├─ components/StellarCheckoutButton.jsx "Pay with USDC" │
│ ├─ components/StellarOrderWatch.jsx live event monitor │
│ └─ lib/stellar/ │
│ ├─ checkout.ts payment flow │
│ ├─ account.ts trustline / balance / friendbot │
│ ├─ simulate.ts pre-flight fee simulation │
│ ├─ indexer.ts getEvents cursor listener │
│ ├─ freighter.ts wallet connect/sign │
│ ├─ scval.ts ScVal builders │
│ └─ events.ts event decoding │
└──────────────────────────┬────────────────────────────────────────┘
│ invokeHostFunction(pay) via RPC
▼
┌──────────────────────────────────────────────────────────────────┐
│ contracts/checkout (Rust · Soroban SDK 27) │
│ │
│ order registry: order_id -> Order {buyer, amount, token, status} │
│ pay(token, buyer, order_id, amount) buyer → escrow (contract) │
│ dispatch(order_id) escrow → merchant │
│ refund(order_id) escrow → buyer │
│ add_token / remove_token SEP-41 whitelist │
└──────────────────────────┬────────────────────────────────────────┘
│ SEP-41 transfer + contract events
▼
┌──────────────────────────────────────────────────────────────────┐
│ Stellar network (testnet / mainnet) │
│ Ledger stores PaymentReceived / OrderShipped / OrderRefunded │
│ Stellar Asset Contracts: USDC + native XLM │
└──────────────────────────────────────────────────────────────────┘
| Param | Type | Meaning |
|---|---|---|
token |
Address |
Whitelisted SEP-41 token (USDC SAC or native XLM SAC) |
buyer |
Address |
The paying wallet (must authorize) |
order_id |
BytesN<32> |
32-byte unique order identifier |
amount |
i128 |
Raw token units (USDC = 7 decimals) |
pay escrows amount from buyer into the contract, marks the order Paid,
and emits a PaymentReceived event:
topics: ["pay", token, buyer, merchant, order_id]
data: { amount }
The merchant then calls dispatch(order_id) to release the escrow (emits
dispatch) or refund(order_id) to return it to the buyer (emits refund).
The storefront decodes these events live and from confirmed transactions to
display receipts and finish orders.
Full design decisions, the escrow model, and how the pieces fit together: docs/ARCHITECTURE.md.
A clean split between frontend, contracts, and config:
shoesafari/
├── app/ # Frontend — Next.js App Router
│ ├── (landingpage)/ # landing sections (Hero, Stellar, AboutUs…)
│ ├── checkout/page.tsx # checkout with the Stellar/USDC stage
│ ├── shop/ # product catalog + product details
│ ├── collections/ # collections listing
│ ├── admin/ # admin product management
│ └── profile/login/ # authentication
├── components/ # Shared UI (Stellar checkout/wallet buttons, Toast…)
├── lib/ # Client-side libraries
│ ├── stellar/ # Soroban payment library
│ │ ├── config.ts # network / contract / token config
│ │ ├── freighter.ts # wallet connect / signing
│ │ ├── scval.ts # ScVal builders + decoders
│ │ ├── checkout.ts # payWithStellar() payment flow
│ │ ├── account.ts # trustline / balance / friendbot
│ │ ├── simulate.ts # pre-flight resource-fee simulation
│ │ ├── indexer.ts # getEvents cursor listener
│ │ └── events.ts # contract event decoding
│ └── AuthContext.jsx # auth + cart context
├── contracts/
│ └── checkout/ # Contracts — Rust Soroban smart contract
│ ├── src/
│ │ ├── lib.rs # entry points (initialize, pay, dispatch, refund…)
│ │ ├── order.rs # Order struct + status lifecycle
│ │ ├── storage.rs # persistent storage + TTL management
│ │ ├── events.rs # PaymentReceived / OrderShipped / OrderRefunded…
│ │ ├── errors.rs # typed error codes
│ │ └── test.rs # mock-token + native-asset integration tests
│ ├── Cargo.toml
│ └── README.md # contract interface + manual CLI examples
├── docs/
│ ├── ARCHITECTURE.md # Deep Stellar integration design rationale
│ └── STELLAR_API.md # Exported helper API reference
├── scripts/
│ └── deploy-testnet.sh # one-command build + deploy + initialize
├── public/ # Static assets (product images)
├── .env.local.example # Config — environment variable template
├── package.json # Frontend dependencies + scripts
└── LICENSE
| Layer | Technology |
|---|---|
| Frontend | Next.js 14 (App Router), React, Tailwind CSS, Firebase |
| Payments | @stellar/stellar-sdk 16, @stellar/freighter-api 6 |
| Smart chain | Rust, Soroban SDK 27, Stellar CLI |
| Currency | USDC + native XLM via the Stellar Asset Contract (7 decimals) |
Install the following before getting started:
| Tool | Version / Notes | Install |
|---|---|---|
| Node.js | 18.18+ (bundles npm) |
https://nodejs.org |
| Rust | stable, with the wasm32v1-none target |
`curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs |
| Stellar CLI | latest (stellar --version) |
brew install stellar-cli or via cargo/docs |
| Freighter | browser wallet extension (Chrome / Firefox) | https://freighter.app |
Rust note: a C toolchain/LLVM is required to compile the Soroban contract. On macOS install Xcode Command Line Tools (
xcode-select --install).
Verify your setup:
node --version # v18.18+ or newer
cargo --version # 1.7x+
stellar --version # latestgit clone https://github.com/ShoeSafari-Org/ShoeSafari.git
cd ShoeSafarinpm installCopy the template and fill in your values:
cp .env.local.example .env.localA minimal Stellar-only configuration (the rest of the app runs with the existing Firebase/Mongo values you already use):
# --- Stellar / Soroban payments ---
NEXT_PUBLIC_STELLAR_NETWORK=testnet
# NEXT_PUBLIC_STELLAR_RPC_URL=https://soroban-testnet.stellar.org
# NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015
# Deployed checkout contract id (see "Smart Contract Deployment" below)
NEXT_PUBLIC_CHECKOUT_CONTRACT_ID=
# USDC token contract (testnet default; set for mainnet)
# NEXT_PUBLIC_USDC_CONTRACT_ID=CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA
npm run devOpen http://localhost:3000 — you should land on the storefront home page. To see the Stellar checkout, add an item to your cart and visit the checkout page.
Until you deploy the contract (next section),
NEXT_PUBLIC_CHECKOUT_CONTRACT_IDis empty and the Stellar payment stage stays disabled — that's expected.
The checkout contract lives in contracts/checkout/. Deploying it is a
four-part pipeline: build → test → deploy → initialize.
cd contracts/checkout
# Install the Soroban wasm target (one time)
rustup target add wasm32v1-none
# Build the optimized release wasm
stellar contract buildstellar contract build compiles with Cargo and optimizes the wasm by
default. The artifact is written to
contracts/checkout/target/wasm32v1-none/release/shoesafari_checkout.wasm.
Alternatively, the manual Cargo route (optimize with a newer CLI's
stellar contract build --optimize, or the legacy
stellar contract optimize --wasm <file> --out <file>):
cd contracts/checkout
cargo build --target wasm32v1-none --releaseThe test suite uses a self-contained mock token plus the real Stellar Asset Contract for native XLM, so no deployment or network access is needed:
cd contracts/checkout
cargo testExpected output includes the full order lifecycle — escrow + dispatch, refund, native-XLM payments, token whitelist, duplicate-order, not-initialized, zero-amount, double-init, and merchant-change cases.
Generate a funded keypair that the Stellar CLI will use as the deployer:
# Back at the repo root
stellar keys generate alice --network testnet --fund
aliceis the default identity used byscripts/deploy-testnet.shand by the examples below. Swap the name/flag to match your own keyring.
stellar contract deploy \
--wasm contracts/checkout/target/wasm32v1-none/release/shoesafari_checkout.wasm \
--source-account alice \
--network testnetThe command prints a contract id:
Contract: CBL2LFZKZJ4DHANUKYQXHFTTBFEUQ3QYIG4M5CLVND6FRRQTJTY4Q7WG
Save it — you'll need it for the next two steps.
The contract requires a one-time initialize call that sets the merchant
wallet receiving every future payment. The merchant address must authorize
the transaction, so this must be the merchant's own account:
stellar contract invoke \
--id <CONTRACT_ID> \
--source-account alice \
--network testnet \
-- \
initialize \
--merchant G...YOUR_MERCHANT_ADDRESS...Verify it took effect:
stellar contract invoke --id <CONTRACT_ID> --network testnet -- merchantExpected output is the merchant address you just set. An order cannot be paid before this step succeeds.
Warning: the merchant address you pass here authorizes every release from escrow. Triple-check it before submitting on mainnet. This store ships un-initialized on purpose so each team authorizes its own merchant.
Funds can only be escrowed with tokens the merchant has approved. Add USDC and native XLM (testnet values):
stellar contract invoke --id <CONTRACT_ID> --source-account alice --network testnet -- \
add_token --token CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA
stellar contract invoke --id <CONTRACT_ID> --source-account alice --network testnet -- \
add_token --token CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSCVerify with stellar contract invoke --id <CONTRACT_ID> --network testnet -- is_token_allowed --token <TOKEN>.
Add the contract id to .env.local:
NEXT_PUBLIC_CHECKOUT_CONTRACT_ID=<CONTRACT_ID>Restart the dev server and the "Pay with USDC" stage on the checkout page activates.
scripts/deploy-testnet.sh automates Steps 1, 4, and 5 (build → deploy →
initialize). It initializes the contract with the given merchant address, or
with the deploying account's own public key when none is supplied, and
whitelists USDC + native XLM:
./scripts/deploy-testnet.sh # merchant = deployer's public key
./scripts/deploy-testnet.sh G...YOUR_MERCHANT... # explicit merchantIt prints the NEXT_PUBLIC_CHECKOUT_CONTRACT_ID to paste into .env.local.
New to Stellar testnet? Follow the testnet wallet walkthrough to install and configure Freighter, fund a disposable wallet, and complete a test checkout. It explains the difference between test XLM and test USDC and never asks for a wallet secret.
- Install Freighter and set the network to Testnet (Settings → Network → Testnet).
- Open the checkout page, connect Freighter, and tap Pay with USDC.
- The app checks your account (funding it via friendbot if new), verifies your USDC trustline and balance, and simulates the transaction to estimate fees.
- Approve the transaction in Freighter. The contract escrows the exact amount
(7-decimal USDC) and emits a
PaymentReceivedevent; the live order monitor shows it landing on-chain in real time. - Confirm the payment on the explorer link shown after success.
Mainnet: set
NEXT_PUBLIC_STELLAR_NETWORK=mainnet, pointNEXT_PUBLIC_USDC_CONTRACT_IDat the mainnet USDC SAC contract, setNEXT_PUBLIC_NATIVE_ASSET_CONTRACT_ID(mainnet native id differs from testnet), redeploy the contract, and initialize it with your real merchant account.
See the complete environment variables reference for every supported key, its required status, safe defaults, and whether it is public or server-only.
| Variable | Required | Purpose |
|---|---|---|
NEXT_PUBLIC_STELLAR_NETWORK |
no | testnet (default) or mainnet |
NEXT_PUBLIC_STELLAR_RPC_URL |
no | Soroban RPC endpoint (testnet default) |
NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE |
no | Network passphrase (testnet default) |
NEXT_PUBLIC_CHECKOUT_CONTRACT_ID |
yes* | Deployed checkout contract (C…) |
NEXT_PUBLIC_USDC_CONTRACT_ID |
no | USDC token contract (testnet default) |
NEXT_PUBLIC_NATIVE_ASSET_CONTRACT_ID |
no | Native XLM SAC (verified testnet default) |
NEXT_PUBLIC_FIREBASE_* |
yes | Firebase config (existing storefront) |
MONGO_DB_URI |
no | Optional MongoDB alternative; Firestore is used otherwise |
* Required for the Stellar payment stage; empty until you deploy the contract.
npm run build validates required public Firebase, EmailJS, and checkout
configuration before Next.js compiles. Testnet may use the documented RPC,
passphrase, and token defaults. Mainnet builds must explicitly set the RPC URL
and USDC/native token contract IDs. CI uses nonfunctional placeholders to check
the build without production credentials; validation errors name variables but
never print their values. Run npm run validate:production-env to check a
deployment environment directly.
- Merchant authorization is on-chain. The
initializetransaction sets the wallet that owns the contract and can release escrow, and requires that merchant's signature. Verify it before submitting, especially on mainnet. - Escrow, not custody. Funds move
buyer → contract → merchantonly viadispatch, or back to the buyer viarefund. Neither party can withdraw to an arbitrary address, and an order can only be paid once (OrderAlreadyPaid). - Token whitelist. Only tokens the merchant added with
add_tokencan fund orders (TokenNotAllowedotherwise). - Amounts are enforced in the contract.
amount <= 0is rejected and the escrowed amount is exactly what the buyer signed for. - Client totals are convenience only. Verify payments against the emitted
PaymentReceivedevent server-side if you operate a fulfillment backend. - Keys never leave the browser. Freighter holds private keys; the store only ever asks for signatures.
Community contributions are welcome. Please read CONTRIBUTING.md before opening a pull request. As this code supports a live store, keep production credentials private and test payment changes on testnet unless a maintainer explicitly directs otherwise.
MIT — see LICENSE.