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 ProtocolFeeBasisPoints 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 CoinCreatorFeeBasisPoints uint64 CoinCreatorFee uint64 TrackVolume bool TotalUnclaimedTokens uint64 TotalClaimedTokens uint64 CurrentSolVolume uint64 LastUpdateTimestamp int64 MinBaseAmountOut uint64 IxName string CashbackFeeBasisPoints uint64 Cashback uint64 } 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 CoinCreatorFeeBasisPoints uint64 CoinCreatorFee uint64 CashbackFeeBasisPoints uint64 Cashback uint64 } 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(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { if !tx.rawTx.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: if tx.Err != nil { return nil, increaseOffset(offset), InstructionIgnoredError } return ammCreatePoolParser(tx, instruction, innerInstructions, offset) case pumpAmmBuyDiscriminator, pumpAmmBuyV2Discriminator: if tx.Err != nil { return failedTxAmmBuyParser(tx, instruction, innerInstructions, offset) } return ammBuyParser(tx, instruction, innerInstructions, offset) case pumpAmmSellDiscriminator: if tx.Err != nil { return failedTxAmmSellParser(tx, instruction, innerInstructions, offset) } return ammSellParser(tx, instruction, innerInstructions, offset) case pumpAmmDepositDiscriminator: if tx.Err != nil { return nil, increaseOffset(offset), InstructionIgnoredError } return depositParse(tx, instruction, innerInstructions, offset) case pumpAmmWithdrawDiscriminator: if tx.Err != nil { return nil, increaseOffset(offset), InstructionIgnoredError } return withdrawParse(tx, instruction, innerInstructions, offset) default: return nil, increaseOffset(offset), InstructionIgnoredError } } func ammCreatePoolParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { result := tx.rawTx 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]] if _, exists := tx.Token[createEvent.BaseMint]; !exists && !createEvent.BaseMint.Equals(wSolMint) { tx.Token[createEvent.BaseMint] = TokenMeta{ Mint: createEvent.BaseMint, Decimals: createEvent.BaseMintDecimals, TokenProgram: baseTokenProgram, } } if _, exists := tx.Token[createEvent.QuoteMint]; !exists && !createEvent.QuoteMint.Equals(wSolMint) { tx.Token[createEvent.QuoteMint] = TokenMeta{ Mint: createEvent.QuoteMint, Decimals: createEvent.QuoteMintDecimals, TokenProgram: quoteTokenProgram, } } 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 } type PumpSwapArgs struct { Discriminator [8]byte Amount1 uint64 Amount2 uint64 } func failedTxAmmBuyParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { if tx.Err == nil || tx.Err.UnKnown != "" { return nil, increaseOffset(offset), fmt.Errorf("tx pump amm sell failed but error is nil, offset, %d, %d", offset[0], offset[1]) } 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]) } 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]) } if tx.Err.Enum == Custom { if !(tx.Err.CustomCode == 1 || tx.Err.CustomCode == 6004 || tx.Err.CustomCode == 6040 || tx.Err.CustomCode == 6039 || tx.Err.CustomCode == 6016 || tx.Err.CustomCode == 6014) { return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm sell failed but custom error code is unexpected, offset, %d, %d, code: %d", offset[0], offset[1], tx.Err.CustomCode) } } result := tx.rawTx var entryContract = result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] var args PumpSwapArgs err := agbinary.NewBorshDecoder(instruction.Data[:]).Decode(&args) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm buy failed decode error: %v, offset, %d, %d", err, offset[0], offset[1]) } var event string var ( quoteAmount, tokenAmount uint64 ) if bytes.Equal(args.Discriminator[:], pumpAmmBuyV2Discriminator[:]) { event = "buy_failed" quoteAmount = args.Amount1 tokenAmount = args.Amount2 } else if bytes.Equal(args.Discriminator[:], pumpAmmBuyDiscriminator[:]) { event = "buy_failed" quoteAmount = args.Amount2 tokenAmount = args.Amount1 } else { return nil, increaseOffset(offset), fmt.Errorf("unknown pump amm trade instruction discriminator, offset, %d, %d", offset[0], offset[1]) } 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) } } if _, exists := tx.Token[baseMint]; !exists && !baseMint.Equals(wSolMint) { tx.Token[baseMint] = TokenMeta{ Mint: baseMint, Decimals: baseMintDecimals, TokenProgram: baseTokenProgram, } } if _, exists := tx.Token[quoteMint]; !exists && !quoteMint.Equals(wSolMint) { tx.Token[quoteMint] = TokenMeta{ Mint: quoteMint, Decimals: quoteMintDecimals, TokenProgram: quoteTokenProgram, } } var eventUser = tx.rawTx.accountList[instruction.Accounts[1]] baseMintAtaUserIdx := instruction.Accounts[5] userIndex := instruction.Accounts[1] if !eventUser.IsOnCurve() && (entryContract.Equals(okxDexRoutersV2) || entryContract.Equals(okxAggregatorV2)) { userBaseAmount, ataIndex := tokenBalanceChange(result, 0, baseTokenProgram, baseMint) // && userBaseAmount.BigInt().Uint64() == event.BaseAmountOut if !userBaseAmount.IsZero() { eventUser = result.accountList[0] userIndex = 0 baseMintAtaUserIdx = ataIndex } } userBase := getAccountBalanceAfterTx(result, baseMintAtaUserIdx) userQuote := GetTokenBalanceAfterTx(result, userIndex, quoteTokenProgram, quoteMint) if quoteMint.Equals(wSolMint) { userBalance, _ := GetSolAfterTx(result, userIndex) userQuote = userQuote.Add(decimal.NewFromUint64(userBalance)) } baseReserve := getAccountBalanceAfterTx(result, instruction.Accounts[7]) quoteReserve := getAccountBalanceAfterTx(result, instruction.Accounts[8]) return []Swap{ { Program: SolProgramPumpAMM, Event: event, Pool: tx.rawTx.accountList[instruction.Accounts[0]], BaseMint: baseMint, QuoteMint: quoteMint, BaseTokenProgram: baseTokenProgram, QuoteTokenProgram: quoteTokenProgram, BaseMintDecimals: baseMintDecimals, QuoteMintDecimals: quoteMintDecimals, User: eventUser, BaseAmount: decimal.NewFromUint64(tokenAmount), QuoteAmount: decimal.NewFromUint64(quoteAmount), BaseReserve: baseReserve, QuoteReserve: quoteReserve, Mayhem: isMayhemPump(result.accountList[instruction.Accounts[9]]), UserBaseBalance: userBase, UserQuoteBalance: userQuote, EntryContract: entryContract, }, }, offset, nil } func failedTxAmmSellParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { if tx.Err == nil || tx.Err.UnKnown != "" { return nil, increaseOffset(offset), fmt.Errorf("tx pump amm sell failed but error is nil, offset, %d, %d", offset[0], offset[1]) } 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]) } 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]) } if tx.Err.Enum == Custom { if !(tx.Err.CustomCode == 1 || tx.Err.CustomCode == 6004 || tx.Err.CustomCode == 6040 || tx.Err.CustomCode == 6039 || tx.Err.CustomCode == 6016 || tx.Err.CustomCode == 6014) { return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm sell failed but custom error code is unexpected, offset, %d, %d, code: %d", offset[0], offset[1], tx.Err.CustomCode) } } result := tx.rawTx var entryContract = result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] var args PumpSwapArgs err := agbinary.NewBorshDecoder(instruction.Data[:]).Decode(&args) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm buy failed decode error: %v, offset, %d, %d", err, offset[0], offset[1]) } var event string var ( quoteAmount, tokenAmount uint64 ) if bytes.Equal(args.Discriminator[:], pumpAmmSellDiscriminator[:]) { event = "sell_failed" tokenAmount = args.Amount1 quoteAmount = args.Amount2 } else { return nil, increaseOffset(offset), fmt.Errorf("unknown pump amm trade instruction discriminator, offset, %d, %d", offset[0], offset[1]) } 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) } } if _, exists := tx.Token[baseMint]; !exists && !baseMint.Equals(wSolMint) { tx.Token[baseMint] = TokenMeta{ Mint: baseMint, Decimals: baseMintDecimals, TokenProgram: baseTokenProgram, } } if _, exists := tx.Token[quoteMint]; !exists && !quoteMint.Equals(wSolMint) { tx.Token[quoteMint] = TokenMeta{ Mint: quoteMint, Decimals: quoteMintDecimals, TokenProgram: quoteTokenProgram, } } var eventUser = tx.rawTx.accountList[instruction.Accounts[1]] baseMintAtaUserIdx := instruction.Accounts[5] userIndex := instruction.Accounts[1] if !eventUser.IsOnCurve() && (entryContract.Equals(okxDexRoutersV2) || entryContract.Equals(okxAggregatorV2)) { userBaseAmount, ataIndex := tokenBalanceChange(result, 0, baseTokenProgram, baseMint) // && userBaseAmount.BigInt().Uint64() == event.BaseAmountIn if !userBaseAmount.IsZero() { eventUser = result.accountList[0] userIndex = 0 baseMintAtaUserIdx = ataIndex } } userBase := getAccountBalanceAfterTx(result, baseMintAtaUserIdx) userQuote := GetTokenBalanceAfterTx(result, userIndex, quoteTokenProgram, quoteMint) if quoteMint.Equals(wSolMint) { userBalance, _ := GetSolAfterTx(result, userIndex) userQuote = userQuote.Add(decimal.NewFromUint64(userBalance)) } baseReserve := getAccountBalanceAfterTx(result, instruction.Accounts[7]) quoteReserve := getAccountBalanceAfterTx(result, instruction.Accounts[8]) return []Swap{ { Program: SolProgramPumpAMM, Event: event, Pool: tx.rawTx.accountList[instruction.Accounts[0]], BaseMint: baseMint, QuoteMint: quoteMint, BaseTokenProgram: baseTokenProgram, QuoteTokenProgram: quoteTokenProgram, BaseMintDecimals: baseMintDecimals, QuoteMintDecimals: quoteMintDecimals, User: eventUser, BaseAmount: decimal.NewFromUint64(tokenAmount), QuoteAmount: decimal.NewFromUint64(quoteAmount), BaseReserve: baseReserve, QuoteReserve: quoteReserve, Mayhem: isMayhemPump(result.accountList[instruction.Accounts[9]]), UserBaseBalance: userBase, UserQuoteBalance: userQuote, EntryContract: entryContract, }, }, offset, nil } func ammBuyParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { result := tx.rawTx 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) } if instruction.StackHeight != nil && *instruction.StackHeight > 2 { for _, innerInstr := range innerInstructions.Instructions { if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 { entryContract = result.accountList[innerInstr.ProgramIDIndex] break } } } 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) } } if _, exists := tx.Token[baseMint]; !exists && !baseMint.Equals(wSolMint) { tx.Token[baseMint] = TokenMeta{ Mint: baseMint, Decimals: baseMintDecimals, TokenProgram: baseTokenProgram, } } if _, exists := tx.Token[quoteMint]; !exists && !quoteMint.Equals(wSolMint) { tx.Token[quoteMint] = TokenMeta{ Mint: quoteMint, Decimals: quoteMintDecimals, TokenProgram: quoteTokenProgram, } } var eventUser = event.User baseMintAtaUserIdx := instruction.Accounts[5] userIndex := instruction.Accounts[1] if !event.User.IsOnCurve() && (entryContract.Equals(okxDexRoutersV2) || entryContract.Equals(okxAggregatorV2)) { userBaseAmount, ataIndex := tokenBalanceChange(result, 0, baseTokenProgram, baseMint) // && userBaseAmount.BigInt().Uint64() == event.BaseAmountOut if !userBaseAmount.IsZero() { eventUser = result.accountList[0] userIndex = 0 baseMintAtaUserIdx = ataIndex } } userBase := getAccountBalanceAfterTx(result, baseMintAtaUserIdx) userQuote := GetTokenBalanceAfterTx(result, userIndex, quoteTokenProgram, quoteMint) if quoteMint.Equals(wSolMint) { userBalance, _ := GetSolAfterTx(result, userIndex) userQuote = userQuote.Add(decimal.NewFromUint64(userBalance)) } isCashbackCoin := event.CashbackFeeBasisPoints > 0 || event.Cashback > 0 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: eventUser, 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]]), Cashback: isCashbackCoin, UserBaseBalance: userBase, UserQuoteBalance: userQuote, EntryContract: entryContract, }, }, offset, nil } func ammSellParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { result := tx.rawTx 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) } if instruction.StackHeight != nil && *instruction.StackHeight > 2 { for _, innerInstr := range innerInstructions.Instructions { if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 { entryContract = result.accountList[innerInstr.ProgramIDIndex] break } } } 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) } } if _, exists := tx.Token[baseMint]; !exists && !baseMint.Equals(wSolMint) { tx.Token[baseMint] = TokenMeta{ Mint: baseMint, Decimals: baseMintDecimals, TokenProgram: baseTokenProgram, } } if _, exists := tx.Token[quoteMint]; !exists && !quoteMint.Equals(wSolMint) { tx.Token[quoteMint] = TokenMeta{ Mint: quoteMint, Decimals: quoteMintDecimals, TokenProgram: quoteTokenProgram, } } var eventUser = event.User baseMintAtaUserIdx := instruction.Accounts[5] userIndex := instruction.Accounts[1] if !event.User.IsOnCurve() && (entryContract.Equals(okxDexRoutersV2) || entryContract.Equals(okxAggregatorV2)) { userBaseAmount, ataIndex := tokenBalanceChange(result, 0, baseTokenProgram, baseMint) // && userBaseAmount.BigInt().Uint64() == event.BaseAmountIn if !userBaseAmount.IsZero() { eventUser = result.accountList[0] userIndex = 0 baseMintAtaUserIdx = ataIndex } } userBase := getAccountBalanceAfterTx(result, baseMintAtaUserIdx) userQuote := GetTokenBalanceAfterTx(result, userIndex, quoteTokenProgram, quoteMint) if quoteMint.Equals(wSolMint) { userBalance, _ := GetSolAfterTx(result, userIndex) userQuote = userQuote.Add(decimal.NewFromUint64(userBalance)) } isCashbackCoin := event.CashbackFeeBasisPoints > 0 || event.Cashback > 0 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: eventUser, 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]]), Cashback: isCashbackCoin, UserBaseBalance: userBase, UserQuoteBalance: userQuote, EntryContract: entryContract, }, }, offset, nil } func depositParse(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { result := tx.rawTx 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) } } } baseMint := result.accountList[instruction.Accounts[3]] quoteMint := result.accountList[instruction.Accounts[4]] if _, exists := tx.Token[baseMint]; !exists && !baseMint.Equals(wSolMint) { tx.Token[baseMint] = TokenMeta{ Mint: baseMint, Decimals: baseMintDecimals, TokenProgram: baseMintProgram, } } if _, exists := tx.Token[quoteMint]; !exists && !quoteMint.Equals(wSolMint) { tx.Token[quoteMint] = TokenMeta{ Mint: quoteMint, Decimals: quoteMintDecimals, TokenProgram: quoteMintProgram, } } return []Swap{ { Program: SolProgramPumpAMM, Event: "deposit", Pool: event.Pool, BaseMint: baseMint, QuoteMint: quoteMint, 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(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { result := tx.rawTx 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) } } } baseMint := result.accountList[instruction.Accounts[3]] quoteMint := result.accountList[instruction.Accounts[4]] if _, exists := tx.Token[baseMint]; !exists && !baseMint.Equals(wSolMint) { tx.Token[baseMint] = TokenMeta{ Mint: baseMint, Decimals: baseMintDecimals, TokenProgram: baseMintProgram, } } if _, exists := tx.Token[quoteMint]; !exists && !quoteMint.Equals(wSolMint) { tx.Token[quoteMint] = TokenMeta{ Mint: quoteMint, Decimals: quoteMintDecimals, TokenProgram: quoteMintProgram, } } 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 }