|
|
@@ -3,7 +3,6 @@ import {
|
|
|
PublicKey,
|
|
|
type Finality,
|
|
|
type LogsCallback,
|
|
|
- type ParsedInstruction,
|
|
|
type TransactionSignature
|
|
|
} from '@solana/web3.js'
|
|
|
|
|
|
@@ -12,17 +11,30 @@ export type OpenPositionEvent = {
|
|
|
slot: number
|
|
|
blockTime?: number | null
|
|
|
programId: string
|
|
|
- logs: string[]
|
|
|
accounts?: string[]
|
|
|
tokenMints?: string[]
|
|
|
positionAccount?: string
|
|
|
- positionAccountDataBase64?: string
|
|
|
- positionFromNft?: {
|
|
|
- nftMint: string
|
|
|
- positionAccount: string
|
|
|
- dataBase64: string
|
|
|
- tickLower?: number
|
|
|
- tickUpper?: number
|
|
|
+ logs?: string[] // 添加原始日志
|
|
|
+ positionDetails?: {
|
|
|
+ positionAddress: string
|
|
|
+ providerAddress: string
|
|
|
+ nftMintAddress: string
|
|
|
+ poolAddress: string
|
|
|
+ mintA: {
|
|
|
+ address: string
|
|
|
+ symbol: string
|
|
|
+ decimals: number
|
|
|
+ price: string
|
|
|
+ }
|
|
|
+ mintB: {
|
|
|
+ address: string
|
|
|
+ symbol: string
|
|
|
+ decimals: number
|
|
|
+ price: string
|
|
|
+ }
|
|
|
+ totalDeposit: string
|
|
|
+ upperTick: number
|
|
|
+ lowerTick: number
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -31,12 +43,37 @@ export type OpenPositionListenerOpts = {
|
|
|
commitment?: 'processed' | 'confirmed' | 'finalized'
|
|
|
logIncludes?: string[]
|
|
|
maxSupportedTransactionVersion?: number
|
|
|
- getPositionInfoByNftMint?: (nftMint: string) => Promise<{
|
|
|
- positionAccount: string
|
|
|
- dataBase64: string
|
|
|
- tickLower?: number
|
|
|
- tickUpper?: number
|
|
|
- } | null>
|
|
|
+}
|
|
|
+
|
|
|
+type PositionApiResponse = {
|
|
|
+ retCode: number
|
|
|
+ retMsg: string
|
|
|
+ result?: {
|
|
|
+ success?: boolean
|
|
|
+ data?: {
|
|
|
+ positionAddress: string
|
|
|
+ providerAddress: string
|
|
|
+ nftMintAddress: string
|
|
|
+ pool: {
|
|
|
+ poolAddress: string
|
|
|
+ mintA: {
|
|
|
+ address: string
|
|
|
+ symbol: string
|
|
|
+ decimals: number
|
|
|
+ price: string
|
|
|
+ }
|
|
|
+ mintB: {
|
|
|
+ address: string
|
|
|
+ symbol: string
|
|
|
+ decimals: number
|
|
|
+ price: string
|
|
|
+ }
|
|
|
+ }
|
|
|
+ totalDeposit: string
|
|
|
+ upperTick: number
|
|
|
+ lowerTick: number
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
export function listenOpenPosition(
|
|
|
@@ -49,6 +86,57 @@ export function listenOpenPosition(
|
|
|
const programKey = new PublicKey(opts.programId)
|
|
|
const logIncludes = opts.logIncludes ?? []
|
|
|
|
|
|
+ // 通过 API 获取 position 详细信息
|
|
|
+ async function fetchPositionDetails(
|
|
|
+ positionAddress: string
|
|
|
+ ): Promise<OpenPositionEvent['positionDetails'] | undefined> {
|
|
|
+ try {
|
|
|
+ const url = `https://api2.byreal.io/byreal/api/dex/v2/position/detail?address=${positionAddress}`
|
|
|
+ const response = await fetch(url)
|
|
|
+ if (!response.ok) {
|
|
|
+ return undefined
|
|
|
+ }
|
|
|
+
|
|
|
+ const data = (await response.json()) as PositionApiResponse
|
|
|
+ if (data.retCode !== 0 || !data.result?.data) {
|
|
|
+ return undefined
|
|
|
+ }
|
|
|
+ const positionData = data.result.data
|
|
|
+ if (!positionData) {
|
|
|
+ return undefined
|
|
|
+ }
|
|
|
+
|
|
|
+ const pool = positionData.pool
|
|
|
+ if (!pool) {
|
|
|
+ return undefined
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ positionAddress: positionData.positionAddress,
|
|
|
+ providerAddress: positionData.providerAddress,
|
|
|
+ nftMintAddress: positionData.nftMintAddress,
|
|
|
+ poolAddress: pool.poolAddress,
|
|
|
+ mintA: {
|
|
|
+ address: pool.mintA.address,
|
|
|
+ symbol: pool.mintA.symbol,
|
|
|
+ decimals: pool.mintA.decimals,
|
|
|
+ price: pool.mintA.price
|
|
|
+ },
|
|
|
+ mintB: {
|
|
|
+ address: pool.mintB.address,
|
|
|
+ symbol: pool.mintB.symbol,
|
|
|
+ decimals: pool.mintB.decimals,
|
|
|
+ price: pool.mintB.price
|
|
|
+ },
|
|
|
+ totalDeposit: positionData.totalDeposit,
|
|
|
+ upperTick: positionData.upperTick,
|
|
|
+ lowerTick: positionData.lowerTick
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ return undefined
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
const cb: LogsCallback = async (logs, ctx) => {
|
|
|
const lines = logs.logs ?? []
|
|
|
if (logIncludes.length && !logIncludes.every((k) => lines.some((l) => l.includes(k)))) return
|
|
|
@@ -61,58 +149,42 @@ export function listenOpenPosition(
|
|
|
|
|
|
const accounts = tx
|
|
|
? (() => {
|
|
|
- const keys = tx.transaction.message.getAccountKeys()
|
|
|
- const lookup = keys.accountKeysFromLookups
|
|
|
- const all = [
|
|
|
- ...keys.staticAccountKeys,
|
|
|
- ...(lookup?.writable ?? []),
|
|
|
- ...(lookup?.readonly ?? [])
|
|
|
- ]
|
|
|
- return all.map((k) => k.toBase58())
|
|
|
+ const keys = tx.transaction.message.staticAccountKeys
|
|
|
+ return keys.map((k) => k.toBase58())
|
|
|
})()
|
|
|
: undefined
|
|
|
|
|
|
- const tokenMints = tx?.meta?.postTokenBalances?.map((b) => b.mint) ?? tx?.meta?.preTokenBalances?.map((b) => b.mint) ?? []
|
|
|
- const uniqTokenMints = Array.from(new Set(tokenMints))
|
|
|
- const nftMint = uniqTokenMints[0]
|
|
|
-
|
|
|
- const positionFromNft =
|
|
|
- nftMint && opts.getPositionInfoByNftMint
|
|
|
- ? await opts.getPositionInfoByNftMint(nftMint).then((r) =>
|
|
|
- r
|
|
|
- ? {
|
|
|
- nftMint,
|
|
|
- positionAccount: r.positionAccount,
|
|
|
- dataBase64: r.dataBase64,
|
|
|
- tickLower: r.tickLower,
|
|
|
- tickUpper: r.tickUpper
|
|
|
- }
|
|
|
- : undefined
|
|
|
- )
|
|
|
- : undefined
|
|
|
-
|
|
|
- // 从 innerInstructions 里找出本次交易新建的 position 账户(owner=programId)
|
|
|
- const positionAccount = (() => {
|
|
|
- const inners = tx?.meta?.innerInstructions ?? []
|
|
|
- for (const inner of inners) {
|
|
|
- for (const ix of inner.instructions) {
|
|
|
- const pix = ix as Partial<ParsedInstruction>
|
|
|
- if (!pix.parsed || pix.program !== 'system') continue
|
|
|
- const parsedAny = pix.parsed as unknown
|
|
|
- if (typeof parsedAny !== 'object' || parsedAny === null) continue
|
|
|
- const p = parsedAny as { type?: unknown; info?: unknown }
|
|
|
- if (p.type !== 'createAccount') continue
|
|
|
- const info = p.info as { owner?: unknown; newAccount?: unknown } | undefined
|
|
|
- if (info?.owner === opts.programId && typeof info?.newAccount === 'string') return info.newAccount
|
|
|
- }
|
|
|
+ // 从 innerInstructions 中获取 positionAccount
|
|
|
+ // 直接使用第一个 inner instruction 的第一个指令的 accounts[1]
|
|
|
+ let positionAccount: string | undefined
|
|
|
+ let positionAccountIndex: number | undefined
|
|
|
+
|
|
|
+ if (tx?.meta?.innerInstructions?.[0]?.instructions?.[0]?.accounts?.[1] !== undefined && accounts) {
|
|
|
+ const accountIndex = tx.meta.innerInstructions[0].instructions[0].accounts[1]
|
|
|
+ if (typeof accountIndex === 'number' && accounts[accountIndex]) {
|
|
|
+ positionAccount = accounts[accountIndex]
|
|
|
+ positionAccountIndex = accountIndex
|
|
|
}
|
|
|
- return undefined
|
|
|
- })()
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果没找到,使用默认索引 7 作为后备
|
|
|
+ if (!positionAccount && accounts && accounts.length > 7) {
|
|
|
+ positionAccount = accounts[6]
|
|
|
+ positionAccountIndex = 6
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果有 positionAccount,通过 API 获取详细信息
|
|
|
+ // 延迟100秒后获取,因为positionAccount可能还没被写入
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 100 * 1000))
|
|
|
+
|
|
|
+ const positionDetails = positionAccount ? await fetchPositionDetails(positionAccount) : undefined
|
|
|
+ if (!positionDetails) {
|
|
|
+ console.log('positionAccountIndex', positionAccountIndex)
|
|
|
+ // console.log('找不到地址', JSON.stringify(tx, null, 2))
|
|
|
+ console.log('找不到地址')
|
|
|
+ console.log('accounts', accounts)
|
|
|
+ console.log('tx', logs.signature)
|
|
|
|
|
|
- let positionAccountData: string | undefined
|
|
|
- if (positionAccount) {
|
|
|
- const acc = await conn.getAccountInfo(new PublicKey(positionAccount), txFinality)
|
|
|
- if (acc?.data) positionAccountData = Buffer.from(acc.data).toString('base64')
|
|
|
}
|
|
|
|
|
|
onEvent({
|
|
|
@@ -120,12 +192,8 @@ export function listenOpenPosition(
|
|
|
slot: ctx.slot,
|
|
|
blockTime: tx?.blockTime ?? null,
|
|
|
programId: opts.programId,
|
|
|
- logs: lines,
|
|
|
- accounts,
|
|
|
- tokenMints: uniqTokenMints.length ? uniqTokenMints : undefined,
|
|
|
positionAccount,
|
|
|
- positionAccountDataBase64: positionAccountData,
|
|
|
- positionFromNft
|
|
|
+ positionDetails
|
|
|
})
|
|
|
}
|
|
|
|