Skip to content

Instantly share code, notes, and snippets.

@rubpy
Created February 19, 2025 23:31
Show Gist options
  • Select an option

  • Save rubpy/b1cce7d14d8dc22e9ab9f46d49fa7513 to your computer and use it in GitHub Desktop.

Select an option

Save rubpy/b1cce7d14d8dc22e9ab9f46d49fa7513 to your computer and use it in GitHub Desktop.
import web3 from "@solana/web3.js";
import * as BufferLayout from "@solana/buffer-layout";
import * as spl from "@solana/spl-token";
import QuickLRU from "quick-lru";
////////////////////////////////////////////////////////////////////////////////
function onPfTransaction(
signature: string,
slot: number,
rawEvents: PfEvent[],
_txLogs: web3.Logs,
_conn: web3.Connection,
) {
const events = enhancePfEvents(rawEvents);
// Pretty printing...
console.log(
slot, signature,
mapInPlaceObjectContainingBigInts(events, 15),
"\n",
);
}
/*
╔═════════════════════════════ Example output ═════════════════════════════╗
║ 321786059 2wLFDwGuDyKDz13Qxfr6zxq5kyyDJiN1gv2TnHn1Me9NQ217GmpXeXCvLpLyvYUcaEuH6wykTY9uFzw3jreeckkF [
║ {
║ mint: 'ABKC9TX19KVN2zvK6Qvu38hp8EhF6LxmAWhwmSLLpump',
║ solAmount: '125426874',
║ tokenAmount: '3698978768369',
║ isBuy: true,
║ user: '9a51T5oXWpqa59XDERetS5ELaG8sHPwVuWYQq47BusHB',
║ timestamp: '1740007135',
║ virtualSolReserves: '33100858036',
║ virtualTokenReserves: '972482343699938',
║ realSolReserves: '3100858036',
║ realTokenReserves: '692582343699938',
║ type: 'pf:event:TradeEvent',
║ bondingCurve: '5yHhVoiRhHxeMtHRU8NzachEy6WdSYWzqsFPZaZwcZF6',
║ associatedBondingCurve: '3nGeayALRL9KgTWu4XTLCoaoAzf8gg3TUXLtDicru3ku',
║ bondingProgressBefore: '0.122131347656250',
║ bondingProgressAfter: '0.126770019531250',
║ kothProgressBefore: '0.144104003906250',
║ kothProgressAfter: '0.149597167968750',
║ priceBefore: '0.000000033780027',
║ priceAfter: '0.000000034037490',
║ priceAverage: '0.000000033908514',
║ pricePaid: '0.000000033908514',
║ solAmountParsed: '0.125426874000000',
║ tokenAmountParsed: '3698978.768368999939412'
║ }
║ ]
║ . . .
║ 321786060 2aT86SkygKpCWCQGGBB6JNEotqVPr8CZoCWsGE58RNJ7rwXRuRgw9udrdVFaA2Cv4gJwJcQoZpFzUUJ3sM3acx7Z [
║ {
║ mint: '9oxe4qCxAQPtC44kR1Rr6AsSh54j8PAbvkaNwoqepump',
║ solAmount: '10000001',
║ tokenAmount: '38591468667',
║ isBuy: true,
║ user: '4nppHvT9meLhNGAcYW27BVEPybNL7deEfr2xT1xKPi95',
║ timestamp: '1740007135',
║ virtualSolReserves: '91335291845',
║ virtualTokenReserves: '352437713868769',
║ realSolReserves: '61335291845',
║ realTokenReserves: '72537713868769',
║ type: 'pf:event:TradeEvent',
║ bondingCurve: 'ArxiKYrHPGFVjbQVKuuMzPoxMvL9DMndKkSXHs1EZznV',
║ associatedBondingCurve: 'H5DCDjR4WdFJYcGvkyvk7cWoeE44Y5C6Ungq6YVsq4Gd',
║ bondingProgressBefore: '0.908508300781250',
║ bondingProgressAfter: '0.908569335937500',
║ kothProgressBefore: '1.000000000000000',
║ kothProgressAfter: '1.000000000000000',
║ priceBefore: '0.000000259096258',
║ priceAfter: '0.000000259153003',
║ priceAverage: '0.000000259124629',
║ pricePaid: '0.000000259124655',
║ solAmountParsed: '0.010000001000000',
║ tokenAmountParsed: '38591.468667000001005'
║ },
║ {
║ mint: '9oxe4qCxAQPtC44kR1Rr6AsSh54j8PAbvkaNwoqepump',
║ solAmount: '10000000',
║ tokenAmount: '38591468667',
║ isBuy: false,
║ user: '4nppHvT9meLhNGAcYW27BVEPybNL7deEfr2xT1xKPi95',
║ timestamp: '1740007135',
║ virtualSolReserves: '91325291845',
║ virtualTokenReserves: '352476305337436',
║ realSolReserves: '61325291845',
║ realTokenReserves: '72576305337436',
║ type: 'pf:event:TradeEvent',
║ bondingCurve: 'ArxiKYrHPGFVjbQVKuuMzPoxMvL9DMndKkSXHs1EZznV',
║ associatedBondingCurve: 'H5DCDjR4WdFJYcGvkyvk7cWoeE44Y5C6Ungq6YVsq4Gd',
║ bondingProgressBefore: '0.908569335937500',
║ bondingProgressAfter: '0.908508300781250',
║ kothProgressBefore: '1.000000000000000',
║ kothProgressAfter: '1.000000000000000',
║ priceBefore: '0.000000259153003',
║ priceAfter: '0.000000259096258',
║ priceAverage: '0.000000259124629',
║ pricePaid: '0.000000259124629',
║ solAmountParsed: '0.010000000000000',
║ tokenAmountParsed: '38591.468667000001005'
║ }
║ ]
║ . . .
║ 321786065 3VR7yKT84PB9Vnm9ABbmHL9nJsFamUtsD6V7B4pZpwNPjErKRJ4PXVjw2snEskk5KVJv3jjf2BLekYukts9XoSZq [
║ {
║ name: 'shiba',
║ symbol: 'SHIBA',
║ uri: 'https://metadata.pumplify.eu/data/QmTYacvCKbhDtA76cG9CfpGYQ5bNhBXSwPPXEJc3Q2sMYQ',
║ mint: '91QgGcNGFURc99eEAmXk7zKUJZ4fpMead5SGajuZbes2',
║ bondingCurve: 'JXbnDavBeV5s1RkW5rMW3bLVAR2ZWVXssLnXMFPD3t2',
║ user: 'C88rn1cz1PcRJFh44TuynaWbnRis4xuBRi65sD1Nwxog',
║ type: 'pf:event:CreateEvent'
║ },
║ {
║ mint: '91QgGcNGFURc99eEAmXk7zKUJZ4fpMead5SGajuZbes2',
║ solAmount: '3000000000',
║ tokenAmount: '97545454545454',
║ isBuy: true,
║ user: 'C88rn1cz1PcRJFh44TuynaWbnRis4xuBRi65sD1Nwxog',
║ timestamp: '1740007137',
║ virtualSolReserves: '33000000000',
║ virtualTokenReserves: '975454545454546',
║ realSolReserves: '3000000000',
║ realTokenReserves: '695554545454546',
║ type: 'pf:event:TradeEvent',
║ bondingCurve: 'JXbnDavBeV5s1RkW5rMW3bLVAR2ZWVXssLnXMFPD3t2',
║ associatedBondingCurve: 'BgYzNTsTYhgPkmGK1chZju8shTaPKKMzzeQWxbbTcKWK',
║ bondingProgressBefore: '0.000000000000000',
║ bondingProgressAfter: '0.123046875000000',
║ kothProgressBefore: '0.000000000000000',
║ kothProgressAfter: '0.145141601562500',
║ priceBefore: '0.000000027958993',
║ priceAfter: '0.000000033830382',
║ priceAverage: '0.000000030754893',
║ pricePaid: '0.000000030754893',
║ solAmountParsed: '3.000000000000000',
║ tokenAmountParsed: '97545454.545453995466232'
║ }
║ ]
║ 321786065 3p8G6Ksx8tjxcyLTUvyfro1eMmxzWovSRpVYhK7rUxeNE7JMNGhoHTBCEwYSPDJY9ykvSjdGo6NJ1Ke5eMFzCG2i [
║ {
║ user: 'FjSNewStC9txkREbMMrkyRRMivnewips45reKoxtXd76',
║ mint: 'MjmMdCDWPYZQndpyNGY7GN6HwYCtjUPRjCDWvSDpump',
║ bondingCurve: 'A3co8MXGjQgtgrtJxV4iJenSFttdNtsEzm2Dz3pcAWgG',
║ timestamp: '1740007137',
║ type: 'pf:event:CompleteEvent'
║ },
║ {
║ mint: 'MjmMdCDWPYZQndpyNGY7GN6HwYCtjUPRjCDWvSDpump',
║ solAmount: '248293644',
║ tokenAmount: '605604460987',
║ isBuy: true,
║ user: 'FjSNewStC9txkREbMMrkyRRMivnewips45reKoxtXd76',
║ timestamp: '1740007137',
║ virtualSolReserves: '115005359189',
║ virtualTokenReserves: '279900000000000',
║ realSolReserves: '85005359189',
║ realTokenReserves: '0',
║ type: 'pf:event:TradeEvent',
║ bondingCurve: 'A3co8MXGjQgtgrtJxV4iJenSFttdNtsEzm2Dz3pcAWgG',
║ associatedBondingCurve: 'EzaF55dKqT5CEueF3icP5CBtHQv11rHS4kpFbXiSywB',
║ bondingProgressBefore: '0.999267578125000',
║ bondingProgressAfter: '1.000000000000000',
║ kothProgressBefore: '1.000000000000000',
║ kothProgressAfter: '1.000000000000000',
║ priceBefore: '0.000000409107924',
║ priceAfter: '0.000000410880169',
║ priceAverage: '0.000000409993089',
║ pricePaid: '0.000000409993090',
║ solAmountParsed: '0.248293644000000',
║ tokenAmountParsed: '605604.460987000027671'
║ }
║ ]
╚══════════════════════════════════════════════════════════════════════════════╝
*/
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
async function main(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 events = findPfEventsInLogs(txLogs.logs);
if (!events || !events.length) {
return;
}
onPfTransaction(txLogs.signature, ctx.slot, events, txLogs, conn);
});
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
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;
}
};
export 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 class BorshString extends BufferLayout.Layout<string> {
constructor(property?: string) {
super(-1, property);
}
get minSpan() { return 4; }
getSpan(b?: Uint8Array, offset?: number): number {
offset = offset || 0;
if (!b || b.byteLength < offset + 4) {
throw new RangeError("invalid string encoding");
}
const len = Buffer.from(b.buffer, b.byteOffset, b.length).readUInt32LE(offset);
if ((offset + len) > b.byteLength) {
throw new RangeError("encoding overruns Buffer");
}
return len + 4;
}
decode(b: Uint8Array, offset?: number): string {
offset = offset || 0;
const span = this.getSpan(b, offset);
return span <= 4 ? "" : Buffer.from(b.buffer, b.byteOffset, b.length).subarray(offset + 4, offset + span).toString("utf-8");
}
encode(src: string, b: Uint8Array, offset?: number): number {
if (typeof src !== "string") {
src = String(src);
}
offset = offset || 0;
const srcBuf = Buffer.from(src, "utf-8");
const span = srcBuf.length;
if (span > 0xffffffff) {
throw new RangeError("unsupported data size");
}
if ((offset + span + 4) > b.length) {
throw new RangeError("encoding overruns Buffer");
}
const dstBuf = Buffer.from(b.buffer, b.byteOffset, b.length);
dstBuf.writeUInt32LE(span, offset);
srcBuf.copy(dstBuf, offset + 4);
return span + 4;
}
};
export type BufferLayoutStructureWithMinSpan<T>
= (BufferLayout.Structure<T> & { get minSpan(): number });
export function bufferLayoutStructureWithMinSpan<T>(structure: BufferLayout.Structure<T>): BufferLayoutStructureWithMinSpan<T> {
return Object.defineProperty(structure, "minSpan", {
get: function () {
if (!this || !Array.isArray(this.fields)) {
return -1;
}
let minSpan = 0;
for (const field of this.fields) {
if (!field || typeof field.span !== "number") {
return -1;
}
if (field.span >= 0) {
minSpan += field.span;
} else if (typeof field.minSpan === "number" && field.minSpan >= 0) {
minSpan += field.minSpan;
} else {
return -1;
}
}
return minSpan;
},
}) as BufferLayoutStructureWithMinSpan<T>;
};
export function decodeStructure<T, U extends any = T, F extends ((value: T) => U) = ((value: T) => U)>(
layout: BufferLayout.Structure<T>,
buffer: Buffer,
discriminator?: Uint8Array | null,
offset?: number,
mapFn?: F,
): U | null {
if ((layout === null || typeof layout !== "object" || !layout.span)
|| (buffer === null || typeof buffer !== "object")) {
return null;
}
offset = typeof offset === "number" && offset >= 0 ? offset : 0;
if (offset >= buffer.byteLength
|| (buffer.byteLength < (offset + (discriminator ? discriminator.byteLength : 0) + layout.span))
|| ((discriminator && discriminator.byteLength > 0) && !buffer.subarray(offset, (offset += discriminator.byteLength)).equals(discriminator))) {
return null;
}
const value = layout.decode(buffer, offset);
return value && mapFn ? mapFn(value) : (value as any as U);
};
////////////////////////////////////////////////////////////////////////////////
export type PfType = ((<T>(instance: T) => T) & {
readonly symbol: symbol;
readonly [Symbol.hasInstance]: ((instance: any) => boolean);
});
export type PfTypeConstructor = (((symbol: symbol) => PfType) & {
readonly symbol: symbol;
readonly unknown: symbol;
readonly of: (instance: any) => symbol;
readonly is: (type: PfType | symbol, instance: any) => boolean;
readonly as: <T>(type: PfType | symbol, instance: any) => T | null;
readonly tag: <T>(type: PfType | symbol, instance: T, extraSymbol?: symbol) => T;
});
export const PfType = Object.freeze(Object.defineProperties(function (symbol: symbol) {
return Object.freeze(Object.defineProperties(function <T>(instance: T) {
return PfType.tag(symbol, instance);
}, {
symbol: {
get() {
return this[PfType.symbol];
},
},
[PfType.symbol]: {
value: typeof symbol === "symbol" ? symbol : PfType.unknown,
},
toString: {
value: function () {
return Symbol.prototype.toString.call(
this && typeof this[PfType.symbol] === "symbol"
? this[PfType.symbol] : PfType.unknown,
);
},
},
[Symbol.hasInstance]: {
value(instance: any) {
return this && instance !== null && typeof instance === "object"
&& instance[PfType.symbol] === this[PfType.symbol];
}
},
[Symbol.toStringTag]: {
get() {
return Symbol.prototype.toString.call(
this && typeof this[PfType.symbol] === "symbol"
? this[PfType.symbol] : PfType.unknown,
);
},
}
}));
}, {
symbol: { value: Symbol("type") },
unknown: { value: Symbol("unknown") },
of: {
value(instance: any): symbol {
return instance !== null && typeof instance === "object"
&& typeof instance[PfType.symbol] === "symbol"
? instance[PfType.symbol] : PfType.unknown;
}
},
is: {
value(type: PfType | symbol, instance: any): boolean {
const instanceType
= instance !== null && typeof instance === "object"
&& typeof instance[PfType.symbol] === "symbol"
? instance[PfType.symbol] : PfType.unknown;
return instanceType === ((typeof type === "symbol" ? type : type[PfType.symbol]) || PfType.unknown);
}
},
as: {
value<T>(type: PfType | symbol, instance: any): T | null {
return PfType.is(type, instance) ? (instance as T) : null;
}
},
tag: {
value<T>(type: typeof PfType | symbol, instance: T, extraSymbol?: symbol): T {
Object.defineProperty(instance, PfType.symbol, {
value: (typeof type === "symbol" ? type : type[PfType.symbol]) || PfType.unknown,
// enumerable: true,
});
if (typeof extraSymbol === "symbol") {
Object.defineProperty(instance, extraSymbol, {
value: (typeof type === "symbol" ? type : type[PfType.symbol]) || PfType.unknown,
});
}
return instance;
}
},
})) as any as PfTypeConstructor;
export function createPfTypeWrapper<
E extends { [key: string]: (PfType | symbol) },
S extends symbol = symbol,
>(
symbol: S,
types: E,
): ((<T>(type: PfType, data: T) => T) & { readonly type: { [K in keyof E]: PfType }; readonly symbol: symbol; }) {
return Object.freeze(Object.defineProperties(function <T>(type: PfType, data: T) {
return PfType.tag(type, data, PfEvent.symbol);
}, {
symbol: { value: symbol },
type: {
value: Object.fromEntries(Object.keys(types).map(k => {
const v = types[k];
return [k, (typeof v === "symbol" ? PfType(v) : v)];
}))
},
[Symbol.hasInstance]: {
value(instance: any) {
return this && instance !== null && typeof instance === "object"
&& instance[symbol];
}
},
})) as any;
};
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
export interface PfAccountBondingCurve {
virtualTokenReserves: bigint;
virtualSolReserves: bigint;
realTokenReserves: bigint;
realSolReserves: bigint;
tokenTotalSupply: bigint;
complete: boolean;
};
export const PfAccountBondingCurveLayout = BufferLayout.struct<PfAccountBondingCurve>([
new BorshUInt64LE("virtualTokenReserves"),
new BorshUInt64LE("virtualSolReserves"),
new BorshUInt64LE("realTokenReserves"),
new BorshUInt64LE("realSolReserves"),
new BorshUInt64LE("tokenTotalSupply"),
new BorshBoolean("complete"),
]);
export const PF_ACCOUNT_BONDING_CURVE_DISCRIMINATOR = Uint8Array.from([0x17, 0xb7, 0xf8, 0x37, 0x60, 0xd8, 0xac, 0x60]); // `sha256("account:BondingCurve")[0:8]`
export const PF_ACCOUNT_BONDING_CURVE_TOTAL_SIZE = PF_ACCOUNT_BONDING_CURVE_DISCRIMINATOR.byteLength + PfAccountBondingCurveLayout.span;
export const PF_ACCOUNT_BONDING_CURVE_SEED = Buffer.from("bonding-curve");
////////////////////////////////////////////////////////////////////////////////
export interface PfEventCreate {
name: string;
symbol: string;
uri: string;
mint: web3.PublicKey;
bondingCurve: web3.PublicKey;
user: web3.PublicKey;
};
export const PfEventCreateLayout = bufferLayoutStructureWithMinSpan(BufferLayout.struct<PfEventCreate>([
new BorshString("name"),
new BorshString("symbol"),
new BorshString("uri"),
new BorshPublicKey("mint"),
new BorshPublicKey("bondingCurve"),
new BorshPublicKey("user"),
]));
export const PF_EVENT_CREATE_DISCRIMINATOR = Uint8Array.from([0x1b, 0x72, 0xa9, 0x4d, 0xde, 0xeb, 0x63, 0x76]); // `sha256("event:CreateEvent")[0:8]`
////////////////////////////////////////////////////////////////////////////////
export interface PfEventTrade {
mint: web3.PublicKey;
solAmount: bigint;
tokenAmount: bigint;
isBuy: boolean;
user: web3.PublicKey;
timestamp: bigint;
virtualSolReserves: bigint;
virtualTokenReserves: bigint;
realSolReserves: bigint;
realTokenReserves: bigint;
};
export const PfEventTradeLayout = BufferLayout.struct<PfEventTrade>([
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_EVENT_TRADE_DISCRIMINATOR = Uint8Array.from([0xbd, 0xdb, 0x7f, 0xd3, 0x4e, 0xe6, 0x61, 0xee]); // `sha256("event:TradeEvent")[0:8]`
////////////////////////////////////////////////////////////////////////////////
export interface PfEventComplete {
user: web3.PublicKey;
mint: web3.PublicKey;
bondingCurve: web3.PublicKey;
timestamp: bigint;
};
export const PfEventCompleteLayout = BufferLayout.struct<PfEventComplete>([
new BorshPublicKey("user"),
new BorshPublicKey("mint"),
new BorshPublicKey("bondingCurve"),
new BorshInt64LE("timestamp"),
]);
export const PF_EVENT_COMPLETE_DISCRIMINATOR = Uint8Array.from([0x5f, 0x72, 0x61, 0x9c, 0xd4, 0x2e, 0x98, 0x08]); // `sha256("event:CompleteEvent")[0:8]`
////////////////////////////////////////////////////////////////////////////////
export const PfAccount = createPfTypeWrapper(
Symbol("pf:account"),
{
BONDING_CURVE: Symbol("pf:account:BondingCurve"),
}
);
export type PfAccount = PfAccountBondingCurve;
export const PfEvent = createPfTypeWrapper(
Symbol("pf:event"),
{
CREATE: Symbol("pf:event:CreateEvent"),
TRADE: Symbol("pf:event:TradeEvent"),
COMPLETE: Symbol("pf:event:CompleteEvent"),
}
);
export type PfEvent = PfEventCreate | PfEventTrade | PfEventComplete;
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
export const PF_PROGRAM_ID = new web3.PublicKey("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P");
export const PF_CONFIG_DECIMALS_TOKEN = 6n;
export const PF_CONFIG_DECIMALS_TOKEN_PRODUCT = 10n ** PF_CONFIG_DECIMALS_TOKEN;
export const PF_CONFIG_DECIMALS_SOL = 9n;
export const PF_CONFIG_DECIMALS_SOL_PRODUCT = 10n ** PF_CONFIG_DECIMALS_SOL;
export const PF_CONFIG_DECIMALS_TOTAL_PRODUCT = PF_CONFIG_DECIMALS_TOKEN_PRODUCT * PF_CONFIG_DECIMALS_SOL_PRODUCT;
export const PF_CONFIG_DECIMALS_TOTAL_SHIFT_PRODUCT = PF_CONFIG_DECIMALS_TOTAL_PRODUCT * (PF_CONFIG_DECIMALS_SOL_PRODUCT / PF_CONFIG_DECIMALS_TOKEN_PRODUCT);
export const PF_CONFIG_INITIAL_REAL_TOKEN_RESERVES = 793100000000000n;
export const PF_CONFIG_INITIAL_VIRTUAL_TOKEN_RESERVES = 1073000000000000n;
export const PF_CONFIG_INITIAL_VIRTUAL_SOL_RESERVES = 30000000000n;
export const PF_CONFIG_TOKEN_TOTAL_SUPPLY = 1000000000000000n;
export const PF_CONFIG_KOTH_TARGET_FACTOR = 200000000000n;
export const PF_CONFIG_KOTH_TARGET = (
PF_CONFIG_INITIAL_VIRTUAL_TOKEN_RESERVES - BigInt(Math.trunc(
Math.sqrt(Number(
(PF_CONFIG_INITIAL_VIRTUAL_TOKEN_RESERVES
* PF_CONFIG_INITIAL_VIRTUAL_SOL_RESERVES
* PF_CONFIG_TOKEN_TOTAL_SUPPLY)
/ PF_CONFIG_KOTH_TARGET_FACTOR
))
))
); // 671814257481650n
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_EVENT_CREATE_DATA_DECODED_MIN_LENGTH = PF_EVENT_CREATE_DISCRIMINATOR.byteLength + (PfEventCreateLayout.minSpan || 0); // 116
export const PF_LOG_EVENT_CREATE_DATA_ENCODED_MIN_LENGTH = ((4 * (PF_LOG_EVENT_CREATE_DATA_DECODED_MIN_LENGTH / 3)) + 3) & ~3; // 156
export const PF_LOG_EVENT_CREATE_TOTAL_MIN_LENGTH = PF_LOG_DATA_PREFIX.length + PF_LOG_EVENT_CREATE_DATA_ENCODED_MIN_LENGTH; // 170
export const PF_LOG_EVENT_CREATE_DATA_ENCODED_PREFIX = Buffer.from(PF_EVENT_CREATE_DISCRIMINATOR).toString('base64').slice(0, Math.floor(4 * (PF_EVENT_CREATE_DISCRIMINATOR.byteLength / 3))); // "G3KpTd7rY3"
export const PF_LOG_EVENT_CREATE_DATA_ENCODED_PREFIX_OFFSET = PF_LOG_DATA_PREFIX.length; // 14
export const PF_LOG_EVENT_TRADE_DATA_DECODED_LENGTH = PF_EVENT_TRADE_DISCRIMINATOR.byteLength + PfEventTradeLayout.span; // 129
export const PF_LOG_EVENT_TRADE_DATA_ENCODED_LENGTH = ((4 * (PF_LOG_EVENT_TRADE_DATA_DECODED_LENGTH / 3)) + 3) & ~3; // 172
export const PF_LOG_EVENT_TRADE_TOTAL_LENGTH = PF_LOG_DATA_PREFIX.length + PF_LOG_EVENT_TRADE_DATA_ENCODED_LENGTH; // 186
export const PF_LOG_EVENT_TRADE_DATA_ENCODED_PREFIX = Buffer.from(PF_EVENT_TRADE_DISCRIMINATOR).toString('base64').slice(0, Math.floor(4 * (PF_EVENT_TRADE_DISCRIMINATOR.byteLength / 3))); // "vdt/007mYe"
export const PF_LOG_EVENT_TRADE_DATA_ENCODED_PREFIX_OFFSET = PF_LOG_DATA_PREFIX.length; // 14
export const PF_LOG_EVENT_COMPLETE_DATA_DECODED_LENGTH = PF_EVENT_COMPLETE_DISCRIMINATOR.byteLength + PfEventCompleteLayout.span; // 112
export const PF_LOG_EVENT_COMPLETE_DATA_ENCODED_LENGTH = ((4 * (PF_LOG_EVENT_COMPLETE_DATA_DECODED_LENGTH / 3)) + 3) & ~3; // 152
export const PF_LOG_EVENT_COMPLETE_TOTAL_LENGTH = PF_LOG_DATA_PREFIX.length + PF_LOG_EVENT_COMPLETE_DATA_ENCODED_LENGTH; // 166
export const PF_LOG_EVENT_COMPLETE_DATA_ENCODED_PREFIX = Buffer.from(PF_EVENT_COMPLETE_DISCRIMINATOR).toString('base64').slice(0, Math.floor(4 * (PF_EVENT_COMPLETE_DISCRIMINATOR.byteLength / 3))); // "X3JhnNQumA"
export const PF_LOG_EVENT_COMPLETE_DATA_ENCODED_PREFIX_OFFSET = PF_LOG_DATA_PREFIX.length; // 14
export function findPfEventsInLogs(
logs: string[],
parseWhilePredicate?: (events: PfEvent[], currentEvent: PfEvent, previousEvent: PfEvent | null, logs: string[]) => boolean,
): PfEvent[] {
const logsLength = logs.length;
if (!logs || logsLength < 3) {
return [];
}
const events = <PfEvent[]>[];
let previousEvent: PfEvent | null = null;
let currentEvent: PfEvent | null = null;
for (let idx = 1; idx < logsLength; ++idx) {
if (logs[idx].length >= PF_LOG_EVENT_CREATE_TOTAL_MIN_LENGTH && logs[idx].startsWith(PF_LOG_EVENT_CREATE_DATA_ENCODED_PREFIX, PF_LOG_EVENT_CREATE_DATA_ENCODED_PREFIX_OFFSET)) {
if (!(logs[idx - 1] === PF_LOG_SUCCESS || (idx !== (logsLength - 1) && logs[idx + 1].startsWith(PF_LOG_PREFIX)))) {
continue;
}
if ((currentEvent = decodeStructure(
PfEventCreateLayout,
Buffer.from(logs[idx].slice(PF_LOG_EVENT_CREATE_DATA_ENCODED_PREFIX_OFFSET), "base64"),
PF_EVENT_CREATE_DISCRIMINATOR,
0,
e => PfEvent.type.CREATE(e),
))) {
events.push(currentEvent);
if (parseWhilePredicate && !parseWhilePredicate(events, currentEvent, previousEvent, logs)) {
break;
}
previousEvent = currentEvent;
}
continue;
}
if (logs[idx].length >= PF_LOG_EVENT_TRADE_TOTAL_LENGTH && logs[idx].startsWith(PF_LOG_EVENT_TRADE_DATA_ENCODED_PREFIX, PF_LOG_EVENT_TRADE_DATA_ENCODED_PREFIX_OFFSET)) {
if (!(logs[idx - 1] === PF_LOG_SUCCESS || (idx !== (logsLength - 1) && logs[idx + 1].startsWith(PF_LOG_PREFIX)))) {
continue;
}
if ((currentEvent = decodeStructure(
PfEventTradeLayout,
Buffer.from(logs[idx].slice(PF_LOG_EVENT_TRADE_DATA_ENCODED_PREFIX_OFFSET), "base64"),
PF_EVENT_TRADE_DISCRIMINATOR,
0,
e => PfEvent.type.TRADE(e),
))) {
events.push(currentEvent);
if (parseWhilePredicate && !parseWhilePredicate(events, currentEvent, previousEvent, logs)) {
break;
}
previousEvent = currentEvent;
}
continue;
}
if (logs[idx].length >= PF_LOG_EVENT_COMPLETE_TOTAL_LENGTH && logs[idx].startsWith(PF_LOG_EVENT_COMPLETE_DATA_ENCODED_PREFIX, PF_LOG_EVENT_COMPLETE_DATA_ENCODED_PREFIX_OFFSET)) {
if (!(logs[idx - 1] === PF_LOG_SUCCESS || (idx !== (logsLength - 1) && logs[idx + 1].startsWith(PF_LOG_PREFIX)))) {
continue;
}
if ((currentEvent = decodeStructure(
PfEventCompleteLayout,
Buffer.from(logs[idx].slice(PF_LOG_EVENT_COMPLETE_DATA_ENCODED_PREFIX_OFFSET), "base64"),
PF_EVENT_COMPLETE_DISCRIMINATOR,
0,
e => PfEvent.type.COMPLETE(e),
))) {
events.push(currentEvent);
if (parseWhilePredicate && !parseWhilePredicate(events, currentEvent, previousEvent, logs)) {
break;
}
previousEvent = currentEvent;
}
continue;
}
}
return events;
};
export function findPfBondingCurveAddress(tokenMint: web3.PublicKey): web3.PublicKey {
return web3.PublicKey.findProgramAddressSync([
PF_ACCOUNT_BONDING_CURVE_SEED,
tokenMint.toBuffer(),
], PF_PROGRAM_ID)[0];
};
export function findPfAssociatedBondingCurveAddress(tokenMint: web3.PublicKey, bondingCurve?: web3.PublicKey): web3.PublicKey {
if (!bondingCurve) {
bondingCurve = findPfBondingCurveAddress(tokenMint);
}
return spl.getAssociatedTokenAddressSync(tokenMint, bondingCurve, true);
};
////////////////////////////////////////////////////////////////////////////////
function mapInPlaceObjectContainingBigInts(v: any, fixedDecimalPrecision?: number): any {
if (v === null || typeof v !== "object") {
return v;
} else if (Array.isArray(v)) {
return v.map(e => mapInPlaceObjectContainingBigInts(e, fixedDecimalPrecision));
}
for (const k in v) {
if (!v.hasOwnProperty(k) || v[k] === null) {
continue;
}
switch (typeof v[k]) {
case "object":
if (v[k].hasOwnProperty("_bn") || (v[k].constructor && v[k].constructor.hasOwnProperty("isBN")))
v[k] = String(v[k]);
break;
case "bigint":
v[k] = String(v[k]);
break;
case "number":
if (fixedDecimalPrecision)
v[k] = (v[k] as number).toFixed(fixedDecimalPrecision);
break;
}
}
return v;
}
////////////////////////////////////////////////////////////////////////////////
export const OPAQUE_SIGNATURE = "1111111111111111111111111111111111111111111111111111111111111111";
export function calculatePfTokenPriceInShiftedLamports(
virtualTokenReserves: bigint,
virtualSolReserves: bigint,
decimalsTotalProduct: bigint = PF_CONFIG_DECIMALS_TOTAL_PRODUCT,
): bigint {
if (virtualSolReserves <= 0 || virtualTokenReserves <= 0 || decimalsTotalProduct <= 0n) {
return 0n;
}
return (virtualSolReserves * decimalsTotalProduct) / virtualTokenReserves;
};
export function shiftedLamportsToSol(
lamports: bigint | number,
decimalsTotalShiftProduct: bigint | number = PF_CONFIG_DECIMALS_TOTAL_SHIFT_PRODUCT,
): number {
return Number(lamports) / Number(decimalsTotalShiftProduct);
};
export function lamportsToSol(
lamports: bigint | number,
decimalsProduct: bigint | number = PF_CONFIG_DECIMALS_SOL_PRODUCT,
): number {
return Number(lamports) / Number(decimalsProduct);
};
export type PfEventEnhanced = (PfEventCreateEnhanced | PfEventTradeEnhanced | PfEventCompleteEnhanced) & {
readonly type: string;
};
export type PfEventCreateEnhanced = PfEventCreate & {};
export type PfEventTradeEnhanced = PfEventTrade & {
bondingCurve: web3.PublicKey;
associatedBondingCurve: web3.PublicKey;
bondingProgressBefore: number;
bondingProgressAfter: number;
kothProgressBefore: number;
kothProgressAfter: number;
priceBefore: number;
priceAfter: number;
priceAverage: number;
pricePaid: number;
solAmountParsed: number;
tokenAmountParsed: number;
};
export type PfEventCompleteEnhanced = PfEventComplete & {};
export function enhancePfEventTrade(event: PfEventTrade): PfEventTradeEnhanced | null {
if (event === null || typeof event !== "object" || !(event instanceof PfEvent.type.TRADE)) {
return null;
}
const e = event as PfEventTradeEnhanced;
const curvePublicKey = findPfBondingCurveAddress(e.mint);
const curveAssociatedPublicKey = findPfAssociatedBondingCurveAddress(e.mint, curvePublicKey);
const realTokenReservesBefore = e.realTokenReserves + (e.isBuy ? e.tokenAmount : -e.tokenAmount);
const realTokenReservesBeforeDeltaInitial = (PF_CONFIG_INITIAL_REAL_TOKEN_RESERVES - realTokenReservesBefore);
const realTokenReservesDeltaInitial = (PF_CONFIG_INITIAL_REAL_TOKEN_RESERVES - e.realTokenReserves);
const virtualTokenReservesBefore = e.virtualTokenReserves + (e.isBuy ? e.tokenAmount : -e.tokenAmount);
const virtualSolReservesAfter = e.virtualSolReserves + (e.isBuy ? -e.solAmount : e.solAmount);
const bondingProgressBefore = realTokenReservesBeforeDeltaInitial <= 0n ? 0.0
: (1.0 - (Number(realTokenReservesBefore * 16384n / PF_CONFIG_INITIAL_REAL_TOKEN_RESERVES) / 16384.0));
const bondingProgressAfter = realTokenReservesDeltaInitial <= 0n ? 0.0
: (1.0 - (Number(e.realTokenReserves * 16384n / PF_CONFIG_INITIAL_REAL_TOKEN_RESERVES) / 16384.0));
const kothProgressBefore = realTokenReservesBeforeDeltaInitial <= 0n ? 0.0
: realTokenReservesBeforeDeltaInitial >= PF_CONFIG_KOTH_TARGET ? 1.0
: Math.min(1, (Number((realTokenReservesBeforeDeltaInitial * 16384n) / PF_CONFIG_KOTH_TARGET) / 16384.0));
const kothProgressAfter = realTokenReservesDeltaInitial <= 0n ? 0.0
: realTokenReservesDeltaInitial >= PF_CONFIG_KOTH_TARGET ? 1.0
: Math.min(1, (Number((realTokenReservesDeltaInitial * 16384n) / PF_CONFIG_KOTH_TARGET) / 16384.0));
const priceBefore = calculatePfTokenPriceInShiftedLamports(virtualTokenReservesBefore, virtualSolReservesAfter);
const priceAfter = calculatePfTokenPriceInShiftedLamports(e.virtualTokenReserves, e.virtualSolReserves);
const priceAverage = Math.floor(Math.sqrt(Number(priceBefore * priceAfter)));
const pricePaid = calculatePfTokenPriceInShiftedLamports(e.tokenAmount, e.solAmount);
e.bondingCurve = curvePublicKey;
e.associatedBondingCurve = curveAssociatedPublicKey;
e.bondingProgressBefore = bondingProgressBefore;
e.bondingProgressAfter = bondingProgressAfter;
e.kothProgressBefore = kothProgressBefore;
e.kothProgressAfter = kothProgressAfter;
e.priceBefore = shiftedLamportsToSol(priceBefore);
e.priceAfter = shiftedLamportsToSol(priceAfter);
e.priceAverage = shiftedLamportsToSol(priceAverage);
e.pricePaid = shiftedLamportsToSol(pricePaid);
e.solAmountParsed = lamportsToSol(e.solAmount);
e.tokenAmountParsed = lamportsToSol(e.tokenAmount, PF_CONFIG_DECIMALS_TOKEN_PRODUCT);
return e;
}
export function enhancePfEvents(rawEvents: PfEvent[]): PfEventEnhanced[] {
if (!Array.isArray(rawEvents) || !rawEvents.length) {
return [];
}
const events = rawEvents as PfEventEnhanced[];
for (const event of events) {
const type = PfType.of(event);
Object.defineProperty(event, "type", {
value: type.description,
enumerable: true,
});
switch (type) {
case PfEvent.type.TRADE.symbol:
enhancePfEventTrade(event as PfEventTrade);
break;
// case PfEvent.type.CREATE: /* ... */ break;
// case PfEvent.type.COMPLETE: /* ... */ break;
}
}
return events;
}
(async function () {
await main(
process.env.SOL_RPC_URL || "https://mainnet.helius-rpc.com/?api-key=00000000-0000-0000-0000-000000000000",
);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment