# React Native

React Native gets its **own package**: `@peltier/react-native`. It ships the drop-in RN widgets (below), the same headless engine hooks the web widgets run on (via `@peltier/react-core`), and the full platform-neutral core surface (via `@peltier/core`): the session API client, EIP-712 signing, burner creation (viem), calldata verification, and stealth derivation. The web package (`@peltier/react`) is only for DOM apps — never install it in an RN project.

```bash
npm install @peltier/react-native viem
```

```typescript
import { registerSession, watchSession, signIntentPerSourceChain, commitIntent } from '@peltier/react-native'
```

The package root exports a curated, **browser-safe** surface — widgets, hooks, and client primitives alike — and none of it references the DOM, so nothing crashes at import time and no Metro configuration is needed. (Non-React code, e.g. a shared business-logic module, can import the same primitives from `@peltier/core` directly. Server-side `sk_` helpers are deliberately absent — see the availability list below.)

## Setup & polyfills

One polyfill is required: viem's key generation (and Lunar stealth derivation) needs `crypto.getRandomValues`, which React Native does not provide. Install `react-native-get-random-values` and import it as the **first line** of your app entry:

```typescript
// index.js
import 'react-native-get-random-values'  // MUST be first — polyfills crypto.getRandomValues
import { AppRegistry } from 'react-native'
import App from './App'
```

Both `react-native-get-random-values` and `react-native-sse` are declared as **optional peerDependencies** — install only the ones your app actually uses.

## Drop-in widget

A native `DepositWidget` ships in `@peltier/react-native` — the same engine as the web widget (burner → EIP-7702 auth → register → watch → detect-then-sign commit), rendered with React Native primitives. It has **zero native-module dependencies**: QR rendering and clipboard access are injected via props so you choose the libraries (or skip them — the address falls back to selectable text).

```typescript
// App.tsx (React Native)
import { PeltierProvider, DepositWidget } from '@peltier/react-native'
import QRCode from 'react-native-qrcode-svg'
import * as Clipboard from 'expo-clipboard'

<PeltierProvider apiUrl="https://api.peltier.dev" publishableKey="pk_live_…">
  <DepositWidget
    destinationAddress="0x123…abc"
    destinationChainId={8453}
    renderQr={(address) => <QRCode value={address} size={200} />}
    onCopyAddress={(address) => Clipboard.setStringAsync(address)}
    onComplete={(sessionId) => console.log('Done:', sessionId)} />
</PeltierProvider>
```

It accepts the same session props as the web widget where they apply: the merchant checkout trio (`sessionId` / `clientSecret` / `publishableKey`), fixed-token pins (`inToken` / `minOut` / `maxIn`), `intentTtlSeconds`, `theme`, plus `eventSource` for true SSE (below). Prefer your own UI? The engine is exported separately as the `useDepositSession` hook — it returns `{ burnerAddress, session, status, error, connectionState, retryCommit, reset, adoptSession }` and handles everything else, so a custom screen is just rendering those values (`error` is a structured `PeltierError`; `connectionState` reflects the SSE/poll transport; `retryCommit()` re-runs a failed detect-then-sign commit without discarding the funded burner; `adoptSession` lets a Flow B screen watch a session that was registered + committed at creation).

## Flow C: send from the payer's own wallet

The same package ships `SendWidget` — Flow C rendered natively. Its engine is the `useSendFromWallet` hook (quote → **mandatory calldata verification** — recipient + `minOut`, fail-closed without a router whitelist → exact-amount approve → send the verified calldata as-is → poll). Feed it any connected EIP-1193 provider; on mobile that is WalletConnect's React Native provider (`@walletconnect/ethereum-provider` works in RN with the usual polyfills).

```typescript
// SendScreen.tsx (React Native)
import { SendWidget } from '@peltier/react-native'
import { EthereumProvider } from '@walletconnect/ethereum-provider'

const provider = await EthereumProvider.init({ projectId, chains: [8453], showQrModal: false })
await provider.connect()  // deep-links into the payer's wallet app

<SendWidget
  provider={provider}
  recipient="0x123…abc"
  allowedRouters={['0x…UniversalRouter', '0x…SpokePool']}  // REQUIRED — fails closed without it
  onComplete={(txHash) => console.log('Sent:', txHash)} />
```

Like the web `SendFromWallet`, the widget refuses to prompt the wallet unless the quote's calldata verifies against the pinned recipient and the `minOut` floor, and `allowedRouters` must whitelist the real Universal Router / Across SpokePool addresses — with an empty whitelist it fails closed (FC-001). For a custom screen, `useSendFromWallet` exposes the full form + flow state (`account`, chain/token/amount selectors, `quote`, `phase`, `getQuote()`, `send()`).

## Flow B: create a shareable payment request

`RequestWidget` (same package) is the native drop-in for Flow B — the mobile counterpart of the web `<PeltierRequest>`. It generates a burner, signs the open-token, destination-pinned Intent for **every** source chain, and **commits it at creation**, then shows a shareable link to QR-encode. The burner is discardable afterwards: a relayer settles whatever token lands to your destination as USDC, even with no one online. Its engine is the `useRequestSession` hook.

```typescript
// RequestScreen.tsx (React Native)
import { RequestWidget } from '@peltier/react-native'
import QRCode from 'react-native-qrcode-svg'
import * as Clipboard from 'expo-clipboard'

<RequestWidget
  destinationAddress="0x123…abc"
  destinationChainId={8453}
  amountLabel="25 USDC"                       // display-only advisory; not enforced on-chain
  renderQr={(link) => <QRCode value={link} size={200} />}
  onCopyLink={(link) => Clipboard.setStringAsync(link)}
  onCreated={(link, sessionId) => console.log('Request:', link)}
  onComplete={(sessionId) => console.log('Paid:', sessionId)} />
```

For a custom screen, `useRequestSession` returns `{ create, shareLink, status, phase, error, reset }` — call `create()`, render the returned `shareLink` as a QR, and reflect `status` as it settles. The platform-neutral `createRequestSession()` primitive (register → sign-per-source-chain → commit) is also exported from `@peltier/react-native` (and from `@peltier/core`) if you want to drive it without React.

## Session watching without EventSource

React Native has no global `EventSource`, so the SSE stream the web widget uses is not directly available. The native entry exposes `watchSession`, which picks the best available transport: SSE when an `EventSource` implementation exists (global, or injected), otherwise it polls `GET /sessions/:id` (default every 3 s). Both paths surface identical fields, including the `detected` payload the detect-then-sign flow needs. A dropped SSE stream reconnects with capped exponential backoff (up to `maxSseRetries`, default 3), then degrades permanently to polling — a network blip never kills the watch. Surface the transport in your UI via `onConnectionChange`.

```typescript
// watchSession — polling fallback (zero extra deps). onError is now OPTIONAL
// and advisory: (error, { willRetry }) — the watch keeps itself alive.
const unsubscribe = watchSession(apiUrl, sessionId, (session) => {
  // session.status, session.sourceChainId, session.detectedToken, session.detectedAmount
}, (err, { willRetry }) => console.warn('stream error', err, { willRetry }), {
  onConnectionChange: (s) => console.log(s),  // 'connecting' | 'live' | 'reconnecting' | 'polling'
})

// It never stops on its own (failed/expired sessions can re-activate on a late
// deposit) — unsubscribe once you consider the session finished, e.g. on 'swept'.
unsubscribe()
```

```typescript
// watchSession — true SSE via react-native-sse
import EventSource from 'react-native-sse'

watchSession(apiUrl, sessionId, onSession, onError, { eventSource: EventSource })
```

## Full flow: request / receive from a React Native app

Prefer the drop-in? Use `RequestWidget` / `useRequestSession` (above). This shows the raw primitives for a fully custom screen: generate a burner, sign the `chainId: 0` EIP-7702 authorization, register the session, show the address as a QR code, then watch for the deposit.

```typescript
// PayScreen.tsx
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
import { registerSession, watchSession } from '@peltier/react-native'
import QRCode from 'react-native-qrcode-svg'

// 1. Fresh burner + wildcard EIP-7702 auth (code delegation only — funds move
//    only via the separately signed, chain-bound EIP-712 Intent)
const privateKey = generatePrivateKey()
const burner = privateKeyToAccount(privateKey)
const auth = await burner.signAuthorization({
  contractAddress: implementationAddress, chainId: 0, nonce: 0,
})

// 2. Register the session (the backend starts watching all chains)
const session = await registerSession(apiUrl, {
  burnerAddress: burner.address,
  signedAuth: { address: auth.address, chainId: 0, nonce: 0,
    r: auth.r, s: auth.s, yParity: auth.yParity },
  destinationAddress,
  destinationChainId: 8453,
})

// 3. Show the deposit address as a QR code (any RN QR library works)
<QRCode value={burner.address} />

// 4. Watch for the deposit; on 'detected', run detect-then-sign:
//    fetchQuote → build Intent → signIntentPerSourceChain → commitIntent
//    (same primitives the web widget uses — see the API Reference)
const stop = watchSession(apiUrl, session.id, (s) => {
  if (s.status === 'swept') { stop(); onComplete() }
}, onError)
```

Keep the burner private key in memory only (component state), exactly like the web widget does — it is a per-session throwaway. If you persist anything, prefer signing the **standing refund** at creation (`signStandingRefund`) so a killed app never strands funds: the backend can always bounce the balance back to the payer's own wallet.

## What is and isn't available

- ✓ **Available:** the native `DepositWidget` (flow A + anonymous B), `SendWidget` (flow C), `RequestWidget` (flow B request / receive), and `StealthRequestWidget` ([flow E](/docs/flow-e-stealth-request.md), Lunar) in `@peltier/react-native`, their engine hooks `useDepositSession` / `useSendFromWallet` / `useRequestSession` / `useStealthRequest`, `PeltierProvider` / `usePeltier` (pure React context), the browser-safe session API (`registerSession`, `commitIntent`, `getQuote`, `fetchQuote`, refunds), the request-create primitives (`createRequestSession` / `createStealthRequest`), `getSession` / `watchSession`, all EIP-712 signing helpers, calldata verification (`verifyQuoteCalldata` / `verifyMinOut`), the Lunar stealth primitives + withdrawal (`sweepSolanaStealth` / `submitStealthWithdraw`), and the Flow D merchant helper (`setMerchantStealthMeta`). Flows A, B, C, and E have a native drop-in; Flow D (stealth checkout) is merchant-server-side.
- ✗ **Web-only (`@peltier/react`):** the DOM renderings — the web `DepositWidget`, `PeltierCheckout` / `PeltierRequest` / `PeltierSend`, `SendFromWallet`, and the presentational `QuotePreview` — plus `styles.css`. Note that `@peltier/react-native` ships its **own** `DepositWidget` (flows A + B) and `SendWidget` (flow C) on the same `useDepositSession` / `useSendFromWallet` engines, so the widgets are **not** web-exclusive — only these specific DOM components and the CSS are. (`QuotePreview` is a standalone presentational card with no separate native component; `SendWidget` renders the quote inline.)
- ✗ **Server-only (`@peltier/node`, or `@peltier/core` in non-browser code):** the `sk_`-keyed REST helpers — `createPaymentSession`, `getPaymentSession`, `listRequests`, the subscription management fns, and `verifyWebhookSignature`. These are **no longer exported from `@peltier/react-native`** — the package ships a curated browser-safe list, because payment-session creation and webhook verification belong on your server, never in the app.
