chore: support maestro bonk buy/sell

This commit is contained in:
2026-03-23 16:54:23 +08:00
parent b860ff1719
commit 84645d9c09
2 changed files with 201 additions and 15 deletions

View File

@@ -106,7 +106,6 @@ func maestroMultiSwap2DexName(dexID uint8) string {
return "OrcaWhirPool" return "OrcaWhirPool"
case 9: case 9:
return "MeteoraPools" return "MeteoraPools"
case 10: case 10:
return "MeteoraDynamicBondingCurve" return "MeteoraDynamicBondingCurve"
default: default:
@@ -125,20 +124,7 @@ func parseMaestroInstruction(tx VersionedTransaction, idx int) (TxSignalBatch, e
return parseMaestroInstructionDataAndAccounts(tx, ix.Data, ix.Accounts) return parseMaestroInstructionDataAndAccounts(tx, ix.Data, ix.Accounts)
} }
func parseMaestroInstructionDataAndAccounts(tx VersionedTransaction, ixData []byte, ixAccounts []uint8) (TxSignalBatch, error) { func parsePumpAMMMaestroInstructionDataAndAccounts(tx VersionedTransaction, ixAccounts []uint8, args *MaestroMultiSwap2Args) (TxSignalBatch, error) {
args, err := decodeMaestroMultiSwap2Args(ixData)
if err != nil {
return nil, nil
}
// only decode pump amm
if len(args.Routes) != 1 {
return nil, nil
}
if args.Routes[0].DexID != 4 {
return nil, nil
}
var ( var (
event string event string
token0Amount uint64 token0Amount uint64
@@ -255,3 +241,91 @@ func parseMaestroInstructionDataAndAccounts(tx VersionedTransaction, ixData []by
}, },
}, nil }, nil
} }
func parseRaydiumLaunchLabMaestroInstructionDataAndAccounts(tx VersionedTransaction, ixAccounts []uint8, args *MaestroMultiSwap2Args) (TxSignalBatch, error) {
var (
event string
token0Amount uint64
token1Amount uint64
// pool solana.PublicKey
baseMint solana.PublicKey
quoteMint solana.PublicKey
err error
)
routeFlag := args.Routes[0].RouteKeyIdx
if routeFlag == 0x0b {
event = "buy"
token0Amount = args.MinAmountOut
token1Amount = args.AmountIn
if len(ixAccounts) < 18 {
return nil, nil
}
quoteMint, err = tx.GetAccount(int(ixAccounts[7]))
if err != nil {
return nil, err
}
baseMint, err = tx.GetAccount(int(ixAccounts[17]))
if err != nil {
return nil, err
}
} else if routeFlag == 0x08 {
event = "sell"
token0Amount = args.AmountIn
token1Amount = args.MinAmountOut
if len(ixAccounts) < 18 {
return nil, nil
}
quoteMint, err = tx.GetAccount(int(ixAccounts[17]))
if err != nil {
return nil, err
}
baseMint, err = tx.GetAccount(int(ixAccounts[7]))
if err != nil {
return nil, err
}
} else {
return nil, nil
}
if !quoteMint.Equals(wrappedSOL) {
return nil, nil
}
return TxSignalBatch{
&TxSignal{
TxHash: tx.Signatures[0].String(),
Maker: tx.StaticAccountKeys[0].String(),
Token0Address: baseMint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(token0Amount),
Token1Amount: formatTokenAmount(token1Amount),
Event: event,
Program: "RaydiumLaunchLab",
ExactSOL: event == "buy",
Token0AmountUint64: token0Amount,
Token1AmountUint64: token1Amount,
},
}, nil
}
func parseMaestroInstructionDataAndAccounts(tx VersionedTransaction, ixData []byte, ixAccounts []uint8) (TxSignalBatch, error) {
args, err := decodeMaestroMultiSwap2Args(ixData)
if err != nil {
return nil, nil
}
// only decode 1 route
if len(args.Routes) != 1 {
return nil, nil
}
if args.Routes[0].DexID == 4 {
return parsePumpAMMMaestroInstructionDataAndAccounts(tx, ixAccounts, args)
} else if args.Routes[0].DexID == 3 {
return parseRaydiumLaunchLabMaestroInstructionDataAndAccounts(tx, ixAccounts, args)
}
return nil, nil
}

View File

@@ -1012,3 +1012,115 @@ func TestParseFlasBonkSell(t *testing.T) {
t.Fatalf("expected ExactSOL false, got true") t.Fatalf("expected ExactSOL false, got true")
} }
} }
func TestParseMaestroBonkBuy(t *testing.T) {
rpcUrl := os.Getenv("SOL_RPC_URL")
if rpcUrl == "" {
t.Fatalf("SOL_RPC_URL is not set")
}
client := rpc.New(rpcUrl)
loader := manuallyLoadAddressTable(t, client, solana.MustPublicKeyFromBase58("HPjn8EFrUcMxqQrdRzJkgXp85cGUHLFVkY4nZ7EUBbir"))
ch := make(chan TxSignal)
closed := make(chan struct{})
go func() {
ParseTransactionForSubscribe(
context.Background(),
getTransaction(t, client, "LuGGhtCU5enHY2J8qt5KAZybvQyokxKn4NxkhQfKz6RbkxW1anU9vHfAXeEVsjM49mtPmeyyVKKW1myaAXt6BhJ"),
loader,
ch,
closed,
)
}()
go func() {
<-closed
close(ch)
}()
signals := make([]TxSignal, 0)
for signal := range ch {
signals = append(signals, signal)
}
if len(signals) != 1 {
t.Fatalf("expected 1 signal, got %d", len(signals))
}
signal := signals[0]
if signal.Label != "maestro" {
t.Fatalf("expected maestro signal, got %s", signal.Label)
}
if signal.Event != "buy" {
t.Fatalf("expected buy event, got %s", signal.Event)
}
if signal.Maker != "Gyz2QP89RuoFG55V2xVpyGiG7rMya41j1ZbFNbd1WfF9" {
t.Fatalf("expected maker Gyz2QP89RuoFG55V2xVpyGiG7rMya41j1ZbFNbd1WfF9, got %s", signal.Maker)
}
if signal.Token0Address != "2aMTnF7Kz9aRhTMmiVuSwbQ9Msrdkgm4RvFoJfZPbonk" {
t.Fatalf("expected token0 address 2aMTnF7Kz9aRhTMmiVuSwbQ9Msrdkgm4RvFoJfZPbonk, got %s", signal.Token0Address)
}
if signal.Token0AmountUint64 != 11089351947578 {
t.Fatalf("expected token0 amount 1052495896871, got %d", signal.Token0AmountUint64)
}
if signal.Token1AmountUint64 != 1000000000 {
t.Fatalf("expected token1 amount 1000000000, got %d", signal.Token1AmountUint64)
}
if !signal.ExactSOL {
t.Fatalf("expected ExactSOL true, got false")
}
}
func TestParseMaestroBonkSell(t *testing.T) {
rpcUrl := os.Getenv("SOL_RPC_URL")
if rpcUrl == "" {
t.Fatalf("SOL_RPC_URL is not set")
}
client := rpc.New(rpcUrl)
loader := manuallyLoadAddressTable(t, client, solana.MustPublicKeyFromBase58("HPjn8EFrUcMxqQrdRzJkgXp85cGUHLFVkY4nZ7EUBbir"))
ch := make(chan TxSignal)
closed := make(chan struct{})
go func() {
ParseTransactionForSubscribe(
context.Background(),
getTransaction(t, client, "3cXpA8C5uizp1iK8fV8eoxoonT5BcT7G52wN9aRsRwi9pUCUyuDt2FwXVVtbkvocxoAD82ZQWjCaLRgswgcszTHQ"),
loader,
ch,
closed,
)
}()
go func() {
<-closed
close(ch)
}()
signals := make([]TxSignal, 0)
for signal := range ch {
signals = append(signals, signal)
}
if len(signals) != 1 {
t.Fatalf("expected 1 signal, got %d", len(signals))
}
signal := signals[0]
if signal.Label != "maestro" {
t.Fatalf("expected maestro signal, got %s", signal.Label)
}
if signal.Event != "sell" {
t.Fatalf("expected sell event, got %s", signal.Event)
}
if signal.Maker != "fwBVjnPQHwrAVWP4VnwbA1Y9BiDWnixJ39G5PA59sZv" {
t.Fatalf("expected maker fwBVjnPQHwrAVWP4VnwbA1Y9BiDWnixJ39G5PA59sZv, got %s", signal.Maker)
}
if signal.Token0Address != "9553NoaZEQGYsttrym5w85RQVHhwVi3BfNU9GLfEbonk" {
t.Fatalf("expected token0 address 9553NoaZEQGYsttrym5w85RQVHhwVi3BfNU9GLfEbonk, got %s", signal.Token0Address)
}
if signal.Token0AmountUint64 != 11299453877090 {
t.Fatalf("expected token0 amount 11299453877090, got %d", signal.Token0AmountUint64)
}
if signal.Token1AmountUint64 != 892516080 {
t.Fatalf("expected token1 amount 892516080, got %d", signal.Token1AmountUint64)
}
if signal.ExactSOL {
t.Fatalf("expected ExactSOL false, got true")
}
}