ows-react-native

March 25, 2026 ยท View on GitHub

The first React Native library for the Open Wallet Standard (OWS) โ€” policy-gated autonomous payments on Solana using USDC, with Stripe MPP / x402 HTTP 402 support.

npm version npm downloads License: MIT Built for Solana TypeScript

๐Ÿ“ฆ npmjs.com/package/ows-react-native


Why this exists

The Open Wallet Standard (OWS) defines how wallets and apps should communicate โ€” but there is no official React Native implementation. This library fills that gap.

It's designed specifically for AI agents that need to make autonomous micro-payments (e.g. paying for API access, model inference, data feeds) without human intervention, while keeping spending under strict policy controls.

Key problems this solves:

ProblemThis library's solution
RN apps can't safely hold private keysBackend vault โ€” keys never leave the server
Agents need to pay HTTP 402 endpointsparseMppChallenge() + payMppChallenge()
No spending guardrails for autonomous agentsOWS policy engine (per-tx limit, daily cap, allowlist)
No standard RN wallet interfaceHooks-first API matching the OWS spec
Large payments need human approvalBiometric gate via expo-local-authentication

Features

  • OwsProvider โ€” React context with backend URL, active wallet, policies, and tx history
  • useOwsWallet() โ€” Create wallets, load from vault, get addresses and balances
  • usePayWithOws() โ€” Parse + pay HTTP 402 / x402 / MPP challenges on Solana
  • usePolicy() โ€” Set per-tx limits, daily caps, recipient allowlists, pause/resume
  • <TransactionHistory /> โ€” Drop-in component for payment history
  • parseMppChallenge() โ€” Parses x402 JSON, Stripe MPP headers, and X-Payment-Required headers
  • Biometric approval โ€” Face ID / Touch ID gate for payments above your threshold
  • Full TypeScript โ€” Complete type coverage, zero any in public API
  • New Architecture ready โ€” Compatible with React Native's Bridgeless mode

Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚         React Native App (Oscar)        โ”‚
โ”‚                                         โ”‚
โ”‚  useOwsWallet()   usePayWithOws()       โ”‚
โ”‚  usePolicy()      <TransactionHistory/> โ”‚
โ”‚                                         โ”‚
โ”‚  OwsProvider  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                โ”‚  HTTP (localhost in dev, HTTPS in prod)
                โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚        OWS Backend (Express)            โ”‚
โ”‚                                         โ”‚
โ”‚  POST /create-wallet  โ† generates keypair
โ”‚  POST /pay-mpp        โ† enforces policy โ”‚
โ”‚  POST /pay-solana-usdc                  โ”‚
โ”‚  GET  /get-balance/:id                  โ”‚
โ”‚  POST /update-policy/:id                โ”‚
โ”‚                                         โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚  .ows/ vault (keys never leave) โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                โ”‚  @solana/web3.js + @solana/spl-token
                โ–ผ
        Solana devnet / mainnet
        USDC SPL Token transfers

Security model: Private keys are generated and stored exclusively in the backend vault (.ows/). The React Native app only ever receives public addresses and transaction signatures. The backend re-validates all OWS policies before signing any transaction โ€” the client-side checks are a UX convenience only.


Installation

npm install ows-react-native
# If using Expo, also install native dependencies:
npx expo install expo-local-authentication expo-secure-store

Backend setup

The OWS backend is a standalone Express server that manages your wallet vault, enforces policies, and signs Solana transactions. Private keys never leave it.

# Clone the repo and start the backend
git clone https://github.com/rkmonarch/ows-react-native
cd ows-react-native/backend
npm install
npm run dev
# โ†’ Listening on http://localhost:3001

Set PORT and SOLANA_NETWORK env vars to configure. Defaults to port 3001 on devnet.


Quick Start

1. Wrap your app

import { OwsProvider } from 'ows-react-native';

export default function App() {
  return (
    // In production: use HTTPS and your deployed backend URL
    <OwsProvider backendUrl="http://localhost:3001">
      <YourApp />
    </OwsProvider>
  );
}

2. Create a wallet

import { useOwsWallet } from 'ows-react-native';

function SetupScreen() {
  const { wallet, balance, createWallet } = useOwsWallet('solana');

  return (
    <>
      <Button title="Create Wallet" onPress={() => createWallet('My Agent')} />
      {wallet && <Text>Address: {wallet.address}</Text>}
      {balance && <Text>USDC: ${balance.usdc.toFixed(2)}</Text>}
    </>
  );
}

3. Pay an x402 / MPP challenge (agent payment)

import { usePayWithOws, parseMppChallenge } from 'ows-react-native';

function AgentScreen() {
  const { payMppChallenge, isLoading } = usePayWithOws();

  const fetchProtectedResource = async () => {
    const res = await fetch('https://api.example.com/research-results');

    if (res.status === 402) {
      // Parse the 402 challenge (x402 JSON or MPP header)
      const body = await res.json();
      const headers = Object.fromEntries(res.headers.entries());
      const challenge = parseMppChallenge(body, headers);

      // Pay it โ€” backend checks policy, signs, sends USDC on Solana
      const result = await payMppChallenge(challenge);
      console.log('Paid!', result.explorerUrl);

      // Retry the request with payment proof
      return fetch('https://api.example.com/research-results', {
        headers: { 'X-Payment-Signature': result.signature },
      });
    }

    return res;
  };
}

4. Set spend policies

import { usePolicy } from 'ows-react-native';

function PolicyScreen() {
  const { policy, setMaxPerTx, setDailyLimit, setAllowlist, pauseWallet } = usePolicy();

  // Limit the agent to \$1.00 per payment, \$10.00 per day
  await setMaxPerTx(1.00);
  await setDailyLimit(10.00);

  // Restrict to specific recipients (leave empty to allow all)
  await setAllowlist(['RecipientAddress1...', 'RecipientAddress2...']);

  // Emergency stop โ€” blocks all payments immediately
  await pauseWallet();
}

5. Show transaction history

import { TransactionHistory } from 'ows-react-native';

function HistoryScreen() {
  return (
    <TransactionHistory
      limit={20}
      showExplorerLink
      onTransactionPress={(tx) => console.log(tx)}
    />
  );
}

x402 / MPP Challenge Format

This library parses all three common formats of HTTP 402 Payment Required challenges:

{
  "x402Version": 1,
  "accepts": [{
    "scheme": "exact",
    "network": "solana-devnet",
    "maxAmountRequired": "0.10",
    "payTo": "RecipientPubkey...",
    "tokenMint": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
    "memo": "api-access-ref-123"
  }]
}

Format 2 โ€” Stripe MPP JSON body

{
  "scheme": "exact",
  "network": "solana-devnet",
  "amount": "0.10",
  "recipient": "RecipientPubkey...",
  "memo": "stripe-mpp-ref-456"
}

Format 3 โ€” WWW-Authenticate MPP header

WWW-Authenticate: MPP realm="API", amount="0.10", recipient="PubKey...", network="solana-devnet"

API Reference

OwsProvider

<OwsProvider
  backendUrl="http://localhost:3001"  // Required: backend URL
  maxHistorySize={50}                  // Optional: tx history limit (default: 50)
>

useOwsWallet(chain)

const {
  wallet,          // OWSWallet | null โ€” currently active wallet
  balance,         // WalletBalance | null โ€” { sol, usdc, lamports, usdcRaw }
  isLoading,       // boolean
  error,           // string | null
  createWallet,    // (label?: string) => Promise<OWSWallet>
  loadWallet,      // (walletId: string) => Promise<OWSWallet>
  listWallets,     // () => Promise<OWSWallet[]>
  getAddress,      // () => string | null
  getBalance,      // () => Promise<WalletBalance>
  refreshBalance,  // () => Promise<void>
} = useOwsWallet('solana');

usePayWithOws()

const {
  isLoading,        // boolean
  error,            // string | null
  lastPayment,      // PaymentResult | null
  payMppChallenge,  // (challenge: MppChallenge) => Promise<PaymentResult>
  payDirect,        // (params: DirectPayParams) => Promise<PaymentResult>
} = usePayWithOws();

PaymentResult:

{
  signature: string;      // Solana transaction signature
  explorerUrl: string;    // Full Solana Explorer URL
  amountUsdc: number;     // Amount paid
  recipient: string;      // Recipient address
  timestamp: string;      // ISO-8601
  memo?: string;          // Memo attached to transaction
}

usePolicy()

const {
  policy,         // OWSPolicy | null
  isLoading,      // boolean
  error,          // string | null
  setMaxPerTx,    // (amount: number) => Promise<void>
  setDailyLimit,  // (amount: number) => Promise<void>
  setAllowlist,   // (addresses: string[]) => Promise<void>
  pauseWallet,    // () => Promise<void>
  resumeWallet,   // () => Promise<void>
  refreshPolicy,  // () => Promise<void>
} = usePolicy();

OWSPolicy:

{
  maxPerTx: number;                    // Max USDC per transaction
  dailyLimit: number;                  // Rolling 24h cap in USDC
  allowlist: string[];                 // Empty = allow all recipients
  paused: boolean;                     // Kill-switch
  merchantLimits?: Record<string, number>; // Per-address limits
}

parseMppChallenge(body, headers)

import { parseMppChallenge } from 'ows-react-native';

const challenge: MppChallenge = parseMppChallenge(
  responseBody,   // Record<string, unknown> | null
  responseHeaders // Record<string, string>
);
// Throws if format is unrecognised

Backend API

The included Express backend exposes these endpoints:

MethodEndpointDescription
GET/healthServer status + network info
POST/create-walletGenerate new Solana keypair, store in vault
GET/list-walletsList all vault wallets (no secret keys)
GET/get-balance/:idSOL + USDC balance on-chain
GET/get-policy/:idCurrent OWS policy for wallet
POST/update-policy/:idUpdate policy fields
POST/pay-solana-usdcDirect USDC transfer (policy-gated)
POST/pay-mppPay an x402/MPP challenge (policy-gated)
POST/mock-402Dev only โ€” returns a test 402 challenge

Backend environment variables

# backend/.env
PORT=3001
SOLANA_NETWORK=devnet        # or mainnet-beta
VAULT_DIR=./.ows             # path to encrypted key vault

Solana Constants

NetworkUSDC Mint Address
Devnet4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU
MainnetEPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v

Funding Devnet Wallets (Testing)

  1. SOL (for transaction fees): faucet.solana.com
  2. USDC (devnet): spl-token-faucet.com
  3. Verify: explorer.solana.com

End-to-End Test (curl)

# 1. Start the backend
cd backend && npm run dev

# 2. Create a wallet
curl -X POST http://localhost:3001/create-wallet \
  -H "Content-Type: application/json" \
  -d '{"label":"test-agent"}'
# โ†’ { "wallet": { "id": "...", "address": "...", "chain": "solana" } }

# 3. Set policies
curl -X POST http://localhost:3001/update-policy/<WALLET_ID> \
  -H "Content-Type: application/json" \
  -d '{"maxPerTx": 0.50, "dailyLimit": 5.00}'

# 4. Get a mock 402 challenge
curl -X POST http://localhost:3001/mock-402 \
  -H "Content-Type: application/json" \
  -d '{"amount":"0.10"}'
# โ†’ HTTP 402 with x402 JSON body

# 5. Pay the challenge
curl -X POST http://localhost:3001/pay-mpp \
  -H "Content-Type: application/json" \
  -d '{
    "walletId": "<WALLET_ID>",
    "challenge": { "recipient": "<ADDRESS>", "amountFloat": 0.10 }
  }'
# โ†’ { "result": { "signature": "...", "explorerUrl": "..." } }

# 6. View on Explorer
open "https://explorer.solana.com/tx/<SIGNATURE>?cluster=devnet"

Policy enforcement tests (red-team)

# Exceed maxPerTx โ€” should return 403
curl -X POST http://localhost:3001/pay-solana-usdc \
  -d '{"walletId":"<ID>","recipient":"<ADDR>","amountUsdc":100}'
# โ†’ 403 { "message": "Amount \$100.00 exceeds maxPerTx limit of \$0.50" }

# Blocked recipient (allowlist active)
curl -X POST http://localhost:3001/update-policy/<ID> \
  -d '{"allowlist":["<ALLOWED_ADDR>"]}'
curl -X POST http://localhost:3001/pay-solana-usdc \
  -d '{"walletId":"<ID>","recipient":"<OTHER_ADDR>","amountUsdc":0.01}'
# โ†’ 403 { "message": "Recipient ... is not on the allowlist" }

# Paused wallet
curl -X POST http://localhost:3001/update-policy/<ID> -d '{"paused":true}'
curl -X POST http://localhost:3001/pay-solana-usdc \
  -d '{"walletId":"<ID>","recipient":"<ADDR>","amountUsdc":0.01}'
# โ†’ 403 { "message": "Wallet is paused โ€” no payments allowed" }

Security

โš ๏ธ Read before deploying to production

What is safe

  • โœ… Public addresses are safe to share (they're on the blockchain)
  • โœ… Transaction signatures are safe to expose
  • โœ… Policy configs are safe to send over HTTP

What must be protected

  • ๐Ÿ” .ows/ directory โ€” contains private keys. Never commit it. Add to .gitignore.
  • ๐Ÿ” Backend API โ€” in production, add Authorization: Bearer <token> header authentication
  • ๐Ÿ” HTTPS โ€” run the backend behind TLS in production (NGINX, Caddy, etc.)
  • ๐Ÿ” Vault encryption โ€” for production, replace plain JSON files with AWS KMS, HashiCorp Vault, or a TEE/HSM

Policy enforcement

All policies are enforced server-side before any signing. The client-side pre-checks in hooks are UX convenience only โ€” a malicious client cannot bypass them by directly calling the backend without a valid walletId and passing the same policy checks.

Private key isolation

The /create-wallet endpoint returns only { id, address, chain, label, createdAt }. The secretKey field is stripped before the response is sent. No endpoint ever returns a private key.


Roadmap

  • Native module path โ€” TurboModules + iOS Secure Enclave for on-device signing
  • Streaming payments โ€” x402 streaming scheme support
  • Multi-sig policy approval for spends above threshold
  • Persistent transaction history via expo-sqlite
  • Push notifications on payment confirmation
  • EVM support (chain: 'base' | 'ethereum')
  • Mainnet launch checklist and audit

Project Structure

ows-react-native/
โ”œโ”€โ”€ src/                          # Library source (TypeScript)
โ”‚   โ”œโ”€โ”€ index.ts                  # Public API exports
โ”‚   โ”œโ”€โ”€ types.ts                  # All TypeScript types
โ”‚   โ”œโ”€โ”€ components/
โ”‚   โ”‚   โ”œโ”€โ”€ OwsProvider.tsx       # React context + provider
โ”‚   โ”‚   โ””โ”€โ”€ TransactionHistory.tsx
โ”‚   โ”œโ”€โ”€ hooks/
โ”‚   โ”‚   โ”œโ”€โ”€ useOwsWallet.ts       # Wallet lifecycle
โ”‚   โ”‚   โ”œโ”€โ”€ usePayWithOws.ts      # Payment hook
โ”‚   โ”‚   โ””โ”€โ”€ usePolicy.ts          # Policy management
โ”‚   โ””โ”€โ”€ utils/
โ”‚       โ”œโ”€โ”€ mppParser.ts          # x402 / MPP challenge parser
โ”‚       โ””โ”€โ”€ apiClient.ts          # Typed fetch wrapper
โ”œโ”€โ”€ backend/
โ”‚   โ”œโ”€โ”€ server.ts                 # Express server (wallet vault + Solana)
โ”‚   โ””โ”€โ”€ package.json
โ”œโ”€โ”€ example/                      # Oscar โ€” full Expo demo app
โ”‚   โ”œโ”€โ”€ App.tsx
โ”‚   โ”œโ”€โ”€ theme.ts                  # Design tokens
โ”‚   โ””โ”€โ”€ screens/
โ”‚       โ”œโ”€โ”€ OnboardingScreen.tsx
โ”‚       โ”œโ”€โ”€ DashboardScreen.tsx
โ”‚       โ”œโ”€โ”€ PolicySetupScreen.tsx
โ”‚       โ”œโ”€โ”€ AgentDemoScreen.tsx
โ”‚       โ””โ”€โ”€ HistoryScreen.tsx
โ””โ”€โ”€ lib/                          # Built output (commonjs + module + types)

Contributing

Pull requests welcome. For major changes, open an issue first.

git clone https://github.com/rkmonarch/ows-react-native
cd ows-react-native
npm install
cd backend && npm install
npm run typecheck   # type-check library


License

MIT ยฉ rkmonarch