178-使用Raydium的SDK来swap

今天我们来用一下Raydium的sdk来搞swap

本来用这些dex的sdk来做swap是非常非常简单的事情

但是可能是Raydium的sdk弄得不太好

或者是我太笨了

导致搞raydium的swap遇到了很多很多问题

真的很烦

首先我们来看一下raydiumSDK的文档吧

看一下示例代码

import { Connection, PublicKey } from "@solana/web3.js";
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
import {
  TokenAccount,
  SPL_ACCOUNT_LAYOUT,
  LIQUIDITY_STATE_LAYOUT_V4,
} from "@raydium-io/raydium-sdk";
import { OpenOrders } from "@project-serum/serum";
import BN from "bn.js";

async function getTokenAccounts(connection: Connection, owner: PublicKey) {
  const tokenResp = await connection.getTokenAccountsByOwner(owner, {
    programId: TOKEN_PROGRAM_ID,
  });

  const accounts: TokenAccount[] = [];
  for (const { pubkey, account } of tokenResp.value) {
    accounts.push({
      pubkey,
      accountInfo: SPL_ACCOUNT_LAYOUT.decode(account.data),
    });
  }

  return accounts;
}

// raydium pool id can get from api: https://api.raydium.io/v2/sdk/liquidity/mainnet.json
const SOL_USDC_POOL_ID = "58oQChx4yWmvKdwLLZzBi4ChoCc2fqCUWBkwMihLYQo2";
const OPENBOOK_PROGRAM_ID = new PublicKey(
  "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX"
);

export async function parsePoolInfo() {
  const connection = new Connection({mainnet rpc node}, "confirmed");
  const owner = new PublicKey("VnxDzsZ7chE88e9rB6UKztCt2HUwrkgCTx8WieWf5mM");

  const tokenAccounts = await getTokenAccounts(connection, owner);

  // example to get pool info
  const info = await connection.getAccountInfo(new PublicKey(SOL_USDC_POOL_ID));
  if (!info) return;

  const poolState = LIQUIDITY_STATE_LAYOUT_V4.decode(info.data);
  const openOrders = await OpenOrders.load(
    connection,
    poolState.openOrders,
    OPENBOOK_PROGRAM_ID // OPENBOOK_PROGRAM_ID(marketProgramId) of each pool can get from api: https://api.raydium.io/v2/sdk/liquidity/mainnet.json
  );

  const baseDecimal = 10 ** poolState.baseDecimal.toNumber(); // e.g. 10 ^ 6
  const quoteDecimal = 10 ** poolState.quoteDecimal.toNumber();

  const baseTokenAmount = await connection.getTokenAccountBalance(
    poolState.baseVault
  );
  const quoteTokenAmount = await connection.getTokenAccountBalance(
    poolState.quoteVault
  );

  const basePnl = poolState.baseNeedTakePnl.toNumber() / baseDecimal;
  const quotePnl = poolState.quoteNeedTakePnl.toNumber() / quoteDecimal;

  const openOrdersBaseTokenTotal =
    openOrders.baseTokenTotal.toNumber() / baseDecimal;
  const openOrdersQuoteTokenTotal =
    openOrders.quoteTokenTotal.toNumber() / quoteDecimal;

  const base =
    (baseTokenAmount.value?.uiAmount || 0) + openOrdersBaseTokenTotal - basePnl;
  const quote =
    (quoteTokenAmount.value?.uiAmount || 0) +
    openOrdersQuoteTokenTotal -
    quotePnl;

  const denominator = new BN(10).pow(poolState.baseDecimal);

  const addedLpAccount = tokenAccounts.find((a) =>
    a.accountInfo.mint.equals(poolState.lpMint)
  );

  console.log(
    "SOL_USDC pool info:",
    "pool total base " + base,
    "pool total quote " + quote,

    "base vault balance " + baseTokenAmount.value.uiAmount,
    "quote vault balance " + quoteTokenAmount.value.uiAmount,

    "base tokens in openorders " + openOrdersBaseTokenTotal,
    "quote tokens in openorders  " + openOrdersQuoteTokenTotal,

    "base token decimals " + poolState.baseDecimal.toNumber(),
    "quote token decimals " + poolState.quoteDecimal.toNumber(),
    "total lp " + poolState.lpReserve.div(denominator).toString(),

    "addedLpAmount " +
      (addedLpAccount?.accountInfo.amount.toNumber() || 0) / baseDecimal
  );
}

parsePoolInfo();

然后我们来看一下raydiumSDK的demo

GitHub - raydium-io/sdk_demo

然后demo里面有一些示例代码

可以直接拿过来用

还是挺方便的

我们先看一下swapOnlyCLMM

import { AmmV3, ApiAmmV3PoolsItem, buildTransaction, Percent, Token, TokenAmount } from '@raydium-io/raydium-sdk'
import { Keypair, PublicKey } from '@solana/web3.js'

import { connection, ENDPOINT, RAYDIUM_MAINNET_API, wallet, wantBuildTxVersion } from '../config'
import { getWalletTokenAccount, sendTx } from './util'

type WalletTokenAccounts = Awaited<ReturnType<typeof getWalletTokenAccount>>
type TestTxInputInfo = {
  outputToken: Token
  targetPool: string
  inputTokenAmount: TokenAmount
  slippage: Percent
  walletTokenAccounts: WalletTokenAccounts
  wallet: Keypair
}

async function swapOnlyCLMM(input: TestTxInputInfo) {
  // -------- pre-action: fetch ammV3 pools info --------
  const ammV3Pool = (await (await fetch(ENDPOINT + RAYDIUM_MAINNET_API.ammV3Pools)).json()).data.filter(
    (pool: ApiAmmV3PoolsItem) => pool.id === input.targetPool
  )
  const { [input.targetPool]: ammV3PoolInfo } = await AmmV3.fetchMultiplePoolInfos({
    connection,
    poolKeys: ammV3Pool,
    chainTime: new Date().getTime() / 1000,
  })

  // -------- step 1: fetch tick array --------
  const tickCache = await AmmV3.fetchMultiplePoolTickArrays({
    connection,
    poolKeys: [ammV3PoolInfo.state],
    batchRequest: true,
  })

  // -------- step 2: calc amount out by SDK function --------
  // Configure input/output parameters, in this example, this token amount will swap 0.0001 USDC to RAY
  const { minAmountOut, remainingAccounts } = AmmV3.computeAmountOutFormat({
    poolInfo: ammV3PoolInfo.state,
    tickArrayCache: tickCache[input.targetPool],
    amountIn: input.inputTokenAmount,
    currencyOut: input.outputToken,
    slippage: input.slippage,
  })

  // -------- step 3: create instructions by SDK function --------
  const { innerTransactions } = await AmmV3.makeSwapBaseInInstructionSimple({
    connection,
    poolInfo: ammV3PoolInfo.state,
    ownerInfo: {
      feePayer: input.wallet.publicKey,
      wallet: input.wallet.publicKey,
      tokenAccounts: input.walletTokenAccounts,
    },
    inputMint: input.inputTokenAmount.token.mint,
    amountIn: input.inputTokenAmount.raw,
    amountOutMin: minAmountOut.raw,
    remainingAccounts,
  })

  // -------- step 4: compose instructions to several transactions --------
  const transactions = await buildTransaction({
    connection,
    txType: wantBuildTxVersion,
    payer: input.wallet.publicKey,
    innerTransactions: innerTransactions,
  })

  // -------- step 5: send transaction --------
  const txids = await sendTx(connection, input.wallet, wantBuildTxVersion, transactions)
  return { txids }
}

async function howToUse() {
  const inputToken = new Token(new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'), 6, 'USDC', 'USDC') // USDC
  const outputToken = new Token(new PublicKey('4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R'), 6, 'RAY', 'RAY') // RAY
  const targetPool = '61R1ndXxvsWXXkWSyNkCxnzwd3zUNB8Q2ibmkiLPC8ht' // USDC-RAY pool
  const inputTokenAmount = new TokenAmount(inputToken, 100)
  const slippage = new Percent(1, 100)
  const walletTokenAccounts = await getWalletTokenAccount(connection, wallet.publicKey)

  swapOnlyCLMM({
    outputToken,
    targetPool,
    inputTokenAmount,
    slippage,
    walletTokenAccounts,
    wallet: wallet,
  }).then(({ txids }) => {
    /** continue with txids */
    console.log('txids', txids)
  })
}

然后我们再来看一下swapRoute

import { AmmV3, buildTransaction, ENDPOINT, Percent, Token, TokenAmount, TradeV2 } from '@raydium-io/raydium-sdk'
import { Keypair, PublicKey } from '@solana/web3.js'
import { connection, RAYDIUM_MAINNET_API, wallet, wantBuildTxVersion } from '../config'
import { getWalletTokenAccount, sendTx } from './util'

type WalletTokenAccounts = Awaited<ReturnType<typeof getWalletTokenAccount>>
type TestTxInputInfo = {
  inputToken: Token
  outputToken: Token
  targetPool: string
  inputTokenAmount: TokenAmount
  slippage: Percent
  walletTokenAccounts: WalletTokenAccounts
  wallet: Keypair
}

/**
 * pre-action: fetch ammV3 pools info and ammV2 pools info
 * step 1: get all route
 * step 2: fetch tick array and pool info
 * step 3: calculation result of all route
 * step 4: create instructions by SDK function
 * step 5: compose instructions to several transactions
 * step 6: send transactions
 */
async function routeSwap(input: TestTxInputInfo) {
  // -------- pre-action: fetch ammV3 pools info and ammV2 pools info --------
  const ammV3Pool = (await (await fetch(ENDPOINT + RAYDIUM_MAINNET_API.ammV3Pools)).json()).data // If the clmm pool is not required for routing, then this variable can be configured as undefined
  const ammV3PoolInfos = Object.values(
    await AmmV3.fetchMultiplePoolInfos({ connection, poolKeys: ammV3Pool, chainTime: new Date().getTime() / 1000 })
  ).map((i) => i.state)

  const ammV2Pool = await (await fetch(ENDPOINT + RAYDIUM_MAINNET_API.poolInfo)).json() // If the Liquidity pool is not required for routing, then this variable can be configured as undefined

  // -------- step 1: get all route --------
  const getRoute = TradeV2.getAllRoute({
    inputMint: input.inputToken.mint,
    outputMint: input.outputToken.mint,
    apiPoolList: ammV2Pool,
    ammV3List: ammV3PoolInfos,
  })

  // -------- step 2: fetch tick array and pool info --------
  const [tickCache, poolInfosCache] = await Promise.all([
    await AmmV3.fetchMultiplePoolTickArrays({ connection, poolKeys: getRoute.needTickArray, batchRequest: true }),
    await TradeV2.fetchMultipleInfo({ connection, pools: getRoute.needSimulate, batchRequest: true }),
  ])

  // -------- step 3: calculation result of all route --------
  const [routeInfo] = TradeV2.getAllRouteComputeAmountOut({
    directPath: getRoute.directPath,
    routePathDict: getRoute.routePathDict,
    simulateCache: poolInfosCache,
    tickCache,
    inputTokenAmount: input.inputTokenAmount,
    outputToken: input.outputToken,
    slippage: input.slippage,
    chainTime: new Date().getTime() / 1000, // this chain time
  })

  // -------- step 4: create instructions by SDK function --------
  const { innerTransactions } = await TradeV2.makeSwapInstructionSimple({
    connection,
    swapInfo: routeInfo,
    ownerInfo: {
      wallet: input.wallet.publicKey,
      tokenAccounts: input.walletTokenAccounts,
      associatedOnly: true,
    },
    checkTransaction: true,
  })

  // -------- step 5: compose instructions to several transactions --------
  const transactions = await buildTransaction({
    connection,
    txType: wantBuildTxVersion,
    payer: input.wallet.publicKey,
    innerTransactions: innerTransactions,
  })

  // -------- step 6: send transactions --------
  const txids = await sendTx(connection, input.wallet, wantBuildTxVersion, transactions)
  return { txids }
}

async function howToUse() {
  const targetPool = '61R1ndXxvsWXXkWSyNkCxnzwd3zUNB8Q2ibmkiLPC8ht' // USDC-RAY pool
  const outputToken = new Token(new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'), 6, 'USDC', 'USDC') // USDC
  const inputToken = new Token(new PublicKey('4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R'), 6, 'RAY', 'RAY') // RAY
  const inputTokenAmount = new TokenAmount(inputToken, 100)
  const slippage = new Percent(1, 100)
  const walletTokenAccounts = await getWalletTokenAccount(connection, wallet.publicKey)

  routeSwap({
    inputToken,
    outputToken,
    targetPool,
    inputTokenAmount,
    slippage,
    walletTokenAccounts,
    wallet: wallet,
  }).then(({ txids }) => {
    /** continue with txids */
    console.log('txids', txids)
  })
}

实际上这里我们会遇到一个问题

是挺麻烦的

就是当我们用SOL的时候

这里的swap会用WSOL

我不知道是SDK的设置问题还是什么问题

总之很烦

可能经过什么设置可以直接用SOL

也就是不用wrap和unwrap

也就是不用SOL和WSOL的转换

但是我没找到怎么设置

所以我们来加一点wrap和unwrap的代码

首先是unwrap

unwrap是比较简单的

直接closeAccount就可以了

那么我们看下代码

      const ata = await getAta(input.wallet.publicKey, SOL_ID);
      console.log(ata);
      const closeTxId = await closeAccount(connection, input.wallet, ata, input.wallet.publicKey, input.wallet);
      console.log(closeTxId);

这里直接closeAccount就行了

我们再看下如何wrap

    const ata = await getAta(wallet.publicKey, SOL_ID);

    const transaction = new Transaction();
    const wsol = await walletGetWSOLBalance();

    if (wsol <= 0) {
      transaction.add(
        createAssociatedTokenAccountInstruction(
          wallet.publicKey,
          ata,
          wallet.publicKey,
          SOL_ID
        )
      );
    }

    transaction.add(
      SystemProgram.transfer({
        fromPubkey: wallet.publicKey,
        toPubkey: ata,
        lamports: amount
      }),
      createSyncNativeInstruction(
        ata
      )
    );

    const tx = await sendAndConfirmTransaction(connection, transaction, [wallet]);
    console.log(tx);

这边是用了spl-token包

如果不用这个包

我们如何来搞呢

可以自己来搞Transaction

我们把一些方法写好

import {TransactionInstruction} from "@solana/web3.js";
import {SYSTEM_PROGRAM_ID, TOKEN_PROGRAM_ID} from "./WalletHandler";

export const getAssociatedTokenAccountInstruction = (
  payer,
  associatedToken,
  owner,
  mint,
  programId,
  associatedTokenProgramId
) => {
  const keys = [
    {pubkey: payer, isSigner: true, isWritable: true},
    {pubkey: associatedToken, isSigner: false, isWritable: true},
    {pubkey: owner, isSigner: false, isWritable: false},
    {pubkey: mint, isSigner: false, isWritable: false},
    {pubkey: SYSTEM_PROGRAM_ID, isSigner: false, isWritable: false},
    {pubkey: programId, isSigner: false, isWritable: false},
  ];

  const instructionData = Buffer.alloc(0);

  return new TransactionInstruction({
    keys,
    programId: associatedTokenProgramId,
    data: instructionData,
  });
}

export const getSyncNativeInstruction = (account) => {
  const keys = [{pubkey: account, isSigner: false, isWritable: true}];
  const data = Buffer.from([17]);
  return new TransactionInstruction({keys, programId: TOKEN_PROGRAM_ID, data});
}

export const getCloseAccountInstruction = (
  signer,
  account,
  destination
) => {
  const keys = addSigners(
    [
      {pubkey: account, isSigner: false, isWritable: true},
      {pubkey: destination, isSigner: false, isWritable: true},
    ],
    signer,
    [signer]
  )
  const data = Buffer.from([9]);
  return new TransactionInstruction({keys, programId: TOKEN_PROGRAM_ID, data});
}

const addSigners = (
  keys,
  ownerOrAuthority,
  multiSigners
) => {
  if (multiSigners.length) {
    keys.push({pubkey: ownerOrAuthority, isSigner: false, isWritable: false});
    for (const signer of multiSigners) {
      keys.push({
        pubkey: signer,
        isSigner: true,
        isWritable: false,
      });
    }
  } else {
    keys.push({pubkey: ownerOrAuthority, isSigner: true, isWritable: false});
  }
  return keys;
}

然后看看unwrap

  async function closeAccount(connection, wallet) {
    const wsol = await walletGetWSOLBalance();
    if (wsol > 0) {
      const ata = await getAta(wallet.publicKey, SOL_ID);
      const transaction = new Transaction();
      transaction.add(
        getCloseAccountInstruction(wallet.publicKey, ata, wallet.publicKey)
      );
      const closeTx = await sendAndConfirmTransaction(connection, transaction, [wallet]);
      console.log(closeTx);
    }
  }

然后看看wrap

  const toWSOL = async (connection, wallet, amount) => {
    const ata = await getAta(wallet.publicKey, SOL_ID);

    const transaction = new Transaction();
    const wsol = await walletGetWSOLBalance();

    if (wsol <= 0) {
      transaction.add(
        getAssociatedTokenAccountInstruction(
          wallet.publicKey,
          ata,
          wallet.publicKey,
          SOL_ID,
          TOKEN_PROGRAM_ID,
          ASSOCIATED_TOKEN_PROGRAM_ID
        )
      );
    }

    transaction.add(
      SystemProgram.transfer({
        fromPubkey: wallet.publicKey,
        toPubkey: ata,
        lamports: amount
      }),
      getSyncNativeInstruction(
        ata
      )
    );

    const tx = await sendAndConfirmTransaction(connection, transaction, [wallet]);
    console.log(tx);
  };

不错

写得不错

真的很无语

经过我的努力

才让官方在demo里面加了东西

async function howToUse() {
  // sol -> new Currency(9, 'SOL', 'SOL')
  const outputToken = new Token(new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'), 6, 'USDC', 'USDC') // USDC
  const inputToken = new Token(new PublicKey('4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R'), 6, 'RAY', 'RAY') // RAY
  // const inputToken = new Currency(9, 'SOL', 'SOL')

  const inputTokenAmount = new (inputToken instanceof Token ? TokenAmount : CurrencyAmount)(inputToken, 100)
  const slippage = new Percent(1, 100)
  const walletTokenAccounts = await getWalletTokenAccount(connection, wallet.publicKey)

  routeSwap({
    inputToken,
    outputToken,
    inputTokenAmount,
    slippage,
    walletTokenAccounts,
    wallet: wallet,
  }).then(({ txids }) => {
    /** continue with txids */
    console.log('txids', txids)
  })
}

howToUse()

我也算是推动了一点点发展吧

无语

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值