> OVERVIEW_
// ZK-powered payment protocol for Solana with privacy, gasless transactions, and x402 support.
ShadowPay is a privacy-preserving payment system that combines zero-knowledge proofs (Groth16 + ElGamal encryption) with an escrow-based architecture to enable private, gasless payments on Solana. Payment amounts are encrypted, sender identities are hidden, and all transaction fees are handled by relayers.
→ Privacy: ElGamal-encrypted amounts + ZK proofs hide sender
→ Gasless: Relayer handles on-chain gas (0.0001 SOL service fee)
→ Escrow System: PDA-based accounts for fast settlements
→ x402 Protocol: HTTP 402 Payment Required standard
Live API: https://shadow.radr.fun/shadowpay
> ARCHITECTURE_
- Anchor Program: GQBqwwoikYh7p6KEUHDUu5r9dHHXx9tMGskAPubmFPzD
- PDA Escrow Accounts: Deterministic, auto-created on first deposit
- ZK Proof System: Groth16 circuit + ElGamal encryption
- Relayer Network: Handles gas fees and on-chain settlement
Payment Flow (Text Diagram):
[User Wallet]
↓ (1) Deposit SOL
[Escrow PDA] ← seeds=[b"escrow", user_pubkey]
↓ (2) Generate ZK Proof (client-side, 5-10s)
[Settler API] ← /verify proof
↓ (3) Return payment_token
[Settler API] ← /settle (with token)
↓ (4) Relayer signs & sends tx (gasless)
[Solana Chain] ← settlement confirmed
↓ (5) Escrow balance updated
[Merchant] ← receives funds> ESCROW ACCOUNTS_
IMPORTANT: Escrow is NOT a separate wallet. It's a Program Derived Address (PDA) controlled by the program.
What is an Escrow Account?
An escrow account is a PDA (Program Derived Address) automatically derived from your wallet address. It's created on your first deposit and stores your ShadowPay balance.
→ Formula: seeds = [b"escrow", user_pubkey]
Key Characteristics:
- → Deterministic: Same wallet = same escrow PDA every time
- → Auto-created: Uses
init_if_neededon first deposit - → One-time rent: ~0.002 SOL (paid on creation, reclaimed on close)
- → Program-owned: Only the program can modify it (via user signatures)
- → Account size: 65 bytes
Account Structure:
pub struct EscrowAccount {
pub owner: Pubkey, // Your wallet address
pub balance: u64, // Available SOL (in lamports)
pub total_deposited: u64, // Lifetime deposits
pub total_withdrawn: u64, // Lifetime withdrawals
pub bump: u8, // PDA bump seed
}Computing Escrow Address (Client-Side):
import { PublicKey } from '@solana/web3.js';
const PROGRAM_ID = new PublicKey('GQBqwwoikYh7p6KEUHDUu5r9dHHXx9tMGskAPubmFPzD');
function getEscrowPDA(userWallet: PublicKey): PublicKey {
const [escrowPDA] = PublicKey.findProgramAddressSync(
[Buffer.from('escrow'), userWallet.toBuffer()],
PROGRAM_ID
);
return escrowPDA;
}
// Usage:
const myWallet = wallet.publicKey;
const myEscrow = getEscrowPDA(myWallet);
console.log('My escrow PDA:', myEscrow.toString());Checking if Escrow Exists:
import { Connection } from '@solana/web3.js';
async function checkEscrowExists(connection: Connection, wallet: PublicKey): Promise<boolean> {
const escrowPDA = getEscrowPDA(wallet);
const accountInfo = await connection.getAccountInfo(escrowPDA);
return accountInfo !== null;
}
// Check balance via API (easier):
const response = await fetch(`https://shadow.radr.fun/shadowpay/api/escrow/balance/${wallet.toString()}`);
const data = await response.json();
console.log('Balance:', data.balance, 'SOL');Visual: Your Wallet → Escrow PDA (derived) → Payments deducted → Merchant receives
> FOR USERS_
Getting Started:
- 1.Install Phantom wallet (or compatible Solana wallet)
- 2.Connect wallet to a ShadowPay-enabled site
- 3.Deposit SOL to your escrow (first time: +0.002 SOL rent)
- 4.Make private payments — relayer handles gas!
First Deposit (Creates Escrow):
→ Ensure you have at least (amount + 0.002 SOL + 0.000005 SOL fee)
→ Example: To deposit 0.5 SOL, you need ~0.503 SOL in wallet
→ The 0.002 SOL rent is reclaimable if you close the account
Making a Payment:
- 1.Click "Pay with ShadowPay" on merchant site
- 2.Check your escrow balance (auto-displayed)
- 3.If needed, deposit more SOL to escrow
- 4.Wait 5-10 seconds for ZK proof generation
- 5.Payment settles automatically (gasless!)
- 6.Receive confirmation + transaction signature
Checking Balance:
Visit: https://shadow.radr.fun/shadowpay/api/escrow/balance/YOUR_WALLET_ADDRESS Or use the merchant's UI to see balance automatically.
Withdrawing Funds:
Currently, withdrawals require merchant signature or relayer authorization. Future updates will add user-initiated withdrawals.
If transaction fails: Check balance, refresh page, try desktop browser (mobile may run out of memory during proof generation).
> FOR DEVELOPERS_
Integration Overview:
ShadowPay requires three main scripts loaded in your HTML:
<!-- 1. Solana Web3.js --> <script src="https://unpkg.com/@solana/web3.js@latest/lib/index.iife.min.js"></script> <!-- 2. SnarkJS for ZK proofs --> <script src="https://unpkg.com/snarkjs@latest/build/snarkjs.min.js"></script> <!-- 3. ShadowPayClient --> <script src="https://shadow.radr.fun/shadowpay/shadowpay-client.js"></script>
Complete Payment Flow:
// Step 1: Connect wallet
const provider = window.solana;
if (!provider?.isPhantom) {
alert('Install Phantom wallet!');
return;
}
await provider.connect();
const walletAddress = provider.publicKey.toString();
// Step 2: Initialize ShadowPayClient
const shadowPay = new ShadowPayClient();
await shadowPay.initialize(); // Loads circuit artifacts (~5s)
// Step 3: Check escrow balance
const balanceRes = await fetch(
`https://shadow.radr.fun/shadowpay/api/escrow/balance/${walletAddress}`
);
const { balance } = await balanceRes.json();
console.log('Escrow balance:', balance, 'SOL');
// Step 4: Deposit if needed (first time or insufficient balance)
if (balance < paymentAmount) {
const depositRes = await fetch(
'https://shadow.radr.fun/shadowpay/api/escrow/deposit',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
wallet: walletAddress,
amount: paymentAmount, // in SOL
}),
}
);
const { transaction } = await depositRes.json();
// Decode and sign transaction
const txBuffer = Buffer.from(transaction, 'base64');
const tx = solanaWeb3.Transaction.from(txBuffer);
const signedTx = await provider.signTransaction(tx);
// Send transaction
const connection = new solanaWeb3.Connection(
'https://api.mainnet-beta.solana.com'
);
const signature = await connection.sendRawTransaction(signedTx.serialize());
// Wait for confirmation
await connection.confirmTransaction(signature, 'confirmed');
console.log('Deposit confirmed:', signature);
}
// Step 5: Generate ZK proof (client-side, 5-10 seconds)
console.log('Generating ZK proof...');
const proofData = await shadowPay.generatePaymentProof(
walletAddress,
merchantAddress,
paymentAmount,
resourceUrl
);
console.log('Proof generated!');
// Step 6: Verify proof with settler
const verifyRes = await fetch(
'https://shadow.radr.fun/shadowpay/verify',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(proofData),
}
);
const { valid, paymentToken } = await verifyRes.json();
if (!valid) {
alert('Proof verification failed!');
return;
}
// Step 7: Settle payment on-chain (gasless!)
const settleRes = await fetch(
'https://shadow.radr.fun/shadowpay/settle',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...proofData,
paymentToken,
resource: resourceUrl,
}),
}
);
const { status, txSignature } = await settleRes.json();
if (status === 'success') {
console.log('Payment successful!');
console.log('Transaction:', txSignature);
console.log('View on Solscan:', `https://solscan.io/tx/${txSignature}`);
} else {
alert('Settlement failed!');
}
// Step 8: Backend verification (CRITICAL!)
// Send paymentToken to your backend to verify with settler
// NEVER trust client-side verification alone!First-Time vs. Subsequent Deposits:
→ First deposit: Creates escrow PDA (costs ~0.002 SOL rent + tx fee)
→ Subsequent deposits: Just adds to balance (only tx fee ~0.000005 SOL)
→ Check if escrow exists first: getAccountInfo(escrowPDA)
Backend Verification (CRITICAL):
// Node.js backend example
app.post('/verify-payment', async (req, res) => {
const { paymentToken, resourceUrl, userId } = req.body;
// Verify with settler API
const response = await fetch(
'https://shadow.radr.fun/shadowpay/api/verify-token',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ paymentToken, resource: resourceUrl }),
}
);
const { valid, amount, merchantAddress } = await response.json();
if (!valid) {
return res.status(400).json({ error: 'Invalid payment' });
}
// Check if token already used (prevent replay attacks!)
const existingPayment = await db.payments.findOne({ paymentToken });
if (existingPayment) {
return res.status(400).json({ error: 'Payment already processed' });
}
// Store payment
await db.payments.create({
userId,
paymentToken,
amount,
resourceUrl,
timestamp: Date.now(),
});
// Grant access to resource
await grantAccess(userId, resourceUrl);
res.json({ success: true });
});Testing on Devnet:
→ Change RPC to devnet: https://api.devnet.solana.com
→ Get test SOL: https://faucet.solana.com
→ Use devnet program ID (if different)
→ Test full flow before mainnet launch
> API REFERENCE_
Base URL: https://shadow.radr.fun/shadowpay
GET /supported
Purpose: Check x402 protocol support
Request: None
curl https://shadow.radr.fun/shadowpay/supported
Response:
{
"supported": true,
"version": "1.0",
"network": "solana-mainnet"
}GET /api/escrow/balance/:wallet
Purpose: Get user's escrow balance
Parameters: wallet address (base58)
curl https://shadow.radr.fun/shadowpay/api/escrow/balance/YOUR_WALLET
// JavaScript
const res = await fetch(`https://shadow.radr.fun/shadowpay/api/escrow/balance/${wallet}`);
const data = await res.json();
console.log('Balance:', data.balance, 'SOL');Response:
{
"balance": 1.5,
"wallet": "YOUR_WALLET_ADDRESS"
}Errors:
→ 400: Invalid wallet address
→ 404: Escrow account doesn't exist (balance = 0)
POST /api/escrow/deposit
Purpose: Create unsigned deposit transaction
Request Body:
{
"wallet": "YOUR_WALLET_ADDRESS",
"amount": 0.5 // in SOL
}Response:
{
"transaction": "BASE64_ENCODED_TRANSACTION"
}
// Decode and sign:
const tx = solanaWeb3.Transaction.from(
Buffer.from(response.transaction, 'base64')
);
const signed = await wallet.signTransaction(tx);
const sig = await connection.sendRawTransaction(signed.serialize());Errors:
→ 400: Insufficient SOL in wallet
→ 400: Invalid amount (must be > 0)
Note: First deposit creates escrow (costs ~0.002 SOL rent)
POST /verify
Purpose: Verify ZK payment proof
Request Body:
{
"proof": ["string[]"], // Groth16 proof
"publicSignals": ["string[]"], // Public inputs
"encryptedAmount": "string", // ElGamal ciphertext
"amountProof": ["string[]"], // Amount range proof
"amountPublicSignals": ["string[]"] // Amount public inputs
}Response:
{
"valid": true,
"paymentToken": "JWT_TOKEN_STRING"
}Common Failures:
→ Invalid proof structure
→ Public signals don't match proof
→ Amount out of range
→ Encrypted amount verification failed
POST /settle
Purpose: Settle payment on-chain (relayer handles gas)
Request Body:
{
...verifyFields, // All fields from /verify
"paymentToken": "JWT_FROM_VERIFY",
"resource": "https://example.com/premium-content"
}Response:
{
"status": "success",
"txSignature": "TRANSACTION_SIGNATURE_STRING"
}
// View on Solscan:
https://solscan.io/tx/TRANSACTION_SIGNATURE_STRINGSettlement Process:
→ Relayer creates transaction
→ Relayer pays on-chain gas fees
→ Service fee (0.0001 SOL) deducted from escrow
→ Transaction sent to Solana
→ Escrow balance updated
→ Merchant receives funds
HTTP Status Codes:
→ 200: Success
→ 400: Bad request (invalid params)
→ 402: Payment Required (x402 protocol)
→ 403: Forbidden (invalid token)
→ 500: Server error
Rate Limiting:
→ 100 requests per minute per IP
→ 429 status if exceeded
→ Retry after 60 seconds
> INTEGRATION EXAMPLES_
Complete HTML/JavaScript Example (Copy-Paste Ready):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ShadowPay Demo</title>
<style>
body { font-family: monospace; background: #0A0E1A; color: #fff; padding: 20px; }
button { background: #FF6B2C; color: #fff; border: none; padding: 10px 20px; cursor: pointer; }
button:disabled { opacity: 0.5; cursor: not-allowed; }
#status { margin-top: 20px; }
</style>
</head>
<body>
<h1>ShadowPay Payment Demo</h1>
<button id="connectBtn">Connect Wallet</button>
<button id="payBtn" disabled>Pay 0.1 SOL</button>
<div id="status"></div>
<script src="https://unpkg.com/@solana/web3.js@latest/lib/index.iife.min.js"></script>
<script src="https://unpkg.com/snarkjs@latest/build/snarkjs.min.js"></script>
<script src="https://shadow.radr.fun/shadowpay/shadowpay-client.js"></script>
<script>
const status = document.getElementById('status');
const connectBtn = document.getElementById('connectBtn');
const payBtn = document.getElementById('payBtn');
let wallet = null;
let shadowPay = null;
connectBtn.addEventListener('click', async () => {
try {
status.textContent = 'Connecting...';
const provider = window.solana;
if (!provider?.isPhantom) {
alert('Please install Phantom wallet!');
return;
}
await provider.connect();
wallet = provider.publicKey.toString();
status.textContent = `Connected: ${wallet.slice(0,8)}...`;
connectBtn.disabled = true;
payBtn.disabled = false;
// Initialize ShadowPay
shadowPay = new ShadowPayClient();
await shadowPay.initialize();
// Check balance
const res = await fetch(`https://shadow.radr.fun/shadowpay/api/escrow/balance/${wallet}`);
const { balance } = await res.json();
status.textContent += ` | Escrow: ${balance} SOL`;
} catch (err) {
status.textContent = `Error: ${err.message}`;
}
});
payBtn.addEventListener('click', async () => {
try {
payBtn.disabled = true;
status.textContent = 'Processing payment...';
const MERCHANT = 'MERCHANT_WALLET_ADDRESS_HERE';
const AMOUNT = 0.1;
const RESOURCE = window.location.href;
// Check balance
const balRes = await fetch(`https://shadow.radr.fun/shadowpay/api/escrow/balance/${wallet}`);
const { balance } = await balRes.json();
// Deposit if needed
if (balance < AMOUNT) {
status.textContent = 'Depositing to escrow...';
const depRes = await fetch('https://shadow.radr.fun/shadowpay/api/escrow/deposit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ wallet, amount: AMOUNT }),
});
const { transaction } = await depRes.json();
const tx = solanaWeb3.Transaction.from(Buffer.from(transaction, 'base64'));
const signed = await window.solana.signTransaction(tx);
const connection = new solanaWeb3.Connection('https://api.mainnet-beta.solana.com');
const sig = await connection.sendRawTransaction(signed.serialize());
await connection.confirmTransaction(sig, 'confirmed');
status.textContent = 'Deposit confirmed!';
}
// Generate proof
status.textContent = 'Generating ZK proof (5-10s)...';
const proofData = await shadowPay.generatePaymentProof(wallet, MERCHANT, AMOUNT, RESOURCE);
// Verify
status.textContent = 'Verifying proof...';
const verifyRes = await fetch('https://shadow.radr.fun/shadowpay/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(proofData),
});
const { valid, paymentToken } = await verifyRes.json();
if (!valid) throw new Error('Proof verification failed');
// Settle
status.textContent = 'Settling payment...';
const settleRes = await fetch('https://shadow.radr.fun/shadowpay/settle', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...proofData, paymentToken, resource: RESOURCE }),
});
const { status: settleStatus, txSignature } = await settleRes.json();
if (settleStatus === 'success') {
status.innerHTML = `✓ Payment successful!<br>TX: <a href="https://solscan.io/tx/${txSignature}" target="_blank">${txSignature.slice(0,16)}...</a>`;
} else {
throw new Error('Settlement failed');
}
} catch (err) {
status.textContent = `Error: ${err.message}`;
} finally {
payBtn.disabled = false;
}
});
</script>
</body>
</html>React Component Example:
import { useState, useEffect } from 'react';
import { useWallet } from '@solana/wallet-adapter-react';
import { Connection, Transaction } from '@solana/web3.js';
declare global {
interface Window {
ShadowPayClient: any;
}
}
export function ShadowPayButton({ amount, merchantAddress, resourceUrl }) {
const { publicKey, signTransaction } = useWallet();
const [loading, setLoading] = useState(false);
const [status, setStatus] = useState('');
const [balance, setBalance] = useState(0);
useEffect(() => {
if (publicKey) {
checkBalance();
}
}, [publicKey]);
async function checkBalance() {
const res = await fetch(
`https://shadow.radr.fun/shadowpay/api/escrow/balance/${publicKey.toString()}`
);
const data = await res.json();
setBalance(data.balance);
}
async function handlePayment() {
if (!publicKey || !signTransaction) return;
setLoading(true);
try {
// Initialize client
const shadowPay = new window.ShadowPayClient();
await shadowPay.initialize();
// Deposit if needed
if (balance < amount) {
setStatus('Depositing...');
const depositRes = await fetch(
'https://shadow.radr.fun/shadowpay/api/escrow/deposit',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
wallet: publicKey.toString(),
amount,
}),
}
);
const { transaction } = await depositRes.json();
const tx = Transaction.from(Buffer.from(transaction, 'base64'));
const signed = await signTransaction(tx);
const connection = new Connection('https://api.mainnet-beta.solana.com');
const sig = await connection.sendRawTransaction(signed.serialize());
await connection.confirmTransaction(sig, 'confirmed');
}
// Generate proof
setStatus('Generating proof...');
const proofData = await shadowPay.generatePaymentProof(
publicKey.toString(),
merchantAddress,
amount,
resourceUrl
);
// Verify
setStatus('Verifying...');
const verifyRes = await fetch('https://shadow.radr.fun/shadowpay/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(proofData),
});
const { valid, paymentToken } = await verifyRes.json();
if (!valid) throw new Error('Verification failed');
// Settle
setStatus('Settling...');
const settleRes = await fetch('https://shadow.radr.fun/shadowpay/settle', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...proofData, paymentToken, resource: resourceUrl }),
});
const { status: settleStatus, txSignature } = await settleRes.json();
if (settleStatus === 'success') {
setStatus('Payment successful!');
await checkBalance();
} else {
throw new Error('Settlement failed');
}
} catch (err) {
setStatus(`Error: ${err.message}`);
} finally {
setLoading(false);
}
}
return (
<div>
<button onClick={handlePayment} disabled={!publicKey || loading}>
{loading ? 'Processing...' : `Pay ${amount} SOL`}
</button>
{status && <p>{status}</p>}
{publicKey && <p>Escrow: {balance} SOL</p>}
</div>
);
}> SECURITY_
Privacy Guarantees:
✓ Encrypted Amounts: ElGamal encryption hides payment values
✓ Hidden Sender: ZK proofs don't reveal wallet identity
✓ Selective Disclosure: Only sender & merchant can decrypt amounts
✓ On-Chain Privacy: Observers see: merchant, resource URL, encrypted amount
What's NOT Private:
→ Merchant wallet address (public by design)
→ Resource URL being paid for
→ Timestamp of payment
→ That a payment occurred
Developer Security Best Practices:
→ NEVER expose private keys client-side
→ ALWAYS verify payment tokens on your backend
→ Use HTTPS for all API calls
→ Validate payment amounts server-side
→ Implement payment token expiry (TTL)
→ Store used payment tokens to prevent replay attacks
→ Rate limit payment attempts
→ Log all payment attempts for audit trails
Smart Contract Security:
✓ Anchor program audited
✓ PDA ownership model prevents unauthorized access
✓ Relayer authorization enforced on-chain
✓ Balance checks before withdrawals
✓ No reentrancy vulnerabilities
> FAQ_
How does privacy work? What's actually private?
A: Payment amounts are encrypted using ElGamal encryption and proven valid via ZK-SNARKs. On-chain observers cannot see how much you paid, only that you paid. Your wallet identity is also hidden via zero-knowledge proofs. However, the merchant address and resource URL are public.
Why do I need to deposit to escrow first?
A: Escrow enables fast, convenient payments. You deposit once, then make multiple payments without signing each transaction. The relayer handles on-chain gas fees and settlement (charging a small 0.0001 SOL service fee per transaction), making the payment experience seamless.
How long does a payment take?
A: End-to-end payment timing:
- → Proof generation: 5-10 seconds (client-side)
- → Verification: <1 second (API call)
- → Settlement: 1-2 seconds (on-chain transaction)
- → Total: ~7-13 seconds
What are the fees?
A: Fee breakdown:
- → Escrow creation: ~0.002 SOL (one-time rent, paid on first deposit)
- → Deposit fee: ~0.000005 SOL (standard Solana tx fee)
- → Payment fee: 0.0001 SOL per transaction (relayer fee)
- → Platform fee: Currently 0%
Note: The relayer fee covers gas costs for on-chain settlement while maintaining privacy.
Can merchants decrypt payment amounts?
A: Yes, merchants receive the encrypted amount and can decrypt it using their private key. Only the sender and merchant can see the actual amount. On-chain observers only see the ciphertext.
What happens if proof generation fails?
A: Common causes and solutions:
- → Cause: Browser ran out of memory (ZK proofs are computation-heavy)
- → Cause: Circuit artifacts didn't load properly
- → Cause: Invalid parameters passed to proof generator
- → Solution: Retry, use desktop browser with 8GB+ RAM, close other tabs
- → Check: Open browser console for specific error messages
Is this on mainnet or devnet?
A: MAINNET. Program ID: GQBqwwoikYh7p6KEUHDUu5r9dHHXx9tMGskAPubmFPzD. View on Solscan.
Is the escrow address the same every time?
A: Yes! It's deterministically derived from your wallet address using seeds = [b"escrow", user_pubkey]. Same wallet = same escrow PDA every time. You can compute it client-side before any transactions.
> TROUBLESHOOTING_
Error: "Proof generation failed"
Cause: Browser ran out of memory or circuit artifacts didn't load
Solution: Use desktop browser (Chrome/Firefox), close other tabs, retry
Check: Open browser console for specific error messages
Error: "Insufficient escrow balance"
Cause: Not enough SOL in escrow account
Solution: Check balance with /api/escrow/balance/:wallet, deposit more SOL
Note: Balance must cover payment amount (fees are covered by relayer)
Error: "Transaction simulation failed"
Cause: Escrow account doesn't exist, or insufficient rent
Solution: First deposit creates escrow (costs ~0.002 SOL rent)
Check: Ensure wallet has enough SOL for deposit + rent
Error: "Wallet not connected"
Cause: Phantom not installed or not connected
Solution: Install Phantom wallet, refresh page, click connect
Check: window.solana should be defined
Slow proof generation (> 30 seconds)
Cause: Low-powered device or mobile browser
Solution: Use desktop with at least 8GB RAM
Optimization: Load circuit artifacts once, cache in memory
Browser Compatibility:
Supported: Chrome 90+, Firefox 88+, Safari 14+
Not Supported: Internet Explorer, old mobile browsers
Requirement: WebAssembly support required for ZK proofs
> NETWORKS & DEPLOYMENT_
Mainnet (Production):
→ Network: Solana Mainnet Beta
→ Program ID: GQBqwwoikYh7p6KEUHDUu5r9dHHXx9tMGskAPubmFPzD
→ API Base: https://shadow.radr.fun/shadowpay
→ RPC: Helius Mainnet
→ Explorer: View on Solscan
Circuit Artifacts (Hosted):
→ Base URL: https://shadow.radr.fun/shadowpay/
→ Main circuit WASM: /circuit/shadowpay_js/shadowpay.wasm
→ Main proving key: /circuit/shadowpay_final.zkey
→ ElGamal WASM: /circuit-elgamal/shadowpay-elgamal_js/shadowpay-elgamal.wasm
→ ElGamal proving key: /circuit-elgamal/shadowpay-elgamal_final.zkey
Transaction Explorers:
→ View program: Solscan Program
→ View transactions: https://solscan.io/tx/{ signature}
→ Check escrow accounts: https://solscan.io/account/{ escrowPDA}
> PRICING_
Choose a plan that fits your volume.
FREE
0 $RADR
BASIC
$1000 worth of $RADR
PRO
$3000 worth of $RADR
→ All plans include ZK privacy, x402 protocol support, and relayer-handled gas (0.0001 SOL service fee per transaction).
→ Enterprise plans with custom rate limits available. Contact us for details.