⭕
zkvoid Docs
  • ☀️Overview
  • 📗Getting Started
    • ⚙️Setup Token Account
    • ⬇️Deposit Tokens
    • 👨‍🔬Apply Pending Balance
    • 🛫Send Confidential Transfers
    • ⬆️Withdraw Tokens
    • 🤔Understanding Balances
  • 🔎Void vs. CLI
  • ⭕cSOL
  • 📶API
    • ✍️Documentation
    • 👨‍🍳Cookbook
    • 👨‍🔧Deep Dive
Powered by GitBook
On this page
  • ZK Void Confidential Transfer Integration Cookbook
  • Table of Contents
  • Quick Start
  • Common Patterns
  • Use Cases
  • Advanced Examples
  • Troubleshooting
  • Best Practices
  • Summary
  1. API

Cookbook

ZK Void Confidential Transfer Integration Cookbook

This cookbook provides practical examples and patterns for integrating confidential transfers into your dApps using the ZK Void API.

Table of Contents

  • Quick Start

  • Common Patterns

  • Use Cases

  • Advanced Examples

  • Troubleshooting

  • Best Practices

Quick Start

1. Basic Setup

import { Connection, PublicKey, Transaction } from '@solana/web3.js';
import { useWallet } from '@solana/wallet-adapter-react';

const API_BASE_URL = 'https://api.zkvoid.com';
const RPC_URL = 'https://api.mainnet-beta.solana.com'; // Use your preferred RPC

class ConfidentialTransferClient {
  private connection: Connection;

  constructor(rpcUrl: string = RPC_URL) {
    this.connection = new Connection(rpcUrl, 'confirmed');
  }

  async callAPI<T>(endpoint: string, data: any): Promise<T> {
    const response = await fetch(`${API_BASE_URL}${endpoint}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data),
    });

    if (!response.ok) {
      throw new Error(`API request failed: ${response.statusText}`);
    }

    const result = await response.json();
    if (!result.success) {
      throw new Error(result.error);
    }

    return result.data;
  }

  async getKeyDerivationSignature(wallet: any): Promise<string> {
    const message = `Confidential Transfer Key Derivation: ${wallet.publicKey.toString()}`;
    const messageBytes = new TextEncoder().encode(message);
    const signature = await wallet.signMessage(messageBytes);
    return Buffer.from(signature).toString('base64');
  }

  async executeTransaction(wallet: any, transactionBase64: string): Promise<string> {
    const transaction = Transaction.from(Buffer.from(transactionBase64, 'base64'));
    const { blockhash } = await this.connection.getLatestBlockhash();
    
    transaction.recentBlockhash = blockhash;
    transaction.feePayer = wallet.publicKey;
    
    const signedTx = await wallet.signTransaction(transaction);
    const signature = await this.connection.sendRawTransaction(signedTx.serialize());
    await this.connection.confirmTransaction(signature, 'confirmed');
    
    return signature;
  }
}

2. React Component Example

import React, { useState, useEffect } from 'react';
import { useWallet } from '@solana/wallet-adapter-react';

interface ConfidentialWalletProps {
  mintAddress: string;
}

export function ConfidentialWallet({ mintAddress }: ConfidentialWalletProps) {
  const wallet = useWallet();
  const [client] = useState(() => new ConfidentialTransferClient());
  const [balance, setBalance] = useState({ available: 0, pending: 0, public: 0 });
  const [loading, setLoading] = useState(false);

  const loadBalance = async () => {
    if (!wallet.publicKey || !wallet.signMessage) return;

    setLoading(true);
    try {
      const signature = await client.getKeyDerivationSignature(wallet);
      const balanceData = await client.callAPI('/balance', {
        owner_pubkey: wallet.publicKey.toString(),
        signature,
        mint_address: mintAddress,
      });

      setBalance({
        available: balanceData.available_balance,
        pending: balanceData.pending_balance,
        public: balanceData.public_balance,
      });
    } catch (error) {
      console.error('Failed to load balance:', error);
    } finally {
      setLoading(false);
    }
  };

  const setupAccount = async () => {
    if (!wallet.publicKey || !wallet.signMessage) return;

    setLoading(true);
    try {
      const signature = await client.getKeyDerivationSignature(wallet);
      const response = await client.callAPI('/setup-account', {
        owner_pubkey: wallet.publicKey.toString(),
        signature,
        mint_address: mintAddress,
      });

      if (response.transaction !== "Account already exists") {
        await client.executeTransaction(wallet, response.transaction);
      }
      
      await loadBalance();
    } catch (error) {
      console.error('Failed to setup account:', error);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    if (wallet.connected) {
      loadBalance();
    }
  }, [wallet.connected]);

  return (
    <div className="p-6 bg-gray-800 rounded-lg">
      <h2 className="text-xl font-bold mb-4">Confidential Wallet</h2>
      
      {wallet.connected ? (
        <div>
          <div className="grid grid-cols-3 gap-4 mb-6">
            <div className="text-center">
              <p className="text-sm text-gray-400">Available</p>
              <p className="text-lg font-bold">{balance.available}</p>
            </div>
            <div className="text-center">
              <p className="text-sm text-gray-400">Pending</p>
              <p className="text-lg font-bold">{balance.pending}</p>
            </div>
            <div className="text-center">
              <p className="text-sm text-gray-400">Public</p>
              <p className="text-lg font-bold">{balance.public}</p>
            </div>
          </div>

          <div className="space-y-2">
            <button
              onClick={setupAccount}
              disabled={loading}
              className="w-full py-2 px-4 bg-blue-600 text-white rounded disabled:opacity-50"
            >
              {loading ? 'Processing...' : 'Setup Account'}
            </button>
            
            <button
              onClick={loadBalance}
              disabled={loading}
              className="w-full py-2 px-4 bg-gray-600 text-white rounded disabled:opacity-50"
            >
              Refresh Balance
            </button>
          </div>
        </div>
      ) : (
        <p>Please connect your wallet</p>
      )}
    </div>
  );
}

Common Patterns

1. Token Discovery and Selection

interface TokenInfo {
  mint: string;
  name: string;
  symbol: string;
  decimals: number;
  balance: number;
  hasConfidentialTransfer: boolean;
}

class TokenDiscovery {
  private connection: Connection;

  constructor(rpcUrl: string) {
    this.connection = new Connection(rpcUrl, 'confirmed');
  }

  async getConfidentialTokens(walletPublicKey: PublicKey): Promise<TokenInfo[]> {
    // Get all Token 2022 accounts
    const tokenAccounts = await this.connection.getParsedTokenAccountsByOwner(
      walletPublicKey,
      { programId: new PublicKey('TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb') }
    );

    const confidentialTokens: TokenInfo[] = [];

    for (const account of tokenAccounts.value) {
      const accountInfo = account.account.data.parsed.info;
      const mint = accountInfo.mint;
      const balance = parseFloat(accountInfo.tokenAmount.uiAmountString || '0');

      if (balance === 0) continue;

      // Check for confidential transfer extension
      const hasConfidentialTransfer = await this.checkConfidentialExtension(mint);
      
      if (hasConfidentialTransfer) {
        const metadata = await this.getTokenMetadata(mint);
        
        confidentialTokens.push({
          mint,
          name: metadata?.name || `Token ${mint.slice(0, 8)}...`,
          symbol: metadata?.symbol || 'UNKNOWN',
          decimals: accountInfo.tokenAmount.decimals,
          balance,
          hasConfidentialTransfer: true,
        });
      }
    }

    return confidentialTokens;
  }

  private async checkConfidentialExtension(mint: string): Promise<boolean> {
    try {
      const mintAccount = await this.connection.getAccountInfo(new PublicKey(mint));
      if (!mintAccount) return false;

      // Simple heuristic: Token 2022 mints with extensions are larger than 82 bytes
      return mintAccount.data.length > 82;
    } catch {
      return false;
    }
  }

  private async getTokenMetadata(mint: string) {
    try {
      // Try Jupiter token list
      const response = await fetch('https://token.jup.ag/strict');
      const tokens = await response.json();
      return tokens.find((t: any) => t.address === mint);
    } catch {
      return null;
    }
  }
}

2. Multi-Step Transaction Handler

class MultiTransactionHandler {
  private client: ConfidentialTransferClient;

  constructor(client: ConfidentialTransferClient) {
    this.client = client;
  }

  async executeMultiTransaction(
    wallet: any,
    response: { transactions: string[]; descriptions: string[]; additional_signers?: string[] },
    onProgress?: (step: number, total: number, description: string) => void
  ): Promise<string[]> {
    const signatures: string[] = [];
    const total = response.transactions.length;

    for (let i = 0; i < total; i++) {
      const description = response.descriptions[i] || `Transaction ${i + 1}`;
      onProgress?.(i + 1, total, description);

      try {
        let signature: string;

        if (response.additional_signers?.[i]) {
          signature = await this.executeWithAdditionalSigners(
            wallet,
            response.transactions[i],
            response.additional_signers[i]
          );
        } else {
          signature = await this.client.executeTransaction(wallet, response.transactions[i]);
        }

        signatures.push(signature);
      } catch (error) {
        console.error(`Failed to execute transaction ${i + 1}:`, error);
        throw new Error(`Transaction ${i + 1} failed: ${error.message}`);
      }
    }

    return signatures;
  }

  private async executeWithAdditionalSigners(
    wallet: any,
    transactionBase64: string,
    additionalSignerBase64: string
  ): Promise<string> {
    const transaction = Transaction.from(Buffer.from(transactionBase64, 'base64'));
    const { blockhash } = await this.client.connection.getLatestBlockhash();
    
    transaction.recentBlockhash = blockhash;
    transaction.feePayer = wallet.publicKey;

    // Add additional signers
    if (additionalSignerBase64) {
      const { Keypair } = await import('@solana/web3.js');
      const signerBytes = Buffer.from(additionalSignerBase64, 'base64');
      
      // Handle multiple keypairs (64 bytes each)
      const keypairSize = 64;
      const numKeypairs = signerBytes.length / keypairSize;
      
      for (let i = 0; i < numKeypairs; i++) {
        const start = i * keypairSize;
        const end = start + keypairSize;
        const keypairBytes = signerBytes.slice(start, end);
        
        if (keypairBytes.length === keypairSize) {
          const signer = Keypair.fromSecretKey(keypairBytes);
          transaction.partialSign(signer);
        }
      }
    }

    const signedTx = await wallet.signTransaction(transaction);
    const signature = await this.client.connection.sendRawTransaction(signedTx.serialize());
    await this.client.connection.confirmTransaction(signature, 'confirmed');
    
    return signature;
  }
}

3. Error Handling and Retry Logic

class RobustConfidentialClient extends ConfidentialTransferClient {
  async callAPIWithRetry<T>(
    endpoint: string,
    data: any,
    maxRetries: number = 3,
    delay: number = 1000
  ): Promise<T> {
    let lastError: Error;

    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        return await this.callAPI<T>(endpoint, data);
      } catch (error) {
        lastError = error as Error;
        console.warn(`Attempt ${attempt} failed:`, error.message);

        if (attempt < maxRetries) {
          await new Promise(resolve => setTimeout(resolve, delay * attempt));
        }
      }
    }

    throw new Error(`Failed after ${maxRetries} attempts: ${lastError.message}`);
  }

  async executeTransactionWithRetry(
    wallet: any,
    transactionBase64: string,
    maxRetries: number = 3
  ): Promise<string> {
    let lastError: Error;

    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        return await this.executeTransaction(wallet, transactionBase64);
      } catch (error) {
        lastError = error as Error;
        console.warn(`Transaction attempt ${attempt} failed:`, error.message);

        // Don't retry certain errors
        if (error.message.includes('insufficient funds') || 
            error.message.includes('invalid signature')) {
          throw error;
        }

        if (attempt < maxRetries) {
          await new Promise(resolve => setTimeout(resolve, 2000 * attempt));
        }
      }
    }

    throw new Error(`Transaction failed after ${maxRetries} attempts: ${lastError.message}`);
  }
}

Use Cases

1. Private Payment System

class PrivatePaymentSystem {
  private client: ConfidentialTransferClient;
  private mintAddress: string;

  constructor(mintAddress: string) {
    this.client = new ConfidentialTransferClient();
    this.mintAddress = mintAddress;
  }

  async sendPrivatePayment(
    senderWallet: any,
    recipientAddress: string,
    amount: number,
    onProgress?: (step: string) => void
  ): Promise<{ signatures: string[]; totalFee: number }> {
    onProgress?.('Preparing transfer...');

    const signature = await this.client.getKeyDerivationSignature(senderWallet);
    
    // Check sender balance
    const balance = await this.client.callAPI('/balance', {
      owner_pubkey: senderWallet.publicKey.toString(),
      signature,
      mint_address: this.mintAddress,
    });

    if (balance.available_balance < amount) {
      throw new Error('Insufficient confidential balance');
    }

    onProgress?.('Generating zero-knowledge proofs...');

    // Execute transfer
    const response = await this.client.callAPI('/transfer', {
      sender_pubkey: senderWallet.publicKey.toString(),
      sender_signature: signature,
      recipient_pubkey: recipientAddress,
      amount,
      mint_address: this.mintAddress,
    });

    onProgress?.('Executing transactions...');

    const handler = new MultiTransactionHandler(this.client);
    const signatures = await handler.executeMultiTransaction(
      senderWallet,
      response,
      (step, total, desc) => onProgress?.(`${desc} (${step}/${total})`)
    );

    // Calculate total fees (estimate)
    const totalFee = signatures.length * 5000; // ~0.000005 SOL per transaction

    return { signatures, totalFee };
  }

  async setupRecipientAccount(recipientWallet: any): Promise<string> {
    const signature = await this.client.getKeyDerivationSignature(recipientWallet);
    
    const response = await this.client.callAPI('/setup-account', {
      owner_pubkey: recipientWallet.publicKey.toString(),
      signature,
      mint_address: this.mintAddress,
    });

    if (response.transaction === "Account already exists") {
      return "Account already configured";
    }

    return await this.client.executeTransaction(recipientWallet, response.transaction);
  }
}

2. Confidential Escrow Service

interface EscrowConfig {
  mintAddress: string;
  escrowDuration: number; // in seconds
  arbitratorAddress?: string;
}

class ConfidentialEscrow {
  private client: ConfidentialTransferClient;
  private config: EscrowConfig;

  constructor(config: EscrowConfig) {
    this.client = new ConfidentialTransferClient();
    this.config = config;
  }

  async createEscrow(
    buyerWallet: any,
    sellerAddress: string,
    amount: number
  ): Promise<{ escrowId: string; signatures: string[] }> {
    // 1. Setup buyer's confidential account if needed
    await this.ensureAccountSetup(buyerWallet);

    // 2. Check buyer has sufficient balance
    const balance = await this.getBalance(buyerWallet);
    if (balance.available < amount) {
      throw new Error('Insufficient balance for escrow');
    }

    // 3. Create escrow by transferring to escrow service
    // (In a real implementation, you'd transfer to an escrow program)
    const escrowId = this.generateEscrowId();
    
    // For demo: transfer to a temporary holding account
    const signatures = await this.transferToEscrow(buyerWallet, amount);

    return { escrowId, signatures };
  }

  async releaseEscrow(
    escrowId: string,
    buyerWallet: any,
    sellerAddress: string,
    amount: number
  ): Promise<string[]> {
    // Verify escrow exists and is valid
    // In production, check escrow program state
    
    // Transfer from escrow to seller
    const signature = await this.client.getKeyDerivationSignature(buyerWallet);
    
    const response = await this.client.callAPI('/transfer', {
      sender_pubkey: buyerWallet.publicKey.toString(),
      sender_signature: signature,
      recipient_pubkey: sellerAddress,
      amount,
      mint_address: this.config.mintAddress,
    });

    const handler = new MultiTransactionHandler(this.client);
    return await handler.executeMultiTransaction(buyerWallet, response);
  }

  private async ensureAccountSetup(wallet: any): Promise<void> {
    try {
      await this.getBalance(wallet);
    } catch (error) {
      if (error.message.includes('Account not found')) {
        const signature = await this.client.getKeyDerivationSignature(wallet);
        const response = await this.client.callAPI('/setup-account', {
          owner_pubkey: wallet.publicKey.toString(),
          signature,
          mint_address: this.config.mintAddress,
        });

        if (response.transaction !== "Account already exists") {
          await this.client.executeTransaction(wallet, response.transaction);
        }
      } else {
        throw error;
      }
    }
  }

  private async getBalance(wallet: any) {
    const signature = await this.client.getKeyDerivationSignature(wallet);
    return await this.client.callAPI('/balance', {
      owner_pubkey: wallet.publicKey.toString(),
      signature,
      mint_address: this.config.mintAddress,
    });
  }

  private generateEscrowId(): string {
    return `escrow_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  private async transferToEscrow(wallet: any, amount: number): Promise<string[]> {
    // Implementation would transfer to escrow program
    // For demo purposes, this is simplified
    return ['demo_escrow_signature'];
  }
}

3. Anonymous Voting System

interface VoteOption {
  id: string;
  title: string;
  description: string;
}

interface Poll {
  id: string;
  title: string;
  description: string;
  options: VoteOption[];
  endTime: Date;
  tokenWeight: number; // tokens required per vote
}

class AnonymousVoting {
  private client: ConfidentialTransferClient;
  private votingMint: string;
  private votingAuthority: string;

  constructor(votingMint: string, votingAuthority: string) {
    this.client = new ConfidentialTransferClient();
    this.votingMint = votingMint;
    this.votingAuthority = votingAuthority;
  }

  async castVote(
    voterWallet: any,
    pollId: string,
    optionId: string,
    voteWeight: number
  ): Promise<{ voteId: string; signature: string }> {
    // 1. Verify voter has sufficient tokens
    const balance = await this.getVoterBalance(voterWallet);
    if (balance.available < voteWeight) {
      throw new Error('Insufficient voting tokens');
    }

    // 2. Transfer tokens to voting authority (burns vote tokens)
    const signature = await this.client.getKeyDerivationSignature(voterWallet);
    
    const response = await this.client.callAPI('/transfer', {
      sender_pubkey: voterWallet.publicKey.toString(),
      sender_signature: signature,
      recipient_pubkey: this.votingAuthority,
      amount: voteWeight,
      mint_address: this.votingMint,
    });

    // 3. Execute the vote transfer
    const handler = new MultiTransactionHandler(this.client);
    const signatures = await handler.executeMultiTransaction(voterWallet, response);

    // 4. Record vote (in production, this would be on-chain)
    const voteId = this.generateVoteId(pollId, optionId);
    
    return { voteId, signature: signatures[0] };
  }

  async getVotingPower(voterWallet: any): Promise<number> {
    const balance = await this.getVoterBalance(voterWallet);
    return balance.available;
  }

  private async getVoterBalance(wallet: any) {
    const signature = await this.client.getKeyDerivationSignature(wallet);
    return await this.client.callAPI('/balance', {
      owner_pubkey: wallet.publicKey.toString(),
      signature,
      mint_address: this.votingMint,
    });
  }

  private generateVoteId(pollId: string, optionId: string): string {
    return `vote_${pollId}_${optionId}_${Date.now()}`;
  }
}

Advanced Examples

1. Batch Operations

class BatchOperations {
  private client: ConfidentialTransferClient;

  constructor() {
    this.client = new ConfidentialTransferClient();
  }

  async batchTransfer(
    senderWallet: any,
    transfers: Array<{ recipient: string; amount: number }>,
    mintAddress: string,
    onProgress?: (completed: number, total: number) => void
  ): Promise<string[]> {
    const allSignatures: string[] = [];
    const total = transfers.length;

    for (let i = 0; i < transfers.length; i++) {
      const { recipient, amount } = transfers[i];
      
      try {
        const signature = await this.client.getKeyDerivationSignature(senderWallet);
        
        const response = await this.client.callAPI('/transfer', {
          sender_pubkey: senderWallet.publicKey.toString(),
          sender_signature: signature,
          recipient_pubkey: recipient,
          amount,
          mint_address: mintAddress,
        });

        const handler = new MultiTransactionHandler(this.client);
        const signatures = await handler.executeMultiTransaction(senderWallet, response);
        
        allSignatures.push(...signatures);
        onProgress?.(i + 1, total);
        
        // Small delay to avoid rate limiting
        await new Promise(resolve => setTimeout(resolve, 1000));
        
      } catch (error) {
        console.error(`Failed to transfer to ${recipient}:`, error);
        throw new Error(`Batch transfer failed at recipient ${i + 1}: ${error.message}`);
      }
    }

    return allSignatures;
  }

  async batchSetup(
    wallets: any[],
    mintAddress: string,
    onProgress?: (completed: number, total: number) => void
  ): Promise<string[]> {
    const signatures: string[] = [];
    const total = wallets.length;

    for (let i = 0; i < wallets.length; i++) {
      const wallet = wallets[i];
      
      try {
        const signature = await this.client.getKeyDerivationSignature(wallet);
        
        const response = await this.client.callAPI('/setup-account', {
          owner_pubkey: wallet.publicKey.toString(),
          signature,
          mint_address: mintAddress,
        });

        if (response.transaction !== "Account already exists") {
          const txSignature = await this.client.executeTransaction(wallet, response.transaction);
          signatures.push(txSignature);
        }
        
        onProgress?.(i + 1, total);
        
      } catch (error) {
        console.error(`Failed to setup wallet ${i + 1}:`, error);
        // Continue with other wallets
      }
    }

    return signatures;
  }
}

2. Real-time Balance Monitoring

class BalanceMonitor {
  private client: ConfidentialTransferClient;
  private intervals: Map<string, NodeJS.Timeout> = new Map();

  constructor() {
    this.client = new ConfidentialTransferClient();
  }

  startMonitoring(
    wallet: any,
    mintAddress: string,
    callback: (balance: { available: number; pending: number; public: number }) => void,
    intervalMs: number = 10000
  ): string {
    const monitorId = `${wallet.publicKey.toString()}_${mintAddress}`;
    
    // Clear existing monitor if any
    this.stopMonitoring(monitorId);

    const interval = setInterval(async () => {
      try {
        const signature = await this.client.getKeyDerivationSignature(wallet);
        const balance = await this.client.callAPI('/balance', {
          owner_pubkey: wallet.publicKey.toString(),
          signature,
          mint_address: mintAddress,
        });

        callback({
          available: balance.available_balance,
          pending: balance.pending_balance,
          public: balance.public_balance,
        });
      } catch (error) {
        console.error('Balance monitoring error:', error);
      }
    }, intervalMs);

    this.intervals.set(monitorId, interval);
    return monitorId;
  }

  stopMonitoring(monitorId: string): void {
    const interval = this.intervals.get(monitorId);
    if (interval) {
      clearInterval(interval);
      this.intervals.delete(monitorId);
    }
  }

  stopAllMonitoring(): void {
    for (const [id, interval] of this.intervals) {
      clearInterval(interval);
    }
    this.intervals.clear();
  }
}

Troubleshooting

Common Issues and Solutions

1. "Account not found" Error

async function handleAccountNotFound(wallet: any, mintAddress: string) {
  try {
    // Try to get balance first
    const balance = await client.callAPI('/balance', {
      owner_pubkey: wallet.publicKey.toString(),
      signature: await client.getKeyDerivationSignature(wallet),
      mint_address: mintAddress,
    });
    return balance;
  } catch (error) {
    if (error.message.includes('Account not found')) {
      console.log('Setting up account...');
      
      // Setup the account
      const signature = await client.getKeyDerivationSignature(wallet);
      const response = await client.callAPI('/setup-account', {
        owner_pubkey: wallet.publicKey.toString(),
        signature,
        mint_address: mintAddress,
      });

      if (response.transaction !== "Account already exists") {
        await client.executeTransaction(wallet, response.transaction);
      }
      
      // Retry balance check
      return await client.callAPI('/balance', {
        owner_pubkey: wallet.publicKey.toString(),
        signature,
        mint_address: mintAddress,
      });
    }
    throw error;
  }
}

2. Transaction Failures

async function robustTransactionExecution(
  wallet: any,
  transactionBase64: string,
  maxRetries: number = 3
): Promise<string> {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await client.executeTransaction(wallet, transactionBase64);
    } catch (error) {
      console.warn(`Transaction attempt ${attempt} failed:`, error.message);
      
      // Check if it's a retryable error
      if (error.message.includes('blockhash not found') || 
          error.message.includes('network error')) {
        if (attempt < maxRetries) {
          await new Promise(resolve => setTimeout(resolve, 2000 * attempt));
          continue;
        }
      }
      
      // Non-retryable error or max retries reached
      throw error;
    }
  }
}

3. Rate Limiting

class RateLimitedClient extends ConfidentialTransferClient {
  private requestQueue: Array<() => Promise<any>> = [];
  private processing = false;
  private requestDelay = 1000; // 1 second between requests

  async callAPI<T>(endpoint: string, data: any): Promise<T> {
    return new Promise((resolve, reject) => {
      this.requestQueue.push(async () => {
        try {
          const result = await super.callAPI<T>(endpoint, data);
          resolve(result);
        } catch (error) {
          reject(error);
        }
      });

      this.processQueue();
    });
  }

  private async processQueue(): Promise<void> {
    if (this.processing || this.requestQueue.length === 0) {
      return;
    }

    this.processing = true;

    while (this.requestQueue.length > 0) {
      const request = this.requestQueue.shift()!;
      await request();
      
      if (this.requestQueue.length > 0) {
        await new Promise(resolve => setTimeout(resolve, this.requestDelay));
      }
    }

    this.processing = false;
  }
}

Best Practices

1. Error Handling

// Always wrap API calls in try-catch blocks
try {
  const result = await client.callAPI('/balance', requestData);
  // Handle success
} catch (error) {
  if (error.message.includes('Account not found')) {
    // Handle account setup
  } else if (error.message.includes('Insufficient balance')) {
    // Handle insufficient funds
  } else {
    // Handle other errors
    console.error('Unexpected error:', error);
  }
}

2. User Experience

// Provide clear feedback to users
const [status, setStatus] = useState('');
const [progress, setProgress] = useState(0);

const handleTransfer = async () => {
  setStatus('Preparing transfer...');
  setProgress(25);
  
  try {
    const signature = await client.getKeyDerivationSignature(wallet);
    setStatus('Generating proofs...');
    setProgress(50);
    
    const response = await client.callAPI('/transfer', requestData);
    setStatus('Executing transactions...');
    setProgress(75);
    
    const signatures = await handler.executeMultiTransaction(
      wallet,
      response,
      (step, total, desc) => {
        setStatus(`${desc} (${step}/${total})`);
        setProgress(75 + (step / total) * 25);
      }
    );
    
    setStatus('Transfer completed!');
    setProgress(100);
    return signatures;
  } catch (error) {
    setStatus(`Error: ${error.message}`);
    setProgress(0);
    throw error;
  }
};

3. Security

// Always validate inputs
function validateTransferInputs(amount: number, recipient: string) {
  if (!amount || amount <= 0) {
    throw new Error('Invalid amount');
  }
  
  if (!recipient || recipient.length !== 44) {
    throw new Error('Invalid recipient address');
  }
  
  try {
    new PublicKey(recipient);
  } catch {
    throw new Error('Invalid recipient public key format');
  }
}

// Verify transaction contents before signing
function verifyTransaction(transaction: Transaction, expectedAmount: number) {
  // Parse transaction instructions to verify amounts and recipients
  // This is a simplified example - implement based on your needs
  console.log('Verifying transaction:', transaction);
  
  // Check for suspicious instructions
  const suspiciousPrograms = [
    // Add known malicious program IDs
  ];
  
  for (const instruction of transaction.instructions) {
    if (suspiciousPrograms.includes(instruction.programId.toString())) {
      throw new Error('Suspicious transaction detected');
    }
  }
}

4. Performance Optimization

// Cache frequently used data
class CachedConfidentialClient extends ConfidentialTransferClient {
  private balanceCache = new Map<string, { balance: any; timestamp: number }>();
  private cacheTimeout = 30000; // 30 seconds

  async getCachedBalance(wallet: any, mintAddress: string) {
    const cacheKey = `${wallet.publicKey.toString()}_${mintAddress}`;
    const cached = this.balanceCache.get(cacheKey);
    
    if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
      return cached.balance;
    }

    const signature = await this.getKeyDerivationSignature(wallet);
    const balance = await this.callAPI('/balance', {
      owner_pubkey: wallet.publicKey.toString(),
      signature,
      mint_address: mintAddress,
    });

    this.balanceCache.set(cacheKey, {
      balance,
      timestamp: Date.now(),
    });

    return balance;
  }

  clearCache() {
    this.balanceCache.clear();
  }
}

// Batch operations when possible
class OptimizedOperations {
  private pendingOperations: Array<() => Promise<any>> = [];
  private batchTimeout: NodeJS.Timeout | null = null;

  queueOperation<T>(operation: () => Promise<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      this.pendingOperations.push(async () => {
        try {
          const result = await operation();
          resolve(result);
        } catch (error) {
          reject(error);
        }
      });

      this.scheduleBatch();
    });
  }

  private scheduleBatch() {
    if (this.batchTimeout) {
      clearTimeout(this.batchTimeout);
    }

    this.batchTimeout = setTimeout(() => {
      this.executeBatch();
    }, 100); // 100ms batch window
  }

  private async executeBatch() {
    const operations = [...this.pendingOperations];
    this.pendingOperations = [];
    this.batchTimeout = null;

    // Execute operations with small delays
    for (let i = 0; i < operations.length; i++) {
      await operations[i]();
      if (i < operations.length - 1) {
        await new Promise(resolve => setTimeout(resolve, 200));
      }
    }
  }
}

5. Testing

// Mock client for testing
class MockConfidentialClient extends ConfidentialTransferClient {
  private mockResponses = new Map<string, any>();

  setMockResponse(endpoint: string, response: any) {
    this.mockResponses.set(endpoint, response);
  }

  async callAPI<T>(endpoint: string, data: any): Promise<T> {
    const mockResponse = this.mockResponses.get(endpoint);
    if (mockResponse) {
      return mockResponse;
    }
    
    // Fallback to real API in test environment
    return super.callAPI<T>(endpoint, data);
  }

  async executeTransaction(wallet: any, transactionBase64: string): Promise<string> {
    // Return mock signature in test environment
    return 'mock_signature_' + Date.now();
  }
}

// Test utilities
function createMockWallet(publicKey: string) {
  return {
    publicKey: new PublicKey(publicKey),
    signMessage: async (message: Uint8Array) => {
      // Return deterministic mock signature
      return new Uint8Array(64).fill(1);
    },
    signTransaction: async (transaction: Transaction) => {
      // Return mock signed transaction
      return transaction;
    },
  };
}

// Integration tests
describe('Confidential Transfer Integration', () => {
  let client: MockConfidentialClient;
  let mockWallet: any;

  beforeEach(() => {
    client = new MockConfidentialClient();
    mockWallet = createMockWallet('11111111111111111111111111111112');
  });

  test('should setup account successfully', async () => {
    client.setMockResponse('/setup-account', {
      transaction: 'mock_transaction_base64',
      description: 'Setup account',
    });

    const signature = await client.getKeyDerivationSignature(mockWallet);
    const response = await client.callAPI('/setup-account', {
      owner_pubkey: mockWallet.publicKey.toString(),
      signature,
      mint_address: 'mock_mint_address',
    });

    expect(response.transaction).toBe('mock_transaction_base64');
  });

  test('should handle balance retrieval', async () => {
    client.setMockResponse('/balance', {
      available_balance: 1000,
      pending_balance: 500,
      public_balance: 2000,
    });

    const signature = await client.getKeyDerivationSignature(mockWallet);
    const balance = await client.callAPI('/balance', {
      owner_pubkey: mockWallet.publicKey.toString(),
      signature,
      mint_address: 'mock_mint_address',
    });

    expect(balance.available_balance).toBe(1000);
    expect(balance.pending_balance).toBe(500);
    expect(balance.public_balance).toBe(2000);
  });
});

6. Monitoring and Analytics

class AnalyticsClient {
  private events: Array<{ event: string; data: any; timestamp: number }> = [];

  track(event: string, data: any = {}) {
    this.events.push({
      event,
      data,
      timestamp: Date.now(),
    });

    // Send to analytics service
    this.sendToAnalytics(event, data);
  }

  private async sendToAnalytics(event: string, data: any) {
    try {
      // Send to your analytics service
      await fetch('/api/analytics', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ event, data, timestamp: Date.now() }),
      });
    } catch (error) {
      console.warn('Analytics tracking failed:', error);
    }
  }

  getEvents() {
    return [...this.events];
  }
}

// Usage in your application
const analytics = new AnalyticsClient();

// Track user actions
analytics.track('confidential_transfer_initiated', {
  amount,
  mintAddress,
  recipientAddress: recipientAddress.slice(0, 8) + '...', // Privacy-safe
});

analytics.track('confidential_transfer_completed', {
  transactionCount: signatures.length,
  totalFee,
  duration: Date.now() - startTime,
});

Summary

This cookbook provides comprehensive examples for integrating the ZK Void Confidential Transfer API into your dApps. Key takeaways:

  1. Start Simple: Begin with basic setup and balance checking

  2. Handle Errors Gracefully: Implement robust error handling and retry logic

  3. Provide Great UX: Keep users informed with progress indicators and clear messaging

  4. Security First: Always validate inputs and verify transaction contents

  5. Test Thoroughly: Use mocks and integration tests to ensure reliability

  6. Monitor Performance: Track usage and optimize for better user experience

For more detailed API reference, see the API Documentation.

PreviousDocumentationNextDeep Dive

Last updated 8 days ago

📶
👨‍🍳