Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ NEXT_PUBLIC_INDEXER_URL=http://localhost:3001
# USDC token contract (Stellar Asset Contract) used for locks, and its issuer shown in the UI.
NEXT_PUBLIC_USDC_CONTRACT_ID=
NEXT_PUBLIC_USDC_ISSUER=
# Feature flag: enable direct-recipient locks (create_direct_lock). Default false.
NEXT_PUBLIC_ENABLE_DIRECT_PAYMENTS=false
48 changes: 35 additions & 13 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
# Playwright happy path (request -> send -> claim -> verify) against a dedicated, throwaway
# testnet contract (roadmap M3-19). Signing uses a test-only keypair adapter
# (lib/wallet/test-adapter.ts), active only when NEXT_PUBLIC_E2E_TEST_MODE=1 -- never in a real
# deployment. The indexer is built from kinlock-sdk's own Dockerfile and run against a
# throwaway Postgres container, mirroring the pattern already proven in kinlock-sdk's own CI
# (ci.yml's indexer-image job).
# On pull requests, run the browser privacy test without signing secrets. Run the signed
# request -> send -> claim -> verify flow only after reviewed code is pushed to protected main.
# The test-only keypair adapter (lib/wallet/test-adapter.ts) is active only when
# NEXT_PUBLIC_E2E_TEST_MODE=1 and is never enabled in the pull-request job or a deployment.
# The indexer is built from kinlock-sdk's Dockerfile and run against throwaway Postgres.
#
# Only the signed main-branch run needs the indexer (/send lists payees from it). The pull-request
# privacy test opens /claim/{id}, which reads chain state over RPC, so it runs without the
# indexer. Keeping the indexer off the PR path matters because RPC only serves about 7 days of
# events: once E2E_START_LEDGER ages out of that window the indexer exits at startup, which would
# fail every PR for a reason unrelated to the change under review.
name: e2e
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
env:
Expand All @@ -25,19 +32,18 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
with:
repository: Kinlock-Org/kinlock-sdk
path: kinlock-sdk-src
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
with:
repository: Kinlock-Org/kinlock-registry
# TEMPORARY: the fixture payee this job needs is still on an unmerged branch
# (kinlock-registry PR #38, self-authored, needs the owner's own review/merge --
# AGENTS.md's self-approval rule). Revert to the default (main) once it merges.
ref: feat/e2e-fixture-payee
path: kinlock-registry-src

- name: Start Postgres and build the indexer image
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: |
docker network create ci
docker run -d --name pg --network ci -e POSTGRES_PASSWORD=ci postgres:17
Expand All @@ -53,6 +59,7 @@ jobs:
rm -rf kinlock-sdk-src

- name: Migrate and start the indexer against the fixture contract
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: |
docker run --rm --network ci -e DATABASE_URL=postgres://postgres:ci@pg:5432/postgres \
--entrypoint node kinlock-indexer:e2e dist/db/migrate.js
Expand All @@ -68,6 +75,7 @@ jobs:
kinlock-indexer:e2e

- name: Wait for the indexer to ingest and registry-join the fixture payee
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: |
# Event ingestion and the registry join run on separate schedules (the join re-runs
# every REGISTRY_SYNC_MS, 60s by default, not on every new row) -- checking for "any
Expand All @@ -92,7 +100,23 @@ jobs:
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm exec playwright install --with-deps chromium
- run: pnpm e2e
- name: Run privacy E2E on pull requests (no signing secrets)
if: github.event_name == 'pull_request'
run: pnpm exec playwright test e2e/claim-fragment-privacy.spec.ts
env:
NEXT_PUBLIC_STELLAR_NETWORK: testnet
NEXT_PUBLIC_RPC_URLS: ${{ env.E2E_RPC_URL }}
NEXT_PUBLIC_CONTRACT_ID: ${{ env.E2E_CONTRACT_ID }}
NEXT_PUBLIC_INDEXER_URL: http://localhost:3001
NEXT_PUBLIC_USDC_CONTRACT_ID: ${{ env.E2E_TOKEN_ID }}
NEXT_PUBLIC_USDC_ISSUER: ${{ env.E2E_TOKEN_ISSUER }}
# Lock 1 on this job's dedicated fixture contract (the test default 11 is on the
# standing testnet contract).
E2E_PRIVACY_TEST_LOCK_ID: "1"

- name: Run full signed testnet E2E on protected main
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: pnpm e2e
env:
NEXT_PUBLIC_E2E_TEST_MODE: "1"
NEXT_PUBLIC_STELLAR_NETWORK: testnet
Expand All @@ -103,8 +127,6 @@ jobs:
NEXT_PUBLIC_USDC_ISSUER: ${{ env.E2E_TOKEN_ISSUER }}
E2E_SENDER_SECRET: ${{ secrets.E2E_SENDER_SECRET }}
E2E_PAYEE_SECRET: ${{ secrets.E2E_PAYEE_SECRET }}
# claim-fragment-privacy.spec.ts's default (lock 11) is on the standing testnet
# contract, not this job's dedicated fixture one -- lock 1 there is the equivalent.
E2E_PRIVACY_TEST_LOCK_ID: "1"
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
if: ${{ !cancelled() }}
Expand Down
26 changes: 12 additions & 14 deletions DRIPS_WAVE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ This document provides a single-page executive and technical summary for the Dri

**Kinlock App** is the user-facing web portal allowing senders to create locks, payees to issue payment requests and claim funds, and public observers to verify institutional receipts on Stellar.

- **Real-World Problem:** International payments for education, housing, and contracts suffer from lack of trust, opaque fee deductions, and custodial vulnerability. Kinlock provides a non-custodial, scheduled escrow interface directly bound to Soroban smart contracts.
- **Real-World Problem:** Payments for education and housing can be difficult to reconcile and verify, especially when sender and payee are far apart. Kinlock provides a non-custodial way to lock USDC for a verified institution or any Stellar address, with scheduled releases and public on-chain receipts. The product is market-agnostic and launches market by market; it does not claim that a receipt proves a service was delivered.
- **Frontend Architecture & Stellar Integration:**
- **Framework:** Next.js 16 (App Router), React 19, TypeScript, Tailwind CSS.
- **Wallet Integration:** Swappable multi-wallet support via `@creit.tech/stellar-wallets-kit` (Freighter, xBull, Albedo, Lobstr).
Expand All @@ -21,9 +21,9 @@ This document provides a single-page executive and technical summary for the Dri
- Reference identifiers live exclusively in URL hash fragments (`#r=...&s=...`), strictly preventing client credentials and private references from reaching web servers, analytics tools, or access logs.
- Nonce-enforced Content Security Policy (CSP) and site-wide `Referrer-Policy: no-referrer`. Zero third-party analytics scripts on sensitive claim paths.
- **Universal & Market-Agnostic Core:**
- 100% externalized internationalization strings (`messages/en.json`).
- Dynamic currency and date formatting using native `Intl` APIs.
- Zero hardcoded national currencies or assumptions; payee metadata is fetched from canonical registry definitions.
- User-visible strings are externalized in the English message file; translated language packs are not yet provided.
- Number, currency, and date formatting use native `Intl` APIs.
- Country and local-currency values come from payee registry data. The sender currently displays USD only because the app has no live FX-rate provider.

---

Expand All @@ -45,31 +45,29 @@ This document provides a single-page executive and technical summary for the Dri

- [x] **Hosted under Organization:** [`Kinlock-Org`](https://github.com/Kinlock-Org)
- [x] **Permissive License:** Apache-2.0 ([`LICENSE`](LICENSE))
- [x] **Branch Protection:** Enforced on `main` (peer reviews required, required checks passing)
- [x] **Branch Protection:** The live `main` rules require one approval and the `ci`, `docs-in-sync`, `e2e`, and `roadmap-check` status checks (checked 2026-11-06).
- [x] **Continuous Integration (CI):** Green GitHub Actions validating Biome linting, TypeScript `tsc --noEmit`, Vitest test runner, and Next.js production build
- [x] **Security Hardening:**
- CodeQL SAST scanning (Actions, JavaScript/TypeScript)
- Semgrep code security checks
- Strict input sanitization with Zod
- Hardened headers: CSP with dynamic script-src hashes, HSTS, `X-Content-Type-Options: nosniff`, `Referrer-Policy: no-referrer`
- [x] **Testing Rigor:**
- 16 test files with 187 passing unit and integration tests
- Automated tests validating CSP headers, schedule calculation, refund triggers, receipt validation, inline string exclusions, and country-agnostic neutrality
- 16 test files and 197 passing tests on the reviewed working branch (`pnpm test`, run 2026-11-06)
- Automated tests cover CSP headers, schedule calculation, receipt and claim behavior, inline strings, and country-agnostic neutrality. Pull requests run the browser claim-fragment privacy test without signing credentials; the full testnet request → send → claim → verify Playwright flow runs only on protected-main pushes. The full flow previously passed in CI (`M3-19`); the updated workflow still needs a real fork-PR and protected-main run.
- [x] **Roadmap & Progress Tracking:** Every PR tracked and enforced via [`ROADMAP.md`](ROADMAP.md)

---

## 4. Drips Wave Task Queue & Issue Board

Issues for external contributors follow a strict three-tier complexity mapping aligned with Drips Wave points:
- **Trivial (100 points):** Copy updates, accessibility improvements, ARIA landmarks, and UI state polish.
- **Medium (150 points):** Client-side export conveniences, locale selector enhancements, and transaction receipt formatting components.
- **High (200 points):** Comprehensive end-to-end integration workflows (Playwright happy-path test harnesses) and multi-wallet pairing fallbacks.
The live issue tracker is authoritative for available work; tasks and point values change as issues are completed or program guidance changes. Before starting, contributors should confirm an issue is still open, assigned as required, mapped to a current roadmap item, and within the safe contribution scope. Contract fund logic and other security-sensitive changes require maintainer review and are not ordinary Wave tasks.

> **Task Safety Rule:** Under `AGENTS.md`, money-moving state transitions must never read from intermediary Postgres services, and URL fragment references must never be logged or sent off-device. Wave tasks strictly reinforce these guardrails.
> **Task safety:** Money-moving decisions read chain state, not Postgres. Claim-link references stay in the browser and must never be logged or sent to a server. Follow the repository contribution guide and issue acceptance criteria.

- **Active Bounty Issues:** [Kinlock App Wave Issues](https://github.com/Kinlock-Org/kinlock-app/issues?q=is%3Aissue+is%3Aopen+label%3Adrips-wave%2Cwave)
- **Contribution Guide:** [`CONTRIBUTING.md`](CONTRIBUTING.md) and [`docs/wave-issue-format.md`](https://github.com/Kinlock-Org/.github/blob/main/docs/wave-issue-format.md)
- **Open issues:** [Kinlock App issues](https://github.com/Kinlock-Org/kinlock-app/issues?q=is%3Aissue+is%3Aopen)
- **Contribution guide:** [`CONTRIBUTING.md`](CONTRIBUTING.md) and the [Wave issue format](https://github.com/Kinlock-Org/.github/blob/main/docs/wave-issue-format.md)
- **Current implementation and remaining tasks:** [`ROADMAP.md`](ROADMAP.md)

---

Expand Down
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ A 25-second overview of the flow — lock USDC for a verified payee, the payee c
| `/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`) |
| `/payee` | Payee dashboard: connect a wallet, view and filter locks addressed to the payee, and open a lock's claim page. List data comes from the indexer; actions read chain state. |
| `/attester` | Placeholder page; the read-only account-check tooling is not implemented yet (`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. `/send` is `M3-06` in progress, the Playwright happy path (`M3-19`) is still a `test.fixme` stub, and there is no live FX rate yet, so amounts show **USD only with a note**. Full state in `ROADMAP.md` (Phase 4: 11 `DONE`, 6 `IN PROGRESS`, 9 `TODO`).
> **Status:** the sender, claim, lock-detail, payee dashboard and receipt flows are implemented. Money-moving actions and receipt verification read chain state through the SDK; the payee dashboard uses indexer data for lists. The Playwright request → send → claim → verify test is implemented, and the roadmap records it as passing in CI (`M3-19`). Local E2E runs need the dedicated testnet setup and signing secrets. There is no live FX-rate provider yet, so the app shows USD only with an explanatory note. See `ROADMAP.md` for current status and remaining work.

## The rules this repo exists to enforce

Expand All @@ -39,22 +40,22 @@ A 25-second overview of the flow — lock USDC for a verified payee, the payee c
- **Nothing is hard-coded to a country.** Country and `local_currency` come from registry data. Every user-visible string lives in `messages/en.json`, keys are type-checked, and a test fails the build on an inline string; `Intl` handles numbers, currencies and dates (`M3-23` done — other locales are deferred, not missing by accident).
- **Only `lib/sdk.ts` talks to the contract.** Wallets go through `lib/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.
Payee discovery in `/send`, `/request`, and `/payee` reads from the configured indexer. If the indexer is not configured or is unavailable, the pages display an honest unavailable state (`pages.*.indexerDown`). Money-moving actions and receipt verification read chain state directly through the SDK (hard rule 3: chain is truth).

## Quick start

```
pnpm install
pnpm dev
pnpm lint && pnpm typecheck && pnpm test # biome, tsc, vitest (13 files, 69 cases)
pnpm e2e # placeholder until M3-19
pnpm lint && pnpm typecheck && pnpm test
pnpm e2e # Playwright; requires configured testnet E2E setup and secrets
```

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.
Vercel deploys are currently **manual** (`vercel --prod`); a merged PR does not go live by itself.

## Maintainers

Expand Down
Loading
Loading