Skip to content

Samuel1 ona/nim payment unavailable in a browser - #12

Merged
Samuel1-ona merged 2 commits into
mainfrom
samuel1-ona/nim-payment-unavailable-in-a-browser
Sep 2, 2026
Merged

Samuel1 ona/nim payment unavailable in a browser#12
Samuel1-ona merged 2 commits into
mainfrom
samuel1-ona/nim-payment-unavailable-in-a-browser

Conversation

@Samuel1-ona

Copy link
Copy Markdown
Contributor

The hole, and the fix

What this does NOT do / residual risk

Judgement calls

Issues

Closes #
Refs #

Stacking / conflicts

Verification evidence

Remaining ops steps

  • none

Checklist

  • Title is the commit message I want on main
  • Ships the test that fails on pre-fix code; fixtures are real captured data; fakes honour their arguments
  • Covers the seam, not only pure functions; any stated guarantee tested on its failure path
  • Error responses audited for what they leak; reads on write paths bounded at the query
  • Judgement calls / bundled product changes flagged above (or "none")
  • lint / typecheck / test pass locally on the current head
  • Re-read acceptance criteria of every Closes issue — all met
  • Development sidebar links match Closes/Refs above
  • README / runbook / .env.example / examples / error strings updated for the world this creates
  • Lockfile touched → rebased on current main, lockfile regenerated (never hand-resolved)
  • Wallet/provider tree touched → loaded in a normal browser, not only Nimiq Pay
  • Money/security path → money-path-checklist.md run; payout logic compared against the other side
  • No secrets in the diff
  • Questions for the maintainer marked clearly at the end (or "none")

Samuel1-ona and others added 2 commits September 1, 2026 18:20
…allet

The NIM panel rendered nothing outside Nimiq Pay, because a browser has no
injected Nimiq provider to pay with. That is also where every share link
lands: somebody clicking a taunt on X arrives in a desktop browser, where NIM
payment did not exist at all.

The Web Wallet closes that gap through the Hub — a popup at hub.nimiq.com
where the user's own wallet signs. `checkout()` both signs AND broadcasts, and
returns the transaction hash, which is precisely the receipt the settler
already goes looking for.

Nothing on the server changes, and that is the point. `/api/nim/settle`
verifies a payment by reading the funding transaction from a Nimiq node —
"the payer controls their client, so their claim to have paid is not
evidence" — so it neither knows nor cares which transport produced the
transaction. Same quote, same HMAC tag in the data field, same `settledNimTx`
guard, same confirmation wait. The quote route, the settle route and the
contract are untouched.

What changes is one client seam. `sendNimWithData` now dispatches on
`isNimiqPay()`: the mini-app SDK inside Nimiq Pay, `lib/nimiqHub.ts` in a
browser. Both return a hash, both throw `NimiqProviderError`, so callers keep
one contract and `useNimPayment` is unchanged.

Three constraints held:

- **Bundle isolation, in both directions.** The Hub is reached through a
  dynamic `import()`, mirroring the rule that keeps `@nimiq/mini-app-sdk` out
  of a browser's graph — Nimiq Pay runs a 2018 Android System WebView and will
  never open a popup. Verified against the BUILT output, not just the source:
  the Hub SDK lands in its own 26 KB lazy chunk and is absent from both shared
  first-load chunks. Only the endpoint constant reaches the page chunk.
- **The endpoint decides the network.** `checkout` broadcasts as it signs, so
  no client-side check can undo a testnet payment after the fact, and the
  settler pins mainnet (networkId 24). Pointing at the mainnet Hub is the only
  real guard, so that is the default and a test asserts it.
- **Popups need a user gesture.** The panel already pauses for an explicit pay
  tap, so this is satisfied by the existing flow rather than by new code.

One existing test changed meaning rather than breaking: `sendNimWithData`
"throws outside Nimiq Pay" was correct while the SDK was the only transport.
It now asserts the hand-off instead, with the mini-app dialog still never
raised outside Nimiq Pay, plus a new case proving validation runs before
either transport is reached.

Verification: 15 new tests covering the transport (hash out, treasury/Luna/tag
in, a cancelled popup becoming NimiqProviderError, a missing hash refused
rather than reported as success) and the bundle boundary in both directions,
each validation case paired with a control asserting the Hub was never
reached. Full suite 894 passing, up from 838. `tsc --noEmit` clean and
`next build` passes.

Installing @nimiq/hub-api also re-linked ox@0.14.5, which had been in the pnpm
store but not linked into apps/web/node_modules — so the type-check and the 3
test files that failed on main for that reason are green here. That was an
environment fault, not a code change.

NOT verified automatically, and it needs a manual pass on the preview deploy
before the demo: the live popup flow. No test can prove a real Web Wallet
payment reaches the settler — the Hub is mocked here. Worth one real
end-to-end run in a browser, and note that NIM_MAX_ORDER_USD_MICROS must be
correct in that environment or every re-buy is refused (see 92315dc).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdXsPDjCuFTx4BXd7qc1ow
The NIM panel reused the mini-app copy in a browser, so a player paying
through the Web Wallet was told to "Confirm the payment in Nimiq Pay" — an
app they are not in and may not have. Introduced by ec01202, which opened the
panel to browsers without splitting the strings that name the host.

The panel now resolves which wallet it is talking to once, after mount, and
the two strings follow from that. The browser line also warns that the window
may pause: the Hub runs a light client and sits on "Syncing consensus…"
before it can broadcast, and an unexplained wait on a payment screen reads as
a hang — which is what it looked like in testing.

Verification: full suite 894 passing, `tsc --noEmit` clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdXsPDjCuFTx4BXd7qc1ow
@vercel

vercel Bot commented Sep 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
terreno-web Ready Ready Preview Sep 2, 2026 11:25am UTC

Request Review

@Samuel1-ona
Samuel1-ona merged commit 6a005b0 into main Sep 2, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant