Executing a Trade

This section demonstrates how to use a quote to construct and execute a trade on the Storyhunt V3 protocol.

Introduction

This section demonstrates how to use a quote to construct and execute a trade on the Storyhunt V3 protocol. The example involves trading between two ERC20 tokens, WIP and USDC, and allows configuring the input token, output token, input amount, and fee level.

The process includes:

  1. Constructing a route from pool information

  2. Building an unchecked trade

  3. Executing the trade

At the end, you’ll be able to create and execute a trade programmatically between any two ERC20 tokens.

Constructing a Route from Pool Information

First, gather metadata from the relevant pool contract. This includes constant information about the pool and its current state:

async function getPoolInfo() {
    const [fee, liquidity, slot0] = await Promise.all([
        poolContract.fee(),
        poolContract.liquidity(),
        poolContract.slot0(),
    ]);

    return {
        fee,
        liquidity,
        sqrtPriceX96: slot0[0],
        tick: slot0[1],
    };
}

Key values:

  • fee: The fee taken from every swap in the pool, expressed as 1 per million (e.g., a value of 500 means 0.05%).

  • liquidity: The current liquidity available for trades at the current price.

  • sqrtPriceX96: The current price of the pool.

  • tick: The tick corresponding to the current pool price.

Next, construct a Pool instance:

const poolInfo = await getPoolInfo();

const pool = new Pool(
  CurrentConfig.tokens.in,
  CurrentConfig.tokens.out,
  CurrentConfig.tokens.poolFee,
  poolInfo.sqrtPriceX96.toString(),
  poolInfo.liquidity.toString(),
  poolInfo.tick
);

Creating a Route

Using the Pool instance, construct a Route to represent a trade path:

import { Route } from '@storyhunt/v3-sdk';

const swapRoute = new Route(
  [pool],
  CurrentConfig.tokens.in,
  CurrentConfig.tokens.out
);

The Route object specifies that the input token will be traded for the output token over the specified pool.

Building an Unchecked Trade

Obtain a quote for the trade and construct an unchecked trade:

import { SwapQuoter } from '@storyhunt/v3-sdk';
import { CurrencyAmount, TradeType } from '@storyhunt/sdk-core';

const { calldata } = await SwapQuoter.quoteCallParameters(
  swapRoute,
  CurrencyAmount.fromRawAmount(
    CurrentConfig.tokens.in,
    fromReadableAmount(
      CurrentConfig.tokens.amountIn,
      CurrentConfig.tokens.in.decimals
    )
  ),
  TradeType.EXACT_INPUT,
  { useQuoterV2: true }
);

const quoteCallReturnData = await provider.call({
  to: QUOTER_CONTRACT_ADDRESS,
  data: calldata,
});

const amountOut = ethers.utils.defaultAbiCoder.decode(['uint256'], quoteCallReturnData);

const uncheckedTrade = Trade.createUncheckedTrade({
  route: swapRoute,
  inputAmount: CurrencyAmount.fromRawAmount(
    CurrentConfig.tokens.in,
    fromReadableAmount(
      CurrentConfig.tokens.amountIn,
      CurrentConfig.tokens.in.decimals
    )
  ),
  outputAmount: CurrencyAmount.fromRawAmount(
    CurrentConfig.tokens.out,
    JSBI.BigInt(amountOut)
  ),
  tradeType: TradeType.EXACT_INPUT,
});

Executing the Trade

Approve the SwapRouter contract to spend the input token:

const tokenApproval = await getTokenTransferApproval(CurrentConfig.tokens.in);

Set the swap options, including slippage tolerance and deadline:

import { SwapOptions } from '@storyhunt/v3-sdk';
import { Percent } from '@storyhunt/sdk-core';

const options: SwapOptions = {
  slippageTolerance: new Percent(50, 10_000), // 0.50%
  deadline: Math.floor(Date.now() / 1000) + 60 * 20, // 20 minutes from now
  recipient: walletAddress,
};

Use the SwapRouter to get the call parameters for the trade:

import { SwapRouter } from '@storyhunt/v3-sdk';

const methodParameters = SwapRouter.swapCallParameters([uncheckedTrade], options);

Construct and send the transaction:

const tx = {
  data: methodParameters.calldata,
  to: SWAP_ROUTER_ADDRESS,
  value: methodParameters.value,
  from: walletAddress,
  maxFeePerGas: MAX_FEE_PER_GAS,
  maxPriorityFeePerGas: MAX_PRIORITY_FEE_PER_GAS,
};

const res = await wallet.sendTransaction(tx);

Next Steps

With the ability to execute trades, you can now explore routing through multiple pools for optimal pricing and fees. Expand your implementation to include advanced routing features for more efficient swaps.