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.
ows-react-native is an early-stage TypeScript project in the AI payments / x402 ecosystem. It currently has 2 GitHub stars and 1 forks.
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.
📦 npmjs.com/package/ows-react-native
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:
| Problem | This library's solution |
|---|---|
| RN apps can't safely hold private keys | Backend vault — keys never leave the server |
| Agents need to pay HTTP 402 endpoints | parseMppChallenge() + payMppChallenge() |
| No spending guardrails for autonomous agents | OWS policy engine (per-tx limit, daily cap, allowlist) |
| No standard RN wallet interface | Hooks-first API matching the OWS spec |
| Large payments need human approval | Biometric gate via expo-local-authentication |
OwsProvider — React context with backend URL, active wallet, policies, and tx historyuseOwsWallet() — Create wallets, load from vault, get addresses and balancesusePayWithOws() — Parse + pay HTTP 402 / x402 / MPP challenges on SolanausePolicy() — Set per-tx limits, daily caps, recipient allowlists, pause/resume<TransactionHistory /> — Drop-in component for payment historyparseMppChallenge() — Parses x402 JSON, Stripe MPP headers, and X-Payment-Required headersany in public API┌─────────────────────────────────────────┐
│ 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.
npm install ows-react-native
# If using Expo, also install native dependencies:
npx expo install expo-local-authentication expo-secure-store
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
PORTandSOLANA_NETWORKenv vars to configure. Defaults to port3001ondevnet.
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>
);
}
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>}
</>
);
}
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;
};
}
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();
}
import { TransactionHistory } from 'ows-react-native';
function HistoryScreen() {
return (
<TransactionHistory
limit={20}
showExplorerLink
onTransactionPress={(tx) => console.log(tx)}
/>
);
}
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"
}]
}
{
"scheme": "exact",
"network": "solana-devnet",
"amount": "0.10",
"recipient": "RecipientPubkey...",
"memo": "stripe-mpp-ref-456"
}
WWW-Authenticate: MPP realm="API", amount="0.10", recipient="PubKey...", network="solana-devnet"
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
The included Express backend exposes these endpoints:
| Method | Endpoint | Description |
|---|---|---|
GET |
/health |
Server status + network info |
POST |
/create-wallet |
Generate new Solana keypair, store in vault |
GET |
/list-wallets |
List all vault wallets (no secret keys) |
GET |
/get-balance/:id |
SOL + USDC balance on-chain |
GET |
/get-policy/:id |
Current OWS policy for wallet |
POST |
/update-policy/:id |
Update policy fields |
POST |
/pay-solana-usdc |
Direct USDC transfer (policy-gated) |
POST |
/pay-mpp |
Pay an x402/MPP challenge (policy-gated) |
POST |
/mock-402 |
Dev only — returns a test 402 challenge |
# backend/.env
PORT=3001
SOLANA_NETWORK=devnet # or mainnet-beta
VAULT_DIR=./.ows # path to encrypted key vault
| Network | USDC Mint Address |
|---|---|
| Devnet | 4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU |
| Mainnet | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v |
# 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"
# 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" }
⚠️ Read before deploying to production
.ows/ directory — contains private keys. Never commit it. Add to .gitignore.Authorization: Bearer <token> header authenticationAll 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.
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.
expo-sqlitechain: 'base' | 'ethereum')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)
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
MIT © rkmonarch
TypeScript Interface for Machine Payments Protocol
Alephant is an open-source AI Agent Gateway for routing, tracking, and controlling LLM usage across AI agents, members, and workflows, and for publishing agent capabilities as paid endpoints with x402 and MPP payment rails.
Specifications for the Machine Payments Protocol - powered by the "Payment" HTTP authentication scheme
Rust SDK for the Machine Payments Protocol
Website for the Machine Payments Protocol
Building blocks for Agentic payments (x402, MPP, AP2) for TypeScript, Rust, Go, Python, Ruby, PHP, Lua, Kotlin and Swift.