Advanced Examples

Advanced examples and patterns for Papaya SDK

This document covers advanced usage patterns and sophisticated integrations with the Papaya SDK.

Gas Optimization

Batching Transactions

If you need to perform multiple operations, consider batching them to minimize gas costs:

import { ethers } from 'ethers';
import { PapayaSDK } from '@papaya_fi/sdk';

async function optimizedOperations(signer: ethers.Signer) {
  const papaya = PapayaSDK.create(signer, 'polygon', 'USDT');
  
  // Instead of multiple separate transactions:
  // await papaya.deposit(100);
  // await papaya.subscribe(creator1, 10);
  // await papaya.subscribe(creator2, 5);
  
  // Batch the operations in your UI/UX flow
  // First deposit enough tokens for everything
  const depositTx = await papaya.deposit(115); // 100 + 10 + 5
  await depositTx.wait();
  
  // Then do the subscriptions
  const [tx1, tx2] = await Promise.all([
    papaya.subscribe(creator1, 10),
    papaya.subscribe(creator2, 5)
  ]);
  
  await Promise.all([tx1.wait(), tx2.wait()]);
}

Using Permit2 for Deposits

The Papaya SDK supports using Permit2 for approving and depositing tokens in a single transaction, which saves gas:

import { PapayaSDK } from '@papaya_fi/sdk';

async function depositWithPermit2(papaya: PapayaSDK, amount: number) {
  // Set isPermit2 to true to use Permit2 for approval and deposit in one transaction
  const tx = await papaya.deposit(amount, true);
  await tx.wait();
  console.log('Deposit with Permit2 completed successfully');
}

Working with Custom Contract Versions

The Papaya SDK supports multiple contract versions for each network and token.

Specifying a Contract Version

import { PapayaSDK } from '@papaya_fi/sdk';

// Using a specific contract version
const papayaV1 = PapayaSDK.create(provider, 'polygon', 'USDT', '1');

// Using the latest version (default)
const papaya = PapayaSDK.create(provider, 'polygon', 'USDT');

Working with Custom Contracts

You can also specify custom contract and token addresses:

import { PapayaSDK, PapayaSDKOptions } from '@papaya_fi/sdk';

// Using custom addresses
const options: PapayaSDKOptions = {
  provider: signer,
  network: 'polygon',
  tokenSymbol: 'USDT',
  contractAddress: '0xCustomContractAddress',
  tokenAddress: '0xCustomTokenAddress'
};

const papaya = new PapayaSDK(options);

Multiple Network Support

Working with Multiple Networks Simultaneously

For applications that need to work with multiple networks, you can create multiple SDK instances:

import { ethers } from 'ethers';
import { PapayaSDK, NetworkName } from '@papaya_fi/sdk';

// Function to create SDK instances for multiple networks
async function createMultiNetworkSDKs(privateKey: string) {
  const networks: NetworkName[] = ['polygon', 'bsc', 'mainnet'];
  const sdkInstances = {};
  
  for (const network of networks) {
    // Create provider for each network
    const provider = new ethers.JsonRpcProvider(getRpcUrl(network));
    const signer = new ethers.Wallet(privateKey, provider);
    
    // Create SDK instance
    sdkInstances[network] = PapayaSDK.create(signer, network, 'USDT');
  }
  
  return sdkInstances;
}

// Helper function to get RPC URL for a network
function getRpcUrl(network: NetworkName): string {
  const rpcUrls = {
    polygon: 'https://polygon-rpc.com',
    bsc: 'https://bsc-dataseed.binance.org',
    mainnet: 'https://eth.llamarpc.com',
    // Add more networks as needed
  };
  
  return rpcUrls[network] || rpcUrls['polygon']; // Default to polygon
}

Dynamically Switching Networks

For applications that need to switch networks dynamically:

import { ethers } from 'ethers';
import { PapayaSDK, NetworkName, TokenSymbol } from '@papaya_fi/sdk';

class NetworkManager {
  private signer: ethers.Signer;
  private currentSDK: PapayaSDK | null = null;
  private currentNetwork: NetworkName | null = null;
  
  constructor(signer: ethers.Signer) {
    this.signer = signer;
  }
  
  async switchNetwork(network: NetworkName, token: TokenSymbol = 'USDT') {
    if (network === this.currentNetwork) return this.currentSDK;
    
    try {
      // For browser wallet integration, you might need to request network switch
      if (window.ethereum) {
        const chainId = getChainId(network);
        await window.ethereum.request({
          method: 'wallet_switchEthereumChain',
          params: [{ chainId: `0x${chainId.toString(16)}` }],
        });
      }
      
      // Create new SDK instance for the selected network
      this.currentSDK = PapayaSDK.create(this.signer, network, token);
      this.currentNetwork = network;
      
      return this.currentSDK;
    } catch (error) {
      console.error(`Error switching to network ${network}:`, error);
      throw error;
    }
  }
  
  getCurrentSDK(): PapayaSDK {
    if (!this.currentSDK) {
      throw new Error('No network selected. Call switchNetwork first.');
    }
    return this.currentSDK;
  }
}

// Helper function to get chain ID for a network
function getChainId(network: NetworkName): number {
  const chainIds = {
    polygon: 137,
    bsc: 56,
    avalanche: 43114,
    base: 8453,
    scroll: 534352,
    arbitrum: 42161,
    mainnet: 1,
    sei: 32741,
    zksync: 324
  };
  
  return chainIds[network] || 137; // Default to polygon
}

Relayer Services

To fully leverage BySig methods, you'll need a relayer service to submit the signed transactions to the blockchain.

Building a Simple Relayer

Here's a basic example of how to build a simple relayer service using Node.js and Express:

// relayer.ts
import express from 'express';
import { ethers } from 'ethers';
import { PapayaSDK } from '@papaya_fi/sdk';
import dotenv from 'dotenv';

dotenv.config();

const app = express();
app.use(express.json());

const NETWORKS = {
  polygon: 'https://polygon-rpc.com',
  bsc: 'https://bsc-dataseed.binance.org',
  mainnet: 'https://eth.llamarpc.com',
  // Add more networks as needed
};

// Initialize providers and signers for each network
const providers = {};
const signers = {};
const sdkInstances = {};

Object.entries(NETWORKS).forEach(([network, rpcUrl]) => {
  providers[network] = new ethers.JsonRpcProvider(rpcUrl);
  signers[network] = new ethers.Wallet(process.env.RELAYER_PRIVATE_KEY, providers[network]);
  sdkInstances[network] = PapayaSDK.create(signers[network], network as any, 'USDT');
});

// Endpoint to handle gasless deposits
app.post('/api/relay/deposit', async (req, res) => {
  try {
    const { txData, chainId } = req.body;
    
    // Determine network from chain ID
    const network = getNetworkFromChainId(chainId);
    if (!network || !sdkInstances[network]) {
      return res.status(400).json({ 
        success: false, 
        error: 'Unsupported network' 
      });
    }
    
    // Submit the transaction
    const provider = providers[network];
    const signer = signers[network];
    
    // Here you would extract the necessary data from txData
    // and call the contract's bySig method directly
    
    // For a complete implementation, you would:
    // 1. Extract user address, amount, deadline, and signature from txData
    // 2. Call the contract directly with these parameters
    // 3. Return the transaction hash
    
    // Simplified example (this is not complete and would need adaptation):
    const tx = await signer.sendTransaction(txData);
    const receipt = await tx.wait();
    
    return res.json({
      success: true,
      txHash: receipt.hash
    });
  } catch (error) {
    console.error('Relayer error:', error);
    return res.status(500).json({
      success: false,
      error: 'Relayer error: ' + error.message
    });
  }
});

function getNetworkFromChainId(chainId: number): string | null {
  const chainIdMap = {
    1: 'mainnet',
    56: 'bsc',
    137: 'polygon',
    // Add more as needed
  };
  
  return chainIdMap[chainId] || null;
}

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Relayer service running on port ${PORT}`);
});

Error Handling and Recovery

Common Errors and Solutions

Error
Potential Cause
Solution

"Insufficient allowance"

Token approval needed

Call the token's approve method before depositing

"Insufficient balance"

User doesn't have enough tokens

Inform user to get more tokens

"Transaction underpriced"

Gas price too low

Increase gas price or wait for network congestion to decrease

"Nonce too low"

Transaction with same nonce already processed

Reset nonce or use the next available nonce

"Deadline expired"

BySig transaction submitted after deadline

Generate a new signature with a future deadline

Implementing Robust Error Handling

import { PapayaSDK } from '@papaya_fi/sdk';
import { ethers } from 'ethers';

async function robustDeposit(papaya: PapayaSDK, amount: number): Promise<boolean> {
  try {
    const tx = await papaya.deposit(amount);
    await tx.wait();
    return true;
  } catch (error) {
    // Handle specific errors
    if (error.message.includes('insufficient allowance')) {
      console.warn('Token approval needed. Attempting to approve...');
      try {
        // Attempt to approve tokens and retry
        // This would require implementing an approveTokens function
        await approveTokens(papaya, amount);
        
        // Retry deposit
        const tx = await papaya.deposit(amount);
        await tx.wait();
        return true;
      } catch (approvalError) {
        console.error('Failed to approve tokens:', approvalError);
        throw new Error('Token approval failed. Please approve tokens manually.');
      }
    } else if (error.message.includes('insufficient funds')) {
      throw new Error('Insufficient balance. Please add more tokens to your wallet.');
    } else {
      // Generic error handling
      console.error('Deposit error:', error);
      throw error;
    }
  }
}

// Example function to approve tokens (implementation would depend on your setup)
async function approveTokens(papaya: PapayaSDK, amount: number) {
  // Implementation would depend on how you access the token contract
  // This is just a placeholder
  const tokenContract = getTokenContract();
  const tx = await tokenContract.approve(papaya.getContractAddress(), amount);
  await tx.wait();
}

Transaction Monitoring and Recovery

For critical operations, implement transaction monitoring and recovery:

import { PapayaSDK } from '@papaya_fi/sdk';
import { ethers } from 'ethers';

async function monitorTransaction(
  txHash: string,
  provider: ethers.Provider,
  maxAttempts: number = 10
): Promise<ethers.TransactionReceipt> {
  let attempts = 0;
  
  while (attempts < maxAttempts) {
    try {
      const receipt = await provider.getTransactionReceipt(txHash);
      
      if (receipt) {
        // Check if transaction was successful
        if (receipt.status === 1) {
          return receipt;
        } else {
          throw new Error('Transaction failed');
        }
      }
      
      // Wait before checking again
      await new Promise(resolve => setTimeout(resolve, 5000));
      attempts++;
    } catch (error) {
      if (attempts >= maxAttempts) {
        throw error;
      }
      
      // Wait longer before retrying
      await new Promise(resolve => setTimeout(resolve, 5000));
      attempts++;
    }
  }
  
  throw new Error('Transaction not confirmed after maximum attempts');
}

// Usage example
async function safeDeposit(papaya: PapayaSDK, amount: number) {
  try {
    const tx = await papaya.deposit(amount);
    console.log(`Transaction sent: ${tx.hash}`);
    
    // Monitor transaction
    const receipt = await monitorTransaction(tx.hash, papaya.getProvider());
    console.log(`Deposit confirmed in block ${receipt.blockNumber}`);
    
    return receipt;
  } catch (error) {
    console.error('Deposit failed:', error);
    // Implement recovery logic here if needed
    throw error;
  }
}

Performance Optimization

Caching Strategies

class CachedPapayaSDK {
  private papaya: PapayaSDK;
  private cache: Map<string, { data: any; timestamp: number }> = new Map();
  private cacheTimeout: number = 30000; // 30 seconds
  
  constructor(papaya: PapayaSDK) {
    this.papaya = papaya;
  }
  
  async getCachedBalance(address: string): Promise<string> {
    const cacheKey = `balance_${address}`;
    const cached = this.cache.get(cacheKey);
    
    if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
      return cached.data;
    }
    
    const balance = await this.papaya.balanceOf(address);
    this.cache.set(cacheKey, { data: balance, timestamp: Date.now() });
    
    return balance;
  }
  
  async getCachedUserInfo(address: string): Promise<any> {
    const cacheKey = `userInfo_${address}`;
    const cached = this.cache.get(cacheKey);
    
    if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
      return cached.data;
    }
    
    const userInfo = await this.papaya.getUserInfo(address);
    this.cache.set(cacheKey, { data: userInfo, timestamp: Date.now() });
    
    return userInfo;
  }
  
  // Clear cache when transactions are made
  clearCache() {
    this.cache.clear();
  }
}

These advanced techniques will help you build robust applications that leverage the full power of the Papaya SDK while ensuring optimal performance and user experience.

Last updated