Summary

This will walkthrough shows you how to get started with the API by performing a swap of USDC on Noble to TIA on Celestia.

Get in touch if you’re having trouble

Please contact us if you believe the API doesn’t conveniently support your desired user experience, or if you’re confused about how to use it.

You can reach us easily by joining our discord and grabbing the “Skip Go Developer” role.

Reference Docs

For detailed information on all the Skip Go API endpoints and functionality, please see our reference docs.

Example using @skip-go/client package

This example is for browser environments.

1. Install @skip-go/client

In your command line terminal, install the @skip-go/client package:

shell
npm i @skip-go/client

Additional dependencies and steps will be required for EVM & SVM Transactions

This example constructs a cross-chain swap between 2 Cosmos chains, but Skip Go API supports all major EVM chains & Solana. (See Supported Ecosystems for more info about which chains, routes, and tokens we support)

If you’d like to perform a cross-chain swap that starts or ends on Solana or an EVM chain, you’ll need to import additional dependencies and configure additional tx signers. After going through this tutorial, check out EVM Transactions Intro and SVM Transactions Intro if you need.

2. Setup Signers

Optionally, you can set up a signer for each EVM, SVM, or Cosmos SDK ecosystems.

For EVM transactions, You need to install the viem package:

shell
npm i viem

For SVM transactions, You can pick which wallets you want to use in @solana/wallet-adapter-wallets. For this example, we are going to use Phantom wallet:

shell
npm i @solana/wallet-adapter-phantom

Now, let’s create the SkipRouter instance with the signers for each ecosystem!

We are going to use Keplr for Cosmos transactions, MetaMask for EVM transactions, and Phantom for Solana transactions.

SwapPage.tsx
import { SkipRouter } from "@skip-router/client";
import { PhantomWalletAdapter } from "@solana/wallet-adapter-phantom";
import { createWalletClient } from "viem";

const skipClient = new SkipRouter({
    getCosmosSigner: async (chainID) => {
        const offlineSigner = window.keplr?.getOfflineSigner(chainID);
        if (!offlineSigner) throw new Error("Keplr not installed");
        return offlineSigner;
    },
    getEVMSigner: async () => {
        const ethereum = window.ethereum;
        if (!ethereum) throw new Error("MetaMask not installed");
        const client = createWalletClient({
        chain: mainnet,
        transport: custom(window.ethereum),
        });
        return client;
    },
    getSVMSigner: async () => {
        const phantom = new PhantomWalletAdapter();
        return phantom;
    }
});

3. Request a Quote

Now, we can use the SkipRouter.route function to request a quote & route to swap USDC on Noble to TIA on Celestia:

Just add the following line after the line where you initialized the skipClient:

const route = await skipClient.route({
  sourceAssetDenom: 'uusdc',
  sourceAssetChainID: 'noble-1',
  destAssetDenom: 'utia',
  destAssetChainID: 'celestia',
  amountIn: '1000000', // 1 uusdc
  smartSwapOptions: {
    splitRoutes: true,
  },
});

Understanding the Route Response

The route response contains a ton of information about how the user can move from their source chain & token to their destination. For more information, see /v2/fungible/route reference docs.

The most basic outputs you will need are:

  • amountOut: The estimated amount your user will receive after their swap / transfer takes place — net of fees (e.g. bridge fees, swap fees, etc..) and price impact.
  • requiredChainAddresses: These are the chainIDs for which you will need to pass a valid user address when generating the transaction.
  • operations: The set of steps that take place to get the user from their source chain & token to their destination chain & token

Adding a fee for yourself

You can your set your own swap fee into the quote by setting cumulativeAffiliateFeeBPS to the total fee amount you want to collect from the user, measured in hundredths of a percent (aka “bips”).

You can find more info about setting fees in Setting Affiliate Fees

5. Get Required Addresses

Now that we have the route response, we can use our signer to get the list of chains where the user or reeceiver addresses are required for executing the route.

We’ll use these addresses in the next step to populate userAddresses when executing the route with the SkipRouter.executeRoute method.

The requiredChainAddresses field in the output of route gives the chainIDs we need to use.

Use addresses your user can sign with

Funds could end up in any of the addresses you provide — including intermediate chains in certain failure conditions (e.g. an IBC tranfer timesout after a swap). So you must be sure your user can sign for each address you provide.

See Cross-chain Failure Cases for more details on when funds might end up in an intermediate address.

(Rest assured that SkipRouter.transactionStatus will tell you where your users funds end up even if they don’t make it to their final destination.)

It’s recommended you keep the address somewhere and have function to get the address from the chainID. In this example we have a function called getAddress that takes the chainID and returns the address.

Add the following snippet to your script after getting the route to retrieve the address associated with each requiredChainAddresses entry:

// get user addresses for each requiredChainAddress
const userAddresses = await Promise.all(
  route.requiredChainAddresses.map(async (chainID) => {
    return {
      chainID: chainID,
      address: await getAddress(chainID),
    };
  })
);

Two things you should notice about this code:

  1. The userAddresses array contains objects with {chainID, address}
  2. The userAddresses array needs to have the same order and length as the requiredChainAddresses array had

Never attempt to derive an address on one chain from an address on another chain

Whenever you need a user address, please request it from the corresponding wallet / signer. Do not attempt to use bech32 cross-chain derivation.

If you attempt to derive an address on one chain from an address on another chain, you may derive an address that the user cannot actually sign for if the two chains have different address-derivation processes. For example, if you derive a Cosmos address from an Ethereum address, you will get an address that the user cannot sign for and cannot use.

4. Execute the route!

Finally, just use the SkipRouter.executeRoute method to create, sign, submit, and track the transaction for you.

Simply add the following snippet below your retrieval code to execute the route:

await skipClient.executeRoute({
  route,
  userAddresses,
  onTransactionCompleted: (chainID, txHash, status) => {
    console.log(
      `Route completed with tx hash: ${txHash} & status: ${status.state}`
    );
  },
});

After the transaction completes, you’ll have additional TIA in your Celestia address!

executeRoute callbacks

executeRoute takes care of all the complexity between getting a quote & executing a transaction: creating the tx, signing the tx, submitting it, and tracking it across all the chains.

You can supply a variety of callbacks to hook into the process and update your UI or retrieve information while it’s executing.

These callbacks include:

  • onTransactionBroadcast(txInfo {txHash, chainID}): Executes after the transaction that the user signs gets broadcast on chain.
  • onTransactionTracked(txInfo, {txHash, chainID}): Executes after the transaction that the user signs is successfully registered for tracking.
  • onTransactionCompleted(chainID, txHash, status): Executes after all of the operations triggered by a user’s signature complete. For example, in this case, it executed after the transfer to Celestia completed. (For multi-tx routes that require multiple user signatures, this will be called once for each tx)

Want to help us get better? Have questions or feedback?

You can reach us easily by joining our Discord and grabbing the “Skip Go Developer” role.