3 Commits

Author SHA1 Message Date
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
2 changed files with 94 additions and 68 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

@@ -22,40 +22,31 @@ 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")
) )
type AccountNotFoundError struct { type AccountNotFoundError struct {
@@ -105,6 +96,8 @@ 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}
) )
type compiledInstruction struct { type compiledInstruction struct {
@@ -309,50 +302,53 @@ 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")
} }
} }
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 +356,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 +999,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) {
@@ -1238,6 +1293,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 +1329,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,36 +1702,6 @@ func parseFjszInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi
}, nil }, nil
} }
func parseTerminalInstruction(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 matchMethod(instruction.Data, terminalBuyTokensIX) {
return parseTermBuy(tx, &instruction)
} else if matchMethod(instruction.Data, terminalSellTokensIX) {
return parseTermSell(tx, &instruction)
} else if matchMethod(instruction.Data, terminalAmmSellTokensIX) {
return parseTermAmmSell(tx, &instruction)
}
return nil, nil
}
func indexOf(haystack []uint8, needle uint8) int {
for i, v := range haystack {
if v == needle {
return i
}
}
return -1
}
func matchMethod(data []byte, methods []byte) bool { func matchMethod(data []byte, methods []byte) bool {
if len(data) < len(methods) { if len(data) < len(methods) {
return false return false