Accept Crypto Payments for Your Business
Fast, private, and production-ready payment infrastructure for merchants. Get paid in 147ms while protecting customer privacy.
Accept crypto payments 50x faster than traditional blockchain transactions with zero-knowledge privacy built-in.
Why ShadowPay for Merchants?
Accept payments on Solana with complete privacy. Traditional payments expose every transaction amount, sender, and recipient to the world. ShadowPay uses zero-knowledge proofs and ElGamal encryption to keep payment amounts private while maintaining instant settlement (147ms authorization, 5-15s on-chain). Your customers' purchase history stays confidential, and your revenue remains private.
Why ShadowPay?
Instant Payments (147ms)
Customers pay and receive instant access while settlement completes in the background.
Zero-Knowledge Privacy
Payment amounts encrypted with ElGamal. Customer identity and transaction details are private.
Production-Ready
Battle-tested infrastructure with PostgreSQL persistence, API key management, and comprehensive analytics.
Global & Permissionless
Accept payments from anyone, anywhere. No KYC, no chargebacks, no intermediaries. You own your revenue.
Quick Start (5 minutes)
Get your API protected and monetized in three simple steps
Get Your API Key
Create your merchant API key with a simple POST request. You'll need your Solana wallet address and a treasury wallet for receiving payments.
curl -X POST https://shadow.radr.fun/shadowpay/v1/keys/new \
-H "Content-Type: application/json" \
-d '{
"wallet_address": "YOUR_SOLANA_WALLET",
"treasury_wallet": "YOUR_TREASURY_WALLET"
}'{
"api_key": "2hTKeADLwNZPeU5MeFcNKV4ttfWtpBUSEMiRVf4jRyjC",
"rps_limit": 50,
"daily_commit_limit": 10000,
"wallet_address": "YOUR_SOLANA_WALLET",
"treasury_wallet": "YOUR_TREASURY_WALLET"
}Protect Your Endpoint
Add ShadowPay authentication to any endpoint. Return a 402 Payment Required response when no valid access token is present, or verify the token with ShadowPay's API.
const express = require('express');
const axios = require('axios');
const app = express();
// Your credentials
const MERCHANT_API_KEY = '2hTKeADLwNZPeU5MeFcNKV4ttfWtpBUSEMiRVf4jRyjC';
const MERCHANT_WALLET = 'YOUR_TREASURY_WALLET';
const ELGAMAL_SECRET_KEY = process.env.ELGAMAL_SECRET_KEY;
app.use(express.json());
app.get('/api/premium-data', async (req, res) => {
const accessToken = req.headers['authorization']?.replace('Bearer ', '');
// No token? Reject with 402 Payment Required
if (!accessToken) {
return res.status(402).json({
error: 'Payment required',
amount: 1000000, // 0.001 SOL in lamports
merchant: MERCHANT_WALLET,
message: 'Use ShadowPay SDK to authorize payment'
});
}
// Verify token with ShadowPay (or verify JWT locally)
try {
const verify = await axios.get(
`https://shadow.radr.fun/v1/payment/verify-access`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
);
if (verify.data.valid) {
// ✅ Payment authorized - return premium content
return res.json({
data: 'Your premium content here!',
timestamp: Date.now(),
message: 'Payment authorized, settlement in progress'
});
}
} catch (error) {
console.error('Token verification failed:', error.message);
}
// Invalid or expired token
res.status(402).json({ error: 'Invalid or expired payment token' });
});
app.listen(3000, () => {
console.log('✅ Protected API running on port 3000');
});Frontend Integration
Use the ShadowPay JavaScript SDK to handle payments from your frontend. The SDK automatically generates ZK proofs in the background while users get instant access.
<!DOCTYPE html>
<html>
<head>
<title>Premium Content</title>
<script src="https://cdn.jsdelivr.net/npm/snarkjs@latest/build/snarkjs.min.js"></script>
<script src="https://shadow.radr.fun/client/shadowpay-client.js"></script>
</head>
<body>
<button onclick="unlockContent()">Unlock Premium Content (0.001 SOL)</button>
<div id="content"></div>
<script>
const shadowpay = new ShadowPayClient({
baseUrl: 'https://shadow.radr.fun'
});
async function unlockContent() {
try {
// Initialize SDK (load ZK circuits)
await shadowpay.init();
console.log('✅ ShadowPay SDK loaded');
// Connect wallet (Phantom, Backpack, etc.)
const wallet = window.solana;
await wallet.connect();
console.log('✅ Wallet connected:', wallet.publicKey.toString());
// Make payment (instant authorization)
const { accessToken, commitment, proofDeadline } = await shadowpay.pay({
userWallet: wallet.publicKey.toString(),
merchantWallet: 'YOUR_TREASURY_WALLET',
merchantApiKey: 'YOUR_API_KEY',
amount: 1000000, // 0.001 SOL
onProofComplete: (result) => {
if (result.success) {
console.log('✅ Settlement complete:', result.tx_hash);
} else {
console.error('❌ Settlement failed:', result.error);
}
}
});
console.log('✅ Payment authorized! Token:', accessToken);
console.log('⏳ Proof deadline:', new Date(proofDeadline * 1000));
// Fetch premium content with access token
const response = await fetch('http://localhost:3000/api/premium-data', {
headers: { 'Authorization': `Bearer ${accessToken}` }
});
const data = await response.json();
document.getElementById('content').innerHTML = ``;
} catch (error) {
console.error('❌ Payment failed:', error);
alert('Payment failed: ' + error.message);
}
}
</script>
</body>
</html>Setup ZK Circuits (First Time Only)
Download and configure ZK circuit files for proof generation. The ShadowPay SDK needs these files to generate zero-knowledge proofs. This is a one-time setup that happens automatically, but you can also pre-load them.
// Option 1: Automatic (Recommended)
// The SDK automatically downloads circuits on first init()
const shadowpay = new ShadowPayClient({
baseUrl: 'https://shadow.radr.fun'
});
await shadowpay.init(); // Downloads and caches circuits
console.log('✅ Circuits loaded and cached in localStorage');
// Option 2: Manual Pre-loading (Advanced)
async function preloadCircuits() {
const circuits = [
{
name: 'main-wasm',
url: 'https://shadow.radr.fun/shadowpay/circuit/shadowpay_js/shadowpay.wasm',
key: 'shadowpay_circuit_wasm'
},
{
name: 'main-zkey',
url: 'https://shadow.radr.fun/shadowpay/circuit/shadowpay_final.zkey',
key: 'shadowpay_circuit_zkey'
},
{
name: 'elgamal-wasm',
url: 'https://shadow.radr.fun/shadowpay/circuit-elgamal/shadowpay-elgamal_js/shadowpay-elgamal.wasm',
key: 'shadowpay_elgamal_wasm'
},
{
name: 'elgamal-zkey',
url: 'https://shadow.radr.fun/shadowpay/circuit-elgamal/shadowpay-elgamal_final.zkey',
key: 'shadowpay_elgamal_zkey'
}
];
for (const circuit of circuits) {
// Check if already cached
if (localStorage.getItem(circuit.key)) {
console.log(`✓ ${circuit.name} already cached`);
continue;
}
console.log(`📥 Downloading ${circuit.name}...`);
const response = await fetch(circuit.url);
const buffer = await response.arrayBuffer();
const base64 = btoa(String.fromCharCode(...new Uint8Array(buffer)));
localStorage.setItem(circuit.key, base64);
console.log(`✅ Cached ${circuit.name}`);
}
console.log('✅ All circuits cached successfully');
}
// Call this once during app initialization
await preloadCircuits();Frontend ZK Proof Generation (Groth16)
Generate zero-knowledge proofs on the frontend using snarkjs and the downloaded circuit artifacts.
Setup & Dependencies
<!-- Include snarkjs library -->
<script src="https://cdn.jsdelivr.net/npm/snarkjs@latest/build/snarkjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/circomlibjs@latest/build/main.js"></script>
<!-- Or via npm -->
npm install snarkjs circomlibjsStep 1: Prepare Witness Input
import { buildPoseidon } from 'circomlibjs';
async function prepareWitnessInput(paymentData) {
const poseidon = await buildPoseidon();
// User's ShadowID commitment (from registration)
const senderSecret = BigInt(localStorage.getItem('shadowid_secret'));
const senderCommitment = poseidon.F.toString(poseidon([senderSecret]));
// Get Merkle proof for sender
const merkleProof = await fetch('https://shadow.radr.fun/shadowpay/api/shadowid/proof', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ commitment: senderCommitment })
}).then(r => r.json());
// Prepare witness input for circuit
const witnessInput = {
// Private inputs
sender_commitment: senderCommitment,
sender_secret: senderSecret.toString(),
receiver_commitment: paymentData.merchantCommitment,
amount: paymentData.amount.toString(), // lamports
token_mint: "0", // 0 = SOL
salt: generateRandomSalt(),
merkle_path: merkleProof.proof.pathElements,
path_indices: merkleProof.proof.pathIndices,
// ElGamal encryption inputs
encrypted_amount_c1: paymentData.elgamal.c1,
encrypted_amount_c2: paymentData.elgamal.c2,
elgamal_randomness: paymentData.elgamal.randomness,
// Public inputs
shadowid_root: merkleProof.proof.root,
max_amount: (paymentData.amount * 2).toString(), // 2x buffer
receiver_elgamal_pubkey: paymentData.merchantElGamalPubkey
};
return witnessInput;
}
function generateRandomSalt() {
const randomBytes = new Uint8Array(32);
crypto.getRandomValues(randomBytes);
return BigInt('0x' + Array.from(randomBytes).map(b => b.toString(16).padStart(2, '0')).join(''));
}Step 2: Generate Groth16 Proof
async function generateGroth16Proof(witnessInput) {
console.log('🔐 Generating Groth16 proof...');
const startTime = Date.now();
// Load circuit artifacts from cache (see Step 4 in Quick Start)
const wasmB64 = localStorage.getItem('shadowpay_elgamal_wasm');
const zkeyB64 = localStorage.getItem('shadowpay_elgamal_zkey');
if (!wasmB64 || !zkeyB64) {
throw new Error('Circuit artifacts not loaded. Run shadowpay.init() first.');
}
// Convert base64 to Uint8Array
const wasmBytes = Uint8Array.from(atob(wasmB64), c => c.charCodeAt(0));
const zkeyBytes = Uint8Array.from(atob(zkeyB64), c => c.charCodeAt(0));
// Generate proof using snarkjs
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
witnessInput,
wasmBytes,
zkeyBytes
);
const duration = Date.now() - startTime;
console.log(`✅ Proof generated in ${duration}ms`);
return {
proof: {
pi_a: proof.pi_a.slice(0, 2),
pi_b: proof.pi_b.slice(0, 2).map(x => x.slice(0, 2)),
pi_c: proof.pi_c.slice(0, 2),
protocol: proof.protocol,
curve: proof.curve
},
publicSignals: {
shadowid_root: publicSignals[0],
payment_commitment: publicSignals[1],
payment_nullifier: publicSignals[2],
encrypted_amount_commitment: publicSignals[3]
},
generationTime: duration
};
}Complete Payment Flow with ZK Proof
async function makeZKPayment(merchantWallet, amount) {
try {
// 1. Encrypt amount with ElGamal (see ElGamal section)
const elgamal = await encryptAmount(amount, merchantElGamalPubkey);
// 2. Prepare witness input
const witnessInput = await prepareWitnessInput({
merchantCommitment: merchantWallet,
merchantElGamalPubkey: merchantElGamalPubkey,
amount: amount,
elgamal: elgamal
});
// 3. Generate Groth16 proof
const proofData = await generateGroth16Proof(witnessInput);
// 4. Send to ShadowPay for verification and settlement
const response = await fetch('https://shadow.radr.fun/shadowpay/settle', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
x402Version: 1,
scheme: 'zkproof',
network: 'solana-mainnet',
proof: proofData.proof,
publicSignals: proofData.publicSignals,
encryptedAmount: {
c1: elgamal.c1,
c2: elgamal.c2
},
timestamp: Math.floor(Date.now() / 1000)
})
});
const result = await response.json();
if (result.success) {
console.log('✅ Payment settled on-chain:', result.txHash);
return {
accessToken: result.accessToken,
txHash: result.txHash,
proofTime: proofData.generationTime
};
}
} catch (error) {
console.error('❌ Payment failed:', error);
throw error;
}
}ElGamal Encryption (Frontend)
Encrypt payment amounts on the frontend using ElGamal encryption on the BN254 curve with Poseidon hash.
ElGamal Encryption Implementation
import { buildPoseidon } from 'circomlibjs';
class ElGamalEncryptor {
constructor() {
this.poseidon = null;
}
async init() {
this.poseidon = await buildPoseidon();
}
/**
* Encrypt a payment amount using ElGamal
* @param {number} amount - Amount in lamports
* @param {string} recipientPublicKey - Merchant's ElGamal public key
* @returns {object} - { c1, c2, randomness }
*/
encryptAmount(amount, recipientPublicKey) {
if (!this.poseidon) {
throw new Error('ElGamal not initialized. Call init() first.');
}
// Generate random value r
const randomBytes = new Uint8Array(32);
crypto.getRandomValues(randomBytes);
const randomness = BigInt('0x' + Array.from(randomBytes)
.map(b => b.toString(16).padStart(2, '0'))
.join('')
);
// c1 = g^r = Poseidon(r)
const c1 = this.poseidon.F.toString(
this.poseidon([randomness])
);
// c2 = h^r + m = Poseidon(pubkey, r) + amount
const sharedSecret = this.poseidon.F.toString(
this.poseidon([BigInt(recipientPublicKey), randomness])
);
const c2 = (BigInt(sharedSecret) + BigInt(amount)).toString();
return {
c1: c1,
c2: c2,
randomness: randomness.toString()
};
}
/**
* For merchants: Decrypt a payment amount with secret key
* @param {string} c1 - ElGamal ciphertext component 1
* @param {string} c2 - ElGamal ciphertext component 2
* @param {string} secretKey - Merchant's ElGamal secret key
* @returns {string} - Decrypted amount in lamports
*/
decryptAmount(c1, c2, secretKey) {
if (!this.poseidon) {
throw new Error('ElGamal not initialized. Call init() first.');
}
// Reconstruct public key from secret key
const publicKey = this.poseidon.F.toString(
this.poseidon([BigInt(secretKey)])
);
// Decrypt: m = c2 - Poseidon(publicKey, c1)
const sharedSecret = this.poseidon.F.toString(
this.poseidon([BigInt(publicKey), BigInt(c1)])
);
let amount = BigInt(c2) - BigInt(sharedSecret);
// Handle negative values (field arithmetic wrap-around)
const FIELD_SIZE = 2n ** 254n;
if (amount < 0n) {
amount += FIELD_SIZE;
}
return amount.toString();
}
}
// Usage example
const elgamal = new ElGamalEncryptor();
await elgamal.init();
// Encrypt payment amount
const encrypted = elgamal.encryptAmount(
1000000, // 0.001 SOL in lamports
merchantElGamalPubkey
);
console.log('Encrypted amount:', {
c1: encrypted.c1,
c2: encrypted.c2
// randomness is kept secret, used in ZK proof
});
// For merchants: Decrypt received payment
const decrypted = elgamal.decryptAmount(
encrypted.c1,
encrypted.c2,
merchantElGamalSecretKey
);
console.log('Decrypted amount:', decrypted, 'lamports');Integration with Payment Flow
async function integratedPaymentExample() {
// Initialize ElGamal
const elgamal = new ElGamalEncryptor();
await elgamal.init();
// Get merchant's ElGamal public key
const merchantInfo = await fetch(
`https://shadow.radr.fun/api/merchant/info/${merchantWallet}`
).then(r => r.json());
const merchantElGamalPubkey = merchantInfo.elgamal_public_key;
// Encrypt payment amount
const amount = 1000000; // 0.001 SOL
const encrypted = elgamal.encryptAmount(amount, merchantElGamalPubkey);
console.log('📦 Encrypted payment amount');
console.log(' c1:', encrypted.c1.substring(0, 20) + '...');
console.log(' c2:', encrypted.c2.substring(0, 20) + '...');
// Use encrypted amount in ZK proof generation
const proofData = await generateGroth16Proof({
// ... other witness inputs
encrypted_amount_c1: encrypted.c1,
encrypted_amount_c2: encrypted.c2,
elgamal_randomness: encrypted.randomness,
receiver_elgamal_pubkey: merchantElGamalPubkey
});
// Payment is now private - only merchant can decrypt the amount!
return { encrypted, proofData };
}Accept Payments For Anything
Private, instant crypto payments for merchants of all types
E-Commerce & Online Stores
Accept crypto payments for products and services
- •Digital goods
- •Physical products
- •Subscription services
Content Creators
Monetize your content with private payments
- •Premium articles
- •Exclusive videos
- •Digital downloads
SaaS & Software
Recurring payments for software and services
- •Monthly subscriptions
- •Usage-based billing
- •License keys
NFTs & Digital Assets
Private transactions for digital collectibles
- •NFT purchases
- •In-game items
- •Digital art
How Payment Works (Customer Experience)
Customer Initiates Payment
Customer selects product and clicks pay. Their wallet connects automatically (Phantom, Backpack, etc.).
Instant Authorization (147ms)
ShadowPay checks escrow balance and authorizes payment. Customer receives instant confirmation.
Immediate Delivery
Merchant receives notification and delivers product/service immediately. No waiting for blockchain confirmations.
Background Settlement (5-15s)
Zero-knowledge proof generated and settlement completes on-chain. Payment amount stays private with ElGamal encryption.
Total customer wait time: ~200ms ⚡
ShadowPay API Endpoints
Core API endpoints for payment integration
Merchant Setup
/shadowpay/v1/keys/newCreate merchant API key
/shadowpay/v1/keys/by-wallet/{wallet}Get API key by wallet
Pool Management
/shadowpay/api/pool/balance/{wallet}Check user pool balance
/shadowpay/api/pool/depositDeposit SOL to pool
/shadowpay/api/pool/withdrawWithdraw SOL from pool
Payment Flow
/shadowpay/verifyVerify payment (instant check)
/shadowpay/settleSettle payment on-chain
/v1/payment/verify-accessVerify access token
Merchant Analytics
/v1/merchant/revenueTrack earnings (requires X-API-Key)
Circuit Artifacts Reference
Complete reference of all ZK circuit files. The SDK handles downloading automatically (see Quick Start Step 4), but these endpoints are available if you need manual access.
Main Circuit (Payment Proof)
/shadowpay/circuit/shadowpay_final.zkeyDownload proving key (main circuit) • ~50 MB
/shadowpay/circuit/shadowpay_js/shadowpay.wasmDownload WASM (main circuit) • ~2 MB
ElGamal Circuit (Amount Encryption)
/shadowpay/circuit-elgamal/shadowpay-elgamal_final.zkeyDownload ElGamal proving key • ~45 MB
/shadowpay/circuit-elgamal/shadowpay-elgamal_js/shadowpay-elgamal.wasmDownload ElGamal WASM • ~1.8 MB
Advanced: Manual Circuit Loading
// For advanced use cases where you need direct circuit access
async function loadCircuitsManually() {
const circuitWasm = await fetch(
'https://shadow.radr.fun/shadowpay/circuit-elgamal/shadowpay-elgamal_js/shadowpay-elgamal.wasm'
).then(r => r.arrayBuffer());
const provingKey = await fetch(
'https://shadow.radr.fun/shadowpay/circuit-elgamal/shadowpay-elgamal_final.zkey'
).then(r => r.arrayBuffer());
// Use with snarkjs for custom proof generation
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
witnessInput,
new Uint8Array(circuitWasm),
new Uint8Array(provingKey)
);
return { proof, publicSignals };
}
// For most use cases, just use the SDK (see Quick Start Step 4)
const shadowpay = new ShadowPayClient({ baseUrl: 'https://shadow.radr.fun' });
await shadowpay.init(); // Handles everything automatically💡 Most merchants should use the SDK's automatic download (Quick Start Step 4). Manual loading is only needed for custom ZK implementations.
Token Verification
Verify access tokens using JWT (recommended) or API calls
Option A: JWT Verification (Stateless)
const jwt = require('jsonwebtoken');
app.get('/api/premium-data', async (req, res) => {
const token = req.headers['authorization']?.replace('Bearer ', '');
if (!token) {
return res.status(402).json({ error: 'Payment required' });
}
try {
const decoded = jwt.verify(token, process.env.SHADOWPAY_JWT_SECRET);
if (decoded.exp < Math.floor(Date.now() / 1000)) {
return res.status(402).json({ error: 'Token expired' });
}
return res.json({ data: 'Premium content!' });
} catch (error) {
return res.status(402).json({ error: 'Invalid token' });
}
});Option B: API Verification (Stateful)
const axios = require('axios');
app.get('/api/premium-data', async (req, res) => {
const token = req.headers['authorization']?.replace('Bearer ', '');
if (!token) {
return res.status(402).json({ error: 'Payment required' });
}
try {
const verify = await axios.get(
'https://shadow.radr.fun/v1/payment/verify-access',
{ headers: { 'Authorization': `Bearer ${token}` } }
);
if (verify.data.valid) {
return res.json({ data: 'Premium content!' });
}
return res.status(402).json({ error: 'Payment not authorized' });
} catch (error) {
return res.status(402).json({ error: 'Verification failed' });
}
});Track Revenue
Check your earnings via the API:
curl https://shadow.radr.fun/v1/merchant/revenue \
-H "X-API-Key: YOUR_API_KEY"{
"total_revenue": "15450000",
"total_payments": 1545,
"recent_payments": [
{
"commitment": "14282575239580498641995859646780629054729399361619398221561866420322654695676",
"amount": 1000000,
"status": "settled",
"timestamp": 1762764531
}
]
}Pricing & Economics
What You Pay:
- •Solana transaction fees: ~0.000005 SOL per settlement
- •ShadowPay fee: 0% (currently free during beta)
What You Charge:
- •Digital products: 0.01 - 10 SOL per item
- •Subscriptions: 0.1 - 100 SOL per month
- •NFTs & collectibles: 0.1 - 1000+ SOL
- •Services: Set your own pricing in SOL or SPL tokens
Webhook Notifications
Get notified when payments settle (optional)
app.post('/webhooks/shadowpay', async (req, res) => {
const { commitment, status, amount, tx_hash } = req.body;
if (status === 'settled') {
console.log(`✅ Payment settled: ${amount} lamports`);
console.log(`TX: ${tx_hash}`);
// Update database, grant access, etc.
}
res.sendStatus(200);
});Security Best Practices
✅ DO:
- ✓Store API keys in environment variables
- ✓Use HTTPS for all API calls
- ✓Verify tokens on every request
- ✓Set reasonable amount limits
- ✓Log all transactions for auditing
❌ DON'T:
- ✗Expose API keys in client-side code
- ✗Trust client-provided amounts without verification
- ✗Skip token verification
- ✗Store sensitive data in access tokens
Traditional vs ShadowPay x402
See how ShadowPay compares to legacy HTTP 402 implementations
| Feature | Traditional x402 | ShadowPay x402 |
|---|---|---|
| Payment Speed | 5-10 seconds | 147ms |
| Customer Experience | Wait for confirmations | Instant delivery |
| Privacy | All transactions public | Amounts encrypted |
| Chargebacks | Yes (high risk) | None (final settlement) |
| Global Access | Country restrictions | Permissionless |
| Settlement Time | 2-7 business days | 5-15 seconds |
Frequently Asked Questions
Everything you need to know about integrating ShadowPay
Stay off the RADR.
Join the next generation of private payment infrastructure.