npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@yodlpay/react-native

v0.11.0

Published

Yodl React Native SDK — drop-in components for EIP-1193 wallets

Readme

@yodlpay/react-native

Let your users pay real-world merchants with crypto, straight from your React Native app. Scan a local QR, render one component, settle on-chain — Yodl turns stablecoins into the merchant's local currency.

Drop-in crypto payments for React Native. You bring an EIP-1193 wallet provider; the SDK renders the Yodl payment UI in a hardened, origin-locked WebView and handles the RPC bridge and on-chain execution. Users keep self-custody and often settle at FX rates that beat Visa/Mastercard.

The provider interface is always plain EIP-1193. Transaction batching and paymasters ride on top via EIP-5792 (wallet_sendCalls) — available when your wallet supports EIP-5792 natively, or through the optional EIP-7702 add-on for wallets that don't.

import { createYodlSdk, YodlProvider, YodlPayment } from '@yodlpay/react-native';

const sdk = createYodlSdk({
  integrationAddress: '0x000000000000000000000000000000000000dEaD',
  supportedChainIds: [1, 8453],
});

// provider, address, and chainId are the live wallet state from your wallet SDK
<YodlProvider sdk={sdk} provider={walletProvider} address={address} chainId={chainId}>
  <YodlPayment qrData={scannedQr} onTransactionSent={(tx) => console.log(tx)} />
</YodlProvider>;

Contents

Requirements

| Requirement | Version | | ------------- | ----------------- | | React | >= 18 | | React Native | >= 0.72 | | Hermes engine | Enabled (default) |

Uses native modules (react-native-webview), so it requires a custom dev build — it does not run in Expo Go. See Platform Notes.

Installation

Install the SDK and its react-native-webview peer dependency, then rebuild the native app.

Expo (resolves the WebView version to your Expo SDK):

npx expo install @yodlpay/react-native react-native-webview
npx expo run:ios   # or: npx expo run:android

Bare React Native:

npm install @yodlpay/react-native react-native-webview   # or: pnpm add / yarn add
npx pod-install                                          # iOS

Then rebuild the app. That's the full install.

Quick Start

Three steps: create the SDK once, wrap your screen in <YodlProvider>, render <YodlPayment>.

import { createYodlSdk, isPaymentQr, YodlProvider, YodlPayment } from '@yodlpay/react-native';

// 1. Create the SDK once, app-wide.
const sdk = createYodlSdk({
  integrationAddress: '0x000000000000000000000000000000000000dEaD', // your integration wallet address
  supportedChainIds: [1, 8453],
});

// provider, address, and chainId are the live wallet state from your wallet SDK's hooks.
export function PaymentScreen({ qrData, provider, address, chainId }) {
  // 2. Bail out early if the scanned QR isn't a Yodl payment (no SDK instance needed).
  if (!isPaymentQr(qrData)) return null;

  // 3. Supply live wallet state; the SDK keeps the Yodl UI in sync with it.
  return (
    <YodlProvider sdk={sdk} provider={provider} address={address} chainId={chainId}>
      <YodlPayment
        qrData={qrData}
        onTransactionSent={(txHash) => console.log('paid:', txHash)}
        onError={(err) => console.error(err)}
      />
    </YodlProvider>
  );
}

provider, address, and chainId all come from your wallet SDK (Privy, MetaMask, Coinbase Wallet, WalletConnect, …) — chainId is the chain the wallet is currently on, not a fixed value. See <YodlProvider /> for how this state drives the UI, and the full example for scanning, the dashboard, and lifecycle events.

Why Yodl

Pay like a local, with crypto. Live in Argentina, Brazil, the Philippines, and Vietnam — across Pix, Mercado Pago, QRPh, and VietQR.

  • Real-world spending — users pay merchants by scanning everyday local QR codes; merchants get their own currency, never crypto.
  • Self-custody — funds stay in the user's wallet until the moment of payment. No deposits, no custodian.
  • Beats card FX — on-chain settlement regularly comes in cheaper than Visa/Mastercard abroad.
  • Ship in an afternoon — one component, no payment UI or RPC plumbing to build.

Learn more about Yodl Pay →

Features

  • Wallet-agnostic — any EIP-1193 provider (Privy, MetaMask, Coinbase Wallet, Rainbow, WalletConnect).
  • Three drop-in components<YodlSignup> for onboarding, <YodlPayment> for QR payments, <YodlDashboard> for the account view.
  • Hardened by default — UI locked to the Yodl origin, off-origin navigation blocked. No config.
  • Batching & paymasters — bridges EIP-5792 (wallet_sendCalls); native support passes through, or add the EIP-7702 provider for atomic batching + gas sponsorship.
  • Multi-chain — Ethereum, Arbitrum, Base, Polygon, Optimism, BNB Chain, Gnosis.
  • Fully typed — first-class TypeScript throughout.

Platform Notes

Most apps just need the install above and a native rebuild. Edge cases:

  • Not Expo Go — uses the native react-native-webview, so run a dev build (npx expo run:ios / run:android) or bare React Native.
  • Hermes required — the SDK uses BigInt (Hermes is on by default in RN 0.70+). If you disabled Hermes, re-enable it or add a BigInt polyfill.

Usage

Create the SDK once with createYodlSdk(config) and supply wallet state at runtime via <YodlProvider>. Stateless helpers like isPaymentQr(qrData) are standalone exports — no SDK instance required to call them.

Signup flow

Render <YodlSignup /> when you need the hosted onboarding flow. The hosted Yodl UI owns the status check, SIWE, email capture, verification code entry, and the final signed-up screen.

import { useState } from 'react';
import { Text } from 'react-native';
import { YodlProvider, YodlSignup } from '@yodlpay/react-native';
import { sdk } from './sdk';
import { useWallet } from './wallet';

export function SignupScreen() {
  const { provider, address, chainId, isConnected } = useWallet();
  const [showSignup, setShowSignup] = useState(true);

  if (!isConnected) return <Text>Connect a wallet first.</Text>;
  if (!showSignup) return null;

  return (
    <YodlProvider sdk={sdk} provider={provider} address={address} chainId={chainId}>
      <YodlSignup
        onStatusChange={(status) => console.log('signup status:', status)}
        onSignedUp={() => {
          // Store completion however your app tracks user state.
          console.log('signed up');
          setShowSignup(false);
        }}
        onDismiss={() => setShowSignup(false)}
        onError={console.error}
      />
    </YodlProvider>
  );
}

<YodlSignup /> shows the Yodl signup flow and calls onSignedUp when the user is finished. The SDK does not expose account details from the hosted flow. If you need to remember that the user signed up, save completion in your own app state, storage, or backend.

Hosted UI theming

Theming is opt-in. Set theme, themeMode, or both on <YodlProvider> (or on any hosted component) to style the Yodl UI. Omit them and the hosted UI keeps its own appearance. Every field is optional.

import type { YodlTheme } from '@yodlpay/react-native';
import { YodlPayment, YodlProvider } from '@yodlpay/react-native';

// A small, representative subset of tokens — see "Tokens" for the full list.
const brandTheme: YodlTheme = {
  name: 'acme',
  light: { bg: '#ffffff', surface: '#f4f4f5', text: '#18181b', accent: '#6d4fc0' },
  dark: { bg: '#18181b', surface: '#27272a', text: '#fafafa', accent: '#9662ff' },
  images: {
    light: { loading: 'https://cdn.example.com/yodl-loading-light.mp4' },
    dark: { loading: 'https://cdn.example.com/yodl-loading-dark.mp4' },
  },
};

<YodlProvider
  sdk={sdk}
  provider={provider}
  address={address}
  chainId={chainId}
  theme={brandTheme}
  themeMode="system"
>
  <YodlPayment qrData={qrData} />
</YodlProvider>;

Modes

themeMode is 'system' (follow the device), 'light', or 'dark'. The two props combine as:

| theme | themeMode | Result | | --- | --- | --- | | Omitted | Omitted | Hosted UI keeps its own appearance | | Set | Omitted | Your palette, following the device | | Omitted | Set | Stock Yodl palette, pinned to that mode | | Set | Set | Your palette, pinned to that mode |

Defaults and overrides

  • theme / themeMode on <YodlProvider> are defaults for every hosted component under it.
  • The same props on <YodlSignup>, <YodlPayment>, and <YodlDashboard> override the provider per prop — set one and the other still inherits.
  • Remove theme and themeMode from both the component and its provider to hand appearance control back to the hosted UI.
// Provider follows the device; this one screen pins dark but keeps the palette.
<YodlProvider sdk={sdk} provider={provider} address={address} chainId={chainId} theme={brandTheme}>
  <YodlPayment qrData={qrData} themeMode="dark" />
</YodlProvider>;

Tokens

name is an optional label (≤64 chars). Set colors per mode under light and dark; omitted tokens keep their stock value. The 21 keys:

  • Core: bg surface surfaceSecondary text textSecondary textMuted border success error warning accent
  • Fine-tune (derived from core when omitted): bgDeep surfaceHover skeleton accentStrong accentDeep
  • Brand: brandSurface brandSheet brandText brandButton brandButtonText

Light accents are handled for you: icons drawn on an accent fill use white by default, and when white would drop below the 3:1 non-text contrast floor — a lime accent tests at 1.7:1 — near-black ink is substituted automatically. An accent white already reads on is left untouched. Where the accent is used as text it is mixed 45% toward text for the same reason.

Set brandSurface (or brandSheet) alone and readable text/button colors are picked from its luminance — a light brand surface gets near-black text and a dark button.

Shape and typography

font, radiusControl and radiusSurface are top-level keys, not palette keys — none is a color and none varies by mode.

const brandTheme: YodlTheme = {
  font: 'geist',
  radiusControl: '12px',
  light: { bg: '#ffffff' },
};

font is one of 'inter' (stock), 'geist', 'mono', or 'system'. It is a fixed list rather than a family name you supply, because a font bundled in your app is not visible to the WebView that hosts the Yodl UI — naming it would silently fall back to Inter and look like a bug. Ask us if you need a face that isn't listed.

radiusControl is the corner radius of every button, input and badge; stock is 9999px, a full pill. Cards, list rows and sheets follow it in both directions — sharper or rounder than stock — with the rest of the surface scale derived proportionally:

| radiusControl | Buttons | Cards | Sheets | | --- | --- | --- | --- | | '0' | square | 0 | 0 | | '12px' | 12px | 12px | 18px | | '32px' | 32px | 32px | 48px | | omitted (9999px) | pill | 16px | 24px |

The exception is a pill-scale value — 100px and up, or 50% and up (a smaller percentage like '6%' is an ordinary radius and surfaces follow it): that asks for a shape rather than a size, so buttons go pill while surfaces stay at their stock radius instead of becoming lozenges. Set radiusSurface when you want containers to differ from your buttons — pill buttons with square cards, or the very round cards a pill radiusControl won't produce on its own.

Circular elements — avatars, token icons, sheet grab handles, status rings — stay circular by design and follow neither key.

Validation

  • Colors must be #rgb, #rgba, #rrggbb, #rrggbbaa, rgb(…), or rgba(…).
  • font must be one of the four listed values; anything else rejects the payload.
  • radiusControl / radiusSurface must be a plain CSS length — a bare 0, or a number of up to 4 digits with px, rem, em, or % (.5rem is as valid as 0.5rem). A unitless number other than zero ('4' rather than '4px') is not a CSS length and is rejected, as are expressions such as calc(…) and var(…).
  • A single invalid key rejects the whole theme, so the hosted UI falls back to stock rather than applying a half-valid palette. Validate with safeParseYodlTheme() before sending — it returns the offending key by name.
  • Images go under images.light / images.dark, each with payBackground, loading, and processing slots. loading and processing are one visual slot: the hosted UI has a single splash state and renders loading, falling back to processing only when loading is unset — setting loading alone covers everything, and there is no need to set both. A video URL (.mp4, .m4v, .webm, .mov) plays as a looping muted video; anything else renders as an image, so every browser-supported image format works — including animated WebP, APNG, animated AVIF, and GIF. .ogv is excluded on purpose — iOS WebViews cannot decode Ogg/Theora, so it would blank the splash there.
  • The loading splash renders above the stepped caption, in normal flow where the stock Yodl mark sits — not over the whole viewport. The media keeps its own aspect ratio and is scaled proportionally within maximum dimensions; it is not placed into a container of a set size. The maxima are 16rem wide, no more than 70% of the viewport width, and no more than 40% of its height. Width is applied first and the height follows from the asset's own ratio, so a wide asset occupies a shorter band than a square one and the vertical space taken varies by shape. Source pixel dimensions cannot bypass the maxima: a 4000px-wide asset is scaled down to the same width as a 400px-wide one of the same shape, only sharper. The fit is contain, so the media is only ever scaled down to stay within the maxima — never cropped to fill the viewport the way a background image would be — and the whole asset stays visible. Images and videos behave identically. Author roughly square (around 512×512) for predictable scaling, keep important detail away from the edges, and use payBackground for a full-screen backdrop.
  • Image URLs must be https://, at most 2048 characters, with no whitespace, quotes, or backslashes.
  • An invalid theme or themeMode never crashes — it falls back to stock Yodl and logs the error in __DEV__.

Live updates

Changing theme / themeMode on a mounted component applies over the bridge without reloading — the active flow keeps its state.

Compatibility. yodlUiBaseUrl must point at a current hosted UI that supports correlated readiness. Against an older build the theme is never delivered, and the WebView is only revealed by the 8 s ceiling — showing stock Yodl colors rather than yours. See Loading and readiness and Troubleshooting.

Full example

sdk.ts — create the instance once, app-wide:

import { createYodlSdk } from '@yodlpay/react-native';

export const sdk = createYodlSdk({
  integrationAddress: '0x000000000000000000000000000000000000dEaD',
  // yodlUiBaseUrl defaults to https://sdk-webview.yodl.me
  supportedChainIds: [1, 42161, 8453, 137, 10, 56, 100],
});

PaymentScreen.tsx — scan → pay → back, plus the dashboard:

import { useState } from 'react';
import { Button, Text, View } from 'react-native';
import { isPaymentQr, YodlProvider, YodlPayment, YodlDashboard } from '@yodlpay/react-native';
import { sdk } from './sdk';
import { useWallet } from './wallet'; // your wallet SDK (Privy, WalletConnect, …)
import { QrScanner } from './QrScanner'; // your camera component, e.g. expo-camera

type Screen = 'scan' | 'pay' | 'dashboard';

export function PaymentScreen() {
  // `provider` is your EIP-1193 wallet. If it already supports EIP-5792 natively,
  // pass it as-is; if not, wrap it with withYodl7702(...) for batching/paymasters (see below).
  // `address`/`chainId` track the wallet.
  const { provider, address, chainId, isConnected } = useWallet();
  const [screen, setScreen] = useState<Screen>('scan');
  const [qrData, setQrData] = useState<string | null>(null);

  if (!isConnected) {
    return <Text>Connect a wallet to continue.</Text>;
  }

  function handleScan(scanned: string) {
    // Guard before rendering — no SDK instance needed for the check.
    if (!isPaymentQr(scanned)) return; // not a Yodl QR: keep scanning
    setQrData(scanned);
    setScreen('pay');
  }

  return (
    <YodlProvider sdk={sdk} provider={provider} address={address} chainId={chainId}>
      {screen === 'scan' && (
        <View style={{ flex: 1, gap: 12 }}>
          <QrScanner onScan={handleScan} />
          <Button title="Open dashboard" onPress={() => setScreen('dashboard')} />
        </View>
      )}

      {screen === 'pay' && qrData && (
        <YodlPayment
          qrData={qrData}
          onTransactionSent={(txHash) => console.log('submitted:', txHash)}
          onPaymentDetails={(details) => {
            // 'submitted' | 'processing' | 'success' | 'failure'
            if (details.status === 'success') setScreen('scan'); // done → scan again
          }}
          onError={(err) => {
            console.error(err);
            setScreen('scan');
          }}
        />
      )}

      {screen === 'dashboard' && <YodlDashboard onError={console.error} />}
    </YodlProvider>
  );
}

Optional EIP-7702 fallback

Native wallet support is always preferred — if your wallet handles EIP-5792 (wallet_sendCalls) itself, pass it straight through and skip this section. @yodlpay/react-native forwards those calls natively whenever the wallet supports them. The add-on below is purely a fallback for wallets that don't.

Add @yodlpay/react-native-eip-7702-provider only when the wallet lacks the atomic EIP-5792 wallet_sendCalls support your flow requires, can sign EIP-7702 authorizations, and you want Yodl to provide that fallback path. The add-on wraps the same provider and advertises atomic batching (atomic: ready) plus paymaster support (paymasterService) through wallet_getCapabilities — so batched, gas-sponsored calls work even on wallets without native EIP-5792, while native wallet_sendCalls still wins when the wallet supports it.

import { signAuthorizationViaProvider, withYodl7702 } from '@yodlpay/react-native-eip-7702-provider';
import { YodlProvider, YodlPayment } from '@yodlpay/react-native';

// Bring your own RPC + bundler — the add-on ships no defaults, so 7702 traffic
// runs through your endpoints. List only the chains you can actually serve
// (independent of the SDK's `supportedChainIds`).
const provider = withYodl7702(walletProvider, {
  chains: [
    {
      id: 8453,
      rpcUrl: 'https://your-rpc.example/8453',
      bundlerUrl: 'https://api.pimlico.io/v2/8453/rpc?apikey=YOUR_KEY',
    },
  ],
  // Required: you price each fallback UserOperation (the add-on stays bundler-
  // agnostic). See the add-on's "Gas pricing" section for caching guidance.
  estimateUserOperationFees: async (chainId) => getYourBundlerGasPrice(chainId),
  // Required: you sign the EIP-7702 authorization (no universal wallet method).
  // The add-on ships `signAuthorizationViaProvider` to try for EIP-1193 wallets.
  signAuthorization: signAuthorizationViaProvider(walletProvider),
});

<YodlProvider sdk={sdk} provider={provider} address={address} chainId={chainId}>
  <YodlPayment qrData={qrData} />
</YodlProvider>;

Base SDK users can skip this entirely. Without it, a wallet that lacks native EIP-5792 wallet_sendCalls simply can't batch — the base SDK forwards the request and the wallet's own response (it performs no sequential fallback of its own).

API Reference

createYodlSdk(config)

Returns a YodlSdk instance.

| Option | Type | Required | Default | Description | | ------------------- | ----------------------------- | -------- | ----------------------------- | -------------------------------------------------------------------------- | | integrationAddress | string | Yes | — | Your integration wallet address. Identifies you to Yodl and attributes payments on chain | | supportedChainIds | number[] | Yes | — | Chain IDs the wallet can switch to | | yodlUiBaseUrl | string | No | https://sdk-webview.yodl.me | Base URL for the Yodl UI |

integrationAddress must be a non-empty 0x address. The SDK sends it with package metadata on Yodl-owned requests:

X-Yodl-Integration-Id: 0x000000000000000000000000000000000000dEaD
X-Yodl-SDK-Name: @yodlpay/react-native
X-Yodl-SDK-Version: <installed package version>

Instance members:

  • sdk.config — the resolved config (read-only).

isPaymentQr(qrData)

Standalone helper — string | string[]boolean. Returns true if any entry is a Yodl-supported payment QR. Pure: it takes no SDK instance, so you can guard a render before <YodlProvider> is mounted.

import { isPaymentQr } from '@yodlpay/react-native';

if (!isPaymentQr(scannedQr)) return null;

<YodlProvider />

Supplies the SDK instance and wallet state to the components. Render it once, high in your tree.

| Prop | Type | Required | Description | | ---------- | ----------------- | -------- | ------------------------------------- | | sdk | YodlSdk | Yes | Instance from createYodlSdk() | | provider | EIP1193Provider | Yes | EIP-1193 provider from any wallet SDK (@yodlpay/sdk-core's type: request only, events not required; withYodl7702 accepts and returns the same type, so no cast is needed). Prefer the wallet's native EIP-5792 support; only if it lacks it, wrap with withYodl7702(...) for the EIP-7702 fallback | | address | string | Yes | Connected wallet address | | chainId | number | Yes | Chain ID the wallet is currently on | | theme | YodlTheme | No | Default custom hosted-UI palette; all fields are optional | | themeMode | 'system' \| 'light' \| 'dark' | No | Default host-controlled mode |

address and chainId are the wallet's live account and chain — pass them straight from your wallet SDK's hooks (e.g. useWallets(), useAccount()), not as fixed values. They tell the embedded Yodl UI who is connected and on which chain, and the SDK pushes accountsChanged / chainChanged into the UI whenever they change — so keep them updated when the user switches account or network. (The provider is used only as the RPC/signing transport.)

theme and themeMode are also available on <YodlSignup>, <YodlPayment>, and <YodlDashboard> as per-component overrides — along with backgroundColor and style. See Shared visual props and Hosted UI theming.

Shared visual props

<YodlSignup>, <YodlPayment>, and <YodlDashboard> all accept the same visual props. They are listed here once rather than repeated in each component's table below.

| Prop | Type | Required | Description | | ----------------- | ------------------------------- | -------- | --------------------------------------------------------------------------------- | | theme | YodlTheme | No | Palette override; inherits the provider theme when omitted | | themeMode | 'system' \| 'light' \| 'dark' | No | Mode override; inherits the provider themeMode when omitted | | backgroundColor | string | No | Color behind the WebView before first paint; overrides the theme background | | renderLoading | () => ReactElement \| null | No | Replaces the loading overlay's contents; null suppresses the overlay entirely | | onReady | () => void | No | Called once per mount, the first time the hosted UI is revealed | | style | ViewStyle | No | Container style |

See Loading and readiness for renderLoading and onReady.

<YodlSignup />

Renders the hosted signup flow. Use this as a full-screen component inside <YodlProvider>. Also accepts all shared visual props.

| Prop | Type | Required | Description | | ---------------- | ---------------------------------------------------------------------------- | -------- | ----------------------------------------------------- | | onStatusChange | (status: 'checking' \| 'signed_out' \| 'signing_up' \| 'signed_up') => void | No | Called when the hosted flow reports signup status | | onSignedUp | () => void | No | Called when signup completes | | onDismiss | () => void | No | Called when the hosted success screen is dismissed | | onError | (error: Error) => void | No | Called on SIWE, registration, verification, or RPC errors |

<YodlPayment />

Renders the payment flow for a scanned QR code. Renders nothing if qrData is empty or unsupported. Also accepts all shared visual props.

| Prop | Type | Required | Description | | ------------------- | --------------------------------------- | -------- | -------------------------------------------------- | | qrData | string | Yes | Raw scanned QR data string | | navigateOnSubmit | 'always' \| 'never' | No | Whether the hosted UI moves to its own status screen once the transaction is submitted. Defaults to 'always' | | onTransactionSent | (txHash: string) => void | No | Called after a transaction is sent | | onPaymentDetails | (details: YodlPaymentDetails) => void | No | Called with payment lifecycle details from the UI | | onError | (error: Error) => void | No | Called on RPC / payment errors |

navigateOnSubmit="never" parks the hosted UI on a terminal "sent" frame so your app owns the transition after signing — drive it from onTransactionSent or onPaymentDetails. Note the moment: the hosted UI moves when the transaction is submitted, not when it settles. It keeps polling behind that frame, so onPaymentDetails still reports 'processing' and then 'success' or 'failure' — you get a 'submitted' message first, then the same stream 'always' produces. A host that ignores both callbacks leaves the user on that frame, which is why the default is 'always': omit the prop and the URL, and therefore the behavior, is unchanged. The prop feeds the WebView's URL and the WebView is keyed by it, so changing it on a mounted component reloads the hosted UI — pass a constant.

<YodlDashboard />

Renders the full Yodl account dashboard. Also accepts all shared visual props.

| Prop | Type | Required | Description | | ------------------- | --------------------------------------- | -------- | -------------------------------------------------- | | onTransactionSent | (txHash: string) => void | No | Called after a transaction is sent | | onPaymentDetails | (details: YodlPaymentDetails) => void | No | Called with payment lifecycle details from the UI | | onError | (error: Error) => void | No | Called on RPC errors |

Loading and readiness

Until the hosted UI reports it has painted, the SDK covers the WebView with an overlay filled with backgroundColor and centered on an ActivityIndicator. That exists so the user never sees react-native-webview's hardcoded-white loading view or the document's default white before its CSS lands.

renderLoading and onReady are two ways to fix the two-loader cascade you get when your app's loading treatment and Yodl's don't match. They are alternatives — pick one. Using both is how you get two loaders.

Recipe A — Yodl's overlay, your loader. The SDK still owns the cover; you supply its contents. Nothing in your tree needs to know when the hosted UI is up, so onReady has no job here.

<YodlPayment
  qrData={qrData}
  backgroundColor="#18181b"
  renderLoading={() => <MyBrandLoader />}
/>
  • Returning an element renders it centered on backgroundColor. The wrapper stays, so the no-white-flash guarantee is preserved.
  • Returning null suppresses the overlay entirely — no view is mounted, so nothing is left over the WebView intercepting touches. backgroundColor is still painted behind the WebView.
  • It is called on every render while loading, so keep it cheap.

Recipe B — your own screen covers it. Suppress Yodl's overlay so there is only one, and let onReady tell you when to take yours down.

{!ready && <MyLoadingScreen />}
<YodlPayment
  qrData={qrData}
  backgroundColor="#18181b"
  renderLoading={() => null}
  onReady={() => setReady(true)}
/>

onReady is the only way to do this: renderLoading is called during render, so you cannot setState from it.

onReady fires once per mount, the first time the UI is revealed. It does not fire again. The hosted document re-posts readiness when it reloads, but the overlay has already lifted and does not come back, so there is nothing to re-announce — it lifts once and stays lifted.

The reveal has a ceiling. If nothing is heard from the hosted UI within 8 seconds, the SDK reveals anyway and fires onReady. It is a last resort for a page that is genuinely dead, not a routine path: a reveal on that timer arrives without the readiness handshake, so the hosted UI has not received your theme or the SDK's capability announcement. A current hosted build runs its own, shorter ceiling and always reports readiness first, so in practice you only reach 8 seconds when the UI never loaded at all. The ceiling reveals — it does not report an error. Use onError and your own network handling for that.

Payment details events

onPaymentDetails is optional and additive — onTransactionSent(txHash) and onError(error) behavior is unchanged.

The Yodl UI emits generalized payment lifecycle details once it has payment status. The SDK forwards them to onPaymentDetails. Normalized fields cover transaction hash, chain, lifecycle status, recipient, invoice, token amounts, refund state, and rewards when available; the original payload may be included under raw. The SDK does not fetch transaction details itself — it only bridges UI messages.

Security

<YodlSignup>, <YodlPayment>, and <YodlDashboard> inject an EIP-1193 provider bound to the user's wallet, so the underlying Yodl UI is locked to the configured Yodl origin and off-origin navigation is blocked (onShouldStartLoadWithRequest). This prevents origin spoofing and mid-request navigation that could intercept signed responses. The injected provider also enforces a 30 s pending-request timeout.

The bridge forwards EIP-5792 (wallet_sendCalls, wallet_getCallsStatus, wallet_showCallsStatus, wallet_getCapabilities) and all signing methods straight to the supplied wallet provider — it owns no smart-account stack and makes no RPC calls of its own (the embedded UI reads chain state through its own provider). For wallets without native EIP-5792, add @yodlpay/react-native-eip-7702-provider for atomic batching.

Troubleshooting

| Symptom | Cause & fix | | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | Can't find variable: BigInt | Hermes is disabled. Re-enable Hermes or add a BigInt polyfill. See Platform Notes. | | App crashes / blank screen in Expo Go | Native modules aren't available in Expo Go. Use a development build. | | Yodl components must be rendered inside <YodlProvider> | A component is mounted outside the provider. Wrap it in <YodlProvider>. | | Payment screen renders blank | qrData isn't a Yodl payment QR. Guard with isPaymentQr(qrData) before rendering <YodlPayment>. | | webview-action providers are not supported yet | The scanned QR maps to a provider whose hosted webview flow isn't supported yet. Catch it (e.g. via onError or an error boundary) and prompt a retry. | | Overlay lifts after ~8 s onto an unthemed or blank page | That's the reveal ceiling firing because the hosted UI never reported readiness — usually an unreachable yodlUiBaseUrl, a build that predates correlated readiness, or no network. A ceiling reveal carries no theme handshake, so a stale build also shows stock Yodl colors. Point yodlUiBaseUrl at a current hosted build. See Loading and readiness. | | Nothing is visible over the WebView while it loads | renderLoading returned null, which suppresses the overlay by design. Return an element instead, or omit the prop for the default indicator. |

License

BUSL-1.1 © Yodl