Getting BTC Routes to EVM

To perform a swap between the Bitcoin network and any EVM compactible chain, we first have to get a quote from Swing's Cross-Chain API. The response from the /quote endpoint contains the route information and the fees that will be paid by an interacting user.

Making a Request

URL: https://swap.prod.swing.xyz/v0/transfer/quote

Query Parameters:

PropertyExampleDescription
tokenAmount1000000000000000000Amount of the source token being sent (in wei for ETH).
fromChainbitcoinSource Chain slug
fromUserAddressbc1qeegt8mserjpwmaylfmprfswcx6twa4psusas8xSender's wallet address
fromTokenAddressbtcSource Token Address
tokenSymbolBTCSource Token slug
toTokenAddress0x0000000000000000000000000000000000000000Destination Token Address.
toTokenSymbolETHDestination Token slug
toChainethereumDestination Chain slug
toUserAddress0x018c15DA1239B84b08283799B89045CD476BBbBbReceiver's wallet address
projectIdreplugYour project's ID

Navigating to our src/services/requests.ts file, you will find our method for getting a quote from Swing's Cross-Chain API called getQuoteRequest().

export const getQuoteRequest = async (
  queryParams: QuoteQueryParams,
): Promise<QuoteAPIResponse> => {
  try {
    const response = await axios.get<QuoteAPIResponse>(
      `${baseUrl}/transfer/quote`,
      { params: { ...queryParams, projectId } },
    );
    return response.data;
  } catch (error) {
    console.error('Error fetching quote:', error);
    throw error;
  }
};

Navigating to our src/components/Swap.tsx file, you'll find our defaultTransferParams object which will store the default transaction config for our example:

const defaultTransferParams: TransferParams = {
  tokenAmount: '1000000',
  fromChain: 'bitcoin',
  fromUserAddress: 'bc1qeegt8mserjpwmaylfmprfswcx6twa4psusas8x',
  fromTokenAddress: 'btc',
  fromTokenIconUrl:
    'https://raw.githubusercontent.com/Pymmdrza/Cryptocurrency_Logos/mainx/PNG/eth.png',
  tokenSymbol: 'BTC',
  toTokenAddress: '0x0000000000000000000000000000000000000000',
  toTokenSymbol: 'ETH',
  toChain: 'ethereum',
  toTokenIconUrl:
    'https://raw.githubusercontent.com/Pymmdrza/Cryptocurrency_Logos/mainx/PNG/btc.png',
  toUserAddress: '0xE1e0992Be9902E92460AC0Ff625Dcc1c485FCF6b', // enter your ethereum wallet here
};

The response received from the getQuoteRequest endpoint provides us with the fees a user will have to pay when executing a transaction, as well as a list of possible routes for the user to choose from as seen in our bitcoin example project.

btc project filesystem

The definition for the getQuoteRequest response can be found in src/interfaces/quote.interface.ts.

export interface QuoteAPIResponse {
  routes: Route[];
  fromToken: Token;
  fromChain: Chain;
  toToken: Token;
  toChain: Chain;
}

Each Route contains a gasFee, bridgeFee and the amount of tokens the destination wallet will receive.

Here's an example response that contains the route data:

"routes": [
    {
        "duration": 10,
        "gas": "439824241499248",
        "quote": {
            "integration": "thorswap",
            "type": "swap",
            "bridgeFee": "343736",
            "bridgeFeeInNativeToken": "0",
            "amount": "9360457",
            "decimals": 8,
            "amountUSD": "6303.051",
            "bridgeFeeUSD": "231.462",
            "bridgeFeeInNativeTokenUSD": "0",
            "fees": [
                {
                    "type": "bridge",
                    "amount": "343736",
                    "amountUSD": "231.462",
                    "chainSlug": "bitcoin",
                    "tokenSymbol": "BTC",
                    "tokenAddress": "btc",
                    "decimals": 8,
                    "deductedFromSourceToken": true
                },
                {
                    "type": "gas",
                    "amount": "439824241499248",
                    "amountUSD": "1.439",
                    "chainSlug": "ethereum",
                    "tokenSymbol": "ETH",
                    "tokenAddress": "0x0000000000000000000000000000000000000000",
                    "decimals": 18,
                    "deductedFromSourceToken": false
                }
            ]
        },
        "route": [
            {
                "bridge": "thorswap",
                "bridgeTokenAddress": "0x0000000000000000000000000000000000000000",
                "steps": [
                    "allowance",
                    "approve",
                    "send"
                ],
                "name": "ETH",
                "part": 100
            }
        ],
        "distribution": {
            "thorswap": 1
        },
        "gasUSD": "1.439"
    }
],

Getting BTC to EVM Quote

To get a quote for a bridge transaction between Bitcoin and any EVM chain, we have to set our source chain to bitcoin and our destination chain to any EVM chain like ethereum

Source Chain Config

const transferParams: TransferParams = {
  ...
  fromChain: 'bitcoin',
  fromUserAddress: 'bc1qeegt8mserjpwmaylfmprfswcx6twa4psusas8x',
  fromTokenAddress: 'btc',
  tokenSymbol: 'BTC',
  fromChainDecimal: 8,
  ...
};

Destination Chain Config

const transferParams: TransferParams = {
  ...
  toTokenAddress: '0x0000000000000000000000000000000000000000',
  toTokenSymbol: 'ETH',
  toChain: 'ethereum',
  toUserAddress: '0xE1e0992Be9902E92460AC0Ff625Dcc1c485FCF6b',
  toChainDecimal: 8,
  ...
};

To calculate the tokenAmount, which is the amount of assets being bridged, you need to convert the amount entered by a user to the smallest units for both Bitcoin and Ethereum. This requires storing the decimal places that represent these smallest units for both chains.

The decimal places for both our source and destination chains are stored in the fromChainDecimal and toChainDecimal properties respectively.

Here’s how you can covert the tokenAmout:

For Ethereum:

  • The smallest unit is Wei.
  • Decimal places: 18 (1 ETH = 10^18 Wei).

For Bitcoin:

  • The smallest unit is Satoshi.
  • Decimal places: 8 (1 BTC = 10^8 Satoshis).

Navigating to our src/utils folder, you'll find a file ethToWei that contains a function to convert our tokenAmount to either Satoshis or Wei:

export const convertEthToWei = (ethAmount: string, decimals = 18) => {
  // Convert the amount to a string to handle both string and number inputs
  const weiAmount = ethers.utils.parseUnits(ethAmount.toString(), decimals);
  return weiAmount.toString();
};

Selecting The Best Route

By default, the /quote endpoint will return a list of possible routes and will sort them from the cheapest to the most expensive fees that a user will have to pay.

In our example project, we're safely assigning the first route to our transferRoute state variable as its guaranteed to be the cheapest route.

const [transferRoute, setTransferRoute] = useState<Route | null>(null);
 
const transferParams: TransferParams = {
  tokenAmount: '1',
 
  // source chain config
  fromChain: 'bitcoin',
  fromUserAddress: 'bc1qeegt8mserjpwmaylfmprfswcx6twa4psusas8x',
  fromTokenAddress: 'btc',
  fromChainDecimal: 8,
  tokenSymbol: 'BTC',
 
  // destination chain config
  toTokenAddress: '0x0000000000000000000000000000000000000000',
  toTokenSymbol: 'ETH',
  toChain: 'ethereum',
  toUserAddress: '0xE1e0992Be9902E92460AC0Ff625Dcc1c485FCF6b',
  toChainDecimal: 18,
};
 
async function getQuote() {
  // Call `/quote` endpoint via `getQuoteRequest()`
  const quotes = await getQuoteRequest({
    fromChain: transferParams.fromChain,
    fromTokenAddress: transferParams.fromTokenAddress,
    fromUserAddress: transferParams.fromUserAddress,
    toChain: transferParams.toChain,
    tokenSymbol: transferParams.tokenSymbol,
    toTokenAddress: transferParams.toTokenAddress,
    toTokenSymbol: transferParams.toTokenSymbol,
    toUserAddress: transferParams.toUserAddress,
 
    // tokenAmount conversion
    tokenAmount: convertEthToWei(
      transferParams.tokenAmount,
      transferParams.fromChainDecimal,
    ),
  });
 
  // Check if we got back any routes
  if (!quotes?.routes.length) {
    console.log('No routes available. Try increasing the send amount.');
    return;
  }
 
  // If we got back any route, set our current route to the first route as it's the cheapest
  setTransferRoute(quotes.routes[0]!);
}

Getting EVM to BTC Quote

To get a quote for a bridge transaction between any EVM chain and the Bitcoin network, we essentially only have to reverse our transactionParams and perform a getQuoteRequest:

const transferParams: TransferParams = {
  tokenAmount: '1',
 
  fromChain: 'ethereum',
  fromUserAddress: '',
  fromTokenAddress: '0x0000000000000000000000000000000000000000',
  fromChainDecimal: 18,
  tokenSymbol: 'ETH',
 
  toTokenAddress: 'btc',
  toTokenSymbol: 'BTC',
  toChain: 'bitcoin',
  toUserAddress: 'bc1qeegt8mserjpwmaylfmprfswcx6twa4psusas8x', // enter your bitcoin wallet here
  toChainDecimal: 8,
};
 
const quotes = await getQuoteRequest({
  fromChain: transferParams.fromChain,
  fromTokenAddress: transferParams.fromTokenAddress,
  fromUserAddress: transferParams.fromUserAddress,
  toChain: transferParams.toChain,
  tokenSymbol: transferParams.tokenSymbol,
  toTokenAddress: transferParams.toTokenAddress,
  toTokenSymbol: transferParams.toTokenSymbol,
  toUserAddress: transferParams.toUserAddress,
  tokenAmount: convertEthToWei(
    transferParams.tokenAmount,
    transferParams.fromChainDecimal,
  ),
});