@zkp2p/client-sdk

Browser-first TypeScript SDK for integrating ZKP2P into web applications. Built with React hooks, unified authentication, and comprehensive type safety.
Features
- Unified Authentication: Single method for authentication and proof generation
- React Hooks: Complete set of hooks for seamless React integration
- Enhanced Callbacks: Granular progress tracking and error handling
- Comprehensive Constants: All platforms, currencies, and chain data exported
- TypeScript First: Full type safety and IntelliSense support
- Multi-Chain Support: Base (Production & Staging), Base Sepolia, and Hardhat networks
- Extension Integration: Built-in support for peerauth browser extension
- Payment Platforms: Support for Venmo, Revolut, CashApp, Wise, MercadoPago, Zelle, PayPal, and Monzo
- Data Enrichment: Automatic enrichment of intents and deposits with payment metadata
Installation
npm install @zkp2p/client-sdk viem
yarn add @zkp2p/client-sdk viem
pnpm add @zkp2p/client-sdk viem
Releases
Quick Start
Versioned Imports (Recommended)
Google-style subpath imports provide compile-time isolation between API versions and better tree-shaking:
import { Zkp2pClient } from '@zkp2p/client-sdk/v1';
The root import @zkp2p/client-sdk
continues to expose the current default (v1) for backward compatibility.
Basic Client Setup
import { Zkp2pClient, SUPPORTED_CHAIN_IDS } from '@zkp2p/client-sdk/v1';
import { createWalletClient, custom } from 'viem';
import { base } from 'viem/chains';
const walletClient = createWalletClient({
chain: base,
transport: custom(window.ethereum),
});
const client = new Zkp2pClient({
walletClient,
apiKey: 'YOUR_API_KEY',
chainId: SUPPORTED_CHAIN_IDS.BASE_MAINNET,
environment: 'production',
});
const stagingClient = new Zkp2pClient({
walletClient,
apiKey: 'YOUR_API_KEY',
chainId: SUPPORTED_CHAIN_IDS.BASE_MAINNET,
environment: 'staging',
});
Fetching Quotes
import { Currency, PAYMENT_PLATFORMS } from '@zkp2p/client-sdk/v1';
const quotes = await client.getQuote({
paymentPlatforms: ['wise', 'revolut', 'venmo'],
fiatCurrency: Currency.USD,
user: '0xYourAddress',
recipient: '0xRecipientAddress',
destinationChainId: SUPPORTED_CHAIN_IDS.BASE_MAINNET,
destinationToken: client.getUsdcAddress(),
amount: '100',
});
console.log('Available quotes:', quotes);
Register Payee Details (payeeHash)
const validation = await client.validatePayeeDetails({
processorName: 'mercadopago',
depositData: { identifier: 'user@example.com', accountHolderName: 'Alice Doe' },
});
if (!validation.responseObject.isValid) {
console.error('Invalid payee details:', validation.responseObject.errors);
throw new Error('Please correct payee details');
}
const created = await client.registerPayeeDetails({
processorName: 'mercadopago',
depositData: { identifier: 'user@example.com', accountHolderName: 'Alice Doe' },
});
const payeeHash = created.responseObject.hashedOnchainId;
Validate and Register (one call)
const { isValid, validation, registration } = await client.validateAndRegisterPayeeDetails({
processorName: 'mercadopago',
depositData: { identifier: 'user@example.com', accountHolderName: 'Alice Doe' },
});
if (!isValid) {
console.error('Invalid details', validation.responseObject);
}
const payeeHash = registration?.responseObject.hashedOnchainId;
Listing Registered Payees
const { responseObject: makers } = await client.listPayees('mercadopago');
makers.forEach(m => console.log(m.processorName, m.hashedOnchainId));
Intents by Recipient
const intents = await client.getIntentsByRecipient({ recipientAddress: '0xRecipient', status: ['SIGNALED','FULFILLED'] });
Deposit Spreads (Maker dashboards)
await client.getDepositSpread(123);
await client.listDepositSpreads();
await client.getSpreadsByDepositIds([1,2,3]);
await client.createSpread({ depositId: 123, spread: 0.01, minPrice: null, maxPrice: null });
await client.updateSpread(123, { spread: 0.0125 });
await client.upsertSpread(123, { spread: 0.01 });
await client.deleteSpread(123);
Auth Options
Client supports both x-api-key
and optional Authorization: Bearer <token>
for hybrid auth.
const client = new Zkp2pClient({
walletClient,
apiKey: 'YOUR_API_KEY',
authorizationToken: 'JWT_OR_OAUTH_TOKEN',
chainId: 8453,
});
Statuses and Types
- Orders API statuses:
SIGNALED | FULFILLED | PRUNED
. SDK returns these for API history calls.
- On-chain views remain exposed via
getAccountDeposits
/getAccountIntent
parsers.
- Historical responses automatically convert ISO timestamps into
Date
objects.
React Integration
Complete React Example
import React, { useState } from 'react';
import {
useZkp2pClient,
useQuote,
useSignalIntent,
useCreateDeposit,
useFulfillIntent,
useExtensionOrchestrator,
PLATFORM_METADATA,
Currency,
type PaymentPlatformType
} from '@zkp2p/client-sdk/v1';
function ZKP2PApp() {
const [selectedPlatform, setSelectedPlatform] = useState<PaymentPlatformType>('venmo');
const { client, isInitialized, error: clientError } = useZkp2pClient({
walletClient: window.walletClient,
apiKey: process.env.REACT_APP_ZKP2P_API_KEY!,
chainId: 8453,
});
const {
fetchQuote,
quote,
isLoading: quoteLoading,
error: quoteError
} = useQuote({
client,
onSuccess: (quote) => {
console.log('Quote received:', quote);
},
onError: (error) => {
console.error('Quote error:', error);
},
});
const {
signalIntent,
response: intentResponse,
isLoading: intentLoading
} = useSignalIntent({
client,
onSuccess: (response) => {
console.log('Intent signaled:', response);
},
});
const {
authenticate,
payments,
proofs,
proofBytes,
isAuthenticating,
isGeneratingProof,
progress,
error: proofError,
} = useExtensionOrchestrator({
debug: true,
autoDispose: true,
});
const {
fulfillIntent,
txHash,
isLoading: fulfillLoading
} = useFulfillIntent({
client,
onSuccess: (hash) => {
console.log('Intent fulfilled! Transaction:', hash);
},
});
const handleAuthenticateAndProve = async () => {
if (!intentResponse?.intentHash) {
alert('Please signal an intent first');
return;
}
const result = await authenticate(selectedPlatform, {
autoGenerateProof: {
intentHashHex: intentResponse.intentHash,
itemIndex: 0,
onProofGenerated: (proofs) => {
console.log('Proofs generated:', proofs);
},
onProofError: (error) => {
console.error('Proof generation failed:', error);
},
onProgress: (progress) => {
console.log('Progress:', progress);
},
},
onPaymentsReceived: (payments) => {
console.log('Payments received:', payments);
},
});
if (result?.proofs && intentResponse?.intentHash) {
await fulfillIntent({
intentHash: intentResponse.intentHash,
paymentProofs: result.proofs.map(proof => ({ proof })),
});
}
};
const handleFetchQuote = async () => {
await fetchQuote({
paymentPlatforms: [selectedPlatform],
fiatCurrency: Currency.USD,
user: '0xYourAddress',
recipient: '0xRecipientAddress',
destinationChainId: 8453,
destinationToken: client?.getUsdcAddress() || '0x',
amount: '100',
});
};
if (!isInitialized) {
return <div>Initializing ZKP2P client...</div>;
}
if (clientError) {
return <div>Error initializing client: {clientError.message}</div>;
}
return (
<div className="zkp2p-app">
<h1>ZKP2P Integration</h1>
{/* Platform Selection */}
<div className="platform-selector">
<h2>Select Payment Platform</h2>
{Object.entries(PLATFORM_METADATA).map(([key, platform]) => (
<button
key={key}
onClick={() => setSelectedPlatform(key as PaymentPlatformType)}
className={selectedPlatform === key ? 'selected' : ''}
>
{platform.logo} {platform.displayName}
</button>
))}
</div>
{/* Quote Section */}
<div className="quote-section">
<h2>Get Quote</h2>
<button onClick={handleFetchQuote} disabled={quoteLoading}>
{quoteLoading ? 'Fetching...' : 'Fetch Quote'}
</button>
{quote && (
<div className="quote-display">
<h3>Quote Details:</h3>
<pre>{JSON.stringify(quote, null, 2)}</pre>
</div>
)}
{quoteError && <div className="error">Error: {quoteError.message}</div>}
</div>
{/* Authentication & Proof Generation */}
<div className="proof-section">
<h2>Generate Payment Proof</h2>
<button
onClick={handleAuthenticateAndProve}
disabled={isAuthenticating || isGeneratingProof}
>
{isAuthenticating
? 'Authenticating...'
: isGeneratingProof
? `Generating Proof... ${progress?.stage || ''}`
: 'Authenticate & Generate Proof'}
</button>
{/* Display progress */}
{progress && (
<div className="progress">
<p>Stage: {progress.stage}</p>
<p>Proof Index: {progress.proofIndex}</p>
{progress.message && <p>Message: {progress.message}</p>}
</div>
)}
{/* Display generated proofs */}
{proofs && (
<div className="proof-display">
<h3>Generated Proofs:</h3>
<p>Number of proofs: {proofs.length}</p>
<p>Proof bytes: {proofBytes}</p>
</div>
)}
{/* Display errors */}
{proofError && <div className="error">Error: {proofError.message}</div>}
</div>
{/* Transaction Status */}
{txHash && (
<div className="success">
<h3>✅ Transaction Successful!</h3>
<p>Hash: {txHash}</p>
<a
href={`https://basescan.org/tx/${txHash}`}
target="_blank"
rel="noopener noreferrer"
>
View on BaseScan
</a>
</div>
)}
</div>
);
}
export default ZKP2PApp;
Individual Hook Examples
useCreateDeposit
- Create Liquidity Deposits
import { useCreateDeposit, Currency, type CreateDepositConversionRate } from '@zkp2p/client-sdk/v1';
function DepositCreator() {
const { client } = useZkp2pClient({ });
const {
createDeposit,
txHash,
depositDetails,
isLoading,
error
} = useCreateDeposit({
client,
onSuccess: ({ hash, depositDetails }) => {
console.log('Deposit created:', hash);
console.log('Deposit details:', depositDetails);
},
});
const handleCreateDeposit = async () => {
const conversionRates: CreateDepositConversionRate[][] = [[
{ currency: Currency.USD, conversionRate: '1000000' },
]];
await createDeposit({
token: client!.getUsdcAddress(),
amount: BigInt('1000000'),
intentAmountRange: {
min: BigInt('500000'),
max: BigInt('2000000'),
},
conversionRates,
processorNames: ['venmo'],
depositData: [{
venmoUsername: 'alice123',
}],
});
};
return (
<button onClick={handleCreateDeposit} disabled={isLoading}>
{isLoading ? 'Creating Deposit...' : 'Create Deposit'}
</button>
);
}
useSignalIntent
- Signal Trading Intent
import { useSignalIntent, Currency } from '@zkp2p/client-sdk/v1';
function IntentSignaler() {
const { client } = useZkp2pClient({ });
const {
signalIntent,
response,
isLoading
} = useSignalIntent({
client,
onSuccess: (response) => {
console.log('Intent hash:', response.intentHash);
console.log('Timestamp:', response.timestamp);
},
});
const handleSignalIntent = async () => {
await signalIntent({
processorName: 'wise',
depositId: '123',
tokenAmount: '1000000',
payeeDetails: JSON.stringify({
email: 'alice@example.com',
accountNumber: '12345678',
}),
toAddress: '0xRecipientAddress',
currency: Currency.USD,
});
};
return (
<button onClick={handleSignalIntent} disabled={isLoading}>
{isLoading ? 'Signaling...' : 'Signal Intent'}
</button>
);
}
Extension Integration
Unified Authentication Flow (Recommended)
import { PLATFORM_METADATA } from '@zkp2p/client-sdk/v1';
import { ExtensionOrchestrator } from '@zkp2p/client-sdk/extension';
async function authenticateAndGenerateProof() {
const orchestrator = new ExtensionOrchestrator({
debug: true,
metadataTimeoutMs: 60000,
});
try {
const result = await orchestrator.authenticateAndGenerateProof('revolut', {
paymentMethod: 1,
autoGenerateProof: {
intentHashHex: '0x123...abc',
itemIndex: 0,
onProofGenerated: (proofs) => {
console.log(`✅ Generated ${proofs.length} proof(s)`);
},
onProofError: (error) => {
console.error('❌ Proof generation failed:', error);
},
onProgress: (progress) => {
console.log(`⏳ ${progress.stage} - Proof ${progress.proofIndex + 1}`);
},
},
onPaymentsReceived: (payments) => {
console.log(`📱 Received ${payments.length} payments`);
},
});
console.log('Payments:', result.payments);
console.log('Proofs:', result.proofs);
console.log('Proof bytes:', result.proofBytes);
if (result.proofs && result.proofBytes) {
await client.fulfillIntent({
intentHash: '0x123...abc',
paymentProofs: result.proofs.map(p => ({ proof: p })),
});
}
} finally {
orchestrator.dispose();
}
}
Manual Proof Generation Flow
import {
ExtensionProofFlow,
ExtensionMetadataFlow,
metadataUtils,
intentHashHexToDecimalString,
assembleProofBytes
} from '@zkp2p/client-sdk/extension';
async function manualProofFlow() {
const metaFlow = new ExtensionMetadataFlow({ debug: true });
const payments = await new Promise((resolve) => {
const unsub = metaFlow.subscribe((platform, record) => {
if (platform === 'venmo') {
const visible = metadataUtils.filterVisible(record.metadata);
const sorted = metadataUtils.sortByDateDesc(visible);
unsub();
resolve(sorted);
}
});
metaFlow.requestMetadata('list_transactions', 'venmo');
});
const selectedPayment = payments[0];
const proofFlow = new ExtensionProofFlow({ debug: true });
const proofs = await proofFlow.generateProofs(
'venmo',
intentHashHexToDecimalString('0x123...abc'),
selectedPayment.originalIndex,
{
requiredProofs: 1,
timeoutMs: 120000,
pollIntervalMs: 3000,
},
(progress) => {
console.log('Progress:', progress);
}
);
const proofBytes = assembleProofBytes(proofs, { paymentMethod: 1 });
await client.fulfillIntent({
intentHash: '0x123...abc',
paymentProofs: proofs.map(p => ({ proof: p })),
});
metaFlow.dispose();
proofFlow.dispose();
}
Working with Constants
Platform Information
import {
PAYMENT_PLATFORMS,
PLATFORM_METADATA,
type PaymentPlatformType
} from '@zkp2p/client-sdk/v1';
console.log('Supported platforms:', PAYMENT_PLATFORMS);
PAYMENT_PLATFORMS.forEach(platform => {
const meta = PLATFORM_METADATA[platform];
console.log(`${meta.logo} ${meta.displayName}: ${meta.requiredProofs} proof(s) required`);
});
function processPlatform(platform: PaymentPlatformType) {
const metadata = PLATFORM_METADATA[platform];
console.log(`Processing ${metadata.displayName}...`);
}
Currency Information
import {
Currency,
currencyInfo,
type CurrencyType,
type CurrencyData
} from '@zkp2p/client-sdk/v1';
const usdCurrency: CurrencyType = Currency.USD;
const eurCurrency: CurrencyType = Currency.EUR;
const usdInfo: CurrencyData = currencyInfo[Currency.USD];
console.log(`${usdInfo.currencySymbol} - ${usdInfo.currencyName}`);
Object.values(Currency).forEach(code => {
const info = currencyInfo[code];
console.log(`${info.countryCode}: ${info.currencySymbol} ${info.currencyCode}`);
});
Chain and Contract Information
import {
SUPPORTED_CHAIN_IDS,
DEPLOYED_ADDRESSES,
DEFAULT_BASE_API_URL,
DEFAULT_WITNESS_URL,
type SupportedChainId
} from '@zkp2p/client-sdk';
console.log('Base Mainnet:', SUPPORTED_CHAIN_IDS.BASE_MAINNET);
console.log('Base Sepolia:', SUPPORTED_CHAIN_IDS.BASE_SEPOLIA);
console.log('Scroll:', SUPPORTED_CHAIN_IDS.SCROLL_MAINNET);
const baseContracts = DEPLOYED_ADDRESSES[SUPPORTED_CHAIN_IDS.BASE_MAINNET];
console.log('Escrow:', baseContracts.escrow);
console.log('USDC:', baseContracts.usdc);
console.log('Venmo Verifier:', baseContracts.venmo);
console.log('API URL:', DEFAULT_BASE_API_URL);
console.log('Witness URL:', DEFAULT_WITNESS_URL);
Advanced Usage
On-chain Views Enrichment
When fetching on-chain deposits and intents, the SDK automatically enriches the data with payment metadata when an API key is provided:
const deposits = await client.getAccountDeposits('0xYourAddress');
deposits.forEach(deposit => {
deposit.verifiers.forEach(verifier => {
console.log('Platform:', verifier.verificationData.paymentMethod);
console.log('Payment Data:', verifier.verificationData.paymentData);
});
});
const intent = await client.getAccountIntent('0xYourAddress');
if (intent) {
console.log('Payment Platform:', intent.intent.paymentMethod);
console.log('Payment Data:', intent.intent.paymentData);
intent.deposit.verifiers.forEach(verifier => {
console.log('Verifier Platform:', verifier.verificationData.paymentMethod);
console.log('Verifier Data:', verifier.verificationData.paymentData);
});
}
Notes:
- Enrichment is best-effort and won't throw errors if it fails
paymentMethod
is always set when the verifier is a recognized platform
paymentData
requires a valid API key and may not always be available
- The enrichment happens automatically - no additional configuration needed
Custom Timeout Configuration
const client = new Zkp2pClient({
walletClient,
apiKey: 'YOUR_API_KEY',
chainId: 8453,
timeouts: {
api: 30000,
transaction: 60000,
proofGeneration: 120000,
extension: 60000,
},
});
Error Handling
import {
ZKP2PError,
ValidationError,
NetworkError,
APIError,
ContractError,
ProofGenerationError,
ErrorCode
} from '@zkp2p/client-sdk';
try {
await client.signalIntent({ });
} catch (error) {
if (error instanceof ValidationError) {
console.error('Validation failed:', error.message);
console.error('Error code:', error.code);
} else if (error instanceof NetworkError) {
console.error('Network error:', error.message);
} else if (error instanceof APIError) {
console.error('API error:', error.message);
} else if (error instanceof ContractError) {
console.error('Smart contract error:', error.message);
} else if (error instanceof ProofGenerationError) {
console.error('Proof generation failed:', error.message);
} else {
console.error('Unknown error:', error);
}
}
Logging Configuration
import { logger, setLogLevel } from '@zkp2p/client-sdk/v1';
setLogLevel('debug');
logger.debug('Debug message');
logger.info('Info message');
logger.warn('Warning message');
logger.error('Error message');
Proof Encoding Utilities
import {
encodeProofAsBytes,
encodeTwoProofs,
encodeManyProofs,
encodeProofAndPaymentMethodAsBytes,
assembleProofBytes,
type ReclaimProof
} from '@zkp2p/client-sdk/v1';
import { parseExtensionProof } from '@zkp2p/client-sdk/extension';
const extensionPayload = '...';
const reclaimProof: ReclaimProof = parseExtensionProof(extensionPayload);
const singleBytes = encodeProofAsBytes(reclaimProof);
const twoBytes = encodeTwoProofs(proof1, proof2);
const manyBytes = encodeManyProofs([proof1, proof2, proof3]);
const taggedBytes = encodeProofAndPaymentMethodAsBytes(singleBytes, 1);
const assembled = assembleProofBytes([proof1, proof2], { paymentMethod: 1 });
Environment Configuration
Vite
VITE_ZKP2P_API_KEY=your_api_key_here
VITE_ZKP2P_RPC_URL=https://base-mainnet.g.alchemy.com/v2/your_key
VITE_ZKP2P_BASE_API_URL=https://api.zkp2p.xyz/v1
VITE_ZKP2P_WITNESS_URL=https://witness-proxy.zkp2p.xyz
import { Zkp2pClient } from '@zkp2p/client-sdk/v1';
const client = new Zkp2pClient({
walletClient,
apiKey: import.meta.env.VITE_ZKP2P_API_KEY!,
chainId: 8453,
rpcUrl: import.meta.env.VITE_ZKP2P_RPC_URL,
baseApiUrl: import.meta.env.VITE_ZKP2P_BASE_API_URL,
witnessUrl: import.meta.env.VITE_ZKP2P_WITNESS_URL,
});
Next.js
NEXT_PUBLIC_ZKP2P_API_KEY=your_api_key_here
NEXT_PUBLIC_ZKP2P_RPC_URL=https://base-mainnet.g.alchemy.com/v2/your_key
'use client';
import { Zkp2pClient } from '@zkp2p/client-sdk/v1';
const client = new Zkp2pClient({
walletClient,
apiKey: process.env.NEXT_PUBLIC_ZKP2P_API_KEY!,
chainId: 8453,
rpcUrl: process.env.NEXT_PUBLIC_ZKP2P_RPC_URL,
});
Create React App
REACT_APP_ZKP2P_API_KEY=your_api_key_here
REACT_APP_ZKP2P_RPC_URL=https://base-mainnet.g.alchemy.com/v2/your_key
const client = new Zkp2pClient({
walletClient,
apiKey: process.env.REACT_APP_ZKP2P_API_KEY!,
chainId: 8453,
rpcUrl: process.env.REACT_APP_ZKP2P_RPC_URL,
});
Complete API Reference
Client Methods
getQuote(request) | Get quotes from liquidity providers | QuoteResponse |
createDeposit(params) | Create a new liquidity deposit | { hash, depositDetails } |
signalIntent(params) | Signal intent to trade | SignalIntentResponse & { txHash? } |
fulfillIntent(params) | Fulfill intent with payment proof | Hash |
withdrawDeposit(params) | Withdraw a deposit | Hash |
cancelIntent(params) | Cancel a pending intent | Hash |
releaseFundsToPayer(params) | Release escrowed funds | Hash |
validatePayeeDetails(params) | Validate payee information | ValidatePayeeDetailsResponse |
registerPayeeDetails(params) | Register payee and get hashedOnchainId | RegisterPayeeDetailsResponse |
validateAndRegisterPayeeDetails(params) | Validate then register; returns both | { isValid, validation, registration? } |
listPayees(processorName?) | List registered payees (makers) | PresentedMaker[] |
getIntentsByRecipient(params) | Intents for a recipient address | Intent[] |
getDepositSpread(id) | Get spread for deposit | API response |
listDepositSpreads() | List all spreads | API response |
getSpreadsByDepositIds(ids) | Bulk spreads | API response |
createSpread(body) | Create spread | API response |
updateSpread(id, body) | Update spread | API response |
upsertSpread(id, body) | Upsert spread | API response |
deleteSpread(id) | Delete spread | API response |
getAccountDeposits(address) | Get account's deposits | EscrowDepositView[] |
getAccountIntent(address) | Get account's current intent | EscrowIntentView |
React Hooks
useZkp2pClient | Initialize client | { client, isInitialized, error } |
useQuote | Manage quotes | { fetchQuote, quote, isLoading, error } |
useSignalIntent | Signal intents | { signalIntent, response, isLoading } |
useCreateDeposit | Create deposits | { createDeposit, txHash, depositDetails } |
useRegisterPayeeDetails | Register payee and get payeeHash | { registerPayeeDetails, response, isLoading } |
useValidatePayeeDetails | Validate payee details | { validatePayeeDetails, response, isLoading } |
usePayeeRegistration | Validate then register flow | { validateAndRegister, result, isLoading } |
useFulfillIntent | Fulfill intents | { fulfillIntent, txHash, isLoading } |
useExtensionOrchestrator | Extension integration | { authenticate, payments, proofs } |
Testing
import { Zkp2pClient } from '@zkp2p/client-sdk/v1';
import { createWalletClient, http } from 'viem';
import { hardhat } from 'viem/chains';
const testClient = new Zkp2pClient({
walletClient: createWalletClient({
chain: hardhat,
transport: http(),
}),
apiKey: 'TEST_KEY',
chainId: 31337,
});
Contributing
We welcome contributions! Please see our Contributing Guide for details.
License
MIT License - see LICENSE for details.
Links
Support