Skip to main content

Bungee smart contract integration

info

This guide is for integrating Bungee contracts directly into your smart contracts. This guide is not relevant to integration if you're integrating via Bungee API (previously Socket API) on your frontend.

1. Get quote from Bungee API [off-chain]

Firstly, a quote needs to be fetched from the Bungee API for the different routes available between two chains for specified tokens.

  • The isContractCall & singleTxOnly flag need to be set to true while calling the quote API. This removes any routes involving bridges that require off-chain signing transactions from user wallet, or routes that need transactions post-bridging on the destination chain
  • One of the routes can be selected from the routes array and the selected route object needs to be passed in the body of the POST request to the /build-tx endpoint
  • This returns the transaction data for the bridging tx and target to address to send that transaction. These need to be used as arguments for the integratorContract that calls the Bungee smart contracts
import { ethers } from "ethers";

// Uses web3 wallet in browser as provider
const provider = new ethers.providers.Web3Provider(window.ethereum, "any");

// Prompt user for account connections
await provider.send("eth_requestAccounts", []);

// Stores signer
const signer = provider.getSigner();

const apiKey = "72a5b4b0-e727-48be-8aa1-5da9d62fe635"; // public API Key, for testing only

const integratorContractAddress = "0x3e8cB4bd04d81498aB4b94a392c334F5328b237b";

// Makes a GET request to Bungee APIs for quote
async function getQuote(
fromChainId,
fromTokenAddress,
toChainId,
toTokenAddress,
fromAmount,
userAddress,
uniqueRoutesPerBridge,
sort,
singleTxOnly,
isContractCall
) {
const response = await fetch(
`https://api.socket.tech/v2/quote?fromChainId=${fromChainId}&fromTokenAddress=${fromTokenAddress}&toChainId=${toChainId}&toTokenAddress=${toTokenAddress}&fromAmount=${fromAmount}&userAddress=${userAddress}&uniqueRoutesPerBridge=${uniqueRoutesPerBridge}&sort=${sort}&singleTxOnly=${singleTxOnly}&isContractCall=${isContractCall}`,
{
method: "GET",
headers: {
"API-KEY": apiKey,
Accept: "application/json",
"Content-Type": "application/json",
},
}
);

const json = await response.json();
return json;
}

// Makes a POST request to Bungee APIs for transaction data
async function getRouteTransactionData(route) {
const response = await fetch("<https://api.socket.tech/v2/build-tx>", {
method: "POST",
headers: {
"API-KEY": apiKey,
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ route: route }),
});

const json = await response.json();
return json;
}

// Main function
async function main() {
// Quote for bridging 100 USDC on Polygon to USDT on BSC
// For single transaction bridging, mark singleTxOnly flag as true in query params
// For initiating Bungee txs through smart contracts, mark isContractCall to true

const quote = await getQuote(
137,
"0x2791bca1f2de4661ed88a30c99a7a9449aa84174",
56,
"0x55d398326f99059fF775485246999027B3197955",
100000000,
integratorContractAddress,
true,
"output",
true,
true
);

// URL of quote
// <https://api.socket.tech/v2/quote?fromChainId=137&fromTokenAddress=0x2791bca1f2de4661ed88a30c99a7a9449aa84174&toChainId=56&toTokenAddress=0x1af3f329e8be154074d8769d1ffa4ee058b1dbc3&fromAmount=100000000&userAddress=0x3e8cB4bd04d81498aB4b94a392c334F5328b237b&uniqueRoutesPerBridge=true&sort=output&isContractCall=true>

// Choosing first route from the returned route results
const route = quote.result.routes[0];

// Fetching transaction data for swap/bridge tx using the first route
let apiReturnData = await getRouteTransactionData(route);

// Integrator contract
const integratorContract = new ethers.Contract(
integratorContractAddress,
integratorContractAbi,
signer
);

// Calls Integrator Contract function for native tokens which calls Bungee contracts
const tx = await integratorContract.contractCallNativeToken(
apiReturnData.result.txTarget,
apiReturnData.result.txData,
apiReturnData.result.value
);

// Calls Integrator Contract function for ERC20s which calls Bungee contracts
// const tx = await integratorContract.contractCallERC20(apiReturnData.result.txTarget,
// apiReturnData.result.txData, apiReturnData.result.approvalData.approvalTokenAddress
// apiReturnData.result.approvalData.allowanceTarget,
// apiReturnData.result.approvalData.minimumApprovalAmount);

// Initiates transaction on user's frontend which user has to sign
const receipt = await tx.wait();

// console.log(receipt);
}

2. Call function on Integrator contract with fetched quote data

The /build-tx response also includes metadata of the address & amount for token approval for ERC-20 tokens.

  • The contract needs to have sufficient balance of the token specified in the quote for bridging
  • The example below shows two different functions for Native token and ERC-20 tokens[which need approval]. These could be clubbed together into a single function as well.
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract SocketIntegratorContract {

// For Native Tokens
// Approves Bungee Impl spending & initiates bridging in single transaction
function contractCallNativeToken (address payable _to, bytes memory txData, uint256 _amount) public payable {
(bool success, ) = _to.call{value: _amount}(txData);
require(success);
}

// For ERC-20 tokens
// Approves Bungee Impl spending & initiates bridging in single transaction
function contractCallERC20 (address payable _to,
bytes memory txData
address _token,
address _allowanceTarget,
uint256 _amount
) public {
IERC20(_token).approve(_allowanceTarget, _amount);
(bool success, ) = _to.call(txData);
require(success);
}

}

3. Destination contract

The tokens can be used in any way once they arrive in the destination contract address [NOTE : address needs to be same as source contract address]

  • At times, while bridging with Anyswap or Hop, due to lack of liquidity on the destination pool, the contract might receive anyTokens or hTokens, which are intermediary bridge tokens. Normally, these anyTokens / hTokens are swapped by the user on respective app UIs for their corresponding Tokens once there’s liquidity in the bridge liquidity pool. In case of the smart contract integration, a fallback function will have to be added to swap tokens.
  • A withdraw function would be the easiest solution here to recover hTokens/anyTokens into a secure wallet & manually swap them via Hop & Anyswap UI
  • We’re working on better fallback mechanisms for this. Reach out to us if you have any questions!
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract DestinationContract {

// onlyAdmin modifier
...
//

function withdrawTokens(address _token, address _recepient) public onlyAdmin {
uint256 amount = IERC20(_token).balanceOf(address(this));
IERC20.transfer(_recepient, amount);
}
}