Skip to main content
@buckspay/react wraps the core client in idiomatic hooks. Wrap your component tree once with BuckspayProvider; every component inside can then connect a wallet and send gasless payments with two hooks.
1
Install
2
pnpm add @buckspay/react @buckspay/core @buckspay/accounts @buckspay/signers @buckspay/relayer
3
Wrap the tree with BuckspayProvider
4
Place BuckspayProvider at the root of the tree (or the subtree that needs payment). Pass a BuckspayConfig and a simulator context so useStellarPay().pay() can simulate the transaction before signing.
5
import { BuckspayProvider } from "@buckspay/react";
import { createBuckspayClient, createRpcSimContext } from "@buckspay/core";
import { classicAccount } from "@buckspay/accounts/classic";
import { walletsKit } from "@buckspay/signers/wallets-kit";
import { buckspayFacilitator } from "@buckspay/relayer/buckspay-facilitator";

const config = {
  network: "testnet",
  account: classicAccount(),
  signer: walletsKit({ network: "testnet" }),
  relayer: buckspayFacilitator({ url: "/api/gasless", network: "testnet" }),
  gas: { mode: "sponsored" as const }
};

export function App() {
  return (
    <BuckspayProvider config={config} sim={createRpcSimContext("https://soroban-testnet.stellar.org")}>
      {/* your app */}
    </BuckspayProvider>
  );
}
6
Pass sim (a createRpcSimContext(rpcUrl)) so pay() can record authorization entries against the Soroban RPC. Omit it only in connect-only apps that never call pay().
7
Connect with useWallet
8
useWallet() returns { wallet, address, connect, status, error }. Gate the payment UI on wallet !== null:
9
import { useWallet } from "@buckspay/react";

function ConnectButton() {
  const { wallet, connect, status } = useWallet();
  if (wallet) return null;
  return (
    <button onClick={() => void connect()} disabled={status === "connecting"} aria-live="polite">
      {status === "connecting" ? "Connecting..." : "Connect wallet"}
    </button>
  );
}
10
Pay with useStellarPay
11
useStellarPay() returns { pay, prepare, sign, status, receipt, error, reset }.
12
  • pay(calls) - one-shot: prepare -> sign -> send in a single call.
  • prepare(calls) / sign(intent) - split flow for apps that validate the signed intent in a BFF before sending. See Facilitator and BFF.
  • reset() - returns the store to idle so the user can retry.
  • 13
    Status machine
    14
    status follows the path:
    15
    idle -> connecting -> ready -> signing -> relaying -> success | error
    
    16
    Render error.code (a BuckspayErrorCode) for user-facing messages, and announce state changes with aria-live for accessibility.

    Full example

    The component below is the compiled, type-checked reference implementation.
    // React hooks - full pay component. Compiles against @types/react 19 + the real hook types.
    import { useMemo, type CSSProperties } from "react";
    import { Address, nativeToScVal } from "@stellar/stellar-sdk";
    import { useWallet, useStellarPay } from "@buckspay/react";
    import type { Call } from "@buckspay/core";
    
    const USDC_SAC = "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA";
    const MERCHANT = "GA6HCMBLTZS5VYYBCATRBR5VBZJEH5C2OON6XQGB3RNYDDAQ7JZ65YQH";
    const btn: CSSProperties = { padding: "0.75rem 1.25rem", borderRadius: 8, cursor: "pointer" };
    
    export function PayButton() {
      const { wallet, address, connect, status: wStatus, error: wErr } = useWallet();
      const { pay, status, receipt, error } = useStellarPay();
    
      const transferCall = useMemo<Call | null>(() => {
        if (!address) return null;
        return {
          contract: USDC_SAC,
          fn: "transfer",
          args: [new Address(address).toScVal(), new Address(MERCHANT).toScVal(), nativeToScVal(15000000n, { type: "i128" })]
        };
      }, [address]);
    
      const busy = status === "signing" || status === "relaying";
      const shownError = error ?? wErr;
    
      if (!wallet) {
        return (
          <button onClick={() => void connect()} style={btn} disabled={wStatus === "connecting"} aria-live="polite">
            {wStatus === "connecting" ? "Connecting..." : "Connect"}
          </button>
        );
      }
      return (
        <div>
          <button onClick={() => transferCall && void pay([transferCall])} style={btn} disabled={busy || !transferCall}>
            {busy ? "Paying..." : "Pay 1.50 USDC (free)"}
          </button>
          {receipt && <p aria-live="polite">settled: {receipt.transferTx}</p>}
          {shownError && (
            <p role="alert">
              {shownError.code}: {shownError.message}
            </p>
          )}
        </div>
      );
    }
    

    Next

    React Native

    The same hooks on iOS and Android, with nativePasskey and secure-storage adapters.

    Next.js BFF

    Route factories that keep the API key server-side.