Created
November 5, 2024 21:58
-
-
Save rubpy/8bdfa5baaa2c461fe5d83a5b9e42deb9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import web3 from "@solana/web3.js"; | |
import * as BufferLayout from "@solana/buffer-layout"; | |
import QuickLRU from "quick-lru"; | |
//////////////////////////////////////////////////////////////////////////////// | |
export class BorshBoolean extends BufferLayout.Layout<boolean> { | |
constructor(property?: string) { | |
super(1, property); | |
} | |
decode(b: Buffer, offset = 0): boolean { | |
return !!Buffer.from(b.buffer, b.byteOffset, b.length)[offset]; | |
} | |
encode(src: boolean, b: Buffer, offset = 0): number { | |
Buffer.from(b.buffer, b.byteOffset, b.length).writeUInt8(src ? 1 : 0, offset); | |
return this.span; | |
} | |
} | |
export class BorshUInt64LE extends BufferLayout.Layout<bigint> { | |
constructor(property?: string) { | |
super(8, property); | |
} | |
decode(b: Buffer, offset = 0): bigint { | |
return Buffer.from(b.buffer, b.byteOffset, b.length).readBigUInt64LE(offset); | |
} | |
encode(src: bigint, b: Buffer, offset = 0): number { | |
Buffer.from(b.buffer, b.byteOffset, b.length).writeBigUInt64LE(BigInt(src), offset); | |
return this.span; | |
} | |
} | |
export class BorshInt64LE extends BufferLayout.Layout<bigint> { | |
constructor(property?: string) { | |
super(8, property); | |
} | |
decode(b: Buffer, offset = 0): bigint { | |
return Buffer.from(b.buffer, b.byteOffset, b.length).readBigInt64LE(offset); | |
} | |
encode(src: bigint, b: Buffer, offset = 0): number { | |
Buffer.from(b.buffer, b.byteOffset, b.length).writeBigInt64LE(BigInt(src), offset); | |
return this.span; | |
} | |
} | |
class BorshPublicKey extends BufferLayout.Layout<web3.PublicKey> { | |
constructor(property?: string) { | |
super(32, property); | |
} | |
decode(b: Uint8Array, offset?: number): web3.PublicKey { | |
offset = offset || 0; | |
const span = this.getSpan(b, offset); | |
return new web3.PublicKey( | |
Buffer.from(b.buffer, b.byteOffset, b.length).subarray(offset, offset + span), | |
); | |
} | |
encode(src: web3.PublicKey, b: Uint8Array, offset?: number): number { | |
offset = offset || 0; | |
const dstBuf = Buffer.from(b.buffer, b.byteOffset, b.length); | |
const srcBuf = src.toBuffer(); | |
return srcBuf.copy(dstBuf, offset); | |
} | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
export type PfTradeEventLayout = { | |
mint: web3.PublicKey; | |
solAmount: bigint; | |
tokenAmount: bigint; | |
isBuy: boolean; | |
user: web3.PublicKey; | |
timestamp: bigint; | |
virtualSolReserves: bigint; | |
virtualTokenReserves: bigint; | |
realSolReserves: bigint; | |
realTokenReserves: bigint; | |
}; | |
export const PfTradeEventLayout = BufferLayout.struct<PfTradeEventLayout>([ | |
new BorshPublicKey("mint"), | |
new BorshUInt64LE("solAmount"), | |
new BorshUInt64LE("tokenAmount"), | |
new BorshBoolean("isBuy"), | |
new BorshPublicKey("user"), | |
new BorshInt64LE("timestamp"), | |
new BorshUInt64LE("virtualSolReserves"), | |
new BorshUInt64LE("virtualTokenReserves"), | |
new BorshUInt64LE("realSolReserves"), | |
new BorshUInt64LE("realTokenReserves"), | |
]); | |
export const PF_PROGRAM_ID = new web3.PublicKey("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"); | |
export const PF_LOG_PREFIX = `Program ${PF_PROGRAM_ID.toBase58()}`; | |
export const PF_LOG_SUCCESS = `${PF_LOG_PREFIX} success`; | |
export const PF_LOG_DATA_PREFIX = "Program data: "; | |
export const PF_LOG_TRADE_IDL_DISCRIMINATOR = Uint8Array.from([0xbd, 0xdb, 0x7f, 0xd3, 0x4e, 0xe6, 0x61, 0xee]); | |
export const PF_LOG_TRADE_DATA_DECODED_LENGTH = PF_LOG_TRADE_IDL_DISCRIMINATOR.byteLength + PfTradeEventLayout.span; // 129 | |
export const PF_LOG_TRADE_DATA_ENCODED_LENGTH = ((4 * (PF_LOG_TRADE_DATA_DECODED_LENGTH / 3)) + 3) & ~3; // 172 | |
export const PF_LOG_TRADE_TOTAL_LENGTH = PF_LOG_DATA_PREFIX.length + PF_LOG_TRADE_DATA_ENCODED_LENGTH; // 186 | |
export const PF_LOG_TRADE_DATA_ENCODED_PREFIX = Buffer.from(PF_LOG_TRADE_IDL_DISCRIMINATOR).toString('base64').slice(0, Math.floor(4 * (PF_LOG_TRADE_IDL_DISCRIMINATOR.byteLength / 3))); // "vdt/007mYe" | |
export const PF_LOG_TRADE_DATA_ENCODED_PREFIX_OFFSET = PF_LOG_DATA_PREFIX.length; // 14 | |
export function findPfTradeInLogs(logs: string[]): PfTradeEventLayout | null { | |
const logsLength = logs.length; | |
if (!logs || logsLength < 3) { | |
return null; | |
} | |
for (let idx = 1; idx < logsLength; ++idx) { | |
if (logs[idx].length !== PF_LOG_TRADE_TOTAL_LENGTH | |
|| !logs[idx].startsWith(PF_LOG_TRADE_DATA_ENCODED_PREFIX, PF_LOG_TRADE_DATA_ENCODED_PREFIX_OFFSET)) { | |
continue; | |
} | |
if (logs[idx - 1] !== PF_LOG_SUCCESS | |
&& (idx !== (logsLength - 1) && !logs[idx + 1].startsWith(PF_LOG_PREFIX))) { | |
continue; | |
} | |
try { | |
const rawData = Buffer.from(logs[idx].slice(PF_LOG_TRADE_DATA_ENCODED_PREFIX_OFFSET), "base64"); | |
return PfTradeEventLayout.decode(Buffer.from(rawData.buffer, rawData.byteOffset + 8, rawData.byteLength - 8)); | |
} catch (e) { | |
return null; | |
} | |
} | |
return null; | |
} | |
export const PF_BONDING_CURVE_SEED = Buffer.from("bonding-curve"); | |
export function findPfBondingCurveAddress(tokenMint: web3.PublicKey): web3.PublicKey { | |
return web3.PublicKey.findProgramAddressSync([ | |
PF_BONDING_CURVE_SEED, | |
tokenMint.toBuffer(), | |
], PF_PROGRAM_ID)[0]; | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
export const OPAQUE_SIGNATURE = "1111111111111111111111111111111111111111111111111111111111111111"; | |
(async (rpcUrl: string) => { | |
const conn = new web3.Connection(rpcUrl, "confirmed"); | |
const seenSignatures = new QuickLRU({ maxSize: 256 }); | |
conn.onLogs(PF_PROGRAM_ID, (txLogs: web3.Logs, ctx: web3.Context) => { | |
if ((!ctx || !txLogs || txLogs.err !== null || !txLogs.logs || txLogs.logs.length < 3) | |
|| (!txLogs.signature || txLogs.signature === OPAQUE_SIGNATURE)) { | |
return; | |
} | |
if (seenSignatures.has(txLogs.signature)) return; | |
seenSignatures.set(txLogs.signature, true); | |
const trade = findPfTradeInLogs(txLogs.logs); | |
if (!trade) { | |
return; | |
} | |
// NOTE: this is optional; showing how the address can be derived (even | |
// though it is not present in the logged event itself). | |
(trade as any).bondingCurve = findPfBondingCurveAddress(trade.mint); | |
console.log(ctx.slot, txLogs.signature); | |
console.log(JSON.stringify( | |
Object.fromEntries(Object.entries(trade).map(([k, v]) => | |
[k, v !== null && (typeof v === "object" || typeof v === "bigint") ? String(v) : v])), | |
null, 2, | |
)); | |
console.log(); | |
}); | |
})(process.env.SOL_RPC_URL || "https://mainnet.helius-rpc.com/?api-key=00000000-0000-0000-0000-000000000000"); | |
//////////////////////////////////////////////////////////////////////////////// | |
/* ---------- Output (example) ---------- | |
299711471 nyxck8KdQG3tADVg5aVjL27x5jirN8PbQnADkq1uUgRajrzacofAyLiaYzWH79VHumVQbgtTTJ81bnFDNxaem78 | |
{ | |
"mint": "vCWfJEK7usVaLbvrVwkzYAcEfs2ME7UneAtEfkVpump", | |
"solAmount": "7337100", | |
"tokenAmount": "261347508837", | |
"isBuy": false, | |
"user": "DNngDZZ46FMreCGvdvCywopa54QaE3qA5rQzzbRhxvDM", | |
"timestamp": "1730842893", | |
"virtualSolReserves": "30058031026", | |
"virtualTokenReserves": "1070928431798389", | |
"realSolReserves": "58031026", | |
"realTokenReserves": "791028431798389", | |
"bondingCurve": "D7rTBjaBqPmXvnhfq24uk39Poyt67XfU1df9Yj4MUopi" | |
} | |
299711471 2MTdJKZboSM4ab3AHvTvqXdFirDBvoTn8wDKCM9dbjPJ3u33WNBhuCMKi8s44xXbRS4hvp2PCQsN22Aifh6bxeNt | |
{ | |
"mint": "2py6SsiJgonGmfaCoVEBPbLLEs2YSpyvNmM1XUjWpump", | |
"solAmount": "4872906", | |
"tokenAmount": "79937904407", | |
"isBuy": false, | |
"user": "qzdBcZ4kAnEfTJKUvnhwVJuMWnx9A8F5m57w9yoTF67", | |
"timestamp": "1730842893", | |
"virtualSolReserves": "44294953943", | |
"virtualTokenReserves": "726719347731142", | |
"realSolReserves": "14294953943", | |
"realTokenReserves": "446819347731142", | |
"bondingCurve": "5StpJ5SAS19Y9vrWPupgpSfHwJxSL3rUbZHbQCv6A84E" | |
} | |
... | |
299711472 2Hq2YVJuLgonaLoLcCzLi9VLhAzEFhBzDU8uyyTpjKunxB1Cs3ZGZqdJ2GXN1XTefgqBRwsro66GmCD8FTWzwLtm | |
{ | |
"mint": "9Ri9NfFMcnMgFUXqNQMudnRMZtY4nTcKSHNUUMM84Ztu", | |
"solAmount": "20000000", | |
"tokenAmount": "221005693641", | |
"isBuy": true, | |
"user": "59V6rEqH9xVSpeUtfttQ4uVVJhcdvMfpaKYsWTsyDEL9", | |
"timestamp": "1730842894", | |
"virtualSolReserves": "53222793380", | |
"virtualTokenReserves": "587906012730295", | |
"realSolReserves": "23222793380", | |
"realTokenReserves": "376106012730295", | |
"bondingCurve": "3rZVMm7vSCBPDvy33ggFTAnCwYXrdGRtJmN9scWN4TKn" | |
} | |
299711472 5Rz7Dbv2mKvfxakgLxuwgzcoo2jiWqtELaVD2UEs1NbHWiQ7AQw5CZ44asQZYzki2CR4wgomrK5bDSFHMdmYaBv1 | |
{ | |
"mint": "3byxQJYoFdCRX1qRk3EW8hKWvin5tEgik3KozLXepump", | |
"solAmount": "1868084", | |
"tokenAmount": "11308008764", | |
"isBuy": false, | |
"user": "feJ8dgSw4ag9x74kQ4GffrfwSmor9oQ5FAJzaWQbQMC", | |
"timestamp": "1730842894", | |
"virtualSolReserves": "72922262219", | |
"virtualTokenReserves": "441428983109251", | |
"realSolReserves": "42922262219", | |
"realTokenReserves": "161528983109251", | |
"bondingCurve": "AmEQFZnkGByjmeMxaTsqCWfDr3QZ2oFLB2jkMkg8PkF1" | |
} | |
299711473 31vbPUxM1g1WMfMWZ2g3etZEVMhADztDT2etNBrnG91XuvPsELv9Ly9iHhz1d3ny5emW9ZBQqpE4btCh3sTtL6LZ | |
{ | |
"mint": "B7T6NYtb1VvzCbFCPkEGepFCxPnJNb8V9tQiJSu9pump", | |
"solAmount": "10088406", | |
"tokenAmount": "61349693251", | |
"isBuy": true, | |
"user": "9CDWeVFvNkho6r9jTPDAutTfDMavd24hGfQwoNpVL7xc", | |
"timestamp": "1730842894", | |
"virtualSolReserves": "72760497627", | |
"virtualTokenReserves": "442410388536965", | |
"realSolReserves": "42760497627", | |
"realTokenReserves": "162510388536965", | |
"bondingCurve": "DcAWVrFjXsH2oyifcnBCmt8eGN4dsG2AWrPyv45d83Ay" | |
} | |
299711473 sb5e3Eq5p9Yq42u27LtqCLPEfu3DpP5jfJ6X64T66T8JhzgfcUh6HYM3mRPkL8v4AeD1WNzUMgA2LFAPypJcasc | |
{ | |
"mint": "Aj95ceHTnNEHBNmSbrUNhksuAcrWjPhR8pAtdMCypump", | |
"solAmount": "244284670", | |
"tokenAmount": "3296440108891", | |
"isBuy": true, | |
"user": "9sQmz1ufJcSJbr45R1pu3XNWEjEfLuvjuTTdWCV9sp2h", | |
"timestamp": "1730842894", | |
"virtualSolReserves": "48963458479", | |
"virtualTokenReserves": "657429050641188", | |
"realSolReserves": "18963458479", | |
"realTokenReserves": "377529050641188", | |
"bondingCurve": "CQTyh7ML1QExjrZCv9XPDFMUn3gGrUyJGJwG1RvW8c9Z" | |
} | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment