# Advanced Examples

## Gas Optimization <a href="#gas-optimization" id="gas-optimization"></a>

### **Batching Transactions**

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

```tsx
import { ethers } from 'ethers';
import { AzothSDK } from '@azothpay/sdk';

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

### **Using Permit2 for Deposits**

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

```typescript
import { AzothSDK } from '@azothpay/sdk';

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

### Working with Custom Contract Versions <a href="#working-with-custom-contract-versions" id="working-with-custom-contract-versions"></a>

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

### **Specifying a Contract Version**

```typescript
import { AzothSDK } from '@azothpay/sdk';

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

// Using the latest version (default)
const azoth = AzothSDK.create(provider, 'polygon', 'USDT');
```

### **Working with Custom Contracts**

You can also specify custom contract and token addresses:

```typescript
import { AzothSDK, AzothSDKOptions } from '@azothpay/sdk';

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

const azoth = new AzothSDK(options);
```

### Multiple Network Support <a href="#multiple-network-support" id="multiple-network-support"></a>

### **Working with Multiple Networks Simultaneously**

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

```typescript
import { ethers } from 'ethers';
import { AzothSDK, NetworkName } from '@azothpay/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] = AzothSDK.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:

```typescript
import { ethers } from 'ethers';
import { AzothSDK, NetworkName, TokenSymbol } from '@azothpay/sdk';

class NetworkManager {
  private signer: ethers.Signer;
  private currentSDK: AzothSDK | 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 = AzothSDK.create(this.signer, network, token);
      this.currentNetwork = network;
      
      return this.currentSDK;
    } catch (error) {
      console.error(`Error switching to network ${network}:`, error);
      throw error;
    }
  }
  
  getCurrentSDK(): AzothSDK {
    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 <a href="#relayer-services" id="relayer-services"></a>

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**:

```typescript
// relayer.ts
import express from 'express';
import { ethers } from 'ethers';
import { AzothSDK } from '@azothpay/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] = AzothSDK.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}`);
});
```

#### Using Multicall for Combined Operations

The `depositAndSubscribe()` method combines deposit and subscribe operations in a single transaction using multicall, which saves gas and improves UX:

```typescript
import { PapayaSDK, RatePeriod } from '@papaya_fi/sdk';

async function quickSubscribe(papaya: PapayaSDK, creatorAddress: string, projectId: number) {
  // Deposit $100 and subscribe to $100/month in a single transaction
  const tx = await papaya.depositAndSubscribe(
    creatorAddress,  // author
    '100',           // deposit amount ($100)
    '100',           // subscription rate ($100/month)
    RatePeriod.MONTH,
    projectId,
    false,           // isPermit2
    6                // decimals (USDT/USDC)
  );
  
  const receipt = await tx.wait();
  
  if (receipt.status === 1) {
    console.log('✅ Success! Deposit and subscription completed in one transaction!');
  }
}
```

#### Using Multicall Directly

For more control or to combine different operations, you can use the `multicall()` method directly:

```typescript
import { PapayaSDK, formatInput, convertRatePerSecond, RatePeriod } from '@papaya_fi/sdk';

async function customMulticall(papaya: PapayaSDK, creatorAddress: string, projectId: number) {
  // Encode deposit call
  const depositData = papaya.contract.interface.encodeFunctionData("deposit", [
    formatInput('100', 6),  // amount in wei
    false                    // isPermit2
  ]);
  
  // Encode subscribe call
  const subscribeData = papaya.contract.interface.encodeFunctionData("subscribe", [
    creatorAddress,
    convertRatePerSecond('100', RatePeriod.MONTH),
    projectId
  ]);
  
  // Execute both operations in one transaction
  // Order is important: deposit first, then subscribe
  const tx = await papaya.multicall([depositData, subscribeData]);
  
  const receipt = await tx.wait();
  
  if (receipt.status === 1) {
    console.log('Multicall transaction successful!');
  }
}
```

### Error Handling and Recovery <a href="#error-handling-and-recovery" id="error-handling-and-recovery"></a>

**Common Errors and Solutions**

<table data-full-width="true"><thead><tr><th>Error</th><th>Potential Cause</th><th>Solution</th></tr></thead><tbody><tr><td>"Insufficient allowance"</td><td>Token approval needed</td><td>Call the token's approve method before depositing</td></tr><tr><td>"Insufficient balance"</td><td>User doesn't have enough tokens</td><td>Inform user to get more tokens</td></tr><tr><td>"Transaction underpriced"</td><td>Gas price too low</td><td>Increase gas price or wait for network congestion to decrease</td></tr><tr><td>"Nonce too low"</td><td>Transaction with same nonce already processed</td><td>Reset nonce or use the next available nonce</td></tr><tr><td>"Deadline expired"</td><td>BySig transaction submitted after deadline</td><td>Generate a new signature with a future deadline</td></tr></tbody></table>

### **Implementing Robust Error Handling**

```typescript
import { AzothSDK } from '@azothpay/sdk';
import { ethers } from 'ethers';

async function robustDeposit(azoth: AzothSDK, amount: number): Promise<boolean> {
  try {
    const tx = await azoth.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(azoth, amount);
        
        // Retry deposit
        const tx = await azoth.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(azoth: AzothSDK, 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(azoth.getContractAddress(), amount);
  await tx.wait();
}
```

### **Transaction Monitoring and Recovery**

For critical operations, implement transaction monitoring and recovery:

```typescript
import { AzothSDK } from '@azothpay/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(azoth: AzothSDK, amount: number) {
  try {
    const tx = await azoth.deposit(amount);
    console.log(`Transaction sent: ${tx.hash}`);
    
    // Monitor transaction
    const receipt = await monitorTransaction(tx.hash, azoth.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**

```typescript
class CachedAzothSDK {
  private azoth: AzothhSDK;
  private cache: Map<string, { data: any; timestamp: number }> = new Map();
  private cacheTimeout: number = 30000; // 30 seconds
  
  constructor(azoth: AzothSDK) {
    this.azoth = azoth;
  }
  
  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.azoth.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.azoth.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 Azoth SDK while ensuring optimal performance and user experience.* <br>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.azothpay.com/sdk/examples/advanced-examples.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
