10 Commits

Author SHA1 Message Date
cachalots
c7be7cf4fd culimit 2026-02-27 16:45:57 +08:00
cachalots
62c313d4a1 culimit 2026-02-27 15:41:20 +08:00
cachalots
5fa6944a37 culimit 2026-02-27 15:40:18 +08:00
cachalots
5d06d18aa8 culimit 2026-02-27 15:32:36 +08:00
9877794d1c fix: flas buy/sell 2026-02-21 19:03:00 +08:00
f6242f0193 Merge branch 'master' of https://go.onsig.ai/onsig/libsam 2026-02-21 18:52:17 +08:00
b06a1fa377 chore: upgrade axiom pumpfun buy/sell 2026-02-21 18:42:18 +08:00
75c35f56f1 fix juo v6 pump swap percent 2026-02-17 19:13:12 +08:00
79859bc079 update dflow new action 2026-02-15 20:41:47 +08:00
bijianing97
bd2dbe3c91 Add lbPairAddress for TxSignal 2026-02-12 17:37:29 +08:00
8 changed files with 126 additions and 17 deletions

View File

@@ -72,6 +72,11 @@ const (
ActOpenPredictionsOrder ActOpenPredictionsOrder
ActScorchSwap ActScorchSwap
ActIncludeAccount ActIncludeAccount
ActDFLOWStabbleWeightedSwap
ActVertigoSwap
ActSetMinimumLegOutputs
ActSetMinimumLegPrices
) )
// DynamicRouteV1CandidateAction tags // DynamicRouteV1CandidateAction tags
@@ -104,7 +109,7 @@ type dflowSwapParams struct {
// bytes to skip for Action variants; only PumpFun* actions are decoded. // bytes to skip for Action variants; only PumpFun* actions are decoded.
func skipDflowAction(dec *bin.Decoder, tag uint8) (*pumpFunAction, error) { func skipDflowAction(dec *bin.Decoder, tag uint8) (*pumpFunAction, error) {
switch tag { switch tag {
case ActWhirlpoolsSwap, ActClearpoolsSwap, ActWhirlpoolsSwapV2: case ActWhirlpoolsSwap, ActClearpoolsSwap, ActWhirlpoolsSwapV2, ActDFLOWStabbleWeightedSwap, ActVertigoSwap:
// amount u64 + bool + orchestrator_flags u8 // amount u64 + bool + orchestrator_flags u8
return nil, dec.SkipBytes(8 + 1 + 1) return nil, dec.SkipBytes(8 + 1 + 1)
case ActRaydiumAmmSwap, ActLifinityV2Swap, ActObricV2Swap, case ActRaydiumAmmSwap, ActLifinityV2Swap, ActObricV2Swap,
@@ -181,6 +186,19 @@ func skipDflowAction(dec *bin.Decoder, tag uint8) (*pumpFunAction, error) {
return nil, dec.SkipBytes(8 + 16 + 1) return nil, dec.SkipBytes(8 + 16 + 1)
case ActIncludeAccount: case ActIncludeAccount:
return nil, nil return nil, nil
case ActSetMinimumLegOutputs:
ln, err := dec.ReadUint32(binary.LittleEndian)
if err != nil {
return nil, err
}
return nil, dec.SkipBytes(uint(8 * ln))
case ActSetMinimumLegPrices:
// Vec<(u64, u8)>; read length and skip the pairs
ln, err := dec.ReadUint32(binary.LittleEndian)
if err != nil {
return nil, err
}
return nil, dec.SkipBytes(uint(uint64(ln) * (8 + 1)))
default: default:
return nil, fmt.Errorf("unsupported action tag %d", tag) return nil, fmt.Errorf("unsupported action tag %d", tag)
} }

View File

@@ -0,0 +1,61 @@
package shreder
import (
"encoding/hex"
"testing"
)
func TestDFlowDecodedSwapParams(t *testing.T) {
tests := []struct {
name string
hexData string
}{
{
name: "DFlow swap Test 0",
hexData: "f8c69e91e17587c806000000256cb411cd4dcea8c073833936254cc3a7a6f1bc3e1106af1fceaed1bf6d75184d8149476a66d1f0d4c23c177e81d73b8b11297c7f7d8a8d6e339939647915d8096cfcdd170000000093000000300300000000000000000000000000000000000000a84325c4000000002d0decfc36e0bc09000001197bcde00df80000000180130edffead0800000081711ebdc4000000002c013200",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
instrData, err := hex.DecodeString(tt.hexData)
if err != nil {
t.Fatalf("failed to decode hex string: %v", err)
return
}
//t.Logf("raw bytes: %x", instrData[8:])
args, err := decodeSwapParams(instrData[8:])
if err != nil {
t.Fatalf("failed to decode dflow arguments: %v", err)
return
}
t.Logf("decoded args: %+v", args)
})
}
}
func TestDFlowV2DecodedSwapParams(t *testing.T) {
tests := []struct {
name string
hexData string
}{
{
name: "DFlow swap 2 Test 0",
hexData: "414b3f4ceb5b5b880300000025427ee16eed91684faaad4a3f161acd31d92bbc3d1ba0e2ebdb4678448fd5a7aeade9c8b38e8755e811f3373a0056cd5647e4cc3510135f98e97cb03c046ade049d08de17000000007800000023f50a0000000000002e1bfe04000000000001c3e13700000000003601000005",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
instrData, err := hex.DecodeString(tt.hexData)
if err != nil {
t.Fatalf("failed to decode hex string: %v", err)
return
}
args, err := decodeSwap2Params(instrData[8:])
if err != nil {
t.Fatalf("failed to decode dflow arguments: %v", err)
return
}
t.Logf("decoded args: %+v", args)
})
}
}

View File

@@ -122,6 +122,10 @@ func parseDlmmInstruction(tx VersionedTransaction, instructionIndex int) (TxSign
if err != nil { if err != nil {
return nil, err return nil, err
} }
lbPair, err := tx.GetAccount(int(instruction.Accounts[0]))
if err != nil {
return nil, err
}
userTokenOut, err := tx.GetAccount(int(instruction.Accounts[5])) userTokenOut, err := tx.GetAccount(int(instruction.Accounts[5]))
if err != nil { if err != nil {
return nil, err return nil, err
@@ -220,6 +224,7 @@ func parseDlmmInstruction(tx VersionedTransaction, instructionIndex int) (TxSign
ExactSOL: exactSol, ExactSOL: exactSol,
ActiveBin: args.ActiveBin, ActiveBin: args.ActiveBin,
MaxPriceImpactBps: args.MaxPriceImpactBps, MaxPriceImpactBps: args.MaxPriceImpactBps,
LbPairAddress: lbPair.String(),
Block: tx.Block, Block: tx.Block,
Token0AmountUint64: token0AmountUint64, Token0AmountUint64: token0AmountUint64,
Token1AmountUint64: token1AmountUint64, Token1AmountUint64: token1AmountUint64,

View File

@@ -10,8 +10,8 @@ import (
var flasProgramID = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9") var flasProgramID = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9")
var ( var (
flasBuyTokensIX = []byte{0x00, 0x1, 0x4} flasBuyTokensIX = []byte{0x00, 0x1, 0x1b}
flasSellTokensIX = []byte{0x01, 0x1, 0x3} flasSellTokensIX = []byte{0x01, 0x1, 0x1a}
flasAmmBuyTokensIX = []byte{0x00, 0x2, 0x2} flasAmmBuyTokensIX = []byte{0x00, 0x2, 0x2}
flasAmmSellTokensIX = []byte{0x01, 0x2, 0x2} flasAmmSellTokensIX = []byte{0x01, 0x2, 0x2}
) )
@@ -140,11 +140,11 @@ func parseFlasAmmBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal,
func parseFlasSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) { func parseFlasSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
instruction := tx.Instructions[instructionIndex] instruction := tx.Instructions[instructionIndex]
if len(instruction.Accounts) < 9 { if len(instruction.Accounts) < 11 {
return nil, fmt.Errorf("accounts too short") return nil, fmt.Errorf("accounts too short")
} }
mint, err := tx.GetAccount(int(instruction.Accounts[8])) mint, err := tx.GetAccount(int(instruction.Accounts[10]))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -178,15 +178,15 @@ func parseFlasSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, er
func parseFlasBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) { func parseFlasBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
instruction := tx.Instructions[instructionIndex] instruction := tx.Instructions[instructionIndex]
if len(instruction.Accounts) < 9 { if len(instruction.Accounts) < 11 {
return nil, fmt.Errorf("accounts too short") return nil, fmt.Errorf("accounts too short")
} }
mint, err := tx.GetAccount(int(instruction.Accounts[8])) mint, err := tx.GetAccount(int(instruction.Accounts[10]))
if err != nil { if err != nil {
return nil, err return nil, err
} }
user, err := tx.GetAccount(int(instruction.Accounts[0])) user, err := tx.GetAccount(int(instruction.Accounts[1]))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -1122,6 +1122,8 @@ func parseJupiterPumpAmmRoute(tx VersionedTransaction, instruction Instructions,
isBuy bool isBuy bool
isSell bool isSell bool
count int count int
sellPercent uint8
buyPercent uint8
) )
for _, step := range plan { for _, step := range plan {
if !isInputIdx0(step.InputIdx) { if !isInputIdx0(step.InputIdx) {
@@ -1130,9 +1132,11 @@ func parseJupiterPumpAmmRoute(tx VersionedTransaction, instruction Instructions,
if isPumpSwapSellKind(step.Swap.Kind) { if isPumpSwapSellKind(step.Swap.Kind) {
isSell = true isSell = true
count++ count++
sellPercent = step.Percent
} else if isPumpSwapBuyKind(step.Swap.Kind) { } else if isPumpSwapBuyKind(step.Swap.Kind) {
isBuy = true isBuy = true
count++ count++
buyPercent = step.Percent
} }
} }
if count == 0 { if count == 0 {
@@ -1154,6 +1158,9 @@ func parseJupiterPumpAmmRoute(tx VersionedTransaction, instruction Instructions,
if in > 0 { if in > 0 {
token0Amount = formatTokenAmount(in) token0Amount = formatTokenAmount(in)
} }
if sellPercent > 0 && sellPercent < 100 {
token0Amount = token0Amount.Mul(decimal.NewFromInt(int64(sellPercent))).Div(decimal.NewFromInt(100))
}
return &TxSignal{ return &TxSignal{
TxHash: tx.Signatures[0].String(), TxHash: tx.Signatures[0].String(),
Maker: tx.StaticAccountKeys[0].String(), Maker: tx.StaticAccountKeys[0].String(),
@@ -1189,6 +1196,10 @@ func parseJupiterPumpAmmRoute(tx VersionedTransaction, instruction Instructions,
if in > 0 { if in > 0 {
token1Amount = formatSolAmount(in) token1Amount = formatSolAmount(in)
} }
if buyPercent > 0 && buyPercent < 100 {
token1Amount = token1Amount.Mul(decimal.NewFromInt(int64(buyPercent))).Div(decimal.NewFromInt(100))
token0Amount = token0Amount.Mul(decimal.NewFromInt(int64(buyPercent))).Div(decimal.NewFromInt(100))
}
return &TxSignal{ return &TxSignal{
TxHash: tx.Signatures[0].String(), TxHash: tx.Signatures[0].String(),
Maker: tx.StaticAccountKeys[0].String(), Maker: tx.StaticAccountKeys[0].String(),

View File

@@ -67,6 +67,10 @@ func TestDecodeRouteArg(t *testing.T) {
name: "Jupiter V6 RouteArg Test 1", name: "Jupiter V6 RouteArg Test 1",
hexData: "e517cb977ae3ad2a03000000646400017ab0b6c3d206f46577050000000c0000526401025f00640203bb628e2902000000338c430100000000320000", hexData: "e517cb977ae3ad2a03000000646400017ab0b6c3d206f46577050000000c0000526401025f00640203bb628e2902000000338c430100000000320000",
}, },
{
name: "Jupiter V6 RouteArg Test 2",
hexData: "e517cb977ae3ad2a04000000642300024b00000000410002761acfb15ea9fdcd0501200204769358e96343759bf8014402046196591e1e020000f5bf6fe101000000d00700",
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View File

@@ -45,8 +45,10 @@ type TxSignal struct {
IsToken2022 bool `json:"is_token2022"` IsToken2022 bool `json:"is_token2022"`
IsMayhemMode bool `json:"is_mayhem_mode"` IsMayhemMode bool `json:"is_mayhem_mode"`
CUPrice decimal.Decimal `json:"cu_price"` CUPrice decimal.Decimal `json:"cu_price"`
CULimit uint32 `json:"cu_limit"`
SWQoSAgent string `json:"swqos_agent"` SWQoSAgent string `json:"swqos_agent"`
SWQoSTips decimal.Decimal `json:"swqos_tips"` SWQoSTips decimal.Decimal `json:"swqos_tips"`
TableCnt int `json:"table_cnt"`
ExactSOL bool `json:"exact_in"` ExactSOL bool `json:"exact_in"`
@@ -55,6 +57,8 @@ type TxSignal struct {
ActiveBin int32 `json:"active_bin"` ActiveBin int32 `json:"active_bin"`
// MaxPriceImpactBps is the price impact guard for swap_with_price_impact(2). // MaxPriceImpactBps is the price impact guard for swap_with_price_impact(2).
MaxPriceImpactBps uint16 `json:"max_price_impact_bps"` MaxPriceImpactBps uint16 `json:"max_price_impact_bps"`
//
LbPairAddress string `json:"lb_pair_address"`
// parsed values // parsed values
Token0AmountUint64 uint64 `json:"-"` Token0AmountUint64 uint64 `json:"-"`

View File

@@ -182,7 +182,8 @@ func ParseTransactionForEntries(ctx context.Context, slot uint64, entriesReader
} }
func ParseTransactionWithHandler(ctx context.Context, versioned VersionedTransaction, loader *AddressTables, parsed chan<- TxSignal, handlers map[solana.PublicKey]Handler) { func ParseTransactionWithHandler(ctx context.Context, versioned VersionedTransaction, loader *AddressTables, parsed chan<- TxSignal, handlers map[solana.PublicKey]Handler) {
if loader != nil && len(versioned.AddressTableLookups) > 0 { tableCnt := len(versioned.AddressTableLookups)
if loader != nil && tableCnt > 0 {
lookupTableOk := true lookupTableOk := true
for _, lookups := range versioned.AddressTableLookups { for _, lookups := range versioned.AddressTableLookups {
lookupTableOk = loader.FillToTx(&versioned, lookups.AccountKey, lookups.WritableIndexes) lookupTableOk = loader.FillToTx(&versioned, lookups.AccountKey, lookups.WritableIndexes)
@@ -203,16 +204,19 @@ func ParseTransactionWithHandler(ctx context.Context, versioned VersionedTransac
cuPrice := decimal.Zero cuPrice := decimal.Zero
swqosAgent := "" swqosAgent := ""
swqosTips := decimal.Zero swqosTips := decimal.Zero
cuLimit := uint32(0)
for _, instruction := range versioned.Instructions { for _, instruction := range versioned.Instructions {
program, err := versioned.GetAccount(int(instruction.ProgramIDIndex)) program, err := versioned.GetAccount(int(instruction.ProgramIDIndex))
if err != nil { if err != nil {
continue continue
} }
if program.Equals(ComputeBudgetProgram) && if program.Equals(ComputeBudgetProgram) {
len(instruction.Data) == 9 && if len(instruction.Data) == 9 && instruction.Data[0] == 0x03 {
instruction.Data[0] == 0x03 {
cuPriceUint64 := binary.LittleEndian.Uint64(instruction.Data[1:9]) cuPriceUint64 := binary.LittleEndian.Uint64(instruction.Data[1:9])
cuPrice = formatCUPrice(cuPriceUint64) cuPrice = formatCUPrice(cuPriceUint64)
} else if len(instruction.Data) == 5 && instruction.Data[0] == 0x02 {
cuLimit = binary.LittleEndian.Uint32(instruction.Data[1:5])
}
} }
if program.Equals(solana.SystemProgramID) && if program.Equals(solana.SystemProgramID) &&
len(instruction.Data) == 12 && len(instruction.Data) == 12 &&
@@ -260,8 +264,10 @@ func ParseTransactionWithHandler(ctx context.Context, versioned VersionedTransac
one.Label = handler.Label one.Label = handler.Label
one.Block = versioned.Block one.Block = versioned.Block
one.CUPrice = cuPrice one.CUPrice = cuPrice
one.CULimit = cuLimit
one.SWQoSAgent = swqosAgent one.SWQoSAgent = swqosAgent
one.SWQoSTips = swqosTips one.SWQoSTips = swqosTips
one.TableCnt = tableCnt
select { select {
case <-ctx.Done(): case <-ctx.Done():
return return