Skip to content

fix(pos-app): iOS payment details crash + "Incorrect API key" errors - #655

Merged
juanisierra merged 2 commits into
mainfrom
fix/pos-details-crash-and-api-key-errors
Oct 23, 2026
Merged

juanisierra merged 2 commits into
mainfrom
fix/pos-details-crash-and-api-key-errors

Conversation

@juanisierra

Copy link
Copy Markdown
Collaborator

Summary

iOS crash when opening a payment's details

  • The transaction detail sheet formatted the token amount with formatTokenAmount from @walletconnect/pay-core, which reads the decimal separator via Intl.NumberFormat#formatToParts. Hermes doesn't implement that on Apple platforms (llvm_unreachable("formatToParts is unimplemented on Apple platforms") in the shipped Hermes version), so opening details crashed the iOS app (RCTFatal).
  • utils/currency.ts now has its own formatTokenAmount with the same output as pay-core (2–6 decimals, ≈ when digits are cut, <0.000001 for dust) that reads the separator from toLocaleString(). The detail sheet and the receipt printer use it. pay-core 0.7.0 (latest) still has the issue.
  • Not demo-only: real payments with a token amount go through the same path.

"Incorrect API key" when the API rejects credentials

  • Create payment: the failure screen now says "Incorrect API key" — "No funds were moved. Check that the merchant ID and customer API key are correct." A bare 401 (no invalid_api_key code) is mapped to it too. Button unchanged (Go to Settings for local credentials, Start new payment in the dashboard iframe).
  • List payments: Activity shows "Incorrect API key" with Go to Settings instead of "Check your internet connection". In the dashboard iframe it keeps Try again, since Settings can't fix the dashboard's credentials. The refresh toast mentions the API key too.
  • services/transactions.web.ts now keeps the HTTP status and API error code (via readProxyResponse) instead of throwing a plain Error, so the screen can tell a 401 from a network failure.
  • Shared detection: isInvalidApiKeyError() in utils/payment-errors.ts (invalid_api_key code or status 401).

Tests

  • __tests__/utils/currency.test.ts: expected outputs, parity with pay-core, and a check that formatToParts is never called (fails against the pay-core re-export).
  • isInvalidApiKeyError, web transactions keeping 401 + code, and Activity screen cases (message, Go to Settings, bare 401, refresh toast). These fail without the change.
  • npm test: 39 suites / 384 tests passing; lint clean.
  • Not verified on a physical iPhone yet — please check the details sheet on an iOS build before merging.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CmgutsXzUS2j26BNUevhrE

juanisierra and others added 2 commits October 22, 2026 14:33
Creating a payment with rejected credentials showed "This terminal has
lost access", and listing payments showed "Check your internet
connection". Both now say the API key is incorrect when the error is
invalid_api_key or a 401:

- Payment failure: "Incorrect API key" (still routes to Settings for
  direct credentials); a bare 401 now maps to invalid_api_key too.
- Activity: "Incorrect API key" with Go to Settings (Try again in the
  dashboard iframe, whose credentials Settings can't fix); the refresh
  toast mentions the API key.
- Web transactions service keeps the HTTP status and API error code
  instead of throwing a plain Error, so the screen can tell a 401 apart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CmgutsXzUS2j26BNUevhrE
The transaction detail sheet formats the token amount with pay-core's
formatTokenAmount, which reads the decimal separator via
Intl.NumberFormat#formatToParts. Hermes doesn't implement that on Apple
platforms (llvm_unreachable), so opening a payment's details crashed the
iOS app (RCTFatal). Format token amounts locally with the same output,
reading the separator from toLocaleString instead.

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

vercel Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
pos-demo Ready Ready Preview Oct 22, 2026 12:37pm UTC
2 Skipped Deployments
Project Deployment Actions Updated
react-native-appkit Ignored Ignored Oct 22, 2026 12:37pm UTC
react-native-walletkit Ignored Ignored Oct 22, 2026 12:37pm UTC

Request Review

@claude

claude Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Claude finished @juanisierra's task in 3m 14s —— View job


Code Review

  • Gather context
  • Read changed files
  • Analyze for issues
  • Post findings

Found 2 issue(s)

Issue 1: BigInt.prototype.toLocaleString() may crash on Hermes iOS

ID: currency-bigint-tolocalestring-hermes-3f1a
File: dapps/pos-app/utils/currency.ts:142
Severity: MEDIUM
Category: correctness

Context:

  • Pattern: wholePart.toLocaleString() is called on a BigInt value inside formatTokenAmount
  • Risk: This PR specifically fixes a Hermes iOS crash caused by Intl.NumberFormat#formatToParts not being implemented. BigInt.prototype.toLocaleString() calls into the same Intl internals and may be unimplemented in the shipped Hermes version — same class of bug
  • Impact: Any transaction where the token amount ≥ 1 whole unit would crash the app on iOS (the original bug category: llvm_unreachable)
  • Trigger: Opening the details sheet for a payment with a token amount ≥ 1 (e.g. "1000000" USDC with 6 decimals)

Recommendation: Replace with String(wholePart) (no thousands separators, but safe) or check whether pay-core's output includes them. If thousands separators are wanted, format via Number when safe, falling back to string:

// Safe on Hermes — avoids BigInt Intl path entirely
const wholeStr =
  wholePart <= BigInt(Number.MAX_SAFE_INTEGER)
    ? Number(wholePart).toLocaleString()
    : String(wholePart);

Issue 2: toLocaleString() on BigInt whole part adds locale thousands separators — parity with pay-core untested for amounts ≥ 1,000

ID: currency-bigint-thousands-parity-8c2e
File: dapps/pos-app/__tests__/utils/currency.test.ts:21
Severity: LOW
Category: test-coverage

Context:

  • Pattern: The "matches pay-core" test suite covers amounts where the whole part is always 0 or 1 — no case exercises thousands separators in the whole part
  • Risk: 1234000000n / 1000000n = 1234n; (1234n).toLocaleString() returns "1,234" in en-US. If pay-core produces "1234" (no separator), the parity assertion would fail in any locale where thousands separators differ
  • Impact: Display mismatch between the POS app and the receipt email for amounts ≥ 1,000 units
  • Trigger: Token with 6 decimals and a raw value of "1000000000" or more (= 1,000+ whole units)

Recommendation: Add one parity case with a whole part ≥ 1,000:

["1234000000", 6],  // whole part 1234 — exercises thousands separator path
| [Branch](https://github.com/reown-com/react-native-examples/tree/fix/pos-details-crash-and-api-key-errors)

@juanisierra
juanisierra merged commit a02a14a into main Oct 23, 2026
10 checks passed
@juanisierra
juanisierra deleted the fix/pos-details-crash-and-api-key-errors branch October 23, 2026 12:06

This branch was successfully deployed

1 active deployment
Preview – pos-demo — e3832af6 Deployed Oct 22, 2026 by vercel[bot]
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.

2 participants