├── .gitignore ├── .prettierignore ├── .prettierrc ├── README.md ├── config.ts ├── package.json ├── src ├── ammAddLiquidity.ts ├── ammCreatePool.ts ├── ammRemoveLiquidity.ts ├── ammV4MockPoolInfo.ts ├── autoAddPosition.ts ├── calculateClmmApr.ts ├── calculateFarmApr.ts ├── checkClmmPosition.ts ├── clmmAddPosition.ts ├── clmmCreatePool.ts ├── clmmCreatePosition.ts ├── clmmInitPoolReward.ts ├── clmmMarketMaker │ ├── clmmTx.ts │ ├── index.ts │ ├── tokenAccount.ts │ └── util.ts ├── clmmOwnerPositionInfo.ts ├── clmmRemovePosition.ts ├── clmmSetPoolReward.ts ├── createFarm.ts ├── formatAmmKeys.ts ├── formatAmmKeysById.ts ├── formatClmmConfigs.ts ├── formatClmmKeys.ts ├── formatClmmKeysById.ts ├── getClmmPoolInfo.ts ├── getOutOfRangePositionOutAmount.ts ├── harvestAndAddPosition.ts ├── stakeFarm.ts ├── subNewAmmPool.ts ├── swapOnlyAmm.ts ├── swapOnlyCLMM.ts ├── swapRoute.ts ├── unstakeFarm.ts ├── util.ts ├── utils1216.ts ├── utilsChangeMintAuthority.ts └── utilsCreateMarket.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | config.ts 7 | js/ -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | yarn.lock 4 | package-lock.json 5 | public 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "singleQuote": true, 5 | "semi": false, 6 | "bracketSpacing": true, 7 | "printWidth": 120, 8 | "arrowParens": "always", 9 | "eslintIntegration": true 10 | } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RAYDIUM SDK V1 demo 2 | 3 | ## About the project 4 | This project is for [RAYDIUM SDK](https://github.com/raydium-io/raydium-sdk) demonstration 5 | 6 | ## Getting Started 7 | ### Installation 8 | 9 | `yarn install` 10 | 11 | this will install the dependencies for running the demo script 12 | 13 | ### Prerequisites 14 | Modify `config.ts.template` to fit your configuration, and rename it to `config.ts` 15 | 16 | - ``: replace to your own one 17 | - ``: replace to your prefer one 18 | 19 | ### Usage 20 | 21 | - `yarn clean` clean up the old scripts (you don't need this for the very first time) 22 | - `yarn build` build the scripts 23 | - `yarn start js/src/` run the specific demo script 24 | - `yarn ammv3-market 2QdhepnKRTLjjSqPL1PtKNwqrUkoLee5Gqs8bvZhRdMv 10 20` run ammv3 market maker, arguments 0: poolId, 1: create position deviation, 2: close position deviation, remember to replace rpc url and secret key in code and uncomment `closePositionTx` and `createPositionTx` code part 25 | 26 | ![image](https://github.com/raydium-io/raydium-sdk-V1-demo/assets/6680106/95ddb134-fd02-40eb-a868-3effcfdb2d5e) 27 | 28 | 29 | 30 | you can simply combine the command to run a demo, e.g 31 | 32 | `yarn clean && yarn build && yarn start js/src/stakeFarm.js` 33 | 34 | -------------------------------------------------------------------------------- /config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ENDPOINT as _ENDPOINT, 3 | Currency, 4 | LOOKUP_TABLE_CACHE, 5 | MAINNET_PROGRAM_ID, 6 | RAYDIUM_MAINNET, 7 | Token, 8 | TOKEN_PROGRAM_ID, 9 | TxVersion, 10 | } from '@raydium-io/raydium-sdk'; 11 | import { 12 | Connection, 13 | Keypair, 14 | PublicKey, 15 | } from '@solana/web3.js'; 16 | 17 | export const rpcUrl: string = 'https://xxx.xxx.xxx/' 18 | export const rpcToken: string | undefined = undefined 19 | 20 | export const wallet = Keypair.fromSecretKey(Buffer.from('')) 21 | 22 | export const connection = new Connection(''); 23 | 24 | export const PROGRAMIDS = MAINNET_PROGRAM_ID; 25 | 26 | export const ENDPOINT = _ENDPOINT; 27 | 28 | export const RAYDIUM_MAINNET_API = RAYDIUM_MAINNET; 29 | 30 | export const makeTxVersion = TxVersion.V0; // LEGACY 31 | 32 | export const addLookupTableInfo = LOOKUP_TABLE_CACHE // only mainnet. other = undefined 33 | 34 | export const DEFAULT_TOKEN = { 35 | 'SOL': new Currency(9, 'USDC', 'USDC'), 36 | 'WSOL': new Token(TOKEN_PROGRAM_ID, new PublicKey('So11111111111111111111111111111111111111112'), 9, 'WSOL', 'WSOL'), 37 | 'USDC': new Token(TOKEN_PROGRAM_ID, new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'), 6, 'USDC', 'USDC'), 38 | 'RAY': new Token(TOKEN_PROGRAM_ID, new PublicKey('4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R'), 6, 'RAY', 'RAY'), 39 | 'RAY_USDC-LP': new Token(TOKEN_PROGRAM_ID, new PublicKey('FGYXP4vBkMEtKhxrmEBcWN8VNmXX8qNgEJpENKDETZ4Y'), 6, 'RAY-USDC', 'RAY-USDC'), 40 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@raydium-io/raydium-sdk": "1.3.1-beta.50", 4 | "@solana/spl-token": "^0.4.1", 5 | "@solana/web3.js": "1.90.0", 6 | "@triton-one/yellowstone-grpc": "^0.3.0", 7 | "@types/node-cron": "^3.0.11", 8 | "axios": "^1.6.7", 9 | "bs58": "^5.0.0", 10 | "decimal.js": "^10.4.3", 11 | "node-cron": "^3.0.3", 12 | "ts-node": "^10.9.2", 13 | "typescript": "^5.3.3" 14 | }, 15 | "devDependencies": { 16 | "@types/bn.js": "^5.1.5", 17 | "npm-check-updates": "^16.14.15" 18 | }, 19 | "scripts": { 20 | "start": "node", 21 | "build": "tsc", 22 | "clean": "tsc --build --clean", 23 | "ammv3-market": "ts-node src/ammV3MarketMaker/index.ts" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/ammAddLiquidity.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | import { 4 | CurrencyAmount, 5 | jsonInfo2PoolKeys, 6 | Liquidity, 7 | LiquidityPoolKeys, 8 | Percent, 9 | Token, 10 | TokenAmount 11 | } from '@raydium-io/raydium-sdk'; 12 | import { Keypair } from '@solana/web3.js'; 13 | 14 | import Decimal from 'decimal.js'; 15 | import { 16 | connection, 17 | DEFAULT_TOKEN, 18 | makeTxVersion, 19 | wallet 20 | } from '../config'; 21 | import { formatAmmKeysById } from './formatAmmKeysById'; 22 | import { 23 | buildAndSendTx, 24 | getWalletTokenAccount, 25 | } from './util'; 26 | 27 | type WalletTokenAccounts = Awaited> 28 | type TestTxInputInfo = { 29 | baseToken: Token 30 | quoteToken: Token 31 | targetPool: string 32 | inputTokenAmount: TokenAmount 33 | slippage: Percent 34 | walletTokenAccounts: WalletTokenAccounts 35 | wallet: Keypair 36 | } 37 | 38 | async function ammAddLiquidity( 39 | input: TestTxInputInfo 40 | ): Promise<{ txids: string[]; anotherAmount: TokenAmount | CurrencyAmount }> { 41 | const targetPoolInfo = await formatAmmKeysById(input.targetPool) 42 | assert(targetPoolInfo, 'cannot find the target pool') 43 | 44 | // -------- step 1: compute another amount -------- 45 | const poolKeys = jsonInfo2PoolKeys(targetPoolInfo) as LiquidityPoolKeys 46 | const extraPoolInfo = await Liquidity.fetchInfo({ connection, poolKeys }) 47 | const { maxAnotherAmount, anotherAmount, liquidity } = Liquidity.computeAnotherAmount({ 48 | poolKeys, 49 | poolInfo: { ...targetPoolInfo, ...extraPoolInfo }, 50 | amount: input.inputTokenAmount, 51 | anotherCurrency: input.quoteToken, 52 | slippage: input.slippage, 53 | }) 54 | 55 | console.log('will add liquidity info', { 56 | liquidity: liquidity.toString(), 57 | liquidityD: new Decimal(liquidity.toString()).div(10 ** extraPoolInfo.lpDecimals), 58 | }) 59 | 60 | // -------- step 2: make instructions -------- 61 | const addLiquidityInstructionResponse = await Liquidity.makeAddLiquidityInstructionSimple({ 62 | connection, 63 | poolKeys, 64 | userKeys: { 65 | owner: input.wallet.publicKey, 66 | payer: input.wallet.publicKey, 67 | tokenAccounts: input.walletTokenAccounts, 68 | }, 69 | amountInA: input.inputTokenAmount, 70 | amountInB: maxAnotherAmount, 71 | fixedSide: 'a', 72 | makeTxVersion, 73 | }) 74 | 75 | return { txids: await buildAndSendTx(addLiquidityInstructionResponse.innerTransactions), anotherAmount } 76 | } 77 | 78 | async function howToUse() { 79 | const baseToken = DEFAULT_TOKEN.USDC // USDC 80 | const quoteToken = DEFAULT_TOKEN.RAY // RAY 81 | const targetPool = 'pool id' 82 | const inputTokenAmount = new TokenAmount(baseToken, 100) 83 | const slippage = new Percent(1, 100) 84 | const walletTokenAccounts = await getWalletTokenAccount(connection, wallet.publicKey) 85 | 86 | ammAddLiquidity({ 87 | baseToken, 88 | quoteToken, 89 | targetPool, 90 | inputTokenAmount, 91 | slippage, 92 | walletTokenAccounts, 93 | wallet: wallet, 94 | }).then(({ txids, anotherAmount }) => { 95 | /** continue with txids */ 96 | console.log('txids', txids) 97 | }) 98 | } 99 | -------------------------------------------------------------------------------- /src/ammCreatePool.ts: -------------------------------------------------------------------------------- 1 | import { BN } from 'bn.js'; 2 | 3 | import { 4 | Liquidity, 5 | MAINNET_PROGRAM_ID, 6 | Token, 7 | } from '@raydium-io/raydium-sdk'; 8 | import { 9 | Keypair, 10 | PublicKey, 11 | } from '@solana/web3.js'; 12 | 13 | import { 14 | connection, 15 | DEFAULT_TOKEN, 16 | makeTxVersion, 17 | PROGRAMIDS, 18 | wallet, 19 | } from '../config'; 20 | import { 21 | buildAndSendTx, 22 | getWalletTokenAccount, 23 | } from './util'; 24 | import Decimal from 'decimal.js'; 25 | 26 | const ZERO = new BN(0) 27 | type BN = typeof ZERO 28 | 29 | type CalcStartPrice = { 30 | addBaseAmount: BN 31 | addQuoteAmount: BN 32 | } 33 | 34 | type LiquidityPairTargetInfo = { 35 | baseToken: Token 36 | quoteToken: Token 37 | targetMarketId: PublicKey 38 | } 39 | 40 | type WalletTokenAccounts = Awaited> 41 | type TestTxInputInfo = LiquidityPairTargetInfo & 42 | CalcStartPrice & { 43 | startTime: number // seconds 44 | walletTokenAccounts: WalletTokenAccounts 45 | wallet: Keypair 46 | } 47 | 48 | async function ammCreatePool(input: TestTxInputInfo): Promise<{ txids: string[] }> { 49 | // -------- step 1: make instructions -------- 50 | const initPoolInstructionResponse = await Liquidity.makeCreatePoolV4InstructionV2Simple({ 51 | connection, 52 | programId: PROGRAMIDS.AmmV4, 53 | marketInfo: { 54 | marketId: input.targetMarketId, 55 | programId: PROGRAMIDS.OPENBOOK_MARKET, 56 | }, 57 | baseMintInfo: input.baseToken, 58 | quoteMintInfo: input.quoteToken, 59 | baseAmount: input.addBaseAmount, 60 | quoteAmount: input.addQuoteAmount, 61 | startTime: new BN(Math.floor(input.startTime)), 62 | ownerInfo: { 63 | feePayer: input.wallet.publicKey, 64 | wallet: input.wallet.publicKey, 65 | tokenAccounts: input.walletTokenAccounts, 66 | useSOLBalance: true, 67 | }, 68 | associatedOnly: false, 69 | checkCreateATAOwner: true, 70 | makeTxVersion, 71 | feeDestinationId: new PublicKey('7YttLkHDoNj9wyDur5pM1ejNaAvT9X4eqaYcHQqtj2G5'), // only mainnet use this 72 | }) 73 | 74 | return { txids: await buildAndSendTx(initPoolInstructionResponse.innerTransactions) } 75 | } 76 | 77 | async function howToUse() { 78 | const baseToken = DEFAULT_TOKEN.USDC // USDC 79 | const quoteToken = DEFAULT_TOKEN.RAY // RAY 80 | const targetMarketId = Keypair.generate().publicKey 81 | const addBaseAmount = new BN(10000) 82 | const addQuoteAmount = new BN(10000) 83 | const startTime = Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7 // start from 7 days later 84 | const walletTokenAccounts = await getWalletTokenAccount(connection, wallet.publicKey) 85 | 86 | /* do something with start price if needed */ 87 | console.log('pool price', new Decimal(addBaseAmount.toString()).div(new Decimal(10 ** baseToken.decimals)).div(new Decimal(addQuoteAmount.toString()).div(new Decimal(10 ** quoteToken.decimals))).toString()) 88 | 89 | ammCreatePool({ 90 | startTime, 91 | addBaseAmount, 92 | addQuoteAmount, 93 | baseToken, 94 | quoteToken, 95 | targetMarketId, 96 | wallet, 97 | walletTokenAccounts, 98 | }).then(({ txids }) => { 99 | /** continue with txids */ 100 | console.log('txids', txids) 101 | }) 102 | } 103 | -------------------------------------------------------------------------------- /src/ammRemoveLiquidity.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | import { 4 | jsonInfo2PoolKeys, 5 | Liquidity, 6 | LiquidityPoolKeys, 7 | TokenAmount 8 | } from '@raydium-io/raydium-sdk'; 9 | import { Keypair } from '@solana/web3.js'; 10 | 11 | import { 12 | connection, 13 | DEFAULT_TOKEN, 14 | makeTxVersion, 15 | wallet 16 | } from '../config'; 17 | import { formatAmmKeysById } from './formatAmmKeysById'; 18 | import { 19 | buildAndSendTx, 20 | getWalletTokenAccount, 21 | } from './util'; 22 | 23 | type WalletTokenAccounts = Awaited> 24 | type TestTxInputInfo = { 25 | removeLpTokenAmount: TokenAmount 26 | targetPool: string 27 | walletTokenAccounts: WalletTokenAccounts 28 | wallet: Keypair 29 | } 30 | 31 | async function ammRemoveLiquidity(input: TestTxInputInfo) { 32 | // -------- pre-action: fetch basic info -------- 33 | const targetPoolInfo = await formatAmmKeysById(input.targetPool) 34 | assert(targetPoolInfo, 'cannot find the target pool') 35 | 36 | // -------- step 1: make instructions -------- 37 | const poolKeys = jsonInfo2PoolKeys(targetPoolInfo) as LiquidityPoolKeys 38 | const removeLiquidityInstructionResponse = await Liquidity.makeRemoveLiquidityInstructionSimple({ 39 | connection, 40 | poolKeys, 41 | userKeys: { 42 | owner: input.wallet.publicKey, 43 | payer: input.wallet.publicKey, 44 | tokenAccounts: input.walletTokenAccounts, 45 | }, 46 | amountIn: input.removeLpTokenAmount, 47 | makeTxVersion, 48 | }) 49 | 50 | return { txids: await buildAndSendTx(removeLiquidityInstructionResponse.innerTransactions) } 51 | } 52 | 53 | async function howToUse() { 54 | const lpToken = DEFAULT_TOKEN['RAY_USDC-LP'] // LP 55 | const removeLpTokenAmount = new TokenAmount(lpToken, 100) 56 | const targetPool = 'pool id' // RAY-USDC pool 57 | const walletTokenAccounts = await getWalletTokenAccount(connection, wallet.publicKey) 58 | 59 | ammRemoveLiquidity({ 60 | removeLpTokenAmount, 61 | targetPool, 62 | walletTokenAccounts, 63 | wallet: wallet, 64 | }).then(({ txids }) => { 65 | /** continue with txids */ 66 | console.log('txids', txids) 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /src/ammV4MockPoolInfo.ts: -------------------------------------------------------------------------------- 1 | import { Liquidity } from '@raydium-io/raydium-sdk' 2 | import { PublicKey } from '@solana/web3.js' 3 | 4 | async function generateV4PoolInfo() { 5 | // RAY-USDC 6 | const poolInfo = Liquidity.getAssociatedPoolKeys({ 7 | version: 4, 8 | marketVersion: 3, 9 | baseMint: new PublicKey('4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R'), 10 | quoteMint: new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'), 11 | baseDecimals: 6, 12 | quoteDecimals: 6, 13 | programId: new PublicKey('675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8'), 14 | 15 | marketId: new PublicKey('DZjbn4XC8qoHKikZqzmhemykVzmossoayV9ffbsUqxVj'), 16 | marketProgramId: new PublicKey('srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX'), 17 | }) 18 | 19 | return { poolInfo } 20 | } 21 | 22 | async function howToUse() { 23 | generateV4PoolInfo().then(({ poolInfo }) => { 24 | console.log('poolInfo: ', poolInfo) 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /src/autoAddPosition.ts: -------------------------------------------------------------------------------- 1 | import BN from 'bn.js'; 2 | 3 | import { 4 | ApiClmmPoolsItem, 5 | ApiPoolInfo, 6 | Clmm, 7 | ClmmPoolInfo, 8 | ClmmPoolPersonalPosition, 9 | ComputeAmountOutAmmLayout, 10 | ComputeAmountOutRouteLayout, 11 | fetchMultipleMintInfos, 12 | getPdaPersonalPositionAddress, 13 | LiquidityMath, 14 | Percent, 15 | PositionInfoLayout, 16 | ReturnTypeFetchMultipleInfo, 17 | ReturnTypeFetchMultiplePoolTickArrays, 18 | ReturnTypeGetAllRoute, 19 | Token, 20 | TokenAccount, 21 | TokenAmount, 22 | TradeV2, 23 | ZERO 24 | } from '@raydium-io/raydium-sdk'; 25 | import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token'; 26 | import { 27 | PublicKey 28 | } from '@solana/web3.js'; 29 | 30 | import Decimal from 'decimal.js'; 31 | import { 32 | connection, 33 | makeTxVersion, 34 | PROGRAMIDS, 35 | wallet 36 | } from '../config'; 37 | import { formatAmmKeysToApi } from './formatAmmKeys'; 38 | import { formatClmmKeys } from './formatClmmKeys'; 39 | import { 40 | buildAndSendTx, 41 | getATAAddress, 42 | getWalletTokenAccount, 43 | sleepTime 44 | } from './util'; 45 | 46 | async function autoAddPosition() { 47 | const positionMint = '' // pls input mint 48 | 49 | const walletTokenAccounts = await getWalletTokenAccount(connection, wallet.publicKey) 50 | const positionAccount = walletTokenAccounts.find(i => i.accountInfo.mint.toString() === positionMint && i.accountInfo.amount.toNumber() === 1) 51 | if (positionAccount === undefined) { 52 | throw Error('find positon from wallet error') 53 | } 54 | 55 | const positionAccountAddress = getPdaPersonalPositionAddress(PROGRAMIDS.CLMM, new PublicKey(positionMint)).publicKey 56 | const positionAccountInfo = await connection.getAccountInfo(positionAccountAddress) 57 | if (positionAccountInfo === null) throw Error('get positionAccountInfo error') 58 | const positionAccountData = PositionInfoLayout.decode(positionAccountInfo.data) 59 | 60 | const positionPooId = positionAccountData.poolId 61 | console.log('position pool id -> ', positionPooId.toString()) 62 | 63 | const clmmPools: ApiClmmPoolsItem[] = await formatClmmKeys(PROGRAMIDS.CLMM.toString(), true) 64 | 65 | const clmmPool = clmmPools.find(i => i.id === positionPooId.toString()) 66 | if (clmmPool === undefined) throw Error('not found pool info from api') 67 | 68 | const clmmPoolInfo = await Clmm.fetchMultiplePoolInfos({ 69 | connection, 70 | poolKeys: [clmmPool], 71 | chainTime: new Date().getTime() / 1000, 72 | ownerInfo: { 73 | wallet: wallet.publicKey, 74 | tokenAccounts: [positionAccount], 75 | }, 76 | batchRequest: true, 77 | updateOwnerRewardAndFee: true, 78 | }) 79 | 80 | const clmmInfo = clmmPoolInfo[positionPooId.toString()].state 81 | const ownerPositionInfo = clmmPoolInfo[positionPooId.toString()].positionAccount![0] 82 | 83 | const ownerMintAtaA = getATAAddress(clmmInfo.mintA.programId, wallet.publicKey, clmmInfo.mintA.mint).publicKey 84 | const ownerMintAtaB = getATAAddress(clmmInfo.mintB.programId, wallet.publicKey, clmmInfo.mintB.mint).publicKey 85 | const ownerAccountA = walletTokenAccounts.find(i => i.pubkey.equals(ownerMintAtaA))?.accountInfo.amount ?? ZERO 86 | const ownerAccountB = walletTokenAccounts.find(i => i.pubkey.equals(ownerMintAtaB))?.accountInfo.amount ?? ZERO 87 | 88 | const clmmList = Object.values( 89 | await Clmm.fetchMultiplePoolInfos({ connection, poolKeys: clmmPools, chainTime: new Date().getTime() / 1000 }) 90 | ).map((i) => i.state) 91 | const sPool: ApiPoolInfo = await formatAmmKeysToApi(PROGRAMIDS.AmmV4.toString(), true) 92 | 93 | await autoAddPositionFunc({ 94 | poolInfo: clmmInfo, 95 | positionInfo: ownerPositionInfo, 96 | addMintAmountA: ownerAccountA, 97 | addMintAmountB: ownerAccountB, 98 | walletTokenAccounts, 99 | clmmList, 100 | sPool, 101 | clmmPools, 102 | }) 103 | } 104 | 105 | 106 | export async function autoAddPositionFunc({ poolInfo, positionInfo, addMintAmountA, addMintAmountB, walletTokenAccounts, clmmList, sPool, clmmPools }: { 107 | poolInfo: ClmmPoolInfo, 108 | positionInfo: ClmmPoolPersonalPosition, 109 | addMintAmountA: BN, 110 | addMintAmountB: BN, 111 | walletTokenAccounts: TokenAccount[], 112 | clmmList: ClmmPoolInfo[], 113 | sPool: ApiPoolInfo, 114 | clmmPools: ApiClmmPoolsItem[] 115 | }) { 116 | if (addMintAmountA.isZero() && addMintAmountB.isZero()) new Error('input amount is zero') 117 | console.log('will add amount -> ', addMintAmountA.toString(), addMintAmountB.toString()) 118 | 119 | const priceLower = Clmm.getTickPrice({ 120 | poolInfo, 121 | tick: positionInfo.tickLower, 122 | baseIn: true, 123 | }) 124 | const priceUpper = Clmm.getTickPrice({ 125 | poolInfo, 126 | tick: positionInfo.tickUpper, 127 | baseIn: true, 128 | }) 129 | const { amountA, amountB } = LiquidityMath.getAmountsFromLiquidity( 130 | poolInfo.sqrtPriceX64, 131 | priceLower.tickSqrtPriceX64, 132 | priceUpper.tickSqrtPriceX64, 133 | new BN(1000000000), 134 | false, 135 | ) 136 | 137 | let swapRatio: number[] = [] 138 | if (amountA.isZero() || amountB.isZero()) { 139 | swapRatio = [1] 140 | } else { 141 | swapRatio = Array.from({ length: Math.ceil(1 / 0.05) }, (_, i) => Math.floor((i + 1) * 0.05 * 100) / 100) 142 | } 143 | 144 | const willR = new Decimal(amountA.toString()).div(amountB.toString()) 145 | 146 | let swapType: 'A To B' | 'B To A' | 'not need swap base A' | 'not need swap base B' 147 | if (amountB.isZero() && addMintAmountB.isZero()) { 148 | swapType = 'not need swap base A' 149 | } else if (amountB.isZero()) { 150 | swapType = 'B To A' 151 | } else if (addMintAmountB.isZero()) { 152 | swapType = 'A To B' 153 | } else { 154 | const amountR = new Decimal(addMintAmountA.toString()).div(addMintAmountB.toString()) 155 | 156 | if (willR.eq(amountR)) swapType = 'not need swap base B' // amount A = 0 157 | else swapType = willR.gt(amountR) ? 'B To A' : 'A To B' 158 | } 159 | 160 | console.log('will add pisition ratio', JSON.stringify({ amountA: String(amountA), amountB: String(amountB), ratio: willR })) 161 | 162 | const poolMintA = new Token(poolInfo.mintA.programId, poolInfo.mintA.mint, poolInfo.mintA.decimals) 163 | const poolMintB = new Token(poolInfo.mintB.programId, poolInfo.mintB.mint, poolInfo.mintB.decimals) 164 | 165 | let willRouteInfo: ComputeAmountOutLayout | undefined = undefined 166 | let willPositionInputAmount: BN = ZERO 167 | let willPositionOtherAmountMax: BN = ZERO 168 | let baseA = swapType === 'A To B' || swapType === 'not need swap base A' 169 | 170 | if (swapType === 'not need swap base A') { 171 | willPositionInputAmount = addMintAmountA 172 | willPositionOtherAmountMax = addMintAmountB 173 | } 174 | if (swapType === 'not need swap base B') { 175 | willPositionInputAmount = addMintAmountB 176 | willPositionOtherAmountMax = addMintAmountA 177 | } 178 | 179 | if (swapType === 'A To B') { 180 | const fromToken = poolMintA 181 | const toToken = poolMintB 182 | const fromAmount = addMintAmountA 183 | const toAmount = addMintAmountB 184 | 185 | const _willSwapAmount = fromAmount 186 | const swapFromMintTokenAmount = new TokenAmount(fromToken, _willSwapAmount) 187 | const { getRoute, tickCache, poolInfosCache, } = await swapStep1({ 188 | swapFromMintTokenAmount, 189 | swapToMintToken: toToken, 190 | clmmList, 191 | sPool, 192 | }) 193 | 194 | for (const itemRatio of swapRatio) { 195 | const willSwapAmount = new BN(new Decimal(fromAmount.toString()).mul(itemRatio).toFixed(0)) 196 | const swapFromMintTokenAmount = new TokenAmount(fromToken, willSwapAmount) 197 | 198 | const routeInfo = await swapStep2({ 199 | getRoute, tickCache, poolInfosCache, swapFromMintTokenAmount, 200 | swapToMintToken: toToken, 201 | slippage: new Percent(1, 100), 202 | clmmPools, 203 | }) 204 | 205 | const outA = fromAmount.sub(willSwapAmount) 206 | const outB = toAmount.add(routeInfo.minAmountOut.amount.raw) 207 | 208 | if (!outB.isZero() && new Decimal(outA.toString()).div(outB.toString()).lte(willR)) { 209 | if (outA.isZero()) { 210 | baseA = false 211 | willPositionInputAmount = outB 212 | willPositionOtherAmountMax = ZERO 213 | } else { 214 | willPositionInputAmount = outA 215 | willPositionOtherAmountMax = toAmount.add(routeInfo.amountOut.amount.raw) 216 | } 217 | 218 | willRouteInfo = routeInfo 219 | console.log('will swap A To B info ->', JSON.stringify({ 220 | fromToken: fromToken.mint.toString(), 221 | toToken: toToken.mint.toString(), 222 | fromAmount: willSwapAmount.toString(), 223 | toAmountMin: routeInfo.minAmountOut.amount.raw.toString(), 224 | swapRatio: itemRatio, 225 | })) 226 | break 227 | } 228 | } 229 | } else if (swapType === 'B To A') { 230 | const fromToken = poolMintB 231 | const toToken = poolMintA 232 | const fromAmount = addMintAmountB 233 | const toAmount = addMintAmountA 234 | 235 | const _willSwapAmount = fromAmount 236 | const swapFromMintTokenAmount = new TokenAmount(fromToken, _willSwapAmount) 237 | const { getRoute, tickCache, poolInfosCache, } = await swapStep1({ 238 | swapFromMintTokenAmount, 239 | swapToMintToken: toToken, 240 | clmmList, 241 | sPool, 242 | }) 243 | 244 | for (const itemRatio of swapRatio) { 245 | const willSwapAmount = new BN(new Decimal(fromAmount.toString()).mul(itemRatio).toFixed(0)) 246 | const swapFromMintTokenAmount = new TokenAmount(fromToken, willSwapAmount) 247 | 248 | const routeInfo = await swapStep2({ 249 | getRoute, tickCache, poolInfosCache, swapFromMintTokenAmount, 250 | swapToMintToken: toToken, 251 | slippage: new Percent(1, 100), 252 | clmmPools, 253 | }) 254 | 255 | const outB = fromAmount.sub(willSwapAmount) 256 | const outA = toAmount.add(routeInfo.minAmountOut.amount.raw) 257 | 258 | if (!outA.isZero() && new Decimal(outB.toString()).div(outA.toString()).lte(new Decimal(1).div(willR))) { 259 | if (outB.isZero()) { 260 | baseA = true 261 | willPositionInputAmount = outA 262 | willPositionOtherAmountMax = ZERO 263 | } else { 264 | willPositionInputAmount = outB 265 | willPositionOtherAmountMax = toAmount.add(routeInfo.amountOut.amount.raw) 266 | } 267 | 268 | willRouteInfo = routeInfo 269 | console.log('will swap B To A info ->', JSON.stringify({ 270 | fromToken: fromToken.mint.toString(), 271 | toToken: toToken.mint.toString(), 272 | fromAmount: willSwapAmount.toString(), 273 | toAmountMin: routeInfo.minAmountOut.amount.raw.toString(), 274 | swapRatio: itemRatio, 275 | })) 276 | break 277 | } 278 | } 279 | } 280 | 281 | if (willRouteInfo !== undefined) { 282 | console.log('send Swap Instruction') 283 | 284 | const swapIns = await swapStep3({ 285 | wallet: wallet.publicKey, 286 | tokenAccounts: walletTokenAccounts, 287 | routeInfo: willRouteInfo, 288 | }) 289 | 290 | console.log('swap txid -> ', await buildAndSendTx(swapIns)) 291 | } 292 | 293 | const ins = await Clmm.makeIncreasePositionFromBaseInstructionSimple({ 294 | makeTxVersion, 295 | connection, 296 | poolInfo, 297 | ownerPosition: positionInfo, 298 | ownerInfo: { 299 | feePayer: wallet.publicKey, 300 | wallet: wallet.publicKey, 301 | tokenAccounts: walletTokenAccounts, 302 | }, 303 | 304 | base: baseA ? 'MintA' : 'MintB', 305 | baseAmount: willPositionInputAmount!, 306 | otherAmountMax: willPositionOtherAmountMax!, 307 | 308 | associatedOnly: true, 309 | }) 310 | 311 | await sleepTime(3 * 1000) 312 | 313 | console.log('increase position txid -> ', await buildAndSendTx(ins.innerTransactions, { skipPreflight: true })) 314 | } 315 | 316 | async function swapStep1({ swapFromMintTokenAmount, swapToMintToken, clmmList, sPool }: { 317 | swapFromMintTokenAmount: TokenAmount, 318 | swapToMintToken: Token, 319 | clmmList: ClmmPoolInfo[], 320 | sPool: ApiPoolInfo, 321 | }) { 322 | const getRoute = TradeV2.getAllRoute({ 323 | inputMint: swapFromMintTokenAmount.token.mint, 324 | outputMint: swapToMintToken.mint, 325 | apiPoolList: sPool, 326 | clmmList, 327 | }) 328 | 329 | const [tickCache, poolInfosCache] = await Promise.all([ 330 | await Clmm.fetchMultiplePoolTickArrays({ connection, poolKeys: getRoute.needTickArray, batchRequest: true }), 331 | await TradeV2.fetchMultipleInfo({ connection, pools: getRoute.needSimulate, batchRequest: true }), 332 | ]) 333 | 334 | return { getRoute, tickCache, poolInfosCache } 335 | } 336 | 337 | async function swapStep2({ getRoute, tickCache, poolInfosCache, swapFromMintTokenAmount, swapToMintToken, slippage, clmmPools }: { 338 | getRoute: ReturnTypeGetAllRoute, 339 | tickCache: ReturnTypeFetchMultiplePoolTickArrays, 340 | poolInfosCache: ReturnTypeFetchMultipleInfo, 341 | swapFromMintTokenAmount: TokenAmount, 342 | swapToMintToken: Token, 343 | slippage: Percent, 344 | clmmPools: ApiClmmPoolsItem[] 345 | }) { 346 | const [routeInfo] = TradeV2.getAllRouteComputeAmountOut({ 347 | directPath: getRoute.directPath, 348 | routePathDict: getRoute.routePathDict, 349 | simulateCache: poolInfosCache, 350 | tickCache, 351 | inputTokenAmount: swapFromMintTokenAmount, 352 | outputToken: swapToMintToken, 353 | slippage, 354 | chainTime: new Date().getTime() / 1000, // this chain time 355 | 356 | mintInfos: await fetchMultipleMintInfos({ 357 | connection, mints: [ 358 | ...clmmPools.map(i => [{ mint: i.mintA, program: i.mintProgramIdA }, { mint: i.mintB, program: i.mintProgramIdB }]).flat().filter(i => i.program === TOKEN_2022_PROGRAM_ID.toString()).map(i => new PublicKey(i.mint)), 359 | ] 360 | }), 361 | 362 | epochInfo: await connection.getEpochInfo(), 363 | }) 364 | 365 | return routeInfo 366 | } 367 | 368 | type ComputeAmountOutLayout = ComputeAmountOutAmmLayout | ComputeAmountOutRouteLayout 369 | async function swapStep3({ routeInfo, wallet, tokenAccounts }: { 370 | wallet: PublicKey, 371 | tokenAccounts: TokenAccount[], 372 | routeInfo: ComputeAmountOutLayout 373 | }) { 374 | const { innerTransactions } = await TradeV2.makeSwapInstructionSimple({ 375 | routeProgram: PROGRAMIDS.Router, 376 | connection, 377 | swapInfo: routeInfo, 378 | ownerInfo: { 379 | wallet, 380 | tokenAccounts, 381 | associatedOnly: true, 382 | checkCreateATAOwner: true, 383 | }, 384 | makeTxVersion, 385 | }) 386 | 387 | return innerTransactions 388 | } 389 | 390 | -------------------------------------------------------------------------------- /src/calculateClmmApr.ts: -------------------------------------------------------------------------------- 1 | import { ApiClmmPoolsItem, MathUtil, PoolInfoLayout } from "@raydium-io/raydium-sdk" 2 | import { ParsedAccountData, PublicKey } from "@solana/web3.js" 3 | import Decimal from "decimal.js" 4 | import { ENDPOINT, PROGRAMIDS, RAYDIUM_MAINNET_API, connection } from "../config" 5 | import { formatClmmKeys } from "./formatClmmKeys" 6 | 7 | 8 | async function calculateClmmApr() { 9 | const poolId = '' 10 | 11 | const poolAccountInfo = await connection.getAccountInfo(new PublicKey(poolId)) 12 | 13 | if (poolAccountInfo === null) throw Error('get pool account data error') 14 | 15 | const mintPrice: { [mint: string]: number } = {} 16 | for (const [mint, price] of Object.entries(await (await fetch(ENDPOINT + RAYDIUM_MAINNET_API.price)).json()) as [string, number][]) mintPrice[mint] = price 17 | 18 | const poolApiInfo: { [poolId: string]: ApiClmmPoolsItem } = {} 19 | for (const item of await formatClmmKeys(PROGRAMIDS.CLMM.toString(), true)) poolApiInfo[item.id] = item 20 | 21 | const apiPoolInfo = poolApiInfo[poolId] 22 | if (apiPoolInfo === undefined) throw Error('api pool info check error') 23 | 24 | const poolInfo = PoolInfoLayout.decode(poolAccountInfo.data) 25 | 26 | const chainTime = await connection.getBlockTime(await connection.getSlot()) 27 | if (chainTime === null) throw Error('get chain time error') 28 | 29 | const formatRewardInfo: { 30 | mint: string, 31 | price: number, 32 | sendCountYear: number, 33 | sendCountYearToU: number, 34 | tvl: number, 35 | apr: number, 36 | }[] = [] 37 | 38 | for (const rewardInfo of poolInfo.rewardInfos) { 39 | if (rewardInfo.tokenMint.equals(PublicKey.default)) continue 40 | 41 | const rewardVaultAdress = rewardInfo.tokenVault 42 | const rewardVaultAccount = await connection.getParsedAccountInfo(rewardVaultAdress) 43 | const rewardVaultAccountData = rewardVaultAccount.value?.data as ParsedAccountData 44 | if (rewardVaultAccountData.program !== 'spl-token') continue 45 | 46 | const rewardPerSecond = (rewardInfo.openTime.toNumber() < chainTime && rewardInfo.endTime.toNumber() > chainTime) ? MathUtil.x64ToDecimal(rewardInfo.emissionsPerSecondX64) : new Decimal(0) 47 | 48 | const sendCountYear = new Decimal(rewardPerSecond.mul(3600 * 24 * 365).toString()).div(10 ** rewardVaultAccountData.parsed.info.tokenAmount.decimals) 49 | const sendCountYearToU = sendCountYear.mul(mintPrice[rewardVaultAccountData.parsed.info.mint] ?? 0) 50 | 51 | const tvl = apiPoolInfo.tvl 52 | 53 | formatRewardInfo.push({ 54 | mint: rewardVaultAccountData.parsed.info.mint, 55 | price: mintPrice[rewardVaultAccountData.parsed.info.mint] ?? 0, 56 | sendCountYear: sendCountYear.toNumber(), 57 | sendCountYearToU: sendCountYearToU.toNumber(), 58 | tvl, 59 | apr: tvl !== 0 ? sendCountYearToU.div(tvl).toNumber() : 0, 60 | }) 61 | } 62 | 63 | console.log(formatRewardInfo) 64 | } 65 | 66 | calculateClmmApr() -------------------------------------------------------------------------------- /src/calculateFarmApr.ts: -------------------------------------------------------------------------------- 1 | import { FARM_STATE_LAYOUT_V3, FARM_STATE_LAYOUT_V5, FARM_STATE_LAYOUT_V6 } from "@raydium-io/raydium-sdk" 2 | import { ParsedAccountData, PublicKey } from "@solana/web3.js" 3 | import { BN } from "bn.js" 4 | import Decimal from "decimal.js" 5 | import { ENDPOINT, PROGRAMIDS, RAYDIUM_MAINNET_API, connection } from "../config" 6 | 7 | 8 | async function calculateFarmApr() { 9 | const poolId = '' 10 | 11 | const poolAccountInfo = await connection.getAccountInfo(new PublicKey(poolId)) 12 | 13 | if (poolAccountInfo === null) throw Error('get pool account data error') 14 | 15 | const mintPrice: { [mint: string]: number } = {} 16 | for (const [mint, price] of Object.entries(await (await fetch(ENDPOINT + RAYDIUM_MAINNET_API.price)).json()) as [string, number][]) mintPrice[mint] = price 17 | 18 | const poolTvl: { [poolId: string]: number } = {} 19 | for (const info of (await (await fetch(ENDPOINT + RAYDIUM_MAINNET_API.farmApr)).json()).data) poolTvl[info.id] = info.tvl 20 | 21 | const rewardInfo: { mint: string, price: number, sendCountYear: number, sendCountYearToU: number, tvl: number, apr: number }[] = [] 22 | 23 | switch (poolAccountInfo.owner.toString()) { 24 | case PROGRAMIDS.FarmV3.toString(): 25 | case PROGRAMIDS.FarmV5.toString(): { 26 | const layout = PROGRAMIDS.FarmV3.toString() === poolAccountInfo.owner.toString() ? FARM_STATE_LAYOUT_V3 : FARM_STATE_LAYOUT_V5 27 | const poolInfo = layout.decode(poolAccountInfo.data) 28 | 29 | const poolVaultAccount = await connection.getParsedAccountInfo(poolInfo.lpVault) 30 | const poolVaultAccountData = poolVaultAccount.value?.data as ParsedAccountData 31 | if (poolVaultAccountData.program !== 'spl-token') break 32 | 33 | for (const itemRewardInfo of poolInfo.rewardInfos) { 34 | const rewardVaultAdress = itemRewardInfo.rewardVault 35 | const rewardVaultAccount = await connection.getParsedAccountInfo(rewardVaultAdress) 36 | const rewardVaultAccountData = rewardVaultAccount.value?.data as ParsedAccountData 37 | if (rewardVaultAccountData.program !== 'spl-token') continue 38 | 39 | const sendCountYear = new Decimal(itemRewardInfo.perSlotReward.mul(new BN(2.5 * 3600 * 24 * 365)).toString()).div(10 ** rewardVaultAccountData.parsed.info.tokenAmount.decimals) // one slot -> 400ms 40 | const sendCountYearToU = sendCountYear.mul(mintPrice[rewardVaultAccountData.parsed.info.mint] ?? 0) 41 | 42 | const tvl = poolTvl[poolId] !== undefined ? poolTvl[poolId] : poolVaultAccountData.parsed.info.tokenAmount.uiAmount * (mintPrice[poolVaultAccountData.parsed.info.mint] ?? 0) 43 | 44 | rewardInfo.push({ 45 | mint: rewardVaultAccountData.parsed.info.mint, 46 | price: mintPrice[rewardVaultAccountData.parsed.info.mint] ?? 0, 47 | sendCountYear: sendCountYear.toNumber(), 48 | sendCountYearToU: sendCountYearToU.toNumber(), 49 | tvl, 50 | apr: tvl !== 0 ? sendCountYearToU.div(tvl).toNumber() : 0, 51 | }) 52 | } 53 | break 54 | } 55 | case PROGRAMIDS.FarmV6.toString(): { 56 | const layout = FARM_STATE_LAYOUT_V6 57 | const poolInfo = layout.decode(poolAccountInfo.data) 58 | 59 | const chainTime = await connection.getBlockTime(await connection.getSlot()) 60 | if (chainTime === null) throw Error('get chain time error') 61 | 62 | const poolVaultAccount = await connection.getParsedAccountInfo(poolInfo.lpVault) 63 | const poolVaultAccountData = poolVaultAccount.value?.data as ParsedAccountData 64 | if (poolVaultAccountData.program !== 'spl-token') break 65 | 66 | for (const itemRewardInfo of poolInfo.rewardInfos) { 67 | const rewardVaultAdress = itemRewardInfo.rewardVault 68 | const rewardVaultAccount = await connection.getParsedAccountInfo(rewardVaultAdress) 69 | const rewardVaultAccountData = rewardVaultAccount.value?.data as ParsedAccountData 70 | if (rewardVaultAccountData.program !== 'spl-token') continue 71 | 72 | const rewardPerSecond = (itemRewardInfo.rewardOpenTime.toNumber() < chainTime && itemRewardInfo.rewardEndTime.toNumber() > chainTime) ? itemRewardInfo.rewardPerSecond : new BN(0) 73 | 74 | const sendCountYear = new Decimal(rewardPerSecond.mul(new BN(3600 * 24 * 365)).toString()).div(10 ** rewardVaultAccountData.parsed.info.tokenAmount.decimals) 75 | const sendCountYearToU = sendCountYear.mul(mintPrice[rewardVaultAccountData.parsed.info.mint] ?? 0) 76 | 77 | const tvl = poolTvl[poolId] !== undefined ? poolTvl[poolId] : poolVaultAccountData.parsed.info.tokenAmount.uiAmount * (mintPrice[poolVaultAccountData.parsed.info.mint] ?? 0) 78 | 79 | rewardInfo.push({ 80 | mint: rewardVaultAccountData.parsed.info.mint, 81 | price: mintPrice[rewardVaultAccountData.parsed.info.mint] ?? 0, 82 | sendCountYear: sendCountYear.toNumber(), 83 | sendCountYearToU: sendCountYearToU.toNumber(), 84 | tvl, 85 | apr: tvl !== 0 ? sendCountYearToU.div(tvl).toNumber() : 0, 86 | }) 87 | } 88 | break 89 | } 90 | default: 91 | throw Error('program Id check error') 92 | } 93 | console.log(rewardInfo) 94 | } 95 | -------------------------------------------------------------------------------- /src/checkClmmPosition.ts: -------------------------------------------------------------------------------- 1 | import BN from 'bn.js'; 2 | import Decimal from 'decimal.js'; 3 | 4 | import { 5 | LiquidityMath, 6 | PoolInfoLayout, 7 | PositionInfoLayout, 8 | PositionUtils, 9 | SPL_ACCOUNT_LAYOUT, 10 | SqrtPriceMath, 11 | Tick, 12 | TickArrayLayout, 13 | TickUtils, 14 | } from '@raydium-io/raydium-sdk'; 15 | import { MintLayout } from '@solana/spl-token'; 16 | import { 17 | Connection, 18 | PublicKey, 19 | } from '@solana/web3.js'; 20 | 21 | import { 22 | connection, 23 | PROGRAMIDS, 24 | } from '../config'; 25 | 26 | async function checkClmmPosition() { 27 | const poolId = new PublicKey("poolId") 28 | 29 | const poolInfoAccount = await connection.getAccountInfo(poolId) 30 | if (poolInfoAccount === null) throw Error(' pool id error ') 31 | 32 | const poolInfo = PoolInfoLayout.decode(poolInfoAccount.data) 33 | 34 | console.log("current activated liquidity:", poolInfo.liquidity.toString()); 35 | 36 | const gPA = await connection.getProgramAccounts(PROGRAMIDS.CLMM, { 37 | commitment: "confirmed", 38 | filters: [ 39 | { dataSize: PositionInfoLayout.span }, 40 | { memcmp: { bytes: poolId.toBase58(), offset: PositionInfoLayout.offsetOf('poolId') } }, 41 | ] 42 | }); 43 | 44 | const poolRewardMint = poolInfo.rewardInfos.map(i => i.tokenMint) 45 | const poolRewardMintAccount = await connection.getMultipleAccountsInfo(poolRewardMint) 46 | const poolRewardMintDecimals = [] 47 | for (let i = 0; i < 3; i++) { 48 | const mint = poolRewardMint[i].toString() 49 | const account = poolRewardMintAccount[i] 50 | if (mint.toString() === PublicKey.default.toString()) { 51 | poolRewardMintDecimals.push(0) 52 | } else if (account === null) { 53 | throw Error('get reward mint info error') 54 | } else { 55 | const _mint = MintLayout.decode(account.data) 56 | poolRewardMintDecimals.push(_mint.decimals) 57 | } 58 | } 59 | 60 | console.log("num of positions:", gPA.length); 61 | let checkSumLiquidity = new BN(0); 62 | for (const account of gPA) { 63 | const position = PositionInfoLayout.decode(account.account.data); 64 | 65 | const owner = await findNftOwner(position.nftMint); 66 | 67 | const status = checkPositionStatus(poolInfo, position); 68 | if (status === "InRange") checkSumLiquidity = checkSumLiquidity.add(position.liquidity); 69 | 70 | const amounts = LiquidityMath.getAmountsFromLiquidity( 71 | poolInfo.sqrtPriceX64, 72 | SqrtPriceMath.getSqrtPriceX64FromTick(position.tickLower), 73 | SqrtPriceMath.getSqrtPriceX64FromTick(position.tickUpper), 74 | position.liquidity, 75 | false 76 | ); 77 | const amountA = new Decimal(amounts.amountA.toString()).div(10 ** poolInfo.mintDecimalsA) 78 | const amountB = new Decimal(amounts.amountB.toString()).div(10 ** poolInfo.mintDecimalsB) 79 | 80 | 81 | const tickArrayLowerAddress = TickUtils.getTickArrayAddressByTick( 82 | poolInfoAccount.owner, 83 | poolId, 84 | position.tickLower, 85 | poolInfo.tickSpacing 86 | ) 87 | const tickArrayUpperAddress = TickUtils.getTickArrayAddressByTick( 88 | poolInfoAccount.owner, 89 | poolId, 90 | position.tickUpper, 91 | poolInfo.tickSpacing 92 | ) 93 | 94 | const tickLowerState = (await getAndCacheTick(connection, tickArrayLowerAddress)).ticks[TickUtils.getTickOffsetInArray( 95 | position.tickLower, 96 | poolInfo.tickSpacing 97 | )] 98 | const tickUpperState = (await getAndCacheTick(connection, tickArrayUpperAddress)).ticks[TickUtils.getTickOffsetInArray( 99 | position.tickUpper, 100 | poolInfo.tickSpacing 101 | )] 102 | 103 | // @ts-ignore 104 | const { tokenFeeAmountA: _pendingFeeA, tokenFeeAmountB: _pendingFeeB } = PositionUtils.GetPositionFees({ 105 | tickCurrent: poolInfo.tickCurrent, 106 | feeGrowthGlobalX64A: new BN(poolInfo.feeGrowthGlobalX64A), 107 | feeGrowthGlobalX64B: new BN(poolInfo.feeGrowthGlobalX64B), 108 | }, { 109 | feeGrowthInsideLastX64A: new BN(position.feeGrowthInsideLastX64A), 110 | feeGrowthInsideLastX64B: new BN(position.feeGrowthInsideLastX64B), 111 | tokenFeesOwedA: new BN(position.tokenFeesOwedA), 112 | tokenFeesOwedB: new BN(position.tokenFeesOwedB), 113 | liquidity: new BN(position.liquidity), 114 | }, tickLowerState, tickUpperState) 115 | 116 | const pendingFeeA = new Decimal(_pendingFeeA.toString()).div(10 ** poolInfo.mintDecimalsA) 117 | const pendingFeeB = new Decimal(_pendingFeeB.toString()).div(10 ** poolInfo.mintDecimalsB) 118 | 119 | const rewardInfos = PositionUtils.GetPositionRewards({ 120 | tickCurrent: poolInfo.tickCurrent, 121 | // @ts-ignore 122 | rewardInfos: poolInfo.rewardInfos.map((i: any) => ({ rewardGrowthGlobalX64: new BN(i.rewardGrowthGlobalX64) })) 123 | }, { 124 | liquidity: new BN(position.liquidity), 125 | rewardInfos: position.rewardInfos.map((i: any) => ({ growthInsideLastX64: new BN(i.growthInsideLastX64), rewardAmountOwed: new BN(i.rewardAmountOwed) })) 126 | }, tickLowerState, tickUpperState) 127 | 128 | console.log( 129 | "\taddress:", account.pubkey.toBase58(), 130 | "\towner:", owner?.toBase58() ?? "NOTFOUND", 131 | "\tliquidity:", position.liquidity.toString(), 132 | "\tstatus:", status, 133 | "\tamountA:", amountA.toString(), 134 | "\tamountB:", amountB.toString(), 135 | "\tpendingFeeA:", pendingFeeA.toString(), 136 | "\tpendingFeeB:", pendingFeeB.toString(), 137 | "\trewardA:", new Decimal(rewardInfos[0].toString()).div(10 ** poolRewardMintDecimals[0]).toString(), 138 | "\trewardB:", new Decimal(rewardInfos[1].toString()).div(10 ** poolRewardMintDecimals[1]).toString(), 139 | "\trewardC:", new Decimal(rewardInfos[2].toString()).div(10 ** poolRewardMintDecimals[2]).toString(), 140 | ); 141 | } 142 | 143 | console.log("check sum:", checkSumLiquidity.eq(poolInfo.liquidity)); 144 | } 145 | 146 | function checkPositionStatus(poolInfo: { tickCurrent: number }, position: { tickLower: number, tickUpper: number }) { 147 | if (position.tickUpper <= poolInfo.tickCurrent) return "OutOfRange(PriceIsAboveRange)"; 148 | if (position.tickLower > poolInfo.tickCurrent) return "OutOfRange(PriceIsBelowRange)"; 149 | return "InRange"; 150 | } 151 | 152 | async function findNftOwner(mint: PublicKey): Promise { 153 | const res = await connection.getTokenLargestAccounts(mint); 154 | if (!res.value) return null; 155 | if (res.value.length === 0) return null; 156 | if (res.value[0].uiAmount !== 1) return null; 157 | 158 | const account = await connection.getAccountInfo(res.value[0].address) 159 | const info = SPL_ACCOUNT_LAYOUT.decode(account?.data!) 160 | 161 | return info.owner 162 | } 163 | 164 | const _tempCache: { [address: string]: { ticks: { [key: number]: Tick } } } = {} 165 | async function getAndCacheTick(connection: Connection, address: PublicKey) { 166 | if (_tempCache[address.toString()] !== undefined) return _tempCache[address.toString()] 167 | const account = await connection.getAccountInfo(address) 168 | 169 | if (account === null) throw Error(' get tick error ') 170 | 171 | const _d = TickArrayLayout.decode(account.data) 172 | 173 | _tempCache[address.toString()] = _d 174 | 175 | return _d 176 | } 177 | 178 | checkClmmPosition() -------------------------------------------------------------------------------- /src/clmmAddPosition.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | import { 4 | Clmm, 5 | fetchMultipleMintInfos 6 | } from '@raydium-io/raydium-sdk'; 7 | import { Keypair, PublicKey } from '@solana/web3.js'; 8 | 9 | import { BN } from 'bn.js'; 10 | import Decimal from 'decimal.js'; 11 | import { 12 | connection, 13 | makeTxVersion, 14 | wallet 15 | } from '../config'; 16 | import { formatClmmKeysById } from './formatClmmKeysById'; 17 | import { _d } from './getOutOfRangePositionOutAmount'; 18 | import { 19 | buildAndSendTx, 20 | getWalletTokenAccount, 21 | } from './util'; 22 | 23 | type WalletTokenAccounts = Awaited> 24 | type TestTxInputInfo = { 25 | targetPool: string 26 | inputTokenAmount: Decimal 27 | inputTokenMint: 'mintA' | 'mintB' 28 | walletTokenAccounts: WalletTokenAccounts 29 | wallet: Keypair 30 | slippage: number 31 | 32 | positionMint: PublicKey 33 | } 34 | 35 | async function clmmAddPosition({ targetPool, inputTokenAmount, inputTokenMint, wallet, walletTokenAccounts, slippage, positionMint}: TestTxInputInfo): Promise<{ txids: string[] }> { 36 | // -------- pre-action: fetch basic info -------- 37 | const clmmPool = await formatClmmKeysById(targetPool) 38 | 39 | // -------- step 1: Clmm info and Clmm position -------- 40 | const { [clmmPool.id]: { state: poolInfo, positionAccount } } = await Clmm.fetchMultiplePoolInfos({ 41 | connection, 42 | poolKeys: [clmmPool], 43 | chainTime: new Date().getTime() / 1000, 44 | ownerInfo: { 45 | wallet: wallet.publicKey, 46 | tokenAccounts: walletTokenAccounts, 47 | }, 48 | }) 49 | assert(positionAccount && positionAccount.length, "position is not exist/is empty, so can't continue to add position") 50 | const clmmPosition = positionAccount.find(i => i.nftMint.equals(positionMint)) // assume first one is your target 51 | if (clmmPosition === undefined) throw Error('not found position') 52 | 53 | // -------- step 2: calculate liquidity -------- 54 | const { liquidity, amountSlippageA, amountSlippageB } = Clmm.getLiquidityAmountOutFromAmountIn({ 55 | poolInfo, 56 | slippage: 0, 57 | inputA: inputTokenMint === 'mintA', 58 | tickUpper: clmmPosition.tickUpper, 59 | tickLower: clmmPosition.tickLower, 60 | amount: new BN(inputTokenAmount.mul(10 ** poolInfo[inputTokenMint].decimals).toFixed(0)), 61 | add: true, // SDK flag for math round direction 62 | amountHasFee: true, 63 | token2022Infos: await fetchMultipleMintInfos({ connection, mints: [poolInfo.mintA.mint, poolInfo.mintB.mint]}), 64 | epochInfo: await connection.getEpochInfo() 65 | }) 66 | console.log(`will add liquidity -> ${liquidity.toString()} - amount A -> ${_d(poolInfo, amountSlippageA.amount, 'A')} - amount B -> ${_d(poolInfo, amountSlippageB.amount, 'B')}`) 67 | 68 | // -------- step 3: create instructions by SDK function -------- 69 | const makeIncreaseLiquidityInstruction = await Clmm.makeIncreasePositionFromLiquidityInstructionSimple({ 70 | connection, 71 | poolInfo, 72 | ownerPosition: clmmPosition, 73 | ownerInfo: { 74 | feePayer: wallet.publicKey, 75 | wallet: wallet.publicKey, 76 | tokenAccounts: walletTokenAccounts, 77 | }, 78 | liquidity, 79 | makeTxVersion, 80 | amountMaxA: amountSlippageA.amount, 81 | amountMaxB: amountSlippageB.amount, 82 | }) 83 | 84 | return { txids: await buildAndSendTx(makeIncreaseLiquidityInstruction.innerTransactions) } 85 | } 86 | 87 | async function howToUse() { 88 | const targetPool = 'pool id' // RAY-USDC pool 89 | const inputTokenAmount = new Decimal(1) 90 | const inputTokenMint: 'mintA' | 'mintB' = 'mintA' 91 | const walletTokenAccounts = await getWalletTokenAccount(connection, wallet.publicKey) 92 | const slippage = 0.01 93 | const positionMint = new PublicKey('') 94 | 95 | clmmAddPosition({ 96 | targetPool, 97 | inputTokenAmount, 98 | inputTokenMint, 99 | walletTokenAccounts, 100 | wallet, 101 | slippage, 102 | 103 | positionMint, 104 | }).then(({ txids }) => { 105 | /** continue with txids */ 106 | console.log('txids', txids) 107 | }) 108 | } 109 | -------------------------------------------------------------------------------- /src/clmmCreatePool.ts: -------------------------------------------------------------------------------- 1 | import BN from 'bn.js'; 2 | import Decimal from 'decimal.js'; 3 | 4 | import { 5 | Clmm, 6 | ClmmConfigInfo, 7 | Token 8 | } from '@raydium-io/raydium-sdk'; 9 | import { 10 | Keypair, 11 | PublicKey, 12 | } from '@solana/web3.js'; 13 | 14 | import { 15 | connection, 16 | DEFAULT_TOKEN, 17 | makeTxVersion, 18 | PROGRAMIDS, 19 | wallet 20 | } from '../config'; 21 | import { formatClmmConfigs } from './formatClmmConfigs'; 22 | import { buildAndSendTx } from './util'; 23 | 24 | type TestTxInputInfo = { 25 | baseToken: Token 26 | quoteToken: Token 27 | clmmConfigId: string 28 | wallet: Keypair 29 | startPoolPrice: Decimal 30 | startTime: BN 31 | } 32 | 33 | async function clmmCreatePool(input: TestTxInputInfo) { 34 | // -------- pre-action: fetch basic ammConfig info -------- 35 | const _ammConfig = (await formatClmmConfigs(PROGRAMIDS.CLMM.toString()))[input.clmmConfigId] 36 | const ammConfig: ClmmConfigInfo = { ..._ammConfig, id: new PublicKey(_ammConfig.id) } 37 | 38 | // -------- step 1: make create pool instructions -------- 39 | const makeCreatePoolInstruction = await Clmm.makeCreatePoolInstructionSimple({ 40 | connection, 41 | programId: PROGRAMIDS.CLMM, 42 | owner: input.wallet.publicKey, 43 | mint1: input.baseToken, 44 | mint2: input.quoteToken, 45 | ammConfig, 46 | initialPrice: input.startPoolPrice, 47 | startTime: input.startTime, 48 | makeTxVersion, 49 | payer: wallet.publicKey, 50 | }) 51 | 52 | // -------- step 2: (optional) get mockPool info -------- 53 | const mockPoolInfo = Clmm.makeMockPoolInfo({ 54 | programId: PROGRAMIDS.CLMM, 55 | mint1: input.baseToken, 56 | mint2: input.quoteToken, 57 | ammConfig, 58 | createPoolInstructionSimpleAddress: makeCreatePoolInstruction.address, 59 | owner: input.wallet.publicKey, 60 | initialPrice: input.startPoolPrice, 61 | startTime: input.startTime 62 | }) 63 | 64 | return { txids: await buildAndSendTx(makeCreatePoolInstruction.innerTransactions), mockPoolInfo } 65 | } 66 | 67 | async function howToUse() { 68 | const baseToken = DEFAULT_TOKEN.USDC // USDC 69 | const quoteToken = DEFAULT_TOKEN.RAY // RAY 70 | const clmmConfigId = 'pool id' 71 | const startPoolPrice = new Decimal(1) 72 | const startTime = new BN(Math.floor(new Date().getTime() / 1000)) 73 | 74 | clmmCreatePool({ 75 | baseToken, 76 | quoteToken, 77 | clmmConfigId, 78 | wallet: wallet, 79 | startPoolPrice, 80 | startTime, 81 | }).then(({ txids, mockPoolInfo }) => { 82 | /** continue with txids */ 83 | console.log('txids', txids) 84 | }) 85 | } 86 | -------------------------------------------------------------------------------- /src/clmmCreatePosition.ts: -------------------------------------------------------------------------------- 1 | import Decimal from 'decimal.js'; 2 | 3 | import { 4 | Clmm, 5 | fetchMultipleMintInfos 6 | } from '@raydium-io/raydium-sdk'; 7 | import { Keypair } from '@solana/web3.js'; 8 | 9 | import BN from 'bn.js'; 10 | import { 11 | connection, 12 | makeTxVersion, 13 | wallet 14 | } from '../config'; 15 | import { formatClmmKeysById } from './formatClmmKeysById'; 16 | import { _d } from './getOutOfRangePositionOutAmount'; 17 | import { 18 | buildAndSendTx, 19 | getWalletTokenAccount, 20 | } from './util'; 21 | 22 | type WalletTokenAccounts = Awaited> 23 | type TestTxInputInfo = { 24 | targetPool: string 25 | inputTokenAmount: Decimal 26 | inputTokenMint: 'mintA' | 'mintB' 27 | walletTokenAccounts: WalletTokenAccounts 28 | wallet: Keypair 29 | startPrice: Decimal 30 | endPrice: Decimal 31 | slippage: number 32 | } 33 | 34 | async function clmmCreatePosition({ targetPool, inputTokenAmount, inputTokenMint, walletTokenAccounts, wallet, startPrice, endPrice, slippage }: TestTxInputInfo) { 35 | if (startPrice.gte(endPrice)) throw Error('price input error') 36 | // -------- pre-action: fetch basic info -------- 37 | const clmmPool = await formatClmmKeysById(targetPool) 38 | 39 | // -------- step 1: Clmm info and Clmm position -------- 40 | const { [clmmPool.id]: { state: poolInfo } } = await Clmm.fetchMultiplePoolInfos({ 41 | connection, 42 | poolKeys: [clmmPool], 43 | chainTime: new Date().getTime() / 1000, 44 | ownerInfo: { 45 | wallet: wallet.publicKey, 46 | tokenAccounts: walletTokenAccounts, 47 | }, 48 | }) 49 | 50 | // -------- step 2: get tickUpper and tickLower -------- 51 | const { tick: tickLower } = Clmm.getPriceAndTick({ 52 | poolInfo, 53 | baseIn: true, 54 | price: startPrice, 55 | }) 56 | const { tick: tickUpper } = Clmm.getPriceAndTick({ 57 | poolInfo, 58 | baseIn: true, 59 | price: endPrice, 60 | }) 61 | 62 | // -------- step 3: get liquidity -------- 63 | const { liquidity, amountSlippageA, amountSlippageB } = Clmm.getLiquidityAmountOutFromAmountIn({ 64 | poolInfo, 65 | slippage, 66 | inputA: inputTokenMint === 'mintA', 67 | tickUpper, 68 | tickLower, 69 | amount: new BN(inputTokenAmount.mul(10 ** poolInfo[inputTokenMint].decimals).toFixed(0)), 70 | add: true, 71 | 72 | amountHasFee: true, 73 | 74 | token2022Infos: await fetchMultipleMintInfos({ connection, mints: [poolInfo.mintA.mint, poolInfo.mintB.mint] }), 75 | epochInfo: await connection.getEpochInfo(), 76 | }) 77 | 78 | console.log(`will add liquidity -> ${liquidity.toString()} - amount A -> ${_d(poolInfo, amountSlippageA.amount, 'A')} - amount B -> ${_d(poolInfo, amountSlippageB.amount, 'B')}`) 79 | // -------- step 4: make open position instruction -------- 80 | const makeOpenPositionInstruction = await Clmm.makeOpenPositionFromLiquidityInstructionSimple({ 81 | connection, 82 | poolInfo, 83 | ownerInfo: { 84 | feePayer: wallet.publicKey, 85 | wallet: wallet.publicKey, 86 | tokenAccounts: walletTokenAccounts, 87 | }, 88 | tickLower, 89 | tickUpper, 90 | liquidity, 91 | makeTxVersion, 92 | amountMaxA: amountSlippageA.amount, 93 | amountMaxB: amountSlippageB.amount, 94 | }) 95 | console.log('create position mint -> ', makeOpenPositionInstruction.address.nftMint.toString()) 96 | 97 | return { txids: await buildAndSendTx(makeOpenPositionInstruction.innerTransactions) } 98 | } 99 | 100 | async function howToUse() { 101 | const targetPool = 'pool id' // RAY-USDC pool 102 | const inputTokenAmount = new Decimal(1) 103 | const inputTokenMint: 'mintA' | 'mintB' = 'mintA' 104 | const walletTokenAccounts = await getWalletTokenAccount(connection, wallet.publicKey) 105 | const startPrice = new Decimal(0.1) 106 | const endPrice = new Decimal(1) 107 | const slippage = 0.01 108 | 109 | clmmCreatePosition({ 110 | targetPool, 111 | inputTokenAmount, 112 | inputTokenMint, 113 | walletTokenAccounts, 114 | wallet, 115 | startPrice, 116 | endPrice, 117 | slippage, 118 | }).then(({ txids }) => { 119 | /** continue with txids */ 120 | console.log('txids', txids) 121 | }) 122 | } 123 | 124 | howToUse() -------------------------------------------------------------------------------- /src/clmmInitPoolReward.ts: -------------------------------------------------------------------------------- 1 | import Decimal from 'decimal.js'; 2 | 3 | import { 4 | Clmm, 5 | Token 6 | } from '@raydium-io/raydium-sdk'; 7 | import { Keypair } from '@solana/web3.js'; 8 | 9 | import { 10 | connection, 11 | DEFAULT_TOKEN, 12 | makeTxVersion, 13 | wallet 14 | } from '../config'; 15 | import { formatClmmKeysById } from './formatClmmKeysById'; 16 | import { 17 | buildAndSendTx, 18 | getWalletTokenAccount, 19 | } from './util'; 20 | 21 | type WalletTokenAccounts = Awaited> 22 | type TestTxInputInfo = { 23 | targetPool: string 24 | walletTokenAccounts: WalletTokenAccounts 25 | wallet: Keypair 26 | rewardInfos: { token: Token; openTime: number; endTime: number; perSecond: Decimal }[] 27 | } 28 | 29 | async function clmmInitPoolReward(input: TestTxInputInfo) { 30 | // -------- pre-action: fetch basic info -------- 31 | const clmmPool = await formatClmmKeysById(input.targetPool) 32 | 33 | // -------- step 1: Clmm info and Clmm position -------- 34 | const { [clmmPool.id]: { state: poolInfo } } = await Clmm.fetchMultiplePoolInfos({ 35 | connection, 36 | poolKeys: [clmmPool], 37 | chainTime: new Date().getTime() / 1000, 38 | ownerInfo: { 39 | wallet: input.wallet.publicKey, 40 | tokenAccounts: input.walletTokenAccounts, 41 | }, 42 | }) 43 | 44 | // prepare instruction 45 | const makeInitRewardsInstruction = await Clmm.makeInitRewardsInstructionSimple({ 46 | connection, 47 | poolInfo, 48 | ownerInfo: { 49 | feePayer: input.wallet.publicKey, 50 | wallet: input.wallet.publicKey, 51 | tokenAccounts: input.walletTokenAccounts, 52 | }, 53 | rewardInfos: input.rewardInfos.map((r) => ({ ...r, mint: r.token.mint, programId: r.token.programId, })), 54 | makeTxVersion, 55 | }) 56 | 57 | return { txids: await buildAndSendTx(makeInitRewardsInstruction.innerTransactions) } 58 | } 59 | 60 | async function howToUse() { 61 | const targetPool = 'pool id' // USDC-RAY pool 62 | const walletTokenAccounts = await getWalletTokenAccount(connection, wallet.publicKey) 63 | const rewardInfos = [ 64 | { 65 | token: DEFAULT_TOKEN.RAY, 66 | openTime: 4073858467, // Wed Feb 04 2099 03:21:07 GMT+0000 67 | endTime: 4076277667, // Wed Mar 04 2099 03:21:07 GMT+0000 68 | perSecond: new Decimal(0.000001), 69 | }, 70 | ] 71 | 72 | clmmInitPoolReward({ 73 | targetPool, 74 | walletTokenAccounts, 75 | wallet: wallet, 76 | rewardInfos, 77 | }).then(({ txids }) => { 78 | /** continue with txids */ 79 | console.log('txids', txids) 80 | }) 81 | } 82 | -------------------------------------------------------------------------------- /src/clmmMarketMaker/clmmTx.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Clmm, 3 | ClmmPoolInfo, 4 | fetchMultipleMintInfos, 5 | TokenAccount, 6 | TxVersion, 7 | ClmmPoolPersonalPosition, 8 | } from "@raydium-io/raydium-sdk"; 9 | import Decimal from "decimal.js"; 10 | import BN from "bn.js"; 11 | import { Connection, Keypair, Signer } from "@solana/web3.js"; 12 | import { buildAndSendTx } from "./util"; 13 | 14 | export async function createPositionTx({ 15 | connection, 16 | poolInfo, 17 | priceLower, 18 | priceUpper, 19 | owner, 20 | tokenAccounts, 21 | makeTxVersion = TxVersion.V0, 22 | amountA, 23 | }: { 24 | connection: Connection; 25 | poolInfo: ClmmPoolInfo; 26 | priceLower: Decimal; 27 | priceUpper: Decimal; 28 | owner: Keypair | Signer; 29 | tokenAccounts: TokenAccount[]; 30 | makeTxVersion?: TxVersion; 31 | amountA: BN; 32 | }) { 33 | const { tick: tickLower } = Clmm.getPriceAndTick({ 34 | poolInfo, 35 | baseIn: true, 36 | price: priceLower, // will add position start price 37 | }); 38 | const { tick: tickUpper } = Clmm.getPriceAndTick({ 39 | poolInfo, 40 | baseIn: true, 41 | price: priceUpper, // will add position end price 42 | }); 43 | 44 | const { liquidity, amountSlippageA, amountSlippageB } = 45 | Clmm.getLiquidityAmountOutFromAmountIn({ 46 | poolInfo, 47 | slippage: 0, 48 | inputA: true, 49 | tickUpper, 50 | tickLower, 51 | amount: amountA, // e.g. new BN(100000), 52 | add: true, // SDK flag for math round direction 53 | 54 | amountHasFee: true, 55 | 56 | token2022Infos: await fetchMultipleMintInfos({ 57 | connection, 58 | mints: [poolInfo.mintA.mint, poolInfo.mintB.mint], 59 | }), 60 | epochInfo: await connection.getEpochInfo(), 61 | }); 62 | 63 | const makeOpenPositionInstruction = 64 | await Clmm.makeOpenPositionFromLiquidityInstructionSimple({ 65 | connection, 66 | poolInfo, 67 | ownerInfo: { 68 | feePayer: owner.publicKey, 69 | wallet: owner.publicKey, 70 | tokenAccounts, 71 | }, 72 | tickLower, 73 | tickUpper, 74 | liquidity, 75 | makeTxVersion, 76 | amountMaxA: amountSlippageA.amount, 77 | amountMaxB: amountSlippageB.amount, 78 | }); 79 | 80 | return { 81 | txids: await buildAndSendTx({ 82 | connection, 83 | makeTxVersion, 84 | owner, 85 | innerSimpleV0Transaction: makeOpenPositionInstruction.innerTransactions, 86 | }), 87 | }; 88 | } 89 | 90 | export async function closePositionTx({ 91 | connection, 92 | poolInfo, 93 | position, 94 | owner, 95 | tokenAccounts, 96 | makeTxVersion = TxVersion.V0, 97 | }: { 98 | connection: Connection; 99 | poolInfo: ClmmPoolInfo; 100 | position: ClmmPoolPersonalPosition; 101 | owner: Keypair | Signer; 102 | tokenAccounts: TokenAccount[]; 103 | makeTxVersion?: TxVersion; 104 | }) { 105 | const makeDecreaseLiquidityInstruction = 106 | await Clmm.makeDecreaseLiquidityInstructionSimple({ 107 | connection, 108 | poolInfo, 109 | ownerPosition: position, 110 | ownerInfo: { 111 | feePayer: owner.publicKey, 112 | wallet: owner.publicKey, 113 | tokenAccounts: tokenAccounts, 114 | closePosition: true, // for close 115 | }, 116 | liquidity: position.liquidity, //for close position, use 'ammV3Position.liquidity' without dividend 117 | // slippage: 1, // if encouter slippage check error, try uncomment this line and set a number manually 118 | makeTxVersion, 119 | amountMinA: new BN(0), 120 | amountMinB: new BN(0), 121 | }); 122 | 123 | return { 124 | txids: await buildAndSendTx({ 125 | connection, 126 | makeTxVersion, 127 | owner, 128 | innerSimpleV0Transaction: 129 | makeDecreaseLiquidityInstruction.innerTransactions, 130 | }), 131 | }; 132 | } 133 | -------------------------------------------------------------------------------- /src/clmmMarketMaker/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApiClmmPoolsItem, 3 | Clmm, 4 | ReturnTypeFetchMultiplePoolInfos, 5 | TokenAccount 6 | } from '@raydium-io/raydium-sdk' 7 | import { Connection, Keypair } from '@solana/web3.js' 8 | 9 | import cron from 'node-cron' 10 | 11 | import bs58 from 'bs58' 12 | import { PROGRAMIDS } from '../../config' 13 | import { formatClmmKeys } from '../formatClmmKeys' 14 | import { getUserTokenAccounts, TokenAccountInfo } from './tokenAccount' 15 | 16 | // SOL-USDC pool id 2QdhepnKRTLjjSqPL1PtKNwqrUkoLee5Gqs8bvZhRdMv 17 | 18 | const commitment = 'confirmed' 19 | const poolId = process.argv[2] 20 | const createDeviation = !isNaN(Number(process.argv[3])) ? Number(process.argv[3]) : 10 21 | const closeDeviation = !isNaN(Number(process.argv[4])) ? Number(process.argv[4]) : 5 22 | const connection = new Connection('rpc node url', commitment) 23 | 24 | const owner = Keypair.fromSecretKey(bs58.decode(`your secret key here`)) 25 | 26 | let cachedPools: ApiClmmPoolsItem[] = [] 27 | 28 | let accounts: TokenAccountInfo[] = [] 29 | let accountsRawInfo: TokenAccount[] = [] 30 | let accountListenerId: number | undefined 31 | 32 | async function getPoolInfo(poolId: string): Promise { 33 | if (!poolId) return {} 34 | if (!cachedPools.length) { 35 | cachedPools = await formatClmmKeys(PROGRAMIDS.CLMM.toString(), true) 36 | } 37 | 38 | 39 | const pool = cachedPools.find((p) => p.id === poolId) 40 | if (pool) { 41 | return await Clmm.fetchMultiplePoolInfos({ 42 | poolKeys: [pool], 43 | connection, 44 | ownerInfo: { tokenAccounts: accountsRawInfo, wallet: owner.publicKey }, 45 | chainTime: Date.now() / 1000, 46 | batchRequest: true, 47 | }) 48 | } 49 | 50 | return {} 51 | } 52 | 53 | async function checkPosition() { 54 | if (!poolId) { 55 | console.log('please provide pool id') 56 | return 57 | } 58 | if (!accounts.length) { 59 | const fetchFuc = async () => { 60 | const accountRes = await getUserTokenAccounts({ 61 | connection, 62 | commitment, 63 | owner: owner.publicKey, 64 | }) 65 | accounts = [...accountRes.accounts] 66 | accountsRawInfo = [...accountRes.accountsRawInfo] 67 | } 68 | await fetchFuc() 69 | 70 | if (accountListenerId) { 71 | connection.removeAccountChangeListener(accountListenerId) 72 | accountListenerId = undefined 73 | } 74 | accountListenerId = connection.onAccountChange(owner.publicKey, fetchFuc) 75 | } 76 | 77 | const res = await getPoolInfo(poolId) 78 | const parsedPool = res[poolId] 79 | if (parsedPool) { 80 | console.log(`\nConcentrated pool: ${poolId}`) 81 | console.log(`\nclose deviation setting: ${closeDeviation}%, create deviation setting: ${createDeviation}%`) 82 | const currentPrice = parsedPool.state.currentPrice 83 | parsedPool.positionAccount?.forEach(async (position, idx) => { 84 | const { priceLower, priceUpper } = position 85 | console.log( 86 | `\n===== position ${idx + 1} =====\n`, 87 | `current price: ${currentPrice}\n`, 88 | `priceLower: ${priceLower.toString()}\n`, 89 | `priceUpper: ${priceUpper.toString()}` 90 | ) 91 | const currentPositionMid = priceLower.add(priceUpper).div(2) 92 | const [closeLow, closeUp] = [ 93 | currentPrice.mul((100 - closeDeviation) / 100), 94 | currentPrice.mul((100 + closeDeviation) / 100), 95 | ] 96 | 97 | if (currentPositionMid < closeLow || currentPositionMid > closeUp) { 98 | console.log('\n⛔ close position triggered!') 99 | console.log(`closeLower:${closeLow}\ncurrentPosition:${currentPositionMid}\ncloseUpper: ${closeUp}`) 100 | /* close position here */ 101 | // await closePositionTx({ 102 | // connection, 103 | // poolInfo: parsedPool.state, 104 | // position, 105 | // owner, 106 | // tokenAccounts: accountsRawInfo, 107 | // }); 108 | 109 | const [recreateLower, recreateUpper] = [ 110 | currentPrice.mul((100 - createDeviation) / 100), 111 | currentPrice.mul((100 + createDeviation) / 100), 112 | ] 113 | console.log('\n ✅ create new position') 114 | console.log(`priceLower:${recreateLower}\npriceUpper: ${recreateUpper}`) 115 | /* create position here */ 116 | // await createPositionTx({ 117 | // connection, 118 | // poolInfo: parsedPool.state, 119 | // priceLower, 120 | // priceUpper, 121 | // owner, 122 | // tokenAccounts: accountsRawInfo, 123 | // amountA: new BN(10000), 124 | // }); 125 | return 126 | } 127 | console.log('position in range, no action needed') 128 | }) 129 | } 130 | } 131 | 132 | const job = cron.schedule('*/1 * * * *', checkPosition, { 133 | scheduled: false, 134 | }) 135 | 136 | if (poolId) { 137 | checkPosition() 138 | job.start() 139 | } else { 140 | console.log('please provide pool id') 141 | } 142 | -------------------------------------------------------------------------------- /src/clmmMarketMaker/tokenAccount.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey, Commitment, Connection } from "@solana/web3.js"; 2 | import { TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID } from "@solana/spl-token"; 3 | import BN from "bn.js"; 4 | import { SPL_ACCOUNT_LAYOUT, TokenAccount, Spl } from "@raydium-io/raydium-sdk"; 5 | 6 | export interface TokenAccountInfo { 7 | programId?: PublicKey; 8 | publicKey?: PublicKey; 9 | mint?: PublicKey; 10 | isAssociated?: boolean; 11 | amount: BN; 12 | isNative: boolean; 13 | } 14 | 15 | export async function getUserTokenAccounts(props: { 16 | connection: Connection; 17 | owner: PublicKey; 18 | commitment: Commitment; 19 | }) { 20 | const { connection, owner, commitment } = props; 21 | 22 | console.log("fetching token accounts...\n"); 23 | 24 | const accounts: TokenAccountInfo[] = []; 25 | const accountsRawInfo: TokenAccount[] = []; 26 | 27 | const solReq = connection.getAccountInfo(owner, commitment); 28 | const tokenReq = connection.getTokenAccountsByOwner( 29 | owner, 30 | { programId: TOKEN_PROGRAM_ID }, 31 | commitment 32 | ); 33 | const token2022Req = connection.getTokenAccountsByOwner( 34 | owner, 35 | { programId: TOKEN_2022_PROGRAM_ID }, 36 | commitment 37 | ); 38 | const [solRes, tokenRes, token2022Res] = await Promise.all([ 39 | solReq, 40 | tokenReq, 41 | token2022Req, 42 | ]); 43 | 44 | console.log("fetching token accounts done.\n"); 45 | 46 | for (const { pubkey, account } of [ 47 | ...tokenRes.value, 48 | ...token2022Res.value, 49 | ]) { 50 | const rawResult = SPL_ACCOUNT_LAYOUT.decode(account.data); 51 | const { mint, amount } = rawResult; 52 | const associatedTokenAddress = Spl.getAssociatedTokenAccount({ 53 | mint, 54 | owner, 55 | programId: account.owner, 56 | }); 57 | accounts.push({ 58 | publicKey: pubkey, 59 | mint, 60 | isAssociated: associatedTokenAddress.equals(pubkey), 61 | amount, 62 | isNative: false, 63 | }); 64 | 65 | accountsRawInfo.push({ 66 | pubkey, 67 | accountInfo: rawResult, 68 | programId: account.owner, 69 | } as TokenAccount); 70 | } 71 | 72 | accounts.push({ 73 | amount: new BN(solRes ? String(solRes.lamports) : 0), 74 | isNative: true, 75 | }); 76 | 77 | return { accounts, accountsRawInfo }; 78 | } 79 | -------------------------------------------------------------------------------- /src/clmmMarketMaker/util.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ComputeBudgetConfig, 3 | InnerSimpleV0Transaction, 4 | TxVersion, 5 | buildSimpleTransaction, 6 | } from "@raydium-io/raydium-sdk"; 7 | import { 8 | Connection, 9 | Keypair, 10 | SendOptions, 11 | Signer, 12 | Transaction, 13 | VersionedTransaction 14 | } from "@solana/web3.js"; 15 | import axios from "axios"; 16 | import { addLookupTableInfo } from "../../config"; 17 | 18 | interface SolanaFeeInfo { 19 | min: number; 20 | max: number; 21 | avg: number; 22 | priorityTx: number; 23 | nonVotes: number; 24 | priorityRatio: number; 25 | avgCuPerBlock: number; 26 | blockspaceUsageRatio: number; 27 | } 28 | type SolanaFeeInfoJson = { 29 | "1": SolanaFeeInfo; 30 | "5": SolanaFeeInfo; 31 | "15": SolanaFeeInfo; 32 | }; 33 | 34 | export async function getComputeBudgetConfig(): Promise< 35 | ComputeBudgetConfig | undefined 36 | > { 37 | const { data } = await axios.get( 38 | `https://solanacompass.com/api/fees?cacheFreshTime=${5 * 60 * 1000}` 39 | ); 40 | const { avg } = data?.[15] ?? {}; 41 | if (!avg) return undefined; // fetch error 42 | return { 43 | units: 400000, 44 | microLamports: Math.min(Math.ceil((avg * 1000000) / 400000), 25000), 45 | } as ComputeBudgetConfig; 46 | } 47 | 48 | export async function sendTx( 49 | connection: Connection, 50 | payer: Keypair | Signer, 51 | txs: (VersionedTransaction | Transaction)[], 52 | options?: SendOptions 53 | ): Promise { 54 | const txids: string[] = []; 55 | for (const iTx of txs) { 56 | if (iTx instanceof VersionedTransaction) { 57 | iTx.sign([payer]); 58 | txids.push(await connection.sendTransaction(iTx, options)); 59 | } else { 60 | txids.push(await connection.sendTransaction(iTx, [payer], options)); 61 | } 62 | } 63 | return txids; 64 | } 65 | 66 | export async function buildAndSendTx({ 67 | connection, 68 | makeTxVersion, 69 | owner, 70 | innerSimpleV0Transaction, 71 | }: { 72 | connection: Connection; 73 | makeTxVersion: TxVersion; 74 | owner: Keypair | Signer; 75 | innerSimpleV0Transaction: InnerSimpleV0Transaction[]; 76 | }) { 77 | const willSendTx = await buildSimpleTransaction({ 78 | connection, 79 | makeTxVersion, 80 | payer: owner.publicKey, 81 | innerTransactions: innerSimpleV0Transaction, 82 | addLookupTableInfo: addLookupTableInfo, 83 | }); 84 | 85 | return await sendTx(connection, owner, willSendTx); 86 | } 87 | -------------------------------------------------------------------------------- /src/clmmOwnerPositionInfo.ts: -------------------------------------------------------------------------------- 1 | import { Clmm } from '@raydium-io/raydium-sdk' 2 | import { Keypair } from '@solana/web3.js' 3 | 4 | import { connection, PROGRAMIDS, wallet } from '../config' 5 | import { formatClmmKeys } from './formatClmmKeys' 6 | import { getWalletTokenAccount } from './util' 7 | 8 | type WalletTokenAccounts = Awaited> 9 | type TestTxInputInfo = { 10 | walletTokenAccounts: WalletTokenAccounts 11 | wallet: Keypair 12 | } 13 | async function clmmOwnerPositionInfo(input: TestTxInputInfo) { 14 | const poolKeys = await formatClmmKeys(PROGRAMIDS.CLMM.toString()) 15 | 16 | const infos = await Clmm.fetchMultiplePoolInfos({ 17 | connection, 18 | poolKeys, 19 | chainTime: new Date().getTime() / 1000, 20 | ownerInfo: { 21 | wallet: input.wallet.publicKey, 22 | tokenAccounts: input.walletTokenAccounts, 23 | }, 24 | }) 25 | 26 | return { infos } 27 | } 28 | 29 | async function howToUse() { 30 | const walletTokenAccounts = await getWalletTokenAccount(connection, wallet.publicKey) 31 | 32 | clmmOwnerPositionInfo({ 33 | walletTokenAccounts, 34 | wallet: wallet, 35 | }).then(({ infos }) => { 36 | /** continue with infos */ 37 | console.log('infos', infos) 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /src/clmmRemovePosition.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import BN from 'bn.js'; 3 | 4 | import { 5 | Clmm, 6 | ZERO 7 | } from '@raydium-io/raydium-sdk'; 8 | import { Keypair } from '@solana/web3.js'; 9 | 10 | import { 11 | connection, 12 | makeTxVersion, 13 | wallet 14 | } from '../config'; 15 | import { formatClmmKeysById } from './formatClmmKeysById'; 16 | import { 17 | buildAndSendTx, 18 | getWalletTokenAccount, 19 | } from './util'; 20 | 21 | type WalletTokenAccounts = Awaited> 22 | type TestTxInputInfo = { 23 | targetPool: string 24 | walletTokenAccounts: WalletTokenAccounts 25 | wallet: Keypair 26 | } 27 | 28 | async function clmmRemovePosition(input: TestTxInputInfo) { 29 | // -------- pre-action: fetch basic info -------- 30 | const clmmPool = await formatClmmKeysById(input.targetPool) 31 | 32 | // -------- step 1: ammV3 info and ammV3 position -------- 33 | const { [clmmPool.id]: sdkParsedAmmV3Info } = await Clmm.fetchMultiplePoolInfos({ 34 | connection, 35 | poolKeys: [clmmPool], 36 | chainTime: new Date().getTime() / 1000, 37 | ownerInfo: { 38 | wallet: wallet.publicKey, 39 | tokenAccounts: input.walletTokenAccounts, 40 | }, 41 | }) 42 | const { state: clmmPoolInfo, positionAccount } = sdkParsedAmmV3Info 43 | assert(positionAccount && positionAccount.length, "position is not exist/is empty, so can't continue to add position") 44 | const ammV3Position = positionAccount[0] // assume first one is your target 45 | 46 | // -------- step 2: make ammV3 remove position instructions -------- 47 | const makeDecreaseLiquidityInstruction = await Clmm.makeDecreaseLiquidityInstructionSimple({ 48 | connection, 49 | poolInfo: clmmPoolInfo, 50 | ownerPosition: ammV3Position, 51 | ownerInfo: { 52 | feePayer: wallet.publicKey, 53 | wallet: wallet.publicKey, 54 | tokenAccounts: input.walletTokenAccounts, 55 | // closePosition: true, // for close 56 | }, 57 | liquidity: ammV3Position.liquidity.div(new BN(2)), //for close position, use 'ammV3Position.liquidity' without dividend 58 | // slippage: 1, // if encouter slippage check error, try uncomment this line and set a number manually 59 | makeTxVersion, 60 | amountMinA: ZERO, 61 | amountMinB: ZERO 62 | }) 63 | 64 | return { txids: await buildAndSendTx(makeDecreaseLiquidityInstruction.innerTransactions) } 65 | } 66 | 67 | async function howToUse() { 68 | const targetPool = 'pool id' // USDC-RAY pool 69 | const walletTokenAccounts = await getWalletTokenAccount(connection, wallet.publicKey) 70 | 71 | clmmRemovePosition({ 72 | targetPool, 73 | walletTokenAccounts, 74 | wallet: wallet, 75 | }).then(({ txids }) => { 76 | /** continue with txids */ 77 | console.log('txids', txids) 78 | }) 79 | } 80 | -------------------------------------------------------------------------------- /src/clmmSetPoolReward.ts: -------------------------------------------------------------------------------- 1 | import Decimal from 'decimal.js'; 2 | 3 | import { 4 | Clmm, 5 | Token 6 | } from '@raydium-io/raydium-sdk'; 7 | import { Keypair } from '@solana/web3.js'; 8 | 9 | import { 10 | connection, 11 | DEFAULT_TOKEN, 12 | makeTxVersion, 13 | wallet 14 | } from '../config'; 15 | import { formatClmmKeysById } from './formatClmmKeysById'; 16 | import { 17 | buildAndSendTx, 18 | getWalletTokenAccount, 19 | } from './util'; 20 | 21 | type WalletTokenAccounts = Awaited> 22 | type TestTxInputInfo = { 23 | targetPool: string 24 | walletTokenAccounts: WalletTokenAccounts 25 | wallet: Keypair 26 | rewardInfos: { token: Token; openTime: number; endTime: number; perSecond: Decimal }[] 27 | } 28 | 29 | async function clmmSetPoolReward(input: TestTxInputInfo) { 30 | // -------- pre-action: fetch basic info -------- 31 | const clmmPool = await formatClmmKeysById(input.targetPool) 32 | 33 | // -------- step 1: clmm info -------- 34 | const { [clmmPool.id]: { state: clmmPoolInfo } } = await Clmm.fetchMultiplePoolInfos({ 35 | connection, 36 | poolKeys: [clmmPool], 37 | chainTime: new Date().getTime() / 1000, 38 | ownerInfo: { 39 | wallet: input.wallet.publicKey, 40 | tokenAccounts: input.walletTokenAccounts, 41 | }, 42 | }) 43 | 44 | // -------- step 2: create set reward instructions -------- 45 | const makeSetRewardsInstruction = await Clmm.makeSetRewardsInstructionSimple({ 46 | connection, 47 | poolInfo: clmmPoolInfo, 48 | ownerInfo: { 49 | feePayer: input.wallet.publicKey, 50 | wallet: input.wallet.publicKey, 51 | tokenAccounts: input.walletTokenAccounts, 52 | }, 53 | rewardInfos: input.rewardInfos.map((r) => ({ ...r, mint: r.token.mint, programId: r.token.programId, })), 54 | chainTime: new Date().getTime() / 1000, 55 | makeTxVersion, 56 | }) 57 | 58 | return { txids: await buildAndSendTx(makeSetRewardsInstruction.innerTransactions) } 59 | } 60 | 61 | async function howToUse() { 62 | const targetPool = 'pool id' // USDC-RAY pool 63 | const walletTokenAccounts = await getWalletTokenAccount(connection, wallet.publicKey) 64 | const rewardInfos = [ 65 | { 66 | token: DEFAULT_TOKEN.RAY, 67 | openTime: 4073858467, // Wed Feb 04 2099 03:21:07 GMT+0000 68 | endTime: 4076277667, // Wed Mar 04 2099 03:21:07 GMT+0000 69 | perSecond: new Decimal(0.000001), 70 | }, 71 | ] 72 | 73 | clmmSetPoolReward({ 74 | targetPool, 75 | walletTokenAccounts, 76 | wallet: wallet, 77 | rewardInfos, 78 | }).then(({ txids }) => { 79 | /** continue with txids */ 80 | console.log('txids', txids) 81 | }) 82 | } 83 | -------------------------------------------------------------------------------- /src/createFarm.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | import { 4 | Farm, 5 | MAINNET_PROGRAM_ID, 6 | Token 7 | } from '@raydium-io/raydium-sdk'; 8 | import { 9 | Keypair, 10 | PublicKey, 11 | } from '@solana/web3.js'; 12 | 13 | import { 14 | connection, 15 | DEFAULT_TOKEN, 16 | makeTxVersion, 17 | wallet 18 | } from '../config'; 19 | import { formatAmmKeysById } from './formatAmmKeysById'; 20 | import { 21 | buildAndSendTx, 22 | getWalletTokenAccount, 23 | } from './util'; 24 | 25 | type WalletTokenAccounts = Awaited> 26 | type TestTxInputInfo = { 27 | targetPool: string 28 | walletTokenAccounts: WalletTokenAccounts 29 | wallet: Keypair 30 | rewardInfos: { 31 | token: Token 32 | openTime: number 33 | endTime: number 34 | perSecond: number 35 | type?: 'Standard SPL' | 'Option tokens' 36 | }[] 37 | lockInfo: { 38 | lockMint: PublicKey 39 | lockVault: PublicKey 40 | } 41 | } 42 | 43 | async function createFarm(input: TestTxInputInfo) { 44 | // -------- pre-action: fetch basic info -------- 45 | const targetPoolInfo = await formatAmmKeysById(input.targetPool) 46 | assert(targetPoolInfo, 'cannot find the target pool') 47 | 48 | // -------- step 1: create instructions by SDK function -------- 49 | const makeCreateFarmInstruction = await Farm.makeCreateFarmInstructionSimple({ 50 | connection, 51 | userKeys: { 52 | tokenAccounts: input.walletTokenAccounts, 53 | owner: input.wallet.publicKey, 54 | }, 55 | poolInfo: { 56 | version: 6, 57 | programId: MAINNET_PROGRAM_ID.FarmV6, 58 | lpMint: new PublicKey(targetPoolInfo.lpMint), 59 | rewardInfos: input.rewardInfos.map((r) => ({ 60 | rewardMint: r.token.mint, 61 | rewardOpenTime: r.openTime, 62 | rewardEndTime: r.endTime, 63 | rewardPerSecond: r.perSecond, 64 | rewardType: r.type ?? 'Standard SPL', 65 | })), 66 | lockInfo: input.lockInfo, 67 | }, 68 | makeTxVersion, 69 | }) 70 | 71 | return { txids: await buildAndSendTx(makeCreateFarmInstruction.innerTransactions) } 72 | } 73 | 74 | async function howToUse() { 75 | const targetPool = 'pool id' // USDC-RAY pool 76 | const walletTokenAccounts = await getWalletTokenAccount(connection, wallet.publicKey) 77 | const rewardInfos = [ 78 | { 79 | token: DEFAULT_TOKEN.RAY, 80 | perSecond: 1, 81 | openTime: 4073858467, // Wed Feb 04 2099 03:21:07 GMT+0000 82 | endTime: 4076277667, // Wed Mar 04 2099 03:21:07 GMT+0000 83 | }, 84 | ] 85 | const lockInfo = { 86 | lockMint: new PublicKey('4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R'), 87 | lockVault: new PublicKey('FrspKwj8i3pNmKwXreTveC4fu7KL5ZbGeXdZBe2XViu1'), 88 | } 89 | 90 | createFarm({ 91 | targetPool, 92 | walletTokenAccounts, 93 | wallet: wallet, 94 | rewardInfos, 95 | lockInfo, 96 | }).then(({ txids }) => { 97 | /** continue with txids */ 98 | console.log('txids', txids) 99 | }) 100 | } 101 | -------------------------------------------------------------------------------- /src/formatAmmKeys.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApiPoolInfo, 3 | ApiPoolInfoV4, 4 | LIQUIDITY_STATE_LAYOUT_V4, 5 | Liquidity, 6 | MARKET_STATE_LAYOUT_V3, 7 | Market 8 | } from '@raydium-io/raydium-sdk'; 9 | import { 10 | AddressLookupTableAccount, 11 | PublicKey 12 | } from '@solana/web3.js'; 13 | 14 | import { connection } from '../config'; 15 | 16 | export async function formatAmmKeys(programId: string, findLookupTableAddress: boolean = false): Promise { 17 | const filterDefKey = PublicKey.default.toString() 18 | const allAmmAccount = await connection.getProgramAccounts(new PublicKey(programId), { filters: [{ dataSize: LIQUIDITY_STATE_LAYOUT_V4.span }] }) 19 | const amAccountmData = allAmmAccount.map(i => ({ id: i.pubkey, programId: i.account.owner, ...LIQUIDITY_STATE_LAYOUT_V4.decode(i.account.data) })).filter(i => i.marketProgramId.toString() !== filterDefKey) 20 | 21 | const allMarketProgram = new Set(amAccountmData.map(i => i.marketProgramId.toString())) 22 | 23 | const marketInfo: { 24 | [marketId: string]: { 25 | marketProgramId: string, 26 | marketAuthority: string, 27 | marketBaseVault: string, 28 | marketQuoteVault: string, 29 | marketBids: string, 30 | marketAsks: string, 31 | marketEventQueue: string, 32 | } 33 | } = {} 34 | for (const itemMarketProgram of allMarketProgram) { 35 | const allMarketInfo = await connection.getProgramAccounts(new PublicKey(itemMarketProgram), { filters: [{ dataSize: MARKET_STATE_LAYOUT_V3.span }] }) 36 | for (const itemAccount of allMarketInfo) { 37 | const itemMarketInfo = MARKET_STATE_LAYOUT_V3.decode(itemAccount.account.data) 38 | marketInfo[itemAccount.pubkey.toString()] = { 39 | marketProgramId: itemAccount.account.owner.toString(), 40 | marketAuthority: Market.getAssociatedAuthority({ programId: itemAccount.account.owner, marketId: itemAccount.pubkey }).publicKey.toString(), 41 | marketBaseVault: itemMarketInfo.baseVault.toString(), 42 | marketQuoteVault: itemMarketInfo.quoteVault.toString(), 43 | marketBids: itemMarketInfo.bids.toString(), 44 | marketAsks: itemMarketInfo.asks.toString(), 45 | marketEventQueue: itemMarketInfo.eventQueue.toString() 46 | } 47 | } 48 | } 49 | 50 | const ammFormatData = (amAccountmData.map(itemAmm => { 51 | const itemMarket = marketInfo[itemAmm.marketId.toString()] 52 | if (itemMarket === undefined) return undefined 53 | 54 | const format: ApiPoolInfoV4 = { 55 | id: itemAmm.id.toString(), 56 | baseMint: itemAmm.baseMint.toString(), 57 | quoteMint: itemAmm.quoteMint.toString(), 58 | lpMint: itemAmm.lpMint.toString(), 59 | baseDecimals: itemAmm.baseDecimal.toNumber(), 60 | quoteDecimals: itemAmm.quoteDecimal.toNumber(), 61 | lpDecimals: itemAmm.baseDecimal.toNumber(), 62 | version: 4, 63 | programId: itemAmm.programId.toString(), 64 | authority: Liquidity.getAssociatedAuthority({ programId: itemAmm.programId }).publicKey.toString(), 65 | openOrders: itemAmm.openOrders.toString(), 66 | targetOrders: itemAmm.targetOrders.toString(), 67 | baseVault: itemAmm.baseVault.toString(), 68 | quoteVault: itemAmm.quoteVault.toString(), 69 | withdrawQueue: itemAmm.withdrawQueue.toString(), 70 | lpVault: itemAmm.lpVault.toString(), 71 | marketVersion: 3, 72 | marketId: itemAmm.marketId.toString(), 73 | ...itemMarket, 74 | lookupTableAccount: filterDefKey 75 | } 76 | return format 77 | }).filter(i => i !== undefined) as ApiPoolInfoV4[]).reduce((a, b) => { a[b.id] = b; return a }, {} as { [id: string]: ApiPoolInfoV4 }) 78 | 79 | if (findLookupTableAddress) { 80 | const ltas = await connection.getProgramAccounts(new PublicKey('AddressLookupTab1e1111111111111111111111111'), { 81 | filters: [{ memcmp: { offset: 22, bytes: 'RayZuc5vEK174xfgNFdD9YADqbbwbFjVjY4NM8itSF9' } }] 82 | }) 83 | for (const itemLTA of ltas) { 84 | const keyStr = itemLTA.pubkey.toString() 85 | const ltaForamt = new AddressLookupTableAccount({ key: itemLTA.pubkey, state: AddressLookupTableAccount.deserialize(itemLTA.account.data) }) 86 | for (const itemKey of ltaForamt.state.addresses) { 87 | const itemKeyStr = itemKey.toString() 88 | if (ammFormatData[itemKeyStr] === undefined) continue 89 | ammFormatData[itemKeyStr].lookupTableAccount = keyStr 90 | } 91 | } 92 | } 93 | 94 | return Object.values(ammFormatData) 95 | } 96 | 97 | 98 | export async function formatAmmKeysToApi(programId: string, findLookupTableAddress: boolean = false): Promise { 99 | return { 100 | official: [], 101 | unOfficial: await formatAmmKeys(programId, findLookupTableAddress) 102 | } 103 | } -------------------------------------------------------------------------------- /src/formatAmmKeysById.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApiPoolInfoV4, 3 | LIQUIDITY_STATE_LAYOUT_V4, 4 | Liquidity, 5 | MARKET_STATE_LAYOUT_V3, 6 | Market, 7 | SPL_MINT_LAYOUT 8 | } from '@raydium-io/raydium-sdk'; 9 | import { 10 | PublicKey 11 | } from '@solana/web3.js'; 12 | 13 | import { connection } from '../config'; 14 | 15 | export async function formatAmmKeysById(id: string): Promise { 16 | const account = await connection.getAccountInfo(new PublicKey(id)) 17 | if (account === null) throw Error(' get id info error ') 18 | const info = LIQUIDITY_STATE_LAYOUT_V4.decode(account.data) 19 | 20 | const marketId = info.marketId 21 | const marketAccount = await connection.getAccountInfo(marketId) 22 | if (marketAccount === null) throw Error(' get market info error') 23 | const marketInfo = MARKET_STATE_LAYOUT_V3.decode(marketAccount.data) 24 | 25 | const lpMint = info.lpMint 26 | const lpMintAccount = await connection.getAccountInfo(lpMint) 27 | if (lpMintAccount === null) throw Error(' get lp mint info error') 28 | const lpMintInfo = SPL_MINT_LAYOUT.decode(lpMintAccount.data) 29 | 30 | return { 31 | id, 32 | baseMint: info.baseMint.toString(), 33 | quoteMint: info.quoteMint.toString(), 34 | lpMint: info.lpMint.toString(), 35 | baseDecimals: info.baseDecimal.toNumber(), 36 | quoteDecimals: info.quoteDecimal.toNumber(), 37 | lpDecimals: lpMintInfo.decimals, 38 | version: 4, 39 | programId: account.owner.toString(), 40 | authority: Liquidity.getAssociatedAuthority({ programId: account.owner }).publicKey.toString(), 41 | openOrders: info.openOrders.toString(), 42 | targetOrders: info.targetOrders.toString(), 43 | baseVault: info.baseVault.toString(), 44 | quoteVault: info.quoteVault.toString(), 45 | withdrawQueue: info.withdrawQueue.toString(), 46 | lpVault: info.lpVault.toString(), 47 | marketVersion: 3, 48 | marketProgramId: info.marketProgramId.toString(), 49 | marketId: info.marketId.toString(), 50 | marketAuthority: Market.getAssociatedAuthority({ programId: info.marketProgramId, marketId: info.marketId }).publicKey.toString(), 51 | marketBaseVault: marketInfo.baseVault.toString(), 52 | marketQuoteVault: marketInfo.quoteVault.toString(), 53 | marketBids: marketInfo.bids.toString(), 54 | marketAsks: marketInfo.asks.toString(), 55 | marketEventQueue: marketInfo.eventQueue.toString(), 56 | lookupTableAccount: PublicKey.default.toString() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/formatClmmConfigs.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AmmConfigLayout, 3 | ApiClmmConfigItem 4 | } from '@raydium-io/raydium-sdk'; 5 | import { 6 | AccountInfo, 7 | PublicKey 8 | } from '@solana/web3.js'; 9 | 10 | import { connection } from '../config'; 11 | 12 | export function formatConfigInfo(id: PublicKey, account: AccountInfo): ApiClmmConfigItem { 13 | const info = AmmConfigLayout.decode(account.data) 14 | 15 | return { 16 | id: id.toBase58(), 17 | index: info.index, 18 | protocolFeeRate: info.protocolFeeRate, 19 | tradeFeeRate: info.tradeFeeRate, 20 | tickSpacing: info.tickSpacing, 21 | fundFeeRate: info.fundFeeRate, 22 | fundOwner: info.fundOwner.toString(), 23 | description: '', 24 | } 25 | } 26 | 27 | export async function formatClmmConfigs(programId: string) { 28 | const configAccountInfo = await connection.getProgramAccounts(new PublicKey(programId), { filters: [{ dataSize: AmmConfigLayout.span }] }) 29 | return configAccountInfo.map(i => formatConfigInfo(i.pubkey, i.account)).reduce((a, b) => { a[b.id] = b; return a }, {} as { [id: string]: ApiClmmConfigItem }) 30 | } 31 | -------------------------------------------------------------------------------- /src/formatClmmKeys.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApiClmmPoolsItem, 3 | ApiClmmPoolsItemStatistics, 4 | PoolInfoLayout, 5 | getMultipleAccountsInfoWithCustomFlags 6 | } from '@raydium-io/raydium-sdk'; 7 | import { 8 | AddressLookupTableAccount, 9 | PublicKey 10 | } from '@solana/web3.js'; 11 | 12 | import { connection } from '../config'; 13 | import { formatClmmConfigs } from './formatClmmConfigs'; 14 | 15 | export function getApiClmmPoolsItemStatisticsDefault(): ApiClmmPoolsItemStatistics { 16 | return { 17 | volume: 0, 18 | volumeFee: 0, 19 | feeA: 0, 20 | feeB: 0, 21 | feeApr: 0, 22 | rewardApr: { A: 0, B: 0, C: 0 }, 23 | apr: 0, 24 | priceMin: 0, 25 | priceMax: 0, 26 | } 27 | } 28 | 29 | export async function formatClmmKeys(programId: string, findLookupTableAddress: boolean = false): Promise { 30 | const filterDefKey = PublicKey.default.toString() 31 | 32 | const poolAccountInfo = await connection.getProgramAccounts(new PublicKey(programId), { filters: [{ dataSize: PoolInfoLayout.span }] }) 33 | 34 | const configIdToData = await formatClmmConfigs(programId) 35 | 36 | const poolAccountFormat = poolAccountInfo.map(i => ({ id: i.pubkey, ...PoolInfoLayout.decode(i.account.data) })) 37 | 38 | const allMint = [...new Set(poolAccountFormat.map(i => [i.mintA.toString(), i.mintB.toString(), ...i.rewardInfos.map(ii => ii.tokenMint.toString())]).flat())].filter(i => i !== filterDefKey).map(i => ({ pubkey: new PublicKey(i) })) 39 | const mintAccount = await getMultipleAccountsInfoWithCustomFlags(connection, allMint) 40 | const mintInfoDict = mintAccount.filter(i => i.accountInfo !== null).reduce((a, b) => { a[b.pubkey.toString()] = { programId: b.accountInfo!.owner.toString() }; return a }, {} as { [mint: string]: { programId: string } }) 41 | 42 | 43 | const poolInfoDict = poolAccountFormat.map(i => { 44 | const mintProgramIdA = mintInfoDict[i.mintA.toString()].programId 45 | const mintProgramIdB = mintInfoDict[i.mintB.toString()].programId 46 | const rewardInfos = i.rewardInfos 47 | .filter((i) => !i.tokenMint.equals(PublicKey.default)) 48 | .map((i) => ({ 49 | mint: i.tokenMint.toString(), 50 | programId: mintInfoDict[i.tokenMint.toString()].programId, 51 | })) 52 | 53 | return { 54 | id: i.id.toString(), 55 | mintProgramIdA, 56 | mintProgramIdB, 57 | mintA: i.mintA.toString(), 58 | mintB: i.mintB.toString(), 59 | vaultA: i.vaultA.toString(), 60 | vaultB: i.vaultB.toString(), 61 | mintDecimalsA: i.mintDecimalsA, 62 | mintDecimalsB: i.mintDecimalsB, 63 | ammConfig: configIdToData[i.ammConfig.toString()], 64 | rewardInfos, 65 | tvl: 0, 66 | day: getApiClmmPoolsItemStatisticsDefault(), 67 | week: getApiClmmPoolsItemStatisticsDefault(), 68 | month: getApiClmmPoolsItemStatisticsDefault(), 69 | lookupTableAccount: PublicKey.default.toBase58(), 70 | } 71 | }).reduce((a, b) => { a[b.id] = b; return a }, {} as { [id: string]: ApiClmmPoolsItem }) 72 | 73 | if (findLookupTableAddress) { 74 | const ltas = await connection.getProgramAccounts(new PublicKey('AddressLookupTab1e1111111111111111111111111'), { 75 | filters: [{ memcmp: { offset: 22, bytes: 'RayZuc5vEK174xfgNFdD9YADqbbwbFjVjY4NM8itSF9' } }] 76 | }) 77 | for (const itemLTA of ltas) { 78 | const keyStr = itemLTA.pubkey.toString() 79 | const ltaForamt = new AddressLookupTableAccount({ key: itemLTA.pubkey, state: AddressLookupTableAccount.deserialize(itemLTA.account.data) }) 80 | for (const itemKey of ltaForamt.state.addresses) { 81 | const itemKeyStr = itemKey.toString() 82 | if (poolInfoDict[itemKeyStr] === undefined) continue 83 | poolInfoDict[itemKeyStr].lookupTableAccount = keyStr 84 | } 85 | } 86 | } 87 | 88 | return Object.values(poolInfoDict) 89 | } 90 | -------------------------------------------------------------------------------- /src/formatClmmKeysById.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApiClmmConfigItem, 3 | ApiClmmPoolsItem, 4 | PoolInfoLayout 5 | } from '@raydium-io/raydium-sdk'; 6 | import { 7 | PublicKey 8 | } from '@solana/web3.js'; 9 | 10 | import { connection } from '../config'; 11 | import { formatConfigInfo } from './formatClmmConfigs'; 12 | import { getApiClmmPoolsItemStatisticsDefault } from './formatClmmKeys'; 13 | 14 | async function getMintProgram(mint: PublicKey) { 15 | const account = await connection.getAccountInfo(mint) 16 | if (account === null) throw Error(' get id info error ') 17 | return account.owner 18 | } 19 | async function getConfigInfo(configId: PublicKey): Promise { 20 | const account = await connection.getAccountInfo(configId) 21 | if (account === null) throw Error(' get id info error ') 22 | return formatConfigInfo(configId, account) 23 | } 24 | 25 | export async function formatClmmKeysById(id: string): Promise { 26 | const account = await connection.getAccountInfo(new PublicKey(id)) 27 | if (account === null) throw Error(' get id info error ') 28 | const info = PoolInfoLayout.decode(account.data) 29 | 30 | return { 31 | id, 32 | mintProgramIdA: (await getMintProgram(info.mintA)).toString(), 33 | mintProgramIdB: (await getMintProgram(info.mintB)).toString(), 34 | mintA: info.mintA.toString(), 35 | mintB: info.mintB.toString(), 36 | vaultA: info.vaultA.toString(), 37 | vaultB: info.vaultB.toString(), 38 | mintDecimalsA: info.mintDecimalsA, 39 | mintDecimalsB: info.mintDecimalsB, 40 | ammConfig: await getConfigInfo(info.ammConfig), 41 | rewardInfos: await Promise.all( 42 | info.rewardInfos 43 | .filter((i) => !i.tokenMint.equals(PublicKey.default)) 44 | .map(async (i) => ({ 45 | mint: i.tokenMint.toString(), 46 | programId: (await getMintProgram(i.tokenMint)).toString(), 47 | })) 48 | ), 49 | tvl: 0, 50 | day: getApiClmmPoolsItemStatisticsDefault(), 51 | week: getApiClmmPoolsItemStatisticsDefault(), 52 | month: getApiClmmPoolsItemStatisticsDefault(), 53 | lookupTableAccount: PublicKey.default.toBase58(), 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/getClmmPoolInfo.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PoolInfoLayout, 3 | SqrtPriceMath, 4 | } from '@raydium-io/raydium-sdk'; 5 | import { PublicKey } from '@solana/web3.js'; 6 | 7 | import { connection } from '../config'; 8 | 9 | async function getClmmPoolInfo() { 10 | const id = new PublicKey('< pool id >') 11 | 12 | const accountInfo = await connection.getAccountInfo(id) 13 | 14 | if (accountInfo === null) throw Error(' get pool info error ') 15 | 16 | const poolData = PoolInfoLayout.decode(accountInfo.data) 17 | 18 | console.log('current price -> ', SqrtPriceMath.sqrtPriceX64ToPrice(poolData.sqrtPriceX64, poolData.mintDecimalsA, poolData.mintDecimalsB)) 19 | } 20 | 21 | getClmmPoolInfo() 22 | -------------------------------------------------------------------------------- /src/getOutOfRangePositionOutAmount.ts: -------------------------------------------------------------------------------- 1 | import BN from 'bn.js'; 2 | import Decimal from 'decimal.js'; 3 | 4 | import { 5 | Clmm, 6 | ClmmPoolInfo, 7 | fetchMultipleMintInfos, 8 | SqrtPriceMath, 9 | } from '@raydium-io/raydium-sdk'; 10 | 11 | import { connection } from '../config'; 12 | import { formatClmmKeysById } from './formatClmmKeysById'; 13 | 14 | export function _d(poolInfo: ClmmPoolInfo, amount: BN, type: 'A' | 'B') { 15 | const decimal = poolInfo[type === 'A' ? 'mintA' : 'mintB'].decimals 16 | return new Decimal(amount.toString()).div(new Decimal(10).pow(decimal)) 17 | } 18 | 19 | async function getOutOfRangePositionOutAmount() { 20 | const poolId = '' // need change 21 | 22 | const poolKey = await formatClmmKeysById(poolId) 23 | 24 | const poolInfo = (await Clmm.fetchMultiplePoolInfos({ connection, poolKeys: [poolKey], chainTime: new Date().getTime() / 1000, }))[poolId].state 25 | 26 | const priceLower = new Decimal(10) // need change 27 | const priceUpper = new Decimal(30) // need change 28 | const inputAmount = new Decimal(100) // need change 29 | const inputAmountMint = poolInfo.mintA // need change 30 | 31 | const tickLower = Clmm.getPriceAndTick({ poolInfo, price: priceLower, baseIn: true }).tick 32 | const tickUpper = Clmm.getPriceAndTick({ poolInfo, price: priceUpper, baseIn: true }).tick 33 | const token2022Infos = await fetchMultipleMintInfos({ connection, mints: [poolInfo.mintA.mint, poolInfo.mintB.mint] }) 34 | const epochInfo = await connection.getEpochInfo() 35 | 36 | const liquidityInfo = await Clmm.getLiquidityAmountOutFromAmountIn({ 37 | poolInfo, 38 | inputA: inputAmountMint.mint.equals(poolInfo.mintA.mint), 39 | tickLower, 40 | tickUpper, 41 | amount: new BN(inputAmount.mul(new Decimal(10).pow(inputAmountMint.decimals)).toFixed(0)), 42 | slippage: 0, 43 | add: true, 44 | 45 | amountHasFee: true, 46 | 47 | token2022Infos, 48 | epochInfo, 49 | }) 50 | 51 | const amountLower = Clmm.getAmountsFromLiquidity({ 52 | poolInfo: { ...poolInfo, sqrtPriceX64: SqrtPriceMath.getSqrtPriceX64FromTick(tickLower) }, 53 | tickLower, 54 | tickUpper, 55 | liquidity: liquidityInfo.liquidity, 56 | slippage: 0, 57 | add: false, 58 | 59 | token2022Infos, 60 | epochInfo, 61 | amountAddFee: false, 62 | }) 63 | 64 | const amountUpper = Clmm.getAmountsFromLiquidity({ 65 | poolInfo: { ...poolInfo, sqrtPriceX64: SqrtPriceMath.getSqrtPriceX64FromTick(tickUpper) }, 66 | tickLower, 67 | tickUpper, 68 | liquidity: liquidityInfo.liquidity, 69 | slippage: 0, 70 | add: false, 71 | 72 | token2022Infos, 73 | epochInfo, 74 | amountAddFee: false 75 | }) 76 | 77 | 78 | console.log(`create position info -> liquidity: ${liquidityInfo.liquidity.toString()} amountA: ${_d(poolInfo, liquidityInfo.amountA.amount, 'A')} amountB: ${_d(poolInfo, liquidityInfo.amountB.amount, 'B')}`) 79 | console.log(`out of range position(lower) info -> liquidity: ${amountLower.liquidity.toString()} amountA: ${_d(poolInfo, amountLower.amountA.amount, 'A')} amountB: ${_d(poolInfo, amountLower.amountB.amount, 'B')}`) 80 | console.log(`out of range position(upper) info -> liquidity: ${amountUpper.liquidity.toString()} amountA: ${_d(poolInfo, amountUpper.amountA.amount, 'A')} amountB: ${_d(poolInfo, amountUpper.amountB.amount, 'B')}`) 81 | } 82 | -------------------------------------------------------------------------------- /src/harvestAndAddPosition.ts: -------------------------------------------------------------------------------- 1 | import BN from 'bn.js'; 2 | 3 | import { 4 | ApiClmmPoolsItem, 5 | ApiPoolInfo, 6 | Clmm, 7 | ClmmPoolInfo, 8 | ClmmPoolRewardInfo, 9 | fetchMultipleMintInfos, 10 | getPdaPersonalPositionAddress, 11 | Percent, 12 | PositionInfoLayout, 13 | SPL_MINT_LAYOUT, 14 | Token, 15 | TokenAccount, 16 | TokenAmount, 17 | TradeV2, 18 | ZERO 19 | } from '@raydium-io/raydium-sdk'; 20 | import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token'; 21 | import { 22 | PublicKey 23 | } from '@solana/web3.js'; 24 | 25 | import { 26 | connection, 27 | makeTxVersion, 28 | PROGRAMIDS, 29 | wallet 30 | } from '../config'; 31 | import { autoAddPositionFunc } from './autoAddPosition'; 32 | import { formatAmmKeysToApi } from './formatAmmKeys'; 33 | import { formatClmmKeys } from './formatClmmKeys'; 34 | import { 35 | buildAndSendTx, 36 | getATAAddress, 37 | getWalletTokenAccount, 38 | sleepTime, 39 | } from './util'; 40 | 41 | 42 | async function harvestAndAddPosition() { 43 | const positionMint = '' 44 | 45 | const walletTokenAccounts = await getWalletTokenAccount(connection, wallet.publicKey) 46 | const positionAccount = walletTokenAccounts.find(i => i.accountInfo.mint.toString() === positionMint && i.accountInfo.amount.toNumber() === 1) 47 | if (positionAccount === undefined) { 48 | throw Error('find positon from wallet error') 49 | } 50 | 51 | const positionAccountAddress = getPdaPersonalPositionAddress(PROGRAMIDS.CLMM, new PublicKey(positionMint)).publicKey 52 | const positionAccountInfo = await connection.getAccountInfo(positionAccountAddress) 53 | if (positionAccountInfo === null) throw Error('get positionAccountInfo error') 54 | const positionAccountData = PositionInfoLayout.decode(positionAccountInfo.data) 55 | 56 | const positionPooId = positionAccountData.poolId 57 | console.log('position pool id -> ', positionPooId.toString()) 58 | 59 | const clmmPools: ApiClmmPoolsItem[] = await formatClmmKeys(PROGRAMIDS.CLMM.toString(), true) 60 | const clmmPool = clmmPools.find(i => i.id === positionPooId.toString()) 61 | if (clmmPool === undefined) throw Error('not found pool info from api') 62 | 63 | const clmmPoolInfo = await Clmm.fetchMultiplePoolInfos({ 64 | connection, 65 | poolKeys: [clmmPool], 66 | chainTime: new Date().getTime() / 1000, 67 | ownerInfo: { 68 | wallet: wallet.publicKey, 69 | tokenAccounts: [positionAccount], 70 | }, 71 | batchRequest: true, 72 | updateOwnerRewardAndFee: true, 73 | }) 74 | 75 | const clmmInfo = clmmPoolInfo[positionPooId.toString()].state 76 | const ownerPositionInfo = clmmPoolInfo[positionPooId.toString()].positionAccount![0] 77 | 78 | const rewardInfos: { 79 | poolReward: ClmmPoolRewardInfo, ownerReward: { 80 | growthInsideLastX64: BN; 81 | rewardAmountOwed: BN; 82 | pendingReward: BN; 83 | } 84 | }[] = [] 85 | for (let i = 0; i < Math.min(clmmInfo.rewardInfos.length, ownerPositionInfo.rewardInfos.length); i++) rewardInfos.push({ poolReward: clmmInfo.rewardInfos[i], ownerReward: ownerPositionInfo.rewardInfos[i] }) 86 | 87 | console.log('ownerPositionInfo') 88 | console.log('amount -> ', Object.entries({ liquidity: ownerPositionInfo.liquidity, amountA: ownerPositionInfo.amountA, amountB: ownerPositionInfo.amountB }).map(i => `${i[0]} -- ${String(i[1])}`)) 89 | console.log('fee -> ', Object.entries({ tokenFeeAmountA: ownerPositionInfo.tokenFeeAmountA, tokenFeeAmountB: ownerPositionInfo.tokenFeeAmountB }).map(i => `${i[0]} -- ${String(i[1])}`)) 90 | console.log('reward -> ', rewardInfos.map(i => ({ mint: i.poolReward.tokenMint, pending: i.ownerReward.pendingReward })).map(ii => Object.entries(ii).map(i => `${i[0]} -- ${String(i[1])}`))) 91 | 92 | const tempCount = ownerPositionInfo.tokenFeeAmountA.add(ownerPositionInfo.tokenFeeAmountB).add(rewardInfos.map(i => i.ownerReward.pendingReward).reduce((a, b) => a.add(b), new BN(0))) 93 | 94 | if (tempCount.lte(ZERO)) throw Error('No need to withdraw token') 95 | 96 | const needCacheMint = [ 97 | { programId: clmmInfo.mintA.programId.toString(), mint: clmmInfo.mintA.mint.toString() }, 98 | { programId: clmmInfo.mintB.programId.toString(), mint: clmmInfo.mintB.mint.toString() }, 99 | ...clmmInfo.rewardInfos.map(i => ({ programId: i.tokenProgramId.toString(), mint: i.tokenMint.toString() })).filter(i => i.mint !== PublicKey.default.toString())] 100 | 101 | const mintAccount: { [account: string]: { mint: string, amount: BN } } = {} 102 | for (const itemMint of needCacheMint) { 103 | const mintAllAccount = walletTokenAccounts.filter(i => i.accountInfo.mint.toString() === itemMint.mint) 104 | const mintAta = getATAAddress(new PublicKey(itemMint.programId), wallet.publicKey, new PublicKey(itemMint.mint)).publicKey 105 | mintAccount[mintAta.toString()] = { 106 | mint: itemMint.mint, 107 | amount: mintAllAccount.find(i => i.pubkey.equals(mintAta))?.accountInfo.amount ?? ZERO, 108 | } 109 | } 110 | 111 | console.log('start amount cache', Object.entries(mintAccount).map(i => `account ->${i[0]} -- mint-> ${i[1].mint} -- amount -> ${String(i[1].amount)} `)) 112 | 113 | // claim fee and reward 114 | const decreaseIns = await Clmm.makeDecreaseLiquidityInstructionSimple({ 115 | connection, 116 | poolInfo: clmmInfo, 117 | ownerPosition: ownerPositionInfo, 118 | liquidity: ZERO, 119 | amountMinA: ZERO, 120 | amountMinB: ZERO, 121 | makeTxVersion, 122 | ownerInfo: { 123 | feePayer: wallet.publicKey, 124 | wallet: wallet.publicKey, 125 | tokenAccounts: walletTokenAccounts, 126 | useSOLBalance: false, 127 | closePosition: false, 128 | }, 129 | associatedOnly: true, 130 | }) 131 | 132 | console.log('claim fee and reward txid: ', await buildAndSendTx(decreaseIns.innerTransactions)) 133 | 134 | const _tempBaseMint = [ 135 | 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC 136 | 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', // USDT 137 | 'So11111111111111111111111111111111111111112', // WSOL 138 | '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', // RAY 139 | ] 140 | const swapToMintBase: "A" | 'B' = _tempBaseMint.includes(clmmInfo.mintA.mint.toString()) ? 'A' : 'B' 141 | const _tempMintInfo = swapToMintBase === 'A' ? clmmInfo.mintA : clmmInfo.mintB 142 | const swapToMintToken = new Token(_tempMintInfo.programId, _tempMintInfo.mint, _tempMintInfo.decimals, 'temp', 'temp') 143 | 144 | // swap start 145 | const sPool: ApiPoolInfo = await formatAmmKeysToApi(PROGRAMIDS.AmmV4.toString(), true) 146 | 147 | const clmmList = Object.values( 148 | await Clmm.fetchMultiplePoolInfos({ connection, poolKeys: clmmPools, chainTime: new Date().getTime() / 1000 }) 149 | ).map((i) => i.state) 150 | 151 | for (const itemReward of rewardInfos) { 152 | const rewardMintAccountInfo = await connection.getAccountInfo(itemReward.poolReward.tokenMint) 153 | const rewardMintInfo = SPL_MINT_LAYOUT.decode(rewardMintAccountInfo!.data) 154 | const swapFromMintToken = new Token(itemReward.poolReward.tokenProgramId, itemReward.poolReward.tokenMint, rewardMintInfo.decimals, '_temp', '_temp') 155 | const swapFromMintTokenAmount = new TokenAmount(swapFromMintToken, itemReward.ownerReward.pendingReward) 156 | 157 | const swapInfo = await swap({ 158 | wallet: wallet.publicKey, 159 | tokenAccounts: walletTokenAccounts, 160 | clmmList, 161 | clmmPools, 162 | swapFromMintTokenAmount, 163 | swapToMintToken, 164 | sPool, 165 | slippage: new Percent(1, 100), 166 | }) 167 | 168 | console.log('will swap reward -> ', Object.entries({ 169 | programId: itemReward.poolReward.tokenProgramId, 170 | mint: itemReward.poolReward.tokenMint, 171 | decimals: rewardMintInfo.decimals, 172 | amount: itemReward.ownerReward.pendingReward, 173 | swapToMint: _tempMintInfo.mint, 174 | swapToAmount: swapInfo.amountMin.amount.raw.sub(swapInfo.amountMin.fee?.raw ?? ZERO), 175 | }).map(i => `${i[0]} -- ${String(i[1])}`)) 176 | 177 | console.log('swap reward txid: ', await buildAndSendTx(decreaseIns.innerTransactions)) 178 | } 179 | 180 | if (rewardInfos.length > 0) await sleepTime(30 * 1000) // await to confirm 181 | 182 | const walletTokenAccountsSwapRewardOver = await getWalletTokenAccount(connection, wallet.publicKey) 183 | const mintAccountSwapRewardOver: { [account: string]: { mint: string, amount: BN } } = {} 184 | for (const itemMint of needCacheMint) { 185 | const mintAllAccount = walletTokenAccountsSwapRewardOver.filter(i => i.accountInfo.mint.toString() === itemMint.mint) 186 | const mintAta = getATAAddress(new PublicKey(itemMint.programId), wallet.publicKey, new PublicKey(itemMint.mint)).publicKey 187 | mintAccountSwapRewardOver[mintAta.toString()] = { 188 | mint: itemMint.mint, 189 | amount: mintAllAccount.find(i => i.pubkey.equals(mintAta))?.accountInfo.amount ?? ZERO, 190 | } 191 | } 192 | 193 | console.log('swap reward over amount cache', Object.entries(mintAccountSwapRewardOver).map(i => `account ->${i[0]} -- mint-> ${i[1].mint} -- amount -> ${String(i[1].amount)} `)) 194 | 195 | const mintAtaA = getATAAddress(new PublicKey(clmmInfo.mintA.programId), wallet.publicKey, new PublicKey(clmmInfo.mintA.mint)).publicKey.toString() 196 | const mintAtaB = getATAAddress(new PublicKey(clmmInfo.mintB.programId), wallet.publicKey, new PublicKey(clmmInfo.mintB.mint)).publicKey.toString() 197 | const willAddMintAmountA = (mintAccountSwapRewardOver[mintAtaA].amount ?? ZERO).sub(mintAccount[mintAtaA].amount ?? ZERO) 198 | const willAddMintAmountB = (mintAccountSwapRewardOver[mintAtaB].amount ?? ZERO).sub(mintAccount[mintAtaB].amount ?? ZERO) 199 | 200 | await autoAddPositionFunc({ 201 | poolInfo: clmmInfo, 202 | positionInfo: ownerPositionInfo, 203 | addMintAmountA: willAddMintAmountA, 204 | addMintAmountB: willAddMintAmountB, 205 | walletTokenAccounts, 206 | clmmList, 207 | sPool, 208 | clmmPools, 209 | }) 210 | } 211 | 212 | async function swap({ wallet, tokenAccounts, clmmPools, swapFromMintTokenAmount, swapToMintToken, clmmList, sPool, slippage }: { 213 | wallet: PublicKey, 214 | tokenAccounts: TokenAccount[], 215 | clmmPools: ApiClmmPoolsItem[], 216 | swapFromMintTokenAmount: TokenAmount, 217 | swapToMintToken: Token, 218 | clmmList: ClmmPoolInfo[], 219 | sPool: ApiPoolInfo, 220 | slippage: Percent, 221 | }) { 222 | const getRoute = TradeV2.getAllRoute({ 223 | inputMint: swapFromMintTokenAmount.token.mint, 224 | outputMint: swapToMintToken.mint, 225 | apiPoolList: sPool, 226 | clmmList, 227 | }) 228 | 229 | const [tickCache, poolInfosCache] = await Promise.all([ 230 | await Clmm.fetchMultiplePoolTickArrays({ connection, poolKeys: getRoute.needTickArray, batchRequest: true }), 231 | await TradeV2.fetchMultipleInfo({ connection, pools: getRoute.needSimulate, batchRequest: true }), 232 | ]) 233 | 234 | const [routeInfo] = TradeV2.getAllRouteComputeAmountOut({ 235 | directPath: getRoute.directPath, 236 | routePathDict: getRoute.routePathDict, 237 | simulateCache: poolInfosCache, 238 | tickCache, 239 | inputTokenAmount: swapFromMintTokenAmount, 240 | outputToken: swapToMintToken, 241 | slippage, 242 | chainTime: new Date().getTime() / 1000, // this chain time 243 | 244 | mintInfos: await fetchMultipleMintInfos({ 245 | connection, mints: [ 246 | ...clmmPools.map(i => [{ mint: i.mintA, program: i.mintProgramIdA }, { mint: i.mintB, program: i.mintProgramIdB }]).flat().filter(i => i.program === TOKEN_2022_PROGRAM_ID.toString()).map(i => new PublicKey(i.mint)), 247 | ] 248 | }), 249 | 250 | epochInfo: await connection.getEpochInfo(), 251 | }) 252 | 253 | const { innerTransactions } = await TradeV2.makeSwapInstructionSimple({ 254 | routeProgram: PROGRAMIDS.Router, 255 | connection, 256 | swapInfo: routeInfo, 257 | ownerInfo: { 258 | wallet, 259 | tokenAccounts, 260 | associatedOnly: true, 261 | checkCreateATAOwner: true, 262 | }, 263 | makeTxVersion, 264 | }) 265 | 266 | return { 267 | ins: innerTransactions, 268 | amountMin: routeInfo.minAmountOut, 269 | } 270 | } -------------------------------------------------------------------------------- /src/stakeFarm.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | import { 4 | ApiFarmInfo, 5 | Farm, 6 | FarmPoolKeys, 7 | jsonInfo2PoolKeys, 8 | TokenAmount, 9 | } from '@raydium-io/raydium-sdk'; 10 | import { Keypair } from '@solana/web3.js'; 11 | 12 | import { 13 | connection, 14 | DEFAULT_TOKEN, 15 | ENDPOINT, 16 | makeTxVersion, 17 | RAYDIUM_MAINNET_API, 18 | wallet, 19 | } from '../config'; 20 | import { 21 | buildAndSendTx, 22 | getWalletTokenAccount, 23 | } from './util'; 24 | 25 | type WalletTokenAccounts = Awaited> 26 | type TestTxInputInfo = { 27 | targetFarm: string 28 | inputTokenAmount: TokenAmount 29 | walletTokenAccounts: WalletTokenAccounts 30 | wallet: Keypair 31 | } 32 | 33 | async function stakeFarm(input: TestTxInputInfo) { 34 | // -------- pre-action: fetch target farm json info -------- 35 | const farmPools: ApiFarmInfo = await (await fetch(ENDPOINT + RAYDIUM_MAINNET_API.farmInfo)).json() 36 | assert(farmPools, 'farm pool is undefined') 37 | const targetFarmJsonInfo: any = farmPools.raydium.find((pool) => pool.id === input.targetFarm) 38 | assert(targetFarmJsonInfo, 'target farm not found') 39 | const targetFarmInfo = jsonInfo2PoolKeys(targetFarmJsonInfo) as FarmPoolKeys 40 | 41 | const chainTime = Math.floor(new Date().getTime() / 1000) // TODO 42 | 43 | const { [input.targetFarm]: farmFetchInfo } = await Farm.fetchMultipleInfoAndUpdate({ 44 | connection, 45 | pools: [targetFarmInfo], 46 | chainTime, 47 | }) 48 | assert(Object.keys(farmFetchInfo).length && farmFetchInfo, 'cannot fetch target farm info') 49 | 50 | // -------- step 1: create instructions by SDK function -------- 51 | const makeDepositInstruction = await Farm.makeDepositInstructionSimple({ 52 | connection, 53 | poolKeys: targetFarmInfo, 54 | fetchPoolInfo: farmFetchInfo, 55 | ownerInfo: { 56 | feePayer: input.wallet.publicKey, 57 | wallet: input.wallet.publicKey, 58 | tokenAccounts: input.walletTokenAccounts, 59 | }, 60 | amount: input.inputTokenAmount.raw, 61 | makeTxVersion, 62 | }) 63 | 64 | return { txids: await buildAndSendTx(makeDepositInstruction.innerTransactions) } 65 | } 66 | 67 | async function howToUse() { 68 | const targetFarm = 'pool id' // RAY-USDC farm 69 | const lpToken = DEFAULT_TOKEN['RAY_USDC-LP'] 70 | const inputTokenAmount = new TokenAmount(lpToken, 100) 71 | const walletTokenAccounts = await getWalletTokenAccount(connection, wallet.publicKey) 72 | 73 | stakeFarm({ 74 | targetFarm, 75 | inputTokenAmount, 76 | walletTokenAccounts, 77 | wallet: wallet, 78 | }).then(({ txids }) => { 79 | /** continue with txids */ 80 | console.log('txids', txids) 81 | }) 82 | } 83 | -------------------------------------------------------------------------------- /src/subNewAmmPool.ts: -------------------------------------------------------------------------------- 1 | import { ApiPoolInfoV4, MARKET_STATE_LAYOUT_V3, Market, SPL_MINT_LAYOUT } from "@raydium-io/raydium-sdk"; 2 | import { PublicKey } from "@solana/web3.js"; 3 | import Client from "@triton-one/yellowstone-grpc"; 4 | import base58 from "bs58"; 5 | import { connection, rpcToken, rpcUrl } from "../config"; 6 | 7 | async function subNewAmmPool() { 8 | const programId = '675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8' 9 | const createPoolFeeAccount = '7YttLkHDoNj9wyDur5pM1ejNaAvT9X4eqaYcHQqtj2G5' // only mainnet, dev pls use 3XMrhbv989VxAMi3DErLV9eJht1pHppW5LbKxe9fkEFR 10 | 11 | const client = new Client(rpcUrl, rpcToken); 12 | const rpcConnInfo = await client.subscribe(); 13 | 14 | rpcConnInfo.on("data", (data) => { 15 | callback(data, programId) 16 | }); 17 | 18 | await new Promise((resolve, reject) => { 19 | if (rpcConnInfo === undefined) throw Error('rpc conn error') 20 | rpcConnInfo.write({ 21 | slots: {}, 22 | accounts: {}, 23 | transactions: { 24 | transactionsSubKey: { 25 | accountInclude: [createPoolFeeAccount], 26 | accountExclude: [], 27 | accountRequired: [] 28 | } 29 | }, 30 | blocks: {}, 31 | blocksMeta: {}, 32 | accountsDataSlice: [], 33 | entry: {}, 34 | commitment: 1 35 | }, (err: Error) => { 36 | if (err === null || err === undefined) { 37 | resolve(); 38 | } else { 39 | reject(err); 40 | } 41 | }); 42 | }).catch((reason) => { 43 | console.error(reason); 44 | throw reason; 45 | }); 46 | } 47 | 48 | async function callback(data: any, programId: string) { 49 | if (!data.filters.includes('transactionsSubKey')) return undefined 50 | 51 | const info = data.transaction 52 | if (info.transaction.meta.err !== undefined) return undefined 53 | 54 | const formatData: { 55 | updateTime: number, slot: number, txid: string, poolInfos: ApiPoolInfoV4[] 56 | } = { 57 | updateTime: new Date().getTime(), 58 | slot: info.slot, 59 | txid: base58.encode(info.transaction.signature), 60 | poolInfos: [] 61 | } 62 | 63 | const accounts = info.transaction.transaction.message.accountKeys.map((i: Buffer) => base58.encode(i)) 64 | for (const item of [...info.transaction.transaction.message.instructions, ...info.transaction.meta.innerInstructions.map((i: any) => i.instructions).flat()]) { 65 | if (accounts[item.programIdIndex] !== programId) continue 66 | 67 | if ([...(item.data as Buffer).values()][0] != 1) continue 68 | 69 | const keyIndex = [...(item.accounts as Buffer).values()] 70 | 71 | const [baseMintAccount, quoteMintAccount, marketAccount] = await connection.getMultipleAccountsInfo([ 72 | new PublicKey(accounts[keyIndex[8]]), 73 | new PublicKey(accounts[keyIndex[9]]), 74 | new PublicKey(accounts[keyIndex[16]]), 75 | ]) 76 | 77 | if (baseMintAccount === null || quoteMintAccount === null || marketAccount === null) throw Error('get account info error') 78 | 79 | const baseMintInfo = SPL_MINT_LAYOUT.decode(baseMintAccount.data) 80 | const quoteMintInfo = SPL_MINT_LAYOUT.decode(quoteMintAccount.data) 81 | const marketInfo = MARKET_STATE_LAYOUT_V3.decode(marketAccount.data) 82 | 83 | formatData.poolInfos.push({ 84 | id: accounts[keyIndex[4]], 85 | baseMint: accounts[keyIndex[8]], 86 | quoteMint: accounts[keyIndex[9]], 87 | lpMint: accounts[keyIndex[7]], 88 | baseDecimals: baseMintInfo.decimals, 89 | quoteDecimals: quoteMintInfo.decimals, 90 | lpDecimals: baseMintInfo.decimals, 91 | version: 4, 92 | programId: programId, 93 | authority: accounts[keyIndex[5]], 94 | openOrders: accounts[keyIndex[6]], 95 | targetOrders: accounts[keyIndex[12]], 96 | baseVault: accounts[keyIndex[10]], 97 | quoteVault: accounts[keyIndex[11]], 98 | withdrawQueue: PublicKey.default.toString(), 99 | lpVault: PublicKey.default.toString(), 100 | marketVersion: 3, 101 | marketProgramId: marketAccount.owner.toString(), 102 | marketId: accounts[keyIndex[16]], 103 | marketAuthority: Market.getAssociatedAuthority({ programId: marketAccount.owner, marketId: new PublicKey(accounts[keyIndex[16]]) }).publicKey.toString(), 104 | marketBaseVault: marketInfo.baseVault.toString(), 105 | marketQuoteVault: marketInfo.quoteVault.toString(), 106 | marketBids: marketInfo.bids.toString(), 107 | marketAsks: marketInfo.asks.toString(), 108 | marketEventQueue: marketInfo.eventQueue.toString(), 109 | lookupTableAccount: PublicKey.default.toString() 110 | }) 111 | } 112 | 113 | console.log(formatData) 114 | 115 | return formatData 116 | } 117 | 118 | subNewAmmPool() -------------------------------------------------------------------------------- /src/swapOnlyAmm.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | import { 4 | jsonInfo2PoolKeys, 5 | Liquidity, 6 | LiquidityPoolKeys, 7 | Percent, 8 | Token, 9 | TokenAmount, 10 | } from '@raydium-io/raydium-sdk'; 11 | import { Keypair } from '@solana/web3.js'; 12 | 13 | import { 14 | connection, 15 | DEFAULT_TOKEN, 16 | makeTxVersion, 17 | wallet 18 | } from '../config'; 19 | import { formatAmmKeysById } from './formatAmmKeysById'; 20 | import { 21 | buildAndSendTx, 22 | getWalletTokenAccount, 23 | } from './util'; 24 | 25 | type WalletTokenAccounts = Awaited> 26 | type TestTxInputInfo = { 27 | outputToken: Token 28 | targetPool: string 29 | inputTokenAmount: TokenAmount 30 | slippage: Percent 31 | walletTokenAccounts: WalletTokenAccounts 32 | wallet: Keypair 33 | } 34 | 35 | async function swapOnlyAmm(input: TestTxInputInfo) { 36 | // -------- pre-action: get pool info -------- 37 | const targetPoolInfo = await formatAmmKeysById(input.targetPool) 38 | assert(targetPoolInfo, 'cannot find the target pool') 39 | const poolKeys = jsonInfo2PoolKeys(targetPoolInfo) as LiquidityPoolKeys 40 | 41 | // -------- step 1: coumpute amount out -------- 42 | const { amountOut, minAmountOut } = Liquidity.computeAmountOut({ 43 | poolKeys: poolKeys, 44 | poolInfo: await Liquidity.fetchInfo({ connection, poolKeys }), 45 | amountIn: input.inputTokenAmount, 46 | currencyOut: input.outputToken, 47 | slippage: input.slippage, 48 | }) 49 | 50 | // -------- step 2: create instructions by SDK function -------- 51 | const { innerTransactions } = await Liquidity.makeSwapInstructionSimple({ 52 | connection, 53 | poolKeys, 54 | userKeys: { 55 | tokenAccounts: input.walletTokenAccounts, 56 | owner: input.wallet.publicKey, 57 | }, 58 | amountIn: input.inputTokenAmount, 59 | amountOut: minAmountOut, 60 | fixedSide: 'in', 61 | makeTxVersion, 62 | }) 63 | 64 | console.log('amountOut:', amountOut.toFixed(), ' minAmountOut: ', minAmountOut.toFixed()) 65 | 66 | return { txids: await buildAndSendTx(innerTransactions) } 67 | } 68 | 69 | async function howToUse() { 70 | const inputToken = DEFAULT_TOKEN.USDC // USDC 71 | const outputToken = DEFAULT_TOKEN.RAY // RAY 72 | const targetPool = 'pool id' // USDC-RAY pool 73 | const inputTokenAmount = new TokenAmount(inputToken, 10000) 74 | const slippage = new Percent(1, 100) 75 | const walletTokenAccounts = await getWalletTokenAccount(connection, wallet.publicKey) 76 | 77 | swapOnlyAmm({ 78 | outputToken, 79 | targetPool, 80 | inputTokenAmount, 81 | slippage, 82 | walletTokenAccounts, 83 | wallet: wallet, 84 | }).then(({ txids }) => { 85 | /** continue with txids */ 86 | console.log('txids', txids) 87 | }) 88 | } 89 | -------------------------------------------------------------------------------- /src/swapOnlyCLMM.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApiClmmPoolsItem, 3 | Clmm, 4 | fetchMultipleMintInfos, 5 | Percent, 6 | Token, 7 | TokenAmount, 8 | } from '@raydium-io/raydium-sdk'; 9 | import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token'; 10 | import { 11 | Keypair, 12 | PublicKey, 13 | } from '@solana/web3.js'; 14 | 15 | import { 16 | connection, 17 | DEFAULT_TOKEN, 18 | makeTxVersion, 19 | wallet 20 | } from '../config'; 21 | import { formatClmmKeysById } from './formatClmmKeysById'; 22 | import { 23 | buildAndSendTx, 24 | getWalletTokenAccount, 25 | } from './util'; 26 | 27 | type WalletTokenAccounts = Awaited> 28 | type TestTxInputInfo = { 29 | outputToken: Token 30 | targetPool: string 31 | inputTokenAmount: TokenAmount 32 | slippage: Percent 33 | walletTokenAccounts: WalletTokenAccounts 34 | wallet: Keypair 35 | } 36 | 37 | async function swapOnlyCLMM(input: TestTxInputInfo) { 38 | // -------- pre-action: fetch Clmm pools info -------- 39 | const clmmPools: ApiClmmPoolsItem[] = [await formatClmmKeysById(input.targetPool)] 40 | const { [input.targetPool]: clmmPoolInfo } = await Clmm.fetchMultiplePoolInfos({ 41 | connection, 42 | poolKeys: clmmPools, 43 | chainTime: new Date().getTime() / 1000, 44 | }) 45 | 46 | // -------- step 1: fetch tick array -------- 47 | const tickCache = await Clmm.fetchMultiplePoolTickArrays({ 48 | connection, 49 | poolKeys: [clmmPoolInfo.state], 50 | batchRequest: true, 51 | }) 52 | 53 | // -------- step 2: calc amount out by SDK function -------- 54 | // Configure input/output parameters, in this example, this token amount will swap 0.0001 USDC to RAY 55 | const { minAmountOut, remainingAccounts } = Clmm.computeAmountOutFormat({ 56 | poolInfo: clmmPoolInfo.state, 57 | tickArrayCache: tickCache[input.targetPool], 58 | amountIn: input.inputTokenAmount, 59 | currencyOut: input.outputToken, 60 | slippage: input.slippage, 61 | epochInfo: await connection.getEpochInfo(), 62 | token2022Infos: await fetchMultipleMintInfos({ 63 | connection, mints: [ 64 | ...clmmPools.map(i => [{ mint: i.mintA, program: i.mintProgramIdA }, { mint: i.mintB, program: i.mintProgramIdB }]).flat().filter(i => i.program === TOKEN_2022_PROGRAM_ID.toString()).map(i => new PublicKey(i.mint)), 65 | ] 66 | }), 67 | catchLiquidityInsufficient: false, 68 | }) 69 | 70 | // -------- step 3: create instructions by SDK function -------- 71 | const { innerTransactions } = await Clmm.makeSwapBaseInInstructionSimple({ 72 | connection, 73 | poolInfo: clmmPoolInfo.state, 74 | ownerInfo: { 75 | feePayer: input.wallet.publicKey, 76 | wallet: input.wallet.publicKey, 77 | tokenAccounts: input.walletTokenAccounts, 78 | }, 79 | inputMint: input.inputTokenAmount.token.mint, 80 | amountIn: input.inputTokenAmount.raw, 81 | amountOutMin: minAmountOut.amount.raw, 82 | remainingAccounts, 83 | makeTxVersion, 84 | }) 85 | 86 | return { txids: await buildAndSendTx(innerTransactions) } 87 | } 88 | 89 | async function howToUse() { 90 | const inputToken = DEFAULT_TOKEN.USDC // USDC 91 | const outputToken = DEFAULT_TOKEN.RAY // RAY 92 | const targetPool = 'pool id' // USDC-RAY pool 93 | const inputTokenAmount = new TokenAmount(inputToken, 100) 94 | const slippage = new Percent(1, 100) 95 | const walletTokenAccounts = await getWalletTokenAccount(connection, wallet.publicKey) 96 | 97 | swapOnlyCLMM({ 98 | outputToken, 99 | targetPool, 100 | inputTokenAmount, 101 | slippage, 102 | walletTokenAccounts, 103 | wallet: wallet, 104 | }).then(({ txids }) => { 105 | /** continue with txids */ 106 | console.log('txids', txids) 107 | }) 108 | } 109 | -------------------------------------------------------------------------------- /src/swapRoute.ts: -------------------------------------------------------------------------------- 1 | import BN from 'bn.js'; 2 | 3 | import { 4 | ApiClmmPoolsItem, 5 | ApiPoolInfo, 6 | Clmm, 7 | Currency, 8 | CurrencyAmount, 9 | fetchMultipleMintInfos, 10 | Percent, 11 | Token, 12 | TokenAmount, 13 | TradeV2 14 | } from '@raydium-io/raydium-sdk'; 15 | import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token'; 16 | import { 17 | Keypair, 18 | PublicKey, 19 | } from '@solana/web3.js'; 20 | 21 | import { 22 | connection, 23 | DEFAULT_TOKEN, 24 | makeTxVersion, 25 | PROGRAMIDS, 26 | wallet 27 | } from '../config'; 28 | import { formatAmmKeysToApi } from './formatAmmKeys'; 29 | import { formatClmmKeys } from './formatClmmKeys'; 30 | import { 31 | buildAndSendTx, 32 | getWalletTokenAccount, 33 | } from './util'; 34 | 35 | type WalletTokenAccounts = Awaited> 36 | type TestTxInputInfo = { 37 | inputToken: Token | Currency 38 | outputToken: Token | Currency 39 | inputTokenAmount: TokenAmount | CurrencyAmount 40 | slippage: Percent 41 | walletTokenAccounts: WalletTokenAccounts 42 | wallet: Keypair 43 | 44 | feeConfig?: { 45 | feeBps: BN, 46 | feeAccount: PublicKey 47 | } 48 | } 49 | 50 | async function routeSwap(input: TestTxInputInfo) { 51 | // -------- pre-action: fetch Clmm pools info and ammV2 pools info -------- 52 | const clmmPools: ApiClmmPoolsItem[] = await formatClmmKeys(PROGRAMIDS.CLMM.toString()) // If the clmm pool is not required for routing, then this variable can be configured as undefined 53 | const clmmList = Object.values( 54 | await Clmm.fetchMultiplePoolInfos({ connection, poolKeys: clmmPools, chainTime: new Date().getTime() / 1000 }) 55 | ).map((i) => i.state) 56 | 57 | const sPool: ApiPoolInfo = await formatAmmKeysToApi(PROGRAMIDS.AmmV4.toString(), true) // If the Liquidity pool is not required for routing, then this variable can be configured as undefined 58 | 59 | // -------- step 1: get all route -------- 60 | const getRoute = TradeV2.getAllRoute({ 61 | inputMint: input.inputToken instanceof Token ? input.inputToken.mint : PublicKey.default, 62 | outputMint: input.outputToken instanceof Token ? input.outputToken.mint : PublicKey.default, 63 | apiPoolList: sPool, 64 | clmmList, 65 | }) 66 | 67 | // -------- step 2: fetch tick array and pool info -------- 68 | const [tickCache, poolInfosCache] = await Promise.all([ 69 | await Clmm.fetchMultiplePoolTickArrays({ connection, poolKeys: getRoute.needTickArray, batchRequest: true }), 70 | await TradeV2.fetchMultipleInfo({ connection, pools: getRoute.needSimulate, batchRequest: true }), 71 | ]) 72 | 73 | // -------- step 3: calculation result of all route -------- 74 | const [routeInfo] = TradeV2.getAllRouteComputeAmountOut({ 75 | directPath: getRoute.directPath, 76 | routePathDict: getRoute.routePathDict, 77 | simulateCache: poolInfosCache, 78 | tickCache, 79 | inputTokenAmount: input.inputTokenAmount, 80 | outputToken: input.outputToken, 81 | slippage: input.slippage, 82 | chainTime: new Date().getTime() / 1000, // this chain time 83 | 84 | feeConfig: input.feeConfig, 85 | 86 | mintInfos: await fetchMultipleMintInfos({connection, mints: [ 87 | ...clmmPools.map(i => [{mint: i.mintA, program: i.mintProgramIdA}, {mint: i.mintB, program: i.mintProgramIdB}]).flat().filter(i => i.program === TOKEN_2022_PROGRAM_ID.toString()).map(i => new PublicKey(i.mint)), 88 | ]}), 89 | 90 | epochInfo: await connection.getEpochInfo(), 91 | }) 92 | 93 | // -------- step 4: create instructions by SDK function -------- 94 | const { innerTransactions } = await TradeV2.makeSwapInstructionSimple({ 95 | routeProgram: PROGRAMIDS.Router, 96 | connection, 97 | swapInfo: routeInfo, 98 | ownerInfo: { 99 | wallet: input.wallet.publicKey, 100 | tokenAccounts: input.walletTokenAccounts, 101 | associatedOnly: true, 102 | checkCreateATAOwner: true, 103 | }, 104 | 105 | computeBudgetConfig: { // if you want add compute instruction 106 | units: 400000, // compute instruction 107 | microLamports: 1, // fee add 1 * 400000 / 10 ** 9 SOL 108 | }, 109 | makeTxVersion, 110 | }) 111 | 112 | return { txids: await buildAndSendTx(innerTransactions) } 113 | } 114 | 115 | async function howToUse() { 116 | // sol -> new Currency(9, 'SOL', 'SOL') 117 | const outputToken = DEFAULT_TOKEN.USDC // USDC 118 | const inputToken = DEFAULT_TOKEN.RAY // RAY 119 | // const inputToken = new Currency(9, 'SOL', 'SOL') 120 | 121 | const inputTokenAmount = new (inputToken instanceof Token ? TokenAmount : CurrencyAmount)(inputToken, 100) 122 | const slippage = new Percent(1, 100) 123 | const walletTokenAccounts = await getWalletTokenAccount(connection, wallet.publicKey) 124 | 125 | routeSwap({ 126 | inputToken, 127 | outputToken, 128 | inputTokenAmount, 129 | slippage, 130 | walletTokenAccounts, 131 | wallet, 132 | 133 | // feeConfig: { 134 | // feeBps: new BN(25), 135 | // feeAccount: Keypair.generate().publicKey // test 136 | // } 137 | }).then(({ txids }) => { 138 | /** continue with txids */ 139 | console.log('txids', txids) 140 | }) 141 | } 142 | 143 | howToUse() 144 | -------------------------------------------------------------------------------- /src/unstakeFarm.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | import { 4 | ApiFarmInfo, 5 | ENDPOINT, 6 | Farm, 7 | FarmPoolKeys, 8 | jsonInfo2PoolKeys, 9 | TokenAmount, 10 | } from '@raydium-io/raydium-sdk'; 11 | import { Keypair } from '@solana/web3.js'; 12 | 13 | import { 14 | connection, 15 | DEFAULT_TOKEN, 16 | makeTxVersion, 17 | RAYDIUM_MAINNET_API, 18 | wallet, 19 | } from '../config'; 20 | import { 21 | buildAndSendTx, 22 | getWalletTokenAccount, 23 | } from './util'; 24 | 25 | type WalletTokenAccounts = Awaited> 26 | type TestTxInputInfo = { 27 | targetFarm: string 28 | inputTokenAmount: TokenAmount 29 | walletTokenAccounts: WalletTokenAccounts 30 | wallet: Keypair 31 | } 32 | 33 | async function unstakeFarm(input: TestTxInputInfo) { 34 | // -------- pre-action: fetch farm info -------- 35 | const farmPool: ApiFarmInfo = await (await fetch(ENDPOINT + RAYDIUM_MAINNET_API.farmInfo)).json() 36 | assert(farmPool, 'farm pool is undefined') 37 | const targetFarmJsonInfo: any = farmPool.raydium.find((pool) => pool.id === input.targetFarm) 38 | assert(targetFarmJsonInfo, 'target farm not found') 39 | const targetFarmInfo = jsonInfo2PoolKeys(targetFarmJsonInfo) as FarmPoolKeys 40 | 41 | const chainTime = Math.floor(new Date().getTime() / 1000) // TODO 42 | // -------- step 1: Fetch farm pool info -------- 43 | const { [input.targetFarm]: farmPoolInfo } = await Farm.fetchMultipleInfoAndUpdate({ 44 | connection, 45 | pools: [targetFarmInfo], 46 | owner: input.wallet.publicKey, 47 | chainTime, 48 | }) 49 | assert(farmPoolInfo, 'cannot fetch target farm info') 50 | 51 | // -------- step 2: create instructions by SDK function -------- 52 | const makeWithdrawInstruction = await Farm.makeWithdrawInstructionSimple({ 53 | connection, 54 | fetchPoolInfo: farmPoolInfo, 55 | ownerInfo: { 56 | feePayer: input.wallet.publicKey, 57 | wallet: input.wallet.publicKey, 58 | tokenAccounts: input.walletTokenAccounts, 59 | }, 60 | amount: input.inputTokenAmount.raw, 61 | makeTxVersion, 62 | }) 63 | 64 | return { txids: await buildAndSendTx(makeWithdrawInstruction.innerTransactions) } 65 | } 66 | 67 | async function howToUse() { 68 | const targetFarm = 'pool id' // RAY-USDC farm 69 | const lpToken = DEFAULT_TOKEN['RAY_USDC-LP'] 70 | const inputTokenAmount = new TokenAmount(lpToken, 100) 71 | const walletTokenAccounts = await getWalletTokenAccount(connection, wallet.publicKey) 72 | 73 | unstakeFarm({ 74 | targetFarm, 75 | inputTokenAmount, 76 | walletTokenAccounts, 77 | wallet: wallet, 78 | }).then(({ txids }) => { 79 | /** continue with txids */ 80 | console.log('txids', txids) 81 | }) 82 | } 83 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | import { 2 | buildSimpleTransaction, 3 | findProgramAddress, 4 | InnerSimpleV0Transaction, 5 | SPL_ACCOUNT_LAYOUT, 6 | TOKEN_PROGRAM_ID, 7 | TokenAccount, 8 | } from '@raydium-io/raydium-sdk'; 9 | import { 10 | Connection, 11 | Keypair, 12 | PublicKey, 13 | SendOptions, 14 | Signer, 15 | Transaction, 16 | VersionedTransaction, 17 | } from '@solana/web3.js'; 18 | 19 | import { 20 | addLookupTableInfo, 21 | connection, 22 | makeTxVersion, 23 | wallet, 24 | } from '../config'; 25 | 26 | export async function sendTx( 27 | connection: Connection, 28 | payer: Keypair | Signer, 29 | txs: (VersionedTransaction | Transaction)[], 30 | options?: SendOptions 31 | ): Promise { 32 | const txids: string[] = []; 33 | for (const iTx of txs) { 34 | if (iTx instanceof VersionedTransaction) { 35 | iTx.sign([payer]); 36 | txids.push(await connection.sendTransaction(iTx, options)); 37 | } else { 38 | txids.push(await connection.sendTransaction(iTx, [payer], options)); 39 | } 40 | } 41 | return txids; 42 | } 43 | 44 | export async function getWalletTokenAccount(connection: Connection, wallet: PublicKey): Promise { 45 | const walletTokenAccount = await connection.getTokenAccountsByOwner(wallet, { 46 | programId: TOKEN_PROGRAM_ID, 47 | }); 48 | return walletTokenAccount.value.map((i) => ({ 49 | pubkey: i.pubkey, 50 | programId: i.account.owner, 51 | accountInfo: SPL_ACCOUNT_LAYOUT.decode(i.account.data), 52 | })); 53 | } 54 | 55 | export async function buildAndSendTx(innerSimpleV0Transaction: InnerSimpleV0Transaction[], options?: SendOptions) { 56 | const willSendTx = await buildSimpleTransaction({ 57 | connection, 58 | makeTxVersion, 59 | payer: wallet.publicKey, 60 | innerTransactions: innerSimpleV0Transaction, 61 | addLookupTableInfo: addLookupTableInfo, 62 | }) 63 | 64 | return await sendTx(connection, wallet, willSendTx, options) 65 | } 66 | 67 | export function getATAAddress(programId: PublicKey, owner: PublicKey, mint: PublicKey) { 68 | const { publicKey, nonce } = findProgramAddress( 69 | [owner.toBuffer(), programId.toBuffer(), mint.toBuffer()], 70 | new PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") 71 | ); 72 | return { publicKey, nonce }; 73 | } 74 | 75 | export async function sleepTime(ms: number) { 76 | console.log((new Date()).toLocaleString(), 'sleepTime', ms) 77 | return new Promise(resolve => setTimeout(resolve, ms)) 78 | } 79 | -------------------------------------------------------------------------------- /src/utils1216.ts: -------------------------------------------------------------------------------- 1 | import { Utils1216 } from '@raydium-io/raydium-sdk'; 2 | import { Keypair } from '@solana/web3.js'; 3 | 4 | import { 5 | connection, 6 | makeTxVersion, 7 | PROGRAMIDS, 8 | wallet, 9 | } from '../config'; 10 | import { 11 | buildAndSendTx, 12 | getWalletTokenAccount, 13 | } from './util'; 14 | 15 | type TestTxInputInfo = { 16 | wallet: Keypair 17 | } 18 | 19 | export async function utils1216(input: TestTxInputInfo) { 20 | // -------- pre-action: fetch compensation info list -------- 21 | const infoList = await Utils1216.getAllInfo({ 22 | connection, 23 | programId: PROGRAMIDS.UTIL1216, 24 | poolIds: Utils1216.DEFAULT_POOL_ID, 25 | wallet: input.wallet.publicKey, 26 | chainTime: new Date().getTime() / 1000, 27 | }) 28 | 29 | // -------- step 1: create instructions by SDK function -------- 30 | const claim = await Utils1216.makeClaimInstructionSimple({ 31 | connection, 32 | poolInfo: infoList[0], 33 | ownerInfo: { 34 | wallet: input.wallet.publicKey, 35 | tokenAccounts: await getWalletTokenAccount(connection, input.wallet.publicKey), 36 | associatedOnly: true, 37 | checkCreateATAOwner: true, 38 | }, 39 | makeTxVersion, 40 | }) 41 | 42 | // -------- step 1: create instructions by SDK function -------- 43 | const claimAll = await Utils1216.makeClaimAllInstructionSimple({ 44 | connection, 45 | poolInfos: infoList, 46 | ownerInfo: { 47 | wallet: input.wallet.publicKey, 48 | tokenAccounts: await getWalletTokenAccount(connection, input.wallet.publicKey), 49 | associatedOnly: true, 50 | checkCreateATAOwner: true, 51 | }, 52 | makeTxVersion, 53 | }) 54 | 55 | return { txids: await buildAndSendTx(claimAll.innerTransactions) } 56 | } 57 | 58 | async function howToUse() { 59 | utils1216({ 60 | wallet: wallet, 61 | }).then(({ txids }) => { 62 | /** continue with txids */ 63 | console.log('txids', txids) 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /src/utilsChangeMintAuthority.ts: -------------------------------------------------------------------------------- 1 | import { InstructionType } from '@raydium-io/raydium-sdk'; 2 | import { 3 | AuthorityType, 4 | createSetAuthorityInstruction, 5 | } from '@solana/spl-token'; 6 | import { PublicKey } from '@solana/web3.js'; 7 | 8 | import { wallet } from '../config'; 9 | import { buildAndSendTx } from './util'; 10 | 11 | (async () => { 12 | console.log({ 13 | txids: await buildAndSendTx([ 14 | { 15 | instructionTypes: [ 16 | InstructionType.test, 17 | ], 18 | instructions: [ 19 | createSetAuthorityInstruction( 20 | new PublicKey(' mint address '), 21 | wallet.publicKey, 22 | AuthorityType.FreezeAccount, 23 | null // if will delete , change -> new PublicKey(' new authority address ') 24 | ) 25 | ], 26 | signers: [], 27 | } 28 | ]) 29 | }) 30 | })() 31 | -------------------------------------------------------------------------------- /src/utilsCreateMarket.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MarketV2, 3 | Token, 4 | } from '@raydium-io/raydium-sdk'; 5 | import { Keypair } from '@solana/web3.js'; 6 | 7 | import { 8 | connection, 9 | DEFAULT_TOKEN, 10 | makeTxVersion, 11 | PROGRAMIDS, 12 | wallet, 13 | } from '../config'; 14 | import { buildAndSendTx } from './util'; 15 | 16 | type TestTxInputInfo = { 17 | baseToken: Token 18 | quoteToken: Token 19 | wallet: Keypair 20 | } 21 | 22 | export async function createMarket(input: TestTxInputInfo) { 23 | // -------- step 1: make instructions -------- 24 | const createMarketInstruments = await MarketV2.makeCreateMarketInstructionSimple({ 25 | connection, 26 | wallet: input.wallet.publicKey, 27 | baseInfo: input.baseToken, 28 | quoteInfo: input.quoteToken, 29 | lotSize: 1, // default 1 30 | tickSize: 0.01, // default 0.01 31 | dexProgramId: PROGRAMIDS.OPENBOOK_MARKET, 32 | makeTxVersion, 33 | }) 34 | 35 | return { txids: await buildAndSendTx(createMarketInstruments.innerTransactions) } 36 | } 37 | 38 | async function howToUse() { 39 | const baseToken = DEFAULT_TOKEN.RAY // RAY 40 | const quoteToken = DEFAULT_TOKEN.USDC // USDC 41 | 42 | createMarket({ 43 | baseToken, 44 | quoteToken, 45 | wallet: wallet, 46 | }).then(({ txids }) => { 47 | /** continue with txids */ 48 | console.log('txids', txids) 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "outDir": "./js", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "sourceMap": true 11 | }, 12 | "exclude": [ 13 | "otherCode" 14 | ] 15 | } 16 | --------------------------------------------------------------------------------