package shreder import ( "bytes" "encoding/binary" "fmt" bin "github.com/gagliardetto/binary" "github.com/gagliardetto/solana-go" "github.com/shopspring/decimal" ) var ( dflowProgramID = solana.MustPublicKeyFromBase58("DF1ow4tspfHX9JwWJsAb9epbkA8hmpSEAtxXy1V27QBH") dflowProgramString = dflowProgramID.String() dflowSwapDisc = []byte{248, 198, 158, 145, 225, 117, 135, 200} dflowSwap2Disc = []byte{65, 75, 63, 76, 235, 91, 91, 136} dflowSwapWithDestinationDisc = []byte{168, 172, 24, 77, 197, 156, 135, 101} dflowSwapWithDestinationNative = []byte{205, 77, 127, 108, 241, 32, 196, 195} dflowSwap2WithDestinationDisc = []byte{95, 123, 213, 246, 122, 1, 86, 231} dflowSwap2WithDestinationNative = []byte{222, 100, 184, 146, 186, 196, 105, 165} wrappedSOL = solana.MustPublicKeyFromBase58("So11111111111111111111111111111111111111112") ) // Action enum tags (0-based, per dflow_idl Action variants) const ( ActWhirlpoolsSwap uint8 = iota ActClearpoolsSwap ActRaydiumAmmSwap ActLifinityV2Swap ActMeteoraDlmmSwap ActRaydiumClmmSwap ActRaydiumClmmSwapV2 ActPhoenixSwap ActPumpFunBuy ActPumpFunSell ActGammaSwap ActObricV2Swap ActPumpFunAmmBuy ActPumpFunAmmSell ActSolFiSwap ActRubiconSwap ActMeteoraDammV1Swap ActRaydiumCpSwap ActStabbleStableSwap ActTesseraVSwap ActMeteoraDammV2Swap ActRaydiumLaunchlabSwap ActMeteoraDbcSwap ActHumidiFiSwap ActWhirlpoolsSwapV2 ActMeteoraDlmmSwapV2 ActZeroFiSwap ActAlphaQSwap ActTokenSwap ActSolFiV2Swap ActMozartSwap ActDFlowDynamicRouteV1 ActHeavenSwap ActNexusSwap ActSarosDlmmSwap ActTransferFee ActTransferFeeWithMint ActRecordId ActRecordId2 ActManifestSwap ActBisonFiSwap ActSanctumInfinitySwap ActSanctumInfinityLiquidity ActOpenPredictionsOrder ActScorchSwap ActIncludeAccount ) // DynamicRouteV1CandidateAction tags const ( drv1SolFi uint8 = iota drv1Rubicon drv1TesseraV drv1HumidiFi drv1SolFiV2 drv1Mozart drv1ObricV2 drv1Nexus ) // PumpFun*Options { amount: u64, orchestrator_flags: OrchestratorFlags{flags u8} } type pumpFunAction struct { Amount uint64 Flags uint8 } type dflowAction struct { Tag uint8 Pump *pumpFunAction } type dflowSwapParams struct { Actions []dflowAction } // bytes to skip for Action variants; only PumpFun* actions are decoded. func skipDflowAction(dec *bin.Decoder, tag uint8) (*pumpFunAction, error) { switch tag { case ActWhirlpoolsSwap, ActClearpoolsSwap, ActWhirlpoolsSwapV2: // amount u64 + bool + orchestrator_flags u8 return nil, dec.SkipBytes(8 + 1 + 1) case ActRaydiumAmmSwap, ActLifinityV2Swap, ActObricV2Swap, ActSolFiSwap, ActRubiconSwap, ActMeteoraDammV1Swap, ActRaydiumCpSwap, ActStabbleStableSwap, ActTesseraVSwap, ActMeteoraDammV2Swap, ActRaydiumLaunchlabSwap, ActZeroFiSwap, ActAlphaQSwap, ActTokenSwap, ActSolFiV2Swap, ActMozartSwap, ActHeavenSwap, ActNexusSwap, ActSarosDlmmSwap, ActManifestSwap, ActBisonFiSwap: // amount u64 + orchestrator_flags u8 return nil, dec.SkipBytes(8 + 1) case ActMeteoraDlmmSwap, ActRaydiumClmmSwap, ActRaydiumClmmSwapV2, ActMeteoraDlmmSwapV2: // amount u64 + u8 + orchestrator_flags u8 return nil, dec.SkipBytes(8 + 1 + 1) case ActPhoenixSwap: // amount u64 + side u8 + orchestrator_flags u8 return nil, dec.SkipBytes(8 + 1 + 1) case ActGammaSwap: // amount u64 + endorsed bool + orchestrator_flags u8 return nil, dec.SkipBytes(8 + 1 + 1) case ActPumpFunAmmSell, ActPumpFunAmmBuy, ActPumpFunBuy, ActPumpFunSell: amt, err := dec.ReadUint64(binary.LittleEndian) if err != nil { return nil, err } flg, err := dec.ReadUint8() if err != nil { return nil, err } return &pumpFunAction{Amount: amt, Flags: flg}, nil case ActMeteoraDbcSwap: // amount u64 + is_rate_limiter_applied bool + orchestrator_flags u8 return nil, dec.SkipBytes(8 + 1 + 1) case ActHumidiFiSwap: // amount u64 + swap_id u64 + orchestrator_flags u8 return nil, dec.SkipBytes(8 + 8 + 1) case ActDFlowDynamicRouteV1: // candidate_actions Vec + amount u64 + orchestrator_flags u8 ln, err := dec.ReadUint32(binary.LittleEndian) if err != nil { return nil, err } for j := uint32(0); j < ln; j++ { t, err := dec.ReadUint8() if err != nil { return nil, err } if t == drv1HumidiFi { if err := dec.SkipBytes(8); err != nil { return nil, err } } // other variants carry no payload } if err := dec.SkipBytes(8); err != nil { // amount return nil, err } return nil, dec.SkipBytes(1) // orchestrator_flags case ActTransferFee, ActTransferFeeWithMint: return nil, dec.SkipBytes(8) case ActRecordId: return nil, dec.SkipBytes(76) case ActRecordId2: return nil, dec.SkipBytes(4) case ActSanctumInfinitySwap: // amount u64 + src_lst_value_calc_accs u8 + dst_lst_value_calc_accs u8 + src_lst_index u32 + dst_lst_index u32 + orchestrator_flags u8 return nil, dec.SkipBytes(8 + 1 + 1 + 4 + 4 + 1) case ActSanctumInfinityLiquidity: // amount u64 + lst_value_calc_accs u8 + lst_index u32 + orchestrator_flags u8 return nil, dec.SkipBytes(8 + 1 + 4 + 1) case ActOpenPredictionsOrder: // nonce u64 + order_outcome u8 + quoted_out_amount u64 + slippage_bps u16 + platform_fee_recipient_vault pubkey(32) + platform_fee_scale u16 return nil, dec.SkipBytes(8 + 1 + 8 + 2 + 32 + 2) case ActScorchSwap: // amount u64 + id u128 + orchestrator_flags u8 return nil, dec.SkipBytes(8 + 16 + 1) case ActIncludeAccount: return nil, nil default: return nil, fmt.Errorf("unsupported action tag %d", tag) } } // SwapParams: actions Vec, quoted_out_amount u64, slippage_bps u16, platform_fee_bps u16 func decodeSwapParams(data []byte) (*dflowSwapParams, error) { dec := bin.NewBorshDecoder(data) out := &dflowSwapParams{} ln, err := dec.ReadUint32(binary.LittleEndian) if err != nil { return nil, err } out.Actions = make([]dflowAction, 0, ln) for i := uint32(0); i < ln; i++ { tag, err := dec.ReadUint8() if err != nil { return nil, fmt.Errorf("actions[%d] tag: %w", i, err) } pump, err := skipDflowAction(dec, tag) if err != nil { return nil, fmt.Errorf("actions[%d]: %w", i, err) } out.Actions = append(out.Actions, dflowAction{Tag: tag, Pump: pump}) } return out, nil } // Swap2Params: actions Vec, quoted_out_amount u64, slippage_bps u16, platform_fee_bps u16, positive_slippage_fee_limit_pct u8 func decodeSwap2Params(data []byte) (*dflowSwapParams, error) { dec := bin.NewBorshDecoder(data) out := &dflowSwapParams{} ln, err := dec.ReadUint32(binary.LittleEndian) if err != nil { return nil, err } out.Actions = make([]dflowAction, 0, ln) for i := uint32(0); i < ln; i++ { tag, err := dec.ReadUint8() if err != nil { return nil, fmt.Errorf("actions[%d] tag: %w", i, err) } pump, err := skipDflowAction(dec, tag) if err != nil { return nil, fmt.Errorf("actions[%d]: %w", i, err) } out.Actions = append(out.Actions, dflowAction{Tag: tag, Pump: pump}) } return out, nil } func findDflowPumpAmmMints(tx VersionedTransaction, accounts []uint8) (solana.PublicKey, solana.PublicKey, bool, error) { for i, acctIdx := range accounts { key, err := tx.GetAccount(int(acctIdx)) if err != nil { return solana.PublicKey{}, solana.PublicKey{}, false, err } if !key.Equals(pumpAmmProgramID) { continue } baseIdx := i + 4 quoteIdx := i + 5 if baseIdx >= len(accounts) || quoteIdx >= len(accounts) { return solana.PublicKey{}, solana.PublicKey{}, false, nil } baseMint, err := tx.GetAccount(int(accounts[baseIdx])) if err != nil { return solana.PublicKey{}, solana.PublicKey{}, false, err } quoteMint, err := tx.GetAccount(int(accounts[quoteIdx])) if err != nil { return solana.PublicKey{}, solana.PublicKey{}, false, err } return baseMint, quoteMint, true, nil } return solana.PublicKey{}, solana.PublicKey{}, false, nil } func parseDFlowInstruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) { if instructionIndex >= len(tx.Instructions) { return nil, fmt.Errorf("instruction index out of bounds") } ix := tx.Instructions[instructionIndex] if len(ix.Data) < 8 { return nil, nil } var err error disc := ix.Data[:8] payload := ix.Data[8:] var params *dflowSwapParams switch { case bytes.Equal(disc, dflowSwapDisc), bytes.Equal(disc, dflowSwapWithDestinationDisc), bytes.Equal(disc, dflowSwapWithDestinationNative): params, err = decodeSwapParams(payload) case bytes.Equal(disc, dflowSwap2Disc), bytes.Equal(disc, dflowSwap2WithDestinationDisc), bytes.Equal(disc, dflowSwap2WithDestinationNative): params, err = decodeSwap2Params(payload) default: return nil, nil } if err != nil { return nil, err } if params == nil { return nil, nil } var ( pumpAmmBuy *pumpFunAction pumpAmmSell *pumpFunAction pumpBuy *pumpFunAction pumpSell *pumpFunAction ) for _, act := range params.Actions { if act.Pump == nil { continue } switch act.Tag { case ActPumpFunAmmSell: pumpAmmSell = act.Pump case ActPumpFunAmmBuy: pumpAmmBuy = act.Pump case ActPumpFunBuy: pumpBuy = act.Pump case ActPumpFunSell: pumpSell = act.Pump } } out := make(TxSignalBatch, 0, 2) if pumpAmmSell != nil || pumpAmmBuy != nil { event := "sell" amt := pumpAmmSell isBuy := false if amt == nil { event = "buy" isBuy = true amt = pumpAmmBuy } baseMint, quoteMint, ok, err := findDflowPumpAmmMints(tx, ix.Accounts) if err != nil { return nil, err } if ok && quoteMint.Equals(solana.WrappedSol) { var ( token0Amount decimal.Decimal token1Amount decimal.Decimal token0AmountUint64 uint64 token1AmountUint64 uint64 exactSol bool ) if isBuy { exactSol = true token1Amount = formatSolAmount(amt.Amount) token1AmountUint64 = amt.Amount } else { token0Amount = formatTokenAmount(amt.Amount) token0AmountUint64 = amt.Amount } out = append(out, &TxSignal{ TxHash: tx.Signatures[0].String(), Maker: tx.StaticAccountKeys[0].String(), Program: "PumpAMM", Event: event, Token0Address: baseMint.String(), Token1Address: wsolMint, Token0Amount: token0Amount, Token1Amount: token1Amount, ExactSOL: exactSol, Token0AmountUint64: token0AmountUint64, Token1AmountUint64: token1AmountUint64, }) } } if pumpSell != nil || pumpBuy != nil { event := "sell" amt := pumpSell isBuy := false if amt == nil { event = "buy" isBuy = true amt = pumpBuy } mint, ok, err := findPumpFunMint(tx, ix.Accounts) if err != nil { return nil, err } if ok { var ( token0Amount decimal.Decimal token1Amount decimal.Decimal token0AmountUint64 uint64 token1AmountUint64 uint64 exactSol bool ) if isBuy { exactSol = true token1Amount = formatSolAmount(amt.Amount) token1AmountUint64 = amt.Amount } else { token0Amount = formatTokenAmount(amt.Amount) token0AmountUint64 = amt.Amount } out = append(out, &TxSignal{ TxHash: tx.Signatures[0].String(), Maker: tx.StaticAccountKeys[0].String(), Program: "Pump", Event: event, Token0Address: mint.String(), Token1Address: wsolMint, Token0Amount: token0Amount, Token1Amount: token1Amount, ExactSOL: exactSol, Token0AmountUint64: token0AmountUint64, Token1AmountUint64: token1AmountUint64, }) } } if len(out) == 0 { return nil, nil } return out, nil }