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 } 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 } 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: return ammCreatePoolParser(tx, instruction, innerInstructions, offset) case pumpAmmBuyDiscriminator, pumpAmmBuyV2Discriminator: return ammBuyParser(tx, instruction, innerInstructions, offset) case pumpAmmSellDiscriminator: return ammSellParser(tx, instruction, innerInstructions, offset) case pumpAmmDepositDiscriminator: return depositParse(tx, instruction, innerInstructions, offset) case pumpAmmWithdrawDiscriminator: 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 } 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)) } 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]]), 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)) } 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]]), 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 }