今天我们来用一下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
然后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()
我也算是推动了一点点发展吧
无语