Perform an Asset Swap

Swapping assets locally or across different chains.

The /routes endpoint (callable via the getRoutes method from our SDK) lists different routes for a proposed swap (local or cross-chain).

Code Example

📘

Prerequisites

  • Use the following link as an example of how to programmatically use the /routes endpoint: Example Code.
  • Before running the script, ensure your development environment is set up correctly by following this guide: Set Up the Development Environment.

This script sets up a configuration for a route (routeParameters object) that specifies the swap details, including the source and destination chains, source and destination tokens, maximum slippage, and recipients.

To run the script, use the command:

npm exec ts-node src/main/scripts/do-swap.ts

This script will create, sign, and broadcast transactions. You can inspect this functionality in the utility scriptsign-and-broadcast-transaction.ts.

Steps for a Cross-Chain Transfer

Step 1. Get Routes

First, get possible routes for your transfer using the getRoutes method. This defines token information and route parameters.

For example, let us pick the token OP on Optimism (as the source chain) and the MATIC token on Polygon (as the target chain). To perform the cross-chain swap, you may obtain the token objects using the TokenAPI, and then populate the fields of routeParametersconstant. The following snippet illustrates this process:

    const tokenListOP = await tokensAPI.getTokens(tokensParametersOP);
    const tokenListPol = await tokensAPI.getTokens(tokensParametersPol);
    sourceToken = tokenListOP["eip155:10"][0]; // first token of this array is OP
    targetToken = tokenListPol["eip155:137"][0]; // first token of this array is MATIC

		[...]
   
   const routeParameters: GetRoutesRequest = {
    fromChain: "eip155:10",
    fromToken: sourceToken.address,
    fromAmount: amountToTransferUnits, // 1000000000000000000 for 1 OP token, since it has 18 decimals
    toChain: "eip155:137",
    toToken: targetToken.address,
    fromAddress: optimismWallet.address,
    toAddress: polygonWallet.address,
    slippage: 0.1,
  };

Note that we could have applied different filtering logic to the token lists (e.g., by tokenAddress, tokenSymbol and tagLimit. Alternatively, you can obtain routes via cURL (replace fromAddressand toAddresswith your addresses):

curl -X GET 'https://svc.blockdaemon.com/defi/v1/exchange/routes?fromAddress=0xf271AAFC62634e6Dc9A276ac0f6145C4fDbE2Ced&toAddress=0xf271AAFC62634e6Dc9A276ac0f6145C4fDbE2Ced&fromChain=eip155:10&fromAmount=1000000000000000000&fromToken=0x4200000000000000000000000000000000000042&slippage=0.1&toChain=eip155:137&toToken=0x0000000000000000000000000000000000000000' \
-H "Authorization: Bearer $API_KEY"

The result is a routes object and should look similar to the following:

{
  "routes": [
    {
      "fromAmount": "100000000000000000",
      "fromAmountUSD": "0.1002621696",
      "fromToken": {
        "address": "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1",
        "chainID": "10",
        "chainType": "evm",
        "decimals": 18,
        "name": "Dai Stablecoin",
        "symbol": "DAI"
      },
      "gasCostUSD": "0.003103509152",
      "slippage": 0.1,
      "steps": [
        {
          "action": {
            "fromAddress": "0xf271AAFC62634e6Dc9A276ac0f6145C4fDbE2Ced",
            "fromAmount": "100000000000000000",
            "fromToken": {
              "address": "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1",
              "chainID": "10",
              "chainType": "evm",
              "decimals": 18,
              "name": "Dai Stablecoin",
              "symbol": "DAI"
            },
            "slippage": 0.1,
            "toAddress": "0xf271AAFC62634e6Dc9A276ac0f6145C4fDbE2Ced",
            "toToken": {
              "address": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
              "chainID": "137",
              "chainType": "evm",
              "decimals": 18,
              "name": "Dai Stablecoin",
              "symbol": "DAI"
            }
          },
          "estimate": {
            "approvalAddress": "0xB0D502E938ed5f4df2E681fE6E419ff29631d62b",
            "executionDuration": 1548,
            "gasCosts": [
              {
                "amount": "909198000000",
                "amountUSD": "0.003103509152",
                "estimate": "909198000000",
                "limit": "450000",
                "price": "2020440",
                "token": {
                  "address": "0x0000000000000000000000000000000000000000",
                  "chainID": "10",
                  "chainType": "evm",
                  "decimals": 18,
                  "name": "Ether",
                  "symbol": "ETH"
                },
                "type": "SEND"
              }
            ],
            "integrationDetails": {
              "key": "stargate",
              "name": "Stargate",
              "type": "bridge"
            },
            "toAmount": "99910000000000000000000000000"
          },
          "type": "cross"
        }
      ],
      "tags": ["stargate"],
      "toAmount": "99910000000000000000000000000",
      "toAmountMin": "999000000000000000",
      "toAmountUSD": "0.1002621696",
      "toToken": {
        "address": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
        "chainID": "137",
        "chainType": "evm",
        "decimals": 18,
        "name": "Dai Stablecoin",
        "symbol": "DAI"
      },
      "tool": "stargate"
    }
  ]
}

Step 2. Select a Route

Now, choose a route. For example, let's choose the first route available.

Before signing and broadcasting the transaction, authorize the bridge to use your DAI tokens. You can send an approval transaction using the Blocdaemon’s Accounts API.

Step 3. Get Approvals

You must authorize the route provider to use your tokens to perform a cross-chain swap. We recommend that you allow only the required funds for that specific transaction to be transacted.

Ensure you have received the approvals to interact with the exchange/bridge. You can also check these approvals after choosing a specific route approval address, which can be found in the estimate field for the selected route.

This is done programmatically in the following excerpt:

const approvalTxHash = await handleTokenApproval(
  selectedRoute,
  routeParameters,
  approvalsAPI,
  optimismWallet,
  OPTIMISM_RPC,
  logger,
);

if (approvalTxHash) {
  logger.debug("Approval needed. Checking for approval status...");
  const checkParams = {
    fromChain: routeParameters.fromChain,
    toChain: routeParameters.fromChain,
    transactionID: approvalTxHash.toString(),
    targetID: "485dc835-b4a1-5cee-9c3d-a4e2e224ad56",
	};
	await checkTransactionStatus(exchangeAPI, checkParams);
  } else {
  	logger.debug("No new approval needed.");
}

Step 4. Sign and Broadcast

Now, let's use the utility functions provided in the examples to sign and broadcast the transaction found in the routes'TransactionRequest field.

let txPayload = selectedRoute.transactionRequest.data;

[...]

const broadcastResult = await signTxObjectAndBroadcast(
  txPayload,
  optimismWallet.privateKey,
  OPTIMISM_RPC,
);

Under the hood, we sign and broadcast a transaction object to the source chain network. The transaction is transmitted to the source blockchain and processed by the route providers. The script logs helpful information about the transaction broadcast process:

if (broadcastResult) {
      logger.info("Successfully broadcast signed data to Optimism");
      logger.debug("Broadcast result:", broadcastResult);
      logger.info("Transaction hash:", broadcastResult.transactionHash);
      logger.info(
        "Check transaction at: https://optimistic.etherscan.io/tx/" +
          broadcastResult.transactionHash,
      );

Step 5. Get the Transaction Status

To check cross-chain transaction status, monitor both source chain transactions and activities on the target chain. For this, you can use the /exchange/status endpoint

await checkTransactionStatus(exchangeAPI, checkParamsSwap);

Under the hood, this function makes an HTTP request against the DeFi status API and retries until the status of the transaction is erroneous or finalized. Under the hood, it does a few checks:

for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
  try {
    const approvalStatus = await exchangeAPI.getStatus(request);

    if (approvalStatus.status === StatusEnum.Done) {
      logger.info("Status of transaction is DONE");
      return;
    }

    if (approvalStatus.status === StatusEnum.Failed) {
      throw new Error("Transaction failed");
    }

    if (
      isSdkErrorResponse(approvalStatus) ||
      approvalStatus.status === StatusEnum.NotFound ||
      approvalStatus.status === StatusEnum.NeedGas
    ) {
      logger.info(`Current approval status: ${approvalStatus.status}`);

      if (attempt < MAX_RETRIES - 1) {
        logger.debug(
          `Waiting for ${WAIT_TIME / 1000} seconds before retry ${attempt + 1} of ${MAX_RETRIES}`,
        );
        await new Promise((resolve) => setTimeout(resolve, WAIT_TIME));
      }

      [...]

This process ensures that the transaction moving funds across blockchains was finalized successfully or that the end-user is informed if an error occurred.

📘

Info:

For more information, refer to Get Transaction Status endpoint.

👋 Need Help?

Contact us through email or our support page for any issues, bugs, or assistance you may need.