Compare commits

..

11 Commits

Author SHA1 Message Date
bijianing97
e82bcb3c07 Merge remote-tracking branch 'origin' 2026-03-12 16:22:09 +08:00
bijianing97
a74f769064 Add raydiumv4 swapv2 2026-03-12 16:21:38 +08:00
thloyi
1e276e8bd2 [tx] swap inner idx 2026-03-12 13:49:34 +08:00
thloyi
eb2bde98ac update parsed tx: add swap at instr idx 2026-03-12 13:49:34 +08:00
66f0d247f5 chore: add astralane fee address 2026-03-12 12:08:06 +08:00
879b7fefad chore: add PlatformDexScreener 2026-03-12 11:52:58 +08:00
149dfae378 chore: add trojan fee address 2026-03-10 11:23:15 +08:00
8c4b43747c fix EntryContractAxiom 2026-03-02 20:04:38 +08:00
thloyi
e9ba16766f chain link parser 2026-03-02 18:01:32 +08:00
thloyi
cd1d681621 record ProgramFailedToComplete failed tx 2026-03-02 15:47:11 +08:00
thloyi
920c5ba25b fix errTx do not updte sol transfer 2026-02-27 17:41:58 +08:00
11 changed files with 286 additions and 29 deletions

120
chainlink.go Normal file
View File

@@ -0,0 +1,120 @@
package pump_parser
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math/big"
"github.com/gagliardetto/solana-go"
"github.com/shopspring/decimal"
)
var (
chainlinkSOLUSDFeedAccount = solana.MustPublicKeyFromBase58("CH31Xns5z3M1cTAbKW34jcxPPciazARpijcHj9rxtemt")
chainlinkSubmitDiscriminator = calculateDiscriminator("global:submit")
)
func chainLinkParser(tx *Tx, instruction Instruction, inners InnerInstructions, offset [2]uint) ([2]uint, error) {
if !tx.rawTx.accountList[instruction.ProgramIDIndex].Equals(chainLinkProgram) {
return increaseOffset(offset), fmt.Errorf("system program instruction not found, block: %d, tx: %s, outerIndex: %d, innerIndex: %d", tx.rawTx.Slot, tx.rawTx.TxHash(), offset[0], offset[1])
}
if tx.rawTx.Meta.Err != nil {
return increaseOffset(offset), nil
}
decode := instruction.Data
discriminator := binary.LittleEndian.Uint32(decode[0:4])
switch discriminator {
case transferDiscriminator:
return chainLinkSubmitParser(instruction, inners, offset, tx, decode[4:])
default:
return increaseOffset(offset), nil
}
}
func chainLinkSubmitParser(instruction Instruction, inners InnerInstructions, offset [2]uint, tx *Tx, decodeData []byte) ([2]uint, error) {
if len(instruction.Accounts) < 6 {
return increaseOffset(offset), InstructionIgnoredError
}
inner, err := getInnerInstructions(inners, offset[1])
if err != nil {
return increaseOffset(offset), err
}
if len(inner) < 1 {
return increaseOffset(offset), InstructionIgnoredError
}
storeInstruction := inner[0]
if len(storeInstruction.Accounts) < 2 {
return increaseOffset(offset), InstructionIgnoredError
}
if storeInstruction.Accounts[0] >= len(tx.rawTx.accountList) || tx.rawTx.accountList[storeInstruction.Accounts[0]] != chainlinkSOLUSDFeedAccount {
return increaseOffset(offset), InstructionIgnoredError
}
if !bytes.Equal(storeInstruction.Data[0:8], chainlinkSubmitDiscriminator[:]) {
return increaseOffset(offset), InstructionIgnoredError
}
data, err := parseChainLinkSubmitData(storeInstruction.Data)
if err != nil {
return increaseOffset(offset), InstructionIgnoredError
}
tx.ChainLink.Timestamp = int64(data.Timestamp)
tx.ChainLink.Price = decimal.NewFromBigInt(data.Price(), -8)
return increaseOffset(offset), nil
}
type SubmitData struct {
Discriminator [8]byte
Timestamp uint64
Answer [16]byte
}
func parseChainLinkSubmitData(data []byte) (*SubmitData, error) {
if len(data) != 32 {
return nil, errors.New("invalid submit data length")
}
var submitData SubmitData
copy(submitData.Discriminator[:], data[:8])
submitData.Timestamp = binary.LittleEndian.Uint64(data[8:16])
copy(submitData.Answer[:], data[16:32])
return &submitData, nil
}
func (s *SubmitData) Price() *big.Int {
return int128LEBytesToBigInt(s.Answer)
}
func int128LEBytesToBigInt(bytes [16]byte) *big.Int {
// Create new big.Int
bigInt := new(big.Int)
// Reverse bytes for little-endian to big-endian conversion
reversed := make([]byte, 16)
for i := 0; i < 16; i++ {
reversed[15-i] = bytes[i]
}
// Check if negative (first byte in little-endian is highest byte)
isNegative := bytes[15]&0x80 != 0
if isNegative {
// If negative, flip all bits
for i := range reversed {
reversed[i] = ^reversed[i]
}
// Convert to big.Int
bigInt.SetBytes(reversed)
// Add 1 and negate
bigInt.Add(bigInt, big.NewInt(1))
bigInt.Neg(bigInt)
} else {
// If positive, convert directly
bigInt.SetBytes(reversed)
}
return bigInt
}

View File

@@ -41,6 +41,12 @@ var platformFeeAddresses = map[solana.PublicKey]string{
solana.MustPublicKeyFromBase58("9yMwSPk9mrXSN7yDHUuZurAh1sjbJsfpUqjZ7SvVtdco"): PlatformTrojan, solana.MustPublicKeyFromBase58("9yMwSPk9mrXSN7yDHUuZurAh1sjbJsfpUqjZ7SvVtdco"): PlatformTrojan,
solana.MustPublicKeyFromBase58("92Med3qeK7duC5iiYsHX38H2f2twJfRsSx93oNrza2VH"): PlatformTrojan, solana.MustPublicKeyFromBase58("92Med3qeK7duC5iiYsHX38H2f2twJfRsSx93oNrza2VH"): PlatformTrojan,
solana.MustPublicKeyFromBase58("65gDv7pZQCZELsNpNYSFEBtNFpWZAbxmRFB6BGMqFkHH"): PlatformTrojan, solana.MustPublicKeyFromBase58("65gDv7pZQCZELsNpNYSFEBtNFpWZAbxmRFB6BGMqFkHH"): PlatformTrojan,
solana.MustPublicKeyFromBase58("8jgg7moFJkHyTtAv9M6RBSPMp2oXeXhuiUMKW8YbYCWn"): PlatformTrojan,
solana.MustPublicKeyFromBase58("BJgbYMZgqm79gNrmm31tV3L8GQorw91XFm4m7evyfPjr"): PlatformTrojan,
solana.MustPublicKeyFromBase58("BWgb8wR1FEGiu1jCDSKuHKf752W27b4iN6SvoNCiK4qp"): PlatformTrojan,
solana.MustPublicKeyFromBase58("GV4Bt6ehW5x5dqtaWAJBSnz8uum5Z2Rp9P2Tr5iVuQn5"): PlatformTrojan,
solana.MustPublicKeyFromBase58("2jwHNxavSoMZMEDbT1eV9PcPt5dDcayCqM6MkgaPpmWQ"): PlatformTrojan,
solana.MustPublicKeyFromBase58("66N1M2aaDSdJFZ1d7YoVN4EU45ju6XiscapLVHn5FLms"): PlatformTrojan,
solana.MustPublicKeyFromBase58("4mih95RmBqfHYvEfqq6uGGLp1Fr3gVS3VNSEa3JVRfQK"): PlatformRaybot, solana.MustPublicKeyFromBase58("4mih95RmBqfHYvEfqq6uGGLp1Fr3gVS3VNSEa3JVRfQK"): PlatformRaybot,
solana.MustPublicKeyFromBase58("3udvfL24waJcLhskRAsStNMoNUvtyXdxrWQz4hgi953N"): PlatformMoonshot, solana.MustPublicKeyFromBase58("3udvfL24waJcLhskRAsStNMoNUvtyXdxrWQz4hgi953N"): PlatformMoonshot,
solana.MustPublicKeyFromBase58("3kxSQybWEeQZsMuNWMRJH4TxrhwoDwfv41TNMLRzFP5A"): PlatformMEVX, solana.MustPublicKeyFromBase58("3kxSQybWEeQZsMuNWMRJH4TxrhwoDwfv41TNMLRzFP5A"): PlatformMEVX,
@@ -61,6 +67,7 @@ var platformFeeAddresses = map[solana.PublicKey]string{
solana.MustPublicKeyFromBase58("HkJYryz2BNeMQfuuSWDYktWt5fZLV26eK6nqu7EJycoG"): PlatformAxiom, solana.MustPublicKeyFromBase58("HkJYryz2BNeMQfuuSWDYktWt5fZLV26eK6nqu7EJycoG"): PlatformAxiom,
solana.MustPublicKeyFromBase58("BfFX9rUm8qTZiZjmeq9BktWVTNuG3YWMc5AvkrCKJike"): PlatformAxiom, solana.MustPublicKeyFromBase58("BfFX9rUm8qTZiZjmeq9BktWVTNuG3YWMc5AvkrCKJike"): PlatformAxiom,
solana.MustPublicKeyFromBase58("2ApLdwLrGayEmxgpLX9BTR47Q2QprfMg5SpjrLeaK8s7"): PlatformAxiom, solana.MustPublicKeyFromBase58("2ApLdwLrGayEmxgpLX9BTR47Q2QprfMg5SpjrLeaK8s7"): PlatformAxiom,
solana.MustPublicKeyFromBase58("AVahywMVNRYzdgWrufSWrtdGXAeNEvfpJFxhVFK516mT"): PlatformDexScreener,
} }
var mevAgentFeeAddresses = map[solana.PublicKey]string{ var mevAgentFeeAddresses = map[solana.PublicKey]string{
@@ -190,6 +197,7 @@ var mevAgentFeeAddresses = map[solana.PublicKey]string{
solana.MustPublicKeyFromBase58("ASde6y8pBCU1aityWHRpqT7pEAcEonjCgFUMeh5egRes"): MevAgentAstralane, solana.MustPublicKeyFromBase58("ASde6y8pBCU1aityWHRpqT7pEAcEonjCgFUMeh5egRes"): MevAgentAstralane,
solana.MustPublicKeyFromBase58("ASUv6G8Cj6zt71UAqD1aVtDC3CRn6FFddqF17ZiegrES"): MevAgentAstralane, solana.MustPublicKeyFromBase58("ASUv6G8Cj6zt71UAqD1aVtDC3CRn6FFddqF17ZiegrES"): MevAgentAstralane,
solana.MustPublicKeyFromBase58("ASY4mvCtrACKFK8Jiuvqcu8fad9gGTzvfm5zp4megRes"): MevAgentAstralane, solana.MustPublicKeyFromBase58("ASY4mvCtrACKFK8Jiuvqcu8fad9gGTzvfm5zp4megRes"): MevAgentAstralane,
solana.MustPublicKeyFromBase58("astraubkDw81n4LuutzSQ8uzHCv4BhPVhfvTcYv8SKC"): MevAgentAstralane,
solana.MustPublicKeyFromBase58("astraEJ2fEj8Xmy6KLG7B3VfbKfsHXhHrNdCQx7iGJK"): MevAgentAstralane, solana.MustPublicKeyFromBase58("astraEJ2fEj8Xmy6KLG7B3VfbKfsHXhHrNdCQx7iGJK"): MevAgentAstralane,
solana.MustPublicKeyFromBase58("B1ooMsWjc4SUVVuLyCu1ig2RdomQnHKgMzBMfmSo3DK"): MevAgentAstralane, solana.MustPublicKeyFromBase58("B1ooMsWjc4SUVVuLyCu1ig2RdomQnHKgMzBMfmSo3DK"): MevAgentAstralane,
solana.MustPublicKeyFromBase58("B1ooMZfUJmAvppzc5cr7eYG8Cenig4FbQGBytr4DGCh"): MevAgentAstralane, solana.MustPublicKeyFromBase58("B1ooMZfUJmAvppzc5cr7eYG8Cenig4FbQGBytr4DGCh"): MevAgentAstralane,
@@ -360,6 +368,7 @@ var entryContractAddresses = map[solana.PublicKey]string{
solana.MustPublicKeyFromBase58("B3111yJCeHBcA1bizdJjUFPALfhAfSRnAbJzGUtnt56A"): EntryContractBinanceWallet, solana.MustPublicKeyFromBase58("B3111yJCeHBcA1bizdJjUFPALfhAfSRnAbJzGUtnt56A"): EntryContractBinanceWallet,
solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9"): EntryContractAxiom, solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9"): EntryContractAxiom,
solana.MustPublicKeyFromBase58("F5tfvbLog9VdGUPqBDTT8rgXvTTcq7e5UiGnupL1zvBq"): EntryContractAxiom, solana.MustPublicKeyFromBase58("F5tfvbLog9VdGUPqBDTT8rgXvTTcq7e5UiGnupL1zvBq"): EntryContractAxiom,
solana.MustPublicKeyFromBase58("Gz9VPiSLQYbvKyb3jZPjNfyA6n4T4qVFUuAukgL964nL"): EntryContractAxiom,
solana.MustPublicKeyFromBase58("B3jytJa6Tzpn4Ly7GNnDm3dMGqUin5aMRm5aPsJGU5G7"): EntryContractTradewiz, solana.MustPublicKeyFromBase58("B3jytJa6Tzpn4Ly7GNnDm3dMGqUin5aMRm5aPsJGU5G7"): EntryContractTradewiz,
solana.MustPublicKeyFromBase58("DBotWvSso9oD1ZB3aHx2LiD2ZoFpF8PbKjaT4uHKLLVs"): EntryContractDbot, solana.MustPublicKeyFromBase58("DBotWvSso9oD1ZB3aHx2LiD2ZoFpF8PbKjaT4uHKLLVs"): EntryContractDbot,
solana.MustPublicKeyFromBase58("term9YPb9mzAsABaqN71A4xdbxHmpBNZavpBiQKZzN3"): EntryContractPadre, solana.MustPublicKeyFromBase58("term9YPb9mzAsABaqN71A4xdbxHmpBNZavpBiQKZzN3"): EntryContractPadre,
@@ -367,3 +376,5 @@ var entryContractAddresses = map[solana.PublicKey]string{
var okxDexRoutersV2 = solana.MustPublicKeyFromBase58("proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u") var okxDexRoutersV2 = solana.MustPublicKeyFromBase58("proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u")
var okxAggregatorV2 = solana.MustPublicKeyFromBase58("6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma") var okxAggregatorV2 = solana.MustPublicKeyFromBase58("6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma")
var axiomOuterContract = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9")

View File

@@ -75,6 +75,7 @@ const (
PlatformMaestro = "maestro" PlatformMaestro = "maestro"
PlatformBonkBot = "bonkbot" PlatformBonkBot = "bonkbot"
PlatformPadre = "padre" PlatformPadre = "padre"
PlatformDexScreener = "dexscreener"
// used to flag transactions impersonating platform users // used to flag transactions impersonating platform users
PlatformFake = "fake" PlatformFake = "fake"

View File

@@ -200,8 +200,10 @@ var (
const ( const (
raydiumV4InitializePoolDiscriminator = uint8(1) raydiumV4InitializePoolDiscriminator = uint8(1)
raydiumV4SwapBaseInDiscriminator = uint8(9) raydiumV4SwapBaseInDiscriminator = uint8(9)
raydiumV4SwapBaseOutDiscriminator = uint8(11) raydiumV4SwapBaseOutDiscriminator = uint8(11)
raydiumV4SwapBaseInV2Discriminator = uint8(16)
raydiumV4SwapBaseOutV2Discriminator = uint8(17)
raydiumV4AddLiquidityDiscriminator = uint8(3) raydiumV4AddLiquidityDiscriminator = uint8(3)
raydiumV4RemoveLiquidityDiscriminator = uint8(4) raydiumV4RemoveLiquidityDiscriminator = uint8(4)
@@ -236,4 +238,6 @@ var createAccountWithSeedDiscriminator = uint32(3)
var systemProgram = solana.MustPublicKeyFromBase58("11111111111111111111111111111111") var systemProgram = solana.MustPublicKeyFromBase58("11111111111111111111111111111111")
var momoProgram = solana.MustPublicKeyFromBase58("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr") var momoProgram = solana.MustPublicKeyFromBase58("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr")
var chainLinkProgram = solana.MustPublicKeyFromBase58("cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ")
var eventDiscriminator = [8]byte{228, 69, 165, 46, 81, 203, 154, 29} var eventDiscriminator = [8]byte{228, 69, 165, 46, 81, 203, 154, 29}

View File

@@ -60,8 +60,9 @@ func WithMeteoraDlmm() ParserOption {
} }
var actionPrograms = map[solana.PublicKey]actionParser{ var actionPrograms = map[solana.PublicKey]actionParser{
systemProgram: systemParser, systemProgram: systemParser,
budgGetProgram: budgetParser, budgGetProgram: budgetParser,
chainLinkProgram: chainLinkParser,
} }
func ParseRawTx(rawTx *RawTx) (*Tx, error) { func ParseRawTx(rawTx *RawTx) (*Tx, error) {
@@ -126,6 +127,9 @@ func (tx *Tx) Parser() error {
//fmt.Printf("parser failed tx error: %s, block: %d tx: %s\n", err, tx.Block, tx.GetTxHash()) //fmt.Printf("parser failed tx error: %s, block: %d tx: %s\n", err, tx.Block, tx.GetTxHash())
} }
if len(swaps) > 0 { if len(swaps) > 0 {
for i := range swaps {
swaps[i].InstrIdx = tx.Err.Index
}
tx.Swaps = swaps tx.Swaps = swaps
} }
for i, instr := range tx.rawTx.Transaction.Message.Instructions { for i, instr := range tx.rawTx.Transaction.Message.Instructions {
@@ -171,6 +175,7 @@ func (tx *Tx) Parser() error {
swap.AfterSOLBalance = decimal.NewFromUint64(tx.rawTx.Meta.PostBalances[userIdx]).Div(decimal.NewFromInt(1e9)) swap.AfterSOLBalance = decimal.NewFromUint64(tx.rawTx.Meta.PostBalances[userIdx]).Div(decimal.NewFromInt(1e9))
} }
} }
swap.InstrIdx = uint8(i)
tx.Swaps = append(tx.Swaps, swap) tx.Swaps = append(tx.Swaps, swap)
} }
@@ -218,6 +223,8 @@ func (tx *Tx) Parser() error {
swap.AfterSOLBalance = decimal.NewFromUint64(tx.rawTx.Meta.PostBalances[userIdx]).Div(decimal.NewFromInt(1e9)) swap.AfterSOLBalance = decimal.NewFromUint64(tx.rawTx.Meta.PostBalances[userIdx]).Div(decimal.NewFromInt(1e9))
} }
} }
swap.InstrIdx = uint8(i)
swap.InnerIdx = uint8(j)
tx.Swaps = append(tx.Swaps, swap) tx.Swaps = append(tx.Swaps, swap)
} }
// tx.Swaps = append(tx.Swaps, swaps...) // tx.Swaps = append(tx.Swaps, swaps...)

14
pump.go
View File

@@ -225,7 +225,7 @@ func failedTxBuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions
if tx.Err.Variant != InstructionError { if tx.Err.Variant != InstructionError {
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump failed but error variant is not instruction error, offset, %d, %d", offset[0], offset[1]) return nil, increaseOffset(offset), fmt.Errorf("failed tx pump failed but error variant is not instruction error, offset, %d, %d", offset[0], offset[1])
} }
if tx.Err.Enum != Custom && tx.Err.Enum != ComputationalBudgetExceeded { if tx.Err.Enum != Custom && tx.Err.Enum != ComputationalBudgetExceeded && tx.Err.Enum != ProgramFailedToComplete {
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump failed but error is not custom or computational budget exceeded, offset, %d, %d", offset[0], offset[1]) return nil, increaseOffset(offset), fmt.Errorf("failed tx pump failed but error is not custom or computational budget exceeded, offset, %d, %d", offset[0], offset[1])
} }
if tx.Err.Enum == Custom { if tx.Err.Enum == Custom {
@@ -344,11 +344,13 @@ func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerIns
if err != nil { if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("pump create get inner instructions error: %v,offset, %d, %d", err, offset[0], offset[1]) return nil, increaseOffset(offset), fmt.Errorf("pump create get inner instructions error: %v,offset, %d, %d", err, offset[0], offset[1])
} }
if instruction.StackHeight != nil && *instruction.StackHeight > 2 { if !entryContract.Equals(axiomOuterContract) {
for _, innerInstr := range innerInstructions.Instructions { if instruction.StackHeight != nil && *instruction.StackHeight > 2 {
if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 { for _, innerInstr := range innerInstructions.Instructions {
entryContract = result.accountList[innerInstr.ProgramIDIndex] if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 {
break entryContract = result.accountList[innerInstr.ProgramIDIndex]
break
}
} }
} }
} }

View File

@@ -268,7 +268,7 @@ func failedTxAmmBuyParser(tx *Tx, instruction Instruction, innerInstructions Inn
if tx.Err.Variant != InstructionError { if tx.Err.Variant != InstructionError {
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm sell failed but error variant is not instruction error, offset, %d, %d", offset[0], offset[1]) return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm sell failed but error variant is not instruction error, offset, %d, %d", offset[0], offset[1])
} }
if tx.Err.Enum != Custom && tx.Err.Enum != ComputationalBudgetExceeded { if tx.Err.Enum != Custom && tx.Err.Enum != ComputationalBudgetExceeded && tx.Err.Enum != ProgramFailedToComplete {
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm sell failed but error is not custom or computational budget exceeded, offset, %d, %d", offset[0], offset[1]) return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm sell failed but error is not custom or computational budget exceeded, offset, %d, %d", offset[0], offset[1])
} }
if tx.Err.Enum == Custom { if tx.Err.Enum == Custom {
@@ -392,7 +392,7 @@ func failedTxAmmSellParser(tx *Tx, instruction Instruction, innerInstructions In
if tx.Err.Variant != InstructionError { if tx.Err.Variant != InstructionError {
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm sell failed but error variant is not instruction error, offset, %d, %d", offset[0], offset[1]) return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm sell failed but error variant is not instruction error, offset, %d, %d", offset[0], offset[1])
} }
if tx.Err.Enum != Custom && tx.Err.Enum != ComputationalBudgetExceeded { if tx.Err.Enum != Custom && tx.Err.Enum != ComputationalBudgetExceeded && tx.Err.Enum != ProgramFailedToComplete {
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm sell failed but error is not custom or computational budget exceeded, offset, %d, %d", offset[0], offset[1]) return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm sell failed but error is not custom or computational budget exceeded, offset, %d, %d", offset[0], offset[1])
} }
if tx.Err.Enum == Custom { if tx.Err.Enum == Custom {
@@ -512,11 +512,13 @@ func ammBuyParser(tx *Tx, instruction Instruction, innerInstructions InnerInstru
if err != nil { if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("pumpamm create get inner instructions error: %v, offset: %d, %d", err, offset[0], prefixLen) return nil, increaseOffset(offset), fmt.Errorf("pumpamm create get inner instructions error: %v, offset: %d, %d", err, offset[0], prefixLen)
} }
if instruction.StackHeight != nil && *instruction.StackHeight > 2 { if !entryContract.Equals(axiomOuterContract) {
for _, innerInstr := range innerInstructions.Instructions { if instruction.StackHeight != nil && *instruction.StackHeight > 2 {
if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 { for _, innerInstr := range innerInstructions.Instructions {
entryContract = result.accountList[innerInstr.ProgramIDIndex] if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 {
break entryContract = result.accountList[innerInstr.ProgramIDIndex]
break
}
} }
} }
} }
@@ -633,11 +635,13 @@ func ammSellParser(tx *Tx, instruction Instruction, innerInstructions InnerInstr
return nil, increaseOffset(offset), fmt.Errorf("pumpamm sell get inner instructions error: %v, offset: %d, %d", err, offset[0], prefixLen) return nil, increaseOffset(offset), fmt.Errorf("pumpamm sell get inner instructions error: %v, offset: %d, %d", err, offset[0], prefixLen)
} }
if instruction.StackHeight != nil && *instruction.StackHeight > 2 { if !entryContract.Equals(axiomOuterContract) {
for _, innerInstr := range innerInstructions.Instructions { if instruction.StackHeight != nil && *instruction.StackHeight > 2 {
if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 { for _, innerInstr := range innerInstructions.Instructions {
entryContract = result.accountList[innerInstr.ProgramIDIndex] if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 {
break entryContract = result.accountList[innerInstr.ProgramIDIndex]
break
}
} }
} }
} }

View File

@@ -354,7 +354,7 @@ func FromRpcTransactionWithMeta(tx rpc.TransactionWithMeta, blockTime *uint64, s
} }
errDetail, ok := oErr[1].(string) errDetail, ok := oErr[1].(string)
if ok { if ok {
if errDetail == "ComputationalBudgetExceeded" { if errDetail == "ComputationalBudgetExceeded" || errDetail == "ProgramFailedToComplete" {
sTx.Meta.Err.Enum = ComputationalBudgetExceeded sTx.Meta.Err.Enum = ComputationalBudgetExceeded
} else { } else {
sTx.Meta.Err.UnKnown = errDetail sTx.Meta.Err.UnKnown = errDetail

View File

@@ -29,6 +29,8 @@ func raydiumV4Parser(tx *Tx, instruction Instruction, innerInstructions InnerIns
return raydiumv4WithdrawPNLParser(tx, instruction, innerInstructions, offset) return raydiumv4WithdrawPNLParser(tx, instruction, innerInstructions, offset)
case raydiumV4SwapBaseInDiscriminator, raydiumV4SwapBaseOutDiscriminator: case raydiumV4SwapBaseInDiscriminator, raydiumV4SwapBaseOutDiscriminator:
return raydiumv4SwapParser(tx, instruction, innerInstructions, offset) return raydiumv4SwapParser(tx, instruction, innerInstructions, offset)
case raydiumV4SwapBaseInV2Discriminator, raydiumV4SwapBaseOutV2Discriminator:
return raydiumv4SwapV2Parser(tx, instruction, innerInstructions, offset)
default: default:
return nil, increaseOffset(offset), InstructionIgnoredError return nil, increaseOffset(offset), InstructionIgnoredError
@@ -397,3 +399,99 @@ func raydiumv4SwapParser(tx *Tx, instruction Instruction, innerInstructions Inne
}, },
}, offset, nil }, offset, nil
} }
func raydiumv4SwapV2Parser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
accountsLen := len(instruction.Accounts)
if accountsLen != 8 {
return nil, increaseOffset(offset), fmt.Errorf("invalid number of accounts for raydiumv4 swapv2 instruction, offset %d, %d", offset[0], offset[1])
}
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
ammAccount := tx.rawTx.accountList[instruction.Accounts[1]]
user := tx.rawTx.accountList[instruction.Accounts[7]]
userSourceTokenAccount := tx.rawTx.accountList[instruction.Accounts[5]]
userDestinationTokenAccount := tx.rawTx.accountList[instruction.Accounts[6]]
baseVaultIdx := instruction.Accounts[3]
quoteVaultIdx := instruction.Accounts[4]
baseTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultIdx)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("failed to get base vault balance after tx: %v", err)
}
quoteTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultIdx)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("failed to get quote vault balance after tx: %v", err)
}
inners, err := getInnerInstructions(innerInstructions, offset[1])
if err != nil {
return nil, increaseOffset(offset), err
}
var nextIndex int
var srcFound, destFound bool
var baseAmount, quoteAmount decimal.Decimal
var event string
for i, inner := range inners {
from, to, amount, err := parseTokenTransfer(tx.rawTx, inner)
if err != nil {
continue
}
if from.Equals(userSourceTokenAccount) && !srcFound {
if to.Equals(tx.rawTx.accountList[baseVaultIdx]) {
event = "sell"
baseAmount = decimal.NewFromUint64(amount)
srcFound = true
} else if to.Equals(tx.rawTx.accountList[quoteVaultIdx]) {
event = "buy"
quoteAmount = decimal.NewFromUint64(amount)
srcFound = true
}
} else if to.Equals(userDestinationTokenAccount) && !destFound {
if from.Equals(tx.rawTx.accountList[quoteVaultIdx]) {
event = "sell"
quoteAmount = decimal.NewFromUint64(amount)
destFound = true
} else if from.Equals(tx.rawTx.accountList[baseVaultIdx]) {
event = "buy"
baseAmount = decimal.NewFromUint64(amount)
destFound = true
}
}
if srcFound && destFound {
nextIndex = i + 1
break
}
}
if !srcFound || !destFound {
return nil, increaseOffset(offset), fmt.Errorf("raydiumv4 failed to find token transfer inner instruction for swapv2, offset %d, %d", offset[0], offset[1])
}
offset[1] += uint(nextIndex + 1)
userBase := getAccountBalanceAfterTx(tx.rawTx, instruction.Accounts[5])
userQuote := getAccountBalanceAfterTx(tx.rawTx, instruction.Accounts[6])
baseReserve, _ := decimal.NewFromString(baseTokenbalance.UITokenAmount.Amount)
quoteReserve, _ := decimal.NewFromString(quoteTokenbalance.UITokenAmount.Amount)
return []Swap{
{
Program: SolProgramRaydiumV4,
Event: event,
Pool: ammAccount,
BaseMint: baseTokenbalance.MintAccount,
QuoteMint: quoteTokenbalance.MintAccount,
BaseTokenProgram: baseTokenbalance.ProgramIDAccount,
QuoteTokenProgram: quoteTokenbalance.ProgramIDAccount,
BaseMintDecimals: uint8(baseTokenbalance.UITokenAmount.Decimals),
QuoteMintDecimals: uint8(quoteTokenbalance.UITokenAmount.Decimals),
User: user,
BaseAmount: baseAmount,
QuoteAmount: quoteAmount,
BaseReserve: baseReserve,
QuoteReserve: quoteReserve,
Mayhem: false,
UserBaseBalance: userBase,
UserQuoteBalance: userQuote,
EntryContract: entryContract,
},
}, offset, nil
}

View File

@@ -34,12 +34,14 @@ func TransferParser(result *RawTx, instruction Instruction, offset [2]uint, tx *
from := result.accountList[result.Transaction.Message.Instructions[offset[0]].Accounts[0]] from := result.accountList[result.Transaction.Message.Instructions[offset[0]].Accounts[0]]
to := result.accountList[instruction.Accounts[1]] to := result.accountList[instruction.Accounts[1]]
if offset[1] == 0 { if result.Meta.Err == nil {
tx.SolTransfer = append(tx.SolTransfer, SolTransfer{ if offset[1] == 0 {
From: from, tx.SolTransfer = append(tx.SolTransfer, SolTransfer{
To: to, From: from,
Amount: decimal.NewFromInt(int64(lamports)), // solana decimals To: to,
}) Amount: decimal.NewFromInt(int64(lamports)), // solana decimals
})
}
} }
// load platform by to address // load platform by to address
platform, ok := platformFeeAddresses[to] platform, ok := platformFeeAddresses[to]

8
tx.go
View File

@@ -12,6 +12,9 @@ type Swap struct {
TxIndex int TxIndex int
InstrIdx uint8
InnerIdx uint8
Pool solana.PublicKey Pool solana.PublicKey
BaseMint solana.PublicKey BaseMint solana.PublicKey
QuoteMint solana.PublicKey QuoteMint solana.PublicKey
@@ -74,6 +77,10 @@ type SolTransfer struct {
To solana.PublicKey To solana.PublicKey
Amount decimal.Decimal Amount decimal.Decimal
} }
type ChainLink struct {
Timestamp int64
Price decimal.Decimal
}
type Tx struct { type Tx struct {
rawTx *RawTx rawTx *RawTx
@@ -83,6 +90,7 @@ type Tx struct {
Swaps []Swap `json:"swaps,omitempty"` Swaps []Swap `json:"swaps,omitempty"`
SolTransfer []SolTransfer `json:"sol_transfer,omitempty"` SolTransfer []SolTransfer `json:"sol_transfer,omitempty"`
Block uint64 `json:"block"` Block uint64 `json:"block"`
ChainLink ChainLink `json:"chain_link"`
BlockIndex uint64 `json:"index"` BlockIndex uint64 `json:"index"`
TxHash *[64]byte `json:"-"` TxHash *[64]byte `json:"-"`
BlockAt int64 `json:"block_at"` BlockAt int64 `json:"block_at"`