This module can be used to get information about Magicswap pools, allowing you to perform token swaps and manage pool liquidity.

Public members

Assets/Treasure/TDK/Runtime/Magicswap/TDK.Magicswap.cs
// getters
public async Task<List<MagicswapPool>> GetAllPools();
public async Task<MagicswapPool> GetPoolById(string id);
public async Task<MagicswapRoute> GetRoute(string tokenInId, string tokenOutId, string amount, bool isExactOut);

// actions
public async Task<Transaction> Swap(SwapBody swapBody);
public async Task<Transaction> AddLiquidity(string poolId, AddLiquidityBody addLiquidityBody);
public async Task<Transaction> RemoveLiquidity(string poolId, RemoveLiquidityBody removeLiquidityBody);

// liquidity helpers
public BigInteger GetQuote(BigInteger amountA, BigInteger reserveA, BigInteger reserveB);
public BigInteger GetAmountMin(BigInteger amount, double slippage);

// allowance helpers
public async Task<BigInteger> GetERC20Allowance(string tokenAddress, string address);
public async Task<bool> IsERC20Approved(string tokenAddress, string address, BigInteger amount);
public async Task<bool> IsERC1155Approved(string tokenAddress, string address);
public async Task<bool> IsERC721Approved(string tokenAddress, string address);

Usage

Swap and liquidity operations require the user’s smart wallet to be connected and a started session (See Connect & Identity)

Token Approval

In order to approve tokens and NFTs for transfer when swapping or adding liquidity, the token addresses must be added to the callTargets array in the config JSON or TDKConfig asset.

LP token addresses (pool ids) must also be added as callTargets in order to approve for transfer when removing liquidity.

Tokens can be approved using the TDK.Common.ApproveERC* helpers. For example:

var magicswapRouterAddress = TDK.Common.GetContractAddress(Contract.MagicswapV2Router);
// approving ERC20
var magicAddress = TDK.Common.GetContractAddress(Contract.Magic);
await TDK.Common.ApproveERC20(magicAddress, magicswapRouterAddress, amount);
await TDK.Common.ApproveERC20(poolData.id, magicswapRouterAddress, amount);
// approving ERC1155
await TDK.Common.ApproveERC1155(/* ERC1155 token address */, magicswapRouterAddress);

Performing a swap

Step 1: Fetch pool and route data

using Treasure;

// fetch any necessary pool data, including token ids for nfts
var poolData = await TDK.Magicswap.GetPoolById(/* your pool id */);

// calculate swap route by specifying the types of tokens to swap and the desired output amount
var routeData = await TDK.Magicswap.GetRoute(
    tokenInId: poolData.token0.id, // token you want to swap from
    tokenOutId: poolData.token1.id, // token you want to swap to
    amount: "1", // amount of tokens you are swapping or receiving
    isExactOut: true // true if you want to receive `amount` tokens (see below)
);

If isExactOut = true then amount is the amount out. If isExactOut = false then amount is the amount in. See this section below for details.

Step 1.1: For swaps that involve NFTs

The user must select the NFTs to swap (and their quantities)

  • if the input token is NFT, select from routeData.tokenIn.collectionTokenIds up to routeData.amountIn NFTs
  • if the output token is NFT, select from routeData.tokenOut.collectionTokenIds up to routeData.amountOut NFTs
// let's say the user picked the first one from the list as output
var userSelectedNFTsOutput = new List<NFTInput> {
    new() {
        id = routeData.tokenOut.collectionTokenIds[0],
        quantity = 1,
    }
};

Step 2: Perform the swap

// use route data and user selection to build the body
var swapBody = new SwapBody {
    tokenInId = routeData.tokenIn.id,
    tokenOutId = routeData.tokenOut.id,
    path = routeData.path,
    amountIn = routeData.amountIn,
    nftsOut = userSelectedNFTsOutput,
    isExactOut = true
};
// send request to perform swap (ERC20 to NFT swap in this example)
var transaction = await TDK.Magicswap.Swap(swapBody);

// check the result
var success = transaction.status == "mined";

Other types of swap

Different properties of SwapBody can be used for different kinds of swap:

Swap typeProperties
ERC20 to NFTamountIn & nftsOut
ERC20 to ERC20amountIn & amountOut
NFT to NFTnftsIn & nftsOut
NFT to ERC20nftsIn & amountOut

The isExactOut parameter

When calling GetRoute and Swap you must specify the isExactOut parameter. The same value should be used for both.

isExactOut = true means that the user has specified the exact amount of tokens they want to receive.

There are a few different scenarios:

  • ERC20⇔NFT swaps
    • If the user is trading their NFTs, isExactOut = false because they must specify exactly how many NFTs they are putting in.
    • If the user is trading to NFTs, isExactOut = true because they must specify exactly how many NFTs they are receiving.
  • ERC20⇔ERC20 or NFT⇔NFT swaps; isExactOut depends on whatever the user specified.
    • For example, if you’re swapping MAGIC to VEE, then isExactOut = true if you specify how much VEE you want, and isExactOut = false if you specify how much MAGIC you will give.

Adding Liquidity

var poolData = await TDK.Magicswap.GetPoolById(/* your pool id */);
// for tokenA we should know the amount we want to add (amountA = 1 in this case)
// for tokenB we can calculate amountB based on amountA and the pool reserves
var tokenA = poolData.token1;
var tokenB = poolData.token0;
var amountA = new BigInteger(1);
var reserveA = BigInteger.Parse(tokenA.reserve);
var reserveB = BigInteger.Parse(tokenB.reserve);
var amountB = TDK.Magicswap.GetQuote(
    amountA.AdjustDecimals(0, tokenA.decimals),
    reserveA,
    reserveB
);

var addLiquidityBody = new AddLiquidityBody {
    amount0 = amountB.ToString(),
    amount0Min = TDK.Magicswap.GetAmountMin(amountB, 0.01).ToString(),
    nfts1 = new List<NFTInput>() {
        new() {
            id = tokenA.collectionTokenIds[0],
            quantity = (int)amountA,
        }
    }
};

// send request to add liquidity (ERC20⇔NFT pool in this example)
var transaction = await TDK.Magicswap.AddLiquidity(poolData.id, addLiquidityBody);

// check the result
var success = transaction.status == "mined";

Other types of pools

Different properties of AddLiquidityBody can be used for different kinds of pools:

Pool typeProperties
ERC20⇔NFTamount0 & amount0Min & nfts1
ERC20⇔ERC20amount0 & amount0Min & amount1 & amount1Min
NFT⇔NFTnfts0 & nfts1
NFT⇔ERC20nfts0 & amount1 & amount1Min

Removing Liquidity

var poolData = await TDK.Magicswap.GetPoolById(/* your pool id */);

var reserve0 = BigInteger.Parse(poolData.token0.reserve);
var reserve1 = BigInteger.Parse(poolData.token1.reserve);
var totalSupply = BigInteger.Parse(poolData.totalSupply);

var nftsDesired = new BigInteger(1).AdjustDecimals(0, poolData.token1.decimals);
// calculate LP amount to get the desired amount of nfts (1 in this example).
// the LP amount could otherwise be inputted by the user.
var amountLPWei = BigInteger.DivRem(nftsDesired * totalSupply, reserve1, out BigInteger reminder);
if (reminder > 0) amountLPWei += 1;

var amount0 = amountLPWei * reserve0 / totalSupply;
var amount1 = amountLPWei * reserve1 / totalSupply; // should be 1 nft from our LP calculation

// floor amount1 since token1 is nft
var amount1FlooredNoDecimals = amount1.AdjustDecimals(poolData.token1.decimals, 0);

// use helper to account for slippage for amount0 since its an ERC20 token
var amount0Min = TDK.Magicswap.GetAmountMin(amount0, 0.01);
// dont use helper for amount1 since its NFT, use floored value with decimals instead
var amount1Min = amount1FlooredNoDecimals.AdjustDecimals(0, poolData.token1.decimals);

var removeLiquidityBody = new RemoveLiquidityBody {
    amountLP = amountLPWei.ToString(),
    amount0Min = amount0Min.ToString(),
    amount1Min = amount1Min.ToString(),
    nfts1 = new List<NFTInput>() {
        new() {
            id = poolData.token1.collectionTokenIds[0],
            quantity = (int)amount1FlooredNoDecimals,
        }
    }
};
// send request to remove liquidity (ERC20⇔NFT pool in this example)
var transaction = await TDK.Magicswap.RemoveLiquidity(poolData.id, removeLiquidityBody);

// check the result
var success = transaction.status == "mined";

Useful allowance helpers

Some helpers are provided for reading relevant information, such as token balance, allowance, or approval status.

// setup
var poolData = await TDK.Magicswap.GetPoolById(/* your pool id */);
var tokenAddress = TDK.Common.GetContractAddress(poolData.token0.id); // or poolData.token1.id or poolData.id

// getting token balance (ERC20)
var tokenBalance = await TDK.Common.GetFormattedERC20Balance(tokenAddress, TDK.Identity.Address, 18);
Debug.Log($"Balance: {tokenBalance}");

// getting token allowance (ERC20)
var tokenAllowance = await TDK.Magicswap.GetERC20Allowance(tokenAddress, TDK.Identity.Address);
Debug.Log($"Allowance: {Thirdweb.Utils.ToEth(tokenAllowance.ToString())}");

// getting token approval (ERC1155)
var tokenIsApproved = await TDK.Magicswap.IsERC1155Approved(tokenAddress, TDK.Identity.Address);
Debug.Log(tokenIsApproved ? "Token is approved" : "Token is not approved");