ShadowPay Docs

Back to Homepage

> 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_needed on 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. 1.Install Phantom wallet (or compatible Solana wallet)
  2. 2.Connect wallet to a ShadowPay-enabled site
  3. 3.Deposit SOL to your escrow (first time: +0.002 SOL rent)
  4. 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. 1.Click "Pay with ShadowPay" on merchant site
  2. 2.Check your escrow balance (auto-displayed)
  3. 3.If needed, deposit more SOL to escrow
  4. 4.Wait 5-10 seconds for ZK proof generation
  5. 5.Payment settles automatically (gasless!)
  6. 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_STRING

Settlement 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.

Most popular

FREE

100,000 settlements per month
50 requests per second

0 $RADR

Coming soon

BASIC

1,000,000 settlements per month
100 requests per second

$1000 worth of $RADR

Coming soon

PRO

5,000,000 settlements per month
250 requests per second

$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.

>