punm parser

This commit is contained in:
thloyi
2025-11-20 17:56:45 +08:00
commit a945f3b45d
29 changed files with 9665 additions and 0 deletions

46
budget.go Normal file
View File

@@ -0,0 +1,46 @@
package pump_parser
import (
"encoding/binary"
"github.com/shopspring/decimal"
)
type setComputeData struct {
Units uint64
}
func budgetParser(result *RawTx, instr Instruction, innerInstructions InnerInstructions, offset [2]uint, tx *Tx) ([2]uint, error) {
if offset[1] != 0 {
return increaseOffset(offset), nil
}
decode := result.Transaction.Message.Instructions[offset[0]].Data
discriminator := decode[0]
switch discriminator {
case setComputeUnitLimitDiscriminator:
return computeUnitLimitParser(result, offset, tx, decode[1:])
case setComputeUnitPriceDiscriminator:
return computeUnitPriceParser(result, offset, tx, decode[1:])
default:
return increaseOffset(offset), nil
}
}
func computeUnitLimitParser(_ *RawTx, offset [2]uint, _ *Tx, decodedData []byte) ([2]uint, error) {
if len(decodedData) < 8 {
return increaseOffset(offset), nil
}
return increaseOffset(offset), nil
}
func computeUnitPriceParser(_ *RawTx, offset [2]uint, tx *Tx, decodedData []byte) ([2]uint, error) {
if len(decodedData) < 8 {
return increaseOffset(offset), nil
}
cuPrice := binary.LittleEndian.Uint64(decodedData[:8])
tx.CUPrice = decimal.NewFromInt(int64(cuPrice)).Div(decimal.NewFromInt(1000000)) // cu decimals
return increaseOffset(offset), nil
}

368
checking.go Normal file
View File

@@ -0,0 +1,368 @@
package pump_parser
import (
"log"
"strings"
"github.com/gagliardetto/solana-go"
)
func checkBonkGmgnBuy(rawTx *RawTx) bool {
// 检查交易版本
var version, _ = rawTx.Version.(solana.MessageVersion)
if version != solana.MessageVersionLegacy {
return false
}
// 检查交易指令数量
if len(rawTx.Transaction.Message.Instructions) != 10 && len(rawTx.Transaction.Message.Instructions) != 9 {
return false
}
accountList := rawTx.getAccountList()
// 检查 cu limit
{
instruction := rawTx.Transaction.Message.Instructions[0]
programId := accountList[instruction.ProgramIDIndex]
if programId != solana.ComputeBudget {
return false
}
if len(instruction.Accounts) != 1 {
return false
}
accountId := accountList[instruction.Accounts[0]].String()
if !strings.HasPrefix(accountId, "jitodontfront1111111111151111111111111655") {
return false
}
}
// 检查 cu price
{
instruction := rawTx.Transaction.Message.Instructions[1]
programId := accountList[instruction.ProgramIDIndex]
if programId != solana.ComputeBudget {
return false
}
}
// 检查 ata.createIdempotent
{
instruction := rawTx.Transaction.Message.Instructions[2]
programId := accountList[instruction.ProgramIDIndex]
if programId != solana.SPLAssociatedTokenAccountProgramID {
return false
}
if instruction.Data.String() != "2" {
return false
}
if len(instruction.Accounts) < 4 {
return false
}
// gmgn 会先创建 wsol 账户, 而不是 token 账户
accountId := accountList[instruction.Accounts[3]]
if accountId != solana.WrappedSol {
return false
}
}
// 检查 transfer
{
instruction := rawTx.Transaction.Message.Instructions[3]
programId := accountList[instruction.ProgramIDIndex]
if programId != solana.SystemProgramID {
return false
}
if len(instruction.Data) == 0 || instruction.Data[0] != 2 {
return false
}
}
// 检查 token.syncNative
{
instruction := rawTx.Transaction.Message.Instructions[4]
programId := accountList[instruction.ProgramIDIndex]
if programId != solana.TokenProgramID {
return false
}
if instruction.Data.String() != "J" {
return false
}
}
offset := 5
if len(rawTx.Transaction.Message.Instructions) == 10 {
// 检查 ata.create
{
instruction := rawTx.Transaction.Message.Instructions[offset]
programId := accountList[instruction.ProgramIDIndex]
if programId != solana.SPLAssociatedTokenAccountProgramID {
return false
}
if instruction.Data.String() != "1" {
return false
}
}
offset++
}
// 检查 bonk.buy
{
instruction := rawTx.Transaction.Message.Instructions[offset]
programId := accountList[instruction.ProgramIDIndex]
if programId != raydiumLaunchLabProgramID {
return false
}
}
offset++
// 检查 token.closeAccount
{
instruction := rawTx.Transaction.Message.Instructions[offset]
programId := accountList[instruction.ProgramIDIndex]
if programId != solana.TokenProgramID {
return false
}
if instruction.Data.String() != "A" {
return false
}
}
offset++
// 检查 transfer
{
instruction := rawTx.Transaction.Message.Instructions[offset]
programId := accountList[instruction.ProgramIDIndex]
if programId != solana.SystemProgramID {
return false
}
if len(instruction.Data) == 0 || instruction.Data[0] != 2 {
return false
}
}
offset++
// 检查 transfer
{
instruction := rawTx.Transaction.Message.Instructions[offset]
programId := accountList[instruction.ProgramIDIndex]
if programId != solana.SystemProgramID {
return false
}
if len(instruction.Data) == 0 || instruction.Data[0] != 2 {
return false
}
}
return true
}
var (
axiomTxLoopupTable = solana.MustPublicKeyFromBase58("7RKtfATWCe98ChuwecNq8XCzAzfoK3DtZTprFsPMGtio")
)
func checkBonkAxiomBuy(rawTx *RawTx) bool {
// 检查交易版本
var version, _ = rawTx.Version.(solana.MessageVersion)
if version == solana.MessageVersionLegacy || len(rawTx.Transaction.Message.AddressTableLookups) != 1 {
log.Printf("CheckBonkAxiomBuy: Invalid transaction version or address table lookups: %v %v %v", rawTx.Transaction.Signatures[0].String(), version, len(rawTx.Transaction.Message.AddressTableLookups))
return false
}
// 检查 addressLookupTable 是否是 Axiom 的
if rawTx.Transaction.Message.AddressTableLookups[0].AccountKey != axiomTxLoopupTable {
log.Printf("CheckBonkAxiomBuy: Invalid address lookup table: %v", rawTx.Transaction.Signatures[0].String())
return false
}
// 检查交易指令数量
if len(rawTx.Transaction.Message.Instructions) != 10 {
return false
}
accountList := rawTx.getAccountList()
// 检查 cu limit
{
instruction := rawTx.Transaction.Message.Instructions[0]
programId := accountList[instruction.ProgramIDIndex]
if programId != solana.ComputeBudget {
log.Printf("CheckBonkAxiomBuy: Invalid program ID for ComputeBudget: %v", programId)
return false
}
if len(instruction.Accounts) != 1 {
log.Printf("CheckBonkAxiomBuy: Invalid number of accounts for ComputeBudget: %v", len(instruction.Accounts))
return false
}
accountId := accountList[instruction.Accounts[0]].String()
if !strings.HasPrefix(accountId, "jitodontfront") || !strings.HasSuffix(accountId, "TradeWithAxiomDotTrade") {
log.Printf("CheckBonkAxiomBuy: Invalid account ID for ComputeBudget: %v", accountId)
return false
}
}
// 检查 cu price
{
instruction := rawTx.Transaction.Message.Instructions[1]
programId := accountList[instruction.ProgramIDIndex]
if programId != solana.ComputeBudget {
log.Printf("CheckBonkAxiomBuy: Invalid program ID for ComputeBudget: %v", programId)
return false
}
}
// 检查 ata.createIdempotent
{
instruction := rawTx.Transaction.Message.Instructions[2]
programId := accountList[instruction.ProgramIDIndex]
if programId != solana.SPLAssociatedTokenAccountProgramID {
log.Printf("CheckBonkAxiomBuy: Invalid program ID for SPLAssociatedTokenAccount: %v", programId)
return false
}
if instruction.Data.String() != "2" {
log.Printf("CheckBonkAxiomBuy: Invalid data for SPLAssociatedTokenAccount: %v", instruction.Data.String())
return false
}
if len(instruction.Accounts) < 4 {
log.Printf("CheckBonkAxiomBuy: Invalid number of accounts for SPLAssociatedTokenAccount: %v", len(instruction.Accounts))
return false
}
// axiom 会先创建 token 账户, 而不是 wsol 账户
accountId := accountList[instruction.Accounts[3]]
if accountId == solana.WrappedSol {
log.Printf("CheckBonkAxiomBuy: Invalid account ID for SPLAssociatedTokenAccount, expected token account but got wsol: %v", accountId)
return false
}
}
// 检查 ata.createIdempotent
{
instruction := rawTx.Transaction.Message.Instructions[3]
programId := accountList[instruction.ProgramIDIndex]
if programId != solana.SPLAssociatedTokenAccountProgramID {
log.Printf("CheckBonkAxiomBuy: Invalid program ID for SPLAssociatedTokenAccount2: %v", programId)
return false
}
if instruction.Data.String() != "2" {
log.Printf("CheckBonkAxiomBuy: Invalid data for SPLAssociatedTokenAccount2: %v", instruction.Data.String())
return false
}
if len(instruction.Accounts) < 4 {
log.Printf("CheckBonkAxiomBuy: Invalid number of accounts for SPLAssociatedTokenAccount2: %v", len(instruction.Accounts))
return false
}
// axiom 会先创建 token 账户, 而不是 wsol 账户
accountId := accountList[instruction.Accounts[3]]
if accountId != solana.WrappedSol {
log.Printf("CheckBonkAxiomBuy: Invalid account ID for SPLAssociatedTokenAccount2, expected wsol but got: %v", accountId)
return false
}
}
// 检查 transfer
{
instruction := rawTx.Transaction.Message.Instructions[4]
programId := accountList[instruction.ProgramIDIndex]
if programId != solana.SystemProgramID {
log.Printf("CheckBonkAxiomBuy: Invalid program ID for SystemProgram3: %v", programId)
return false
}
if len(instruction.Data) == 0 || instruction.Data[0] != 2 {
log.Printf("CheckBonkAxiomBuy: Invalid data for SystemProgram transfer3: %v", instruction.Data)
return false
}
}
// 检查 token.syncNative
{
instruction := rawTx.Transaction.Message.Instructions[5]
programId := accountList[instruction.ProgramIDIndex]
if programId != solana.TokenProgramID {
log.Printf("CheckBonkAxiomBuy: Invalid program ID for TokenProgram3: %v", programId)
return false
}
if instruction.Data.String() != "J" {
log.Printf("CheckBonkAxiomBuy: Invalid data for TokenProgram syncNative3: %v", instruction.Data.String())
return false
}
}
// 检查 bonk.buy
{
instruction := rawTx.Transaction.Message.Instructions[6]
programId := accountList[instruction.ProgramIDIndex]
if programId != raydiumLaunchLabProgramID {
return false
}
}
// 检查 token.closeAccount
{
instruction := rawTx.Transaction.Message.Instructions[7]
programId := accountList[instruction.ProgramIDIndex]
if programId != solana.TokenProgramID {
log.Printf("CheckBonkAxiomBuy: Invalid program ID for TokenProgram closeAccount: %v", programId)
return false
}
if instruction.Data.String() != "A" {
log.Printf("CheckBonkAxiomBuy: Invalid data for TokenProgram closeAccount: %v", instruction.Data.String())
return false
}
}
// 检查 transfer
{
instruction := rawTx.Transaction.Message.Instructions[8]
programId := accountList[instruction.ProgramIDIndex]
if programId != solana.SystemProgramID {
log.Printf("CheckBonkAxiomBuy: Invalid program ID for SystemProgram transfer4: %v", programId)
return false
}
if len(instruction.Data) == 0 || instruction.Data[0] != 2 {
log.Printf("CheckBonkAxiomBuy: Invalid data for SystemProgram transfer4: %v", instruction.Data)
return false
}
}
// 检查 transfer
{
instruction := rawTx.Transaction.Message.Instructions[9]
programId := accountList[instruction.ProgramIDIndex]
if programId != solana.SystemProgramID {
log.Printf("CheckBonkAxiomBuy: Invalid program ID for SystemProgram transfer5: %v", programId)
return false
}
if len(instruction.Data) == 0 || instruction.Data[0] != 2 {
log.Printf("CheckBonkAxiomBuy: Invalid data for SystemProgram transfer5: %v", instruction.Data)
return false
}
}
return true
}

180
consts.go Normal file
View File

@@ -0,0 +1,180 @@
package pump_parser
import "github.com/gagliardetto/solana-go"
var platformFeeAddresses = map[solana.PublicKey]string{
solana.MustPublicKeyFromBase58("BB5dnY55FXS1e1NXqZDwCzgdYJdMCj3B92PU6Q5Fb6DT"): PlatformGMGN,
solana.MustPublicKeyFromBase58("AVUCZyuT35YSuj4RH7fwiyPu82Djn2Hfg7y2ND2XcnZH"): PlatformPhoton,
solana.MustPublicKeyFromBase58("7LCZckF6XXGQ1hDY6HFXBKWAtiUgL9QY5vj1C4Bn1Qjj"): PlatformAxiom,
solana.MustPublicKeyFromBase58("4V65jvcDG9DSQioUVqVPiUcUY9v6sb6HKtMnsxSKEz5S"): PlatformAxiom,
solana.MustPublicKeyFromBase58("CeA3sPZfWWToFEBmw5n1Y93tnV66Vmp8LacLzsVprgxZ"): PlatformAxiom,
solana.MustPublicKeyFromBase58("AaG6of1gbj1pbDumvbSiTuJhRCRkkUNaWVxijSbWvTJW"): PlatformAxiom,
solana.MustPublicKeyFromBase58("7oi1L8U9MRu5zDz5syFahsiLUric47LzvJBQX6r827ws"): PlatformAxiom,
solana.MustPublicKeyFromBase58("9kPrgLggBJ69tx1czYAbp7fezuUmL337BsqQTKETUEhP"): PlatformAxiom,
solana.MustPublicKeyFromBase58("DKyUs1xXMDy8Z11zNsLnUg3dy9HZf6hYZidB6WodcaGy"): PlatformAxiom,
solana.MustPublicKeyFromBase58("4FobGn5ZWYquoJkxMzh2VUAWvV36xMgxQ3M7uG1pGGhd"): PlatformAxiom,
solana.MustPublicKeyFromBase58("76sxKrPtgoJHDJvxwFHqb3cAXWfRHFLe3VpKcLCAHSEf"): PlatformAxiom,
solana.MustPublicKeyFromBase58("H2cDR3EkJjtTKDQKk8SJS48du9mhsdzQhy8xJx5UMqQK"): PlatformAxiom,
solana.MustPublicKeyFromBase58("8m5GkL7nVy95G4YVUbs79z873oVKqg2afgKRmqxsiiRm"): PlatformAxiom,
solana.MustPublicKeyFromBase58("4kuG6NsAFJNwqEkac8GFDMMheCGKUPEbaRVHHyFHSwWz"): PlatformAxiom,
solana.MustPublicKeyFromBase58("8vFGAKdwpn4hk7kc1cBgfWZzpyW3MEMDATDzVZhddeQb"): PlatformAxiom,
solana.MustPublicKeyFromBase58("86Vh4XGLW2b6nvWbRyDs4ScgMXbuvRCHT7WbUT3RFxKG"): PlatformAxiom,
solana.MustPublicKeyFromBase58("DZfEurFKFtSbdWZsKSDTqpqsQgvXxmESpvRtXkAdgLwM"): PlatformAxiom,
solana.MustPublicKeyFromBase58("5L2QKqDn5ukJSWGyqR4RPvFvwnBabKWqAqMzH4heaQNB"): PlatformAxiom,
solana.MustPublicKeyFromBase58("DYVeNgXGLAhZdeLMMYnCw1nPnMxkBN7fJnNpHmizTrrF"): PlatformAxiom,
solana.MustPublicKeyFromBase58("Hbj6XdxX6eV4nfbYTseysibp4zZJtVRRPn2J3BhGRuK9"): PlatformAxiom,
solana.MustPublicKeyFromBase58("846ah7iBSu9ApuCyEhA5xpnjHHX7d4QJKetWLbwzmJZ8"): PlatformAxiom,
solana.MustPublicKeyFromBase58("5BqYhuD4q1YD3DMAYkc1FeTu9vqQVYYdfBAmkZjamyZg"): PlatformAxiom,
solana.MustPublicKeyFromBase58("G9PhF9C9H83mAjjkdJz4MDqkufiTPMJkx7TnKE1kFyCp"): PlatformPepe,
solana.MustPublicKeyFromBase58("9RYJ3qr5eU5xAooqVcbmdeusjcViL5Nkiq7Gske3tiKq"): PlatformBullX,
solana.MustPublicKeyFromBase58("F4hJ3Ee3c5UuaorKAMfELBjYCjiiLH75haZTKqTywRP3"): PlatformBullX,
solana.MustPublicKeyFromBase58("47hEzz83VFR23rLTEeVm9A7eFzjJwjvdupPPmX3cePqF"): PlatformBanana,
solana.MustPublicKeyFromBase58("9yMwSPk9mrXSN7yDHUuZurAh1sjbJsfpUqjZ7SvVtdco"): PlatformTrojan,
solana.MustPublicKeyFromBase58("4mih95RmBqfHYvEfqq6uGGLp1Fr3gVS3VNSEa3JVRfQK"): PlatformRaybot,
solana.MustPublicKeyFromBase58("3udvfL24waJcLhskRAsStNMoNUvtyXdxrWQz4hgi953N"): PlatformMoonshot,
solana.MustPublicKeyFromBase58("3kxSQybWEeQZsMuNWMRJH4TxrhwoDwfv41TNMLRzFP5A"): PlatformMEVX,
solana.MustPublicKeyFromBase58("BS3CyJ9rRC4Tp8G7f86r6hGvuu3XdrVGNVpbNM9U5WRZ"): PlatformMEVX,
solana.MustPublicKeyFromBase58("97VmzkjX9w8gMFS2RnHTSjtMEDbifGXBq9pgosFdFnM"): PlatformTradeWiz,
solana.MustPublicKeyFromBase58("F34kcgMgCF7mYWkwLN3WN7KrFprr2NbwxuLvXx4fbztj"): PlatformSolTradingBot,
solana.MustPublicKeyFromBase58("96aFQc9qyqpjMfqdUeurZVYRrrwPJG2uPV6pceu4B1yb"): PlatformSolTradingBot,
solana.MustPublicKeyFromBase58("5wkyL2FLEcyUUgc3UeGntHTAfWfzDrVuxMnaMm7792Gk"): PlatformMoonshotMoney,
solana.MustPublicKeyFromBase58("MaestroUL88UBnZr3wfoN7hqmNWFi3ZYCGqZoJJHE36"): PlatformMaestro,
solana.MustPublicKeyFromBase58("ZG98FUCjb8mJ824Gbs6RsgVmr1FhXb2oNiJHa2dwmPd"): PlatformBonkBot,
}
var mevAgentFeeAddresses = map[solana.PublicKey]string{
solana.MustPublicKeyFromBase58("96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5"): MevAgentJito,
solana.MustPublicKeyFromBase58("HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe"): MevAgentJito,
solana.MustPublicKeyFromBase58("Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY"): MevAgentJito,
solana.MustPublicKeyFromBase58("ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49"): MevAgentJito,
solana.MustPublicKeyFromBase58("DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh"): MevAgentJito,
solana.MustPublicKeyFromBase58("ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt"): MevAgentJito,
solana.MustPublicKeyFromBase58("DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL"): MevAgentJito,
solana.MustPublicKeyFromBase58("3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT"): MevAgentJito,
solana.MustPublicKeyFromBase58("6fQaVhYZA4w3MBSXjJ81Vf6W1EDYeUPXpgVQ6UQyU1Av"): MevAgent0slot,
solana.MustPublicKeyFromBase58("4HiwLEP2Bzqj3hM2ENxJuzhcPCdsafwiet3oGkMkuQY4"): MevAgent0slot,
solana.MustPublicKeyFromBase58("7toBU3inhmrARGngC7z6SjyP85HgGMmCTEwGNRAcYnEK"): MevAgent0slot,
solana.MustPublicKeyFromBase58("8mR3wB1nh4D6J9RUCugxUpc6ya8w38LPxZ3ZjcBhgzws"): MevAgent0slot,
solana.MustPublicKeyFromBase58("6SiVU5WEwqfFapRuYCndomztEwDjvS5xgtEof3PLEGm9"): MevAgent0slot,
solana.MustPublicKeyFromBase58("TpdxgNJBWZRL8UXF5mrEsyWxDWx9HQexA9P1eTWQ42p"): MevAgent0slot,
solana.MustPublicKeyFromBase58("D8f3WkQu6dCF33cZxuAsrKHrGsqGP2yvAHf8mX6RXnwf"): MevAgent0slot,
solana.MustPublicKeyFromBase58("GQPFicsy3P3NXxB5piJohoxACqTvWE9fKpLgdsMduoHE"): MevAgent0slot,
solana.MustPublicKeyFromBase58("Ey2JEr8hDkgN8qKJGrLf2yFjRhW7rab99HVxwi5rcvJE"): MevAgent0slot,
solana.MustPublicKeyFromBase58("4iUgjMT8q2hNZnLuhpqZ1QtiV8deFPy2ajvvjEpKKgsS"): MevAgent0slot,
solana.MustPublicKeyFromBase58("3Rz8uD83QsU8wKvZbgWAPvCNDU6Fy8TSZTMcPm3RB6zt"): MevAgent0slot,
solana.MustPublicKeyFromBase58("DiTmWENJsHQdawVUUKnUXkconcpW4Jv52TnMWhkncF6t"): MevAgent0slot,
solana.MustPublicKeyFromBase58("HRyRhQ86t3H4aAtgvHVpUJmw64BDrb61gRiKcdKUXs5c"): MevAgent0slot,
solana.MustPublicKeyFromBase58("7y4whZmw388w1ggjToDLSBLv47drw5SUXcLk6jtmwixd"): MevAgent0slot,
solana.MustPublicKeyFromBase58("J9BMEWFbCBEjtQ1fG5Lo9kouX1HfrKQxeUxetwXrifBw"): MevAgent0slot,
solana.MustPublicKeyFromBase58("8U1JPQh3mVQ4F5jwRdFTBzvNRQaYFQppHQYoH38DJGSQ"): MevAgent0slot,
solana.MustPublicKeyFromBase58("Eb2KpSC8uMt9GmzyAEm5Eb1AAAgTjRaXWFjKyFXHZxF3"): MevAgent0slot,
solana.MustPublicKeyFromBase58("FCjUJZ1qozm1e8romw216qyfQMaaWKxWsuySnumVCCNe"): MevAgent0slot,
solana.MustPublicKeyFromBase58("ENxTEjSQ1YabmUpXAdCgevnHQ9MHdLv8tzFiuiYJqa13"): MevAgent0slot,
solana.MustPublicKeyFromBase58("6rYLG55Q9RpsPGvqdPNJs4z5WTxJVatMB8zV3WJhs5EK"): MevAgent0slot,
solana.MustPublicKeyFromBase58("Cix2bHfqPcKcM233mzxbLk14kSggUUiz2A87fJtGivXr"): MevAgent0slot,
solana.MustPublicKeyFromBase58("HWEoBxYs7ssKuudEjzjmpfJVX7Dvi7wescFsVx2L5yoY"): MevAgentBlocxRoute,
solana.MustPublicKeyFromBase58("7ks326H4LbMVaUC8nW5FpC5EoAf5eK5pf4Dsx4HDQLpq"): MevAgentBlocxRoute,
solana.MustPublicKeyFromBase58("95cfoy472fcQHaw4tPGBTKpn6ZQnfEPfBgDQx6gcRmRg"): MevAgentBlocxRoute,
solana.MustPublicKeyFromBase58("6GZVKMaoWry4UFiydjeQU9nmAxj3hEARAStQ7Hc2z6TB"): MevAgentBlocxRoute,
solana.MustPublicKeyFromBase58("HZTmLyC683y74TW3HtGbNX5orxjm2sPuZBEYwwSgAM8v"): MevAgentBlocxRoute,
solana.MustPublicKeyFromBase58("FogxVNs6Mm2w9rnGL1vkARSwJxvLE8mujTv3LK8RnUhF"): MevAgentBlocxRoute,
solana.MustPublicKeyFromBase58("3UQUKjhMKaY2S6bjcQD6yHB7utcZt5bfarRCmctpRtUd"): MevAgentBlocxRoute,
solana.MustPublicKeyFromBase58("TEMPaMeCRFAS9EKF53Jd6KpHxgL47uWLcpFArU1Fanq"): MevAgentNozomi,
solana.MustPublicKeyFromBase58("noz3jAjPiHuBPqiSPkkugaJDkJscPuRhYnSpbi8UvC4"): MevAgentNozomi,
solana.MustPublicKeyFromBase58("noz3str9KXfpKknefHji8L1mPgimezaiUyCHYMDv1GE"): MevAgentNozomi,
solana.MustPublicKeyFromBase58("noz6uoYCDijhu1V7cutCpwxNiSovEwLdRHPwmgCGDNo"): MevAgentNozomi,
solana.MustPublicKeyFromBase58("noz9EPNcT7WH6Sou3sr3GGjHQYVkN3DNirpbvDkv9YJ"): MevAgentNozomi,
solana.MustPublicKeyFromBase58("nozc5yT15LazbLTFVZzoNZCwjh3yUtW86LoUyqsBu4L"): MevAgentNozomi,
solana.MustPublicKeyFromBase58("nozFrhfnNGoyqwVuwPAW4aaGqempx4PU6g6D9CJMv7Z"): MevAgentNozomi,
solana.MustPublicKeyFromBase58("nozievPk7HyK1Rqy1MPJwVQ7qQg2QoJGyP71oeDwbsu"): MevAgentNozomi,
solana.MustPublicKeyFromBase58("noznbgwYnBLDHu8wcQVCEw6kDrXkPdKkydGJGNXGvL7"): MevAgentNozomi,
solana.MustPublicKeyFromBase58("nozNVWs5N8mgzuD3qigrCG2UoKxZttxzZ85pvAQVrbP"): MevAgentNozomi,
solana.MustPublicKeyFromBase58("nozpEGbwx4BcGp6pvEdAh1JoC2CQGZdU6HbNP1v2p6P"): MevAgentNozomi,
solana.MustPublicKeyFromBase58("nozrhjhkCr3zXT3BiT4WCodYCUFeQvcdUkM7MqhKqge"): MevAgentNozomi,
solana.MustPublicKeyFromBase58("nozrwQtWhEdrA6W8dkbt9gnUaMs52PdAv5byipnadq3"): MevAgentNozomi,
solana.MustPublicKeyFromBase58("nozUacTVWub3cL4mJmGCYjKZTnE9RbdY5AP46iQgbPJ"): MevAgentNozomi,
solana.MustPublicKeyFromBase58("nozWCyTPppJjRuw2fpzDhhWbW355fzosWSzrrMYB1Qk"): MevAgentNozomi,
solana.MustPublicKeyFromBase58("nozWNju6dY353eMkMqURqwQEoM3SFgEKC6psLCSfUne"): MevAgentNozomi,
solana.MustPublicKeyFromBase58("nozxNBgWohjR75vdspfxR5H9ceC7XXH99xpxhVGt3Bb"): MevAgentNozomi,
solana.MustPublicKeyFromBase58("NextbLoCkVtMGcV47JzewQdvBpLqT9TxQFozQkN98pE"): MevAgentNextBlock,
solana.MustPublicKeyFromBase58("NexTbLoCkWykbLuB1NkjXgFWkX9oAtcoagQegygXXA2"): MevAgentNextBlock,
solana.MustPublicKeyFromBase58("NeXTBLoCKs9F1y5PJS9CKrFNNLU1keHW71rfh7KgA1X"): MevAgentNextBlock,
solana.MustPublicKeyFromBase58("NexTBLockJYZ7QD7p2byrUa6df8ndV2WSd8GkbWqfbb"): MevAgentNextBlock,
solana.MustPublicKeyFromBase58("neXtBLock1LeC67jYd1QdAa32kbVeubsfPNTJC1V5At"): MevAgentNextBlock,
solana.MustPublicKeyFromBase58("nEXTBLockYgngeRmRrjDV31mGSekVPqZoMGhQEZtPVG"): MevAgentNextBlock,
solana.MustPublicKeyFromBase58("NEXTbLoCkB51HpLBLojQfpyVAMorm3zzKg7w9NFdqid"): MevAgentNextBlock,
solana.MustPublicKeyFromBase58("nextBLoCkPMgmG8ZgJtABeScP35qLa2AMCNKntAP7Xc"): MevAgentNextBlock,
solana.MustPublicKeyFromBase58("4ACfpUFoaSD9bfPdeu6DBt89gB6ENTeHBXCAi87NhDEE"): MevAgentHelius,
solana.MustPublicKeyFromBase58("D2L6yPZ2FmmmTKPgzaMKdhu6EWZcTpLy1Vhx8uvZe7NZ"): MevAgentHelius,
solana.MustPublicKeyFromBase58("9bnz4RShgq1hAnLnZbP8kbgBg1kEmcJBYQq3gQbmnSta"): MevAgentHelius,
solana.MustPublicKeyFromBase58("5VY91ws6B2hMmBFRsXkoAAdsPHBJwRfBht4DXox3xkwn"): MevAgentHelius,
solana.MustPublicKeyFromBase58("2nyhqdwKcJZR2vcqCyrYsaPVdAnFoJjiksCXJ7hfEYgD"): MevAgentHelius,
solana.MustPublicKeyFromBase58("2q5pghRs6arqVjRvT5gfgWfWcHWmw1ZuCzphgd5KfWGJ"): MevAgentHelius,
solana.MustPublicKeyFromBase58("wyvPkWjVZz1M8fHQnMMCDTQDbkManefNNhweYk5WkcF"): MevAgentHelius,
solana.MustPublicKeyFromBase58("3KCKozbAaF75qEU33jtzozcJ29yJuaLJTy2jFdzUY8bT"): MevAgentHelius,
solana.MustPublicKeyFromBase58("4vieeGHPYPG2MmyPRcYjdiDmmhN3ww7hsFNap8pVN3Ey"): MevAgentHelius,
solana.MustPublicKeyFromBase58("4TQLFNWK8AovT1gFvda5jfw2oJeRMKEmw7aH6MGBJ3or"): MevAgentHelius,
solana.MustPublicKeyFromBase58("node1PqAa3BWWzUnTHVbw8NJHC874zn9ngAkXjgWEej"): MevAgentNode1,
solana.MustPublicKeyFromBase58("node1UzzTxAAeBTpfZkQPJXBAqixsbdth11ba1NXLBG"): MevAgentNode1,
solana.MustPublicKeyFromBase58("node1Qm1bV4fwYnCurP8otJ9s5yrkPq7SPZ5uhj3Tsv"): MevAgentNode1,
solana.MustPublicKeyFromBase58("node1PUber6SFmSQgvf2ECmXsHP5o3boRSGhvJyPMX1"): MevAgentNode1,
solana.MustPublicKeyFromBase58("node1AyMbeqiVN6eoQzEAwCA6Pk826hrdqdAHR7cdJ3"): MevAgentNode1,
solana.MustPublicKeyFromBase58("node1YtWCoTwwVYTFLfS19zquRQzYX332hs1HEuRBjC"): MevAgentNode1,
solana.MustPublicKeyFromBase58("FLasHstqx11M8W56zrSEqkCyhMCCpr6ze6Mjdvqope5s"): MevAgentFlashBlock,
solana.MustPublicKeyFromBase58("FLasHXTqrbNvpWFB6grN47HGZfK6pze9HLNTgbukfPSk"): MevAgentFlashBlock,
solana.MustPublicKeyFromBase58("FLashhsorBmM9dLpuq6qATawcpqk1Y2aqaZfkd48iT3W"): MevAgentFlashBlock,
solana.MustPublicKeyFromBase58("FLASHRzANfcAKDuQ3RXv9hbkBy4WVEKDzoAgxJ56DiE4"): MevAgentFlashBlock,
solana.MustPublicKeyFromBase58("FLAsHZTRcf3Dy1APaz6j74ebdMC6Xx4g6i9YxjyrDybR"): MevAgentFlashBlock,
solana.MustPublicKeyFromBase58("FLAshyAyBcKb39KPxSzXcepiS8iDYUhDGwJcJDPX4g2B"): MevAgentFlashBlock,
solana.MustPublicKeyFromBase58("FLaSHJNm5dWYzEgnHJWWJP5ccu128Mu61NJLxUf7mUXU"): MevAgentFlashBlock,
solana.MustPublicKeyFromBase58("FLaSHR4Vv7sttd6TyDF4yR1bJyAxRwWKbohDytEMu3wL"): MevAgentFlashBlock,
solana.MustPublicKeyFromBase58("FLaShB3iXXTWE1vu9wQsChUKq3HFtpMAhb8kAh1pf1wi"): MevAgentFlashBlock,
solana.MustPublicKeyFromBase58("FLAShWTjcweNT4NSotpjpxAkwxUr2we3eXQGhpTVzRwy"): MevAgentFlashBlock,
solana.MustPublicKeyFromBase58("FjmZZrFvhnqqb9ThCuMVnENaM3JGVuGWNyCAxRJcFpg9"): MevAgentBlockRazor,
solana.MustPublicKeyFromBase58("6No2i3aawzHsjtThw81iq1EXPJN6rh8eSJCLaYZfKDTG"): MevAgentBlockRazor,
solana.MustPublicKeyFromBase58("A9cWowVAiHe9pJfKAj3TJiN9VpbzMUq6E4kEvf5mUT22"): MevAgentBlockRazor,
solana.MustPublicKeyFromBase58("Gywj98ophM7GmkDdaWs4isqZnDdFCW7B46TXmKfvyqSm"): MevAgentBlockRazor,
solana.MustPublicKeyFromBase58("68Pwb4jS7eZATjDfhmTXgRJjCiZmw1L7Huy4HNpnxJ3o"): MevAgentBlockRazor,
solana.MustPublicKeyFromBase58("4ABhJh5rZPjv63RBJBuyWzBK3g9gWMUQdTZP2kiW31V9"): MevAgentBlockRazor,
solana.MustPublicKeyFromBase58("B2M4NG5eyZp5SBQrSdtemzk5TqVuaWGQnowGaCBt8GyM"): MevAgentBlockRazor,
solana.MustPublicKeyFromBase58("5jA59cXMKQqZAVdtopv8q3yyw9SYfiE3vUCbt7p8MfVf"): MevAgentBlockRazor,
solana.MustPublicKeyFromBase58("5YktoWygr1Bp9wiS1xtMtUki1PeYuuzuCF98tqwYxf61"): MevAgentBlockRazor,
solana.MustPublicKeyFromBase58("295Avbam4qGShBYK7E9H5Ldew4B3WyJGmgmXfiWdeeyV"): MevAgentBlockRazor,
solana.MustPublicKeyFromBase58("EDi4rSy2LZgKJX74mbLTFk4mxoTgT6F7HxxzG2HBAFyK"): MevAgentBlockRazor,
solana.MustPublicKeyFromBase58("BnGKHAC386n4Qmv9xtpBVbRaUTKixjBe3oagkPFKtoy6"): MevAgentBlockRazor,
solana.MustPublicKeyFromBase58("Dd7K2Fp7AtoN8xCghKDRmyqr5U169t48Tw5fEd3wT9mq"): MevAgentBlockRazor,
solana.MustPublicKeyFromBase58("AP6qExwrbRgBAVaehg4b5xHENX815sMabtBzUzVB4v8S"): MevAgentBlockRazor,
}
var entryContractAddresses = map[solana.PublicKey]string{
solana.MustPublicKeyFromBase58("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"): EntryContractPumpFunAMM,
solana.MustPublicKeyFromBase58("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"): EntryContractPumpFun,
solana.MustPublicKeyFromBase58("GMgnVFR8Jb39LoXsEVzb3DvBy3ywCmdmJquHUy1Lrkqb"): EntryContractGMGN,
solana.MustPublicKeyFromBase58("Axiom3a2w1UbMt2SMgqSvRiuJFTPusDhwKamNgPTeNQ9"): EntryContractAxiom,
solana.MustPublicKeyFromBase58("AxiomfHaWDemCFBLBayqnEnNwE6b7B2Qz3UmzMpgbMG6"): EntryContractAxiom,
solana.MustPublicKeyFromBase58("AxiomxSitiyXyPjKgJ9XSrdhsydtZsskZTEDam3PxKcC"): EntryContractAxiom,
solana.MustPublicKeyFromBase58("BANANAjs7FJiPQqJTGFzkZJndT9o7UmKiYYGaJz6frGu"): EntryContractBananaGun,
solana.MustPublicKeyFromBase58("MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG"): EntryContractMoonshot,
solana.MustPublicKeyFromBase58("troY36YiPGqMyAYCNbEqYCdN2tb91Zf7bHcQt7KUi61"): EntryContractTrojan,
solana.MustPublicKeyFromBase58("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"): EntryContractRaydiumAMMV4,
solana.MustPublicKeyFromBase58("routeUGWgWzqBWFcrCfv8tritsqukccJPu3q5GPP3xS"): EntryContractRaydiumAMMRouting,
solana.MustPublicKeyFromBase58("CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK"): EntryContractRaydiumConcetratedLiquidity,
solana.MustPublicKeyFromBase58("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C"): EntryContractRaydiumCPMM,
solana.MustPublicKeyFromBase58("LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo"): EntryContractMeteoraDLMM,
solana.MustPublicKeyFromBase58("Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB"): EntryContractMeteoraPools,
solana.MustPublicKeyFromBase58("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"): EntryContractJupiterAggregatorV6,
solana.MustPublicKeyFromBase58("PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu"): EntryContractJupiterPerps,
solana.MustPublicKeyFromBase58("obriQD1zbpyLz95G5n7nJe6a4DPjpFwa5XYPoNm113y"): EntryContractObricV2,
solana.MustPublicKeyFromBase58("1oopBoJG58DgkUVKkEzKgyG9dvRmpgeEm1AVjoHkF78"): EntryContractLoopscale,
solana.MustPublicKeyFromBase58("fragnAis7Bp6FTsMoa6YcH8UffhEw43Ph79qAiK3iF3"): EntryContractFragmetricLiquidStaking,
solana.MustPublicKeyFromBase58("BSfD6SHZigAfDWSjzD5Q41jw8LmKwtmjskPH9XW1mrRW"): EntryContractPhoton,
solana.MustPublicKeyFromBase58("2wT8Yq49kHgDzXuPxZSaeLaH1qbmGXtEyPy64bL7aD3c"): EntryContractLfinitySwapV2,
solana.MustPublicKeyFromBase58("SoLFiHG9TfgtdUXUjWAxi3LtvYuFyDLVhBWxdMZxyCe"): EntryContractSolFi,
solana.MustPublicKeyFromBase58("NUMERUNsFCP3kuNmWZuXtm1AaQCPj9uw6Guv2Ekoi5P"): EntryContractNumeraire,
solana.MustPublicKeyFromBase58("b1oomGGqPKGD6errbyfbVMBuzSC8WtAAYo8MwNafWW1"): EntryContractBloomRouter,
solana.MustPublicKeyFromBase58("6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma"): EntryContractOKXAggregatorV2,
solana.MustPublicKeyFromBase58("FLUXubRmkEi2q6K3Y9kBPg9248ggaZVsoSFhtJHSrm1X"): EntryContractFluxbeamDEX,
solana.MustPublicKeyFromBase58("NoVA1TmDUqksaj2hB1nayFkPysjJbFiU76dT4qPw2wm"): EntryContractNovaBotsProgram,
solana.MustPublicKeyFromBase58("E6YoRP3adE5XYneSseLee15wJshDxCsmyD2WtLvAmfLi"): EntryContractTaggedSearcher,
solana.MustPublicKeyFromBase58("MAyhSmzXzV1pTf7LsNkrNwkWKTo4ougAJ1PPg47MD4e"): EntryContractMayhem,
}

1
convert.go Normal file
View File

@@ -0,0 +1 @@
package pump_parser

10
discriminator.go Normal file
View File

@@ -0,0 +1,10 @@
package pump_parser
import "crypto/sha256"
func calculateDiscriminator(instructionName string) [8]byte {
hash := sha256.Sum256([]byte(instructionName))
var discriminator [8]byte
copy(discriminator[:], hash[:8])
return discriminator
}

106
enum.go Normal file
View File

@@ -0,0 +1,106 @@
package pump_parser
const (
MevAgentJito = "jito"
MevAgent0slot = "0slot"
MevAgentBlocxRoute = "blocxroute"
MevAgentNozomi = "nozomi"
MevAgentNextBlock = "nextblock"
MevAgentHelius = "helius"
MevAgentNode1 = "node1"
MevAgentFlashBlock = "flashBlock"
MevAgentUnknown = "unknown"
MevAgentBlockRazor = "blockrazor"
)
const (
EntryContractPumpFunAMM = "pumpFunAMM"
EntryContractPumpFun = "pumpFun"
EntryContractGMGN = "gmgn"
EntryContractAxiom = "axiom"
EntryContractBananaGun = "bananaGun"
EntryContractMoonshot = "moonshot"
EntryContractTrojan = "trojan"
EntryContractRaydiumAMMV4 = "raydiumAMMV4"
EntryContractRaydiumAMMRouting = "raydiumAMMRouting"
EntryContractRaydiumConcetratedLiquidity = "raydiumConcetratedLiquidity"
EntryContractRaydiumCPMM = "raydiumCPMM"
EntryContractMeteoraDLMM = "meteoraDLMM"
EntryContractMeteoraPools = "meteoraPools"
EntryContractJupiterAggregatorV6 = "jupiterAggregatorV6"
EntryContractJupiterPerps = "jupiterPerps"
EntryContractObricV2 = "obricV2"
EntryContractLoopscale = "loopscale"
EntryContractFragmetricLiquidStaking = "fragmetricLiquidStaking"
EntryContractPhoton = "photon"
EntryContractLfinitySwapV2 = "lfinitySwapV2"
EntryContractSolFi = "solFi"
EntryContractNumeraire = "numeraire"
EntryContractBloomRouter = "bloomRouter"
EntryContractOKXAggregatorV2 = "oKXAggregatorV2"
EntryContractFluxbeamDEX = "fluxbeamDEX"
EntryContractNovaBotsProgram = "novaBotsProgram"
EntryContractTaggedSearcher = "taggedSearcher"
EntryContractMayhem = "pumpMayhem"
EntryContractUnknown = "unknown"
)
const (
PlatformGMGN = "gmgn"
PlatformPhoton = "photon"
PlatformAxiom = "axiom"
PlatformPepe = "pepe" // pepeboost & xxyy
PlatformBullX = "bullx"
PlatformBanana = "banana"
PlatformTrojan = "trojan"
PlatformRaybot = "raybot"
PlatformMoonshot = "moonshot"
PlatformMEVX = "mevx"
PlatformTradeWiz = "tradewiz"
PlatformSolTradingBot = "soltradingbot"
PlatformMoonshotMoney = "moonshot.money"
PlatformMaestro = "maestro"
PlatformBonkBot = "bonkbot"
// used to flag transactions impersonating platform users
PlatformFake = "fake"
PlatformNone = "none"
)
const (
SolProgramPump = "Pump"
SolProgramRaydiumV4 = "RaydiumV4"
SolProgramRaydiumCLMM = "RaydiumCLMM"
SolProgramRaydiumCPMM = "RaydiumCPMM"
SolProgramMeteoraDLMM = "MeteoraDLMM"
SolProgramOrcaWhirPool = "OrcaWhirPool"
SolProgramPumpAMM = "PumpAMM"
SolProgramMeteoraAmmV2 = "MeteoraAmmV2"
SolProgramMeteoraBondingCurve = "MeteoraBondingCurve"
SolProgramMeteoraPools = "MeteoraPools"
SolProgramRaydiumLaunchLab = "RaydiumLaunchLab"
SolProgramRaydiumLaunchLabBonk = "RaydiumLaunchLab-Bonk"
)
// PUMP
const (
NewPump = "pump"
NewRaydium = "raydium"
GraduatedSoon = "graduatedsoon"
)
func GetConditionByProgram(program string) []string {
if program == SolProgramRaydiumLaunchLabBonk {
return []string{SolProgramRaydiumLaunchLabBonk, SolProgramRaydiumV4, SolProgramRaydiumCPMM}
}
return []string{SolProgramPump, SolProgramPumpAMM}
}
const (
TxEventAddLP = "add"
TxEventRemoveLP = "remove"
TxEventBuy = "buy"
TxEventSell = "sell"
TxEventBurn = "burn"
)

7
example/geyser/Makefile Normal file
View File

@@ -0,0 +1,7 @@
protoc:
protoc \
--go_out=./proto \
--go_opt=paths=source_relative \
--go-grpc_out=./proto \
--go-grpc_opt=paths=source_relative \
--proto_path ./proto/ ./proto/*.proto

View File

@@ -0,0 +1,94 @@
package main
import (
"context"
"fmt"
"time"
parser "github.com/thloyi/pump-parser"
example "github.com/thloyi/pump-parser/example"
"github.com/thloyi/pump-parser/example/geyser"
)
func main() {
//pool, err := ants.NewPool(100, ants.WithPreAlloc(true), ants.WithNonblocking(true))
//if err != nil {
// panic(err)
//}
//xt := tracker.NewTwitterTracker(nil) // Initialize Twitter tracker if needed
// laserstream-mainnet-slc.helius-rpc.com:80
ch := make(chan geyser.SubscriptionMessage, 1)
go geyser.RunLoopWithReConnect(context.Background(), "127.0.0.1:10001", parser.SolProgramPump, ch)
// var tokenTxs = make(map[string]*types.Tx)
currentBlock := uint64(0)
for msg := range ch {
if msg.Tx == nil {
block := msg.Block
if block.Slot%100 == 0 {
fmt.Printf("slot: %d, hash: %s, time: %s, height: %d, estimate delay second: %d\n",
block.Slot, block.BlockHash, time.Unix(block.BlockTime, 0).Format("2006-01-02 15:04:05"), block.Height, msg.EstimateDelaySecond)
}
continue
}
ptx := msg.Tx
//data, _ := json.Marshal(tx)
//fmt.Println(string(data))
//continue
//if tx.Token0Address != "HRHLDjqFBhNeyTXUuZQE9gTy5z2112qeQBS9U79NHyyp" {
// continue
//}
//if tx.Program != parser.SolProgramPump {
// continue
//}
if currentBlock == ptx.Block {
continue
}
// 处理交易
txErr, ok := ptx.Err.(*geyser.TransactionError)
var customerErrCode uint32
var instructorErrIndex uint8
if ok {
instructorErrIndex, customerErrCode, _ = txErr.GetCustomErrorCode()
fmt.Printf("now: %s, block: %d, tx: %s, errInstr Code: %d, errInstrIndex: %d, err: %v\n", time.Now().Format("2006-01-02 15:04:05"), ptx.Block, ptx.GetTxHash(), customerErrCode, instructorErrIndex, ptx.Err)
} else {
txs := example.FromTx(ptx, msg.RawTx)
if len(txs) == 0 {
fmt.Printf("tx is empty, block: %d, tx %s \n", ptx.Block, ptx.GetTxHash())
continue
}
printed := false
for _, tx := range txs {
if tx.Program != parser.SolProgramPump {
continue
}
printed = true
fmt.Printf("t: %s, block: %d, hash: %s, signer: %s, program: %s, event: %s, token1: %s, cuPrice: %s, mevAgent: %s, mevFee: %s, platform: %s, platformFee: %s, entryContract: %s, mayhem: %t\n",
time.Now().Format(time.RFC3339Nano),
tx.Block, tx.GetTxHash(), tx.Maker, tx.Program, tx.Event, tx.Token1Amount, tx.CUPrice, tx.MevAgent, tx.MevAgentFee, tx.Platform, tx.PlatformFee, tx.EntryContract, tx.Mayhem)
break
}
if !printed {
continue
}
//fmt.Printf("t: %s, block: %d, hash: %s, signer: %s, program: %s, event: %s, token0: %s, token1: %s, signer before sol :%s, after sol: %s, after token: %s, tokencreator: %s, tokenprogram: %s, mayhem: %t\n",
// time.Now().Format(time.RFC3339Nano),
// tx.Block, tx.GetTxHash(), tx.Maker, tx.Program, tx.Event, tx.Token0Amount.String(), tx.Token1Amount.String(),
// tx.BeforeSolBalance, tx.AfterSOLBalance, tx.AfterSignerToken0Balance, tx.TokenCreator, tx.Token0Program, tx.Mayhem)
}
currentBlock = ptx.Block
//
//if tx.Event == "create" {
// if err := pool.Submit(func() {
// now := time.Now()
// xt.AddToken(tx.Token)
// log.Printf("Add token %s, cost: %s %s %v %v", tx.Token.Address, time.Since(now), tx.Token.Twitter, xt.DuplicateCount(tx.Token.Address), xt.HasTwitter(tx.Token.Address))
// }); err != nil {
// fmt.Println(err)
// }
//}
}
}

277
example/geyser/error.go Normal file
View File

@@ -0,0 +1,277 @@
package geyser
import (
"encoding/binary"
"errors"
)
type TransactionErrorVariant uint32
type InstructionErrorVariant uint32
type InstructionErrorEnum struct {
Index uint8
Variant InstructionErrorVariant
rest []byte
}
type CustomInstructionErrorEnum struct {
Code uint32
}
const (
AccountInUse TransactionErrorVariant = iota
AccountLoadedTwice
AccountNotFound
ProgramAccountNotFound
InsufficientFundsForFee
InvalidAccountForFee
AlreadyProcessed
BlockhashNotFound
InstructionError //InstructionError(u8, InstructionError),
CallChainTooDeep
MissingSignatureForFee
InvalidAccountIndex
SignatureFailure
InvalidProgramForExecution
SanitizeFailure
ClusterMaintenance
AccountBorrowOutstanding
WouldExceedMaxBlockCostLimit
UnsupportedVersion
InvalidWritableAccount
WouldExceedMaxAccountCostLimit
WouldExceedAccountDataBlockLimit
TooManyAccountLocks
AddressLookupTableNotFound
InvalidAddressLookupTableOwner
InvalidAddressLookupTableData
InvalidAddressLookupTableIndex
InvalidRentPayingAccount
WouldExceedMaxVoteCostLimit
WouldExceedAccountDataTotalLimit
DuplicateInstruction //DuplicateInstruction(u8),
/*
InsufficientFundsForRent {
account_index: u8,
},
*/
InsufficientFundsForRent
MaxLoadedAccountsDataSizeExceeded
InvalidLoadedAccountsDataSizeLimit
ResanitizationNeeded
/*
ProgramExecutionTemporarilyRestricted {
account_index: u8,
},
*/
ProgramExecutionTemporarilyRestricted
UnbalancedTransaction
ProgramCacheHitMaxLimit
CommitCancelled
)
const (
GenericError InstructionErrorVariant = iota
/// The arguments provided to a program were invalid
InvalidArgument
/// An instruction's data contents were invalid
InvalidInstructionData
/// An account's data contents was invalid
InvalidAccountData
/// An account's data was too small
AccountDataTooSmall
/// An account's balance was too small to complete the instruction
InsufficientFunds
/// The account did not have the expected program id
IncorrectProgramId
/// A signature was required but not found
MissingRequiredSignature
/// An initialize instruction was sent to an account that has already been initialized.
AccountAlreadyInitialized
/// An attempt to operate on an account that hasn't been initialized.
UninitializedAccount
/// Program's instruction lamport balance does not equal the balance after the instruction
UnbalancedInstruction
/// Program illegally modified an account's program id
ModifiedProgramId
/// Program spent the lamports of an account that doesn't belong to it
ExternalAccountLamportSpend
/// Program modified the data of an account that doesn't belong to it
ExternalAccountDataModified
/// Read-only account's lamports modified
ReadonlyLamportChange
/// Read-only account's data was modified
ReadonlyDataModified
/// An account was referenced more than once in a single instruction
// Deprecated, instructions can now contain duplicate accounts
DuplicateAccountIndex
/// Executable bit on account changed, but shouldn't have
ExecutableModified
/// Rent_epoch account changed, but shouldn't have
RentEpochModified
/// The instruction expected additional account keys
NotEnoughAccountKeys
/// Program other than the account's owner changed the size of the account data
AccountDataSizeChanged
/// The instruction expected an executable account
AccountNotExecutable
/// Failed to borrow a reference to account data, already borrowed
AccountBorrowFailed
/// Account data has an outstanding reference after a program's execution
InstructionAccountBorrowOutstanding
/// The same account was multiply passed to an on-chain program's entrypoint, but the program
/// modified them differently. A program can only modify one instance of the account because
/// the runtime cannot determine which changes to pick or how to merge them if both are modified
DuplicateAccountOutOfSync
/// Allows on-chain programs to implement program-specific error types and see them returned
/// by the Solana runtime. A program-specific error may be any type that is represented as
/// or serialized to a u32 integer.
Custom // Custom(u32),
/// The return value from the program was invalid. Valid errors are either a defined builtin
/// error value or a user-defined error in the lower 32 bits.
InvalidError
/// Executable account's data was modified
ExecutableDataModified
/// Executable account's lamports modified
ExecutableLamportChange
/// Executable accounts must be rent exempt
ExecutableAccountNotRentExempt
/// Unsupported program id
UnsupportedProgramId
/// Cross-program invocation call depth too deep
CallDepth
/// An account required by the instruction is missing
MissingAccount
/// Cross-program invocation reentrancy not allowed for this instruction
ReentrancyNotAllowed
/// Length of the seed is too long for address generation
MaxSeedLengthExceeded
/// Provided seeds do not result in a valid address
InvalidSeeds
/// Failed to reallocate account data of this length
InvalidRealloc
/// Computational budget exceeded
ComputationalBudgetExceeded
/// Cross-program invocation with unauthorized signer or writable account
PrivilegeEscalation
/// Failed to create program execution environment
ProgramEnvironmentSetupFailure
/// Program failed to complete
ProgramFailedToComplete
/// Program failed to compile
ProgramFailedToCompile
/// Account is immutable
Immutable
/// Incorrect authority provided
IncorrectAuthority
/// Failed to serialize or deserialize account data
///
/// Warning: This error should never be emitted by the runtime.
///
/// This error includes strings from the underlying 3rd party Borsh crate
/// which can be dangerous because the error strings could change across
/// Borsh versions. Only programs can use this error because they are
/// consistent across Solana software versions.
/// string values from this error should not be used in
BorshIoError // BorshIoError(String)
// An account does not have enough lamports to be rent-exempt
AccountNotRentExempt
/// Invalid account owner
InvalidAccountOwner
/// Program arithmetic overflowed
ArithmeticOverflow
/// Unsupported sysvar
UnsupportedSysvar
/// Illegal account owner
IllegalOwner
/// Accounts data allocations exceeded the maximum allowed per transaction
MaxAccountsDataAllocationsExceeded
/// Max accounts exceeded
MaxAccountsExceeded
/// Max instruction trace length exceeded
MaxInstructionTraceLengthExceeded
/// Builtin programs must consume compute units
BuiltinProgramsMustConsumeComputeUnits
)
type TransactionError struct {
Variant TransactionErrorVariant
rest []byte
}
var (
ErrInvalidTransactionError = errors.New("invalid transaction error")
NotAnInstructionError = errors.New("not an instruction error")
NotACustomInstructionError = errors.New("not a custom instruction error")
UnsupportedInstructionError = errors.New("unsupported instruction error")
)
func DecodeTransactionError(data []byte) (*TransactionError, error) {
if len(data) < 4 {
return nil, ErrInvalidTransactionError
}
var err TransactionError
variant := binary.LittleEndian.Uint32(data[:4])
if variant > uint32(CommitCancelled) {
return nil, UnsupportedInstructionError
}
err.Variant = TransactionErrorVariant(variant)
err.rest = data[4:]
return &err, nil
}
func (e *TransactionError) GetCustomErrorCode() (uint8, uint32, error) {
instr, err := e.GetInstructionError()
if err != nil {
return 0, 0, err
}
custom, err := instr.Custom()
if err != nil {
return 0, 0, err
}
return instr.Index, custom.Code, nil
}
func (e *TransactionError) GetInstructionError() (*InstructionErrorEnum, error) {
if e.Variant != InstructionError {
return nil, NotAnInstructionError
}
if len(e.rest) < 5 {
return nil, NotAnInstructionError
}
var err InstructionErrorEnum
err.Index = e.rest[0]
variant := binary.LittleEndian.Uint32(e.rest[1:5])
if variant > uint32(BuiltinProgramsMustConsumeComputeUnits) {
return nil, UnsupportedInstructionError
}
err.Variant = InstructionErrorVariant(binary.LittleEndian.Uint32(e.rest[1:5]))
err.rest = e.rest[5:]
return &err, nil
}
func (e *InstructionErrorEnum) Custom() (*CustomInstructionErrorEnum, error) {
if e.Variant != Custom {
return nil, NotACustomInstructionError
}
if len(e.rest) < 4 {
return nil, NotACustomInstructionError
}
var custom CustomInstructionErrorEnum
custom.Code = binary.LittleEndian.Uint32(e.rest[:4])
return &custom, nil
}

22
example/geyser/message.go Normal file
View File

@@ -0,0 +1,22 @@
package geyser
import (
"github.com/thloyi/pump-parser"
)
type BlockInfo struct {
Slot uint64
BlockTime int64
BlockHash string
Height uint64
}
type SubscriptionMessage struct {
EstimateDelaySecond int64
Reconnect bool
Block *BlockInfo
Tx *pump_parser.Tx
RawTx *pump_parser.RawTx
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,272 @@
syntax = "proto3";
import "google/protobuf/timestamp.proto";
import public "solana-storage.proto";
option go_package = "github.com/rpcpool/yellowstone-grpc/examples/golang/proto";
package geyser;
service Geyser {
rpc Subscribe(stream SubscribeRequest) returns (stream SubscribeUpdate) {}
rpc Ping(PingRequest) returns (PongResponse) {}
rpc GetLatestBlockhash(GetLatestBlockhashRequest) returns (GetLatestBlockhashResponse) {}
rpc GetBlockHeight(GetBlockHeightRequest) returns (GetBlockHeightResponse) {}
rpc GetSlot(GetSlotRequest) returns (GetSlotResponse) {}
rpc IsBlockhashValid(IsBlockhashValidRequest) returns (IsBlockhashValidResponse) {}
rpc GetVersion(GetVersionRequest) returns (GetVersionResponse) {}
}
enum CommitmentLevel {
PROCESSED = 0;
CONFIRMED = 1;
FINALIZED = 2;
}
enum SlotStatus {
SLOT_PROCESSED = 0;
SLOT_CONFIRMED = 1;
SLOT_FINALIZED = 2;
SLOT_FIRST_SHRED_RECEIVED = 3;
SLOT_COMPLETED = 4;
SLOT_CREATED_BANK = 5;
SLOT_DEAD = 6;
}
message SubscribeRequest {
map<string, SubscribeRequestFilterAccounts> accounts = 1;
map<string, SubscribeRequestFilterSlots> slots = 2;
map<string, SubscribeRequestFilterTransactions> transactions = 3;
map<string, SubscribeRequestFilterTransactions> transactions_status = 10;
map<string, SubscribeRequestFilterBlocks> blocks = 4;
map<string, SubscribeRequestFilterBlocksMeta> blocks_meta = 5;
map<string, SubscribeRequestFilterEntry> entry = 8;
optional CommitmentLevel commitment = 6;
repeated SubscribeRequestAccountsDataSlice accounts_data_slice = 7;
optional SubscribeRequestPing ping = 9;
optional uint64 from_slot = 11;
}
message SubscribeRequestFilterAccounts {
repeated string account = 2;
repeated string owner = 3;
repeated SubscribeRequestFilterAccountsFilter filters = 4;
optional bool nonempty_txn_signature = 5;
}
message SubscribeRequestFilterAccountsFilter {
oneof filter {
SubscribeRequestFilterAccountsFilterMemcmp memcmp = 1;
uint64 datasize = 2;
bool token_account_state = 3;
SubscribeRequestFilterAccountsFilterLamports lamports = 4;
}
}
message SubscribeRequestFilterAccountsFilterMemcmp {
uint64 offset = 1;
oneof data {
bytes bytes = 2;
string base58 = 3;
string base64 = 4;
}
}
message SubscribeRequestFilterAccountsFilterLamports {
oneof cmp {
uint64 eq = 1;
uint64 ne = 2;
uint64 lt = 3;
uint64 gt = 4;
}
}
message SubscribeRequestFilterSlots {
optional bool filter_by_commitment = 1;
optional bool interslot_updates = 2;
}
message SubscribeRequestFilterTransactions {
optional bool vote = 1;
optional bool failed = 2;
optional string signature = 5;
repeated string account_include = 3;
repeated string account_exclude = 4;
repeated string account_required = 6;
}
message SubscribeRequestFilterBlocks {
repeated string account_include = 1;
optional bool include_transactions = 2;
optional bool include_accounts = 3;
optional bool include_entries = 4;
}
message SubscribeRequestFilterBlocksMeta {}
message SubscribeRequestFilterEntry {}
message SubscribeRequestAccountsDataSlice {
uint64 offset = 1;
uint64 length = 2;
}
message SubscribeRequestPing {
int32 id = 1;
}
message SubscribeUpdate {
repeated string filters = 1;
oneof update_oneof {
SubscribeUpdateAccount account = 2;
SubscribeUpdateSlot slot = 3;
SubscribeUpdateTransaction transaction = 4;
SubscribeUpdateTransactionStatus transaction_status = 10;
SubscribeUpdateBlock block = 5;
SubscribeUpdatePing ping = 6;
SubscribeUpdatePong pong = 9;
SubscribeUpdateBlockMeta block_meta = 7;
SubscribeUpdateEntry entry = 8;
}
google.protobuf.Timestamp created_at = 11;
}
message SubscribeUpdateAccount {
SubscribeUpdateAccountInfo account = 1;
uint64 slot = 2;
bool is_startup = 3;
}
message SubscribeUpdateAccountInfo {
bytes pubkey = 1;
uint64 lamports = 2;
bytes owner = 3;
bool executable = 4;
uint64 rent_epoch = 5;
bytes data = 6;
uint64 write_version = 7;
optional bytes txn_signature = 8;
}
message SubscribeUpdateSlot {
uint64 slot = 1;
optional uint64 parent = 2;
SlotStatus status = 3;
optional string dead_error = 4;
}
message SubscribeUpdateTransaction {
SubscribeUpdateTransactionInfo transaction = 1;
uint64 slot = 2;
}
message SubscribeUpdateTransactionInfo {
bytes signature = 1;
bool is_vote = 2;
solana.storage.ConfirmedBlock.Transaction transaction = 3;
solana.storage.ConfirmedBlock.TransactionStatusMeta meta = 4;
uint64 index = 5;
}
message SubscribeUpdateTransactionStatus {
uint64 slot = 1;
bytes signature = 2;
bool is_vote = 3;
uint64 index = 4;
solana.storage.ConfirmedBlock.TransactionError err = 5;
}
message SubscribeUpdateBlock {
uint64 slot = 1;
string blockhash = 2;
solana.storage.ConfirmedBlock.Rewards rewards = 3;
solana.storage.ConfirmedBlock.UnixTimestamp block_time = 4;
solana.storage.ConfirmedBlock.BlockHeight block_height = 5;
uint64 parent_slot = 7;
string parent_blockhash = 8;
uint64 executed_transaction_count = 9;
repeated SubscribeUpdateTransactionInfo transactions = 6;
uint64 updated_account_count = 10;
repeated SubscribeUpdateAccountInfo accounts = 11;
uint64 entries_count = 12;
repeated SubscribeUpdateEntry entries = 13;
}
message SubscribeUpdateBlockMeta {
uint64 slot = 1;
string blockhash = 2;
solana.storage.ConfirmedBlock.Rewards rewards = 3;
solana.storage.ConfirmedBlock.UnixTimestamp block_time = 4;
solana.storage.ConfirmedBlock.BlockHeight block_height = 5;
uint64 parent_slot = 6;
string parent_blockhash = 7;
uint64 executed_transaction_count = 8;
uint64 entries_count = 9;
}
message SubscribeUpdateEntry {
uint64 slot = 1;
uint64 index = 2;
uint64 num_hashes = 3;
bytes hash = 4;
uint64 executed_transaction_count = 5;
uint64 starting_transaction_index = 6; // added in v1.18, for solana 1.17 value is always 0
}
message SubscribeUpdatePing {}
message SubscribeUpdatePong {
int32 id = 1;
}
// non-streaming methods
message PingRequest {
int32 count = 1;
}
message PongResponse {
int32 count = 1;
}
message GetLatestBlockhashRequest {
optional CommitmentLevel commitment = 1;
}
message GetLatestBlockhashResponse {
uint64 slot = 1;
string blockhash = 2;
uint64 last_valid_block_height = 3;
}
message GetBlockHeightRequest {
optional CommitmentLevel commitment = 1;
}
message GetBlockHeightResponse {
uint64 block_height = 1;
}
message GetSlotRequest {
optional CommitmentLevel commitment = 1;
}
message GetSlotResponse {
uint64 slot = 1;
}
message GetVersionRequest {}
message GetVersionResponse {
string version = 1;
}
message IsBlockhashValidRequest {
string blockhash = 1;
optional CommitmentLevel commitment = 2;
}
message IsBlockhashValidResponse {
uint64 slot = 1;
bool valid = 2;
}

View File

@@ -0,0 +1,344 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.29.3
// source: geyser.proto
package proto
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
Geyser_Subscribe_FullMethodName = "/geyser.Geyser/Subscribe"
Geyser_Ping_FullMethodName = "/geyser.Geyser/Ping"
Geyser_GetLatestBlockhash_FullMethodName = "/geyser.Geyser/GetLatestBlockhash"
Geyser_GetBlockHeight_FullMethodName = "/geyser.Geyser/GetBlockHeight"
Geyser_GetSlot_FullMethodName = "/geyser.Geyser/GetSlot"
Geyser_IsBlockhashValid_FullMethodName = "/geyser.Geyser/IsBlockhashValid"
Geyser_GetVersion_FullMethodName = "/geyser.Geyser/GetVersion"
)
// GeyserClient is the client API for Geyser service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type GeyserClient interface {
Subscribe(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SubscribeRequest, SubscribeUpdate], error)
Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PongResponse, error)
GetLatestBlockhash(ctx context.Context, in *GetLatestBlockhashRequest, opts ...grpc.CallOption) (*GetLatestBlockhashResponse, error)
GetBlockHeight(ctx context.Context, in *GetBlockHeightRequest, opts ...grpc.CallOption) (*GetBlockHeightResponse, error)
GetSlot(ctx context.Context, in *GetSlotRequest, opts ...grpc.CallOption) (*GetSlotResponse, error)
IsBlockhashValid(ctx context.Context, in *IsBlockhashValidRequest, opts ...grpc.CallOption) (*IsBlockhashValidResponse, error)
GetVersion(ctx context.Context, in *GetVersionRequest, opts ...grpc.CallOption) (*GetVersionResponse, error)
}
type geyserClient struct {
cc grpc.ClientConnInterface
}
func NewGeyserClient(cc grpc.ClientConnInterface) GeyserClient {
return &geyserClient{cc}
}
func (c *geyserClient) Subscribe(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SubscribeRequest, SubscribeUpdate], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &Geyser_ServiceDesc.Streams[0], Geyser_Subscribe_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[SubscribeRequest, SubscribeUpdate]{ClientStream: stream}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type Geyser_SubscribeClient = grpc.BidiStreamingClient[SubscribeRequest, SubscribeUpdate]
func (c *geyserClient) Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PongResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(PongResponse)
err := c.cc.Invoke(ctx, Geyser_Ping_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *geyserClient) GetLatestBlockhash(ctx context.Context, in *GetLatestBlockhashRequest, opts ...grpc.CallOption) (*GetLatestBlockhashResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetLatestBlockhashResponse)
err := c.cc.Invoke(ctx, Geyser_GetLatestBlockhash_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *geyserClient) GetBlockHeight(ctx context.Context, in *GetBlockHeightRequest, opts ...grpc.CallOption) (*GetBlockHeightResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetBlockHeightResponse)
err := c.cc.Invoke(ctx, Geyser_GetBlockHeight_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *geyserClient) GetSlot(ctx context.Context, in *GetSlotRequest, opts ...grpc.CallOption) (*GetSlotResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetSlotResponse)
err := c.cc.Invoke(ctx, Geyser_GetSlot_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *geyserClient) IsBlockhashValid(ctx context.Context, in *IsBlockhashValidRequest, opts ...grpc.CallOption) (*IsBlockhashValidResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(IsBlockhashValidResponse)
err := c.cc.Invoke(ctx, Geyser_IsBlockhashValid_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *geyserClient) GetVersion(ctx context.Context, in *GetVersionRequest, opts ...grpc.CallOption) (*GetVersionResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetVersionResponse)
err := c.cc.Invoke(ctx, Geyser_GetVersion_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// GeyserServer is the server API for Geyser service.
// All implementations must embed UnimplementedGeyserServer
// for forward compatibility.
type GeyserServer interface {
Subscribe(grpc.BidiStreamingServer[SubscribeRequest, SubscribeUpdate]) error
Ping(context.Context, *PingRequest) (*PongResponse, error)
GetLatestBlockhash(context.Context, *GetLatestBlockhashRequest) (*GetLatestBlockhashResponse, error)
GetBlockHeight(context.Context, *GetBlockHeightRequest) (*GetBlockHeightResponse, error)
GetSlot(context.Context, *GetSlotRequest) (*GetSlotResponse, error)
IsBlockhashValid(context.Context, *IsBlockhashValidRequest) (*IsBlockhashValidResponse, error)
GetVersion(context.Context, *GetVersionRequest) (*GetVersionResponse, error)
mustEmbedUnimplementedGeyserServer()
}
// UnimplementedGeyserServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedGeyserServer struct{}
func (UnimplementedGeyserServer) Subscribe(grpc.BidiStreamingServer[SubscribeRequest, SubscribeUpdate]) error {
return status.Errorf(codes.Unimplemented, "method Subscribe not implemented")
}
func (UnimplementedGeyserServer) Ping(context.Context, *PingRequest) (*PongResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented")
}
func (UnimplementedGeyserServer) GetLatestBlockhash(context.Context, *GetLatestBlockhashRequest) (*GetLatestBlockhashResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetLatestBlockhash not implemented")
}
func (UnimplementedGeyserServer) GetBlockHeight(context.Context, *GetBlockHeightRequest) (*GetBlockHeightResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetBlockHeight not implemented")
}
func (UnimplementedGeyserServer) GetSlot(context.Context, *GetSlotRequest) (*GetSlotResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetSlot not implemented")
}
func (UnimplementedGeyserServer) IsBlockhashValid(context.Context, *IsBlockhashValidRequest) (*IsBlockhashValidResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method IsBlockhashValid not implemented")
}
func (UnimplementedGeyserServer) GetVersion(context.Context, *GetVersionRequest) (*GetVersionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetVersion not implemented")
}
func (UnimplementedGeyserServer) mustEmbedUnimplementedGeyserServer() {}
func (UnimplementedGeyserServer) testEmbeddedByValue() {}
// UnsafeGeyserServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to GeyserServer will
// result in compilation errors.
type UnsafeGeyserServer interface {
mustEmbedUnimplementedGeyserServer()
}
func RegisterGeyserServer(s grpc.ServiceRegistrar, srv GeyserServer) {
// If the following call pancis, it indicates UnimplementedGeyserServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&Geyser_ServiceDesc, srv)
}
func _Geyser_Subscribe_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(GeyserServer).Subscribe(&grpc.GenericServerStream[SubscribeRequest, SubscribeUpdate]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type Geyser_SubscribeServer = grpc.BidiStreamingServer[SubscribeRequest, SubscribeUpdate]
func _Geyser_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PingRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GeyserServer).Ping(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Geyser_Ping_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GeyserServer).Ping(ctx, req.(*PingRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Geyser_GetLatestBlockhash_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetLatestBlockhashRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GeyserServer).GetLatestBlockhash(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Geyser_GetLatestBlockhash_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GeyserServer).GetLatestBlockhash(ctx, req.(*GetLatestBlockhashRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Geyser_GetBlockHeight_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetBlockHeightRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GeyserServer).GetBlockHeight(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Geyser_GetBlockHeight_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GeyserServer).GetBlockHeight(ctx, req.(*GetBlockHeightRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Geyser_GetSlot_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetSlotRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GeyserServer).GetSlot(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Geyser_GetSlot_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GeyserServer).GetSlot(ctx, req.(*GetSlotRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Geyser_IsBlockhashValid_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(IsBlockhashValidRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GeyserServer).IsBlockhashValid(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Geyser_IsBlockhashValid_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GeyserServer).IsBlockhashValid(ctx, req.(*IsBlockhashValidRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Geyser_GetVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetVersionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GeyserServer).GetVersion(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Geyser_GetVersion_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GeyserServer).GetVersion(ctx, req.(*GetVersionRequest))
}
return interceptor(ctx, in, info, handler)
}
// Geyser_ServiceDesc is the grpc.ServiceDesc for Geyser service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Geyser_ServiceDesc = grpc.ServiceDesc{
ServiceName: "geyser.Geyser",
HandlerType: (*GeyserServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Ping",
Handler: _Geyser_Ping_Handler,
},
{
MethodName: "GetLatestBlockhash",
Handler: _Geyser_GetLatestBlockhash_Handler,
},
{
MethodName: "GetBlockHeight",
Handler: _Geyser_GetBlockHeight_Handler,
},
{
MethodName: "GetSlot",
Handler: _Geyser_GetSlot_Handler,
},
{
MethodName: "IsBlockhashValid",
Handler: _Geyser_IsBlockhashValid_Handler,
},
{
MethodName: "GetVersion",
Handler: _Geyser_GetVersion_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "Subscribe",
Handler: _Geyser_Subscribe_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "geyser.proto",
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,149 @@
syntax = "proto3";
package solana.storage.ConfirmedBlock;
option go_package = "github.com/rpcpool/yellowstone-grpc/examples/golang/proto";
message ConfirmedBlock {
string previous_blockhash = 1;
string blockhash = 2;
uint64 parent_slot = 3;
repeated ConfirmedTransaction transactions = 4;
repeated Reward rewards = 5;
UnixTimestamp block_time = 6;
BlockHeight block_height = 7;
NumPartitions num_partitions = 8;
}
message ConfirmedTransaction {
Transaction transaction = 1;
TransactionStatusMeta meta = 2;
}
message Transaction {
repeated bytes signatures = 1;
Message message = 2;
}
message Message {
MessageHeader header = 1;
repeated bytes account_keys = 2;
bytes recent_blockhash = 3;
repeated CompiledInstruction instructions = 4;
bool versioned = 5;
repeated MessageAddressTableLookup address_table_lookups = 6;
}
message MessageHeader {
uint32 num_required_signatures = 1;
uint32 num_readonly_signed_accounts = 2;
uint32 num_readonly_unsigned_accounts = 3;
}
message MessageAddressTableLookup {
bytes account_key = 1;
bytes writable_indexes = 2;
bytes readonly_indexes = 3;
}
message TransactionStatusMeta {
TransactionError err = 1;
uint64 fee = 2;
repeated uint64 pre_balances = 3;
repeated uint64 post_balances = 4;
repeated InnerInstructions inner_instructions = 5;
bool inner_instructions_none = 10;
repeated string log_messages = 6;
bool log_messages_none = 11;
repeated TokenBalance pre_token_balances = 7;
repeated TokenBalance post_token_balances = 8;
repeated Reward rewards = 9;
repeated bytes loaded_writable_addresses = 12;
repeated bytes loaded_readonly_addresses = 13;
ReturnData return_data = 14;
bool return_data_none = 15;
// Sum of compute units consumed by all instructions.
// Available since Solana v1.10.35 / v1.11.6.
// Set to `None` for txs executed on earlier versions.
optional uint64 compute_units_consumed = 16;
}
message TransactionError {
bytes err = 1;
}
message InnerInstructions {
uint32 index = 1;
repeated InnerInstruction instructions = 2;
}
message InnerInstruction {
uint32 program_id_index = 1;
bytes accounts = 2;
bytes data = 3;
// Invocation stack height of an inner instruction.
// Available since Solana v1.14.6
// Set to `None` for txs executed on earlier versions.
optional uint32 stack_height = 4;
}
message CompiledInstruction {
uint32 program_id_index = 1;
bytes accounts = 2;
bytes data = 3;
}
message TokenBalance {
uint32 account_index = 1;
string mint = 2;
UiTokenAmount ui_token_amount = 3;
string owner = 4;
string program_id = 5;
}
message UiTokenAmount {
double ui_amount = 1;
uint32 decimals = 2;
string amount = 3;
string ui_amount_string = 4;
}
message ReturnData {
bytes program_id = 1;
bytes data = 2;
}
enum RewardType {
Unspecified = 0;
Fee = 1;
Rent = 2;
Staking = 3;
Voting = 4;
}
message Reward {
string pubkey = 1;
int64 lamports = 2;
uint64 post_balance = 3;
RewardType reward_type = 4;
string commission = 5;
}
message Rewards {
repeated Reward rewards = 1;
NumPartitions num_partitions = 2;
}
message UnixTimestamp {
int64 timestamp = 1;
}
message BlockHeight {
uint64 block_height = 1;
}
message NumPartitions {
uint64 num_partitions = 1;
}

60
example/geyser/pump.go Normal file
View File

@@ -0,0 +1,60 @@
package geyser
import (
"fmt"
"github.com/shopspring/decimal"
types "github.com/thloyi/pump-parser"
)
type PumpHandler struct {
callback func(*types.Tx, *types.RawTx)
}
func NewPumpHandler(cb func(*types.Tx, *types.RawTx)) *PumpHandler {
return &PumpHandler{
callback: func(tx *types.Tx, tx2 *types.RawTx) {
//tx.Check(tx2)
cb(tx, tx2)
},
}
}
func (h *PumpHandler) HandleMessage(rawTx *types.RawTx) {
if rawTx.Meta.Err != nil {
// Notify the channel about the failed transaction
beforeSolBalance := decimal.Zero
afterSolBalance := decimal.Zero
if rawTx.Meta.PreBalances != nil && len(rawTx.Meta.PreBalances) > 0 {
beforeSolBalance = decimal.NewFromUint64(rawTx.Meta.PreBalances[0]).Div(decimal.NewFromInt(1e9))
}
if rawTx.Meta.PostBalances != nil && len(rawTx.Meta.PostBalances) > 0 {
afterSolBalance = decimal.NewFromUint64(rawTx.Meta.PostBalances[0]).Div(decimal.NewFromInt(1e9))
}
h.callback(&types.Tx{
TxHash: (*[64]byte)((rawTx.Transaction.Signatures[0][:])),
Err: rawTx.Meta.Err,
Signer: rawTx.GetSigner(),
Block: rawTx.Slot,
BlockIndex: uint64(rawTx.IndexWithinBlock),
BeforeSolBalance: beforeSolBalance,
AfterSOLBalance: afterSolBalance,
}, rawTx)
return
}
parsedTx, err := types.Parser(rawTx)
if err != nil {
fmt.Printf("parser error: %s, block: %d tx: %s\n", err, rawTx.Slot, rawTx.TxHash())
return
}
if len(parsedTx.Swaps) == 0 {
// no swap, ignore
return
}
// fmt.Println(parsedTx.GetTxHash(), len(parsedTx.Swaps))
if h.callback != nil {
h.callback(parsedTx, rawTx)
}
}

View File

@@ -0,0 +1,527 @@
package geyser
import (
"context"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"log"
"time"
solana2 "github.com/gagliardetto/solana-go"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata"
types "github.com/thloyi/pump-parser"
pb "github.com/thloyi/pump-parser/example/geyser/proto"
)
type Handler interface {
HandleMessage(rawTx *types.RawTx)
}
var kacp = keepalive.ClientParameters{
Time: 10 * time.Second, // send pings every 10 seconds if there is no activity
Timeout: time.Second, // wait 1 second for ping ack before considering the connection dead
PermitWithoutStream: true, // send pings even without active streams
}
type Client struct {
ch chan SubscriptionMessage
endpoint string
conn *grpc.ClientConn
ctx context.Context
lastReceiveTime time.Time
backoffFactor float64
subscription *pb.SubscribeRequest
subStatus bool
leastBlock BlockInfo
firstMessage bool
handler Handler
}
func NewClientWithPumpSwap(endpoint string, ch chan SubscriptionMessage) *Client {
var subscription pb.SubscribeRequest
var failed = false
var vote = false
subscription.Transactions = make(map[string]*pb.SubscribeRequestFilterTransactions)
subscription.Transactions["transactions_sub"] = &pb.SubscribeRequestFilterTransactions{
Failed: &failed,
Vote: &vote,
}
subscription.Transactions["transactions_sub"].AccountInclude = []string{
"pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", //Pump AMM
"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", //Pump
}
subscription.BlocksMeta = make(map[string]*pb.SubscribeRequestFilterBlocksMeta)
subscription.BlocksMeta["block_meta"] = &pb.SubscribeRequestFilterBlocksMeta{}
c := &Client{
backoffFactor: 1.5,
ch: ch,
endpoint: endpoint,
lastReceiveTime: time.Now(),
subStatus: false,
subscription: &subscription,
}
c.handler = NewPumpHandler(func(tx *types.Tx, tx2 *types.RawTx) {
c.sendTx(tx, tx2)
})
return c
}
func NewClientWithLaunchLab(endpoint string, ch chan SubscriptionMessage) *Client {
var subscription pb.SubscribeRequest
var failed = false
var vote = false
subscription.Transactions = make(map[string]*pb.SubscribeRequestFilterTransactions)
subscription.Transactions["transactions_sub"] = &pb.SubscribeRequestFilterTransactions{
Failed: &failed,
Vote: &vote,
}
subscription.Transactions["transactions_sub"].AccountInclude = []string{
"LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj", //LaunchLab
"CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C", //CPMM
//"675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8", //V4
}
subscription.BlocksMeta = make(map[string]*pb.SubscribeRequestFilterBlocksMeta)
subscription.BlocksMeta["block_meta"] = &pb.SubscribeRequestFilterBlocksMeta{}
c := &Client{
backoffFactor: 1.5,
ch: ch,
endpoint: endpoint,
lastReceiveTime: time.Now(),
subStatus: false,
subscription: &subscription,
}
c.handler = NewPumpHandler(func(tx *types.Tx, tx2 *types.RawTx) {
c.sendTx(tx, tx2)
})
return c
}
func RunLoopWithReConnect(ctx context.Context, endpoint, program string, ch chan SubscriptionMessage) {
var client *Client
if program == types.SolProgramRaydiumLaunchLab {
client = NewClientWithLaunchLab(endpoint, ch)
} else {
client = NewClientWithPumpSwap(endpoint, ch)
}
for {
select {
case <-ctx.Done():
log.Println("Context done, exiting loop")
return
default:
}
err := client.Connect(ctx)
if err != nil {
log.Printf("Failed to connect: %v", err)
time.Sleep(5 * time.Second)
continue
}
// should not reach here, because Connect will block
panic("geyser already connected, waiting for messages...")
}
}
func (c *Client) SetSubscribe(subscription *pb.SubscribeRequest) {
c.subscription = subscription
}
func (c *Client) Connect(ctx context.Context) error {
c.ctx = ctx
if c.conn == nil {
// 连接到 geyser
conn, err := c.grpcConnect(c.endpoint, true)
if err != nil {
return err
}
c.conn = conn
}
if c.subStatus {
return nil // 已经订阅了
}
// 订阅交易
err := c.grpcSubscribe(ctx, c.conn)
if err != nil {
if c.conn != nil {
c.conn.Close()
}
c.conn = nil
c.subStatus = false
log.Printf("Failed to subscribe: %v", err)
return err
}
return nil
}
func (c *Client) grpcConnect(address string, plaintext bool) (*grpc.ClientConn, error) {
var opts []grpc.DialOption
if plaintext {
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
} else {
pool, _ := x509.SystemCertPool()
creds := credentials.NewClientTLSFromCert(pool, "")
opts = append(opts, grpc.WithTransportCredentials(creds))
}
opts = append(opts, grpc.WithKeepaliveParams(kacp))
log.Println("Starting grpc client, connecting to", address)
conn, err := grpc.NewClient(address, opts...)
if err != nil {
return nil, fmt.Errorf("fail to dial: %v", err)
}
return conn, nil
}
func (c *Client) grpcSubscribe(ctx context.Context, conn *grpc.ClientConn) error {
var err error
client := pb.NewGeyserClient(conn)
//subscription.Transactions["transactions_sub"].AccountExclude = transactionsAccountsExclude
subscriptionJson, err := json.Marshal(c.subscription)
if err != nil {
log.Printf("Failed to marshal subscription request: %v", subscriptionJson)
return err
}
log.Printf("Subscription request: %s", string(subscriptionJson))
// Set up the subscription request
//if *token != "" {
// md := metadata.New(map[string]string{"x-token": *token})
// ctx = metadata.NewOutgoingContext(ctx, md)
//}
md := metadata.New(map[string]string{"x-token": "5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d"})
ctx = metadata.NewOutgoingContext(ctx, md)
stream, err := client.Subscribe(ctx)
if err != nil {
return err
}
err = stream.Send(c.subscription)
if err != nil {
return err
}
c.subStatus = true
c.firstMessage = true
for {
resp, err := stream.Recv()
if err == io.EOF {
return err
}
if err != nil {
return fmt.Errorf("error occurred in receiving update: %s", err)
}
txn := resp.GetTransaction()
if txn == nil {
blockMeta := resp.GetBlockMeta()
if blockMeta != nil && c.ch != nil {
c.sendBlock(blockMeta)
}
continue
}
rawTx, err := ConvertYellowstoneGrpcTransactionToSolanaTransaction(txn, resp.GetCreatedAt().Seconds)
if err != nil {
log.Printf("Failed to convert transaction: %v", err)
continue
}
c.handler.HandleMessage(rawTx)
}
}
func (c *Client) computeDelay(slot uint64) int64 {
if c.leastBlock.Slot == 0 {
return 0
}
if slot < c.leastBlock.Slot {
return 0
}
delay := time.Now().Unix() - c.leastBlock.BlockTime - (4 * int64(slot-c.leastBlock.Slot) / 10)
return delay
}
func (c *Client) sendTx(t *types.Tx, tx *types.RawTx) {
c.ch <- SubscriptionMessage{
Reconnect: c.firstMessage,
EstimateDelaySecond: c.computeDelay(tx.Slot),
Block: nil,
Tx: t,
RawTx: tx,
}
c.firstMessage = false
}
func (c *Client) sendBlock(blockMeta *pb.SubscribeUpdateBlockMeta) {
c.leastBlock.Slot = blockMeta.GetSlot()
c.leastBlock.BlockTime = blockMeta.GetBlockTime().Timestamp
c.leastBlock.BlockHash = blockMeta.Blockhash
c.leastBlock.Height = blockMeta.BlockHeight.BlockHeight
c.ch <- SubscriptionMessage{
EstimateDelaySecond: time.Now().Unix() - blockMeta.GetBlockTime().Timestamp,
Reconnect: c.firstMessage,
Block: &BlockInfo{
Slot: c.leastBlock.Slot,
BlockTime: c.leastBlock.BlockTime,
BlockHash: c.leastBlock.BlockHash,
Height: c.leastBlock.Height,
},
Tx: nil,
RawTx: nil,
}
c.firstMessage = false
}
func ConvertYellowstoneGrpcTransactionToSolanaTransaction(y *pb.SubscribeUpdateTransaction, created int64) (*types.RawTx, error) {
sTx := &types.RawTx{
BlockTime: created,
Slot: y.Slot,
IndexWithinBlock: int64(y.Transaction.Index),
Meta: types.Meta{
Err: nil,
Fee: 0,
InnerInstructions: nil,
LoadedAddresses: types.LoadedAddresses{},
LogMessages: nil,
PostBalances: nil,
PostTokenBalances: nil,
PreBalances: nil,
PreTokenBalances: nil,
Rewards: nil,
},
//Transaction: types.Transaction{
// Message: types.Message{
// AccountKeys: nil,
// AddressTableLookups: nil,
// Header: types.Header{},
// Instructions: nil,
// RecentBlockHash: "",
// },
// Signatures: nil,
//},
//Version: nil,
}
meta := y.Transaction.GetMeta()
yTx := y.Transaction.Transaction
if meta.Err != nil && len(meta.Err.GetErr()) > 0 {
// If the transaction has an error, we set the error in the Meta
transError, err := DecodeTransactionError(meta.Err.GetErr())
if err != nil {
sTx.Meta.Err = err
} else {
sTx.Meta.Err = transError
}
// sTx.Meta.Err = meta.Err.GetErr()
}
sTx.Meta.Fee = meta.Fee
//sTx.Meta.InnerInstructions = meta.InnerInstructions
for _, innerInstr := range meta.InnerInstructions {
var instrs []types.Instruction
for _, instr := range innerInstr.Instructions {
instrs = append(instrs, types.Instruction{
ProgramIDIndex: int(instr.ProgramIdIndex),
Accounts: func() []int {
var out []int
for i := range instr.Accounts {
out = append(out, int(instr.Accounts[i]))
}
return out
}(),
Data: instr.Data,
})
}
sTx.Meta.InnerInstructions = append(sTx.Meta.InnerInstructions, types.InnerInstructions{
Index: int(innerInstr.Index),
Instructions: instrs,
})
}
sTx.Meta.LogMessages = meta.LogMessages
sTx.Meta.PostBalances = meta.PostBalances
sTx.Meta.PostTokenBalances = grpcTokenBalance(meta.PostTokenBalances)
sTx.Meta.PreBalances = meta.PreBalances
sTx.Meta.PreTokenBalances = grpcTokenBalance(meta.PreTokenBalances)
sTx.Meta.Rewards = nil
sTx.Meta.LoadedAddresses.Readonly = byteSlicesToKeySlices(meta.LoadedReadonlyAddresses)
sTx.Meta.LoadedAddresses.Writable = byteSlicesToKeySlices(meta.LoadedWritableAddresses)
// copy signatures
for i := range yTx.Signatures {
sTx.Transaction.Signatures = append(sTx.Transaction.Signatures, solana2.SignatureFromBytes(yTx.Signatures[i]))
}
// copy message
sTx.Transaction.Message = types.Message{
RecentBlockHash: solana2.HashFromBytes(yTx.Message.RecentBlockhash).String(),
}
// copy message.AccountKeys
//stopAt := len(yTx.Message.AccountKeys) - sTx.Message.NumLookups()
stopAt := len(yTx.Message.AccountKeys)
for accIndex, acc := range yTx.Message.AccountKeys {
sTx.Transaction.Message.AccountKeys = append(sTx.Transaction.Message.AccountKeys, solana2.PublicKeyFromBytes(acc))
if accIndex == stopAt-1 {
break
}
}
// copy message.Header
sTx.Transaction.Message.Header = types.Header{
NumRequiredSignatures: int(yTx.Message.Header.NumRequiredSignatures),
NumReadonlySignedAccounts: int(yTx.Message.Header.NumReadonlySignedAccounts),
NumReadonlyUnsignedAccounts: int(yTx.Message.Header.NumReadonlyUnsignedAccounts),
}
// copy message.versioned
if yTx.Message.Versioned {
sTx.Version = solana2.MessageVersionV0
} else {
sTx.Version = solana2.MessageVersionLegacy
}
// copy address table lookups
{
tables := map[solana2.PublicKey]solana2.PublicKeySlice{}
writable := byteSlicesToKeySlices(meta.LoadedWritableAddresses)
readonly := byteSlicesToKeySlices(meta.LoadedReadonlyAddresses)
for _, addr := range yTx.Message.AddressTableLookups {
sTx.Transaction.Message.AddressTableLookups = append(sTx.Transaction.Message.AddressTableLookups, solana2.MessageAddressTableLookup{
AccountKey: solana2.PublicKeyFromBytes(addr.AccountKey),
WritableIndexes: addr.WritableIndexes,
ReadonlyIndexes: addr.ReadonlyIndexes,
})
numTakeWritable := len(addr.WritableIndexes)
numTakeReadonly := len(addr.ReadonlyIndexes)
tableKey := solana2.PublicKeyFromBytes(addr.AccountKey)
{
// now need to rebuild the address table taking into account the indexes, and put the keys into the tables
maxIndex := 0
for _, indexB := range addr.WritableIndexes {
index := int(indexB)
if index > maxIndex {
maxIndex = index
}
}
for _, indexB := range addr.ReadonlyIndexes {
index := int(indexB)
if index > maxIndex {
maxIndex = index
}
}
tables[tableKey] = make([]solana2.PublicKey, maxIndex+1)
}
if numTakeWritable > 0 {
writableForTable := writable[:numTakeWritable]
for i, indexB := range addr.WritableIndexes {
index := int(indexB)
tables[tableKey][index] = writableForTable[i]
}
writable = writable[numTakeWritable:]
}
if numTakeReadonly > 0 {
readableForTable := readonly[:numTakeReadonly]
for i, indexB := range addr.ReadonlyIndexes {
index := int(indexB)
tables[tableKey][index] = readableForTable[i]
}
readonly = readonly[numTakeReadonly:]
}
}
}
// copy instructions
for _, instr := range yTx.Message.Instructions {
sTx.Transaction.Message.Instructions = append(sTx.Transaction.Message.Instructions, types.Instruction{
ProgramIDIndex: int(instr.ProgramIdIndex),
Accounts: func() []int {
var out []int
for i := range instr.Accounts {
out = append(out, int(instr.Accounts[i]))
}
return out
}(),
Data: instr.Data,
})
}
// resolve the lookups
//{
// if sTx.Transaction.Message.IsVersioned() {
// // only versioned transactions have address table lookups
// err := sTx.Transaction.Message.ResolveLookups()
// if err != nil {
// return sTx, fmt.Errorf("failed to resolve lookups: %w", err)
// }
// }
//}
return sTx, nil
}
func byteSlicesToKeySlices(keys [][]byte) []solana2.PublicKey {
var out []solana2.PublicKey
for _, key := range keys {
var k solana2.PublicKey
copy(k[:], key)
out = append(out, k)
}
return out
}
func grpcTokenBalance(src []*pb.TokenBalance) []types.TokenBalance {
out := make([]types.TokenBalance, len(src))
for i, tb := range src {
var (
mintAccount solana2.PublicKey
ownerAccount solana2.PublicKey
programIDAccount solana2.PublicKey
)
if tb.Mint != "" {
mintAccount, _ = solana2.PublicKeyFromBase58(tb.Mint)
}
if tb.Owner != "" {
ownerAccount, _ = solana2.PublicKeyFromBase58(tb.Owner)
}
if tb.ProgramId != "" {
programIDAccount, _ = solana2.PublicKeyFromBase58(tb.ProgramId)
}
out[i] = types.TokenBalance{
AccountIndex: int(tb.AccountIndex),
MintAccount: mintAccount,
OwnerAccount: &ownerAccount,
ProgramIDAccount: programIDAccount,
Mint: tb.Mint,
Owner: tb.Owner,
ProgramID: tb.ProgramId,
UITokenAmount: types.UITokenAmount{
Amount: tb.UiTokenAmount.Amount,
Decimals: uint64(tb.UiTokenAmount.Decimals),
UIAmount: tb.UiTokenAmount.UiAmount,
UIAmountString: tb.UiTokenAmount.UiAmountString,
},
}
}
return out
}

229
example/tx.go Normal file
View File

@@ -0,0 +1,229 @@
package example
import (
"time"
"github.com/gagliardetto/solana-go"
"github.com/jackc/pgtype"
"github.com/mr-tron/base58"
"github.com/shopspring/decimal"
parser "github.com/thloyi/pump-parser"
)
type Tx struct {
Err interface{} `json:"err,omitempty" gorm:"-"`
BondingCurve string `json:"bonding_curve" gorm:"-"`
PairAddress string `json:"pair_address"`
Maker string `json:"maker"`
Token0Address string `json:"token0_address"`
Token0Program string `json:"token0_program" gorm:"-"`
Token0Decimals uint8 `json:"token0_decimals" gorm:"-"`
Token1Address string `json:"token1_address"`
Token0Amount decimal.Decimal `json:"token0Amount" gorm:"column:token0_amount;type:numeric"`
Token1Amount decimal.Decimal `json:"token1Amount" gorm:"column:token1_amount;type:numeric"`
PriceUsd decimal.Decimal `json:"price_usd" gorm:"column:price_usd;type:numeric"`
AmountUsd decimal.Decimal `json:"amount_usd" gorm:"column:amount_usd;type:numeric"`
Block uint64 `json:"block"`
BlockIndex uint64 `json:"index"`
Event string `json:"event"`
CachedTxHash string `json:"tx_hash" gorm:"column:tx_hash"`
txHash *[64]byte `gorm:"-"`
TxIndex uint64 `json:"topic_index"`
Program string `json:"program"`
BlockAt pgtype.Timestamptz `gorm:"column:block_at;default:NULL" json:"block_at"`
CreatedAt *pgtype.Timestamptz `gorm:"autoCreateTime" json:"-"`
TotalSupply string `gorm:"total_supply"`
AfterReserve0 string `gorm:"after_reserve0"`
AfterReserve1 string `gorm:"after_reserve1"`
PositionChange int64 `gorm:"position_change"`
QuoteIsToken0 bool `gorm:"-"`
CurrentPrice decimal.Decimal `gorm:"-"`
TokenCreator string `gorm:"-"`
Platform string `gorm:"column:tx_platform;type:platform;default:'none'" json:"platform"`
MevAgent string `gorm:"column:tx_mev_agent;type:mev_agent;default:'none'" json:"tx_mev_agent"`
MevAgentFee decimal.Decimal `gorm:"column:tx_mev_agent_fee;type:numeric" json:"tx_mev_agent_fee"`
CUPrice decimal.Decimal `gorm:"column:tx_cu_price;type:numeric" json:"tx_cu_price"`
PlatformFee decimal.Decimal `gorm:"-"`
AfterSignerToken0Balance decimal.Decimal `gorm:"-" json:"-"`
BeforeSolBalance decimal.Decimal `gorm:"-" json:"-"`
AfterSOLBalance decimal.Decimal `gorm:"column:after_sol_balance;type:numeric" json:"after_sol_balance"`
EntryContract string `gorm:"column:tx_entry_contract;type:entry_contract;default:'none'" json:"tx_entry_contract"`
Mayhem bool
}
func (tx *Tx) GetTxHash() string {
if tx.CachedTxHash != "" {
return tx.CachedTxHash
}
if tx.txHash == nil {
return ""
}
tx.CachedTxHash = base58.Encode(tx.txHash[:])
return tx.CachedTxHash
}
func FromTx(tx *parser.Tx, raw *parser.RawTx) []*Tx {
var txs []*Tx = make([]*Tx, 0, len(tx.Swaps))
mev, mevFee := tx.CheckMevAgent()
for i, s := range tx.Swaps {
var newTx *Tx
platform, platformFee := tx.CheckPlatform(s, raw)
token0Program := s.BaseTokenProgram
token0Address := s.BaseMint
if s.Program == "Pump" {
newTx = &Tx{
Err: nil,
BondingCurve: s.Pool.String(),
//PairAddress: s.Pool.String(),
Maker: s.User.String(),
Token0Address: s.BaseMint.String(),
Token0Program: s.BaseTokenProgram.String(),
Token0Decimals: s.BaseMintDecimals,
Token1Address: "",
Token0Amount: s.BaseAmount.Div(decimal.NewFromInt(1e6)),
Token1Amount: s.QuoteAmount.Div(decimal.NewFromInt(1e9)),
Block: tx.Block,
BlockIndex: tx.BlockIndex,
Event: s.Event,
CachedTxHash: "",
txHash: tx.TxHash,
TxIndex: uint64(i),
Program: s.Program,
BlockAt: pgtype.Timestamptz{
Time: time.Unix(tx.BlockAt, 0),
},
//CreatedAt: nil,
TotalSupply: "1000000000",
AfterReserve0: s.BaseReserve.Div(decimal.New(1, int32(s.BaseMintDecimals))).String(),
AfterReserve1: s.QuoteReserve.Div(decimal.New(1, int32(s.QuoteMintDecimals))).String(),
QuoteIsToken0: false,
// CurrentPrice: decimal.Decimal{},
TokenCreator: s.Creator.String(),
Platform: platform,
PlatformFee: platformFee,
MevAgent: mev,
MevAgentFee: mevFee,
CUPrice: tx.CUPrice,
AfterSignerToken0Balance: s.UserBaseBalance.Div(decimal.New(1, int32(s.BaseMintDecimals))),
BeforeSolBalance: tx.BeforeSolBalance,
AfterSOLBalance: tx.AfterSOLBalance,
EntryContract: s.CheckEntryContract(),
Mayhem: s.Mayhem,
}
} else if s.Program == "PumpAMM" {
if s.BaseMint.Equals(solana.WrappedSol) {
eventName := s.Event
if s.Event == "buy" {
eventName = "sell"
} else if s.Event == "sell" {
eventName = "buy"
}
token0Program = s.QuoteTokenProgram
token0Address = s.QuoteMint
newTx = &Tx{
Err: nil,
//BondingCurve: s.Pool.String(),
PairAddress: s.Pool.String(),
Maker: s.User.String(),
Token1Address: s.BaseMint.String(),
Token0Program: s.QuoteTokenProgram.String(),
Token0Decimals: s.QuoteMintDecimals,
Token0Address: s.QuoteMint.String(),
Token1Amount: s.BaseAmount.Div(decimal.New(1, int32(s.BaseMintDecimals))),
Token0Amount: s.QuoteAmount.Div(decimal.New(1, int32(s.QuoteMintDecimals))),
Block: tx.Block,
BlockIndex: tx.BlockIndex,
Event: eventName,
CachedTxHash: "",
txHash: tx.TxHash,
TxIndex: uint64(i),
Program: s.Program,
BlockAt: pgtype.Timestamptz{
Time: time.Unix(tx.BlockAt, 0),
},
//CreatedAt: nil,
TotalSupply: "1000000000",
AfterReserve1: s.BaseReserve.Div(decimal.New(1, int32(s.BaseMintDecimals))).String(),
AfterReserve0: s.QuoteReserve.Div(decimal.New(1, int32(s.QuoteMintDecimals))).String(),
QuoteIsToken0: true,
// CurrentPrice: decimal.Decimal{},
TokenCreator: s.Creator.String(),
Platform: platform,
PlatformFee: platformFee,
MevAgent: mev,
MevAgentFee: mevFee,
CUPrice: tx.CUPrice,
AfterSignerToken0Balance: s.UserQuoteBalance.Div(decimal.New(1, int32(s.QuoteMintDecimals))),
BeforeSolBalance: tx.BeforeSolBalance,
AfterSOLBalance: tx.AfterSOLBalance,
EntryContract: s.CheckEntryContract(),
Mayhem: s.Mayhem,
}
} else {
newTx = &Tx{
Err: nil,
//BondingCurve: s.Pool.String(),
PairAddress: s.Pool.String(),
Maker: s.User.String(),
Token0Address: s.BaseMint.String(),
Token0Program: s.BaseTokenProgram.String(),
Token0Decimals: s.BaseMintDecimals,
Token1Address: s.QuoteMint.String(),
Token0Amount: s.BaseAmount.Div(decimal.New(1, int32(s.BaseMintDecimals))),
Token1Amount: s.QuoteAmount.Div(decimal.New(1, int32(s.QuoteMintDecimals))),
Block: tx.Block,
BlockIndex: tx.BlockIndex,
Event: s.Event,
CachedTxHash: "",
txHash: tx.TxHash,
TxIndex: uint64(i),
Program: s.Program,
BlockAt: pgtype.Timestamptz{
Time: time.Unix(tx.BlockAt, 0),
},
//CreatedAt: nil,
TotalSupply: "1000000000",
AfterReserve0: s.BaseReserve.Div(decimal.New(1, int32(s.BaseMintDecimals))).String(),
AfterReserve1: s.QuoteReserve.Div(decimal.New(1, int32(s.QuoteMintDecimals))).String(),
QuoteIsToken0: false,
// CurrentPrice: decimal.Decimal{},
TokenCreator: s.Creator.String(),
Platform: platform,
PlatformFee: platformFee,
MevAgent: mev,
MevAgentFee: mevFee,
CUPrice: tx.CUPrice,
AfterSignerToken0Balance: s.UserBaseBalance.Div(decimal.New(1, int32(s.BaseMintDecimals))),
BeforeSolBalance: tx.BeforeSolBalance,
AfterSOLBalance: tx.AfterSOLBalance,
EntryContract: s.CheckEntryContract(),
Mayhem: s.Mayhem,
}
}
}
if newTx == nil {
continue
}
if newTx.Maker == "HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K" && newTx.EntryContract == "oKXAggregatorV2" {
newTx.Maker = tx.Signer.String()
newTx.AfterSignerToken0Balance = parser.GetTokenBalanceAfterTx(raw, 0, token0Program, token0Address)
}
txs = append(txs, newTx)
}
return txs
}

67
globals.go Normal file
View File

@@ -0,0 +1,67 @@
package pump_parser
import (
"errors"
"github.com/gagliardetto/solana-go"
)
var (
WSOLString = "So11111111111111111111111111111111111111112"
WSOL = solana.MustPublicKeyFromBase58("So11111111111111111111111111111111111111112")
USDC = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
)
const (
SOLDecimals = 9
)
var (
InstructionIgnoredError = errors.New("instruction ignored")
)
type swapParser func(result *RawTx, instr Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error)
type actionParser func(result *RawTx, instr Instruction, innerInstructions InnerInstructions, offset [2]uint, tx *Tx) ([2]uint, error)
//type PumpComplete struct {
// IsMayhem bool
// IsToken2022 bool
//}
//type PumpStatusCache map[solana.PublicKey]PumpComplete
//
//var (
// pumpCompleteCache PumpStatusCache = make(map[solana.PublicKey]PumpComplete)
// pumpCompleteCacheLock sync.Mutex
//)
//
//func getPumpCompleteStatus(pumpToken solana.PublicKey) (PumpComplete, bool) {
// pumpCompleteCacheLock.Lock()
// defer pumpCompleteCacheLock.Unlock()
// status, exists := pumpCompleteCache[pumpToken]
// return status, exists
//}
//
//func setPumpCompleteStatus(pumpToken solana.PublicKey, mayhem bool, token2022 bool) {
// pumpCompleteCacheLock.Lock()
// defer pumpCompleteCacheLock.Unlock()
// pumpCompleteCache[pumpToken] = PumpComplete{
// IsMayhem: mayhem,
// IsToken2022: token2022,
// }
//}
//
//func removePumpCompleteStatus(pumpToken solana.PublicKey) {
// pumpCompleteCacheLock.Lock()
// defer pumpCompleteCacheLock.Unlock()
// delete(pumpCompleteCache, pumpToken)
//}
func isMayhemPump(feeAccount solana.PublicKey) bool {
for _, mayhemFeeAccount := range mayhemFeeAccounts {
if feeAccount.Equals(mayhemFeeAccount) {
return true
}
}
return false
}

46
go.mod Normal file
View File

@@ -0,0 +1,46 @@
module github.com/thloyi/pump-parser
go 1.25.1
require (
github.com/gagliardetto/binary v0.8.0
github.com/gagliardetto/solana-go v1.14.0
github.com/jackc/pgtype v1.14.4
github.com/mr-tron/base58 v1.2.0
github.com/shopspring/decimal v1.4.0
google.golang.org/grpc v1.77.0
google.golang.org/protobuf v1.36.10
)
require (
filippo.io/edwards25519 v1.0.0 // indirect
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
github.com/blendle/zapdriver v1.3.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.9.0 // indirect
github.com/gagliardetto/treeout v0.1.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 // indirect
github.com/streamingfast/logging v0.0.0-20250918142248-ac5a1e292845 // indirect
go.mongodb.org/mongo-driver v1.12.2 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/ratelimit v0.2.0 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/crypto v0.43.0 // indirect
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/term v0.36.0 // indirect
golang.org/x/text v0.30.0 // indirect
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
)

344
go.sum Normal file
View File

@@ -0,0 +1,344 @@
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI=
github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI=
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE=
github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/gagliardetto/binary v0.8.0 h1:U9ahc45v9HW0d15LoN++vIXSJyqR/pWw8DDlhd7zvxg=
github.com/gagliardetto/binary v0.8.0/go.mod h1:2tfj51g5o9dnvsc+fL3Jxr22MuWzYXwx9wEoN0XQ7/c=
github.com/gagliardetto/solana-go v1.14.0 h1:3WfAi70jOOjAJ0deFMjdhFYlLXATF4tOQXsDNWJtOLw=
github.com/gagliardetto/solana-go v1.14.0/go.mod h1:l/qqqIN6qJJPtxW/G1PF4JtcE3Zg2vD2EliZrr9Gn5k=
github.com/gagliardetto/treeout v0.1.4 h1:ozeYerrLCmCubo1TcIjFiOWTTGteOOHND1twdFpgwaw=
github.com/gagliardetto/treeout v0.1.4/go.mod h1:loUefvXTrlRG5rYmJmExNryyBRh8f89VZhmMOyCyqok=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgtype v1.14.4 h1:fKuNiCumbKTAIxQwXfB/nsrnkEI6bPJrrSiMKgbJ2j8=
github.com/jackc/pgtype v1.14.4/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.18.2 h1:xVpYkNR5pk5bMCZGfClbO962UIqVABcAGt7ha1s/FeU=
github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 h1:mPMvm6X6tf4w8y7j9YIt6V9jfWhL6QlbEc7CCmeQlWk=
github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1/go.mod h1:ye2e/VUEtE2BHE+G/QcKkcLQVAEJoYRFj5VUOQatCRE=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091/go.mod h1:VlduQ80JcGJSargkRU4Sg9Xo63wZD/l8A5NC/Uo1/uU=
github.com/streamingfast/logging v0.0.0-20250918142248-ac5a1e292845 h1:VMA0pZ3MI8BErRA3kh8dKJThP5d0Xh5vZVk5yFIgH/A=
github.com/streamingfast/logging v0.0.0-20250918142248-ac5a1e292845/go.mod h1:BtDq81Tyc7H8up5aXNi/I95nPmG3C0PLEqGWY/iWQ2E=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.mongodb.org/mongo-driver v1.12.2 h1:gbWY1bJkkmUB9jjZzcdhOL8O85N9H+Vvsf2yFN0RDws=
go.mongodb.org/mongo-driver v1.12.2/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA=
go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo=
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=

73
meta.go Normal file
View File

@@ -0,0 +1,73 @@
package pump_parser
import (
"github.com/gagliardetto/solana-go"
)
var pumpProgram = solana.MustPublicKeyFromBase58("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
var pumpMigrationAccount = solana.MustPublicKeyFromBase58("39azUYFWPz3VHgKCf3VChUwbpURdCHRxjWVowf5jUJjg")
var mayhemFeeAccounts = []solana.PublicKey{
solana.MustPublicKeyFromBase58("GesfTA3X2arioaHp8bbKdjG9vJtskViWACZoYvxp4twS"),
solana.MustPublicKeyFromBase58("4budycTjhs9fD6xw62VBducVTNgMgJJ5BgtKq7mAZwn6"),
solana.MustPublicKeyFromBase58("4UQeTP1T39KZ9Sfxzo3WR5skgsaP6NZa87BAkuazLEKH"),
solana.MustPublicKeyFromBase58("8SBKzEQU4nLSzcwF4a74F2iaUDQyTfjGndn6qUWBnrpR"),
solana.MustPublicKeyFromBase58("8sNeir4QsLsJdYpc9RZacohhK1Y5FLU3nC5LXgYB4aa6"),
solana.MustPublicKeyFromBase58("Fh9HmeLNUMVCvejxCtCL2DbYaRyBFVJ5xrWkLnMH6fdk"),
solana.MustPublicKeyFromBase58("463MEnMeGyJekNZFQSTUABBEbLnvMTALbT6ZmsxAbAdq"),
solana.MustPublicKeyFromBase58("6AUH3WEHucYZyC61hqpqYUWVto5qA5hjHuNQ32GNnNxA"),
}
var pumpBuyDiscriminator = calculateDiscriminator("global:buy")
var pumpBuyV2Discriminator = calculateDiscriminator("global:buy_exact_sol_in")
var pumpSellDiscriminator = calculateDiscriminator("global:sell")
var pumpCreateDiscriminator = calculateDiscriminator("global:create")
var pumpCreateV2Discriminator = calculateDiscriminator("global:create_v2")
var pumpAdminSetCreatorDiscriminator = calculateDiscriminator("global:admin_set_creator")
var pumpMigrateDiscriminator = calculateDiscriminator("global:migrate")
var pumpEventDiscriminator = [8]byte{228, 69, 165, 46, 81, 203, 154, 29}
var pumpTradeEventDiscriminator = [16]byte{228, 69, 165, 46, 81, 203, 154, 29, 189, 219, 127, 211, 78, 230, 97, 238}
var pumpCreateEventDiscriminator = [8]byte{27, 114, 169, 77, 222, 235, 99, 118}
var pumpCompleteEventDiscriminator = [8]byte{95, 114, 97, 156, 212, 46, 152, 8}
var pumpMigrateEventDiscriminator = calculateDiscriminator("event:CompletePumpAmmMigrationEvent")
var pumpBuyEventDiscriminator = [8]byte{189, 219, 127, 211, 78, 230, 97, 238}
var (
pumpAmmProgram = solana.MustPublicKeyFromBase58("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
wSolMint = solana.MustPublicKeyFromBase58("So11111111111111111111111111111111111111112")
)
var (
pumpAmmBuyDiscriminator = calculateDiscriminator("global:buy")
pumpAmmSellDiscriminator = calculateDiscriminator("global:sell")
pumpAmmCreateDiscriminator = calculateDiscriminator("global:create_pool")
pumpAmmWithdrawDiscriminator = calculateDiscriminator("global:withdraw")
pumpAmmDepositDiscriminator = calculateDiscriminator("global:deposit")
// UnParsedDiscriminator is the discriminator for unparsed data.
pumpAmmCreateConfigDiscriminator = calculateDiscriminator("global:create_config")
pumpAmmExtendAccountDiscriminator = calculateDiscriminator("global:extend_account")
pumpAmmUpdateFeeConfigDiscriminator = calculateDiscriminator("global:update_fee_config")
pumpAmmDisableDiscriminator = calculateDiscriminator("global:disable")
pumpAmmEventDiscriminator = [8]byte{228, 69, 165, 46, 81, 203, 154, 29}
pumpAmmBuyEventDiscriminator = calculateDiscriminator("event:BuyEvent")
pumpAmmSellEventDiscriminator = calculateDiscriminator("event:SellEvent")
pumpAmmCreateEventDiscriminator = calculateDiscriminator("event:CreatePoolEvent")
pumpAmmWithdrawEventDiscriminator = calculateDiscriminator("event:WithdrawEvent")
pumpAmmDepositEventDiscriminator = calculateDiscriminator("event:DepositEvent")
)
// Program PumpAmm program ID
var budgGetProgram = solana.MustPublicKeyFromBase58("ComputeBudget111111111111111111111111111111")
var setComputeUnitLimitDiscriminator = uint8(2)
var setComputeUnitPriceDiscriminator = uint8(3)
var transferDiscriminator = uint32(2)
var createAccountWithSeedDiscriminator = uint32(3)
var systemProgram = solana.MustPublicKeyFromBase58("11111111111111111111111111111111")
var raydiumLaunchLabProgramID = solana.MustPublicKeyFromBase58("LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj")

97
parser.go Normal file
View File

@@ -0,0 +1,97 @@
package pump_parser
import (
"errors"
"github.com/gagliardetto/solana-go"
"github.com/shopspring/decimal"
)
var swapPrograms = map[solana.PublicKey]swapParser{
pumpAmmProgram: pumpAmmParser,
pumpProgram: pumpParser,
}
var actionPrograms = map[solana.PublicKey]actionParser{
systemProgram: systemParser,
budgGetProgram: budgetParser,
}
func Parser(rawTx *RawTx) (*Tx, error) {
accountList := rawTx.getAccountList()
var tx = &Tx{
TxHash: (*[64]byte)((rawTx.Transaction.Signatures[0][:])),
Signer: rawTx.GetSigner(),
Block: rawTx.Slot,
BlockIndex: uint64(rawTx.IndexWithinBlock),
BlockAt: rawTx.BlockTime,
BeforeSolBalance: decimal.NewFromUint64(rawTx.Meta.PreBalances[0]).Div(decimal.NewFromInt(1e9)),
AfterSOLBalance: decimal.NewFromUint64(rawTx.Meta.PostBalances[0]).Div(decimal.NewFromInt(1e9)),
}
var innersMap = make(map[int]InnerInstructions)
for _, inner := range rawTx.Meta.InnerInstructions {
innersMap[inner.Index] = inner
}
for i, instr := range rawTx.Transaction.Message.Instructions {
programAccount := accountList[instr.ProgramIDIndex]
if p, exists := swapPrograms[programAccount]; exists {
swaps, _, err := p(rawTx, instr, innersMap[i], [2]uint{uint(i), uint(0)})
if err != nil {
if errors.Is(err, InstructionIgnoredError) {
continue
}
return nil, err
}
tx.Swaps = append(tx.Swaps, swaps...)
} else if p, exists := actionPrograms[programAccount]; exists {
_, err := p(rawTx, instr, innersMap[i], [2]uint{uint(i), uint(0)}, tx)
if err != nil {
if errors.Is(err, InstructionIgnoredError) {
continue
}
return nil, err
}
} else {
ii := i
// unknown program, parser inner instructions
innerLength := len(innersMap[i].Instructions)
for j := 1; j <= innerLength; {
innerInstr := innersMap[i].Instructions[j-1]
innerProgramAccount := accountList[innerInstr.ProgramIDIndex]
if p, exists := swapPrograms[innerProgramAccount]; exists {
swaps, offset, err := p(rawTx, innerInstr, innersMap[i], [2]uint{uint(i), uint(j)})
if err != nil {
if errors.Is(err, InstructionIgnoredError) {
j = int(offset[1])
continue
}
return nil, err
}
tx.Swaps = append(tx.Swaps, swaps...)
j = int(offset[1])
ii = int(offset[0])
} else if p, exists := actionPrograms[innerProgramAccount]; exists {
offset, err := p(rawTx, innerInstr, innersMap[i], [2]uint{uint(i), uint(j)}, tx)
if err != nil {
if errors.Is(err, InstructionIgnoredError) {
j = int(offset[1])
continue
}
return nil, err
}
j = int(offset[1])
ii = int(offset[0])
} else {
j++
}
if j > innerLength || ii > i {
break
}
}
}
}
return tx, nil
}

439
pump.go Normal file
View File

@@ -0,0 +1,439 @@
package pump_parser
import (
"bytes"
"fmt"
agbinary "github.com/gagliardetto/binary"
"github.com/gagliardetto/solana-go"
"github.com/shopspring/decimal"
)
func increaseOffset(offset [2]uint) [2]uint {
if offset[1] == 0 {
return [2]uint{offset[0] + 1, offset[1]}
}
return [2]uint{offset[0], offset[1] + 1}
}
// pumpParser // routes pump program instructions to their respective parsers,
// offset is [outerIndex, innerIndex] index of instructions in the transaction,
// if innerIndex == 0 this is outer instruction,if it's an inner instruction, outerIndex is the index of the parent instruction.
func pumpParser(result *RawTx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
if !result.accountList[instruction.ProgramIDIndex].Equals(pumpProgram) {
return nil, increaseOffset(offset), fmt.Errorf("pump program instruction not found, offset, %d, %d", offset[0], offset[1])
}
decode := instruction.Data
if len(decode) < 8 {
offset[1] += 1
return nil, increaseOffset(offset), fmt.Errorf("pump program instruction data too short, offset, %d, %d", offset[0], offset[1])
}
discriminator := *(*[8]byte)(decode[:8])
switch discriminator {
case pumpBuyV2Discriminator, pumpBuyDiscriminator, pumpSellDiscriminator:
return BuyOrSellParser(result, instruction, innerInstructions, offset)
case pumpCreateDiscriminator, pumpCreateV2Discriminator:
return CreateParser(result, instruction, innerInstructions, offset)
case pumpMigrateDiscriminator:
return MigrateParser(result, instruction, innerInstructions, offset)
default:
return nil, increaseOffset(offset), InstructionIgnoredError
}
}
type PumpCreateData struct {
Discriminator uint64
Name string
Symbol string
Uri string
Creator solana.PublicKey
}
type PumpCreateV2Data struct {
Discriminator uint64
Name string
Symbol string
Uri string
Creator solana.PublicKey
IsMayhem bool
}
type PumpCreateEvent struct {
Name string
Symbol string
Uri string
Mint solana.PublicKey
BondingCurve solana.PublicKey
User solana.PublicKey
Creator solana.PublicKey
Timestamp int64
VirtualTokenReserves uint64
VirtualSolReserves uint64
RealTokenReserves uint64
TokenTotalSupply uint64
TokenProgram solana.PublicKey
IsMayhemMode bool
}
func getInnerInstructions(innerInstructions InnerInstructions, offset uint) ([]Instruction, error) {
var inners []Instruction
var prefixLen = offset
if prefixLen > uint(len(innerInstructions.Instructions)) {
return nil, fmt.Errorf("error inner instruction index out of range")
}
if prefixLen == 0 {
inners = innerInstructions.Instructions
} else {
inners = innerInstructions.Instructions[prefixLen:]
}
return inners, nil
}
func CreateParser(result *RawTx, instr Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
var entryContract = result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
var programIndex = instr.ProgramIDIndex
var err error
var createEvent PumpCreateEvent
var prefixLen = offset[1]
inners, err := getInnerInstructions(innerInstructions, prefixLen)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("pump create get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1])
}
for innerIndex, innerInstr := range inners {
if innerInstr.ProgramIDIndex == programIndex && bytes.Equal(innerInstr.Data[:8], pumpEventDiscriminator[:]) && bytes.Equal(innerInstr.Data[8:16], pumpCreateEventDiscriminator[:]) {
err = agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&createEvent)
if offset[1] == 0 {
offset[0] += 1
} else {
offset[1] += uint(innerIndex) + 1 + prefixLen
}
if err != nil {
return nil, offset, fmt.Errorf("pump create event decode error: %v, offset, %d, %d", err, offset[0], offset[1])
}
break
}
}
if createEvent == (PumpCreateEvent{}) {
return nil, increaseOffset(offset), fmt.Errorf("pump create event not found, offset, %d, %d", offset[0], offset[1])
}
userIndex := 0
if bytes.HasPrefix(instr.Data, pumpCreateV2Discriminator[:]) {
userIndex = instr.Accounts[5]
} else if bytes.HasPrefix(instr.Data, pumpCreateDiscriminator[:]) {
userIndex = instr.Accounts[7]
}
userBase := getAccountBalanceAfterTx(result, userIndex)
userQuote, _ := GetSolAfterTx(result, userIndex)
return []Swap{
{
Program: SolProgramPump,
Event: "create",
Pool: createEvent.BondingCurve,
BaseMint: createEvent.Mint,
QuoteMint: solana.PublicKey{},
BaseTokenProgram: createEvent.TokenProgram,
QuoteTokenProgram: solana.PublicKey{},
Creator: createEvent.Creator,
BaseMintDecimals: 6,
QuoteMintDecimals: 9,
User: createEvent.User,
BaseAmount: decimal.Zero,
QuoteAmount: decimal.Zero,
BaseReserve: decimal.NewFromUint64(createEvent.RealTokenReserves),
QuoteReserve: decimal.Zero,
Mayhem: createEvent.IsMayhemMode,
UserBaseBalance: userBase,
UserQuoteBalance: decimal.NewFromUint64(userQuote),
EntryContract: entryContract,
},
}, offset, nil
}
type PumpTradeEvent struct {
Mint solana.PublicKey
SolAmount uint64
TokenAmount uint64
IsBuy bool
User solana.PublicKey
Timestamp int64
VirtualSolReserves uint64
VirtualTokenReserves uint64
RealSolReserves uint64
RealTokenReserves uint64
FeeRecipient solana.PublicKey
FeeBasisPoints uint64
Fee uint64
Creator solana.PublicKey
}
type CompleteEvent struct {
User solana.PublicKey
Mint solana.PublicKey
BondingCurve solana.PublicKey
Timestamp int64
}
func BuyOrSellParser(result *RawTx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
var entryContract = result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
var err error
var programIndex = instruction.ProgramIDIndex
var (
tradeEvent PumpTradeEvent
completeEvent CompleteEvent
completed bool
newoffset [2]uint
)
var prefixLen = offset[1]
inners, err := getInnerInstructions(innerInstructions, prefixLen)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("pump create get inner instructions error: %v,offset, %d, %d", err, offset[0], offset[1])
}
for innerIndex, innerInstr := range inners {
if innerInstr.ProgramIDIndex == programIndex && bytes.Equal(innerInstr.Data[:8], pumpEventDiscriminator[:]) {
if bytes.Equal(innerInstr.Data[8:16], pumpTradeEventDiscriminator[8:16]) {
err = agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&tradeEvent)
if offset[1] == 0 {
newoffset = [2]uint{offset[0] + 1, offset[1]}
} else {
newoffset = [2]uint{offset[0], prefixLen + uint(innerIndex) + 1}
}
if err != nil {
return nil, newoffset, fmt.Errorf("pump buy event decode error: %v, offset, %d, %d", err, offset[0], offset[1])
}
if !tradeEvent.IsBuy {
break
}
} else if bytes.Equal(innerInstr.Data[8:16], pumpCompleteEventDiscriminator[:]) {
err = agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&completeEvent)
if offset[1] == 0 {
newoffset = [2]uint{offset[0] + 1, offset[1]}
} else {
newoffset = [2]uint{offset[0], prefixLen + uint(innerIndex) + 1}
}
if err != nil {
return nil, newoffset, fmt.Errorf("pump completeEvent event decode error: %v, offset, %d, %d", err, offset[0], offset[1])
}
completed = true
break
}
}
}
if tradeEvent == (PumpTradeEvent{}) {
return nil, increaseOffset(offset), fmt.Errorf("pmp buy/sell event not found, offset, %d, %d", offset[0], offset[1])
}
offset = [2]uint{newoffset[0], newoffset[1]}
ataUserIdx := instruction.Accounts[5]
userIndex := instruction.Accounts[6]
userBase := getAccountBalanceAfterTx(result, ataUserIdx)
userQuote, _ := GetSolAfterTx(result, userIndex)
event := ""
baseTokenProgram := solana.TokenProgramID
if tradeEvent.IsBuy {
event = "buy"
baseTokenProgram = result.accountList[instruction.Accounts[8]]
} else {
event = "sell"
baseTokenProgram = result.accountList[instruction.Accounts[9]]
}
swaps := []Swap{
{
Program: SolProgramPump,
Event: event,
Pool: result.accountList[instruction.Accounts[3]],
BaseMint: tradeEvent.Mint,
QuoteMint: solana.PublicKey{},
BaseTokenProgram: baseTokenProgram,
QuoteTokenProgram: solana.PublicKey{},
Creator: tradeEvent.Creator,
BaseMintDecimals: 6,
QuoteMintDecimals: 9,
User: tradeEvent.User,
BaseAmount: decimal.NewFromUint64(tradeEvent.TokenAmount),
QuoteAmount: decimal.NewFromUint64(tradeEvent.SolAmount),
BaseReserve: decimal.NewFromUint64(tradeEvent.RealTokenReserves),
QuoteReserve: decimal.NewFromUint64(tradeEvent.RealSolReserves),
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[1]]),
UserBaseBalance: userBase,
UserQuoteBalance: decimal.NewFromUint64(userQuote),
EntryContract: entryContract,
},
}
if completed {
swaps = append(swaps, Swap{
Program: SolProgramPump,
Event: "complete",
Pool: result.accountList[instruction.Accounts[3]],
BaseMint: tradeEvent.Mint,
QuoteMint: solana.PublicKey{},
BaseTokenProgram: result.accountList[instruction.Accounts[8]],
QuoteTokenProgram: solana.PublicKey{},
Creator: tradeEvent.Creator,
BaseMintDecimals: 6,
QuoteMintDecimals: 9,
User: tradeEvent.User,
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[1]]),
UserBaseBalance: userBase,
UserQuoteBalance: decimal.NewFromUint64(userQuote),
EntryContract: entryContract,
})
}
return swaps, offset, nil
}
type MigrateEvent struct {
User solana.PublicKey
Mint solana.PublicKey
//Creator solana.PublicKey
MintAmount uint64
SolAmount uint64
PoolMigrationFee uint64
//CreatorFee uint64
BondingCurve solana.PublicKey
TimeStamp int64
Pool solana.PublicKey
}
func MigrateParser(result *RawTx, instr Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
var entryContract = result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
var err error
programIndex := instr.ProgramIDIndex
ammprogramIdx := 0
for i, b := range result.accountList {
if b.Equals(pumpAmmProgram) {
ammprogramIdx = i
break
}
}
if ammprogramIdx == 0 {
return nil, increaseOffset(offset), fmt.Errorf("pump migrate, amm program id not found in account list, offset, %d, %d", offset[0], offset[1])
}
var prefixLen = offset[1]
inners, err := getInnerInstructions(innerInstructions, prefixLen)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("pump migrate get inner instructions offset, %d, %d", offset[0], offset[1])
}
var (
migrateEvent MigrateEvent
createEvent ammCreatePoolEvent
newoffset [2]uint
)
for innerIndex, innerInstr := range inners {
if innerInstr.ProgramIDIndex == ammprogramIdx && bytes.Equal(innerInstr.Data[:8], pumpAmmEventDiscriminator[:]) {
if bytes.Equal(innerInstr.Data[8:16], pumpAmmCreateEventDiscriminator[:]) {
err = agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&createEvent)
if offset[1] == 0 {
newoffset = [2]uint{offset[0] + 1, offset[1]}
} else {
newoffset = [2]uint{offset[0], prefixLen + uint(innerIndex) + 1}
}
if err != nil {
return nil, newoffset, fmt.Errorf("pump amm createEvent decode error: %v, offset, %d, %d", err, offset[0], offset[1])
}
}
//
} else if innerInstr.ProgramIDIndex == programIndex && bytes.Equal(innerInstr.Data[:8], pumpEventDiscriminator[:]) {
if bytes.Equal(innerInstr.Data[8:16], pumpMigrateEventDiscriminator[:]) {
err = agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&migrateEvent)
if offset[1] == 0 {
newoffset = [2]uint{offset[0] + 1, offset[1]}
} else {
newoffset = [2]uint{offset[0], prefixLen + uint(innerIndex) + 1}
}
if err != nil {
return nil, newoffset, fmt.Errorf("pump buy event decode error: %v, offset, %d, %d", err, offset[0], offset[1])
}
break
}
}
}
if migrateEvent == (MigrateEvent{}) || createEvent == (ammCreatePoolEvent{}) {
offset = [2]uint{newoffset[0], newoffset[1]}
return nil, increaseOffset(offset), InstructionIgnoredError
}
offset = [2]uint{newoffset[0], newoffset[1]}
// verify migrate by checking create pool and migrate event
userIndex := instr.Accounts[5]
ataBondingCurveAccountIndex := instr.Accounts[4]
bc, err := getTokenBalanceAfterTx(result, ataBondingCurveAccountIndex)
if err != nil || bc == nil {
return nil, increaseOffset(offset), fmt.Errorf("pump migrate get bonding curve balance error: %v, offset, %d, %d", err, offset[0], offset[1])
}
baseTokenProgram := bc.ProgramIDAccount
var userBase decimal.Decimal
if result.accountList[userIndex].Equals(pumpMigrationAccount) {
userBase = decimal.Zero
} else {
userBase = GetTokenBalanceAfterTx(result, userIndex, baseTokenProgram, migrateEvent.Mint)
}
userQuote, _ := GetSolAfterTx(result, userIndex)
swaps := []Swap{
{
Program: SolProgramPump,
Event: "migrate",
Pool: migrateEvent.BondingCurve,
BaseMint: migrateEvent.Mint,
QuoteMint: solana.PublicKey{},
BaseTokenProgram: baseTokenProgram,
QuoteTokenProgram: solana.PublicKey{},
Creator: createEvent.Creator,
BaseMintDecimals: 6,
QuoteMintDecimals: 9,
User: migrateEvent.User,
//BaseAmount: decimal.Decimal{},
//QuoteAmount: decimal.Decimal{},
BaseReserve: decimal.NewFromUint64(migrateEvent.MintAmount),
QuoteReserve: decimal.NewFromUint64(migrateEvent.SolAmount),
Mayhem: createEvent.IsMayhemMode,
UserBaseBalance: userBase,
UserQuoteBalance: decimal.NewFromUint64(userQuote),
EntryContract: entryContract,
},
}
swaps = append(swaps, Swap{
Program: SolProgramPumpAMM,
Event: "create",
Pool: migrateEvent.Pool,
BaseMint: migrateEvent.Mint,
QuoteMint: wSolMint,
BaseTokenProgram: baseTokenProgram,
QuoteTokenProgram: solana.TokenProgramID,
Creator: createEvent.Creator,
BaseMintDecimals: 6,
QuoteMintDecimals: 9,
User: migrateEvent.User,
BaseAmount: decimal.NewFromUint64(migrateEvent.MintAmount),
QuoteAmount: decimal.NewFromUint64(migrateEvent.SolAmount),
BaseReserve: decimal.NewFromUint64(migrateEvent.MintAmount),
QuoteReserve: decimal.NewFromUint64(migrateEvent.SolAmount),
Mayhem: createEvent.IsMayhemMode,
UserBaseBalance: userBase,
UserQuoteBalance: decimal.NewFromUint64(userQuote),
EntryContract: entryContract,
})
return swaps, offset, nil
}

513
pumpamm.go Normal file
View File

@@ -0,0 +1,513 @@
package pump_parser
import (
"bytes"
"fmt"
agbinary "github.com/gagliardetto/binary"
"github.com/gagliardetto/solana-go"
"github.com/shopspring/decimal"
)
type ammBuyEvent struct {
TimeStamp int64
BaseAmountOut uint64
MaxQuoteAmountIn uint64
UserBaseTokenReserve uint64
UserQuoteTokenReserve uint64
PoolBaseTokenReserve uint64
PoolQuoteTokenReserve uint64
QuoteAmountIn uint64
LpFeeBasisPoints uint64
LpFee uint64
ProtocolFee uint64
QuoteAmountInWithLpFee uint64
UserQuoteAmountIn uint64
Pool solana.PublicKey
User solana.PublicKey
UserBaseTokenAccount solana.PublicKey
UserQuoteTokenAccount solana.PublicKey
ProtocolFeeRecipient solana.PublicKey
ProtocolFeeRecipientTokenAccount solana.PublicKey
CoinCreator solana.PublicKey
}
type ammCreatePoolEvent struct {
TimeStamp int64
Index uint16
Creator solana.PublicKey
BaseMint solana.PublicKey
QuoteMint solana.PublicKey
BaseMintDecimals uint8
QuoteMintDecimals uint8
BaseAmountIn uint64
QuoteAmountIn uint64
PoolBaseAmount uint64
PoolQuoteAmount uint64
MinimumLiquidity uint64
InitialLiquidity uint64
LpTokenAmountOut uint64
PoolBump uint8
Pool solana.PublicKey
LpMint solana.PublicKey
UserBaseTokenAccount solana.PublicKey
UserQuoteTokenAccount solana.PublicKey
CoinCreator solana.PublicKey
IsMayhemMode bool
}
type ammDepositEvent struct {
TimeStamp int64
LpTokenAmountOut uint64
MaxBaseAmountIn uint64
MaxQuoteAmountIn uint64
UserBaseTokenReserves uint64
UserQuoteTokenReserves uint64
PoolBaseTokenReserves uint64
PoolQuoteTokenReserves uint64
BaseAmountIn uint64
QuoteAmountIn uint64
LpMintSupply uint64
Pool solana.PublicKey
User solana.PublicKey
UserBaseTokenAccount solana.PublicKey
UserQuoteTokenAccount solana.PublicKey
UserPoolTokenAccount solana.PublicKey
}
type ammSellEvent struct {
Timestamp int64
BaseAmountIn uint64
MinQuoteAmountOut uint64
UserBaseTokenReserves uint64
UserQuoteTokenReserves uint64
PoolBaseTokenReserves uint64
PoolQuoteTokenReserves uint64
QuoteAmountOut uint64
LpFeeBasisPoints uint64
LpFee uint64
ProtocolFeeBasisPoints uint64
ProtocolFee uint64
QuoteAmountOutWithoutLpFee uint64
UserQuoteAmountOut uint64
Pool solana.PublicKey
User solana.PublicKey
UserBaseTokenAccount solana.PublicKey
UserQuoteTokenAccount solana.PublicKey
ProtocolFeeRecipient solana.PublicKey
ProtocolFeeRecipientTokenAccount solana.PublicKey
CoinCreator solana.PublicKey
}
type ammWithdrawEvent struct {
Timestamp int64
LpTokenAmountIn uint64
MinBaseAmountOut uint64
MinQuoteAmountOut uint64
UserBaseTokenReserves uint64
UserQuoteTokenReserves uint64
PoolBaseTokenReserves uint64
PoolQuoteTokenReserves uint64
BaseAmountOut uint64
QuoteAmountOut uint64
LpMintSupply uint64
Pool solana.PublicKey
User solana.PublicKey
UserBaseTokenAccount solana.PublicKey
UserQuoteTokenAccount solana.PublicKey
UserPoolTokenAccount solana.PublicKey
}
func pumpAmmParser(result *RawTx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
if !result.accountList[instruction.ProgramIDIndex].Equals(pumpAmmProgram) {
return nil, increaseOffset(offset), fmt.Errorf("pump amm program instruction not found, offset: %d, %d", offset[0], offset[1])
}
decode := instruction.Data
if len(decode) < 8 {
offset[1] += 1
return nil, increaseOffset(offset), fmt.Errorf("pumpamm program instruction data too short, offset: %d, %d", offset[0], offset[1])
}
discriminator := *(*[8]byte)(decode[:8])
switch discriminator {
case pumpAmmCreateDiscriminator:
return ammCreatePoolParser(result, instruction, innerInstructions, offset)
case pumpAmmBuyDiscriminator:
return ammBuyParser(result, instruction, innerInstructions, offset)
case pumpAmmSellDiscriminator:
return ammSellParser(result, instruction, innerInstructions, offset)
case pumpAmmDepositDiscriminator:
return depositParse(result, instruction, innerInstructions, offset)
case pumpAmmWithdrawDiscriminator:
return withdrawParse(result, instruction, innerInstructions, offset)
default:
return nil, increaseOffset(offset), InstructionIgnoredError
}
}
func ammCreatePoolParser(result *RawTx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
var entryContract = result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
var err error
var prefixLen = offset[1]
inners, err := getInnerInstructions(innerInstructions, prefixLen)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("pumpamm create get inner instructions error: %v, offset: %d, %d", err, offset[0], prefixLen)
}
var createEvent ammCreatePoolEvent
for innerIndex, innerInstr := range inners {
if innerInstr.ProgramIDIndex == instruction.ProgramIDIndex &&
bytes.Equal(innerInstr.Data[:8], pumpAmmEventDiscriminator[:]) &&
bytes.Equal(innerInstr.Data[8:16], pumpAmmCreateEventDiscriminator[:]) {
err = agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&createEvent)
if offset[1] == 0 {
offset[0] += 1
} else {
offset[1] += uint(innerIndex) + 1 + prefixLen
}
if err != nil {
return nil, offset, fmt.Errorf("pump amm create pool event decode error: %v, offset: %d, %d", err, offset[0], prefixLen)
}
break
}
}
if createEvent == (ammCreatePoolEvent{}) {
return nil, increaseOffset(offset), fmt.Errorf("pump amm create pool event not found, offset: %d, %d", offset[0], prefixLen)
}
baseTokenProgram := result.accountList[instruction.Accounts[13]]
quoteTokenProgram := result.accountList[instruction.Accounts[14]]
return []Swap{
{
Program: SolProgramPumpAMM,
Event: "create",
Pool: createEvent.Pool,
BaseMint: createEvent.BaseMint,
QuoteMint: createEvent.QuoteMint,
BaseTokenProgram: baseTokenProgram,
QuoteTokenProgram: quoteTokenProgram,
Creator: createEvent.CoinCreator,
BaseMintDecimals: createEvent.BaseMintDecimals,
QuoteMintDecimals: createEvent.QuoteMintDecimals,
User: createEvent.Creator,
BaseAmount: decimal.NewFromUint64(createEvent.BaseAmountIn),
QuoteAmount: decimal.NewFromUint64(createEvent.QuoteAmountIn),
BaseReserve: decimal.NewFromUint64(createEvent.PoolBaseAmount),
QuoteReserve: decimal.NewFromUint64(createEvent.PoolQuoteAmount),
UserBaseBalance: decimal.Decimal{},
UserQuoteBalance: decimal.Decimal{},
EntryContract: entryContract,
Mayhem: createEvent.IsMayhemMode,
},
}, offset, nil
}
func ammBuyParser(result *RawTx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
var entryContract = result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
var err error
var prefixLen = offset[1]
inners, err := getInnerInstructions(innerInstructions, prefixLen)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("pumpamm create get inner instructions error: %v, offset: %d, %d", err, offset[0], prefixLen)
}
var event ammBuyEvent
for innerIndex, innerInstr := range inners {
if innerInstr.ProgramIDIndex == instruction.ProgramIDIndex &&
bytes.Equal(innerInstr.Data[:8], pumpAmmEventDiscriminator[:]) &&
bytes.Equal(innerInstr.Data[8:16], pumpAmmBuyEventDiscriminator[:]) {
err = agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&event)
if offset[1] == 0 {
offset[0] += 1
} else {
offset[1] += uint(innerIndex) + 1 + prefixLen
}
if err != nil {
return nil, offset, fmt.Errorf("pump amm buy pool event decode error: %v, offset: %d, %d", err, offset[0], prefixLen)
}
break
}
}
if event == (ammBuyEvent{}) {
return nil, increaseOffset(offset), fmt.Errorf("pump amm buy event not found, offset: %d, %d", offset[0], prefixLen)
}
baseMint := result.accountList[instruction.Accounts[3]]
quoteMint := result.accountList[instruction.Accounts[4]]
baseTokenProgram := result.accountList[instruction.Accounts[11]]
quoteTokenProgram := result.accountList[instruction.Accounts[12]]
poolBaseAccountIdx := instruction.Accounts[7]
poolQuoteAccountIdx := instruction.Accounts[8]
var (
baseMintDecimals uint8
quoteMintDecimals uint8
)
for _, meta := range result.Meta.PostTokenBalances {
if meta.AccountIndex == poolBaseAccountIdx {
baseMintDecimals = uint8(meta.UITokenAmount.Decimals)
} else if meta.AccountIndex == poolQuoteAccountIdx {
quoteMintDecimals = uint8(meta.UITokenAmount.Decimals)
}
}
return []Swap{
{
Program: SolProgramPumpAMM,
Event: "buy",
Pool: event.Pool,
BaseMint: baseMint,
QuoteMint: quoteMint,
BaseTokenProgram: baseTokenProgram,
QuoteTokenProgram: quoteTokenProgram,
Creator: event.CoinCreator,
BaseMintDecimals: baseMintDecimals,
QuoteMintDecimals: quoteMintDecimals,
User: event.User,
BaseAmount: decimal.NewFromUint64(event.BaseAmountOut),
QuoteAmount: decimal.NewFromUint64(event.UserQuoteAmountIn),
BaseReserve: decimal.NewFromUint64(event.PoolBaseTokenReserve - event.BaseAmountOut),
QuoteReserve: decimal.NewFromUint64(event.PoolQuoteTokenReserve + event.QuoteAmountIn),
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[9]]),
UserBaseBalance: decimal.NewFromUint64(event.UserBaseTokenReserve + event.BaseAmountOut),
UserQuoteBalance: decimal.NewFromUint64(event.UserQuoteTokenReserve - event.UserQuoteAmountIn),
EntryContract: entryContract,
},
}, offset, nil
}
func ammSellParser(result *RawTx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
var entryContract = result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
var err error
var prefixLen = offset[1]
inners, err := getInnerInstructions(innerInstructions, prefixLen)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("pumpamm sell get inner instructions error: %v, offset: %d, %d", err, offset[0], prefixLen)
}
var event ammSellEvent
for innerIndex, innerInstr := range inners {
if innerInstr.ProgramIDIndex == instruction.ProgramIDIndex &&
bytes.Equal(innerInstr.Data[:8], pumpAmmEventDiscriminator[:]) &&
bytes.Equal(innerInstr.Data[8:16], pumpAmmSellEventDiscriminator[:]) {
err = agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&event)
if offset[1] == 0 {
offset[0] += 1
} else {
offset[1] += uint(innerIndex) + 1 + prefixLen
}
if err != nil {
return nil, offset, fmt.Errorf("pump amm sell pool event decode error: %v, offset: %d, %d", err, offset[0], prefixLen)
}
break
}
}
if event == (ammSellEvent{}) {
return nil, increaseOffset(offset), fmt.Errorf("pump amm sell event not found, offset: %d, %d", offset[0], prefixLen)
}
baseMint := result.accountList[instruction.Accounts[3]]
quoteMint := result.accountList[instruction.Accounts[4]]
baseTokenProgram := result.accountList[instruction.Accounts[11]]
quoteTokenProgram := result.accountList[instruction.Accounts[12]]
poolBaseAccountIdx := instruction.Accounts[7]
poolQuoteAccountIdx := instruction.Accounts[8]
var (
baseMintDecimals uint8
quoteMintDecimals uint8
)
for _, meta := range result.Meta.PostTokenBalances {
if meta.AccountIndex == poolBaseAccountIdx {
baseMintDecimals = uint8(meta.UITokenAmount.Decimals)
} else if meta.AccountIndex == poolQuoteAccountIdx {
quoteMintDecimals = uint8(meta.UITokenAmount.Decimals)
}
}
return []Swap{
{
Program: SolProgramPumpAMM,
Event: "sell",
Pool: event.Pool,
BaseMint: baseMint,
QuoteMint: quoteMint,
BaseTokenProgram: baseTokenProgram,
QuoteTokenProgram: quoteTokenProgram,
Creator: event.CoinCreator,
BaseMintDecimals: baseMintDecimals,
QuoteMintDecimals: quoteMintDecimals,
User: event.User,
BaseAmount: decimal.NewFromUint64(event.BaseAmountIn),
QuoteAmount: decimal.NewFromUint64(event.UserQuoteAmountOut),
BaseReserve: decimal.NewFromUint64(event.PoolBaseTokenReserves + event.BaseAmountIn),
QuoteReserve: decimal.NewFromUint64(event.PoolQuoteTokenReserves - event.QuoteAmountOut),
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[9]]),
UserBaseBalance: decimal.NewFromUint64(event.UserBaseTokenReserves - event.BaseAmountIn),
UserQuoteBalance: decimal.NewFromUint64(event.UserQuoteTokenReserves + event.UserQuoteAmountOut),
EntryContract: entryContract,
},
}, offset, nil
}
func depositParse(result *RawTx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
var entryContract = result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
var err error
var prefixLen = offset[1]
inners, err := getInnerInstructions(innerInstructions, prefixLen)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("pumpamm deposit get inner instructions error: %v, offset: %d, %d", err, offset[0], prefixLen)
}
var event ammDepositEvent
for innerIndex, innerInstr := range inners {
if innerInstr.ProgramIDIndex == instruction.ProgramIDIndex &&
bytes.Equal(innerInstr.Data[:8], pumpAmmEventDiscriminator[:]) &&
bytes.Equal(innerInstr.Data[8:16], pumpAmmDepositEventDiscriminator[:]) {
err = agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&event)
if offset[1] == 0 {
offset[0] += 1
} else {
offset[1] += uint(innerIndex) + 1 + prefixLen
}
if err != nil {
return nil, offset, fmt.Errorf("pump amm deposit pool event decode error: %v, offset: %d, %d", err, offset[0], prefixLen)
}
break
}
}
if event == (ammDepositEvent{}) {
return nil, increaseOffset(offset), fmt.Errorf("pump amm deposit event not found, offset: %d, %d", offset[0], prefixLen)
}
var (
poolBaseAccountIdx = instruction.Accounts[9]
poolQuoteAccountIdx = instruction.Accounts[10]
baseMintDecimals uint8
quoteMintDecimals uint8
baseMintProgram solana.PublicKey
quoteMintProgram solana.PublicKey
)
for _, meta := range result.Meta.PostTokenBalances {
if meta.AccountIndex == poolBaseAccountIdx {
baseMintDecimals = uint8(meta.UITokenAmount.Decimals)
baseMintProgram = meta.ProgramIDAccount
if baseMintProgram.IsZero() && meta.ProgramID != "" {
baseMintProgram = solana.MustPublicKeyFromBase58(meta.ProgramID)
}
}
if meta.AccountIndex == poolQuoteAccountIdx {
quoteMintDecimals = uint8(meta.UITokenAmount.Decimals)
quoteMintProgram = meta.ProgramIDAccount
if quoteMintProgram.IsZero() && meta.ProgramID != "" {
quoteMintProgram = solana.MustPublicKeyFromBase58(meta.ProgramID)
}
}
}
return []Swap{
{
Program: SolProgramPumpAMM,
Event: "deposit",
Pool: event.Pool,
BaseMint: result.accountList[instruction.Accounts[3]],
QuoteMint: result.accountList[instruction.Accounts[4]],
BaseTokenProgram: baseMintProgram,
QuoteTokenProgram: quoteMintProgram,
//Creator: solana.PublicKey{},
BaseMintDecimals: baseMintDecimals,
QuoteMintDecimals: quoteMintDecimals,
User: event.User,
BaseAmount: decimal.NewFromUint64(event.BaseAmountIn),
QuoteAmount: decimal.NewFromUint64(event.QuoteAmountIn),
BaseReserve: decimal.NewFromUint64(event.PoolBaseTokenReserves + event.BaseAmountIn),
QuoteReserve: decimal.NewFromUint64(event.PoolQuoteTokenReserves + event.QuoteAmountIn),
//Mayhem: false,
UserBaseBalance: decimal.NewFromUint64(event.UserBaseTokenReserves - event.BaseAmountIn),
UserQuoteBalance: decimal.NewFromUint64(event.UserQuoteTokenReserves - event.QuoteAmountIn),
EntryContract: entryContract,
},
}, offset, nil
}
func withdrawParse(result *RawTx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
var entryContract = result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
var err error
var prefixLen = offset[1]
inners, err := getInnerInstructions(innerInstructions, prefixLen)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("pumpamm withdraw get inner instructions error: %v, offset: %d, %d", err, offset[0], prefixLen)
}
var event ammWithdrawEvent
for innerIndex, innerInstr := range inners {
if innerInstr.ProgramIDIndex == instruction.ProgramIDIndex &&
bytes.Equal(innerInstr.Data[:8], pumpAmmEventDiscriminator[:]) &&
bytes.Equal(innerInstr.Data[8:16], pumpAmmWithdrawEventDiscriminator[:]) {
err = agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&event)
if offset[1] == 0 {
offset[0] += 1
} else {
offset[1] += uint(innerIndex) + 1 + prefixLen
}
if err != nil {
return nil, offset, fmt.Errorf("pump amm withdraw pool event decode error: %v, offset: %d, %d", err, offset[0], prefixLen)
}
break
}
}
if event == (ammWithdrawEvent{}) {
return nil, increaseOffset(offset), fmt.Errorf("pump amm withdraw event not found, offset: %d, %d", offset[0], prefixLen)
}
var (
poolBaseAccountIdx = instruction.Accounts[9]
poolQuoteAccountIdx = instruction.Accounts[10]
baseMintDecimals uint8
quoteMintDecimals uint8
baseMintProgram solana.PublicKey
quoteMintProgram solana.PublicKey
)
for _, meta := range result.Meta.PostTokenBalances {
if meta.AccountIndex == poolBaseAccountIdx {
baseMintDecimals = uint8(meta.UITokenAmount.Decimals)
baseMintProgram = meta.ProgramIDAccount
if baseMintProgram.IsZero() && meta.ProgramID != "" {
baseMintProgram = solana.MustPublicKeyFromBase58(meta.ProgramID)
}
}
if meta.AccountIndex == poolQuoteAccountIdx {
quoteMintDecimals = uint8(meta.UITokenAmount.Decimals)
quoteMintProgram = meta.ProgramIDAccount
if quoteMintProgram.IsZero() && meta.ProgramID != "" {
quoteMintProgram = solana.MustPublicKeyFromBase58(meta.ProgramID)
}
}
}
return []Swap{
{
Program: SolProgramPumpAMM,
Event: "withdraw",
Pool: event.Pool,
BaseMint: result.accountList[instruction.Accounts[3]],
QuoteMint: result.accountList[instruction.Accounts[4]],
BaseTokenProgram: baseMintProgram,
QuoteTokenProgram: quoteMintProgram,
//Creator: solana.PublicKey{},
BaseMintDecimals: baseMintDecimals,
QuoteMintDecimals: quoteMintDecimals,
User: event.User,
BaseAmount: decimal.NewFromUint64(event.BaseAmountOut),
QuoteAmount: decimal.NewFromUint64(event.QuoteAmountOut),
BaseReserve: decimal.NewFromUint64(event.PoolBaseTokenReserves - event.BaseAmountOut),
QuoteReserve: decimal.NewFromUint64(event.PoolQuoteTokenReserves - event.QuoteAmountOut),
//Mayhem: false,
UserBaseBalance: decimal.NewFromUint64(event.UserBaseTokenReserves + event.BaseAmountOut),
UserQuoteBalance: decimal.NewFromUint64(event.UserQuoteTokenReserves + event.QuoteAmountOut),
EntryContract: entryContract,
},
}, offset, nil
}

41
pumpamm_test.go Normal file
View File

@@ -0,0 +1,41 @@
package pump_parser
import (
"encoding/base64"
"fmt"
agbinary "github.com/gagliardetto/binary"
"github.com/gagliardetto/solana-go"
"testing"
)
type Pool struct {
Bump uint8 // 1
Index uint16 // 3
Creator solana.PublicKey // 35
BaseMint solana.PublicKey // 67
QuoteMint solana.PublicKey // 99
LpMint solana.PublicKey // 131
PoolBaseTokenAccount solana.PublicKey // 163
PoolQuoteTokenAccount solana.PublicKey // 195
LpSupply uint64 // 203
CoinCreator solana.PublicKey // 235
IsMayhemMode bool // 236
}
func TestDecodePoolData(t *testing.T) {
var base64Data = "8ZptBBGxbbz9AAB/+s6++xNrbunrNkEpE9IY0qfortUqEOB5s3kKGSvL+9H0YLudD5fPy6QWG5FDfdl3FQRE3H804Ivg5J2OnSHvBpuIV/6rgYT7aH9jRhjANdrEOdwa6ztVmKDwAAAAAAGcWzgf1bP0MRLCJXxkgXyazv6CfdxoJGAh01hfNVqrkGoU6aOndSrbraRyP929gjNuQ4qg/nj2RjtLlBazO47we2q7UVSLFd69kGDUes9pm4n4dftp/lIZS068RLXhIR1niGtZ0AMAAFC4Q0TJ2zOhAXgWLRoYc4eSeu9nlTcupLKiXLyIDytOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="
data, _ := base64.StdEncoding.DecodeString(base64Data)
var pool Pool
fmt.Println("data length:", len(data))
err := agbinary.NewBorshDecoder(data[8:]).Decode(&pool)
if err != nil {
t.Errorf("Failed to decode pool data: %v", err)
}
fmt.Println(pool)
}
func TestAmmBuyEvent(t *testing.T) {
fmt.Println(pumpAmmBuyEventDiscriminator)
}

443
rawtx.go Normal file
View File

@@ -0,0 +1,443 @@
package pump_parser
import (
"encoding/json"
"fmt"
"time"
bin "github.com/gagliardetto/binary"
"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc"
"github.com/jackc/pgtype"
"github.com/shopspring/decimal"
)
func (tx *RawTx) getAccountList() []solana.PublicKey {
if tx.accountList != nil {
return tx.accountList
}
length := len(tx.Transaction.Message.AccountKeys) +
len(tx.Meta.LoadedAddresses.Writable) +
len(tx.Meta.LoadedAddresses.Readonly)
tx.accountList = make([]solana.PublicKey, length)
var i = 0
for _, v := range tx.Transaction.Message.AccountKeys {
tx.accountList[i] = v
i++
}
for _, v := range tx.Meta.LoadedAddresses.Writable {
tx.accountList[i] = v
i++
}
for _, v := range tx.Meta.LoadedAddresses.Readonly {
tx.accountList[i] = v
i++
}
return tx.accountList
}
func (tx *RawTx) GetSigner() solana.PublicKey {
accountList := tx.getAccountList()
if len(accountList) > 0 {
return accountList[0]
}
return solana.PublicKey{}
}
type RPCResponse struct {
JsonRPC string `json:"jsonrpc"`
Result RawTx `json:"result"`
ID int `json:"id"`
}
type RawTx struct {
accountList []solana.PublicKey
BlockTime int64 `json:"blockTime"`
IndexWithinBlock int64 `json:"indexWithinBlock"`
Meta Meta `json:"meta"`
Slot uint64 `json:"slot"`
Transaction Transaction `json:"transaction"`
Version interface{} `json:"version"`
//Platform string `json:"platform,omitempty"`
//PlatformFee decimal.Decimal `json:"-"`
//CUPrice decimal.Decimal `json:"CUPrice,omitempty"`
//MevAgent string `json:"mevAgent,omitempty"`
//MevAgentFee decimal.Decimal `json:"mevAgentFee,omitempty"`
//EntryContract []string `json:"entryContract,omitempty"`
}
func (tx *RawTx) TxHash() string {
if len(tx.Transaction.Signatures) > 0 {
return tx.Transaction.Signatures[0].String()
}
return ""
}
func (tx *RawTx) GetSignerAfterBalance() decimal.Decimal {
if len(tx.Meta.PostBalances) > 0 {
return decimal.New(int64(tx.Meta.PostBalances[0]), -9)
}
return decimal.Zero
}
func (tx *RawTx) GetSignerBeforeBalance() decimal.Decimal {
if len(tx.Meta.PreBalances) > 0 {
return decimal.New(int64(tx.Meta.PreBalances[0]), -9)
}
return decimal.Zero
}
func (tx *RawTx) GetBlockTime() *pgtype.Timestamptz {
t := pgtype.Timestamptz{}
t.Set(time.Unix(tx.BlockTime, 0))
return &t
}
type Instruction struct {
Accounts []int `json:"accounts"`
Data solana.Base58 `json:"data"`
ProgramIDIndex int `json:"programIdIndex"`
StackHeight *int `json:"stackHeight"`
}
type InnerInstructions struct {
Index int `json:"index"`
Instructions []Instruction `json:"instructions"`
}
type LoadedAddresses struct {
Readonly solana.PublicKeySlice `json:"readonly"`
Writable solana.PublicKeySlice `json:"writable"`
}
type UITokenAmount struct {
Amount string `json:"amount"`
Decimals uint64 `json:"decimals"`
UIAmount float64 `json:"uiAmount"`
UIAmountString string `json:"uiAmountString"`
}
type TokenBalance struct {
AccountIndex int `json:"accountIndex"`
MintAccount solana.PublicKey `json:"mint_account"`
OwnerAccount *solana.PublicKey `json:"owner_account"`
ProgramIDAccount solana.PublicKey `json:"programId_account"`
Mint string `json:"mint"`
Owner string `json:"owner"`
ProgramID string `json:"programId"`
UITokenAmount UITokenAmount `json:"uiTokenAmount"`
}
func (tb *TokenBalance) ParseAccount() {
if tb.Mint == "" {
tb.Mint = tb.MintAccount.String()
}
if tb.Owner == "" && tb.OwnerAccount != nil {
tb.Owner = tb.OwnerAccount.String()
}
if tb.ProgramID == "" {
tb.ProgramID = tb.ProgramIDAccount.String()
}
}
type Meta struct {
Err interface{} `json:"err"`
Fee uint64 `json:"fee"`
InnerInstructions []InnerInstructions `json:"innerInstructions"`
LoadedAddresses LoadedAddresses `json:"loadedAddresses"`
LogMessages []string `json:"logMessages"`
PostBalances []uint64 `json:"postBalances"`
PostTokenBalances []TokenBalance `json:"postTokenBalances"`
PreBalances []uint64 `json:"preBalances"`
PreTokenBalances []TokenBalance `json:"preTokenBalances"`
Rewards []interface{} `json:"rewards"`
}
type Header struct {
NumReadonlySignedAccounts int `json:"numReadonlySignedAccounts"`
NumReadonlyUnsignedAccounts int `json:"numReadonlyUnsignedAccounts"`
NumRequiredSignatures int `json:"numRequiredSignatures"`
}
type Message struct {
AccountKeys solana.PublicKeySlice `json:"accountKeys"`
AddressTableLookups solana.MessageAddressTableLookupSlice `json:"addressTableLookups"`
Header Header `json:"header"`
Instructions []Instruction `json:"instructions"`
RecentBlockHash string `json:"recentBlockhash"`
}
type Transaction struct {
Message Message `json:"message"`
Signatures []solana.Signature `json:"signatures"`
}
func (tx *Transaction) UnmarshalJSON(data []byte) error {
if len(data) == 0 || (len(data) == 4 && string(data) == "null") {
// TODO: is this an error?
return nil
}
firstChar := data[0]
switch firstChar {
// Check if first character is `[`, standing for a JSON array.
case '[':
// It's base64 (or similar)
{
var asDecodedBinary solana.Data
err := asDecodedBinary.UnmarshalJSON(data)
if err != nil {
return err
}
asParsedTransaction := new(solana.Transaction)
err = asParsedTransaction.UnmarshalWithDecoder(bin.NewBinDecoder(asDecodedBinary.Content))
if err != nil {
return err
}
tx.Message = Message{
AccountKeys: asParsedTransaction.Message.AccountKeys,
AddressTableLookups: asParsedTransaction.Message.AddressTableLookups,
Header: Header{
NumReadonlySignedAccounts: int(asParsedTransaction.Message.Header.NumReadonlySignedAccounts),
NumReadonlyUnsignedAccounts: int(asParsedTransaction.Message.Header.NumReadonlyUnsignedAccounts),
NumRequiredSignatures: int(asParsedTransaction.Message.Header.NumRequiredSignatures),
},
Instructions: InstructionsFromRpc(asParsedTransaction.Message.Instructions),
RecentBlockHash: asParsedTransaction.Message.RecentBlockhash.String(),
}
tx.Signatures = asParsedTransaction.Signatures
}
case '{':
// It's JSON, most likely.
{
var asParsedTransaction solana.Transaction
err := json.Unmarshal(data, &asParsedTransaction)
if err != nil {
return err
}
tx.Message = Message{
AccountKeys: asParsedTransaction.Message.AccountKeys,
AddressTableLookups: asParsedTransaction.Message.AddressTableLookups,
Header: Header{
NumReadonlySignedAccounts: int(asParsedTransaction.Message.Header.NumReadonlySignedAccounts),
NumReadonlyUnsignedAccounts: int(asParsedTransaction.Message.Header.NumReadonlyUnsignedAccounts),
NumRequiredSignatures: int(asParsedTransaction.Message.Header.NumRequiredSignatures),
},
Instructions: InstructionsFromRpc(asParsedTransaction.Message.Instructions),
RecentBlockHash: asParsedTransaction.Message.RecentBlockhash.String(),
}
tx.Signatures = asParsedTransaction.Signatures
return nil
}
default:
return fmt.Errorf("Unknown kind: %v", data)
}
return nil
}
type ParsedTx struct {
AccountData []struct {
Account string `json:"account"`
NativeBalanceChange float64 `json:"nativeBalanceChange"`
TokenBalanceChanges []interface{} `json:"tokenBalanceChanges"`
} `json:"accountData"`
Description string `json:"description"`
Fee int `json:"fee"`
FeePayer string `json:"feePayer"`
Instructions []struct {
Accounts []string `json:"accounts"`
Data string `json:"data"`
InnerInstructions []struct {
Accounts []string `json:"accounts"`
Data string `json:"data"`
ProgramID string `json:"programId"`
} `json:"innerInstructions"`
ProgramID string `json:"programId"`
} `json:"instructions"`
NativeTransfers []struct {
Amount float64 `json:"amount"`
FromUserAccount string `json:"fromUserAccount"`
ToUserAccount string `json:"toUserAccount"`
} `json:"nativeTransfers"`
Signature string `json:"signature"`
Slot int `json:"slot"`
Source string `json:"source"`
Timestamp int `json:"timestamp"`
TokenTransfers []struct {
FromTokenAccount string `json:"fromTokenAccount"`
FromUserAccount string `json:"fromUserAccount"`
Mint string `json:"mint"`
ToTokenAccount string `json:"toTokenAccount"`
ToUserAccount string `json:"toUserAccount"`
TokenAmount float64 `json:"tokenAmount"`
TokenStandard string `json:"tokenStandard"`
} `json:"tokenTransfers"`
TransactionError interface{} `json:"transactionError"`
Type string `json:"type"`
}
func InstructionsFromRpc(instructions []solana.CompiledInstruction) []Instruction {
var instrs []Instruction = make([]Instruction, len(instructions))
for i, instruction := range instructions {
instrs[i] = Instruction{
Accounts: intSliceFromUint16Slice(instruction.Accounts),
Data: instruction.Data,
ProgramIDIndex: int(instruction.ProgramIDIndex),
}
}
return instrs
}
func InnerInstructionsFromRpc(instructions []rpc.InnerInstruction) []InnerInstructions {
var innerInstructions []InnerInstructions = make([]InnerInstructions, len(instructions))
for i, instruction := range instructions {
//instruction.Instructions
instrs := make([]Instruction, len(instruction.Instructions))
for j, instr := range instruction.Instructions {
instrs[j] = Instruction{
Accounts: intSliceFromUint16Slice(instr.Accounts),
Data: instr.Data,
ProgramIDIndex: int(instr.ProgramIDIndex),
//StackHeight: instr.StackHeight,
}
}
innerInstructions[i] = InnerInstructions{
Index: int(instruction.Index),
Instructions: instrs,
}
}
return innerInstructions
}
func intSliceFromUint16Slice(in []uint16) []int {
out := make([]int, len(in))
for i, v := range in {
out[i] = int(v)
}
return out
}
func getTokenBalanceAfterTx(result *RawTx, accountIndex int) (*TokenBalance, error) {
var preBalance *TokenBalance
for _, pre := range result.Meta.PreTokenBalances {
if pre.AccountIndex == accountIndex {
preBalance = &pre
break
}
}
var postBalance *TokenBalance
for _, post := range result.Meta.PostTokenBalances {
if post.AccountIndex == accountIndex {
post.ParseAccount()
postBalance = &post
break
}
}
if preBalance == nil && postBalance == nil {
return nil, fmt.Errorf("account not found")
}
if postBalance == nil {
preBalance.ParseAccount()
return &TokenBalance{
AccountIndex: preBalance.AccountIndex,
MintAccount: preBalance.MintAccount,
OwnerAccount: preBalance.OwnerAccount,
ProgramIDAccount: preBalance.ProgramIDAccount,
Mint: preBalance.Mint,
Owner: preBalance.Owner,
ProgramID: preBalance.ProgramID,
UITokenAmount: UITokenAmount{
Amount: "0",
Decimals: preBalance.UITokenAmount.Decimals,
UIAmount: 0,
UIAmountString: "0",
},
}, nil
}
return postBalance, nil
}
func getAccountBalanceAfterTx(result *RawTx, accountIndex int) decimal.Decimal {
x, err := getTokenBalanceAfterTx(result, accountIndex)
if err != nil {
return decimal.Zero
}
amount, err := decimal.NewFromString(x.UITokenAmount.Amount)
if err != nil {
return decimal.Zero
}
return amount
}
func GetTokenBalanceAfterTx(result *RawTx, accountIndex int, tokenProgram, mint solana.PublicKey) decimal.Decimal {
ataAccount, _, _ := solana.FindProgramAddress([][]byte{
result.accountList[accountIndex][:],
tokenProgram[:],
mint[:],
},
solana.SPLAssociatedTokenAccountProgramID)
ataIndex := 0
for i, account := range result.accountList {
if account.Equals(ataAccount) {
ataIndex = i
break
}
}
if ataIndex == 0 {
return decimal.Zero
}
x, err := getTokenBalanceAfterTx(result, ataIndex)
if err != nil {
return decimal.Zero
}
amount, err := decimal.NewFromString(x.UITokenAmount.Amount)
if err != nil {
return decimal.Zero
}
return amount
}
func GetSolAfterTx(result *RawTx, accountIndex int) (uint64, error) {
for i, post := range result.Meta.PostBalances {
if i == accountIndex {
return post, nil
}
}
return 0, fmt.Errorf("account not found")
}
func solSplAccount(owner, mint solana.PublicKey) (solana.PublicKey, error) {
ataAddress, _, err := solana.FindAssociatedTokenAddress(owner, mint)
if err != nil {
return solana.PublicKey{}, err
}
return ataAddress, nil
}
func solSpl2022Account(owner, mint solana.PublicKey) (solana.PublicKey, error) {
address, _, err := solana.FindProgramAddress([][]byte{
owner[:],
solana.Token2022ProgramID[:],
mint[:],
},
solana.SPLAssociatedTokenAccountProgramID,
)
return address, err
}
func isAccountOwner(account, owner, mint solana.PublicKey) (bool, error) {
ata, err := solSplAccount(owner, mint)
if err != nil {
return false, err
}
if account == ata {
return true, nil
}
ata, err = solSpl2022Account(owner, mint)
if err != nil {
return false, err
}
return account == ata, nil
}

83
system.go Normal file
View File

@@ -0,0 +1,83 @@
package pump_parser
import (
"encoding/binary"
"fmt"
"github.com/shopspring/decimal"
)
func systemParser(result *RawTx, instr Instruction, innerInstructions InnerInstructions, offset [2]uint, tx *Tx) ([2]uint, error) {
var instruction Instruction
var found bool
if offset[1] == 0 {
instruction = result.Transaction.Message.Instructions[offset[0]]
found = true
} else {
for _, innerInstruction := range result.Meta.InnerInstructions {
if innerInstruction.Index == int(offset[0]) {
instruction = innerInstruction.Instructions[offset[1]-1]
found = true
break
}
}
}
if !found {
return increaseOffset(offset), fmt.Errorf("system program instruction not found, block: %d, tx: %s, outerIndex: %d, innerIndex: %d", result.Slot, result.TxHash(), offset[0], offset[1])
}
decode := instruction.Data
discriminator := binary.LittleEndian.Uint32(decode[0:4])
switch discriminator {
case transferDiscriminator:
return TransferParser(result, instruction, offset, tx, decode[4:])
default:
return increaseOffset(offset), nil
}
}
func TransferParser(result *RawTx, instruction Instruction, offset [2]uint, tx *Tx, decodeData []byte) ([2]uint, error) {
if len(decodeData) < 8 {
return increaseOffset(offset), nil
}
var lamports uint64 = binary.LittleEndian.Uint64(decodeData)
//from := result.accountList[result.Transaction.Message.Instructions[offset[0]].Accounts[0]]
to := result.accountList[instruction.Accounts[1]]
// load platform by to address
platform, ok := platformFeeAddresses[to]
if ok {
if tx.Platform != nil {
tx.Platform[platform] = platformInfo{
Platform: platform,
PlatformFee: decimal.NewFromInt(int64(lamports)).Div(decimal.NewFromInt(1e9)), // solana decimals
}
} else {
tx.Platform = make(map[string]platformInfo)
tx.Platform[platform] = platformInfo{
Platform: platform,
PlatformFee: decimal.NewFromInt(int64(lamports)).Div(decimal.NewFromInt(1e9)), // solana decimals
}
}
} else if offset[1] == 0 {
// load mev agent by to address
mevAgent, ok := mevAgentFeeAddresses[to]
if !ok {
// mark it as unknown
mevAgent = MevAgentUnknown
}
if tx.MevAgent == nil {
tx.MevAgent = make(map[string]mevInfo)
}
tx.MevAgent[mevAgent] = mevInfo{
MevAgent: mevAgent,
MevAgentFee: decimal.NewFromInt(int64(lamports)).Div(decimal.NewFromInt(1e9)), // solana decimals
}
}
return increaseOffset(offset), nil
}

167
tx.go Normal file
View File

@@ -0,0 +1,167 @@
package pump_parser
import (
"fmt"
"github.com/gagliardetto/solana-go"
"github.com/mr-tron/base58"
"github.com/shopspring/decimal"
)
type Swap struct {
Program string
Event string
Pool solana.PublicKey
BaseMint solana.PublicKey
QuoteMint solana.PublicKey
BaseTokenProgram solana.PublicKey
QuoteTokenProgram solana.PublicKey
Creator solana.PublicKey
BaseMintDecimals uint8
QuoteMintDecimals uint8
User solana.PublicKey
BaseAmount decimal.Decimal
QuoteAmount decimal.Decimal
BaseReserve decimal.Decimal
QuoteReserve decimal.Decimal
Mayhem bool
UserBaseBalance decimal.Decimal
UserQuoteBalance decimal.Decimal
EntryContract solana.PublicKey
}
type platformInfo struct {
Platform string
PlatformFee decimal.Decimal
}
type mevInfo struct {
MevAgent string
MevAgentFee decimal.Decimal
}
type Tx struct {
Signer solana.PublicKey
Err interface{} `json:"err,omitempty"`
Swaps []Swap `json:"swaps,omitempty"`
Block uint64 `json:"block"`
BlockIndex uint64 `json:"index"`
TxHash *[64]byte `json:"-"`
BlockAt int64 `json:"block_at"`
cachedTxHash string
Platform map[string]platformInfo `json:"platform"`
MevAgent map[string]mevInfo ` json:"tx_mev_agent"`
CUPrice decimal.Decimal ` json:"tx_cu_price"`
BeforeSolBalance decimal.Decimal `json:"-"`
AfterSOLBalance decimal.Decimal `json:"after_sol_balance"`
Token []TokenMeta `gorm:"-"`
}
type TokenMeta struct {
Address solana.PublicKey `json:"address"`
TokenProgram solana.PublicKey `json:"token_program"`
Name string
Symbol string
Decimal uint8
Url string
TotalSupply *decimal.Decimal
SignerBalance *decimal.Decimal
}
func (tx *Tx) GetTxHash() string {
if tx.cachedTxHash != "" {
return tx.cachedTxHash
}
if tx.TxHash == nil {
return ""
}
tx.cachedTxHash = base58.Encode(tx.TxHash[:])
return tx.cachedTxHash
}
func (tx *Tx) CheckPlatform(swap Swap, rawTx *RawTx) (string, decimal.Decimal) {
// hasSolProgramRaydiumLaunchLabBonk
var platform string
var platformFee decimal.Decimal
if len(tx.Platform) == 0 {
return PlatformNone, decimal.Zero
}
for p, info := range tx.Platform {
platform = p
platformFee = info.PlatformFee
break
}
if swap.Event == "buy" && swap.Program == SolProgramRaydiumLaunchLabBonk {
for _, p := range tx.Platform {
switch p.Platform {
case PlatformAxiom:
if !checkBonkAxiomBuy(rawTx) {
platform = PlatformFake
}
case PlatformGMGN:
if !checkBonkGmgnBuy(rawTx) {
platform = PlatformFake
}
}
}
}
if platform != "" &&
platform != PlatformFake {
if (swap.QuoteMint.Equals(wSolMint) || swap.QuoteMint.IsZero()) &&
platformFee.LessThan(swap.QuoteAmount.Div(decimal.New(1, int32(swap.QuoteMintDecimals))).Div(decimal.NewFromInt(10000)).Mul(decimal.NewFromInt(9))) {
fmt.Printf("\n amount: %s, platform: %s, fee: %s \n", swap.QuoteAmount.Div(decimal.New(1, int32(swap.QuoteMintDecimals))), platform, platformFee.String())
platform = PlatformFake
} else if swap.BaseMint.Equals(wSolMint) &&
platformFee.LessThan(swap.QuoteAmount.Div(decimal.New(1, int32(swap.QuoteMintDecimals))).Div(decimal.NewFromInt(10000)).Mul(decimal.NewFromInt(9))) {
platform = PlatformFake
}
}
if platform == "" {
platform = PlatformNone
}
return platform, platformFee
}
func (tx *Tx) CheckMevAgent() (string, decimal.Decimal) {
var mevAgent = MevAgentUnknown
var mevAgentFee = decimal.Zero
for m, info := range tx.MevAgent {
if len(tx.MevAgent) > 1 && info.MevAgent == MevAgentUnknown {
continue
}
mevAgent = m
mevAgentFee = info.MevAgentFee
break
}
if len(tx.MevAgent) == 0 && mevAgent == MevAgentUnknown {
// set the mev agent to none if the platform does not exist
return "", decimal.Zero
}
return mevAgent, mevAgentFee
}
func (s Swap) CheckEntryContract() string {
name, ok := entryContractAddresses[s.EntryContract]
if ok {
return name
}
return EntryContractUnknown
}