7 Commits

Author SHA1 Message Date
6bab10866b fix: photon buy 2026-01-08 16:25:34 +08:00
83aa772710 chore: improve test 2026-01-08 16:20:17 +08:00
da51b19b50 chore: add bonk parser 2026-01-08 16:07:57 +08:00
f39b89b497 chore: add simple test 2026-01-08 15:20:50 +08:00
26e07ec52e chore: add gmgn and remove entry contract 2026-01-08 12:54:21 +08:00
35c57c3c7a chore: remove useless 2026-01-08 12:46:58 +08:00
3e58b62e1f chore: enable term 2026-01-08 12:44:47 +08:00
3 changed files with 413 additions and 51 deletions

View File

@@ -45,7 +45,6 @@ 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"`
TxFee decimal.Decimal `json:"tx_fee"` TxFee decimal.Decimal `json:"tx_fee"`
EntryContract string `json:"entry_contract"`
ExactSOL bool `json:"exact_in"` ExactSOL bool `json:"exact_in"`

View File

@@ -23,39 +23,32 @@ const (
// program ids // program ids
var ( var (
pumpProgramID = solana.MustPublicKeyFromBase58("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P") pumpProgramID = solana.MustPublicKeyFromBase58("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
pumpProgramIDString = pumpProgramID.String()
// has no sell function with pump and pump.amm program // has no sell function with pump and pump.amm program
azczProgramID = solana.MustPublicKeyFromBase58("AzcZqCRUQgKEg5FTAgY7JacATABEYCEfMbjXEzspLYFB") azczProgramID = solana.MustPublicKeyFromBase58("AzcZqCRUQgKEg5FTAgY7JacATABEYCEfMbjXEzspLYFB")
azczProgramIDString = azczProgramID.String()
// only buy function with pump program // only buy function with pump program
f5tfProgramID = solana.MustPublicKeyFromBase58("F5tfvbLog9VdGUPqBDTT8rgXvTTcq7e5UiGnupL1zvBq") f5tfProgramID = solana.MustPublicKeyFromBase58("F5tfvbLog9VdGUPqBDTT8rgXvTTcq7e5UiGnupL1zvBq")
f5tfProgramIDString = f5tfProgramID.String()
// only pump.fun function // only pump.fun function
photonProgramID = solana.MustPublicKeyFromBase58("BSfD6SHZigAfDWSjzD5Q41jw8LmKwtmjskPH9XW1mrRW") photonProgramID = solana.MustPublicKeyFromBase58("BSfD6SHZigAfDWSjzD5Q41jw8LmKwtmjskPH9XW1mrRW")
photonProgramIDString = photonProgramID.String()
pumpAmmProgramID = solana.MustPublicKeyFromBase58("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA") pumpAmmProgramID = solana.MustPublicKeyFromBase58("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
pumpAmmProgramIDString = pumpAmmProgramID.String()
boboProgramID = solana.MustPublicKeyFromBase58("BobogA5N2KN2GG4XN3E3rNNRw3L8H1QPXp7QLxGrNHGM") boboProgramID = solana.MustPublicKeyFromBase58("BobogA5N2KN2GG4XN3E3rNNRw3L8H1QPXp7QLxGrNHGM")
boboProgramIDString = boboProgramID.String()
qtkvProgramID = solana.MustPublicKeyFromBase58("qtkvapJEvRWWrB7i5K6RaA1kvq5x3qmMKZ98ad71XQ7") qtkvProgramID = solana.MustPublicKeyFromBase58("qtkvapJEvRWWrB7i5K6RaA1kvq5x3qmMKZ98ad71XQ7")
qtkvProgramIDString = qtkvProgramID.String()
// only buy function with pump program // only buy function with pump program
fjszProgramID = solana.MustPublicKeyFromBase58("FJsZbftBqRLfF7uqUKpm4s2goDr6xsQ5Q3mN7AFJB6hK") fjszProgramID = solana.MustPublicKeyFromBase58("FJsZbftBqRLfF7uqUKpm4s2goDr6xsQ5Q3mN7AFJB6hK")
fjszProgramIDString = fjszProgramID.String()
flasProgramID = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9") flasProgramID = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9")
flasProgramIDString = flasProgramID.String()
terminalProgramID = solana.MustPublicKeyFromBase58("term9YPb9mzAsABaqN71A4xdbxHmpBNZavpBiQKZzN3") terminalProgramID = solana.MustPublicKeyFromBase58("term9YPb9mzAsABaqN71A4xdbxHmpBNZavpBiQKZzN3")
terminalProgramIDString = terminalProgramID.String()
jupiterV6ProgramID = solana.MustPublicKeyFromBase58("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4") jupiterV6ProgramID = solana.MustPublicKeyFromBase58("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4")
jupiterV6ProgramIDString = jupiterV6ProgramID.String()
gmgnProgramID = solana.MustPublicKeyFromBase58("GMgnVFR8Jb39LoXsEVzb3DvBy3ywCmdmJquHUy1Lrkqb")
bonkProgramID = solana.MustPublicKeyFromBase58("BBRouter1cVunVXvkcqeKkZQcBK7ruan37PPm3xzWaXD")
) )
type AccountNotFoundError struct { type AccountNotFoundError struct {
@@ -105,6 +98,10 @@ var (
terminalBuyTokensIX = []byte{0xa6, 0x54, 0x14, 0x96, 0x9f, 0x77, 0x59, 0xca} terminalBuyTokensIX = []byte{0xa6, 0x54, 0x14, 0x96, 0x9f, 0x77, 0x59, 0xca}
terminalSellTokensIX = []byte{0xbe, 0x84, 0xa2, 0x96, 0x93, 0x7c, 0xf8, 0x6b} terminalSellTokensIX = []byte{0xbe, 0x84, 0xa2, 0x96, 0x93, 0x7c, 0xf8, 0x6b}
terminalAmmSellTokensIX = []byte{0x40, 0x64, 0x97, 0xb9, 0x16, 0xfa, 0xec, 0xb1} terminalAmmSellTokensIX = []byte{0x40, 0x64, 0x97, 0xb9, 0x16, 0xfa, 0xec, 0xb1}
gmgnBuyTokensIX = []byte{0x66, 0x06, 0x3d, 0x12, 0x01, 0xda, 0xeb, 0xea}
bonkBuyAndSellTokensIX = []byte{0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a}
) )
type compiledInstruction struct { type compiledInstruction struct {
@@ -309,50 +306,56 @@ func ParseTransaction(update *SubscribeUpdateTransaction, loader *AddressTables,
switch programID { switch programID {
case pumpProgramID: case pumpProgramID:
txRes, err := parsePumpInstruction(versioned, i) txRes, err := parsePumpInstruction(versioned, i)
parsed = appendParsed(now, parsed, txRes, err, txHash, "pump", pumpProgramIDString) parsed = appendParsed(now, parsed, txRes, err, txHash, "pump")
case azczProgramID: case azczProgramID:
txRes, err := parseAzczInstruction(versioned, i) txRes, err := parseAzczInstruction(versioned, i)
parsed = appendParsed(now, parsed, txRes, err, txHash, "azcz", azczProgramIDString) parsed = appendParsed(now, parsed, txRes, err, txHash, "azcz")
case f5tfProgramID: case f5tfProgramID:
txRes, err := parseF5tfInstruction(versioned, i) txRes, err := parseF5tfInstruction(versioned, i)
parsed = appendParsed(now, parsed, txRes, err, txHash, "f5tf", f5tfProgramIDString) parsed = appendParsed(now, parsed, txRes, err, txHash, "f5tf")
case flasProgramID: case flasProgramID:
txRes, err := parseFlasInstruction(versioned, i) txRes, err := parseFlasInstruction(versioned, i)
parsed = appendParsed(now, parsed, txRes, err, txHash, "flas", flasProgramIDString) parsed = appendParsed(now, parsed, txRes, err, txHash, "flas")
case photonProgramID: case photonProgramID:
txRes, err := parsePhotonInstruction(versioned, i) txRes, err := parsePhotonInstruction(versioned, i)
parsed = appendParsed(now, parsed, txRes, err, txHash, "photon", photonProgramIDString) parsed = appendParsed(now, parsed, txRes, err, txHash, "photon")
case pumpAmmProgramID: case pumpAmmProgramID:
txRes, err := parsePumpAmmInstruction(versioned, i) txRes, err := parsePumpAmmInstruction(versioned, i)
parsed = appendParsed(now, parsed, txRes, err, txHash, "pumpamm", pumpAmmProgramIDString) parsed = appendParsed(now, parsed, txRes, err, txHash, "pumpamm")
case boboProgramID: case boboProgramID:
txRes, err := parseBoboInstruction(versioned, i) txRes, err := parseBoboInstruction(versioned, i)
parsed = appendParsed(now, parsed, txRes, err, txHash, "bobo", boboProgramIDString) parsed = appendParsed(now, parsed, txRes, err, txHash, "bobo")
case qtkvProgramID: case qtkvProgramID:
txRes, err := parseQtkvInstruction(versioned, i) txRes, err := parseQtkvInstruction(versioned, i)
parsed = appendParsed(now, parsed, txRes, err, txHash, "qtkv", qtkvProgramIDString) parsed = appendParsed(now, parsed, txRes, err, txHash, "qtkv")
case fjszProgramID: case fjszProgramID:
txRes, err := parseFjszInstruction(versioned, i) txRes, err := parseFjszInstruction(versioned, i)
parsed = appendParsed(now, parsed, txRes, err, txHash, "fjsz", fjszProgramIDString) parsed = appendParsed(now, parsed, txRes, err, txHash, "fjsz")
case terminalProgramID: case terminalProgramID:
txRes, err := parseTermInstruction(versioned, i) txRes, err := parseTermInstruction(versioned, i)
parsed = appendParsed(now, parsed, txRes, err, txHash, "terminal", terminalProgramIDString) parsed = appendParsed(now, parsed, txRes, err, txHash, "terminal")
case jupiterV6ProgramID: case jupiterV6ProgramID:
txRes, err := parseJupiterV6Instruction(versioned, i) txRes, err := parseJupiterV6Instruction(versioned, i)
parsed = appendParsed(now, parsed, txRes, err, txHash, "jupiterv6", jupiterV6ProgramIDString) parsed = appendParsed(now, parsed, txRes, err, txHash, "jupiterv6")
case okxDexRouteV2ProgramID: case okxDexRouteV2ProgramID:
txRes, err := parseOkxDexRouteV2Instruction(versioned, i) txRes, err := parseOkxDexRouteV2Instruction(versioned, i)
parsed = appendParsed(now, parsed, txRes, err, txHash, "okxdexroutev2", okxDexRouteV2ProgramIDString) parsed = appendParsed(now, parsed, txRes, err, txHash, "okxdexroutev2")
case dflowProgramID: case dflowProgramID:
txRes, err := parseDFlowInstruction(versioned, i) txRes, err := parseDFlowInstruction(versioned, i)
parsed = appendParsed(now, parsed, txRes, err, txHash, "dflow", dflowProgramString) parsed = appendParsed(now, parsed, txRes, err, txHash, "dflow")
case gmgnProgramID:
txRes, err := parseGMGNInstruction(versioned, i)
parsed = appendParsed(now, parsed, txRes, err, txHash, "gmgn")
case bonkProgramID:
txRes, err := parseBonkInstruction(versioned, i)
parsed = appendParsed(now, parsed, txRes, err, txHash, "bonk")
} }
} }
return parsed return parsed
} }
func appendParsed(start time.Time, list []*TxSignal, parsed *TxSignal, err error, txHash [64]byte, label string, entryContract string) []*TxSignal { func appendParsed(start time.Time, list []*TxSignal, parsed *TxSignal, err error, txHash [64]byte, label string) []*TxSignal {
if err != nil { if err != nil {
if !strings.HasPrefix(err.Error(), "account index") { if !strings.HasPrefix(err.Error(), "account index") {
logger.Debug("txparser: failed to parse", "label", label, "instruction", err, "tx_hash", base58.Encode(txHash[:])) logger.Debug("txparser: failed to parse", "label", label, "instruction", err, "tx_hash", base58.Encode(txHash[:]))
@@ -360,7 +363,6 @@ func appendParsed(start time.Time, list []*TxSignal, parsed *TxSignal, err error
return list return list
} }
if parsed != nil { if parsed != nil {
parsed.EntryContract = entryContract
parsed.Label = label parsed.Label = label
if !start.IsZero() { if !start.IsZero() {
parsed.ParseEnd = time.Now() parsed.ParseEnd = time.Now()
@@ -1004,6 +1006,66 @@ func parseFlasBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, er
}, nil }, nil
} }
func parseGMGNInstruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) {
msg := tx.Message
if instructionIndex >= len(msg.Instructions) {
return nil, fmt.Errorf("instruction index out of bounds")
}
instruction := msg.Instructions[instructionIndex]
if len(instruction.Data) == 0 {
return nil, fmt.Errorf("data is empty")
}
if len(instruction.Data) < 8 {
return nil, nil
}
if matchMethod(instruction.Data, gmgnBuyTokensIX) {
return parseGMGNBuy(tx, &instruction)
}
return nil, nil
}
func parseGMGNBuy(tx *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) {
if len(instruction.Accounts) < 8 {
return nil, fmt.Errorf("accounts too short")
}
if len(instruction.Data) < 24 {
return nil, fmt.Errorf("data too short for gmgn buy args, len=%d", len(instruction.Data))
}
staticKeys := tx.Message.StaticAccountKeys
mint, err := getStaticKey(staticKeys, int(instruction.Accounts[2]))
if err != nil {
return nil, err
}
user, err := getStaticKey(staticKeys, int(instruction.Accounts[6]))
if err != nil {
return nil, err
}
solAmount := binary.LittleEndian.Uint64(instruction.Data[8:16])
tokenAmount := binary.LittleEndian.Uint64(instruction.Data[16:24])
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "gmgn",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(tokenAmount),
Token1Amount: formatSolAmount(solAmount),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: true,
Block: tx.Block,
Token0AmountUint64: tokenAmount,
Token1AmountUint64: solAmount,
}, nil
}
func parsePhotonInstruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { func parsePhotonInstruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) {
msg := tx.Message msg := tx.Message
if instructionIndex >= len(msg.Instructions) { if instructionIndex >= len(msg.Instructions) {
@@ -1064,6 +1126,7 @@ func parsePhotonBuy(tx *versionedTransaction, instruction *compiledInstruction)
Event: "buy", Event: "buy",
IsToken2022: false, IsToken2022: false,
IsMayhemMode: false, IsMayhemMode: false,
ExactSOL: true,
Block: tx.Block, Block: tx.Block,
Token0AmountUint64: args.TokenAmount, Token0AmountUint64: args.TokenAmount,
Token1AmountUint64: solAmount, Token1AmountUint64: solAmount,
@@ -1238,6 +1301,7 @@ func parseTermBuy(tx *versionedTransaction, instruction *compiledInstruction) (*
Event: "buy", Event: "buy",
IsToken2022: false, IsToken2022: false,
IsMayhemMode: false, IsMayhemMode: false,
ExactSOL: true,
Block: tx.Block, Block: tx.Block,
Token0AmountUint64: tokenAmount, Token0AmountUint64: tokenAmount,
Token1AmountUint64: solAmount, Token1AmountUint64: solAmount,
@@ -1273,6 +1337,7 @@ func parseTermSell(tx *versionedTransaction, instruction *compiledInstruction) (
Event: "buy", Event: "buy",
IsToken2022: false, IsToken2022: false,
IsMayhemMode: false, IsMayhemMode: false,
ExactSOL: false,
Block: tx.Block, Block: tx.Block,
Token0AmountUint64: tokenAmount, Token0AmountUint64: tokenAmount,
Token1AmountUint64: solAmount, Token1AmountUint64: solAmount,
@@ -1645,7 +1710,7 @@ func parseFjszInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi
}, nil }, nil
} }
func parseTerminalInstruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { func parseBonkInstruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) {
msg := tx.Message msg := tx.Message
if instructionIndex >= len(msg.Instructions) { if instructionIndex >= len(msg.Instructions) {
return nil, fmt.Errorf("instruction index out of bounds") return nil, fmt.Errorf("instruction index out of bounds")
@@ -1655,24 +1720,87 @@ func parseTerminalInstruction(tx *versionedTransaction, instructionIndex int) (*
if len(instruction.Data) == 0 { if len(instruction.Data) == 0 {
return nil, fmt.Errorf("data is empty") return nil, fmt.Errorf("data is empty")
} }
if matchMethod(instruction.Data, terminalBuyTokensIX) {
return parseTermBuy(tx, &instruction) if matchMethod(instruction.Data, bonkBuyAndSellTokensIX) {
} else if matchMethod(instruction.Data, terminalSellTokensIX) { return parseBonkBuyAndSell(tx, &instruction)
return parseTermSell(tx, &instruction)
} else if matchMethod(instruction.Data, terminalAmmSellTokensIX) {
return parseTermAmmSell(tx, &instruction)
} }
return nil, nil return nil, nil
} }
func indexOf(haystack []uint8, needle uint8) int { func parseBonkBuyAndSell(tx *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) {
for i, v := range haystack { if len(instruction.Accounts) < 8 {
if v == needle { return nil, fmt.Errorf("accounts too short")
return i
} }
staticKeys := tx.Message.StaticAccountKeys
programId, err := getStaticKey(staticKeys, int(instruction.Accounts[7]))
if err != nil {
return nil, err
}
if programId != pumpProgramID {
return nil, nil
}
user, err := getStaticKey(staticKeys, int(instruction.Accounts[0]))
if err != nil {
return nil, err
}
flagAccount, err := getStaticKey(staticKeys, int(instruction.Accounts[4]))
if err != nil {
return nil, err
}
amount1 := binary.LittleEndian.Uint64(instruction.Data[17:25])
amount2 := binary.LittleEndian.Uint64(instruction.Data[25:33])
if user == flagAccount {
mint, err := getStaticKey(staticKeys, int(instruction.Accounts[6]))
if err != nil {
return nil, err
}
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "bonk",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(amount2),
Token1Amount: formatSolAmount(amount1),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: true,
Block: tx.Block,
Token0AmountUint64: amount2,
Token1AmountUint64: amount1,
}, nil
} else {
mint, err := getStaticKey(staticKeys, int(instruction.Accounts[5]))
if err != nil {
return nil, err
}
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "bonk",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(amount1),
Token1Amount: formatSolAmount(amount2),
Program: "Pump",
Event: "sell",
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: false,
Block: tx.Block,
Token0AmountUint64: amount1,
Token1AmountUint64: amount2,
}, nil
} }
return -1
} }
func matchMethod(data []byte, methods []byte) bool { func matchMethod(data []byte, methods []byte) bool {

View File

@@ -1,9 +1,13 @@
package shreder package shreder
import ( import (
"context"
"encoding/hex" "encoding/hex"
"os"
"testing" "testing"
"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc"
"github.com/near/borsh-go" "github.com/near/borsh-go"
) )
@@ -54,3 +58,234 @@ func TestDecodeAxiomArgs(t *testing.T) {
}) })
} }
} }
func toUpdata(slot uint64, tx *solana.Transaction) *SubscribeUpdateTransaction {
signatures := make([][]byte, len(tx.Signatures))
for i, sig := range tx.Signatures {
signatures[i] = sig[:]
}
accountKeys := make([][]byte, len(tx.Message.AccountKeys))
for i, key := range tx.Message.AccountKeys {
accountKeys[i] = key[:]
}
instructions := make([]*CompiledInstruction, len(tx.Message.Instructions))
for i, instr := range tx.Message.Instructions {
accounts := make([]byte, len(instr.Accounts))
for j, acc := range instr.Accounts {
accounts[j] = byte(acc)
}
instructions[i] = &CompiledInstruction{
ProgramIdIndex: uint32(instr.ProgramIDIndex),
Accounts: accounts,
Data: instr.Data[:],
}
}
addressTableLookups := make([]*MessageAddressTableLookup, len(tx.Message.AddressTableLookups))
for i, lookup := range tx.Message.AddressTableLookups {
writable := make([]byte, len(lookup.WritableIndexes))
for j, idx := range lookup.WritableIndexes {
writable[j] = byte(idx)
}
readonly := make([]byte, len(lookup.ReadonlyIndexes))
for j, idx := range lookup.ReadonlyIndexes {
readonly[j] = byte(idx)
}
addressTableLookups[i] = &MessageAddressTableLookup{
AccountKey: lookup.AccountKey[:],
WritableIndexes: writable,
ReadonlyIndexes: readonly,
}
}
return &SubscribeUpdateTransaction{
Transaction: &Transaction{
Signatures: signatures,
Message: &Message{
Header: &MessageHeader{
NumRequiredSignatures: uint32(tx.Message.Header.NumRequiredSignatures),
NumReadonlySignedAccounts: uint32(tx.Message.Header.NumReadonlySignedAccounts),
NumReadonlyUnsignedAccounts: uint32(tx.Message.Header.NumReadonlyUnsignedAccounts),
},
AccountKeys: accountKeys,
RecentBlockhash: nil, // TODO
Instructions: instructions,
Versioned: false, // TODO
AddressTableLookups: addressTableLookups,
},
},
Slot: slot,
}
}
func getTransaction(t *testing.T, client *rpc.Client, signature string) *SubscribeUpdateTransaction {
version := uint64(0)
tx, err := client.GetTransaction(
context.Background(),
solana.MustSignatureFromBase58(signature),
&rpc.GetTransactionOpts{
Commitment: rpc.CommitmentFinalized,
MaxSupportedTransactionVersion: &version,
},
)
if err != nil {
t.Fatalf("failed to get transaction: %v", err)
}
_tx, err := tx.Transaction.GetTransaction()
if err != nil {
t.Fatalf("failed to get transaction: %v", err)
}
return toUpdata(tx.Slot, _tx)
}
func TestParseTermBuy(t *testing.T) {
rpcUrl := os.Getenv("SOL_RPC_URL")
if rpcUrl == "" {
t.Fatalf("SOL_RPC_URL is not set")
}
client := rpc.New(rpcUrl)
signals := ParseTransaction(
getTransaction(t, client, "5Gz1fa4Qhb35bkg9QCMXpxCX5uuNr7WcjcmrwajGZA7kXsvNS9pDnYe12ggWeSqf1nwZbVPob6DkX6fcwbE9ofBR"),
nil,
false,
)
if len(signals) != 1 {
t.Fatalf("expected 1 signal, got %d", len(signals))
}
signal := signals[0]
if signal.Label != "terminal" {
t.Fatalf("expected terminal signal, got %s", signal.Label)
}
if signal.Event != "buy" {
t.Fatalf("expected buy event, got %s", signal.Event)
}
if signal.Maker != "BaLxyjXzATAnfm7cc5AFhWBpiwnsb71THcnofDLTWAPK" {
t.Fatalf("expected maker BaLxyjXzATAnfm7cc5AFhWBpiwnsb71THcnofDLTWAPK, got %s", signal.Maker)
}
if signal.Token0Address != "5Wgv54peXRKDHYHapAELzgNKEPEh9E5Bf3hUR3sTpump" {
t.Fatalf("expected token0 address 5Wgv54peXRKDHYHapAELzgNKEPEh9E5Bf3hUR3sTpump, got %s", signal.Token0Address)
}
if signal.Token0AmountUint64 != 6952026214256 {
t.Fatalf("expected token0 amount 6952026214256, got %d", signal.Token0AmountUint64)
}
if signal.Token1AmountUint64 != 653333333 {
t.Fatalf("expected token1 amount 653333333, got %d", signal.Token1AmountUint64)
}
}
func TestParseBonkBuy(t *testing.T) {
rpcUrl := os.Getenv("SOL_RPC_URL")
if rpcUrl == "" {
t.Fatalf("SOL_RPC_URL is not set")
}
client := rpc.New(rpcUrl)
signals := ParseTransaction(
getTransaction(t, client, "3gHF3TA2aA8rpjdmoEs2vA89vrq9J9NnTTUSXHfE6uXcaYP9cJgLtEUjCmsK9EWAyHEg7cEiepehQf4GFv1272jW"),
nil,
false,
)
if len(signals) != 1 {
t.Fatalf("expected 1 signal, got %d", len(signals))
}
signal := signals[0]
if signal.Label != "bonk" {
t.Fatalf("expected bonk signal, got %s", signal.Label)
}
if signal.Event != "buy" {
t.Fatalf("expected buy event, got %s", signal.Event)
}
if signal.Maker != "BFobdhAbdBteBuDvHUdBthsQqJyMuWnG9SGUheW1Ni2C" {
t.Fatalf("expected maker BFobdhAbdBteBuDvHUdBthsQqJyMuWnG9SGUheW1Ni2C, got %s", signal.Maker)
}
if signal.Token0Address != "Awupo9Jxe1fsc7eEtCEcN9D3PoyReQhc9WEuEAHXpump" {
t.Fatalf("expected token0 address Awupo9Jxe1fsc7eEtCEcN9D3PoyReQhc9WEuEAHXpump, got %s", signal.Token0Address)
}
if signal.Token0AmountUint64 != 8616799656436 {
t.Fatalf("expected token0 amount 8616799656436, got %d", signal.Token0AmountUint64)
}
if signal.Token1AmountUint64 != 495000000 {
t.Fatalf("expected token1 amount 495000000, got %d", signal.Token1AmountUint64)
}
}
func TestParseBonkSell(t *testing.T) {
rpcUrl := os.Getenv("SOL_RPC_URL")
if rpcUrl == "" {
t.Fatalf("SOL_RPC_URL is not set")
}
client := rpc.New(rpcUrl)
signals := ParseTransaction(
getTransaction(t, client, "3XNi6b3j69SSStqLLRQVH5BNGVfEoFxGCzmpdd5FvrY4kmC8T644WGdEhCH9fAdrxWuR2Mtzgywq8K7qetu5MGyb"),
nil,
false,
)
if len(signals) != 1 {
t.Fatalf("expected 1 signal, got %d", len(signals))
}
signal := signals[0]
if signal.Label != "bonk" {
t.Fatalf("expected bonk signal, got %s", signal.Label)
}
if signal.Event != "sell" {
t.Fatalf("expected sell event, got %s", signal.Event)
}
if signal.Maker != "2xTT7XXCEYSCrRb3G4Egc4ZwpCe78qq6r7w6ChZhbTXc" {
t.Fatalf("expected maker 2xTT7XXCEYSCrRb3G4Egc4ZwpCe78qq6r7w6ChZhbTXc, got %s", signal.Maker)
}
if signal.Token0Address != "8pgpJDYuojYXvb8KE4Hv7DCty12FrkqpKChgfHzspump" {
t.Fatalf("expected token0 address 8pgpJDYuojYXvb8KE4Hv7DCty12FrkqpKChgfHzspump, got %s", signal.Token0Address)
}
if signal.Token0AmountUint64 != 6235736929390 {
t.Fatalf("expected token0 amount 6235736929390, got %d", signal.Token0AmountUint64)
}
if signal.Token1AmountUint64 != 1379707703 {
t.Fatalf("expected token1 amount 1379707703, got %d", signal.Token1AmountUint64)
}
}
func TestParsePhotonBuy(t *testing.T) {
rpcUrl := os.Getenv("SOL_RPC_URL")
if rpcUrl == "" {
t.Fatalf("SOL_RPC_URL is not set")
}
client := rpc.New(rpcUrl)
signals := ParseTransaction(
getTransaction(t, client, "4DCEcXAWBxagXoUNGhWsJ7qfxq5SuE5BG2cBDBqAY7sCHkBopaMJu33ZnXnFHqzPMmWxVxq6666KRF4hMHVB33Ux"),
nil,
false,
)
if len(signals) != 1 {
t.Fatalf("expected 1 signal, got %d", len(signals))
}
signal := signals[0]
if signal.Label != "photon" {
t.Fatalf("expected terminal signal, got %s", signal.Label)
}
if signal.Event != "buy" {
t.Fatalf("expected buy event, got %s", signal.Event)
}
if signal.Maker != "8sUm7sLf3Steu6oVyVQqoA9GpFcMRz6YhrAidd4x7g7a" {
t.Fatalf("expected maker 8sUm7sLf3Steu6oVyVQqoA9GpFcMRz6YhrAidd4x7g7a, got %s", signal.Maker)
}
if signal.Token0Address != "jx4PF2MwC7AK9S8dTeYm29hM3vAN8Rtfs2VX4Vz5UVj" {
t.Fatalf("expected token0 address jx4PF2MwC7AK9S8dTeYm29hM3vAN8Rtfs2VX4Vz5UVj, got %s", signal.Token0Address)
}
if signal.Token0AmountUint64 != 1796593710706 {
t.Fatalf("expected token0 amount 1796593710706, got %d", signal.Token0AmountUint64)
}
if signal.Token1AmountUint64 != 1955555553 {
t.Fatalf("expected token1 amount 1955555553, got %d", signal.Token1AmountUint64)
}
}