An open-source retail storefront built on the Stellar network
ShoeSafari is a reference-architecture e-commerce storefront that shows how a production-style online store can accept Stellar (Soroban) payments — USDC or native XLM paid directly from a customer's Freighter wallet into a Rust smart contract that escrows the funds until the order ships.
It is intentionally built as a real store, not a demo: Next.js storefront, product catalog, cart, email OTP flow, and a checkout that offers both traditional card payment and on-chain Stellar settlement with an order registry, escrow, and on-chain refunds. Teams and builders can use it as a blueprint for adding Soroban checkout to their own store.
GrantFox submission: this repository is submitted as an open-source reference implementation. Milestones, bounties, and issues are tracked natively on the GrantFox platform — see CONTRIBUTING.md.
The full design rationale — escrow, multi-token support, event indexing, pre-flight fee simulation, and how this acts as a portable public-good blueprint — 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 USDC (testnet)
- Environment Variables Reference
- Security Notes
- 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. - Self-funding testnet flow — brand-new accounts are auto-funded via friendbot, so testing takes under a minute.
- Testnet USDC + native XLM (Stellar Asset Contract) — no card, no bank, no KYC required.
┌──────────────────────────────────────────────────────────────────┐
│ 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
├── 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-Hub/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
# Display-only: the merchant wallet that receives USDC payments.
# The authoritative value lives on-chain, set during contract initialize.
# PUBLIC_MERCHANT_ADDRESS=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.
- 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.
| 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) |
PUBLIC_MERCHANT_ADDRESS |
no | Display-only merchant wallet (on-chain value is authoritative) |
NEXT_PUBLIC_FIREBASE_* |
yes | Firebase config (existing storefront) |
MONGO_DB_URI |
yes | Mongo URI (existing storefront) |
* Required for the Stellar payment stage; empty until you deploy the contract.
- 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. Issues, bounties, and milestones are tracked natively on the GrantFox platform. Please read CONTRIBUTING.md before opening your first pull request.
MIT — see LICENSE.