Skip to main content
@buckspay/react-native brings the SDK to iOS and Android. The core pipeline (auth-entry build, fee-bump, prepare -> sign -> send) is platform-agnostic. This package adds three things on top:
  • nativePasskey - a WebAuthnLike backed by react-native-passkey that drives the device secure enclave instead of navigator.credentials. The private key never leaves the device.
  • Secure-storage adapters - memorySecureStore, expoSecureStore, keychainSecureStore for persisting session blobs and credential IDs.
  • Hermes polyfills - installed as a side-effect on import, before @stellar/stellar-sdk is used.
BuckspayProvider, useWallet, and useStellarPay are re-exported by reference from @buckspay/react - the same store, same status machine, zero fork. Your screen logic is identical to a web component; only the host primitives (<View> / <Text> / <Pressable>) and the signer differ.
1
Install the package and peer dependencies
2
Install @buckspay/react-native plus the native peers your app requires:
3
pnpm add @buckspay/react-native @buckspay/react @buckspay/core @buckspay/accounts @buckspay/relayer

# Native peers - install in the app, not in the SDK
pnpm add react-native react-native-passkey react-native-get-random-values
4
The peer modules (react-native-passkey, react-native-get-random-values) are optional: each is lazily loaded only if you use the feature that depends on it, so apps that skip nativePasskey never pull in the native module.
5
Build the config with nativePasskey
6
The config shape is identical to web. The only mobile-specific change is the signer:
7
import { nativePasskey } from "@buckspay/react-native";
import { ozContractAccount } from "@buckspay/accounts/oz-contract";
import { buckspayFacilitator } from "@buckspay/relayer/buckspay-facilitator";
import type { BuckspayConfig } from "@buckspay/core";

const config: BuckspayConfig = {
  network: "testnet",
  account: ozContractAccount({ network: "testnet", sponsorAddress: "GA...SPONSOR" }),
  signer: nativePasskey({ rpId: "app.example.com", rpName: "My App" }),
  relayer: buckspayFacilitator({ url: "/api/gasless", network: "testnet" }),
  gas: { mode: "sponsored" }
};
8
nativePasskey accepts:
9
OptionTypeRequiredDescriptionrpIdstringYesRelying-party domain, bound at registrationrpNamestringNoHuman-readable name shown in the authenticator prompt
10
Wrap the tree with BuckspayProvider
11
Exactly the same as web - pass sim so pay() can simulate:
12
import { BuckspayProvider } from "@buckspay/react-native";
import { createRpcSimContext } from "@buckspay/core";

export function App() {
  return (
    <BuckspayProvider config={config} sim={createRpcSimContext("https://soroban-testnet.stellar.org")}>
      {/* your screens */}
    </BuckspayProvider>
  );
}
13
Connect and pay - identical to web
14
useWallet and useStellarPay work exactly as on web. Replace DOM elements with React Native primitives:
15
import { useWallet, useStellarPay } from "@buckspay/react-native";
import { View, Text, Pressable } from "react-native";

function PayScreen() {
  const { wallet, connect, status } = useWallet();
  const { pay, status: payStatus, receipt } = useStellarPay();

  if (!wallet) {
    return <Pressable onPress={() => void connect()} disabled={status === "connecting"}>
      <Text>{status === "connecting" ? "Connecting..." : "Connect"}</Text>
    </Pressable>;
  }
  return (
    <View>
      <Pressable onPress={() => void pay([/* calls */])} disabled={payStatus === "relaying"}>
        <Text>Pay 1.50 USDC (free)</Text>
      </Pressable>
      {receipt && <Text>Settled: {receipt.transferTx}</Text>}
    </View>
  );
}

Secure storage

Session blobs and credential IDs are persisted via a SecureStore port. Three adapters are exported from @buckspay/react-native:
AdapterUse
memorySecureStore()Tests and ephemeral in-memory sessions
expoSecureStore()Expo managed workflow (expo-secure-store)
keychainSecureStore({ service })Bare React Native (react-native-keychain)
Each adapter lazily imports its peer, so apps that do not use a given adapter never pull the native module into their bundle.

Full example

// Recipe 13 - REACT NATIVE. The mobile binding re-exports the SAME @buckspay/react hooks and only
// swaps the signer (native passkey / secure enclave), storage, and Hermes polyfills. The screen
// logic is identical to web (recipe 05); only the host components (<View>/<Text>) and the signer
// differ. This example pins the @buckspay/react-native surface; the View/Text layer is the app's,
// so it is omitted here (it would pull react-native intrinsics) - fragments stand in for it.
import {
  BuckspayProvider,
  useWallet,
  useStellarPay,
  nativePasskey,
  memorySecureStore,
  type SecureStore
} from "@buckspay/react-native";
import { ozContractAccount } from "@buckspay/accounts/oz-contract";
import { buckspayFacilitator } from "@buckspay/relayer/buckspay-facilitator";
import { createRpcSimContext, type BuckspayConfig, type Call } from "@buckspay/core";

const SPONSOR_G = "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN";
const USDC_SAC = "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA";
const MERCHANT = "GA6HCMBLTZS5VYYBCATRBR5VBZJEH5C2OON6XQGB3RNYDDAQ7JZ65YQH";

// Same config shape as web - the ONLY mobile-specific line is the native passkey signer.
export const mobileConfig: BuckspayConfig = {
  network: "testnet",
  account: ozContractAccount({ network: "testnet", sponsorAddress: SPONSOR_G }),
  signer: nativePasskey({ rpId: "app.buckspay.dev", rpName: "buckspay" }),
  relayer: buckspayFacilitator({ url: "/api/gasless", network: "testnet" }),
  gas: { mode: "sponsored" }
};

// Session blobs / credential ids persist via a SecureStore port (keychain/expo on device).
export const store: SecureStore = memorySecureStore();

// The hooks are byte-identical to @buckspay/react - no fork (recipe 05).
export function PayScreen() {
  const { wallet, address, connect } = useWallet();
  const { pay, status, receipt } = useStellarPay();

  if (!wallet) {
    // On device this is a <Pressable onPress={connect}> inside a <View>.
    return <>{"Connect"}</>;
  }
  const call: Call = {
    contract: USDC_SAC,
    fn: "transfer",
    args: [] // built from address -> MERCHANT in the real screen; see recipe 05
  };
  void address;
  void (() => pay([call]));
  return <>{status === "success" && receipt ? `settled: ${receipt.transferTx}` : "Pay 1.50 USDC (free)"}</>;
}

// Wrap the tree once, exactly like web - pass `sim` so useStellarPay().pay() can simulate.
export function App() {
  return (
    <BuckspayProvider config={mobileConfig} sim={createRpcSimContext("https://soroban-testnet.stellar.org")}>
      <PayScreen />
    </BuckspayProvider>
  );
}

Next

Native passkey signer

How nativePasskey delegates to the shared passkey crypto pipeline.

Web - @buckspay/react

The web counterpart - same hooks, browser authenticator transport.