KaspaFlow is a non-custodial Kaspa-only payment helper for small merchants.
The first product target is a simple cafe, chicken shop, or local retail counter flow:
- Enter a KRW order amount.
- Convert it to KAS for a short payment window.
- Show a QR code and direct Kaspa address.
- Watch the Kaspa chain for the matching payment.
- Keep a merchant-side sales log.
The app is intentionally designed so merchants receive KAS directly into their own wallet. KaspaFlow should not custody funds, convert funds, or settle KRW without a separate legal review.
- Merchant name and Kaspa receiving address form
- Server-side KAS/fiat quote endpoint for KRW, USD, EUR, and JPY
- Payment request API with expiry and calculated KAS amount
- QR generation in the browser
- Payment status polling through a watcher abstraction
- Real Kaspa REST watcher mode with underpaid and overpaid detection
- Refund request tracking with refund transaction hash verification
- Merchant admin settings and employee list management
- Daily and weekly sales dashboard summaries
- Bulk watcher sync for all open payments
- Backend automatic sync loop for open payments
- Automatic expiry cleanup for stale open payments
- Separate payment detail pages at
/payments/:id - File-backed audit log for operational events
- Optional webhook notifications for payment, sync, and refund events
- Mainnet/testnet-10 Kaspa REST API selection
- Docker Compose deployment scaffold
- Installable PWA shell for merchant devices
- PNG, SVG, Apple touch, and maskable PWA icons
- Offline fallback page for installed devices
- Multi-store merchant settings with active store switching
- Server-sent realtime payment, dashboard, and audit updates
- Error monitoring endpoint backed by the audit log
- Testnet dry-run checklist and validation API
- Non-custodial testnet send handoff URI generation
- Store-scoped wallet address book without private keys
- File-backed local sales log
- CSV export at
/api/sales.csv - Korean and global plan infographic assets under
public/infographics
The default watcher is a mock implementation for product and UI development.
It marks a payment as seen after 10 seconds and confirmed after 30 seconds.
Set KASPA_WATCHER_MODE=kaspa-rest to use the REST watcher path against
KASPA_REST_API_URL.
npm install
npm run devOpen http://localhost:3000.
If port 3000 is busy, Next.js will print the fallback port.
Use .env.example for local development and .env.production.example as the
starting point for production deployments.
QUOTE_FIAT=KRW
KAS_PRICE_SOURCE=auto
KAS_KRW_RATE=350
KAS_USD_RATE=0.25
KAS_EUR_RATE=0.23
KAS_JPY_RATE=39
KASPAFLOW_DATA_DIR=./data
KASPAFLOW_STORAGE_PROVIDER=file
DATABASE_URL=
POSTGRES_PASSWORD=
KASPA_WATCHER_MODE=mock
KASPAFLOW_BACKGROUND_SYNC_ENABLED=true
KASPAFLOW_BACKGROUND_SYNC_INTERVAL_MS=15000
KASPA_NETWORK=mainnet
KASPA_REST_API_URL=https://api.kaspa.org
KASPAFLOW_ENABLE_SIMULATION=false
KASPAFLOW_ADMIN_TOKEN=
KASPAFLOW_NOTIFY_WEBHOOK_URL=
KASPAFLOW_NOTIFY_WEBHOOK_FORMAT=json
KASPAFLOW_NOTIFY_TELEGRAM_CHAT_ID=KAS_PRICE_SOURCE accepts:
auto: use Coinone for KAS/KRW and CoinGecko for other fiat quotes.coinone: fetch KAS/KRW from Coinone and fall back to CoinGecko if needed.coingecko: fetch KAS prices from CoinGecko and fall back to mock if the request fails.mock: use configured env rates or built-in development defaults.
When KAS_PRICE_SOURCE is not set, the quote endpoint uses auto live quotes.
Mock defaults are only used when live requests fail or when
KAS_PRICE_SOURCE=mock is explicitly set.
KASPAFLOW_DATA_DIR controls where payments.json is written. The default is
./data, which is ignored by git.
Run npm run backup:data to create a timestamped .tgz archive of that
directory. Set KASPAFLOW_BACKUP_DIR to choose the destination; the default is
./backups. Restore with npm run restore:data -- <backup.tgz>. The restore
command refuses to overwrite a non-empty data directory unless --force is
provided; with --force, it first moves the current directory aside as a
.pre-restore-* copy.
Run npm run reconcile:sales -- YYYY-MM-DD to generate a local JSON
reconciliation summary from file-backed payments.
Run npm run preflight:prod before production deploys to check admin auth,
mainnet watcher, simulation, storage, backup, and notification env.
Run npm run release:check before a tagged deploy to execute the full local
release gate: build, typecheck, tests, production preflight, Docker Compose
config, and production dependency audit.
KASPAFLOW_STORAGE_PROVIDER=file uses the local JSON pilot store.
KASPAFLOW_STORAGE_PROVIDER=postgres enables the PostgreSQL runtime adapter and
requires DATABASE_URL. Use npm run db:schema to apply db/schema.sql before
starting the app in Postgres mode. Use docs/database-migration.md for the beta
storage cutover plan.
KASPAFLOW_ADMIN_TOKEN is optional for local development and required at
production runtime. If production starts without it, merchant/admin APIs return
503 instead of running unauthenticated. API requests that create payments,
update admin settings, sync/expire payments, or record refunds must include the
token as x-kaspaflow-admin-token or Authorization: Bearer <token>.
Merchant-only GET endpoints for payment lists, analytics, audit logs, error
logs, and admin settings require the same token.
The browser UI stores the token in local
storage and a SameSite cookie from the merchant admin panel. The realtime
/api/events stream uses that cookie because browser EventSource cannot attach
custom headers.
KASPA_WATCHER_MODE accepts:
mock: deterministic local status simulation for UI development.kaspa-rest: polls/addresses/{address}/full-transactionsfrom the configured Kaspa REST API and matches payments inside their quote window.
KASPAFLOW_BACKGROUND_SYNC_ENABLED controls the server-side automatic sync
loop. It defaults to enabled. KASPAFLOW_BACKGROUND_SYNC_INTERVAL_MS controls
the interval and is clamped to a minimum of 5000 ms.
KASPA_NETWORK accepts:
mainnet: defaults tohttps://api.kaspa.org.testnet-10: defaults tohttps://api-tn10.kaspa.org.
KASPA_REST_API_URL overrides the network default when set.
KASPAFLOW_ENABLE_SIMULATION=true enables the development-only simulation
endpoint and UI controls for marking payments as seen or confirmed. Leave it
off for real merchant pilots. Production runtime always blocks the simulation
endpoint even if the variable is accidentally set to true.
KASPAFLOW_NOTIFY_WEBHOOK_URL is optional. When configured, KaspaFlow sends
webhook events for payment creation, bulk sync, daily reports, and refund
activity. KASPAFLOW_NOTIFY_WEBHOOK_FORMAT accepts json, discord, or
telegram. Telegram delivery also requires KASPAFLOW_NOTIFY_TELEGRAM_CHAT_ID.
docker compose up --buildOr use the package scripts:
npm run docker:config
npm run release:check
npm run docker:up
npm run docker:down
npm run db:schema
npm run db:export -- db/kaspaflow-import.sql
npm run smoke:postgres
npm run smoke:prodThe compose file mounts /app/data as a persistent volume. Set
KASPAFLOW_ADMIN_TOKEN, KASPA_NETWORK, and webhook values before exposing the
container outside a local pilot network.
For a local Postgres rehearsal:
KASPAFLOW_ADMIN_TOKEN=dummy POSTGRES_PASSWORD=kaspaflow-dev \
docker compose --profile postgres up -d postgres
DATABASE_URL=postgres://kaspaflow:kaspaflow-dev@localhost:5432/kaspaflow \
npm run db:schema
KASPAFLOW_STORAGE_PROVIDER=postgres \
DATABASE_URL=postgres://kaspaflow:kaspaflow-dev@localhost:5432/kaspaflow \
KASPAFLOW_ADMIN_TOKEN=dummy \
KASPAFLOW_ENABLE_SIMULATION=true \
npm run dev -- --port 3003
KASPAFLOW_BASE_URL=http://localhost:3003 \
KASPAFLOW_ADMIN_TOKEN=dummy \
npm run smoke:postgresAfter a production deploy, run a non-mutating smoke check against the deployed URL:
KASPAFLOW_BASE_URL=https://your-kaspaflow-host.example \
KASPAFLOW_ADMIN_TOKEN=<strong-random-token> \
npm run smoke:prodGET /api/quote?fiat=USDreturns the current KAS/fiat quote.GET /api/healthreturns service, watcher, and quote health metadata.GET /api/kaspa/probechecks the configured Kaspa REST API health and blockDAG endpoint.GET /api/testnetreturns the testnet dry-run checklist.GET /api/testnet?mode=balance&address=...checks a testnet address balance.GET /api/testnet?mode=tx&txId=...fetches a transaction detail.POST /api/testnet/sendcreates a non-custodial wallet handoff URI.GET /api/walletslists saved wallet addresses for authenticated merchants.POST /api/walletscreates, updates, or disables wallet address entries.GET /api/paymentslists payment requests for authenticated merchants.POST /api/paymentscreates a payment request.GET /api/payments/:idsyncs the request with the watcher and returns status.POST /api/payments/syncsyncs every open payment with the active watcher.POST /api/payments/:id/simulateupdates a payment in development simulation mode.POST /api/payments/expireexpires stale waiting, seen, and underpaid payments.POST /api/payments/:id/refundrecords a refund request and optionally verifies a provided refund transaction hash.GET /api/payments/:id/refundre-checks the stored refund transaction hash.GET /api/adminreturns merchant settings and employees.POST /api/adminupdates merchant settings or employees.GET /api/analyticsreturns total, daily, weekly, and status summaries.GET /api/auditreturns recent payment, refund, admin, and sync events.GET /api/eventsstreams realtime payment, dashboard, and audit updates.GET /api/errorsreturns recent failed/rejected operational events.GET /api/reports/dailyreturns the current daily sales report, including open payments, attention payments, and refund summaries.POST /api/reports/dailysends the daily sales report to the configured webhook.GET /api/sales.csvdownloads the current sales log for authenticated merchants.GET /payments/:idopens the merchant-facing payment detail page.
Example payment creation:
curl -X POST http://localhost:3000/api/payments \
-H 'Content-Type: application/json' \
-d '{
"merchantName": "Pilot Cafe",
"merchantAddress": "kaspa:q000000000000000000000000000000000000000000000000000000000000",
"fiatAmount": 21000,
"fiatCurrency": "KRW"
}'Before using this with a real merchant:
- Replace the placeholder Kaspa address.
- For testnet pilots, follow
docs/testnet-dry-run.md. - For mainnet pilots, follow
docs/operations.md. - For Korean operator handoff, follow
docs/operations-ko.md. - For Korean staff payment handling, follow
docs/staff-playbook-ko.md. - For Korean accounting and retention operations, follow
docs/accounting-retention-ko.md. - For the beta storage upgrade, follow
docs/database-migration.mdanddb/schema.sql. - For beta launch gates, follow
docs/beta-readiness.md. - Use
KAS_PRICE_SOURCE=auto,coinone, orcoingeckofor live quotes. - Test
kaspa-restwatcher mode with small real payments. - Refunds are non-custodial: the merchant sends funds from their own wallet, then KaspaFlow verifies the refund transaction hash.
- Move from file-backed storage to Postgres before multi-store beta.
- Review legal, tax, refund, custody, and payment-processing implications.
- Kaspa REST API: https://api.kaspa.org/
- Kaspa testnet-10 REST API: https://api-tn10.kaspa.org/docs
- CoinGecko simple price API: https://docs.coingecko.com/reference/simple-price