191 lines
4.9 KiB
Go
191 lines
4.9 KiB
Go
package shreder
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
|
|
"github.com/gagliardetto/solana-go"
|
|
)
|
|
|
|
var bloomRouterProgramID = solana.MustPublicKeyFromBase58("b1oomGGqPKGD6errbyfbVMBuzSC8WtAAYo8MwNafWW1")
|
|
var pumpFunAccount = solana.MustPublicKeyFromBase58("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf")
|
|
|
|
type bloomRouterArgs struct {
|
|
Side uint16
|
|
SolAmount uint64
|
|
TokenAmount uint64
|
|
}
|
|
|
|
func parseBloomRouterInstruction(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) < 26 {
|
|
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 (
|
|
amount uint64
|
|
sol uint64
|
|
exactIn bool
|
|
event string
|
|
program string
|
|
base solana.PublicKey
|
|
quote solana.PublicKey
|
|
err error
|
|
)
|
|
|
|
args, err := decodeBloomRouterArgs(instruction.Data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch args.Side {
|
|
case 0:
|
|
event = "buy"
|
|
exactIn = true
|
|
program = "Pump"
|
|
base, quote, err = findPumpFun()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
case 1:
|
|
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:
|
|
return nil, nil
|
|
}
|
|
if args.SolAmount > ^uint64(0)/100 {
|
|
return nil, fmt.Errorf("bloomrouter sol amount overflow")
|
|
}
|
|
// bloomrouter SOL amount has 2 fewer decimals than lamports.
|
|
sol = args.SolAmount * 100
|
|
amount = args.TokenAmount
|
|
|
|
if len(instruction.Accounts) == 0 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
maker, err := tx.GetAccount(int(instruction.Accounts[0]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return TxSignalBatch{&TxSignal{
|
|
TxHash: tx.Signatures[0].String(),
|
|
Label: "bloomrouter",
|
|
Maker: maker.String(),
|
|
Token0Address: base.String(),
|
|
Token1Address: quote.String(),
|
|
Token0Amount: formatTokenAmount(amount),
|
|
Token1Amount: formatSolAmount(sol),
|
|
Program: program,
|
|
Event: event,
|
|
ExactSOL: exactIn,
|
|
Block: tx.Block,
|
|
Token0AmountUint64: amount,
|
|
Token1AmountUint64: sol,
|
|
}}, nil
|
|
}
|
|
|
|
func decodeBloomRouterArgs(data []byte) (bloomRouterArgs, error) {
|
|
if len(data) < 26 {
|
|
return bloomRouterArgs{}, fmt.Errorf("data too short for bloomrouter args, len=%d", len(data))
|
|
}
|
|
return bloomRouterArgs{
|
|
Side: binary.BigEndian.Uint16(data[8:10]),
|
|
SolAmount: binary.LittleEndian.Uint64(data[10:18]),
|
|
TokenAmount: binary.LittleEndian.Uint64(data[18:26]),
|
|
}, nil
|
|
}
|