chore: support bloomrouter bonk buy/sell

This commit is contained in:
2026-03-23 17:35:31 +08:00
parent 84645d9c09
commit e90a2533fd
2 changed files with 215 additions and 29 deletions

View File

@@ -26,11 +26,80 @@ func parseBloomRouterInstruction(tx VersionedTransaction, instructionIndex int)
return nil, nil return nil, nil
} }
findPumpFun := func() (solana.PublicKey, solana.PublicKey, error) {
var mint solana.PublicKey
foundPumpFun := false
for i, acctIdx := range instruction.Accounts {
key, err := tx.GetAccount(int(acctIdx))
if err != nil {
return solana.PublicKey{}, solana.PublicKey{}, err
}
if key.Equals(pumpFunAccount) {
if i+2 >= len(instruction.Accounts) {
return solana.PublicKey{}, solana.PublicKey{}, fmt.Errorf("accounts too short for pumpfun mint, idx=%d len=%d", i, len(instruction.Accounts))
}
mintKey, err := tx.GetAccount(int(instruction.Accounts[i+2]))
if err != nil {
return solana.PublicKey{}, solana.PublicKey{}, err
}
mint = mintKey
foundPumpFun = true
break
}
}
if !foundPumpFun {
return solana.PublicKey{}, solana.PublicKey{}, nil
}
return mint, wrappedSOL, nil
}
findRaydiumLaunchLab := func(isBuy bool) (solana.PublicKey, solana.PublicKey, error) {
offset := 0
if isBuy {
offset = 10
} else {
offset = 9
}
var base solana.PublicKey
var quote solana.PublicKey
foundRaydiumLaunchLab := false
for i, acctIdx := range instruction.Accounts {
key, err := tx.GetAccount(int(acctIdx))
if err != nil {
return solana.PublicKey{}, solana.PublicKey{}, err
}
if key.Equals(raydiumLaunchLabProgramID) {
if i+offset+1 >= len(instruction.Accounts) {
return solana.PublicKey{}, solana.PublicKey{}, fmt.Errorf("accounts too short for raydium launch lab mint, idx=%d len=%d", i, len(instruction.Accounts))
}
var err error
base, err = tx.GetAccount(int(instruction.Accounts[i+offset]))
if err != nil {
return solana.PublicKey{}, solana.PublicKey{}, err
}
quote, err = tx.GetAccount(int(instruction.Accounts[i+offset+1]))
if err != nil {
return solana.PublicKey{}, solana.PublicKey{}, err
}
foundRaydiumLaunchLab = true
break
}
}
if !foundRaydiumLaunchLab {
return solana.PublicKey{}, solana.PublicKey{}, nil
}
return base, quote, nil
}
var ( var (
amount uint64 amount uint64
sol uint64 sol uint64
exactIn bool exactIn bool
event string event string
program string
base solana.PublicKey
quote solana.PublicKey
err error
) )
args, err := decodeBloomRouterArgs(instruction.Data) args, err := decodeBloomRouterArgs(instruction.Data)
@@ -41,8 +110,39 @@ func parseBloomRouterInstruction(tx VersionedTransaction, instructionIndex int)
case 0: case 0:
event = "buy" event = "buy"
exactIn = true exactIn = true
program = "Pump"
base, quote, err = findPumpFun()
if err != nil {
return nil, err
}
case 1: case 1:
event = "sell" event = "sell"
program = "Pump"
base, quote, err = findPumpFun()
if err != nil {
return nil, err
}
case 0x0b00:
event = "buy"
exactIn = true
program = "RaydiumLaunchLab"
base, quote, err = findRaydiumLaunchLab(true)
if err != nil {
return nil, err
}
if !quote.Equals(wrappedSOL) {
return nil, nil
}
case 0x0b01:
event = "sell"
program = "RaydiumLaunchLab"
base, quote, err = findRaydiumLaunchLab(false)
if err != nil {
return nil, err
}
if !quote.Equals(wrappedSOL) {
return nil, nil
}
default: default:
return nil, nil return nil, nil
} }
@@ -61,43 +161,17 @@ func parseBloomRouterInstruction(tx VersionedTransaction, instructionIndex int)
return nil, err return nil, err
} }
var mint solana.PublicKey
foundPumpFun := false
for i, acctIdx := range instruction.Accounts {
key, err := tx.GetAccount(int(acctIdx))
if err != nil {
return nil, err
}
if key.Equals(pumpFunAccount) {
if i+2 >= len(instruction.Accounts) {
return nil, fmt.Errorf("accounts too short for pumpfun mint, idx=%d len=%d", i, len(instruction.Accounts))
}
mintKey, err := tx.GetAccount(int(instruction.Accounts[i+2]))
if err != nil {
return nil, err
}
mint = mintKey
foundPumpFun = true
break
}
}
if !foundPumpFun {
return nil, nil
}
return TxSignalBatch{&TxSignal{ return TxSignalBatch{&TxSignal{
TxHash: tx.Signatures[0].String(), TxHash: tx.Signatures[0].String(),
Label: "bloomrouter", Label: "bloomrouter",
Maker: maker.String(), Maker: maker.String(),
Token0Address: mint.String(), Token0Address: base.String(),
Token1Address: wsolMint, Token1Address: quote.String(),
Token0Amount: formatTokenAmount(amount), Token0Amount: formatTokenAmount(amount),
Token1Amount: formatSolAmount(sol), Token1Amount: formatSolAmount(sol),
Program: "Pump", Program: program,
Event: event, Event: event,
ExactSOL: exactIn, ExactSOL: exactIn,
IsToken2022: false,
IsMayhemMode: false,
Block: tx.Block, Block: tx.Block,
Token0AmountUint64: amount, Token0AmountUint64: amount,
Token1AmountUint64: sol, Token1AmountUint64: sol,

View File

@@ -1124,3 +1124,115 @@ func TestParseMaestroBonkSell(t *testing.T) {
t.Fatalf("expected ExactSOL false, got true") t.Fatalf("expected ExactSOL false, got true")
} }
} }
func TestParseBloomRouterBonkBuy(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("3xRUDpys1Yy96y5QVBfhW94ukphuDXHsKh4mTAFofA6S"))
ch := make(chan TxSignal)
closed := make(chan struct{})
go func() {
ParseTransactionForSubscribe(
context.Background(),
getTransaction(t, client, "5XEGgHXokNKQpgUUf1zS8LXFRHR7XNBaPiRZxGumFkBH23b3TsTjs6wJ1NRHxf6xRvYBLwXWWJdw7AiNzAAgUzgg"),
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 != "bloomrouter" {
t.Fatalf("expected bloomrouter signal, got %s", signal.Label)
}
if signal.Event != "buy" {
t.Fatalf("expected buy event, got %s", signal.Event)
}
if signal.Maker != "9ozVSeSsgASFDUjveLNQV697kAavStvpXmKP65oMt2Ji" {
t.Fatalf("expected maker 9ozVSeSsgASFDUjveLNQV697kAavStvpXmKP65oMt2Ji, got %s", signal.Maker)
}
if signal.Token0Address != "6qzh89yisR498GsQysqzj69AW9BJc39LWhXtzxudbonk" {
t.Fatalf("expected token0 address 6qzh89yisR498GsQysqzj69AW9BJc39LWhXtzxudbonk, got %s", signal.Token0Address)
}
if signal.Token0AmountUint64 != 0 {
t.Fatalf("expected token0 amount 0, got %d", signal.Token0AmountUint64)
}
if signal.Token1AmountUint64 != 1500000000 {
t.Fatalf("expected token1 amount 1500000000, got %d", signal.Token1AmountUint64)
}
if !signal.ExactSOL {
t.Fatalf("expected ExactSOL true, got false")
}
}
func TestParseBloomRouterBonkSell(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("3xRUDpys1Yy96y5QVBfhW94ukphuDXHsKh4mTAFofA6S"))
ch := make(chan TxSignal)
closed := make(chan struct{})
go func() {
ParseTransactionForSubscribe(
context.Background(),
getTransaction(t, client, "MMdN29CYKDKDurbhUHn51AyyJ5ZEZq1F1TFJTrVYhRT1moaURqXAHJb2pFus4KrXAdAFo5Hr1Jw4bgE2EWeLXf6"),
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 != "bloomrouter" {
t.Fatalf("expected bloomrouter signal, got %s", signal.Label)
}
if signal.Event != "sell" {
t.Fatalf("expected sell event, got %s", signal.Event)
}
if signal.Maker != "HWiPtESw8pvhQWm1a7vbHg9rsnee13Pmb7dUGVC3f4mZ" {
t.Fatalf("expected maker HWiPtESw8pvhQWm1a7vbHg9rsnee13Pmb7dUGVC3f4mZ, got %s", signal.Maker)
}
if signal.Token0Address != "6Yd5AGP4E4b1prHxJmuHexUToohiNKiQSG2nXvXWbonk" {
t.Fatalf("expected token0 address 6Yd5AGP4E4b1prHxJmuHexUToohiNKiQSG2nXvXWbonk, got %s", signal.Token0Address)
}
if signal.Token0AmountUint64 != 6448480270053 {
t.Fatalf("expected token0 amount 6448480270053, got %d", signal.Token0AmountUint64)
}
if signal.Token1AmountUint64 != 1000 {
t.Fatalf("expected token1 amount 1000, got %d", signal.Token1AmountUint64)
}
if signal.ExactSOL {
t.Fatalf("expected ExactSOL false, got true")
}
}