Migrate all per-PP API calls to URL-scoped paths + KYC submission route#37
Merged
Conversation
* listRecentBundles, getBundleDetail, getTreasury, getMetrics, deletePp,
joinCouncil, getCouncilMembership, checkMembershipStatus, EventsClient WS
— all targeting /providers/:ppPublicKey/...
* Add public KYC/KYB submission route at #/entities/register?provider=…
- No localStorage/sessionStorage/IndexedDB/cookies writes for any artifact
(wallet address, nonce, signed challenge, name)
- Wallet adapter isolated from operator-auth: lib/wallet-kyc.ts holds
address in module-local state, never touches provider_admin_address /
master_seed / console_token
- Hard-error UI when ?provider= missing or malformed; no default fallback
- Frontend sanitises name (strips tags) as defence in depth; server is
authoritative
* Add getRouteQuery() to the hash router so per-route query-strings work
* getBundleDetail signature gains ppPublicKey so callers hit the URL-scoped
variant correctly
This was referenced Jun 1, 2026
Merged
AquiGorka
added a commit
to Moonlight-Protocol/local-dev
that referenced
this pull request
Jun 1, 2026
## Summary * Migrate every consumer in this repo to the URL-scoped surface introduced by Moonlight-Protocol/provider-platform#107. Affected: `e2e/dashboard.ts` (channels/mempool/operations/treasury/metrics/audit-export), `e2e/main.ts`, `e2e/pos-instant/main.ts`, `e2e/governance/uc2-approve-reject.ts`, `lifecycle/{main,testnet-verify,ci-test}.ts`, `testnet/main.ts`, `setup-pp.{sh,ts}`, `lib/client/bundle.ts`. Comments and section headers updated to name the new URLs. * Add `lib/client/register-entity.ts` — a shared SEP-43/SEP-53 signed-challenge KYC submission helper, replacing six in-place `registerEntity` copies across `e2e/`, `lifecycle/`, `testnet/` that hit the bare-deleted `POST /api/v1/entities`. Living under `lib/client/` so every docker-compose test-runner that already copies `/lib-src/client/` reaches it. * Rewrite `send-loop.ts:forceExpireBundles` to write `status='EXPIRED'` + back-dated TTL directly to the provider-platform PostgreSQL via `postgres`. Replaces the bare-deleted HTTP admin endpoint `POST /api/v1/dashboard/bundles/expire`; the back-dated TTL lets the platform's mempool TTL-check sweep purge the in-memory slot via the same path it uses for natural TTL expiry. There's a short race window: when the executor wins the race to settle, the verifier later transitions the bundle to COMPLETED; when our UPDATE + back-dated TTL win, the bundle stays EXPIRED. In both cases the bundle reaches a terminal status, which is what `INJECT_EXPIRE` exists to produce. (A race-free guarantee would require in-process mempool access we deliberately don't expose over HTTP.) ## Test-infra fix * `docker-compose.pos-instant.yml`'s test-runner entrypoint only copied top-level `lib/*.ts` into the container — it skipped the `lib/client/` subdirectory the other suites already copy. After `register-entity.ts` moved into `lib/client/`, the suite errored with `Module not found "file:///app/lib/client/register-entity.ts"`. Adding `cp -r /lib-src/client lib/` aligns pos-instant with the other suites' mount shape; the import path becomes `./lib/client/register-entity.ts` to match the container-flattened layout the other pos-instant imports use (`./moonlight-pay-lib/…`, `./e2e/…`). ## URL changes (consumer side) ``` e2e/governance/lifecycle/testnet/setup-pp now hit: POST /providers/:pp/entity/bundles (user-mode submit) GET /providers/:pp/entity/bundles/:id (user-mode poll) POST /providers/:pp/council/join (operator) GET /providers/:pp/council/membership (operator) POST /providers/:pp/council/membership (operator sync) GET /providers/:pp/{channels,mempool,operations, treasury,metrics,audit-export} (operator dashboard) entity registration uses lib/client/register-entity.ts: POST /providers/:pp/entities/challenge (issue nonce) POST /providers/:pp/entities (signed submit) ``` ## Companion PRs - Moonlight-Protocol/provider-platform#107 - Moonlight-Protocol/provider-console#37 - Moonlight-Protocol/pay-platform#36 ## Test plan - [x] `deno fmt --check`, `deno lint` (23 pre-existing warnings, none new) - [x] `test.sh e2e`, `otel`, `governance`, `lifecycle`, `pos-instant`, `invite-gate` — all green against the companion branches - [x] `testnet/run-local.sh payment` + `lifecycle` — 23 OTEL checks each, green - [x] `send-loop.ts EXPIRE=true` — 12-country cycle, force-expire writes EXPIRED to DB (`force-expired 1 of 1 bundle(s) via DB (TTL back-dated…)`); at-rest DB shows EXPIRED for the one bundle whose UPDATE landed before settlement and COMPLETED for the others (race outcome, both terminal — see Summary)
2 tasks
AquiGorka
added a commit
to Moonlight-Protocol/local-dev
that referenced
this pull request
Jun 2, 2026
…KYC route (#108) ## Summary * Tighten the playwright full-flow suite so it actually fails when the payment doesn't settle. The previous assertions accepted UI scaffolding visibility regardless of whether anything landed protocol-side — the suite passed against backends silently returning errors. * Exercise the new public KYC route (`#/entities/register?provider=<ppPubkey>`) introduced by Moonlight-Protocol/provider-console#37: connect Freighter → sign SEP-43/raw challenge → submit name → success card. * Fix three pre-existing test bugs the loose assertions had hidden: silent psql-based cleanup, stale `council_pps` accumulation, and a wrong-pubkey fill in Step 8. ## Bugs the loose assertions hid | # | Bug | Surfaced now via | |---|-----|------------------| | 1 | Step 12 asserted only `#pos-status` visible + non-empty. moonlight-pay writes BOTH success and failure text into that element. | Step 12 now reads the text and fails on `failed|reject|error|expired|insufficient`. | | 2 | Step 13 asserted "balance display OR tx list visible" — both render on a freshly-loaded page regardless of any payment. | Step 13 now queries `pay-platform.transactions` for a COMPLETED IN row with `wallet_public_key = merchant.publicKey` and asserts `sum(amount_stroops) > 0`. | | 3 | `beforeAll` used `execSync("psql …")` to truncate stale rows — psql isn't in the test-runner container, the call no-op'd ("DB cleanup skipped"). | Replaced with `helpers/db.ts:cleanupPayPlatformDb()` using node-pg, now truncating `councils`, `pay_accounts`, AND `council_pps` CASCADE. Adds `pg` + `@types/pg` to `playwright/package.json`. | | 4 | Step 8 filled `#pp-pk` with `profiles.provider.publicKey` (the operator wallet pubkey), but provider-console registers a *derived* PP keypair — pay-platform stored the wrong key and `instant-execute` 500'd with "PP not found or inactive". | New Step 6b discovers the actual PP pubkey via `helpers/db.ts:fetchActivePpPublicKey()`; Step 8 now fills it with `ppPublicKey`. | | 5 | `provider-platform`'s add-bundle gate requires the submitter's entity row to be APPROVED. `pay-service` (the bundle submitter for POS-instant) is auto-created as UNVERIFIED via SEP-10 first-time auth and never promoted — bundle would 403. | Step 6b registers pay-service as APPROVED via `helpers/register-entity.ts` (SEP-43/raw signed-challenge POST to `/providers/:pp/entities`). | ## New surface * `helpers/db.ts` — node-pg helpers: cleanup, PP discovery, completed-inbound-stroops query. * `helpers/register-entity.ts` — the SEP-43/raw signed-challenge flow against `POST /providers/:pp/entities`. * New steps: - **Step 6b** (after council approval, before admin onboarding) — discover PP pubkey + register pay-service as APPROVED. - **Step 13b** (after merchant verification) — exercise the new public KYC route end-to-end with the POS user's Freighter wallet. ## Test plan - [x] `./test.sh playwright` — **16/16 steps pass** (was 14/14 passing against a broken backend; now 16/16 passing against a working one). Tested locally against the four PRs merged to main: - Moonlight-Protocol/provider-platform#107 - Moonlight-Protocol/provider-console#37 - Moonlight-Protocol/pay-platform#36 - #107 - [x] Empirical confirmation of the original false-green: ran playwright + parallel `docker logs provider` capture; bundle submission 500'd with `resolveChannelContext: PP GBOTA2… not found or inactive`, `operations_bundles` table was empty, yet all 14 original steps passed. ## Companion / follow-up None — this is a test-only PR on `local-dev`. No changes to platform behaviour.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
src/lib/api.tsandsrc/lib/events-client.tsto the URL-scoped/api/v1/providers/:ppPublicKey/...paths landing inprovider-platform's companion PR. Affected helpers:listRecentBundles,getBundleDetail,getTreasury,getMetrics,deletePp,joinCouncil,getCouncilMembership,checkMembershipStatus,EventsClientWS URL.#/entities/register?provider=<ppPubkey>. Hard-error UI when?provider=is missing or malformed (no fallback). Wallet connect → fetch SEP-43/SEP-53 challenge fromPOST /providers/:pp/entities/challenge→ sign nonce →POST /providers/:pp/entitieswith{name, jurisdictions, signedChallenge}. Frontend strips HTML tags from the name as defence-in-depth; the server is authoritative.window.namefor any artifact (address, nonce, signature, name).src/lib/wallet-kyc.tsholds the connected address in module-local state and never touches the operator-auth keys (provider_admin_address,master_seed,console_token). The route renders without operator chrome (no nav, no logout, no "My providers"). An existing operator session on/homeis unaffected by visiting/entities/register, and vice versa.Smaller changes
getBundleDetailsignature gainsppPublicKeyso callers hit the URL-scoped variant correctly.lib/router.tsexposesgetRouteQuery()so the hash router can parse?provider=…off#/entities/register?provider=….EventsClientWS URL changes from${base}/events/ws?pp=…to${base}/providers/:pp/events/ws.Companion PRs
Test plan
deno fmt --check,deno lint,deno task check,deno task test(7 pass)deno task buildproducespublic/app.jscontaining the new KYC route#/entities/register?provider=<PP_PK>against the local stack; connect a wallet ≠ the operator wallet you used on/home; submit a name including<script>alert(1)</script>; expect success card; expect nolocalStorage/sessionStoragewrites for KYC artifacts during the flow; expect operator session on/homeunaffected.#/entities/register(no query) and#/entities/register?provider=not-a-keyboth render the "Cannot continue" card without redirect.