The Next.js front end: payee payment requests, the sender's lock flow, the payee's claim, and public receipts. Testnet only, with test assets.
Live app (testnet): kinlock-app.vercel.app · Docs: kinlock-org.github.io
| Route | State |
|---|---|
/ |
Landing, how-it-works, principles |
/request |
Payee builds a payment-request link (amount, reference, schedule) |
/send |
Sender picks a payee, sets a schedule, sees preflight results, funds a lock, gets a claim link |
/claim/[id] |
Payee verifies the reference against the on-chain ref_hash, then releases or declines |
/locks/[id] |
Lock detail with refund, shown only when the contract allows it |
/r/[txHash]/[eventIndex], /verify |
Receipts rebuilt from chain data. "Payment to verified payee" — nothing more |
/payee, /attester |
Stubs (M3-11, M3-12) |
Status: the core money flows are real; the dashboard isn't. Claim, decline, refund, receipts and verify read the chain through the SDK, never the indexer.
/sendisM3-06in progress, the Playwright happy path (M3-19) is still atest.fixmestub, and there is no live FX rate yet, so amounts show USD only with a note. Full state inROADMAP.md(Phase 4: 11DONE, 6IN PROGRESS, 9TODO).
- Chain is truth. Every page that can move money calls
getLock/getPayee/verifyReceipt. Postgres-backed lists are for dashboards, and the dashboard doesn't read them for actions. - The claim-link fragment stays in the browser.
#r=…&s=…is never sent to a server, logged, or handed to a third party./claim/*gets a nonce-only CSP fromproxy.ts,Referrer-Policy: no-referrersite-wide, and zero third-party scripts or analytics. Saved claim links live inlocalStoragewith an export option. - Money is shown honestly.
AssetLabelalways prints code and issuer;AmountDisplayformats frombigint;DateTimeDisplayshows UTC and local time.RateProvideris an interface with no implementation yet — the rate path fails closed to USD-only rather than inventing a number (M3-16,DEC-10). - Nothing is hard-coded to a country. Country and
local_currencycome from registry data. Every user-visible string lives inmessages/en.json, keys are type-checked, and a test fails the build on an inline string;Intlhandles numbers, currencies and dates (M3-23done — other locales are deferred, not missing by accident). - Only
lib/sdk.tstalks to the contract. Wallets go throughlib/wallet/using the Stellar Wallets Kit, so Freighter, xBull, Albedo and Lobstr are swappable.
One caveat worth knowing: until the app is wired to the deployed indexer, /send and /request offer the payees in lib/payees/known.ts — a checked-in snapshot of the three testnet fixture payees (KE, NG, PH). It is not trusted for display: each entry's payee_id and meta_hash are re-derived locally and matched against a live getPayee chain read, and status and payout address always come from chain.
pnpm install
pnpm dev
pnpm lint && pnpm typecheck && pnpm test # biome, tsc, vitest (13 files, 69 cases)
pnpm e2e # placeholder until M3-19
Next.js 16 (App Router), React 19, TypeScript 7, Tailwind 4. Node 20 in CI.
Configuration is Zod-validated in lib/config.ts. Required: NEXT_PUBLIC_STELLAR_NETWORK, NEXT_PUBLIC_RPC_URLS, NEXT_PUBLIC_CONTRACT_ID, NEXT_PUBLIC_USDC_CONTRACT_ID, NEXT_PUBLIC_USDC_ISSUER. NEXT_PUBLIC_INDEXER_URL is optional — only list views need it, and the deployed testnet app currently runs without it. Copy .env.example to .env.local; never commit values.
Deploys to Vercel are currently manual (vercel --prod) — there's no deploy workflow, so a merged PR does not go live by itself.
docs/ARCHITECTURE_ESSENTIALS.md(short; read at the start of every task)AGENTS.md(rules for humans and agents) andCLAUDE.mdROADMAP.md: every PR updates it
next.config.ts, proxy.ts, app/claim/** and lib/claim-links/** are security-sensitive: ask before changing them, and label the PR accordingly.
Docs in docs/ are read-only copies synced from Kinlock-Org/.github.
Found a documentation gap (missing, unclear, or outdated docs)? File it at Kinlock-Org.github.io with area:app, the org's documentation hub, not here.