2026-01-28 14:11:34 +08:00
|
|
|
package shreder
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"encoding/binary"
|
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
|
|
"github.com/gagliardetto/solana-go"
|
|
|
|
|
"github.com/shopspring/decimal"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// For Metaora dlmm
|
|
|
|
|
var dlmmProgramID = solana.MustPublicKeyFromBase58("LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo")
|
|
|
|
|
var (
|
|
|
|
|
dlmmSwapIX = []byte{248, 198, 158, 145, 225, 117, 135, 200}
|
|
|
|
|
dlmmSwap2IX = []byte{65, 75, 63, 76, 235, 91, 91, 136}
|
|
|
|
|
dlmmSwapExactOutIX = []byte{250, 73, 101, 33, 38, 207, 75, 184}
|
|
|
|
|
dlmmSwapExactOut2IX = []byte{43, 215, 247, 132, 137, 60, 243, 81}
|
|
|
|
|
dlmmSwapPriceImpactIX = []byte{56, 173, 230, 208, 173, 228, 156, 205}
|
|
|
|
|
dlmmSwapPriceImpact2IX = []byte{74, 98, 192, 214, 177, 51, 75, 51}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type dlmmParsedArgs struct {
|
|
|
|
|
AmountIn uint64
|
|
|
|
|
AmountOut uint64
|
|
|
|
|
ExactIn bool
|
|
|
|
|
ExactOut bool
|
|
|
|
|
ActiveBin int32
|
|
|
|
|
MaxPriceImpactBps uint16
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func dlmmTokenOrder(tokenX, tokenY solana.PublicKey) (solana.PublicKey, solana.PublicKey) {
|
|
|
|
|
switch {
|
|
|
|
|
case tokenX.Equals(solana.WrappedSol):
|
|
|
|
|
return tokenY, tokenX
|
|
|
|
|
case tokenY.Equals(solana.WrappedSol):
|
|
|
|
|
return tokenX, tokenY
|
|
|
|
|
default:
|
|
|
|
|
return tokenX, tokenY
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func parseDlmmSwapArgs(disc []byte, payload []byte) (*dlmmParsedArgs, error) {
|
|
|
|
|
switch {
|
|
|
|
|
case bytes.Equal(disc, dlmmSwapIX), bytes.Equal(disc, dlmmSwap2IX):
|
|
|
|
|
if len(payload) < 16 {
|
|
|
|
|
return nil, fmt.Errorf("data too short for dlmm swap args, len=%d", len(payload))
|
|
|
|
|
}
|
|
|
|
|
return &dlmmParsedArgs{
|
|
|
|
|
AmountIn: binary.LittleEndian.Uint64(payload[0:8]),
|
|
|
|
|
AmountOut: binary.LittleEndian.Uint64(payload[8:16]),
|
|
|
|
|
ExactIn: true,
|
|
|
|
|
}, nil
|
|
|
|
|
case bytes.Equal(disc, dlmmSwapExactOutIX), bytes.Equal(disc, dlmmSwapExactOut2IX):
|
|
|
|
|
if len(payload) < 16 {
|
|
|
|
|
return nil, fmt.Errorf("data too short for dlmm swap exact out args, len=%d", len(payload))
|
|
|
|
|
}
|
|
|
|
|
return &dlmmParsedArgs{
|
|
|
|
|
AmountIn: binary.LittleEndian.Uint64(payload[0:8]),
|
|
|
|
|
AmountOut: binary.LittleEndian.Uint64(payload[8:16]),
|
|
|
|
|
ExactOut: true,
|
|
|
|
|
}, nil
|
|
|
|
|
case bytes.Equal(disc, dlmmSwapPriceImpactIX), bytes.Equal(disc, dlmmSwapPriceImpact2IX):
|
|
|
|
|
if len(payload) < 11 {
|
|
|
|
|
return nil, fmt.Errorf("data too short for dlmm swap with price impact args, len=%d", len(payload))
|
|
|
|
|
}
|
|
|
|
|
amountIn := binary.LittleEndian.Uint64(payload[0:8])
|
|
|
|
|
idx := 8
|
|
|
|
|
if len(payload) < idx+1 {
|
|
|
|
|
return nil, fmt.Errorf("data too short for dlmm swap with price impact args, len=%d", len(payload))
|
|
|
|
|
}
|
|
|
|
|
activeBinTag := payload[idx]
|
|
|
|
|
idx++
|
|
|
|
|
var activeBin int32
|
|
|
|
|
if activeBinTag == 1 {
|
|
|
|
|
if len(payload) < idx+4 {
|
|
|
|
|
return nil, fmt.Errorf("data too short for dlmm swap with price impact args, len=%d", len(payload))
|
|
|
|
|
}
|
|
|
|
|
activeBin = int32(binary.LittleEndian.Uint32(payload[idx : idx+4]))
|
|
|
|
|
idx += 4
|
|
|
|
|
} else if activeBinTag != 0 {
|
|
|
|
|
return nil, fmt.Errorf("invalid active_id tag %d", activeBinTag)
|
|
|
|
|
}
|
|
|
|
|
if len(payload) < idx+2 {
|
|
|
|
|
return nil, fmt.Errorf("data too short for dlmm swap with price impact args, len=%d", len(payload))
|
|
|
|
|
}
|
|
|
|
|
return &dlmmParsedArgs{
|
|
|
|
|
AmountIn: amountIn,
|
|
|
|
|
ExactIn: true,
|
|
|
|
|
ActiveBin: activeBin,
|
|
|
|
|
MaxPriceImpactBps: binary.LittleEndian.Uint16(payload[idx : idx+2]),
|
|
|
|
|
}, nil
|
|
|
|
|
default:
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func parseDlmmInstruction(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) < 8 {
|
|
|
|
|
return nil, fmt.Errorf("data is empty")
|
|
|
|
|
}
|
|
|
|
|
if len(instruction.Accounts) < 13 {
|
|
|
|
|
return nil, nil // fmt.Errorf("accounts too short")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
disc := instruction.Data[:8]
|
|
|
|
|
payload := instruction.Data[8:]
|
|
|
|
|
|
|
|
|
|
args, err := parseDlmmSwapArgs(disc, payload)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if args == nil {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
userTokenIn, err := tx.GetAccount(int(instruction.Accounts[4]))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-02-12 17:37:29 +08:00
|
|
|
lbPair, err := tx.GetAccount(int(instruction.Accounts[0]))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-01-28 14:11:34 +08:00
|
|
|
userTokenOut, err := tx.GetAccount(int(instruction.Accounts[5]))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
tokenX, err := tx.GetAccount(int(instruction.Accounts[6]))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
tokenY, err := tx.GetAccount(int(instruction.Accounts[7]))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
user, err := tx.GetAccount(int(instruction.Accounts[10]))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
tokenXProgram, err := tx.GetAccount(int(instruction.Accounts[11]))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
tokenYProgram, err := tx.GetAccount(int(instruction.Accounts[12]))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
token0Mint, token1Mint := dlmmTokenOrder(tokenX, tokenY)
|
|
|
|
|
var (
|
|
|
|
|
token0AmountUint64 uint64
|
|
|
|
|
token1AmountUint64 uint64
|
|
|
|
|
)
|
|
|
|
|
if !tokenX.Equals(solana.WrappedSol) && !tokenY.Equals(solana.WrappedSol) {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
wsolProgram := tokenXProgram
|
|
|
|
|
if tokenY.Equals(solana.WrappedSol) {
|
|
|
|
|
wsolProgram = tokenYProgram
|
|
|
|
|
}
|
|
|
|
|
wsolAta, _, err := findAssociatedTokenAddressWithTokenProgram(user, solana.WrappedSol, wsolProgram)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wsolIn := userTokenIn.Equals(wsolAta)
|
|
|
|
|
wsolOut := userTokenOut.Equals(wsolAta)
|
|
|
|
|
if !wsolIn && !wsolOut {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
event := "sell"
|
|
|
|
|
if wsolIn {
|
|
|
|
|
event = "buy"
|
|
|
|
|
}
|
|
|
|
|
exactSol := (args.ExactIn && wsolIn) || (args.ExactOut && wsolOut)
|
|
|
|
|
|
|
|
|
|
if wsolIn {
|
|
|
|
|
if args.ExactIn {
|
|
|
|
|
token1AmountUint64 = args.AmountIn
|
|
|
|
|
}
|
|
|
|
|
if args.ExactOut {
|
|
|
|
|
token0AmountUint64 = args.AmountOut
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if args.ExactOut {
|
|
|
|
|
token1AmountUint64 = args.AmountOut
|
|
|
|
|
}
|
|
|
|
|
if args.ExactIn {
|
|
|
|
|
token0AmountUint64 = args.AmountIn
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
token0Amount := formatTokenAmount(token0AmountUint64)
|
|
|
|
|
if token0Mint.Equals(solana.WrappedSol) {
|
|
|
|
|
token0Amount = formatSolAmount(token0AmountUint64)
|
|
|
|
|
}
|
|
|
|
|
token1Amount := decimal.Zero
|
|
|
|
|
if token1AmountUint64 > 0 {
|
|
|
|
|
if token1Mint.Equals(solana.WrappedSol) {
|
|
|
|
|
token1Amount = formatSolAmount(token1AmountUint64)
|
|
|
|
|
} else {
|
|
|
|
|
token1Amount = formatTokenAmount(token1AmountUint64)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return TxSignalBatch{&TxSignal{
|
|
|
|
|
TxHash: tx.Signatures[0].String(),
|
|
|
|
|
Label: "dlmm",
|
|
|
|
|
Maker: user.String(),
|
|
|
|
|
Token0Address: token0Mint.String(),
|
|
|
|
|
Token1Address: token1Mint.String(),
|
|
|
|
|
Token0Amount: token0Amount,
|
|
|
|
|
Token1Amount: token1Amount,
|
|
|
|
|
Program: "MeteoraDLMM",
|
|
|
|
|
Event: event,
|
|
|
|
|
IsToken2022: false,
|
|
|
|
|
IsMayhemMode: false,
|
|
|
|
|
ExactSOL: exactSol,
|
|
|
|
|
ActiveBin: args.ActiveBin,
|
|
|
|
|
MaxPriceImpactBps: args.MaxPriceImpactBps,
|
2026-02-12 17:37:29 +08:00
|
|
|
LbPairAddress: lbPair.String(),
|
2026-01-28 14:11:34 +08:00
|
|
|
Block: tx.Block,
|
|
|
|
|
Token0AmountUint64: token0AmountUint64,
|
|
|
|
|
Token1AmountUint64: token1AmountUint64,
|
|
|
|
|
}}, nil
|
|
|
|
|
}
|