# React Native & Expo

> @layerhop/react-native — the swap widget and checkout as React Native components (react-native-webview) with typed callbacks, wallet deep-link helpers, and notes on using the SDK in React Native.

Source: https://docs.layerhop.com/sdks/react-native

`@layerhop/react-native` puts the LayerHop swap widget and the hosted checkout into React Native and Expo apps. `<LayerHopWidget />` and `<LayerHopCheckout />` load the hosted pages in a [`react-native-webview`](https://github.com/react-native-webview/react-native-webview) and take the same options and typed callbacks as the [web widget](/widget-reference). Wallet links open the user's wallet app. For a fully custom UI, the [TypeScript SDK](/sdk) and the [React hooks](/sdks/react) run in React Native too.

> Nothing here holds keys. The user connects their own wallet app (WalletConnect or a wallet deep link) and signs there.

## How do I install it?

```bash
# Expo
npx expo install @layerhop/react-native react-native-webview

# bare React Native
npm install @layerhop/react-native react-native-webview && npx pod-install
```

Peers: `react >= 18`, `react-native >= 0.72`, `react-native-webview >= 13`. Embed URLs are built with the standard `URL` API: Expo SDK 50+ includes it. In a bare app on an older React Native, install `react-native-url-polyfill` and add `import "react-native-url-polyfill/auto";` at the top of your entry file. Without it, the components throw a `LayerHopWidgetError` that says so.

## Swap widget

```tsx
import { LayerHopWidget } from "@layerhop/react-native";

export function SwapScreen() {
  return (
    <LayerHopWidget
      srcChain="base" srcToken="USDC" dstChain="solana" dstToken="SOL"
      theme="dark" preset="midnight" gasOnArrival
      integrator="myapp" // your partner id: earns your partner fee
      onQuote={(q) => console.log(q.provider, q.dstAmount)}
      onSubmitted={(e) => console.log("sent", e.srcTxHash)}
      onStatus={(e) => console.log("order", e.state)}
      onError={(e) => console.warn(e.code, e.message)}
    />
  );
}
```

Every [widget option](/widget-reference) works, and so does every callback: `onReady`, `onQuote`, `onRoutes`, `onSubmitted`, `onStatus`, `onError`, `onResize`, `onWalletConnected`, `onWalletDisconnected`, `onAmountChanged` and `onEvent`. Invalid options throw a `LayerHopWidgetError` during render. Changing an option reloads the page, while callbacks can change freely.

| React Native prop | Default | What it does |
|---|---|---|
| `baseUrl` | `https://layerhop.com` | Where `/embed` is served. |
| `autoResize` | `true` | Follow the page's height. Set `false` and give `style` a size (`{ flex: 1 }`) for a full-screen widget. |
| `height` | `640` | Height before the first `resize` event, or always when `autoResize` is off. |
| `style` | | Style of the wrapping `View`. |
| `onExternalLink` | | Called before a wallet link or external page opens. Return `false` to handle it yourself. |
| `webViewProps` | | Extra `react-native-webview` props. The bridge, source and navigation handling stay in place. |

## Checkout

Create the session on your server with your API key ([`lh.checkout.create`](/guides/checkout)), send its id to the app, then render:

```tsx
import { LayerHopCheckout } from "@layerhop/react-native";

<LayerHopCheckout
  sessionId={sessionId}
  onStatus={(status) => console.log(status)} // open → processing → paid
  onPaid={(session) => navigation.navigate("Thanks", { id: session.id })}
  onError={(err) => Alert.alert("Payment unavailable", err.message)}
/>;
```

Fulfil the order on the `checkout.session.completed` [webhook](/guides/webhooks), not only on `onPaid`. Other options:

- `productId="prod_…"`, with optional `reference` and `metadata`, starts a session on mount through the public endpoint. If the product only allows certain web origins, this answers `FORBIDDEN`.
- `mode="modal"` makes the page add a close button. Render the component inside a React Native `<Modal>` and close the modal in `onClose`, which also fires after `onCancel`.
- `autoResize`, `height`, `style`, `renderLoading`, `onExternalLink` and `webViewProps` work as they do on the widget.

## How do wallet deep links work?

When the user connects or signs, the page inside the WebView hands off to a wallet app. It does this through a custom scheme (`wc:`, `metamask://`, `phantom://`) or a universal link (`https://metamask.app.link/…`). The components open these links with `Linking` and keep the WebView on the LayerHop page. Explorer links and other pages outside LayerHop open in the system browser. Sub-frames, such as wallet SDK iframes, load normally.

```tsx
import { createReturnUrl, openWalletLink, useWalletReturn } from "@layerhop/react-native";

// Fires once the user comes back from the wallet app, or when a wallet opens your app through its scheme.
useWalletReturn((e) => console.log("back from wallet", e.reason, e.url), { scheme: "myapp" });

createReturnUrl("myapp", "swap", { order: "q_1" }); // "myapp://swap?order=q_1"
await openWalletLink("metamask://wc?uri=…");        // false when no app can open it
```

The scheme is your app's own: `expo.scheme` in `app.json`, or the URL types and intent filters of a bare app.

## Can I use the SDK and hooks in React Native?

Yes. `@layerhop/sdk` and `@layerhop/react` use only `fetch`, `URLSearchParams` and `AbortController`, and React Native has all three. Live streams use Server-Sent Events: `streamOrder`, `checkout.stream` and `useOrderStream`. React Native's `fetch` can't stream a response body, so the SDK (0.6+) reads these streams with `XMLHttpRequest` progress events in React Native. It does this automatically. To force it, use `new LayerHop({ streaming: "xhr" })`. To use Expo's streaming `fetch` instead, pass it in with `new LayerHop({ fetch })`, importing `fetch` from `expo/fetch`.

Never ship an API key in an app. Public calls (quotes, transactions, order status) don't need one. Use your key on your server for anything else, including creating checkout sessions and verifying webhooks.

## How does it work?

The hosted pages post `{ source: "layerhop", type, payload }` (widget) and `{ source: "layerhop-checkout", … }` (checkout) to `window.parent`, the same [postMessage protocol](/widget-reference) the web embed uses. Before the page loads, the component injects a small script. That script gives the page a `window.parent` that forwards only those envelopes to React Native. The component accepts messages only from the page's own origin and validates them with the same parsers as `@layerhop/widget`. The web widget's live `setParams` updates aren't used here: changing an option reloads the page.
