package pump_parser import ( "bytes" "encoding/binary" "encoding/hex" "fmt" "testing" agbinary "github.com/gagliardetto/binary" "github.com/gagliardetto/solana-go" "github.com/mr-tron/base58" ) type legacyPumpTradeEvent 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 CreatorFeeBasisPoints uint64 CreatorFee uint64 TrackVolume bool TotalUnclaimedTokens uint64 TotalClaimedTokens uint64 CurrentSolVolume uint64 LastUpdateTimestamp int64 IxName string } func TestTradeEvent(t *testing.T) { hexData := "e445a52e51cb9a1dbddb7fd34ee661ee051d1834b36cc6f04cc5bd998d53ab2a566a0ca2415bcfad5f9ed6941a851d3f84ecb200000000006c267d17170000000190d2c525ef0ea205f4b4abfdb6eaaf37fcb5a1b1dec2e2689448eecab6ba93b6c922246900000000c314f11a0d000000be71bf9e22080200c368cd1e06000000bed9ac52910901004ac2f8d0dd5cbc97e3289c197cb5062a54f3d956b9ce6e5115f96567aa5cb3e65f000000000000002c6a010000000000c9e17c171227a50a5b62e3a4a3f8ff4fafe0bca9c332bdf7f32eedbc4229604d1e000000000000005f72000000000000010000000000000000000000000000000000000000000000000000000000000000100000006275795f65786163745f736f6c5f696e" d, err := hex.DecodeString(hexData) if err != nil { t.Errorf("Failed to decode base64 data: %v", err) } var tradeEvent legacyPumpTradeEvent err = agbinary.NewBorshDecoder(d[16:]).Decode(&tradeEvent) if err != nil { t.Fatalf("Failed to deserialize trade event: %v", err) } if tradeEvent.IxName != "buy_exact_sol_in" { t.Fatalf("IxName = %q, want buy_exact_sol_in", tradeEvent.IxName) } if tradeEvent.SolAmount != 11725956 { t.Fatalf("SolAmount = %d, want 11725956", tradeEvent.SolAmount) } if !tradeEvent.IsBuy { t.Fatalf("IsBuy = false, want true") } t.Logf("Trade Event: %+v", tradeEvent) xx, err := base58.Decode("3Bxs48EzTZB4tzRd") fmt.Println(len(xx), err) } func TestCal(t *testing.T) { //e445a52e51cb9a1db94afc7d1bd7bc6f5e99e54b // . b94afc7d1bd7bc6f s := calculateDiscriminator("global:initialize_with_permission") fmt.Println(hex.EncodeToString(s[:])) s2, _ := base58.Decode("6ApXSNCamGdm") s3 := binary.LittleEndian.Uint64(s2[1:]) fmt.Println(s2, s3) fmt.Println(solana.MustPublicKeyFromBase58("BM9CcyErJcu2mjrFvUsRRrD3snGeHDDVirJLvL6EjvMN").IsOnCurve()) } func TestPumpCompleteMatchesTradeEvent(t *testing.T) { mint := solana.MustPublicKeyFromBase58("8GNGkNnfBuoTP3QRnmdNzSYuuE15M8tvcNvxNsV4pump") user := solana.MustPublicKeyFromBase58("DS95KxqUCCjwQaXhD7fhKatXbivwWDNrJdNV5ZcubGdz") bondingCurve := solana.MustPublicKeyFromBase58("Gz5EX3X7kUDS48baijJKubQDKy3BBKpnMJQ3f3W1e9jA") tradeEvent := PumpTradeEvent{ Mint: mint, User: user, } completeEvent := CompleteEvent{ Mint: mint, User: user, BondingCurve: bondingCurve, } if !pumpCompleteMatchesTradeEvent(completeEvent, tradeEvent, bondingCurve) { t.Fatal("pumpCompleteMatchesTradeEvent() = false, want true") } completeEvent.User = solana.MustPublicKeyFromBase58("3g89wLRwJ5P22fkCdPJBAP7iiYAo6yY96geQvMYj6tYm") if pumpCompleteMatchesTradeEvent(completeEvent, tradeEvent, bondingCurve) { t.Fatal("pumpCompleteMatchesTradeEvent() = true for mismatched user") } } func TestPumpExactQuoteInKeepsFeeArgBeforeMatchedTrade(t *testing.T) { EnableAllParsers() tx := mustParseRPCFixtureTx(t, "3jugr2KthX3cUHzPrMpaFKM7RtxXM6Gcxi8eFjDL7aZGLXpc6f1RaVdnAoB4ye5bRVYsP2fFs3aLaP19Utz91ewv") if len(tx.Swaps) != 4 { t.Fatalf("swaps len = %d, want 4", len(tx.Swaps)) } for i := 0; i < 3; i++ { swap := tx.Swaps[i] if swap.Program != SolProgramPump || swap.Event != "buy" { t.Fatalf("swap[%d] = %s/%s, want Pump/buy", i, swap.Program, swap.Event) } assertDecimalString(t, fmt.Sprintf("swap[%d].quote_amount", i), swap.QuoteAmount, "329217") assertDecimalString(t, fmt.Sprintf("swap[%d].fixed_amount", i), swap.FixedAmount, "333333") } sell := tx.Swaps[3] if sell.Program != SolProgramPump || sell.Event != "sell" { t.Fatalf("swap[3] = %s/%s, want Pump/sell", sell.Program, sell.Event) } assertDecimalString(t, "swap[3].base_amount", sell.BaseAmount, "12282189230") assertDecimalString(t, "swap[3].quote_amount", sell.QuoteAmount, "987647") } func TestPumpV2Discriminators(t *testing.T) { tests := []struct { name string got [8]byte want [8]byte }{ {name: "buy_exact_sol_in", got: pumpBuyExactSolInDiscriminator, want: [8]byte{56, 252, 116, 8, 158, 223, 205, 95}}, {name: "buy_v2", got: pumpBuyV2Discriminator, want: [8]byte{184, 23, 238, 97, 103, 197, 211, 61}}, {name: "buy_exact_quote_in_v2", got: pumpBuyExactQuoteInV2Discriminator, want: [8]byte{194, 171, 28, 70, 104, 77, 91, 47}}, {name: "sell_v2", got: pumpSellV2Discriminator, want: [8]byte{93, 246, 130, 60, 231, 233, 64, 178}}, {name: "create_v2", got: pumpCreateV2Discriminator, want: [8]byte{214, 144, 76, 236, 95, 139, 49, 180}}, {name: "migrate_v2", got: pumpMigrateV2Discriminator, want: [8]byte{187, 203, 18, 31, 206, 237, 254, 41}}, } for _, tt := range tests { if tt.got != tt.want { t.Fatalf("%s discriminator = %v, want %v", tt.name, tt.got, tt.want) } } } func TestPumpMigrateLayoutV2(t *testing.T) { accounts := make([]int, 27) for i := range accounts { accounts[i] = i } layout, ok := pumpMigrateLayout(Instruction{ Data: pumpMigrateV2Discriminator[:], Accounts: accounts, }) if !ok { t.Fatal("migrate_v2 layout not recognized") } if !layout.IsV2 || layout.BaseMint != 2 || layout.QuoteMint != 3 || layout.Pool != 4 || layout.BasePoolToken != 5 || layout.QuotePoolToken != 6 || layout.User != 7 || layout.BaseTokenProgram != 19 || layout.QuoteTokenProgram != 20 { t.Fatalf("migrate_v2 layout = %+v", layout) } } func TestPumpTradeAmountInfoV2(t *testing.T) { tests := []struct { name string disc [8]byte wantMode SwapMode }{ {name: "legacy exact quote in", disc: pumpBuyExactSolInDiscriminator, wantMode: SwapModeExactIn}, {name: "v2 exact quote in", disc: pumpBuyExactQuoteInV2Discriminator, wantMode: SwapModeExactIn}, {name: "v2 buy exact out", disc: pumpBuyV2Discriminator, wantMode: SwapModeExactOut}, {name: "v2 sell exact in", disc: pumpSellV2Discriminator, wantMode: SwapModeExactIn}, } for _, tt := range tests { mode, fixed, limit, ok := pumpTradeAmountInfoFromArgs(PumpTradeArgs{ Discriminator: tt.disc, Amount1: 11, Amount2: 22, }) if !ok { t.Fatalf("%s not recognized", tt.name) } if mode != tt.wantMode { t.Fatalf("%s mode = %s, want %s", tt.name, mode.String(), tt.wantMode.String()) } if fixed.String() != "11" || limit.String() != "22" { t.Fatalf("%s fixed/limit = %s/%s, want 11/22", tt.name, fixed, limit) } } } func TestPumpCreateQuoteAccountsOptional(t *testing.T) { createAccounts := make([]int, 17) for i := range createAccounts { createAccounts[i] = i } createV2Accounts := make([]int, 19) for i := range createV2Accounts { createV2Accounts[i] = i } createV2Accounts[16] = 14 createV2Accounts[18] = 16 accountList := make([]solana.PublicKey, 19) accountList[14] = usdcMint accountList[16] = solana.TokenProgramID accountList[18] = solana.TokenProgramID result := &RawTx{accountList: accountList} quoteMint, quoteTokenProgram, quoteDecimals := pumpCreateQuoteAccounts(result, Instruction{ Data: pumpCreateDiscriminator[:], Accounts: createAccounts, }, PumpCreateEvent{}) if !quoteMint.IsZero() || !quoteTokenProgram.IsZero() || quoteDecimals != 9 { t.Fatalf("create quote accounts = %s/%s/%d, want zero/zero/9", quoteMint, quoteTokenProgram, quoteDecimals) } quoteMint, quoteTokenProgram, quoteDecimals = pumpCreateQuoteAccounts(result, Instruction{ Data: pumpCreateV2Discriminator[:], Accounts: createV2Accounts, }, PumpCreateEvent{}) if !quoteMint.Equals(usdcMint) || !quoteTokenProgram.Equals(solana.TokenProgramID) || quoteDecimals != 6 { t.Fatalf("create_v2 quote accounts = %s/%s/%d, want USDC/token/6", quoteMint, quoteTokenProgram, quoteDecimals) } } func TestDecodePumpTradeEventV2QuoteFields(t *testing.T) { user := solana.MustPublicKeyFromBase58("DS95KxqUCCjwQaXhD7fhKatXbivwWDNrJdNV5ZcubGdz") mint := solana.MustPublicKeyFromBase58("8GNGkNnfBuoTP3QRnmdNzSYuuE15M8tvcNvxNsV4pump") want := PumpTradeEvent{ Mint: mint, SolAmount: 1, TokenAmount: 2, IsBuy: true, User: user, VirtualTokenReserves: 3, RealTokenReserves: 4, IxName: "buy_v2", Shareholders: []PumpShareholder{{Address: user, ShareBps: 250}}, QuoteMint: usdcMint, QuoteAmount: 5, VirtualQuoteReserves: 6, RealQuoteReserves: 7, } var buf bytes.Buffer if err := agbinary.NewBorshEncoder(&buf).Encode(want); err != nil { t.Fatalf("encode v2 trade event: %v", err) } got, err := decodePumpTradeEvent(buf.Bytes()) if err != nil { t.Fatalf("decodePumpTradeEvent() error = %v", err) } if !got.QuoteMint.Equals(usdcMint) || got.QuoteAmount != 5 || got.VirtualQuoteReserves != 6 || got.RealQuoteReserves != 7 { t.Fatalf("decoded quote fields = %s/%d/%d/%d", got.QuoteMint, got.QuoteAmount, got.VirtualQuoteReserves, got.RealQuoteReserves) } if len(got.Shareholders) != 1 || got.Shareholders[0].ShareBps != 250 { t.Fatalf("decoded shareholders = %+v", got.Shareholders) } } func TestDecodePumpTradeEventLegacyFallback(t *testing.T) { hexData := "e445a52e51cb9a1dbddb7fd34ee661ee051d1834b36cc6f04cc5bd998d53ab2a566a0ca2415bcfad5f9ed6941a851d3f84ecb200000000006c267d17170000000190d2c525ef0ea205f4b4abfdb6eaaf37fcb5a1b1dec2e2689448eecab6ba93b6c922246900000000c314f11a0d000000be71bf9e22080200c368cd1e06000000bed9ac52910901004ac2f8d0dd5cbc97e3289c197cb5062a54f3d956b9ce6e5115f96567aa5cb3e65f000000000000002c6a010000000000c9e17c171227a50a5b62e3a4a3f8ff4fafe0bca9c332bdf7f32eedbc4229604d1e000000000000005f72000000000000010000000000000000000000000000000000000000000000000000000000000000100000006275795f65786163745f736f6c5f696e" data, err := hex.DecodeString(hexData) if err != nil { t.Fatalf("decode hex: %v", err) } got, err := decodePumpTradeEvent(data[16:]) if err != nil { t.Fatalf("decodePumpTradeEvent() legacy error = %v", err) } if got.IxName != "buy_exact_sol_in" || got.SolAmount != 11725956 || !got.IsBuy { t.Fatalf("legacy event = %+v", got) } if !got.QuoteMint.IsZero() || got.QuoteAmount != 0 || got.RealQuoteReserves != 0 { t.Fatalf("legacy quote fields = %s/%d/%d, want zero", got.QuoteMint, got.QuoteAmount, got.RealQuoteReserves) } }