React Native SDK
@zkp2p/zkp2p-react-native-sdk adds Peer onramp, payment proof, and Seller Autopilot flows to React Native apps. Your app owns the shell and wallet; the SDK handles payment-platform sign-in in a WebView, Buyer TEE proofs, identity attestations, and escrow transactions.
The stable release is 0.5.1. It bundles @zkp2p/sdk@0.14.5, @zkp2p/contracts-v2@0.4.3, and @zkp2p/zkp2p-attestation@3.1.0, so you don't import those packages for normal flows. Remove lockfile overrides that pin older attestation packages.
The WebView intercept layer is built in. Don't install the old @zkp2p/react-native-webview-intercept package.
Installation
npm install @zkp2p/zkp2p-react-native-sdk@0.5.1 @react-native-async-storage/async-storage@2.2.0 @preeternal/react-native-cookie-manager@6.3.3 expo-crypto@~56.0.4 expo-web-browser@~56.0.5 react-native-webview@13.16.1 viem@2.43.2
Use the same package list with yarn add, pnpm add, or bun add. Your app must run react@19.2.3 and react-native@0.85.3. expo-crypto and expo-web-browser are optional peers for Expo-based OAuth and cryptography.
For iOS:
cd ios && pod install
Provider setup
Wrap the app in Zkp2pProvider. You don't need an API key.
import { Zkp2pProvider } from "@zkp2p/zkp2p-react-native-sdk";
import type { WalletClient } from "viem";
declare const walletClient: WalletClient;
declare const secureStorage: {
get: (key: string) => Promise<string | null>;
put: (key: string, value: unknown) => Promise<void>;
del: (key: string) => Promise<void>;
getKeys?: () => Promise<string[]>;
};
export function App() {
return (
<Zkp2pProvider
walletClient={walletClient}
chainId={8453}
rpcUrl="https://base-mainnet.g.alchemy.com/v2/your-key"
storage={secureStorage}
>
<YourScreens />
</Zkp2pProvider>
);
}
| Prop | Default | Description |
|---|---|---|
walletClient | None | viem WalletClient. Needed only to send transactions |
chainId | 8453 | Base |
rpcUrl, rpcTransport | viem's Base default | RPC for reads and receipts |
storage | None | Secure storage for saved credentials, consent, and Seller Autopilot sessions |
baseApiUrl | https://api.zkp2p.xyz | Root of Peer's API. Don't append /v1, /v2, or /v3; the SDK adds versioned paths |
attestationServiceUrl, sarAttestationServiceUrl, identityAttestationServiceUrl | https://attestation-service.zkp2p.xyz | Attestation service for payment proofs, Seller Autopilot, and identity attestations |
renderProofStatus | None | Renders proof progress; see Storage and consent |
logLevel | info | error, info, or debug |
Without walletClient, the provider runs in proof-only mode. WebView sign-in and proof preparation work, and transaction methods throw walletClient is required to ....
import { Zkp2pProvider, useZkp2p } from "@zkp2p/zkp2p-react-native-sdk";
export function App() {
return (
<Zkp2pProvider chainId={8453}>
<PaymentFlow />
</Zkp2pProvider>
);
}
function PaymentFlow() {
const {
initiate,
authenticate,
prepareBuyerTeeProof,
provider,
interceptedPayload,
metadataList,
proofStatus,
} = useZkp2p();
const isProofRunning = proofStatus.phase === "running";
// Call initiate() or authenticate(), then pass the selected metadata row
// into prepareBuyerTeeProof().
}
Buyer TEE fulfillment
Buyer TEE is the mobile proof path. prepareBuyerTeeProof() verifies the attestation service's enclave and encrypts the buyer's payment-platform session to it. fulfillIntent() sends that proof to the attestation service and submits the returned attestation on-chain.
import { useZkp2p } from "@zkp2p/zkp2p-react-native-sdk";
function BuyerFlow({
intentHash,
intentTimestampMs,
}: {
intentHash: `0x${string}`;
intentTimestampMs: string;
}) {
const {
initiate,
prepareBuyerTeeProof,
zkp2pClient,
provider,
interceptedPayload,
metadataList,
proofStatus,
} = useZkp2p();
const buy = async () => {
if (!zkp2pClient) throw new Error("Render inside Zkp2pProvider");
await initiate("venmo", "transfer_venmo", {
initialAction: {
enabled: true,
paymentDetails: {
RECIPIENT_ID: "seller-venmo-id",
AMOUNT: "100.00",
},
},
});
if (!provider || !interceptedPayload || !metadataList[0]) {
throw new Error("Payment metadata unavailable");
}
const proof = await prepareBuyerTeeProof(
provider,
interceptedPayload,
metadataList[0],
);
await zkp2pClient.fulfillIntent({
intentHash,
proof,
platform: "venmo",
actionType: "transfer_venmo",
amount: "100000000",
timestampMs: intentTimestampMs,
fiatCurrency: "0xFiatCurrencyHash",
conversionRate: "1000000000000000000",
payeeDetails: "0xPayeeDetailsHash",
timestampBufferMs: "300000",
});
};
return (
<Button
title={proofStatus.phase === "running" ? "Verifying" : "Buy"}
onPress={buy}
/>
);
}
fulfillIntent() params repeat the intent's details (amount, timestampMs, fiatCurrency, conversionRate, payeeDetails). The SDK reads the intent on-chain when it requests the attestation. initialAction.paymentDetails prefills the payment in the platform's app; the keys come from the platform's template.
Quotes and intents
Use the client from useZkp2p().zkp2pClient, or create one:
import { Zkp2pClient } from "@zkp2p/zkp2p-react-native-sdk";
const client = new Zkp2pClient({
chainId: 8453,
walletClient,
rpcUrl: "https://base-mainnet.g.alchemy.com/v2/your-key",
});
const quote = await client.getQuote({
paymentPlatforms: ["venmo", "wise"],
fiatCurrency: "USD",
user: "0xTakerAddress",
recipient: "0xRecipientAddress",
destinationChainId: 8453,
destinationToken: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
amount: "100000000", // 100 USD, in 6-decimal units
isExactFiat: true,
});
const { txHash } = await client.signalIntent({
processorName: "venmo",
depositId: "123",
amount: "100000000",
payeeDetails: "0x...",
toAddress: "0xRecipientAddress",
currencyHash: "0xUsdCurrencyHash",
conversionRate: "1000000000000000000",
});
amount is a positive integer string in the token's base units, as in the core quote API. Without quotePreference, the mobile SDK requests PREFER_BUYER_TEE, so Seller Autopilot liquidity fills in when no Buyer TEE quote matches. signalIntent() requires currencyHash and fetches the gating signature from Peer's API.
Transaction methods such as signalIntent(), fulfillIntent(), createDeposit(), withdrawDeposit(), cancelIntent(), and releaseFundsToPayer() need a walletClient.
Identity attestations
prepareIdentityAttestation(provider, interceptedPayload, metadataRow, options?) turns an identity sign-in into a signed identity attestation. It supports register_venmo, register_paypal, register_wise, and register_alipay, and returns { platform, actionType, attestation, identity }.
const {
authenticate,
prepareIdentityAttestation,
provider,
interceptedPayload,
metadataList,
} = useZkp2p();
await authenticate("wise", "register_wise");
if (!provider || !interceptedPayload || !metadataList[0]) {
throw new Error("Identity metadata unavailable");
}
const { attestation, identity } = await prepareIdentityAttestation(
provider,
interceptedPayload,
metadataList[0],
);
PayPal, Wise, and Alipay payees need an attestation at registration. Pass it as metadata: { identityAttestation: attestation } in the payee's payeeData entry. The attestation is bound to options.callerAddress, which defaults to the connected wallet.
Seller Autopilot
Seller Autopilot verifies buyer payments from the seller's side. The mobile SDK exposes it through useZkp2p().sar.
import { useZkp2p } from "@zkp2p/zkp2p-react-native-sdk";
function RegisterMakerButton() {
const { sar } = useZkp2p();
const register = async () => {
if (!sar.isSupported("wise")) return;
const result = await sar.registerMaker({
platform: "wise",
payeeHandle: "your-wisetag",
currency: "USD",
});
console.log(result.payeeIdHash, result.status);
};
return <Button title="Register maker" onPress={register} />;
}
sar.isSupported() is true for venmo, wise, cashapp, paypal, and upi. PayPal registration also requires payeeEmail; pass googleOAuthEmail when receipts are forwarded to a different Gmail inbox. registerMaker() returns { platform, payeeIdHash, sessionRef, status }, where status is active, inactive, or missing.
To attach a connected session to a new deposit, pass sellerAutomatedRelease: { sessionRef } to createDeposit(). Use sar.getStatus(), sar.revoke(), sar.onExpired(), and sar.clearAllSessions() to keep local sessions in sync with Peer's API and with sign-out.
Storage and consent
Pass a storage adapter to save credentials, consent choices, and Seller Autopilot sessions. Clear them from the context:
const {
clearAllCredentials,
clearAllConsents,
clearProviderCredentials,
clearProviderConsent,
sar,
} = useZkp2p();
await clearAllCredentials();
await clearAllConsents();
await sar.clearAllSessions();
The provider renders no proof progress UI by default. Pass renderProofStatus to render your own component, or read proofStatus from useZkp2p(). Call cancelProof() to stop the active proof.
<Zkp2pProvider
renderProofStatus={({ visible, proofStatus, onCancel }) =>
visible ? (
<ProofSheet
progress={proofStatus.progress}
message={proofStatus.meta}
status={proofStatus.phase}
error={proofStatus.error}
onCancel={onCancel}
/>
) : null
}
>
<YourScreens />
</Zkp2pProvider>
Troubleshooting
| Problem | Check |
|---|---|
API calls include duplicated versions, such as /v2/v2/... | Pass baseApiUrl as a root, such as https://api.zkp2p.xyz |
A transaction method throws walletClient is required | The provider is in proof-only mode. Pass a viem walletClient |
| A Buyer TEE proof fails before upload | Point attestationServiceUrl at the attestation host root, and check that the platform and action are supported |
Attestation requests hit /?nonce=... or fail with GET /attestation 404 | Remove lockfile overrides to older attestation packages, and don't append /attestation to the host |
| Nitro certificate verification fails only in React Native | Update to the latest SDK, which includes the React Native P-384 verification fixes |
| Seller Autopilot can't save session material | Pass a storage adapter to Zkp2pProvider |
Help?
For integration questions, join the Peer Telegram community. For order-specific help, email support@peer.xyz.