solana-parser
Version:
lightweight transaction parser for popular DeFi applications on the Solana blockchain, written in TypeScript.
197 lines • 8.07 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RaydiumV4Parser = void 0;
const web3_js_1 = require("@solana/web3.js");
const lru_1 = require("../../../core/lru");
const types_1 = require("./types");
const layout_1 = require("./layout");
const utils_1 = require("../../../core/utils");
class RaydiumV4Parser {
constructor(rpcConnection, options) {
this.connection = rpcConnection;
this.poolInfoCache = new lru_1.LRUCache(options.maxPoolCache || 100);
}
getRayLogs(transaction) {
return transaction.meta?.logMessages?.filter((msg) => msg.includes('ray_log'));
}
decodeRayLog(msg) {
const logData = msg.match(/^Program log: ray_log: (.+)$/)?.[1] ?? msg;
const logBuffer = Buffer.from(logData, 'base64');
const logType = logBuffer.slice(0, 1).readInt8();
const dataBuffer = logBuffer.slice(1);
switch (logType) {
case types_1.INIT_LOG_TYPE:
return { ...layout_1.INIT_POOL_LAYOUT.decode(dataBuffer), logType };
case types_1.DEPOSIT_LOG_TYPE:
return { ...layout_1.DEPOSIT_LAYOUT.decode(dataBuffer), logType };
case types_1.WITHDRAW_LOG_TYPE:
return { ...layout_1.WITHDRAW_LAYOUT.decode(dataBuffer), logType };
case types_1.SWAP_BASE_IN_LOG_TYPE:
return { ...layout_1.SWAP_BASE_IN_LAYOUT.decode(dataBuffer), logType };
case types_1.SWAP_BASE_OUT_LOG_TYPE:
return { ...layout_1.SWAP_BASE_OUT_LAYOUT.decode(dataBuffer), logType };
default:
return null;
}
}
async getPoolInfo(poolId) {
const info = this.poolInfoCache.get(poolId);
if (info)
return info;
const poolInfo = await this.connection.getAccountInfo(new web3_js_1.PublicKey(poolId));
if (!poolInfo)
return null;
const parsedInfo = layout_1.RAY_AMM_V4_POOL_LAYOUT.decode(poolInfo.data);
this.poolInfoCache.set(poolId, {
baseMint: parsedInfo.baseMint,
quoteMint: parsedInfo.quoteMint,
baseDecimal: parsedInfo.baseDecimal,
quoteDecimal: parsedInfo.quoteDecimal,
});
return parsedInfo;
}
async handleSwap(parsedLog, instruction) {
const poolId = instruction.accounts[1];
const user = instruction.accounts[instruction.accounts.length - 1];
const poolInfo = await this.getPoolInfo(poolId.toString());
if (!poolInfo)
return null;
return {
type: types_1.ActionType.SWAP,
info: {
amountIn: parsedLog.amountIn,
amountOut: parsedLog.amountOut,
baseReserve: parsedLog.baseReserve,
quoteReserve: parsedLog.quoteReserve,
tokenIn: parsedLog.direction == 1n ? poolInfo.quoteMint : poolInfo.baseMint,
tokenInDecimal: parsedLog.direction == 1n ? poolInfo.quoteDecimal : poolInfo.baseDecimal,
tokenOut: parsedLog.direction == 1n ? poolInfo.baseMint : poolInfo.quoteMint,
tokenOutDecimal: parsedLog.direction == 1n ? poolInfo.baseDecimal : poolInfo.quoteDecimal,
user,
poolId,
},
};
}
handleCreatePool(parsedLog, instruction) {
return {
type: types_1.ActionType.CREATE,
info: {
baseDecimals: parsedLog.baseDecimals,
quoteDecimals: parsedLog.quoteDecimals,
timestamp: parsedLog.timestamp,
baseAmountIn: parsedLog.baseAmountIn,
quoteAmountIn: parsedLog.quoteAmountIn,
baseMint: instruction.accounts[8],
quoteMint: instruction.accounts[9],
marketId: parsedLog.marketId,
user: instruction.accounts[17],
poolId: instruction.accounts[4],
},
};
}
async handleDeposit(parsedLog, instruction) {
const poolId = instruction.accounts[1];
const user = instruction.accounts[12];
const poolInfo = await this.getPoolInfo(poolId.toString());
if (!poolInfo)
return null;
return {
type: types_1.ActionType.ADD,
info: {
user,
poolId,
baseMint: poolInfo.baseMint,
quoteMint: poolInfo.quoteMint,
baseDecimal: poolInfo.baseDecimal,
quoteDecimal: poolInfo.quoteDecimal,
baseAmountIn: parsedLog.baseAmountIn,
quoteAmountIn: parsedLog.quoteAmountIn,
mintedLpAmount: parsedLog.mintedLpAmount,
},
};
}
async handleWithdraw(parsedLog, instruction) {
const poolId = instruction.accounts[1];
const user = instruction.accounts[18];
const poolInfo = await this.getPoolInfo(poolId.toString());
if (!poolInfo)
return null;
return {
type: types_1.ActionType.REMOVE,
info: {
lpAmountOut: parsedLog.withdrawLpAmount,
poolLpAmount: parsedLog.poolLpAmount,
baseReserve: parsedLog.baseReserve,
quoteReserve: parsedLog.quoteReserve,
baseAmountOut: parsedLog.baseAmountOut,
quoteAmountOut: parsedLog.quoteAmountOut,
baseMint: poolInfo.baseMint,
quoteMint: poolInfo.quoteMint,
baseDecimal: poolInfo.baseDecimal,
quoteDecimal: poolInfo.quoteDecimal,
user,
poolId,
},
};
}
async parse(transaction) {
const logs = this.getRayLogs(transaction);
if (!logs) {
return null;
}
const decodedLogs = logs.map((msg) => this.decodeRayLog(msg));
const instructions = (0, utils_1.flattenTransactionInstructions)(transaction).filter((ix) => ix.programId.toString() === types_1.RayV4Program.toString());
if (instructions.length == 0)
return null;
const result = {
platform: 'raydiumv4',
actions: [],
};
for (let i = 0; i < decodedLogs.length; i++) {
let ixLog = decodedLogs[i];
let ix = instructions[i];
let action;
if (!ixLog)
continue;
switch (ixLog.logType) {
case types_1.SWAP_BASE_IN_LOG_TYPE:
action = await this.handleSwap(ixLog, ix);
if (!action)
continue;
result.actions.push(action);
break;
case types_1.SWAP_BASE_OUT_LOG_TYPE:
action = await this.handleSwap(ixLog, ix);
if (!action)
continue;
result.actions.push(action);
break;
case types_1.INIT_LOG_TYPE:
result.actions.push(this.handleCreatePool(ixLog, ix));
break;
case types_1.WITHDRAW_LOG_TYPE:
action = await this.handleWithdraw(ixLog, ix);
if (!action)
continue;
result.actions.push(action);
break;
case types_1.DEPOSIT_LOG_TYPE:
action = await this.handleDeposit(ixLog, ix);
if (!action)
continue;
result.actions.push(action);
break;
default:
continue;
}
}
return result;
}
async parseMultiple(transactions) {
return (await Promise.all(transactions.map(async (txn) => {
return await this.parse(txn);
}))).filter((res) => res !== null);
}
}
exports.RaydiumV4Parser = RaydiumV4Parser;
//# sourceMappingURL=parser.js.map