> For the complete documentation index, see [llms.txt](https://coinfair.gitbook.io/coinfair-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://coinfair.gitbook.io/coinfair-docs/coinfair-swap-protocol-integration-guide.md).

# Coinfair Swap Protocol Integration Guide

This document provides guidance on integrating with the Coinfair swap protocol, focusing on how to select the optimal swap path in the frontend.

### How to Select the Optimal Swap Path

#### Pool Types and Fee Combinations

Coinfair supports multiple pool types and fee combinations:

**Pool Types (5 types):** **`1, 2, 3, 4, 5`**

**Fee Types (3 rates): `3, 5, 10`**

#### Optimal Path Selection Process

Finding the optimal trading path requires a comprehensive evaluation of all possible combinations:

**1. Initialization and Setup**

* Establish connection to blockchain provider
* Initialize Router and Factory contract interfaces
* Setup multicall infrastructure for batch queries
* Define available pool types, fee rates, and intermediary tokens

**2. Direct Path Evaluation**

* For all 15 combinations of pool types and fee rates:
  * Create a direct path array: `[TokenA, TokenB]`
  * Create corresponding poolTypePath and feePath arrays
  * Prepare requests to `Router.getAmountsOut()` or `Router.getAmountsIn()`

**3. Multi-Hop Path Evaluation**

* For each intermediary token:
  * Skip if intermediary is the same as input or output token
  * For all combinations of first-hop and second-hop parameters:
    * Create a multi-hop path array: `[TokenA, Intermediary, TokenB]`
    * Create corresponding poolTypePath and feePath arrays with both hops' parameters
    * Prepare requests to `Router.getAmountsOut()` or `Router.getAmountsIn()`

**4. Comparison and Selection**

* For exact input swaps: select the path with maximum output amount
* For exact output swaps: select the path with minimum input amount
* Consider gas costs when comparing direct vs. multi-hop paths
* Return the optimal path information `(path, poolTypePath, feePath, expected amounts)`

#### TypeScript Implementation

```typescript
import { ethers } from 'ethers';
import { Contract } from '@ethersproject/contracts';
import { Provider } from '@ethersproject/providers';
import { Multicall } from 'ethereum-multicall';

// Configuration constants
const POOL_TYPES = [1, 2, 3, 4, 5];
const FEE_TYPES = [3, 5, 10];

// Intermediate tokens for multi-hop paths
const INTERMEDIARY_TOKENS = [
  '0x55d398326f99059fF775485246999027B3197955',
  '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d',
  '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'
];

// Contract addresses (to be filled with actual addresses)
const ROUTER_ADDRESS = '0x2b5970d94908664aC5023c08FCd48Bf4DBE3E034';
const FACTORY_ADDRESS = '0xBBC73Eaaa789c0Eb860749855B3928f6b851685F';

/**
 * PathFinder class for finding optimal swap paths
 */
export class PathFinder {
  private router: Contract;
  private factory: Contract;
  private provider: Provider;
  private multicall: Multicall;
  
  constructor(provider: Provider, signer?: ethers.Signer) {
    this.provider = provider;
    this.router = new Contract(
      ROUTER_ADDRESS,
      [
        'function getAmountsOut(uint amountIn, address[] path, uint8[] poolTypes, uint[] fees) view returns (uint[] amounts, address[] pairs)',
        'function getAmountsIn(uint amountOut, address[] path, uint8[] poolTypes, uint[] fees) view returns (uint[] amounts, address[] pairs)'
      ],
      signer || provider
    );
    
    this.factory = new Contract(
      FACTORY_ADDRESS,
      ['function getPair(address tokenA, address tokenB, uint8 poolType, uint fee) view returns (address pair)'],
      provider
    );
    
    this.multicall = new Multicall({ ethersProvider: provider });
  }
  
  /**
   * Find the best path for a token swap
   * @param tokenIn Input token address
   * @param tokenOut Output token address
   * @param amountIn Input amount (in wei)
   * @param exactOutput Whether this is an exact output swap
   * @param amountOut Output amount (for exact output swaps)
   * @returns Best path information
   */
  async findBestPath(
    tokenIn: string, 
    tokenOut: string, 
    amountIn: ethers.BigNumber,
    exactOutput: boolean = false,
    amountOut?: ethers.BigNumber
  ) {
    let bestPath = null;
    let bestAmountOut = ethers.BigNumber.from(0);
    let bestAmountIn = ethers.constants.MaxUint256;
    
    // 1. Check direct paths
    const directPathResults = await this.checkDirectPaths(tokenIn, tokenOut, amountIn, exactOutput, amountOut);
    
    if (exactOutput) {
      if (directPathResults.bestAmountIn.lt(bestAmountIn)) {
        bestAmountIn = directPathResults.bestAmountIn;
        bestPath = directPathResults.bestPath;
      }
    } else {
      if (directPathResults.bestAmountOut.gt(bestAmountOut)) {
        bestAmountOut = directPathResults.bestAmountOut;
        bestPath = directPathResults.bestPath;
      }
    }
    
    // 2. Check single-hop paths through intermediary tokens
    for (const intermediary of INTERMEDIARY_TOKENS) {
      // Skip if intermediary is the same as input or output token
      if (intermediary === tokenIn || intermediary === tokenOut) continue;
      
      const multiHopResults = await this.checkMultiHopPath(tokenIn, intermediary, tokenOut, amountIn, exactOutput, amountOut);
      
      if (exactOutput) {
        if (multiHopResults.bestAmountIn.lt(bestAmountIn)) {
          bestAmountIn = multiHopResults.bestAmountIn;
          bestPath = multiHopResults.bestPath;
        }
      } else {
        if (multiHopResults.bestAmountOut.gt(bestAmountOut)) {
          bestAmountOut = multiHopResults.bestAmountOut;
          bestPath = multiHopResults.bestPath;
        }
      }
    }
    
    // If no path found, throw error
    if (!bestPath) {
      throw new Error("No valid swap path found");
    }
    
    return {
      ...bestPath,
      expectedOut: exactOutput ? amountOut : bestAmountOut,
      expectedIn: exactOutput ? bestAmountIn : amountIn
    };
  }
  
  /**
   * Check all direct path combinations using multicall
   */
  private async checkDirectPaths(
    tokenIn: string, 
    tokenOut: string, 
    amountIn: ethers.BigNumber,
    exactOutput: boolean,
    amountOut?: ethers.BigNumber
  ) {
    const multicallContext = [];
    
    // Prepare multicall requests
    for (const poolType of POOL_TYPES) {
      for (const fee of FEE_TYPES) {
        const path = [tokenIn, tokenOut];
        const poolTypePath = [poolType];
        const feePath = [fee];
        
        multicallContext.push({
          reference: `${poolType}-${fee}`,
          contractAddress: ROUTER_ADDRESS,
          abi: [
            exactOutput 
              ? 'function getAmountsIn(uint amountOut, address[] path, uint8[] poolTypes, uint[] fees) view returns (uint[] amounts, address[] pairs)'
              : 'function getAmountsOut(uint amountIn, address[] path, uint8[] poolTypes, uint[] fees) view returns (uint[] amounts, address[] pairs)'
          ],
          calls: [
            {
              reference: 'amounts',
              methodName: exactOutput ? 'getAmountsIn' : 'getAmountsOut',
              methodParameters: exactOutput 
                ? [amountOut, path, poolTypePath, feePath]
                : [amountIn, path, poolTypePath, feePath]
            }
          ]
        });
      }
    }
    
    // Execute multicall
    const { results } = await this.multicall.call(multicallContext);
    
    // Process results
    let bestPath = null;
    let bestAmountOut = ethers.BigNumber.from(0);
    let bestAmountIn = ethers.constants.MaxUint256;
    
    for (const poolType of POOL_TYPES) {
      for (const fee of FEE_TYPES) {
        const key = `${poolType}-${fee}`;
        if (results[key] && results[key].success) {
          const [amounts, pairs] = results[key].callsReturnContext[0].returnValues;
          
          if (exactOutput) {
            // For exact output, we want to minimize input amount
            const inputAmount = ethers.BigNumber.from(amounts[0].hex);
            if (inputAmount.lt(bestAmountIn)) {
              bestAmountIn = inputAmount;
              bestPath = {
                path: [tokenIn, tokenOut],
                poolTypePath: [poolType],
                feePath: [fee]
              };
            }
          } else {
            // For exact input, we want to maximize output amount
            const outputAmount = ethers.BigNumber.from(amounts[1].hex);
            if (outputAmount.gt(bestAmountOut)) {
              bestAmountOut = outputAmount;
              bestPath = {
                path: [tokenIn, tokenOut],
                poolTypePath: [poolType],
                feePath: [fee]
              };
            }
          }
        }
      }
    }
    
    return { bestPath, bestAmountOut, bestAmountIn };
  }
  
  /**
   * Check all multi-hop path combinations for a specific intermediary token
   */
  private async checkMultiHopPath(
    tokenIn: string,
    intermediary: string,
    tokenOut: string,
    amountIn: ethers.BigNumber,
    exactOutput: boolean,
    amountOut?: ethers.BigNumber
  ) {
    const multicallContext = [];
    
    // Prepare multicall requests for all combinations
    for (const firstPoolType of POOL_TYPES) {
      for (const firstFee of FEE_TYPES) {
        for (const secondPoolType of POOL_TYPES) {
          for (const secondFee of FEE_TYPES) {
            const path = [tokenIn, intermediary, tokenOut];
            const poolTypePath = [firstPoolType, secondPoolType];
            const feePath = [firstFee, secondFee];
            
            multicallContext.push({
              reference: `${firstPoolType}-${firstFee}-${secondPoolType}-${secondFee}`,
              contractAddress: ROUTER_ADDRESS,
              abi: [
                exactOutput 
                  ? 'function getAmountsIn(uint amountOut, address[] path, uint8[] poolTypes, uint[] fees) view returns (uint[] amounts, address[] pairs)'
                  : 'function getAmountsOut(uint amountIn, address[] path, uint8[] poolTypes, uint[] fees) view returns (uint[] amounts, address[] pairs)'
              ],
              calls: [
                {
                  reference: 'amounts',
                  methodName: exactOutput ? 'getAmountsIn' : 'getAmountsOut',
                  methodParameters: exactOutput 
                    ? [amountOut, path, poolTypePath, feePath]
                    : [amountIn, path, poolTypePath, feePath]
                }
              ]
            });
          }
        }
      }
    }
    
    // Execute multicall
    const { results } = await this.multicall.call(multicallContext);
    
    // Process results
    let bestPath = null;
    let bestAmountOut = ethers.BigNumber.from(0);
    let bestAmountIn = ethers.constants.MaxUint256;
    
    for (const firstPoolType of POOL_TYPES) {
      for (const firstFee of FEE_TYPES) {
        for (const secondPoolType of POOL_TYPES) {
          for (const secondFee of FEE_TYPES) {
            const key = `${firstPoolType}-${firstFee}-${secondPoolType}-${secondFee}`;
            
            if (results[key] && results[key].success) {
              const [amounts, pairs] = results[key].callsReturnContext[0].returnValues;
              
              if (exactOutput) {
                // For exact output, we want to minimize input amount
                const inputAmount = ethers.BigNumber.from(amounts[0].hex);
                if (inputAmount.lt(bestAmountIn)) {
                  bestAmountIn = inputAmount;
                  bestPath = {
                    path: [tokenIn, intermediary, tokenOut],
                    poolTypePath: [firstPoolType, secondPoolType],
                    feePath: [firstFee, secondFee]
                  };
                }
              } else {
                // For exact input, we want to maximize output amount
                const outputAmount = ethers.BigNumber.from(amounts[2].hex);
                if (outputAmount.gt(bestAmountOut)) {
                  bestAmountOut = outputAmount;
                  bestPath = {
                    path: [tokenIn, intermediary, tokenOut],
                    poolTypePath: [firstPoolType, secondPoolType],
                    feePath: [firstFee, secondFee]
                  };
                }
              }
            }
          }
        }
      }
    }
    
    return { bestPath, bestAmountOut, bestAmountIn };
  }
}

/**
 * Swap executor for performing token swaps
 */
export class SwapExecutor {
  private router: Contract;
  
  constructor(signer: ethers.Signer) {
    this.router = new Contract(
      ROUTER_ADDRESS,
      [
        // Exact input swaps
        'function swapExactTokensForTokens(uint amountIn, uint amountOutMin, address[] calldata path, uint8[] calldata poolTypePath, uint[] calldata feePath, address to, uint deadline) external returns (uint[] memory amounts)',
        'function swapExactETHForTokens(uint amountOutMin, address[] calldata path, uint8[] calldata poolTypePath, uint[] calldata feePath, address to, uint deadline) external payable returns (uint[] memory amounts)',
        'function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, uint8[] calldata poolTypePath, uint[] calldata feePath, address to, uint deadline) external returns (uint[] memory amounts)',
        
        // Exact output swaps
        'function swapTokensForExactTokens(uint amountOut, uint amountInMax, address[] calldata path, uint8[] calldata poolTypePath, uint[] calldata feePath, address to, uint deadline) external returns (uint[] memory amounts)',
        'function swapETHForExactTokens(uint amountOut, address[] calldata path, uint8[] calldata poolTypePath, uint[] calldata feePath, address to, uint deadline) external payable returns (uint[] memory amounts)',
        'function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, uint8[] calldata poolTypePath, uint[] calldata feePath, address to, uint deadline) external returns (uint[] memory amounts)',
        
        // Swaps supporting fee on transfer
        'function swapExactTokensForTokensSupportingFeeOnTransferTokens(uint amountIn, uint amountOutMin, address[] calldata path, uint8[] calldata poolTypePath, uint[] calldata feePath, address to, uint deadline) external',
        'function swapExactETHForTokensSupportingFeeOnTransferTokens(uint amountOutMin, address[] calldata path, uint8[] calldata poolTypePath, uint[] calldata feePath, address to, uint deadline) external payable',
        'function swapExactTokensForETHSupportingFeeOnTransferTokens(uint amountIn, uint amountOutMin, address[] calldata path, uint8[] calldata poolTypePath, uint[] calldata feePath, address to, uint deadline) external'
      ],
      signer
    );
  }
  
  // Swap execution methods would be implemented here
} 
```
