package shreder import ( "bytes" "fmt" "github.com/gagliardetto/solana-go" "github.com/near/borsh-go" ) // only pump.fun function var photonProgramID = solana.MustPublicKeyFromBase58("BSfD6SHZigAfDWSjzD5Q41jw8LmKwtmjskPH9XW1mrRW") var ( photonBuyPumpTokensIX = []byte{0x52, 0xe1, 0x77, 0xe7, 0x4e, 0x1d, 0x2d, 0x46} photonSwapPumpAmmIX = []byte{0x2c, 0x77, 0xaf, 0xda, 0xc7, 0x4d, 0xc4, 0xeb} ) type photonBuyPumpArgs struct { Timestamp uint64 SolAmount uint64 TokenAmount uint64 Fee uint64 } type photonSwapPumpAmmArgs struct { FromAmount uint64 ToAmount uint64 } func parsePhotonInstruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) { if instructionIndex >= len(tx.Instructions) { return nil, fmt.Errorf("instruction index out of bounds") } instruction := tx.Instructions[instructionIndex] if len(instruction.Data) == 0 { return nil, fmt.Errorf("data is empty") } if len(instruction.Data) < 8 { return nil, nil } var ( err error txSignal *TxSignal ) switch { case bytes.Equal(instruction.Data[:8], photonBuyPumpTokensIX): txSignal, err = parsePhotonBuy(tx, instruction) case bytes.Equal(instruction.Data[:8], photonSwapPumpAmmIX): txSignal, err = parsePhotonSwap(tx, instruction) default: return nil, nil } if txSignal != nil { return TxSignalBatch{txSignal}, err } return nil, err } func parsePhotonBuy(tx VersionedTransaction, instruction Instructions) (*TxSignal, error) { if len(instruction.Accounts) < 8 { return nil, fmt.Errorf("accounts too short") } if len(instruction.Data) < 16 { return nil, fmt.Errorf("data too short for photon buy args, len=%d", len(instruction.Data)) } mint, err := tx.GetAccount(int(instruction.Accounts[3])) if err != nil { return nil, err } user, err := tx.GetAccount(int(instruction.Accounts[7])) if err != nil { return nil, err } var args photonBuyPumpArgs if err := borsh.Deserialize(&args, instruction.Data[8:]); err != nil { return nil, fmt.Errorf("failed to parse buy tokens args: %w", err) } solAmount := args.SolAmount * (100000000 - 1234568) / 100000000 return &TxSignal{ TxHash: tx.Signatures[0].String(), Label: "photon", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, Token0Amount: formatTokenAmount(args.TokenAmount), Token1Amount: formatSolAmount(solAmount), Program: "Pump", Event: "buy", IsToken2022: false, IsMayhemMode: false, ExactSOL: true, Block: tx.Block, Token0AmountUint64: args.TokenAmount, Token1AmountUint64: solAmount, }, nil } func parsePhotonSwap(tx VersionedTransaction, instruction Instructions) (*TxSignal, error) { if len(instruction.Accounts) < 8 { return nil, fmt.Errorf("accounts too short") } if len(instruction.Data) < 16 { return nil, fmt.Errorf("data too short for swap args for photon. len=%d", len(instruction.Data)) } base, err := tx.GetAccount(int(instruction.Accounts[3])) if err != nil { return nil, err } quote, err := tx.GetAccount(int(instruction.Accounts[4])) if err != nil { return nil, err } if !quote.Equals(solana.WrappedSol) { return nil, nil } buyer, err := tx.GetAccount(int(instruction.Accounts[1])) if err != nil { return nil, err } var args photonSwapPumpAmmArgs if err := borsh.Deserialize(&args, instruction.Data[8:]); err != nil { return nil, fmt.Errorf("failed to parse swap pump amm tokens args: %w", err) } if args.FromAmount > args.ToAmount { // sell; ignore return nil, nil } solAmount := args.FromAmount * (100000000 - 1234568) / 100000000 return &TxSignal{ TxHash: tx.Signatures[0].String(), Label: "photon", Maker: buyer.String(), Token0Address: base.String(), Token1Address: wsolMint, Token0Amount: formatTokenAmount(args.ToAmount), Token1Amount: formatSolAmount(solAmount), Program: "PumpAMM", Event: "buy", IsToken2022: false, IsMayhemMode: false, Block: tx.Block, Token0AmountUint64: args.ToAmount, Token1AmountUint64: solAmount, }, nil }