Skip to content

Commit

Permalink
add grpc demo
Browse files Browse the repository at this point in the history
  • Loading branch information
rudy5348 committed Dec 2, 2024
1 parent 23fe65c commit 9bef87e
Show file tree
Hide file tree
Showing 8 changed files with 551 additions and 19 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"dependencies": {
"@raydium-io/raydium-sdk-v2": "0.1.92-alpha",
"@solana/spl-token": "^0.4.6",
"@triton-one/yellowstone-grpc": "^1.2.0",
"@types/jsonfile": "^6.1.4",
"bs58": "^5.0.0",
"decimal.js": "^10.4.3",
Expand Down
3 changes: 3 additions & 0 deletions src/config.ts.template
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,6 @@ export const fetchTokenAccountData = async () => {
})
return tokenAccountData
}

export const grpcUrl = '<YOUR_GRPC_URL>'
export const grpcToken = '<YOUR_GRPC_TOKEN>'
1 change: 1 addition & 0 deletions src/grpc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
All demos in this folder need to use grpc, you can contact the rpc service provider to purchase.
94 changes: 94 additions & 0 deletions src/grpc/ammPoolInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { liquidityStateV4Layout, splAccountLayout } from "@raydium-io/raydium-sdk-v2";
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
import Client from "@triton-one/yellowstone-grpc";
import base58 from "bs58";
import Decimal from "decimal.js";
import { grpcToken, grpcUrl } from "../config";

async function ammPoolInfo() {
const programId = '675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8'
const auth = '5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1'

const client = new Client(grpcUrl, grpcToken, undefined);
const rpcConnInfo = await client.subscribe();

rpcConnInfo.on("data", (data) => {
callback(data, programId)
});

await new Promise<void>((resolve, reject) => {
if (rpcConnInfo === undefined) throw Error('rpc conn error')
rpcConnInfo.write({
slots: {},
accounts: {
ammUpdate: {
owner: [programId],
account: [],
filters: [{ datasize: `${liquidityStateV4Layout.span}` }],
nonemptyTxnSignature: true,
},
vaultUpdate: {
owner: [TOKEN_PROGRAM_ID.toString()],
account: [],
filters: [{ memcmp: { offset: `${splAccountLayout.offsetOf('owner')}`, base58: auth } }],
nonemptyTxnSignature: true,
},
},
transactions: {},
transactionsStatus: {},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
entry: {},
commitment: 1
}, (err: Error) => {
if (err === null || err === undefined) {
resolve();
} else {
reject(err);
}
});
}).catch((reason) => {
console.error(reason);
throw reason;
});
}

const vaultToPoolId: { [key: string]: { poolId: string, type: 'base' | 'quote' } } = {}
const poolInfoCache: { [key: string]: { poolInfo: ReturnType<typeof liquidityStateV4Layout.decode>, vaultA: ReturnType<typeof splAccountLayout.decode> | undefined, vaultB: ReturnType<typeof splAccountLayout.decode> | undefined } } = {}

async function callback(_data: any, programId: string) {
if (_data.filters.includes('ammUpdate')) {
const data = _data.account

const formatData = liquidityStateV4Layout.decode(data.account.data)
const pk = base58.encode(data.account.pubkey)

poolInfoCache[pk] = { poolInfo: formatData, vaultA: undefined, vaultB: undefined }
vaultToPoolId[formatData.baseVault.toString()] = { poolId: pk, type: 'base' }
vaultToPoolId[formatData.quoteVault.toString()] = { poolId: pk, type: 'quote' }
} else if (_data.filters.includes('vaultUpdate')) {
const data = _data.account

const formatData = splAccountLayout.decode(data.account.data)
const pk = base58.encode(data.account.pubkey)

if (vaultToPoolId[pk] === undefined) return

const _poolType = vaultToPoolId[pk]

if (_poolType.type === 'base') {
poolInfoCache[_poolType.poolId].vaultA = formatData
} else {
poolInfoCache[_poolType.poolId].vaultB = formatData
}

if (poolInfoCache[_poolType.poolId].vaultA === undefined || poolInfoCache[_poolType.poolId].vaultB === undefined) return

const vaultA = new Decimal(poolInfoCache[_poolType.poolId].vaultA!.amount.sub(poolInfoCache[_poolType.poolId].poolInfo.baseNeedTakePnl).toString()).div(10 ** poolInfoCache[_poolType.poolId].poolInfo.baseDecimal.toNumber())
const vaultB = new Decimal(poolInfoCache[_poolType.poolId].vaultB!.amount.sub(poolInfoCache[_poolType.poolId].poolInfo.quoteNeedTakePnl).toString()).div(10 ** poolInfoCache[_poolType.poolId].poolInfo.quoteDecimal.toNumber())
console.log(_poolType.poolId, vaultA, vaultB, vaultB.div(vaultA))
}
}

ammPoolInfo()
57 changes: 57 additions & 0 deletions src/grpc/clmmPoolInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { PoolInfoLayout, SqrtPriceMath } from '@raydium-io/raydium-sdk-v2';
import Client from "@triton-one/yellowstone-grpc";
import base58 from "bs58";
import { grpcToken, grpcUrl } from "../config";

async function clmmPoolInfo() {
const programId = 'CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK'

const client = new Client(grpcUrl, grpcToken, undefined);
const rpcConnInfo = await client.subscribe();

rpcConnInfo.on("data", (data) => {
callback(data, programId)
});

await new Promise<void>((resolve, reject) => {
if (rpcConnInfo === undefined) throw Error('rpc conn error')
rpcConnInfo.write({
slots: {},
accounts: {
ammUpdate: {
owner: [programId],
account: [],
filters: [{ datasize: `${PoolInfoLayout.span}` }],
nonemptyTxnSignature: true,
},
},
transactions: {},
transactionsStatus: {},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
entry: {},
commitment: 1
}, (err: Error) => {
if (err === null || err === undefined) {
resolve();
} else {
reject(err);
}
});
}).catch((reason) => {
console.error(reason);
throw reason;
});
}

async function callback(_data: any, programId: string) {
const data = _data.account

const formatData = PoolInfoLayout.decode(data.account.data)
const pk = base58.encode(data.account.pubkey)

console.log(pk, SqrtPriceMath.sqrtPriceX64ToPrice(formatData.sqrtPriceX64, formatData.mintDecimalsA, formatData.mintDecimalsB))
}

clmmPoolInfo()
100 changes: 100 additions & 0 deletions src/grpc/cpmmPoolInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { CpmmPoolInfoLayout, splAccountLayout } from "@raydium-io/raydium-sdk-v2";
import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from "@solana/spl-token";
import Client from "@triton-one/yellowstone-grpc";
import base58 from "bs58";
import Decimal from "decimal.js";
import { grpcToken, grpcUrl } from "../config";

async function cpmmPoolInfo() {
const programId = 'CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C'
const auth = 'GpMZbSM2GgvTKHJirzeGfMFoaZ8UR2X7F4v8vHTvxFbL'

const client = new Client(grpcUrl, grpcToken, undefined);
const rpcConnInfo = await client.subscribe();

rpcConnInfo.on("data", (data) => {
callback(data, programId)
});

await new Promise<void>((resolve, reject) => {
if (rpcConnInfo === undefined) throw Error('rpc conn error')
rpcConnInfo.write({
slots: {},
accounts: {
ammUpdate: {
owner: [programId],
account: [],
filters: [{ datasize: `${CpmmPoolInfoLayout.span}` }],
nonemptyTxnSignature: true,
},
vaultUpdate: {
owner: [TOKEN_PROGRAM_ID.toString()],
account: [],
filters: [{ memcmp: { offset: `${splAccountLayout.offsetOf('owner')}`, base58: auth } }],
nonemptyTxnSignature: true,
},
vault2022Update: {
owner: [TOKEN_2022_PROGRAM_ID.toString()],
account: [],
filters: [{ memcmp: { offset: `${splAccountLayout.offsetOf('owner')}`, base58: auth } }],
nonemptyTxnSignature: true,
},
},
transactions: {},
transactionsStatus: {},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
entry: {},
commitment: 1
}, (err: Error) => {
if (err === null || err === undefined) {
resolve();
} else {
reject(err);
}
});
}).catch((reason) => {
console.error(reason);
throw reason;
});
}

const vaultToPoolId: { [key: string]: { poolId: string, type: 'base' | 'quote' } } = {}
const poolInfoCache: { [key: string]: { poolInfo: ReturnType<typeof CpmmPoolInfoLayout.decode>, vaultA: ReturnType<typeof splAccountLayout.decode> | undefined, vaultB: ReturnType<typeof splAccountLayout.decode> | undefined } } = {}

async function callback(_data: any, programId: string) {
if (_data.filters.includes('ammUpdate')) {
const data = _data.account

const formatData = CpmmPoolInfoLayout.decode(data.account.data)
const pk = base58.encode(data.account.pubkey)

poolInfoCache[pk] = { poolInfo: formatData, vaultA: undefined, vaultB: undefined }
vaultToPoolId[formatData.vaultA.toString()] = { poolId: pk, type: 'base' }
vaultToPoolId[formatData.vaultB.toString()] = { poolId: pk, type: 'quote' }
} else if (_data.filters.includes('vaultUpdate') || _data.filters.includes('vault2022Update')) {
const data = _data.account

const formatData = splAccountLayout.decode(data.account.data)
const pk = base58.encode(data.account.pubkey)

if (vaultToPoolId[pk] === undefined) return

const _poolType = vaultToPoolId[pk]

if (_poolType.type === 'base') {
poolInfoCache[_poolType.poolId].vaultA = formatData
} else {
poolInfoCache[_poolType.poolId].vaultB = formatData
}

if (poolInfoCache[_poolType.poolId].vaultA === undefined || poolInfoCache[_poolType.poolId].vaultB === undefined) return

const vaultA = new Decimal(poolInfoCache[_poolType.poolId].vaultA!.amount.sub(poolInfoCache[_poolType.poolId].poolInfo.fundFeesMintA).sub(poolInfoCache[_poolType.poolId].poolInfo.protocolFeesMintA).toString()).div(10 ** poolInfoCache[_poolType.poolId].poolInfo.mintDecimalA)
const vaultB = new Decimal(poolInfoCache[_poolType.poolId].vaultB!.amount.sub(poolInfoCache[_poolType.poolId].poolInfo.fundFeesMintB).sub(poolInfoCache[_poolType.poolId].poolInfo.protocolFeesMintB).toString()).div(10 ** poolInfoCache[_poolType.poolId].poolInfo.mintDecimalB)
console.log(_poolType.poolId, vaultA, vaultB, vaultB.div(vaultA))
}
}

cpmmPoolInfo()
122 changes: 122 additions & 0 deletions src/grpc/subNewAmmPool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { ApiPoolInfoV4, Market, MARKET_STATE_LAYOUT_V3, SPL_MINT_LAYOUT } from "@raydium-io/raydium-sdk-v2";
import { PublicKey } from '@solana/web3.js';
import Client from "@triton-one/yellowstone-grpc";
import base58 from "bs58";
import { connection, grpcToken, grpcUrl } from "../config";

async function subNewAmmPool() {
const programId = '675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8'
const createPoolFeeAccount = '7YttLkHDoNj9wyDur5pM1ejNaAvT9X4eqaYcHQqtj2G5' // only mainnet, dev pls use 3XMrhbv989VxAMi3DErLV9eJht1pHppW5LbKxe9fkEFR

const client = new Client(grpcUrl, grpcToken, undefined);
const rpcConnInfo = await client.subscribe();

rpcConnInfo.on("data", (data) => {
callback(data, programId)
});

await new Promise<void>((resolve, reject) => {
if (rpcConnInfo === undefined) throw Error('rpc conn error')
rpcConnInfo.write({
slots: {},
accounts: {},
transactions: {
transactionsSubKey: {
accountInclude: [createPoolFeeAccount],
accountExclude: [],
accountRequired: []
}
},
transactionsStatus: {},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
entry: {},
commitment: 1
}, (err: Error) => {
if (err === null || err === undefined) {
resolve();
} else {
reject(err);
}
});
}).catch((reason) => {
console.error(reason);
throw reason;
});
}

async function callback(data: any, programId: string) {
if (!data.filters.includes('transactionsSubKey')) return undefined

const info = data.transaction
if (info.transaction.meta.err !== undefined) return undefined

const formatData: {
updateTime: number, slot: number, txid: string, poolInfos: ApiPoolInfoV4[]
} = {
updateTime: new Date().getTime(),
slot: info.slot,
txid: base58.encode(info.transaction.signature),
poolInfos: []
}

const accounts = info.transaction.transaction.message.accountKeys.map((i: Buffer) => base58.encode(i))
for (const item of [...info.transaction.transaction.message.instructions, ...info.transaction.meta.innerInstructions.map((i: any) => i.instructions).flat()]) {
if (accounts[item.programIdIndex] !== programId) continue

if ([...(item.data as Buffer).values()][0] != 1) continue

const keyIndex = [...(item.accounts as Buffer).values()]

const startTime = new Date().getTime()
console.log(new Date().toJSON(), 'new pool Id: ', accounts[keyIndex[4]]);

const [baseMintAccount, quoteMintAccount, marketAccount] = await connection.getMultipleAccountsInfo([
new PublicKey(accounts[keyIndex[8]]),
new PublicKey(accounts[keyIndex[9]]),
new PublicKey(accounts[keyIndex[16]]),
])

if (baseMintAccount === null || quoteMintAccount === null || marketAccount === null) throw Error('get account info error')

const baseMintInfo = SPL_MINT_LAYOUT.decode(baseMintAccount.data)
const quoteMintInfo = SPL_MINT_LAYOUT.decode(quoteMintAccount.data)
const marketInfo = MARKET_STATE_LAYOUT_V3.decode(marketAccount.data)

formatData.poolInfos.push({
id: accounts[keyIndex[4]],
baseMint: accounts[keyIndex[8]],
quoteMint: accounts[keyIndex[9]],
lpMint: accounts[keyIndex[7]],
baseDecimals: baseMintInfo.decimals,
quoteDecimals: quoteMintInfo.decimals,
lpDecimals: baseMintInfo.decimals,
version: 4,
programId: programId,
authority: accounts[keyIndex[5]],
openOrders: accounts[keyIndex[6]],
targetOrders: accounts[keyIndex[12]],
baseVault: accounts[keyIndex[10]],
quoteVault: accounts[keyIndex[11]],
withdrawQueue: PublicKey.default.toString(),
lpVault: PublicKey.default.toString(),
marketVersion: 3,
marketProgramId: marketAccount.owner.toString(),
marketId: accounts[keyIndex[16]],
marketAuthority: Market.getAssociatedAuthority({ programId: marketAccount.owner, marketId: new PublicKey(accounts[keyIndex[16]]) }).publicKey.toString(),
marketBaseVault: marketInfo.baseVault.toString(),
marketQuoteVault: marketInfo.quoteVault.toString(),
marketBids: marketInfo.bids.toString(),
marketAsks: marketInfo.asks.toString(),
marketEventQueue: marketInfo.eventQueue.toString(),
lookupTableAccount: PublicKey.default.toString()
})
}

console.log(formatData)

return formatData
}

subNewAmmPool()
Loading

0 comments on commit 9bef87e

Please sign in to comment.