# Flow B — Request / Receive

Flow B lets a **recipient** create a dynamic-destination payment request, share it as a link or QR, and have any payer send any token — settling to **USDC** at the recipient's chosen chain + address. It reuses the same hosted burner + signed-Intent engine as [checkout (Flow A)](/docs/flow-a-checkout.md), but the destination comes from the request (not a merchant config) and **no merchant account is required**. Sessions carry `flow = 'p2p_request'` and `merchant_id = NULL` — no new tables.

> **Drop-in first, headless below.** For `useRequestSession`, the `createRequestSession` primitive, and listing requests from your server, see the **Headless** section at the bottom of this page.

## No-stranding guarantee (open-token, destination-pinned)

The hard product requirement is that **funds never strand**, even if the recipient closes the tab and never returns. Flow B uses a dedicated contract mode: at *request creation* the recipient signs **one open-token, destination-pinned Intent** — the destination chain + address are fixed in the signature, but the deposit token is left *open*:

- `inToken = address(0)` (the OPEN sentinel selecting the open-token branch)
- `maxIn = type(uint256).max` (no upper bound — settle whatever lands)
- `minOut = 0` (no on-chain slippage floor — the price of "never revert, never strand")
- `destAddr` / `destChainId` = the recipient's chosen destination (pinned in the signature)

The request is `commit`ted at creation, so **the burner key may be discarded immediately** — the recipient does not need to be online for settlement. When a deposit is detected, a relayer settles it asynchronously to the **pinned** destination. Because the destination is in the signature, a malicious or compromised relayer *cannot* redirect or steal funds. A recipient who wants a hard slippage floor (non-zero `minOut`) should use Flow A's online detect-then-sign path instead.

## End-to-end

1. **Recipient creates the request.** The widget picks a destination chain + address (and an optional advisory amount), generates an in-memory burner, signs the EIP-7702 auth and the open-token dest-pinned Intent, registers a `flow: 'p2p_request'` session, and `commit`s the Intent — all at creation.
2. **Share the link / QR.** The widget shows `https://peltier.dev/p2p/{sessionId}?burner={burnerAddress}` plus a QR. The recipient can now close the tab.
3. **Payer sends any token.** The payer lands on `/p2p/{id}`, sees the burner **deposit** address (+ QR) and live status, and sends any token on any supported chain.
4. **Async settlement.** The indexer detects the deposit; the sweeper settles to the pinned destination with **no recipient online required**. USDC arrives on the destination chain.

## Drop-in (`<PeltierRequest>`)

`destinationAddress` + `destinationChainId` are the only required props (it's a `DepositWidget` locked to `flow='p2p_request'`): the widget generates a burner, signs + commits the open-token, destination-pinned Intent at creation, and renders the shareable link + QR — after which the recipient can close the tab.

```typescript
import { PeltierProvider, PeltierRequest } from '@peltier/react'
import '@peltier/react/styles.css'

<PeltierProvider apiUrl="https://api.peltier.dev" publishableKey="pk_live_…">
  <PeltierRequest
    destinationAddress="0xRecipient…"   // where the USDC settles — pinned in the signature
    destinationChainId={8453}            // the recipient's chosen settlement chain
  />
</PeltierProvider>
```

**React Native.** The same flow ships as `<RequestWidget>` in `@peltier/react-native` (inject `renderQr` + `onCopyLink`). See [React Native](/docs/react-native.md).

## Merchant visibility (see requests made in your app)

By default a request is **anonymous** (`merchant_id = NULL`) — the only handle on it is the `sessionId`. If you're building an app on Peltier and want to see the requests your users create, set your **publishable key** (`pk_…`) on the provider:

- Set `publishableKey` **once** on `<PeltierProvider>` — every widget under it inherits the key (override per-widget if needed). It rides as `Authorization: Bearer pk_…` on `POST /sessions`, and the backend records the owning merchant.
- List them from **your server** with the secret key: `GET /v1/requests` (SDK helper `listRequests` — see Headless below), scoped to your merchant + mode, newest first.
- They appear in the hosted dashboard under **Requests**, and settlement fires your existing merchant webhook automatically.

This surface requires `MERCHANT_MODE`. Without a `pk_` the request stays fully anonymous.

## Headless

The headless engine behind the drop-in. Everything here creates + commits the open-token, destination-pinned Intent at creation, so **nothing strands funds** — the payer side is always just the plain `/p2p/{id}` viewer.

| Symbol | What it is |
| --- | --- |
| `useRequestSession` | Headless engine hook: `{ create, shareLink, status, phase, error, connectionState, reset, creating, burnerAddress, session }`. `error` is a structured `PeltierError` or `null`; `connectionState` reflects the SSE/poll transport. |
| `createRequestSession` | Platform-neutral primitive (register → sign-per-source-chain → commit), usable without React. |
| `buildOpenTokenIntent`, `OPEN_TOKEN_ADDRESS`, `OPEN_MAX_IN` | Build the open-token, destination-pinned Intent (`inToken = 0`, `maxIn = MAX`, `minOut = 0`) directly. |
| `listRequests(apiUrl, sk_)` | Server-side: list the requests created in your app (`GET /v1/requests`). Lives in `@peltier/core` / `@peltier/node` — not the browser package. |

**Per-source-chain signing.** Because the deposit chain is unknown at creation (the payer may send from any chain) and the EIP-712 domain binds `block.chainid`, the recipient signs **one Intent signature per candidate source chain** at creation; commit submits them as a `{ chainId: signature }` map. This is why the burner key must be in hand at creation — and why it can be discarded immediately after. The signed Intent (and session) carry the standard **30-minute** TTL.

### Hook (`useRequestSession`)

It takes `apiUrl` + `implementationAddress` as options (no `PeltierProvider` needed).

```typescript
import { useRequestSession } from '@peltier/react'

function RequestButton() {
  const { create, phase, error, shareLink, status, session } = useRequestSession({
    apiUrl: 'https://api.peltier.dev',
    implementationAddress: '0x33333A781cbe9aC82Ba510BfF7b26c47a8FDecD4',
    destinationAddress: '0xRecipient…',
    destinationChainId: 8453,
    // optional: chains?, intentTtlSeconds?, appBaseUrl?, publishableKey?, onCreated?, onComplete?
  })

  // 1. Create + commit — signs one Intent per source chain, then commits at creation.
  //    Once this resolves the burner key is discarded; the recipient can go offline.
  if (phase === 'idle') return <button onClick={create}>Create request</button>
  if (phase === 'creating') return <p>Creating…</p>
  if (phase === 'error') return <p>{error?.message}</p>

  // 2. Share `shareLink` (or QR-encode it) — the payer opens it at /p2p/{id}.
  return (
    <>
      <a href={shareLink!}>{shareLink}</a>
      <p>Status: {status}</p>
      {status === 'swept' && <p>Settled {session?.detectedAmount} of {session?.detectedToken}</p>}
    </>
  )
}
```

Watching is purely cosmetic: because `create()` commits at creation, the relayer settles whatever lands to the pinned `destinationAddress` even if this component unmounts and the recipient never returns. Pass `publishableKey` in the options to tag the request with your merchant — the raw hook does **not** read `<PeltierProvider>` (only the widgets fall back to the provider's key).

### Primitive (`createRequestSession`)

Outside React, `createRequestSession` is the plain-function primitive the hook wraps — register (`flow='p2p_request'`) → sign-per-source-chain → commit — returning `{ session, intent, signatures, shareLink }`. Pass it a `createBurnerWallet(...)` burner and the `sourceChainIds` the payer may deposit on.

### List requests made in your app (server)

```typescript
import { listRequests } from '@peltier/core'   // also re-exported from @peltier/node

const { requests, total } = await listRequests(
  'https://api.peltier.dev',
  process.env.PELTIER_SECRET_KEY!,
  { limit: 50, offset: 0 },
)
```

Scoped to your merchant + mode, newest first. Requires `MERCHANT_MODE` and a `pk_`-tagged widget (see Merchant visibility above).
