๐ŸฐSDK Example

SDK Example for Integrating Rango Exchange

Overview

You could read this guide to understand the flow of integrating Rango-SDK. If you prefer to dive directly into the code and explore it there, you can use the links below.

EVM Example

Install TS SDK

If you decide not to use our TypeScript SDK and prefer integration in other programming languages, feel free to skip this step.

To integrate Rango SDK inside your dApp or wallet, you need to install rango-sdk using npm or yarn.

npm install --save rango-sdk
# or 
yarn add rango-sdk

Then you need to instantiate RangoClient and use it in the next steps.

import { RangoClient } from "rango-sdk"

const rango = new RangoClient(RANGO_API_KEY)

Get Tokens & Blockchains Data

To get the list of available blockchains, tokens, and protocols (dex or bridge) supported by Rango, you could use the getAllMetadata method like this:

const meta = await rango.getAllMetadata()

Routing

Get All Routes

Using information retrieved from the meta, you could implement your own SwapBox including your blockchain and token selector. The next step is to show the preview of the best route possible when the user selects the source and the destination tokens.

The blockchain and symbol names must be exactly what is fetched from Rango's Meta API.

// Converting 0.1 BSC BNB to AVAX_CCHAIN USDT.E 
const routingResponse = await rango.getAllRoutes({
  from: {
    "blockchain": "BSC", 
    "symbol": "BNB", 
    "address": null
  },
  to: {
    "blockchain": "AVAX_CCHAIN", 
    "symbol": "USDT.E", 
    "address": "0xc7198437980c041c805a1edcba50c1ce5db95118"
  },
  amount: "0.1",
  slippage: "1.0",
})

You could call this method periodically to get the updated route before the user confirms the route.

Confirm Route

Using the getAllRoutes method, you obtain a list of available routes. Once the user selects and confirms a route, you need to call the confirm method to notify Rango that the user has chosen this route for the next step and retrieve the final, updated route to display to the user.

To confirm the route, you must provide the selected route's request ID, the wallet addresses for all blockchains in the route, and optionally, a custom destination if the user wishes to send funds to a different wallet than the one specified for the related blockchain (in selectedWallets).

// user selects one of the routes
const selectedRoute = routingResponse.results[0]

const confirmResponse = await rango.confirmRoute({
  requestId: selectedRoute.requestId,
  selectedWallets: {
    'BSC': '0xeae6d42093eae057e770010ffd6f4445f7956613',
    'AVAX_CCHAIN': '0xeae6d42093eae057e770010ffd6f4445f7956613'
  },
  destination: '0x6f33bb1763eebead07cf8815a62fcd7b30311fa3'
})

At this stage, it is also possible to verify if the user has sufficient balance and fees for each step of the route in advance based on confirm response validation field. Also, you can compare the confirmed route with the route's output amount and alert the user if there is a significant difference.

// check if the route was okay
if (!confirmResponse.result || confirmResponse.error) {
  // there was a problem in confirming the route
} else {
  // everything was okay
  // you could compare confirmed route with the route output amount 
  // and warn the user if there is noticable difference
  // e.g. check if confirmed route output is 2% less than previous one
  const confirmedOutput = new BigNumber(routingResponse.results[selected]?.outputAmount)
  const finalOutput = new BigNumber(confirmResponse.result?.outputAmount)
  if (finalOutput.lt(confirmedOutput.multipliedBy(new BigNumber(0.98))) {
    // get double confirmation from the user
  } else {
    // proceed to executing the route  
  }
}

Route Execution

For every steps of the selected route, we need to repeat the next steps:

Creating Transaction

If the source blockchain for this step requires approval, such as EVM-based blockchains, Starknet, or Tron, and the user lacks sufficient approval, the Rango API will return an approval transaction in the response. The user must sign this transaction, and in the next createTransaction call for this step, the Rango API will provide the main transaction. Therefore, the createTransaction API response could either be an approval transaction (isApproval=true) or the main transaction (isApproval=false).

const request: CreateTransactionRequest = {
  requestId: confirmedRoute.requestId,
  step: 1, // 1, 2, 3, ...
  userSettings: {
    slippage: '1.0',
    infiniteApprove: false
  },
  validations: {
    approve: true,
    balance: false,
    fee: false,
  }
}
let createTransactionResponse = await rango.createTransaction(request)
let tx = createTransactionResponse.transaction
if (!tx) {
  throw new Error(`Error creating the transaction ${createTransactionResponse.error}`)
}

If you are verifying the balance and fee amount on your client side or you've already checked them in route confirmation step, it is advisable to set the balance and fee parameters to false to prevent duplicate checks or potential errors during validation.

Tracking Swap Status

After signing the transaction by the user and receiving transaction hash, you could periodically call Rango check-status API to track the transaction status. In Rango, each swap step could have 3 different states: running, failed and success. You only need to keep checking the status until you find out whether the transaction failed or succeeded.

const state = await rango.checkStatus({
    requestId: confirmedRoute.requestId,
    step: 1, // related step
    txId: '0xfa88b705a5b4049adac7caff50c887d9600ef023ef1a937f8f8b6f44e90042b5'
})

if (response.status) {
  // show latest status of the swap to the user
  if (response.status === TransactionStatus.SUCCESS) {
      // swap suceeded
  } else if (response.status === TransactionStatus.FAILED) {
      // swap failed
  } else {
      // swap is still running
      // we need to call check-status method again after a timeout (10s)
  }
}

Complete Code Flow

Node.JS Example
// run `node --import=tsx index.ts` in the terminal

import { CreateTransactionRequest, RangoClient, TransactionStatus, TransactionType } from "rango-sdk";
import { findToken } from '../shared/utils/meta.js'
import { TransactionRequest, ethers } from "ethers";
import { setTimeout } from 'timers/promises'

// setup wallet & RPC provider
// please change rpc provider url if you want to test another chain rather than BSC
const privateKey = 'YOUR_PRIVATE_KEY';
const wallet = new ethers.Wallet(privateKey);
const rpcProvider = new ethers.JsonRpcProvider('https://bsc-dataseed1.defibit.io');
const walletWithProvider = wallet.connect(rpcProvider);
const waleltAddress = walletWithProvider.address

// initiate sdk using your api key
const API_KEY = "c6381a79-2817-4602-83bf-6a641a409e32"
const rango = new RangoClient(API_KEY)

// get blockchains and tokens meta data
const meta = await rango.getAllMetadata()

// some example tokens for test purpose
const sourceBlockchain = "BSC"
const sourceTokenAddress = "0x55d398326f99059ff775485246999027b3197955"
const targetBlockchain = "BSC"
const targetTokenAddress = null
const amount = "0.001"

// find selected tokens in meta.tokens
const sourceToken = findToken(meta.tokens, sourceBlockchain, sourceTokenAddress)
const targetToken = findToken(meta.tokens, targetBlockchain, targetTokenAddress)

// get route
const routingRequest = {
  from: sourceToken,
  to: targetToken,
  amount,
  slippage: '1.0',
}
const routingResponse = await rango.getAllRoutes(routingRequest)
if (routingResponse.results.length === 0) {
  throw new Error(`There was no route! ${routingResponse.error}`)
}

// confirm one of the routes
const selectedRoute = routingResponse.results[0]

const selectedWallets = selectedRoute.swaps
  .flatMap(swap => [swap.from.blockchain, swap.to.blockchain])
  .filter((blockchain, index, self) => self.indexOf(blockchain) === index)
  .map(blockchain => ({ [blockchain]: waleltAddress }))
  .reduce((acc, obj) => {
    return { ...acc, ...obj };
  }, {});

const confirmResponse = await rango.confirmRoute({
  requestId: selectedRoute.requestId,
  selectedWallets,
})

const confirmedRoute = confirmResponse.result

if (!confirmedRoute) {
  throw new Error(`Error in confirming route, ${confirmResponse.error}`)
}

let step = 1
const swapSteps = confirmedRoute.result?.swaps || []
for (const swap of swapSteps) {
  const request: CreateTransactionRequest = {
    requestId: confirmedRoute.requestId,
    step: step,
    userSettings: {
      slippage: '1.0',
      infiniteApprove: false
    },
    validations: {
      approve: true,
      balance: false,
      fee: false,
    }
  }
  let createTransactionResponse = await rango.createTransaction(request)
  let tx = createTransactionResponse.transaction
  if (!tx) {
    throw new Error(`Error creating the transaction ${createTransactionResponse.error}`)
  }

  if (tx.type === TransactionType.EVM) {
    if (tx.isApprovalTx) {
      // sign the approve transaction
      const approveTransaction: TransactionRequest = {
        from: tx.from,
        to: tx.to,
        data: tx.data,
        value: tx.value,
        maxFeePerGas: tx.maxFeePerGas,
        maxPriorityFeePerGas: tx.maxPriorityFeePerGas,
        gasPrice: tx.gasPrice,
        gasLimit: tx.gasLimit,
      }
      const { hash } = await walletWithProvider.sendTransaction(approveTransaction);

      // wait for approval
      while (true) {
        await setTimeout(5_000)
        const { isApproved, currentApprovedAmount, requiredApprovedAmount, txStatus } = await rango.checkApproval(confirmedRoute.requestId, hash)
        if (isApproved)
          break
        else if (txStatus === TransactionStatus.FAILED)
          throw new Error('Approve transaction failed in blockchain')
        else if (txStatus === TransactionStatus.SUCCESS)
          throw new Error(`Insufficient approve, current amount: ${currentApprovedAmount}, required amount: ${requiredApprovedAmount}`)
      }

      // create the main transaction if previous one was approval transaction
      createTransactionResponse = await rango.createTransaction(request)
      tx = createTransactionResponse.transaction
      if (!tx || tx.type !== TransactionType.EVM) {
        throw new Error(`Error creating the transaction ${createTransactionResponse.error}`)
      }
    }

    // sign the main transaction
    const mainTransaction: TransactionRequest = {
      from: tx.from,
      to: tx.to,
      data: tx.data,
      value: tx.value,
      maxFeePerGas: tx.maxFeePerGas,
      maxPriorityFeePerGas: tx.maxPriorityFeePerGas,
      gasPrice: tx.gasPrice,
      gasLimit: tx.gasLimit,
    }
    const { hash } = await walletWithProvider.sendTransaction(mainTransaction);
    
    // track swap status
    while (true) {
      await setTimeout(10_000)
      const state = await rango.checkStatus({
        requestId: confirmedRoute.requestId,
        step,
        txId: hash
      })

      const status = state.status
      if (status === TransactionStatus.SUCCESS) {
        // we could proceed with the next step of the route
        step += 1;
        break
      } else if (status === TransactionStatus.FAILED) {
        throw new Error(`Swap failed on step ${step}`)
      }
    }
  }
}

Last updated