Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09de6ba649 | ||
|
|
7a82990770 | ||
|
|
e82bcb3c07 | ||
|
|
a74f769064 | ||
|
|
1e276e8bd2 | ||
|
|
eb2bde98ac | ||
| 66f0d247f5 | |||
| 879b7fefad | |||
| 149dfae378 | |||
| 8c4b43747c | |||
|
|
e9ba16766f | ||
|
|
cd1d681621 | ||
|
|
920c5ba25b | ||
|
|
3d447ef2e8 | ||
|
|
b0d4342fa2 | ||
|
|
972ddc7960 | ||
| bcd442195c | |||
| 0633707142 | |||
| 8e49f01054 | |||
|
|
62cc64a90a | ||
|
|
629ffe2ea7 | ||
|
|
56dac04a2a | ||
|
|
852ad4b382 |
@@ -27,10 +27,11 @@ func budgetParser(tx *Tx, instr Instruction, _ InnerInstructions, offset [2]uint
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func computeUnitLimitParser(offset [2]uint, _ *Tx, decodedData []byte) ([2]uint, error) {
|
func computeUnitLimitParser(offset [2]uint, tx *Tx, decodedData []byte) ([2]uint, error) {
|
||||||
if len(decodedData) < 8 {
|
if len(decodedData) < 4 {
|
||||||
return increaseOffset(offset), nil
|
return increaseOffset(offset), nil
|
||||||
}
|
}
|
||||||
|
tx.CuLimit = binary.LittleEndian.Uint32(decodedData[:4])
|
||||||
return increaseOffset(offset), nil
|
return increaseOffset(offset), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
120
chainlink.go
Normal file
120
chainlink.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
package pump_parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/gagliardetto/solana-go"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
chainlinkSOLUSDFeedAccount = solana.MustPublicKeyFromBase58("CH31Xns5z3M1cTAbKW34jcxPPciazARpijcHj9rxtemt")
|
||||||
|
chainlinkSubmitDiscriminator = calculateDiscriminator("global:submit")
|
||||||
|
)
|
||||||
|
|
||||||
|
func chainLinkParser(tx *Tx, instruction Instruction, inners InnerInstructions, offset [2]uint) ([2]uint, error) {
|
||||||
|
if !tx.rawTx.accountList[instruction.ProgramIDIndex].Equals(chainLinkProgram) {
|
||||||
|
return increaseOffset(offset), fmt.Errorf("system program instruction not found, block: %d, tx: %s, outerIndex: %d, innerIndex: %d", tx.rawTx.Slot, tx.rawTx.TxHash(), offset[0], offset[1])
|
||||||
|
}
|
||||||
|
if tx.rawTx.Meta.Err != nil {
|
||||||
|
return increaseOffset(offset), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
decode := instruction.Data
|
||||||
|
discriminator := binary.LittleEndian.Uint32(decode[0:4])
|
||||||
|
|
||||||
|
switch discriminator {
|
||||||
|
case transferDiscriminator:
|
||||||
|
return chainLinkSubmitParser(instruction, inners, offset, tx, decode[4:])
|
||||||
|
default:
|
||||||
|
return increaseOffset(offset), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func chainLinkSubmitParser(instruction Instruction, inners InnerInstructions, offset [2]uint, tx *Tx, decodeData []byte) ([2]uint, error) {
|
||||||
|
if len(instruction.Accounts) < 6 {
|
||||||
|
return increaseOffset(offset), InstructionIgnoredError
|
||||||
|
}
|
||||||
|
|
||||||
|
inner, err := getInnerInstructions(inners, offset[1])
|
||||||
|
if err != nil {
|
||||||
|
return increaseOffset(offset), err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(inner) < 1 {
|
||||||
|
return increaseOffset(offset), InstructionIgnoredError
|
||||||
|
}
|
||||||
|
storeInstruction := inner[0]
|
||||||
|
if len(storeInstruction.Accounts) < 2 {
|
||||||
|
return increaseOffset(offset), InstructionIgnoredError
|
||||||
|
}
|
||||||
|
if storeInstruction.Accounts[0] >= len(tx.rawTx.accountList) || tx.rawTx.accountList[storeInstruction.Accounts[0]] != chainlinkSOLUSDFeedAccount {
|
||||||
|
return increaseOffset(offset), InstructionIgnoredError
|
||||||
|
}
|
||||||
|
if !bytes.Equal(storeInstruction.Data[0:8], chainlinkSubmitDiscriminator[:]) {
|
||||||
|
return increaseOffset(offset), InstructionIgnoredError
|
||||||
|
}
|
||||||
|
data, err := parseChainLinkSubmitData(storeInstruction.Data)
|
||||||
|
if err != nil {
|
||||||
|
return increaseOffset(offset), InstructionIgnoredError
|
||||||
|
}
|
||||||
|
tx.ChainLink.Timestamp = int64(data.Timestamp)
|
||||||
|
tx.ChainLink.Price = decimal.NewFromBigInt(data.Price(), -8)
|
||||||
|
return increaseOffset(offset), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubmitData struct {
|
||||||
|
Discriminator [8]byte
|
||||||
|
Timestamp uint64
|
||||||
|
Answer [16]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseChainLinkSubmitData(data []byte) (*SubmitData, error) {
|
||||||
|
if len(data) != 32 {
|
||||||
|
return nil, errors.New("invalid submit data length")
|
||||||
|
}
|
||||||
|
var submitData SubmitData
|
||||||
|
copy(submitData.Discriminator[:], data[:8])
|
||||||
|
submitData.Timestamp = binary.LittleEndian.Uint64(data[8:16])
|
||||||
|
copy(submitData.Answer[:], data[16:32])
|
||||||
|
return &submitData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubmitData) Price() *big.Int {
|
||||||
|
return int128LEBytesToBigInt(s.Answer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func int128LEBytesToBigInt(bytes [16]byte) *big.Int {
|
||||||
|
// Create new big.Int
|
||||||
|
bigInt := new(big.Int)
|
||||||
|
|
||||||
|
// Reverse bytes for little-endian to big-endian conversion
|
||||||
|
reversed := make([]byte, 16)
|
||||||
|
for i := 0; i < 16; i++ {
|
||||||
|
reversed[15-i] = bytes[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if negative (first byte in little-endian is highest byte)
|
||||||
|
isNegative := bytes[15]&0x80 != 0
|
||||||
|
|
||||||
|
if isNegative {
|
||||||
|
// If negative, flip all bits
|
||||||
|
for i := range reversed {
|
||||||
|
reversed[i] = ^reversed[i]
|
||||||
|
}
|
||||||
|
// Convert to big.Int
|
||||||
|
bigInt.SetBytes(reversed)
|
||||||
|
// Add 1 and negate
|
||||||
|
bigInt.Add(bigInt, big.NewInt(1))
|
||||||
|
bigInt.Neg(bigInt)
|
||||||
|
} else {
|
||||||
|
// If positive, convert directly
|
||||||
|
bigInt.SetBytes(reversed)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bigInt
|
||||||
|
}
|
||||||
576
checking.go
576
checking.go
@@ -1,576 +0,0 @@
|
|||||||
package pump_parser
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gagliardetto/solana-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
func checkBonkGmgnBuy(rawTx *RawTx) bool {
|
|
||||||
|
|
||||||
// 检查交易版本
|
|
||||||
var version, _ = rawTx.Version.(solana.MessageVersion)
|
|
||||||
if version != solana.MessageVersionLegacy {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查交易指令数量
|
|
||||||
if len(rawTx.Transaction.Message.Instructions) != 10 && len(rawTx.Transaction.Message.Instructions) != 9 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
accountList := rawTx.getAccountList()
|
|
||||||
// 检查 cu limit
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[0]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.ComputeBudget {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(instruction.Accounts) != 1 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
accountId := accountList[instruction.Accounts[0]].String()
|
|
||||||
if !strings.HasPrefix(accountId, "jitodontfront1111111111151111111111111655") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 cu price
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[1]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.ComputeBudget {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 ata.createIdempotent
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[2]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.SPLAssociatedTokenAccountProgramID {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if instruction.Data.String() != "2" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(instruction.Accounts) < 4 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// gmgn 会先创建 wsol 账户, 而不是 token 账户
|
|
||||||
accountId := accountList[instruction.Accounts[3]]
|
|
||||||
if accountId != solana.WrappedSol {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 transfer
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[3]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.SystemProgramID {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(instruction.Data) == 0 || instruction.Data[0] != 2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 token.syncNative
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[4]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.TokenProgramID {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if instruction.Data.String() != "J" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
offset := 5
|
|
||||||
if len(rawTx.Transaction.Message.Instructions) == 10 {
|
|
||||||
// 检查 ata.create
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[offset]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.SPLAssociatedTokenAccountProgramID {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if instruction.Data.String() != "1" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
offset++
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 bonk.buy
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[offset]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != raydiumLaunchLabProgramID {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
offset++
|
|
||||||
|
|
||||||
// 检查 token.closeAccount
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[offset]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.TokenProgramID {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if instruction.Data.String() != "A" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
offset++
|
|
||||||
|
|
||||||
// 检查 transfer
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[offset]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.SystemProgramID {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(instruction.Data) == 0 || instruction.Data[0] != 2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
offset++
|
|
||||||
|
|
||||||
// 检查 transfer
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[offset]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.SystemProgramID {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(instruction.Data) == 0 || instruction.Data[0] != 2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
axiomTxLoopupTable = solana.MustPublicKeyFromBase58("7RKtfATWCe98ChuwecNq8XCzAzfoK3DtZTprFsPMGtio")
|
|
||||||
axiomProgramID = solana.MustPublicKeyFromBase58("AxiomfHaWDemCFBLBayqnEnNwE6b7B2Qz3UmzMpgbMG6")
|
|
||||||
gmgnProgramID = solana.MustPublicKeyFromBase58("GMgnVFR8Jb39LoXsEVzb3DvBy3ywCmdmJquHUy1Lrkqb")
|
|
||||||
)
|
|
||||||
|
|
||||||
func checkBonkAxiomBuy(rawTx *RawTx) bool {
|
|
||||||
|
|
||||||
// 检查交易版本
|
|
||||||
var version, _ = rawTx.Version.(solana.MessageVersion)
|
|
||||||
if version == solana.MessageVersionLegacy || len(rawTx.Transaction.Message.AddressTableLookups) != 1 {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid transaction version or address table lookups: %v %v %v", rawTx.Transaction.Signatures[0].String(), version, len(rawTx.Transaction.Message.AddressTableLookups))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 addressLookupTable 是否是 Axiom 的
|
|
||||||
if rawTx.Transaction.Message.AddressTableLookups[0].AccountKey != axiomTxLoopupTable {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid address lookup table: %v", rawTx.Transaction.Signatures[0].String())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查交易指令数量
|
|
||||||
if len(rawTx.Transaction.Message.Instructions) != 10 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
accountList := rawTx.getAccountList()
|
|
||||||
// 检查 cu limit
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[0]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.ComputeBudget {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid program ID for ComputeBudget: %v", programId)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(instruction.Accounts) != 1 {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid number of accounts for ComputeBudget: %v", len(instruction.Accounts))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
accountId := accountList[instruction.Accounts[0]].String()
|
|
||||||
if !strings.HasPrefix(accountId, "jitodontfront") || !strings.HasSuffix(accountId, "TradeWithAxiomDotTrade") {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid account ID for ComputeBudget: %v", accountId)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 cu price
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[1]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.ComputeBudget {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid program ID for ComputeBudget: %v", programId)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 ata.createIdempotent
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[2]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.SPLAssociatedTokenAccountProgramID {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid program ID for SPLAssociatedTokenAccount: %v", programId)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if instruction.Data.String() != "2" {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid data for SPLAssociatedTokenAccount: %v", instruction.Data.String())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(instruction.Accounts) < 4 {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid number of accounts for SPLAssociatedTokenAccount: %v", len(instruction.Accounts))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// axiom 会先创建 token 账户, 而不是 wsol 账户
|
|
||||||
accountId := accountList[instruction.Accounts[3]]
|
|
||||||
if accountId == solana.WrappedSol {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid account ID for SPLAssociatedTokenAccount, expected token account but got wsol: %v", accountId)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 ata.createIdempotent
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[3]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.SPLAssociatedTokenAccountProgramID {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid program ID for SPLAssociatedTokenAccount2: %v", programId)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if instruction.Data.String() != "2" {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid data for SPLAssociatedTokenAccount2: %v", instruction.Data.String())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(instruction.Accounts) < 4 {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid number of accounts for SPLAssociatedTokenAccount2: %v", len(instruction.Accounts))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// axiom 会先创建 token 账户, 而不是 wsol 账户
|
|
||||||
accountId := accountList[instruction.Accounts[3]]
|
|
||||||
if accountId != solana.WrappedSol {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid account ID for SPLAssociatedTokenAccount2, expected wsol but got: %v", accountId)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 transfer
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[4]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.SystemProgramID {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid program ID for SystemProgram3: %v", programId)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(instruction.Data) == 0 || instruction.Data[0] != 2 {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid data for SystemProgram transfer3: %v", instruction.Data)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 token.syncNative
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[5]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.TokenProgramID {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid program ID for TokenProgram3: %v", programId)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if instruction.Data.String() != "J" {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid data for TokenProgram syncNative3: %v", instruction.Data.String())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 bonk.buy
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[6]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != raydiumLaunchLabProgramID {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 token.closeAccount
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[7]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.TokenProgramID {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid program ID for TokenProgram closeAccount: %v", programId)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if instruction.Data.String() != "A" {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid data for TokenProgram closeAccount: %v", instruction.Data.String())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 transfer
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[8]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.SystemProgramID {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid program ID for SystemProgram transfer4: %v", programId)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(instruction.Data) == 0 || instruction.Data[0] != 2 {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid data for SystemProgram transfer4: %v", instruction.Data)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 transfer
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[9]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.SystemProgramID {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid program ID for SystemProgram transfer5: %v", programId)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(instruction.Data) == 0 || instruction.Data[0] != 2 {
|
|
||||||
log.Printf("CheckBonkAxiomBuy: Invalid data for SystemProgram transfer5: %v", instruction.Data)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkPumpFunAxiomBuy(rawTx *RawTx) bool {
|
|
||||||
|
|
||||||
// 检查交易版本
|
|
||||||
if rawTx.Version == "legacy" || len(rawTx.Transaction.Message.AddressTableLookups) != 1 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 addressLookupTable 是否是 Axiom 的
|
|
||||||
if rawTx.Transaction.Message.AddressTableLookups[0].AccountKey != axiomTxLoopupTable {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查交易指令数量
|
|
||||||
if len(rawTx.Transaction.Message.Instructions) != 6 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
accountList := rawTx.getAccountList()
|
|
||||||
// 检查 cu limit
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[0]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.ComputeBudget {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(instruction.Accounts) != 1 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
accountId := accountList[instruction.Accounts[0]].String()
|
|
||||||
if !strings.HasPrefix(accountId, "jitodontfront") || !strings.HasSuffix(accountId, "TradeWithAxiomDotTrade") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 cu price
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[1]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.ComputeBudget {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 ata.createIdempotent
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[2]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.SPLAssociatedTokenAccountProgramID {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if instruction.Data.String() != "2" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(instruction.Accounts) < 4 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// axiom 会先创建 token 账户, 而不是 wsol 账户
|
|
||||||
accountId := accountList[instruction.Accounts[3]]
|
|
||||||
if accountId == solana.WrappedSol {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查调用axiom合约
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[3]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != axiomProgramID {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 transfer
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[4]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.SystemProgramID {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(instruction.Data) == 0 || instruction.Data[0] != 2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 transfer
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[5]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.SystemProgramID {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(instruction.Data) == 0 || instruction.Data[0] != 2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkPumpFunGmgnBuy(rawTx *RawTx) bool {
|
|
||||||
|
|
||||||
// 检查交易版本
|
|
||||||
if rawTx.Version != "legacy" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查交易指令数量
|
|
||||||
if len(rawTx.Transaction.Message.Instructions) != 6 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
accountList := rawTx.getAccountList()
|
|
||||||
// 检查 cu limit
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[0]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.ComputeBudget {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(instruction.Accounts) != 1 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
accountId := accountList[instruction.Accounts[0]].String()
|
|
||||||
if !strings.HasPrefix(accountId, "jitodontfront1111111111151111111111111655") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 cu price
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[1]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.ComputeBudget {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 ata.createIdempotent
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[2]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.SPLAssociatedTokenAccountProgramID {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if instruction.Data.String() != "2" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(instruction.Accounts) < 4 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
accountId := accountList[instruction.Accounts[3]]
|
|
||||||
if accountId == solana.WrappedSol {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查调用 gmgn 合约
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[3]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != gmgnProgramID {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 transfer
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[4]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.SystemProgramID {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(instruction.Data) == 0 || instruction.Data[0] != 2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 transfer
|
|
||||||
{
|
|
||||||
instruction := rawTx.Transaction.Message.Instructions[5]
|
|
||||||
programId := accountList[instruction.ProgramIDIndex]
|
|
||||||
if programId != solana.SystemProgramID {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(instruction.Data) == 0 || instruction.Data[0] != 2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
13
consts.go
13
consts.go
@@ -39,6 +39,14 @@ var platformFeeAddresses = map[solana.PublicKey]string{
|
|||||||
solana.MustPublicKeyFromBase58("F4hJ3Ee3c5UuaorKAMfELBjYCjiiLH75haZTKqTywRP3"): PlatformBullX,
|
solana.MustPublicKeyFromBase58("F4hJ3Ee3c5UuaorKAMfELBjYCjiiLH75haZTKqTywRP3"): PlatformBullX,
|
||||||
solana.MustPublicKeyFromBase58("47hEzz83VFR23rLTEeVm9A7eFzjJwjvdupPPmX3cePqF"): PlatformBanana,
|
solana.MustPublicKeyFromBase58("47hEzz83VFR23rLTEeVm9A7eFzjJwjvdupPPmX3cePqF"): PlatformBanana,
|
||||||
solana.MustPublicKeyFromBase58("9yMwSPk9mrXSN7yDHUuZurAh1sjbJsfpUqjZ7SvVtdco"): PlatformTrojan,
|
solana.MustPublicKeyFromBase58("9yMwSPk9mrXSN7yDHUuZurAh1sjbJsfpUqjZ7SvVtdco"): PlatformTrojan,
|
||||||
|
solana.MustPublicKeyFromBase58("92Med3qeK7duC5iiYsHX38H2f2twJfRsSx93oNrza2VH"): PlatformTrojan,
|
||||||
|
solana.MustPublicKeyFromBase58("65gDv7pZQCZELsNpNYSFEBtNFpWZAbxmRFB6BGMqFkHH"): PlatformTrojan,
|
||||||
|
solana.MustPublicKeyFromBase58("8jgg7moFJkHyTtAv9M6RBSPMp2oXeXhuiUMKW8YbYCWn"): PlatformTrojan,
|
||||||
|
solana.MustPublicKeyFromBase58("BJgbYMZgqm79gNrmm31tV3L8GQorw91XFm4m7evyfPjr"): PlatformTrojan,
|
||||||
|
solana.MustPublicKeyFromBase58("BWgb8wR1FEGiu1jCDSKuHKf752W27b4iN6SvoNCiK4qp"): PlatformTrojan,
|
||||||
|
solana.MustPublicKeyFromBase58("GV4Bt6ehW5x5dqtaWAJBSnz8uum5Z2Rp9P2Tr5iVuQn5"): PlatformTrojan,
|
||||||
|
solana.MustPublicKeyFromBase58("2jwHNxavSoMZMEDbT1eV9PcPt5dDcayCqM6MkgaPpmWQ"): PlatformTrojan,
|
||||||
|
solana.MustPublicKeyFromBase58("66N1M2aaDSdJFZ1d7YoVN4EU45ju6XiscapLVHn5FLms"): PlatformTrojan,
|
||||||
solana.MustPublicKeyFromBase58("4mih95RmBqfHYvEfqq6uGGLp1Fr3gVS3VNSEa3JVRfQK"): PlatformRaybot,
|
solana.MustPublicKeyFromBase58("4mih95RmBqfHYvEfqq6uGGLp1Fr3gVS3VNSEa3JVRfQK"): PlatformRaybot,
|
||||||
solana.MustPublicKeyFromBase58("3udvfL24waJcLhskRAsStNMoNUvtyXdxrWQz4hgi953N"): PlatformMoonshot,
|
solana.MustPublicKeyFromBase58("3udvfL24waJcLhskRAsStNMoNUvtyXdxrWQz4hgi953N"): PlatformMoonshot,
|
||||||
solana.MustPublicKeyFromBase58("3kxSQybWEeQZsMuNWMRJH4TxrhwoDwfv41TNMLRzFP5A"): PlatformMEVX,
|
solana.MustPublicKeyFromBase58("3kxSQybWEeQZsMuNWMRJH4TxrhwoDwfv41TNMLRzFP5A"): PlatformMEVX,
|
||||||
@@ -59,6 +67,7 @@ var platformFeeAddresses = map[solana.PublicKey]string{
|
|||||||
solana.MustPublicKeyFromBase58("HkJYryz2BNeMQfuuSWDYktWt5fZLV26eK6nqu7EJycoG"): PlatformAxiom,
|
solana.MustPublicKeyFromBase58("HkJYryz2BNeMQfuuSWDYktWt5fZLV26eK6nqu7EJycoG"): PlatformAxiom,
|
||||||
solana.MustPublicKeyFromBase58("BfFX9rUm8qTZiZjmeq9BktWVTNuG3YWMc5AvkrCKJike"): PlatformAxiom,
|
solana.MustPublicKeyFromBase58("BfFX9rUm8qTZiZjmeq9BktWVTNuG3YWMc5AvkrCKJike"): PlatformAxiom,
|
||||||
solana.MustPublicKeyFromBase58("2ApLdwLrGayEmxgpLX9BTR47Q2QprfMg5SpjrLeaK8s7"): PlatformAxiom,
|
solana.MustPublicKeyFromBase58("2ApLdwLrGayEmxgpLX9BTR47Q2QprfMg5SpjrLeaK8s7"): PlatformAxiom,
|
||||||
|
solana.MustPublicKeyFromBase58("AVahywMVNRYzdgWrufSWrtdGXAeNEvfpJFxhVFK516mT"): PlatformDexScreener,
|
||||||
}
|
}
|
||||||
|
|
||||||
var mevAgentFeeAddresses = map[solana.PublicKey]string{
|
var mevAgentFeeAddresses = map[solana.PublicKey]string{
|
||||||
@@ -188,6 +197,7 @@ var mevAgentFeeAddresses = map[solana.PublicKey]string{
|
|||||||
solana.MustPublicKeyFromBase58("ASde6y8pBCU1aityWHRpqT7pEAcEonjCgFUMeh5egRes"): MevAgentAstralane,
|
solana.MustPublicKeyFromBase58("ASde6y8pBCU1aityWHRpqT7pEAcEonjCgFUMeh5egRes"): MevAgentAstralane,
|
||||||
solana.MustPublicKeyFromBase58("ASUv6G8Cj6zt71UAqD1aVtDC3CRn6FFddqF17ZiegrES"): MevAgentAstralane,
|
solana.MustPublicKeyFromBase58("ASUv6G8Cj6zt71UAqD1aVtDC3CRn6FFddqF17ZiegrES"): MevAgentAstralane,
|
||||||
solana.MustPublicKeyFromBase58("ASY4mvCtrACKFK8Jiuvqcu8fad9gGTzvfm5zp4megRes"): MevAgentAstralane,
|
solana.MustPublicKeyFromBase58("ASY4mvCtrACKFK8Jiuvqcu8fad9gGTzvfm5zp4megRes"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("astraubkDw81n4LuutzSQ8uzHCv4BhPVhfvTcYv8SKC"): MevAgentAstralane,
|
||||||
solana.MustPublicKeyFromBase58("astraEJ2fEj8Xmy6KLG7B3VfbKfsHXhHrNdCQx7iGJK"): MevAgentAstralane,
|
solana.MustPublicKeyFromBase58("astraEJ2fEj8Xmy6KLG7B3VfbKfsHXhHrNdCQx7iGJK"): MevAgentAstralane,
|
||||||
solana.MustPublicKeyFromBase58("B1ooMsWjc4SUVVuLyCu1ig2RdomQnHKgMzBMfmSo3DK"): MevAgentAstralane,
|
solana.MustPublicKeyFromBase58("B1ooMsWjc4SUVVuLyCu1ig2RdomQnHKgMzBMfmSo3DK"): MevAgentAstralane,
|
||||||
solana.MustPublicKeyFromBase58("B1ooMZfUJmAvppzc5cr7eYG8Cenig4FbQGBytr4DGCh"): MevAgentAstralane,
|
solana.MustPublicKeyFromBase58("B1ooMZfUJmAvppzc5cr7eYG8Cenig4FbQGBytr4DGCh"): MevAgentAstralane,
|
||||||
@@ -358,6 +368,7 @@ var entryContractAddresses = map[solana.PublicKey]string{
|
|||||||
solana.MustPublicKeyFromBase58("B3111yJCeHBcA1bizdJjUFPALfhAfSRnAbJzGUtnt56A"): EntryContractBinanceWallet,
|
solana.MustPublicKeyFromBase58("B3111yJCeHBcA1bizdJjUFPALfhAfSRnAbJzGUtnt56A"): EntryContractBinanceWallet,
|
||||||
solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9"): EntryContractAxiom,
|
solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9"): EntryContractAxiom,
|
||||||
solana.MustPublicKeyFromBase58("F5tfvbLog9VdGUPqBDTT8rgXvTTcq7e5UiGnupL1zvBq"): EntryContractAxiom,
|
solana.MustPublicKeyFromBase58("F5tfvbLog9VdGUPqBDTT8rgXvTTcq7e5UiGnupL1zvBq"): EntryContractAxiom,
|
||||||
|
solana.MustPublicKeyFromBase58("Gz9VPiSLQYbvKyb3jZPjNfyA6n4T4qVFUuAukgL964nL"): EntryContractAxiom,
|
||||||
solana.MustPublicKeyFromBase58("B3jytJa6Tzpn4Ly7GNnDm3dMGqUin5aMRm5aPsJGU5G7"): EntryContractTradewiz,
|
solana.MustPublicKeyFromBase58("B3jytJa6Tzpn4Ly7GNnDm3dMGqUin5aMRm5aPsJGU5G7"): EntryContractTradewiz,
|
||||||
solana.MustPublicKeyFromBase58("DBotWvSso9oD1ZB3aHx2LiD2ZoFpF8PbKjaT4uHKLLVs"): EntryContractDbot,
|
solana.MustPublicKeyFromBase58("DBotWvSso9oD1ZB3aHx2LiD2ZoFpF8PbKjaT4uHKLLVs"): EntryContractDbot,
|
||||||
solana.MustPublicKeyFromBase58("term9YPb9mzAsABaqN71A4xdbxHmpBNZavpBiQKZzN3"): EntryContractPadre,
|
solana.MustPublicKeyFromBase58("term9YPb9mzAsABaqN71A4xdbxHmpBNZavpBiQKZzN3"): EntryContractPadre,
|
||||||
@@ -365,3 +376,5 @@ var entryContractAddresses = map[solana.PublicKey]string{
|
|||||||
|
|
||||||
var okxDexRoutersV2 = solana.MustPublicKeyFromBase58("proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u")
|
var okxDexRoutersV2 = solana.MustPublicKeyFromBase58("proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u")
|
||||||
var okxAggregatorV2 = solana.MustPublicKeyFromBase58("6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma")
|
var okxAggregatorV2 = solana.MustPublicKeyFromBase58("6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma")
|
||||||
|
|
||||||
|
var axiomOuterContract = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9")
|
||||||
|
|||||||
1
enum.go
1
enum.go
@@ -75,6 +75,7 @@ const (
|
|||||||
PlatformMaestro = "maestro"
|
PlatformMaestro = "maestro"
|
||||||
PlatformBonkBot = "bonkbot"
|
PlatformBonkBot = "bonkbot"
|
||||||
PlatformPadre = "padre"
|
PlatformPadre = "padre"
|
||||||
|
PlatformDexScreener = "dexscreener"
|
||||||
|
|
||||||
// used to flag transactions impersonating platform users
|
// used to flag transactions impersonating platform users
|
||||||
PlatformFake = "fake"
|
PlatformFake = "fake"
|
||||||
|
|||||||
154
error.go
154
error.go
@@ -79,54 +79,54 @@ const (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
GenericError InstructionErrorVariant = iota
|
GenericError InstructionErrorVariant = iota
|
||||||
/// The arguments provided to a program were invalid
|
// InvalidArgument / The arguments provided to a program were invalid
|
||||||
InvalidArgument
|
InvalidArgument
|
||||||
/// An instruction's data contents were invalid
|
// InvalidInstructionData / An instruction's data contents were invalid
|
||||||
InvalidInstructionData
|
InvalidInstructionData
|
||||||
/// An account's data contents was invalid
|
// InvalidAccountData / An account's data contents was invalid
|
||||||
InvalidAccountData
|
InvalidAccountData
|
||||||
/// An account's data was too small
|
// AccountDataTooSmall / An account's data was too small
|
||||||
AccountDataTooSmall
|
AccountDataTooSmall
|
||||||
/// An account's balance was too small to complete the instruction
|
// InsufficientFunds / An account's balance was too small to complete the instruction
|
||||||
InsufficientFunds
|
InsufficientFunds
|
||||||
/// The account did not have the expected program id
|
// IncorrectProgramId / The account did not have the expected program id
|
||||||
IncorrectProgramId
|
IncorrectProgramId
|
||||||
/// A signature was required but not found
|
// MissingRequiredSignature / A signature was required but not found
|
||||||
MissingRequiredSignature
|
MissingRequiredSignature
|
||||||
/// An initialize instruction was sent to an account that has already been initialized.
|
// AccountAlreadyInitialized / An initialize instruction was sent to an account that has already been initialized.
|
||||||
AccountAlreadyInitialized
|
AccountAlreadyInitialized
|
||||||
/// An attempt to operate on an account that hasn't been initialized.
|
// UninitializedAccount / An attempt to operate on an account that hasn't been initialized.
|
||||||
UninitializedAccount
|
UninitializedAccount
|
||||||
/// Program's instruction lamport balance does not equal the balance after the instruction
|
// UnbalancedInstruction / Program's instruction lamport balance does not equal the balance after the instruction
|
||||||
UnbalancedInstruction
|
UnbalancedInstruction
|
||||||
/// Program illegally modified an account's program id
|
// ModifiedProgramId / Program illegally modified an account's program id
|
||||||
ModifiedProgramId
|
ModifiedProgramId
|
||||||
/// Program spent the lamports of an account that doesn't belong to it
|
// ExternalAccountLamportSpend / Program spent the lamports of an account that doesn't belong to it
|
||||||
ExternalAccountLamportSpend
|
ExternalAccountLamportSpend
|
||||||
/// Program modified the data of an account that doesn't belong to it
|
// ExternalAccountDataModified / Program modified the data of an account that doesn't belong to it
|
||||||
ExternalAccountDataModified
|
ExternalAccountDataModified
|
||||||
/// Read-only account's lamports modified
|
// ReadonlyLamportChange / Read-only account's lamports modified
|
||||||
ReadonlyLamportChange
|
ReadonlyLamportChange
|
||||||
/// Read-only account's data was modified
|
// ReadonlyDataModified / Read-only account's data was modified
|
||||||
ReadonlyDataModified
|
ReadonlyDataModified
|
||||||
/// An account was referenced more than once in a single instruction
|
// DuplicateAccountIndex / An account was referenced more than once in a single instruction
|
||||||
// Deprecated, instructions can now contain duplicate accounts
|
// Deprecated, instructions can now contain duplicate accounts
|
||||||
DuplicateAccountIndex
|
DuplicateAccountIndex
|
||||||
/// Executable bit on account changed, but shouldn't have
|
// ExecutableModified / Executable bit on account changed, but shouldn't have
|
||||||
ExecutableModified
|
ExecutableModified
|
||||||
/// Rent_epoch account changed, but shouldn't have
|
// RentEpochModified / Rent_epoch account changed, but shouldn't have
|
||||||
RentEpochModified
|
RentEpochModified
|
||||||
/// The instruction expected additional account keys
|
// NotEnoughAccountKeys / The instruction expected additional account keys
|
||||||
NotEnoughAccountKeys
|
NotEnoughAccountKeys
|
||||||
/// Program other than the account's owner changed the size of the account data
|
// AccountDataSizeChanged / Program other than the account's owner changed the size of the account data
|
||||||
AccountDataSizeChanged
|
AccountDataSizeChanged
|
||||||
/// The instruction expected an executable account
|
// AccountNotExecutable / The instruction expected an executable account
|
||||||
AccountNotExecutable
|
AccountNotExecutable
|
||||||
/// Failed to borrow a reference to account data, already borrowed
|
// AccountBorrowFailed / Failed to borrow a reference to account data, already borrowed
|
||||||
AccountBorrowFailed
|
AccountBorrowFailed
|
||||||
/// Account data has an outstanding reference after a program's execution
|
// InstructionAccountBorrowOutstanding / Account data has an outstanding reference after a program's execution
|
||||||
InstructionAccountBorrowOutstanding
|
InstructionAccountBorrowOutstanding
|
||||||
/// The same account was multiply passed to an on-chain program's entrypoint, but the program
|
// DuplicateAccountOutOfSync / The same account was multiply passed to an on-chain program's entrypoint, but the program
|
||||||
/// modified them differently. A program can only modify one instance of the account because
|
/// modified them differently. A program can only modify one instance of the account because
|
||||||
/// the runtime cannot determine which changes to pick or how to merge them if both are modified
|
/// the runtime cannot determine which changes to pick or how to merge them if both are modified
|
||||||
DuplicateAccountOutOfSync
|
DuplicateAccountOutOfSync
|
||||||
@@ -136,42 +136,42 @@ const (
|
|||||||
|
|
||||||
Custom // Custom(u32),
|
Custom // Custom(u32),
|
||||||
|
|
||||||
/// The return value from the program was invalid. Valid errors are either a defined builtin
|
// InvalidError / The return value from the program was invalid. Valid errors are either a defined builtin
|
||||||
/// error value or a user-defined error in the lower 32 bits.
|
/// error value or a user-defined error in the lower 32 bits.
|
||||||
InvalidError
|
InvalidError
|
||||||
/// Executable account's data was modified
|
// ExecutableDataModified / Executable account's data was modified
|
||||||
ExecutableDataModified
|
ExecutableDataModified
|
||||||
/// Executable account's lamports modified
|
// ExecutableLamportChange / Executable account's lamports modified
|
||||||
ExecutableLamportChange
|
ExecutableLamportChange
|
||||||
/// Executable accounts must be rent exempt
|
// ExecutableAccountNotRentExempt / Executable accounts must be rent exempt
|
||||||
ExecutableAccountNotRentExempt
|
ExecutableAccountNotRentExempt
|
||||||
/// Unsupported program id
|
// UnsupportedProgramId / Unsupported program id
|
||||||
UnsupportedProgramId
|
UnsupportedProgramId
|
||||||
/// Cross-program invocation call depth too deep
|
// CallDepth / Cross-program invocation call depth too deep
|
||||||
CallDepth
|
CallDepth
|
||||||
/// An account required by the instruction is missing
|
// MissingAccount / An account required by the instruction is missing
|
||||||
MissingAccount
|
MissingAccount
|
||||||
/// Cross-program invocation reentrancy not allowed for this instruction
|
// ReentrancyNotAllowed / Cross-program invocation reentrancy not allowed for this instruction
|
||||||
ReentrancyNotAllowed
|
ReentrancyNotAllowed
|
||||||
/// Length of the seed is too long for address generation
|
// MaxSeedLengthExceeded / Length of the seed is too long for address generation
|
||||||
MaxSeedLengthExceeded
|
MaxSeedLengthExceeded
|
||||||
/// Provided seeds do not result in a valid address
|
// InvalidSeeds / Provided seeds do not result in a valid address
|
||||||
InvalidSeeds
|
InvalidSeeds
|
||||||
/// Failed to reallocate account data of this length
|
// InvalidRealloc / Failed to reallocate account data of this length
|
||||||
InvalidRealloc
|
InvalidRealloc
|
||||||
/// Computational budget exceeded
|
// ComputationalBudgetExceeded / Computational budget exceeded
|
||||||
ComputationalBudgetExceeded
|
ComputationalBudgetExceeded
|
||||||
/// Cross-program invocation with unauthorized signer or writable account
|
// PrivilegeEscalation / Cross-program invocation with unauthorized signer or writable account
|
||||||
PrivilegeEscalation
|
PrivilegeEscalation
|
||||||
/// Failed to create program execution environment
|
// ProgramEnvironmentSetupFailure / Failed to create program execution environment
|
||||||
ProgramEnvironmentSetupFailure
|
ProgramEnvironmentSetupFailure
|
||||||
/// Program failed to complete
|
// ProgramFailedToComplete / Program failed to complete
|
||||||
ProgramFailedToComplete
|
ProgramFailedToComplete
|
||||||
/// Program failed to compile
|
// ProgramFailedToCompile / Program failed to compile
|
||||||
ProgramFailedToCompile
|
ProgramFailedToCompile
|
||||||
/// Account is immutable
|
// Immutable / Account is immutable
|
||||||
Immutable
|
Immutable
|
||||||
/// Incorrect authority provided
|
// IncorrectAuthority / Incorrect authority provided
|
||||||
IncorrectAuthority
|
IncorrectAuthority
|
||||||
/// Failed to serialize or deserialize account data
|
/// Failed to serialize or deserialize account data
|
||||||
///
|
///
|
||||||
@@ -185,23 +185,23 @@ const (
|
|||||||
|
|
||||||
BorshIoError // BorshIoError(String)
|
BorshIoError // BorshIoError(String)
|
||||||
|
|
||||||
// An account does not have enough lamports to be rent-exempt
|
// AccountNotRentExempt An account does not have enough lamports to be rent-exempt
|
||||||
AccountNotRentExempt
|
AccountNotRentExempt
|
||||||
/// Invalid account owner
|
// InvalidAccountOwner Invalid account owner
|
||||||
InvalidAccountOwner
|
InvalidAccountOwner
|
||||||
/// Program arithmetic overflowed
|
// ArithmeticOverflow Program arithmetic overflowed
|
||||||
ArithmeticOverflow
|
ArithmeticOverflow
|
||||||
/// Unsupported sysvar
|
// UnsupportedSysvar Unsupported sysvar
|
||||||
UnsupportedSysvar
|
UnsupportedSysvar
|
||||||
/// Illegal account owner
|
// IllegalOwner Illegal account owner
|
||||||
IllegalOwner
|
IllegalOwner
|
||||||
/// Accounts data allocations exceeded the maximum allowed per transaction
|
// MaxAccountsDataAllocationsExceeded / Accounts data allocations exceeded the maximum allowed per transaction
|
||||||
MaxAccountsDataAllocationsExceeded
|
MaxAccountsDataAllocationsExceeded
|
||||||
/// Max accounts exceeded
|
// MaxAccountsExceeded Max accounts exceeded
|
||||||
MaxAccountsExceeded
|
MaxAccountsExceeded
|
||||||
/// Max instruction trace length exceeded
|
// MaxInstructionTraceLengthExceeded Max instruction trace length exceeded
|
||||||
MaxInstructionTraceLengthExceeded
|
MaxInstructionTraceLengthExceeded
|
||||||
/// Builtin programs must consume compute units
|
// BuiltinProgramsMustConsumeComputeUnits Builtin programs must consume compute units
|
||||||
BuiltinProgramsMustConsumeComputeUnits
|
BuiltinProgramsMustConsumeComputeUnits
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -210,6 +210,15 @@ type TransactionError struct {
|
|||||||
rest []byte
|
rest []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TransactionParsedError struct {
|
||||||
|
Index uint8
|
||||||
|
Variant TransactionErrorVariant
|
||||||
|
Enum InstructionErrorVariant
|
||||||
|
CustomCode uint32
|
||||||
|
|
||||||
|
UnKnown string
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrInvalidTransactionError = errors.New("invalid transaction error")
|
ErrInvalidTransactionError = errors.New("invalid transaction error")
|
||||||
NotAnInstructionError = errors.New("not an instruction error")
|
NotAnInstructionError = errors.New("not an instruction error")
|
||||||
@@ -233,6 +242,49 @@ func DecodeTransactionError(data []byte) (*TransactionError, error) {
|
|||||||
return &err, nil
|
return &err, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParseTransactionErrorFromGeyser(data []byte) *TransactionParsedError {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
transactionError, err := DecodeTransactionError(data)
|
||||||
|
if err != nil {
|
||||||
|
return &TransactionParsedError{
|
||||||
|
UnKnown: string(data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enumErr, err := transactionError.GetInstructionError()
|
||||||
|
if err != nil {
|
||||||
|
return &TransactionParsedError{
|
||||||
|
Variant: transactionError.Variant,
|
||||||
|
UnKnown: string(data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if enumErr.Variant != Custom {
|
||||||
|
return &TransactionParsedError{
|
||||||
|
Index: enumErr.Index,
|
||||||
|
Variant: transactionError.Variant,
|
||||||
|
Enum: enumErr.Variant,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customCode, err := enumErr.Custom()
|
||||||
|
if err != nil {
|
||||||
|
return &TransactionParsedError{
|
||||||
|
Index: enumErr.Index,
|
||||||
|
Variant: transactionError.Variant,
|
||||||
|
Enum: enumErr.Variant,
|
||||||
|
UnKnown: string(data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TransactionParsedError{
|
||||||
|
Index: enumErr.Index,
|
||||||
|
Variant: transactionError.Variant,
|
||||||
|
Enum: enumErr.Variant,
|
||||||
|
CustomCode: customCode.Code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (e *TransactionError) GetCustomErrorCode() (uint8, uint32, error) {
|
func (e *TransactionError) GetCustomErrorCode() (uint8, uint32, error) {
|
||||||
instr, err := e.GetInstructionError()
|
instr, err := e.GetInstructionError()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
47
error_test.go
Normal file
47
error_test.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package pump_parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDecodeTransactionError(t *testing.T) {
|
||||||
|
var testCases = []struct {
|
||||||
|
name string
|
||||||
|
data string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ComputationalBudgetExceeded",
|
||||||
|
data: "CAAAAAMlAAAA",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
bytesData, err := base64.StdEncoding.DecodeString(tc.data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to decode base64 data for %s: %v", tc.name, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
te, err := DecodeTransactionError(bytesData)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Decoded error for %s: %v", tc.name, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if te.Variant != InstructionError {
|
||||||
|
t.Errorf("Expected Variant to be InstructionError for %s, got %d", tc.name, te.Variant)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
errName, err := te.GetInstructionError()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to get instruction error for %s: %v", tc.name, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errName.Variant != ComputationalBudgetExceeded {
|
||||||
|
t.Errorf("Expected instruction error variant to be Custom for %s, got %d", tc.name, errName.Variant)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,10 +5,17 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gagliardetto/solana-go"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
parser "github.com/thloyi/pump-parser"
|
parser "github.com/thloyi/pump-parser"
|
||||||
example "github.com/thloyi/pump-parser/internal/example"
|
example "github.com/thloyi/pump-parser/internal/example"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
pumpProgram = solana.MustPublicKeyFromBase58("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
|
||||||
|
pumpAmmProgram = solana.MustPublicKeyFromBase58("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
//pool, err := ants.NewPool(100, ants.WithPreAlloc(true), ants.WithNonblocking(true))
|
//pool, err := ants.NewPool(100, ants.WithPreAlloc(true), ants.WithNonblocking(true))
|
||||||
//if err != nil {
|
//if err != nil {
|
||||||
@@ -31,6 +38,7 @@ func main() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ptx := msg.Tx
|
ptx := msg.Tx
|
||||||
|
// fmt.Println("consume", ptx.ComputeUnitsConsumed, "limit", ptx.CuLimit, "hash", ptx.GetTxHash())
|
||||||
//data, _ := json.Marshal(tx)
|
//data, _ := json.Marshal(tx)
|
||||||
//fmt.Println(string(data))
|
//fmt.Println(string(data))
|
||||||
//continue
|
//continue
|
||||||
@@ -43,43 +51,9 @@ func main() {
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
// 处理交易
|
// 处理交易
|
||||||
txErr, ok := ptx.Err.(*parser.TransactionError)
|
if len(ptx.Swaps) > 0 && (ptx.Swaps[0].Program == parser.SolProgramPump || ptx.Swaps[0].Program == parser.SolProgramPumpAMM) {
|
||||||
var customerErrCode uint32
|
fmt.Printf("success tx : %s, program: %s, event: %s, block: %d, tx: %s, base: %s, quote: %s \n", time.Now().Format("2006-01-02 15:04:05"), ptx.Swaps[0].Program, ptx.Swaps[0].Event, ptx.Block, ptx.GetTxHash(),
|
||||||
var instructorErrIndex uint8
|
ptx.Swaps[0].BaseAmount.Div(decimal.NewFromInt(1e6)), ptx.Swaps[0].QuoteAmount.Div(decimal.NewFromInt(1e9)))
|
||||||
if ok {
|
|
||||||
instructorErrIndex, customerErrCode, _ = txErr.GetCustomErrorCode()
|
|
||||||
fmt.Printf("now: %s, block: %d, tx: %s, errInstr Code: %d, errInstrIndex: %d, err: %v\n", time.Now().Format("2006-01-02 15:04:05"), ptx.Block, ptx.GetTxHash(), customerErrCode, instructorErrIndex, ptx.Err)
|
|
||||||
} else {
|
|
||||||
txs := example.FromTx(ptx)
|
|
||||||
if len(txs) == 0 {
|
|
||||||
fmt.Printf("tx is empty, block: %d, tx %s \n", ptx.Block, ptx.GetTxHash())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// printed := false
|
|
||||||
for _, tx := range txs {
|
|
||||||
if tx.Program != parser.SolProgramPumpAMM {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if tx.EntryContract == "" || tx.EntryContract == parser.SolProgramPumpAMM || tx.EntryContract == parser.EntryContractOKXDexRouterV2 || tx.EntryContract == "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
//if tx.Token1Amount.GreaterThanOrEqual(decimal.NewFromFloat(0.1)) || tx.Event != "buy" {
|
|
||||||
// continue
|
|
||||||
//}
|
|
||||||
// printed = true
|
|
||||||
fmt.Printf("t: %s, block: %d, hash: %s, maker: %s, program: %s, event: %s, token0: %s, entryContract: %s, token balance: %s, EntryContract: %s\n",
|
|
||||||
time.Now().Format(time.RFC3339Nano),
|
|
||||||
tx.Block, tx.GetTxHash(), tx.Maker, tx.Program, tx.Event, tx.Token0Amount, tx.EntryContract, tx.AfterSignerToken0Balance, tx.EntryContract)
|
|
||||||
//break
|
|
||||||
}
|
|
||||||
//if !printed {
|
|
||||||
// continue
|
|
||||||
//}
|
|
||||||
//fmt.Printf("t: %s, block: %d, hash: %s, signer: %s, program: %s, event: %s, token0: %s, token1: %s, signer before sol :%s, after sol: %s, after token: %s, tokencreator: %s, tokenprogram: %s, mayhem: %t\n",
|
|
||||||
// time.Now().Format(time.RFC3339Nano),
|
|
||||||
// tx.Block, tx.GetTxHash(), tx.Maker, tx.Program, tx.Event, tx.Token0Amount.String(), tx.Token1Amount.String(),
|
|
||||||
// tx.BeforeSolBalance, tx.AfterSOLBalance, tx.AfterSignerToken0Balance, tx.TokenCreator, tx.Token0Program, tx.Mayhem)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
// currentBlock = ptx.Block
|
// currentBlock = ptx.Block
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package parser
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/shopspring/decimal"
|
|
||||||
types "github.com/thloyi/pump-parser"
|
types "github.com/thloyi/pump-parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,24 +22,14 @@ func NewPumpHandler(cb func(*types.Tx)) *PumpHandler {
|
|||||||
func (h *PumpHandler) HandleMessage(rawTx *types.RawTx) {
|
func (h *PumpHandler) HandleMessage(rawTx *types.RawTx) {
|
||||||
if rawTx.Meta.Err != nil {
|
if rawTx.Meta.Err != nil {
|
||||||
// Notify the channel about the failed transaction
|
// Notify the channel about the failed transaction
|
||||||
beforeSolBalance := decimal.Zero
|
var parsedTx = &types.Tx{}
|
||||||
afterSolBalance := decimal.Zero
|
parsedTx.SetRawTx(rawTx)
|
||||||
if rawTx.Meta.PreBalances != nil && len(rawTx.Meta.PreBalances) > 0 {
|
err := parsedTx.Parser()
|
||||||
beforeSolBalance = decimal.NewFromUint64(rawTx.Meta.PreBalances[0]).Div(decimal.NewFromInt(1e9))
|
if err != nil {
|
||||||
|
fmt.Printf("parser failed tx error: %s, block: %d tx: %s\n", err, rawTx.Slot, rawTx.TxHash())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if rawTx.Meta.PostBalances != nil && len(rawTx.Meta.PostBalances) > 0 {
|
h.callback(parsedTx)
|
||||||
afterSolBalance = decimal.NewFromUint64(rawTx.Meta.PostBalances[0]).Div(decimal.NewFromInt(1e9))
|
|
||||||
}
|
|
||||||
h.callback(&types.Tx{
|
|
||||||
TxHash: (*[64]byte)((rawTx.Transaction.Signatures[0][:])),
|
|
||||||
Err: rawTx.Meta.Err,
|
|
||||||
Signer: rawTx.GetSigner(),
|
|
||||||
Block: rawTx.Slot,
|
|
||||||
BlockIndex: uint64(rawTx.IndexWithinBlock),
|
|
||||||
|
|
||||||
BeforeSolBalance: beforeSolBalance,
|
|
||||||
AfterSOLBalance: afterSolBalance,
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ type Tx struct {
|
|||||||
AfterSOLBalance decimal.Decimal `gorm:"column:after_sol_balance;type:numeric" json:"after_sol_balance"`
|
AfterSOLBalance decimal.Decimal `gorm:"column:after_sol_balance;type:numeric" json:"after_sol_balance"`
|
||||||
EntryContract string `gorm:"column:tx_entry_contract;type:entry_contract;default:'none'" json:"tx_entry_contract"`
|
EntryContract string `gorm:"column:tx_entry_contract;type:entry_contract;default:'none'" json:"tx_entry_contract"`
|
||||||
|
|
||||||
Mayhem bool
|
Mayhem bool
|
||||||
|
Cashback bool `json:"is_cashback_coin"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *Tx) GetTxHash() string {
|
func (tx *Tx) GetTxHash() string {
|
||||||
@@ -121,6 +122,7 @@ func FromTx(tx *parser.Tx) []*Tx {
|
|||||||
|
|
||||||
EntryContract: s.CheckEntryContract(),
|
EntryContract: s.CheckEntryContract(),
|
||||||
Mayhem: s.Mayhem,
|
Mayhem: s.Mayhem,
|
||||||
|
Cashback: s.Cashback,
|
||||||
}
|
}
|
||||||
} else if s.Program == "PumpAMM" {
|
} else if s.Program == "PumpAMM" {
|
||||||
if s.BaseMint.Equals(solana.WrappedSol) {
|
if s.BaseMint.Equals(solana.WrappedSol) {
|
||||||
@@ -175,6 +177,7 @@ func FromTx(tx *parser.Tx) []*Tx {
|
|||||||
|
|
||||||
EntryContract: s.CheckEntryContract(),
|
EntryContract: s.CheckEntryContract(),
|
||||||
Mayhem: s.Mayhem,
|
Mayhem: s.Mayhem,
|
||||||
|
Cashback: s.Cashback,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newTx = &Tx{
|
newTx = &Tx{
|
||||||
@@ -219,6 +222,7 @@ func FromTx(tx *parser.Tx) []*Tx {
|
|||||||
|
|
||||||
EntryContract: s.CheckEntryContract(),
|
EntryContract: s.CheckEntryContract(),
|
||||||
Mayhem: s.Mayhem,
|
Mayhem: s.Mayhem,
|
||||||
|
Cashback: s.Cashback,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,21 +50,18 @@ type Client struct {
|
|||||||
func NewClientWithPumpSwap(endpoint string, ch chan SubscriptionMessage) *Client {
|
func NewClientWithPumpSwap(endpoint string, ch chan SubscriptionMessage) *Client {
|
||||||
var subscription pb.SubscribeRequest
|
var subscription pb.SubscribeRequest
|
||||||
|
|
||||||
var failed = false
|
//var failed = true
|
||||||
var vote = false
|
var vote = false
|
||||||
subscription.Transactions = make(map[string]*pb.SubscribeRequestFilterTransactions)
|
subscription.Transactions = make(map[string]*pb.SubscribeRequestFilterTransactions)
|
||||||
subscription.Transactions["transactions_sub"] = &pb.SubscribeRequestFilterTransactions{
|
subscription.Transactions["transactions_sub"] = &pb.SubscribeRequestFilterTransactions{
|
||||||
Failed: &failed,
|
//Failed: &failed,
|
||||||
Vote: &vote,
|
Vote: &vote,
|
||||||
}
|
}
|
||||||
|
|
||||||
subscription.Transactions["transactions_sub"].AccountInclude = []string{
|
subscription.Transactions["transactions_sub"].AccountInclude = []string{
|
||||||
"pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", //Pump AMM
|
"pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", //Pump AMM
|
||||||
"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", //Pump
|
"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", //Pump
|
||||||
}
|
}
|
||||||
subscription.Transactions["transactions_sub"].AccountRequired = []string{
|
|
||||||
"ARu4n5mFdZogZAravu7CcizaojWnS6oqka37gdLT5SZn",
|
|
||||||
}
|
|
||||||
subscription.BlocksMeta = make(map[string]*pb.SubscribeRequestFilterBlocksMeta)
|
subscription.BlocksMeta = make(map[string]*pb.SubscribeRequestFilterBlocksMeta)
|
||||||
subscription.BlocksMeta["block_meta"] = &pb.SubscribeRequestFilterBlocksMeta{}
|
subscription.BlocksMeta["block_meta"] = &pb.SubscribeRequestFilterBlocksMeta{}
|
||||||
|
|
||||||
@@ -85,12 +82,12 @@ func NewClientWithPumpSwap(endpoint string, ch chan SubscriptionMessage) *Client
|
|||||||
func NewClientWithLaunchLab(endpoint string, ch chan SubscriptionMessage) *Client {
|
func NewClientWithLaunchLab(endpoint string, ch chan SubscriptionMessage) *Client {
|
||||||
var subscription pb.SubscribeRequest
|
var subscription pb.SubscribeRequest
|
||||||
|
|
||||||
var failed = false
|
//var failed = false
|
||||||
var vote = false
|
var vote = false
|
||||||
subscription.Transactions = make(map[string]*pb.SubscribeRequestFilterTransactions)
|
subscription.Transactions = make(map[string]*pb.SubscribeRequestFilterTransactions)
|
||||||
subscription.Transactions["transactions_sub"] = &pb.SubscribeRequestFilterTransactions{
|
subscription.Transactions["transactions_sub"] = &pb.SubscribeRequestFilterTransactions{
|
||||||
Failed: &failed,
|
//Failed: &failed,
|
||||||
Vote: &vote,
|
Vote: &vote,
|
||||||
}
|
}
|
||||||
|
|
||||||
subscription.Transactions["transactions_sub"].AccountInclude = []string{
|
subscription.Transactions["transactions_sub"].AccountInclude = []string{
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ var ()
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
var slot uint64 = 399477968
|
var slot uint64 = 403021435
|
||||||
var data = NewBlockData(decimal.NewFromFloat(100.0))
|
var data = NewBlockData(decimal.NewFromFloat(100.0))
|
||||||
client := rpc.New("https://staked.helius-rpc.com?api-key=5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d")
|
client := rpc.New("https://staked.helius-rpc.com?api-key=5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d")
|
||||||
var rewards = false
|
var rewards = false
|
||||||
@@ -53,9 +53,9 @@ func main() {
|
|||||||
fmt.Println("from rpc tx error:", i, err)
|
fmt.Println("from rpc tx error:", i, err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if rawTx.Meta.Err != nil {
|
//if rawTx.Meta.Err != nil {
|
||||||
continue
|
// continue
|
||||||
}
|
//}
|
||||||
parsedTx, err := solana_parser.ParseRawTx(rawTx)
|
parsedTx, err := solana_parser.ParseRawTx(rawTx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("parse tx error:", i, rawTx.TxHash(), err)
|
fmt.Println("parse tx error:", i, rawTx.TxHash(), err)
|
||||||
@@ -91,9 +91,7 @@ func main() {
|
|||||||
fmt.Println("parse action error:", "tx", result.GetTxHash(), "i", i, "err", err)
|
fmt.Println("parse action error:", "tx", result.GetTxHash(), "i", i, "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if result.GetTxHash() == "3vMp5Lqm7PxS9MXfXgmptKA9xLkyfKfJpuFs2mUfhRuqTWjGj7DcvSb65NsHQH5RF9JXVLbxrpUHV4LXrgmjXmft" {
|
|
||||||
fmt.Println("xxx")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fmt.Println("slot", slot, "tx count: ", len(data.Txs))
|
fmt.Println("slot", slot, "tx count: ", len(data.Txs))
|
||||||
|
|
||||||
|
|||||||
817
internal/test3/test.go
Normal file
817
internal/test3/test.go
Normal file
@@ -0,0 +1,817 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"log/slog"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gagliardetto/solana-go"
|
||||||
|
"github.com/gagliardetto/solana-go/rpc"
|
||||||
|
"github.com/jackc/pgtype"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
solana_parser "github.com/thloyi/pump-parser"
|
||||||
|
"gorm.io/driver/postgres"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ()
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
var data = NewBlockData(decimal.NewFromFloat(100.0))
|
||||||
|
client := rpc.New("https://staked.helius-rpc.com?api-key=5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d")
|
||||||
|
var version uint64 = 0
|
||||||
|
txSig, _ := solana.SignatureFromBase58("2LCw5yZy6sGTWKpJNxpFxR11M66cXPsrGmJXnQmWW9QVv6SDWRmu1aevc6yE9NeUz78mFb4T8TEx9w5781NHnz2T")
|
||||||
|
tx, err := client.GetTransaction(context.Background(), txSig, &rpc.GetTransactionOpts{
|
||||||
|
Commitment: rpc.CommitmentFinalized,
|
||||||
|
Encoding: solana.EncodingBase64,
|
||||||
|
MaxSupportedTransactionVersion: &version,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("get block error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
solana_parser.EnableAllParsers()
|
||||||
|
|
||||||
|
var blockTime uint64
|
||||||
|
|
||||||
|
rawTx, err := solana_parser.FromRpcTransactionWithMeta(rpc.TransactionWithMeta{
|
||||||
|
Slot: 0,
|
||||||
|
BlockTime: nil,
|
||||||
|
Transaction: rpc.DataBytesOrJSONFromBytes(tx.Transaction.GetBinary()),
|
||||||
|
Meta: tx.Meta,
|
||||||
|
Version: tx.Version,
|
||||||
|
}, &blockTime, 0, int64(0))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("from rpc tx error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result, err := solana_parser.ParseRawTx(rawTx)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("parse tx error:", rawTx.TxHash(), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
swapsLen := len(result.Swaps)
|
||||||
|
for i := 0; i < swapsLen; i++ {
|
||||||
|
action := result.Swaps[i]
|
||||||
|
var actions []solana_parser.Swap = make([]solana_parser.Swap, 0, 2)
|
||||||
|
actions = append(actions, action)
|
||||||
|
if i+1 < swapsLen {
|
||||||
|
nextAction := result.Swaps[i+1]
|
||||||
|
if action.Event == "buy" && nextAction.Event == "complete" &&
|
||||||
|
action.Program == solana_parser.SolProgramPump &&
|
||||||
|
nextAction.Program == solana_parser.SolProgramPump &&
|
||||||
|
action.BaseMint == nextAction.BaseMint {
|
||||||
|
actions = append(actions, nextAction)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if action.Event == "migrate" && nextAction.Event == "create" &&
|
||||||
|
action.Program == solana_parser.SolProgramPump &&
|
||||||
|
nextAction.Program == solana_parser.SolProgramPumpAMM &&
|
||||||
|
action.BaseMint == nextAction.BaseMint {
|
||||||
|
actions = append(actions, nextAction)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = HandleAction(context.Background(), result, actions, data); err != nil {
|
||||||
|
//h.logger.Errorf("handle action error: %s - %v", result.RawTx.Transaction.Signatures[0].String(), err)
|
||||||
|
fmt.Println("parse action error:", "tx", result.GetTxHash(), "i", i, "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("tx count: ", len(data.Txs))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
meteoraDammV2Program = solana.MustPublicKeyFromBase58("cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG")
|
||||||
|
raydiumCPmmProgramID = solana.MustPublicKeyFromBase58("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C")
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleAction(ctx context.Context, tx *solana_parser.Tx, swaps []solana_parser.Swap, data *BlockData) error {
|
||||||
|
swapLen := len(swaps)
|
||||||
|
if len(swaps) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if swaps[0].BaseMint != solana_parser.WSOL && swaps[0].QuoteMint != solana_parser.WSOL && !swaps[0].QuoteMint.IsZero() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(swaps) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
event := swaps[0].Event
|
||||||
|
swap := swaps[0]
|
||||||
|
action := SwapGetter{swap}
|
||||||
|
switch event {
|
||||||
|
case "buy", "sell":
|
||||||
|
|
||||||
|
data.AppendTx(action.GetTx(tx, uint64(swap.TxIndex), data.Price))
|
||||||
|
if swap.Program == solana_parser.SolProgramPump {
|
||||||
|
if swapLen == 2 && swaps[1].Event == "complete" {
|
||||||
|
t := pgtype.Timestamptz{}
|
||||||
|
t.Set(time.Unix(tx.BlockAt, 0))
|
||||||
|
data.AppendAction(Action{
|
||||||
|
Maker: swaps[1].User.String(),
|
||||||
|
Token: swaps[1].BaseMint.String(),
|
||||||
|
Pair: swaps[1].Pool.String(),
|
||||||
|
Action: "pump-migrate",
|
||||||
|
Block: tx.Block,
|
||||||
|
BlockAt: t,
|
||||||
|
TxHash: tx.GetTxHash(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data.SetPair(action, tx.Block, "")
|
||||||
|
|
||||||
|
case "create":
|
||||||
|
pair, err := action.GetPair(tx.Block, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data.AppendTx(action.GetTx(tx, uint64(swap.TxIndex), data.Price))
|
||||||
|
data.Pairs[pair.Address] = *pair
|
||||||
|
case "add_liquidity", "remove_liquidity", "deposit", "withdraw", "add", "remove":
|
||||||
|
liquidityTx, err := action.GetLiquidityTx(tx, uint64(swap.TxIndex))
|
||||||
|
if liquidityTx == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data.AppendTx(*liquidityTx)
|
||||||
|
return data.SetPair(action, tx.Block, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
if event != "migrate" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if swap.Program == solana_parser.SolProgramPump {
|
||||||
|
t := pgtype.Timestamptz{}
|
||||||
|
t.Set(time.Unix(tx.BlockAt, 0))
|
||||||
|
if swapLen == 2 && swaps[1].Event == "create" && swaps[1].Program == solana_parser.SolProgramPumpAMM && swaps[1].BaseMint == swap.BaseMint {
|
||||||
|
tokenMint := swap.BaseMint.String()
|
||||||
|
data.AppendAction(Action{
|
||||||
|
Maker: swap.User.String(),
|
||||||
|
Token: tokenMint,
|
||||||
|
Pair: swaps[1].Pool.String(),
|
||||||
|
Action: "on-pumpswap",
|
||||||
|
Block: tx.Block,
|
||||||
|
BlockAt: t,
|
||||||
|
TxHash: tx.GetTxHash(),
|
||||||
|
})
|
||||||
|
data.NewRaydium = append(data.NewRaydium, tokenMint)
|
||||||
|
}
|
||||||
|
} else if swap.Program == solana_parser.SolProgramRaydiumLaunchLab || swap.Program == solana_parser.SolProgramRaydiumLaunchLabBonk {
|
||||||
|
t := pgtype.Timestamptz{}
|
||||||
|
t.Set(time.Unix(tx.BlockAt, 0))
|
||||||
|
var actionType string
|
||||||
|
if action.MigrateTopProgram == raydiumCPmmProgramID {
|
||||||
|
actionType = "on-raydium-cpmm"
|
||||||
|
} else {
|
||||||
|
actionType = "on-raydium-amm"
|
||||||
|
}
|
||||||
|
data.AppendAction(Action{
|
||||||
|
Maker: action.User.String(),
|
||||||
|
Token: action.BaseMint.String(),
|
||||||
|
Pair: action.MigrateToPool.String(),
|
||||||
|
Action: actionType,
|
||||||
|
Block: tx.Block,
|
||||||
|
BlockAt: t,
|
||||||
|
TxHash: tx.GetTxHash(),
|
||||||
|
})
|
||||||
|
} else if swap.Program == solana_parser.SolProgramMeteoraBondingCurve {
|
||||||
|
t := pgtype.Timestamptz{}
|
||||||
|
t.Set(time.Unix(tx.BlockAt, 0))
|
||||||
|
var actionType string
|
||||||
|
if swap.MigrateTopProgram == meteoraDammV2Program {
|
||||||
|
actionType = "on-meteora-amm-v2"
|
||||||
|
} else {
|
||||||
|
actionType = "on-meteora-amm-v1"
|
||||||
|
}
|
||||||
|
data.AppendAction(Action{
|
||||||
|
Maker: action.User.String(),
|
||||||
|
Token: action.BaseMint.String(),
|
||||||
|
Pair: action.MigrateToPool.String(),
|
||||||
|
Action: actionType,
|
||||||
|
Block: uint64(tx.Block),
|
||||||
|
BlockAt: t,
|
||||||
|
TxHash: tx.GetTxHash(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Pair struct {
|
||||||
|
Id string `gorm:"column:id;primaryKey;default:uuid_generate_v4()"`
|
||||||
|
Address string
|
||||||
|
Name string
|
||||||
|
Token0 string
|
||||||
|
Token1 string
|
||||||
|
LpToken string
|
||||||
|
ChainId int64
|
||||||
|
Reserve0 decimal.Decimal
|
||||||
|
Reserve1 decimal.Decimal
|
||||||
|
Block uint64
|
||||||
|
BlockAt *pgtype.Timestamptz `gorm:"column:block_at;default:NULL" json:"block_at,omitempty"`
|
||||||
|
CreatedAt *pgtype.Timestamptz `gorm:"autoCreateTime" json:"created_at,omitempty"`
|
||||||
|
SortId uint64
|
||||||
|
Program string
|
||||||
|
|
||||||
|
IsCreate bool `gorm:"-"`
|
||||||
|
//TokenObj *Token `gorm:"-" json:"token_obj,omitempty"`
|
||||||
|
UpdateSlot uint64 `gorm:"-"`
|
||||||
|
InDB bool `gorm:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Tx struct {
|
||||||
|
Id pgtype.UUID `gorm:"column:id;primaryKey;default:uuid_generate_v4()" json:"-"`
|
||||||
|
PairAddress string `json:"pair_address"`
|
||||||
|
Maker string `json:"maker"`
|
||||||
|
Token0Address string `json:"token0_address"`
|
||||||
|
Token1Address string `json:"token1_address"`
|
||||||
|
Token0Amount decimal.Decimal `json:"token0Amount" gorm:"column:token0_amount;type:numeric"`
|
||||||
|
Token1Amount decimal.Decimal `json:"token1Amount" gorm:"column:token1_amount;type:numeric"`
|
||||||
|
PriceUsd decimal.Decimal `json:"price_usd" gorm:"column:price_usd;type:numeric"`
|
||||||
|
AmountUsd decimal.Decimal `json:"amount_usd" gorm:"column:amount_usd;type:numeric"`
|
||||||
|
Block uint64 `json:"block"`
|
||||||
|
BlockIndex uint64 `json:"index"`
|
||||||
|
Event string `json:"event"`
|
||||||
|
TxHash string `json:"tx_hash"`
|
||||||
|
TxIndex uint64 `json:"topic_index"`
|
||||||
|
Program string `json:"program"`
|
||||||
|
BlockAt pgtype.Timestamptz `gorm:"column:block_at;default:NULL" json:"block_at"`
|
||||||
|
CreatedAt *pgtype.Timestamptz `gorm:"autoCreateTime" json:"-"`
|
||||||
|
TotalSupply string `gorm:"total_supply"`
|
||||||
|
AfterReserve0 string `gorm:"after_reserve0"`
|
||||||
|
AfterReserve1 string `gorm:"after_reserve1"`
|
||||||
|
PositionChange int64 `gorm:"position_change"`
|
||||||
|
Platform string `gorm:"column:tx_platform;type:platform;default:'none'" json:"tx_platform"`
|
||||||
|
PlatformFee decimal.Decimal `gorm:"-" json:"-"` // TODO: save to db
|
||||||
|
CUPrice decimal.Decimal `gorm:"column:tx_cu_price;type:numeric" json:"tx_cu_price"`
|
||||||
|
MevAgent string `gorm:"column:tx_mev_agent;type:mev_agent;default:'none'" json:"tx_mev_agent"`
|
||||||
|
MevAgentFee decimal.Decimal `gorm:"column:tx_mev_agent_fee;type:numeric" json:"tx_mev_agent_fee"`
|
||||||
|
AfterSOLBalance decimal.Decimal `gorm:"column:after_sol_balance;type:numeric" json:"after_sol_balance"`
|
||||||
|
EntryContract string `gorm:"column:tx_entry_contract;type:entry_contract;default:'none'" json:"tx_entry_contract"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Action struct {
|
||||||
|
Id pgtype.UUID `gorm:"column:id;primaryKey;default:uuid_generate_v4()" json:"-"`
|
||||||
|
Maker string `json:"maker"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
Pair string `json:"pair"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
Block uint64 `json:"block"`
|
||||||
|
BlockAt pgtype.Timestamptz `json:"block_at"`
|
||||||
|
TxHash string `json:"tx_hash"`
|
||||||
|
CreatedAt *pgtype.Timestamptz `gorm:"autoCreateTime" json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlockData struct {
|
||||||
|
Pairs map[string]Pair
|
||||||
|
Txs []Tx
|
||||||
|
Actions []Action
|
||||||
|
Price decimal.Decimal
|
||||||
|
NewRaydium []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBlockData(price decimal.Decimal) *BlockData {
|
||||||
|
return &BlockData{
|
||||||
|
Pairs: make(map[string]Pair),
|
||||||
|
Txs: make([]Tx, 0),
|
||||||
|
Actions: make([]Action, 0),
|
||||||
|
Price: price,
|
||||||
|
NewRaydium: make([]string, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bd *BlockData) AppendTx(tx Tx) {
|
||||||
|
bd.Txs = append(bd.Txs, tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bd *BlockData) AppendAction(action Action) {
|
||||||
|
bd.Actions = append(bd.Actions, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bd *BlockData) SetPair(action SwapGetter, block uint64, _ string) error {
|
||||||
|
pair, err := action.GetPair(block, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bd.Pairs[pair.Address] = *pair
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type SwapGetter struct {
|
||||||
|
solana_parser.Swap
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
PositionChangeNone = int64(iota)
|
||||||
|
PositionChangeNewBuy
|
||||||
|
PositionChangeBuyMore
|
||||||
|
PositionChangeSellPart
|
||||||
|
PositionChangeSellAll
|
||||||
|
)
|
||||||
|
|
||||||
|
func (spg SwapGetter) GetLiquidityTx(tx *solana_parser.Tx, index uint64) (*Tx, error) {
|
||||||
|
if spg.BaseMint != solana.WrappedSol && spg.QuoteMint != solana.WrappedSol {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
token0 string
|
||||||
|
amount0 decimal.Decimal
|
||||||
|
amount1 decimal.Decimal
|
||||||
|
pool0 decimal.Decimal
|
||||||
|
pool1 decimal.Decimal
|
||||||
|
|
||||||
|
event string
|
||||||
|
)
|
||||||
|
|
||||||
|
if spg.BaseMint == solana.WrappedSol {
|
||||||
|
amount0 = spg.QuoteAmount.Div(decimal.New(1, int32(spg.QuoteMintDecimals)))
|
||||||
|
amount1 = spg.BaseAmount.Div(decimal.New(1, int32(spg.BaseMintDecimals)))
|
||||||
|
token0 = spg.QuoteMint.String()
|
||||||
|
pool0 = spg.QuoteReserve.Div(decimal.New(1, int32(spg.QuoteMintDecimals)))
|
||||||
|
pool1 = spg.BaseReserve.Div(decimal.New(1, int32(spg.BaseMintDecimals)))
|
||||||
|
} else {
|
||||||
|
amount0 = spg.BaseAmount.Div(decimal.New(1, int32(spg.BaseMintDecimals)))
|
||||||
|
amount1 = spg.QuoteAmount.Div(decimal.New(1, int32(spg.QuoteMintDecimals)))
|
||||||
|
token0 = spg.BaseMint.String()
|
||||||
|
pool0 = spg.BaseReserve.Div(decimal.New(1, int32(spg.BaseMintDecimals)))
|
||||||
|
pool1 = spg.QuoteReserve.Div(decimal.New(1, int32(spg.QuoteMintDecimals)))
|
||||||
|
}
|
||||||
|
if spg.Event == "deposit" || spg.Event == "add" || spg.Event == "add_liquidity" || spg.Event == "add_liquidity_one_side" {
|
||||||
|
event = "add"
|
||||||
|
} else if spg.Event == "withdraw" || spg.Event == "remove" || spg.Event == "remove_liquidity" || spg.Event == "remove_liquidity_one_side" {
|
||||||
|
event = "remove"
|
||||||
|
}
|
||||||
|
if event == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mevName, mevFee := tx.CheckMevAgent()
|
||||||
|
platformName, platformFee := tx.CheckPlatform(spg.Swap)
|
||||||
|
|
||||||
|
pairString := ""
|
||||||
|
if spg.Program == solana_parser.SolProgramPump {
|
||||||
|
pairString = spg.BaseMint.String()
|
||||||
|
} else {
|
||||||
|
pairString = spg.Pool.String()
|
||||||
|
}
|
||||||
|
t := pgtype.Timestamptz{}
|
||||||
|
_ = t.Set(time.Unix(tx.BlockAt, 0))
|
||||||
|
return &Tx{
|
||||||
|
PairAddress: pairString,
|
||||||
|
Maker: spg.User.String(),
|
||||||
|
Token0Address: token0,
|
||||||
|
Token1Address: "So11111111111111111111111111111111111111112",
|
||||||
|
Token0Amount: amount0,
|
||||||
|
Token1Amount: amount1,
|
||||||
|
Block: tx.Block,
|
||||||
|
BlockIndex: tx.BlockIndex,
|
||||||
|
Event: event,
|
||||||
|
TxHash: tx.GetTxHash(),
|
||||||
|
TxIndex: index,
|
||||||
|
BlockAt: t,
|
||||||
|
Program: spg.Program,
|
||||||
|
AfterReserve0: pool0.String(),
|
||||||
|
AfterReserve1: pool1.String(),
|
||||||
|
Platform: platformName,
|
||||||
|
PlatformFee: platformFee,
|
||||||
|
CUPrice: tx.CUPrice,
|
||||||
|
MevAgent: mevName,
|
||||||
|
MevAgentFee: mevFee,
|
||||||
|
AfterSOLBalance: spg.AfterSOLBalance,
|
||||||
|
EntryContract: spg.CheckEntryContract(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (spg SwapGetter) GetTx(tx *solana_parser.Tx, index uint64, price decimal.Decimal) Tx {
|
||||||
|
var (
|
||||||
|
token0 string
|
||||||
|
amount0 decimal.Decimal
|
||||||
|
amount1 decimal.Decimal
|
||||||
|
pool0 decimal.Decimal
|
||||||
|
pool1 decimal.Decimal
|
||||||
|
|
||||||
|
event string
|
||||||
|
)
|
||||||
|
|
||||||
|
if spg.BaseMint == solana.WrappedSol {
|
||||||
|
amount0 = spg.QuoteAmount.Div(decimal.New(1, int32(spg.QuoteMintDecimals)))
|
||||||
|
amount1 = spg.BaseAmount.Div(decimal.New(1, int32(spg.BaseMintDecimals)))
|
||||||
|
token0 = spg.QuoteMint.String()
|
||||||
|
pool0 = spg.QuoteReserve.Div(decimal.New(1, int32(spg.QuoteMintDecimals)))
|
||||||
|
pool1 = spg.BaseReserve.Div(decimal.New(1, int32(spg.BaseMintDecimals)))
|
||||||
|
if spg.Event == "buy" {
|
||||||
|
event = "sell"
|
||||||
|
} else if spg.Event == "sell" {
|
||||||
|
event = "buy"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
amount0 = spg.BaseAmount.Div(decimal.New(1, int32(spg.BaseMintDecimals)))
|
||||||
|
amount1 = spg.QuoteAmount.Div(decimal.New(1, int32(spg.QuoteMintDecimals)))
|
||||||
|
token0 = spg.BaseMint.String()
|
||||||
|
pool0 = spg.BaseReserve.Div(decimal.New(1, int32(spg.BaseMintDecimals)))
|
||||||
|
pool1 = spg.QuoteReserve.Div(decimal.New(1, int32(spg.QuoteMintDecimals)))
|
||||||
|
event = spg.Event
|
||||||
|
}
|
||||||
|
|
||||||
|
priceUsd := decimal.Zero
|
||||||
|
if amount0.GreaterThan(priceUsd) {
|
||||||
|
priceUsd = amount1.Div(amount0).Mul(price)
|
||||||
|
}
|
||||||
|
pc := PositionChangeNone
|
||||||
|
if event == "buy" {
|
||||||
|
pc = PositionChangeNewBuy
|
||||||
|
if spg.BaseMint == solana.WrappedSol {
|
||||||
|
if spg.UserQuoteBalance.GreaterThan(spg.QuoteAmount) {
|
||||||
|
pc = PositionChangeBuyMore
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if spg.UserBaseBalance.GreaterThan(spg.BaseAmount) {
|
||||||
|
pc = PositionChangeBuyMore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if event == "sell" {
|
||||||
|
pc = PositionChangeSellPart
|
||||||
|
if spg.BaseMint == solana.WrappedSol {
|
||||||
|
if spg.UserQuoteBalance.Div(decimal.New(1, int32(spg.QuoteMintDecimals))).LessThan(decimal.NewFromFloat(0.01)) {
|
||||||
|
pc = PositionChangeSellAll
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if spg.UserBaseBalance.Div(decimal.New(1, int32(spg.BaseMintDecimals))).LessThan(decimal.NewFromFloat(0.01)) {
|
||||||
|
pc = PositionChangeSellAll
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mevName, mevFee := tx.CheckMevAgent()
|
||||||
|
platformName, platformFee := tx.CheckPlatform(spg.Swap)
|
||||||
|
|
||||||
|
if mevName == "" {
|
||||||
|
mevName = "none"
|
||||||
|
}
|
||||||
|
if mevName == "unknown" {
|
||||||
|
mevName = "none"
|
||||||
|
mevFee = decimal.Zero
|
||||||
|
}
|
||||||
|
pairString := ""
|
||||||
|
if spg.Program == solana_parser.SolProgramPump {
|
||||||
|
pairString = spg.BaseMint.String()
|
||||||
|
} else {
|
||||||
|
pairString = spg.Pool.String()
|
||||||
|
}
|
||||||
|
t := pgtype.Timestamptz{}
|
||||||
|
_ = t.Set(time.Unix(tx.BlockAt, 0))
|
||||||
|
|
||||||
|
return Tx{
|
||||||
|
PairAddress: pairString,
|
||||||
|
Maker: spg.User.String(),
|
||||||
|
Token0Address: token0,
|
||||||
|
Token1Address: "So11111111111111111111111111111111111111112",
|
||||||
|
Token0Amount: amount0,
|
||||||
|
Token1Amount: amount1,
|
||||||
|
PriceUsd: priceUsd,
|
||||||
|
AmountUsd: amount1.Mul(price),
|
||||||
|
Block: tx.Block,
|
||||||
|
BlockIndex: tx.BlockIndex,
|
||||||
|
Event: event,
|
||||||
|
TxHash: tx.GetTxHash(),
|
||||||
|
TxIndex: index,
|
||||||
|
BlockAt: t,
|
||||||
|
Program: spg.Program,
|
||||||
|
AfterReserve0: pool0.String(),
|
||||||
|
AfterReserve1: pool1.String(),
|
||||||
|
PositionChange: pc,
|
||||||
|
Platform: platformName,
|
||||||
|
PlatformFee: platformFee,
|
||||||
|
CUPrice: tx.CUPrice,
|
||||||
|
MevAgent: mevName,
|
||||||
|
MevAgentFee: mevFee,
|
||||||
|
AfterSOLBalance: spg.AfterSOLBalance,
|
||||||
|
EntryContract: spg.CheckEntryContract(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (spg SwapGetter) GetPair(slot uint64, _ string) (*Pair, error) {
|
||||||
|
//pump amm
|
||||||
|
if spg.Program == solana_parser.SolProgramPump {
|
||||||
|
tokenMint := spg.BaseMint.String()
|
||||||
|
return &Pair{
|
||||||
|
Address: tokenMint,
|
||||||
|
Token0: tokenMint,
|
||||||
|
Token1: "So11111111111111111111111111111111111111112",
|
||||||
|
ChainId: 900,
|
||||||
|
Reserve0: spg.BaseReserve.Div(decimal.New(1, int32(spg.BaseMintDecimals))),
|
||||||
|
Reserve1: spg.QuoteReserve.Div(decimal.New(1, int32(spg.QuoteMintDecimals))),
|
||||||
|
IsCreate: spg.Event == "create",
|
||||||
|
Program: spg.Program,
|
||||||
|
UpdateSlot: slot,
|
||||||
|
}, nil
|
||||||
|
} else {
|
||||||
|
var (
|
||||||
|
token0 string
|
||||||
|
amount0 decimal.Decimal
|
||||||
|
amount1 decimal.Decimal
|
||||||
|
)
|
||||||
|
if spg.BaseMint.IsZero() || spg.QuoteMint.IsZero() {
|
||||||
|
return nil, errors.New("base mint or quote mint is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if spg.BaseMint == solana.WrappedSol {
|
||||||
|
amount0 = spg.QuoteReserve.Div(decimal.New(1, int32(spg.QuoteMintDecimals)))
|
||||||
|
amount1 = spg.BaseReserve.Div(decimal.New(1, int32(spg.BaseMintDecimals)))
|
||||||
|
//decimal0 = spg.QuoteMintDecimals
|
||||||
|
token0 = spg.QuoteMint.String()
|
||||||
|
} else {
|
||||||
|
amount0 = spg.BaseReserve.Div(decimal.New(1, int32(spg.BaseMintDecimals)))
|
||||||
|
amount1 = spg.QuoteReserve.Div(decimal.New(1, int32(spg.QuoteMintDecimals)))
|
||||||
|
//decimal0 = a.BaseDecimals
|
||||||
|
token0 = spg.BaseMint.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Pair{
|
||||||
|
Address: spg.Pool.String(),
|
||||||
|
LpToken: spg.LpMint.String(),
|
||||||
|
Token0: token0,
|
||||||
|
Token1: "So11111111111111111111111111111111111111112",
|
||||||
|
ChainId: 900,
|
||||||
|
Reserve0: amount0,
|
||||||
|
Reserve1: amount1,
|
||||||
|
IsCreate: false,
|
||||||
|
Program: spg.Program,
|
||||||
|
UpdateSlot: slot,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBlockTxsFromDb(db *gorm.DB, block uint64) ([]Tx, error) {
|
||||||
|
var txs []Tx
|
||||||
|
result := db.Table("tx").Where("block = ?", block).Find(&txs)
|
||||||
|
return txs, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBlockActionsFromDb(db *gorm.DB, block uint64) ([]Action, error) {
|
||||||
|
var txs []Action
|
||||||
|
result := db.Table("action").Where("block = ?", block).Find(&txs)
|
||||||
|
return txs, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
type dbLog struct {
|
||||||
|
logger *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *dbLog) Printf(format string, args ...interface{}) {
|
||||||
|
l.logger.Info(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDbLog() *dbLog {
|
||||||
|
return &dbLog{logger: slog.Default()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGorm(dsn string) *gorm.DB {
|
||||||
|
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
|
||||||
|
Logger: logger.New(newDbLog(), logger.Config{
|
||||||
|
Colorful: false,
|
||||||
|
LogLevel: logger.Warn,
|
||||||
|
SlowThreshold: time.Second * 10,
|
||||||
|
IgnoreRecordNotFoundError: true,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareTxs(dbTxs []Tx, dataTxs []Tx) (diff int, missing int) {
|
||||||
|
dataByHash := make(map[string][]Tx, len(dataTxs))
|
||||||
|
for _, tx := range dataTxs {
|
||||||
|
dataByHash[fmt.Sprintf("%s-%d", tx.TxHash, tx.TxIndex)] = append(dataByHash[fmt.Sprintf("%s-%d", tx.TxHash, tx.TxIndex)], tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dbTx := range dbTxs {
|
||||||
|
candidates := dataByHash[fmt.Sprintf("%s-%d", dbTx.TxHash, dbTx.TxIndex)]
|
||||||
|
if len(candidates) == 0 {
|
||||||
|
missing++
|
||||||
|
log.Printf("missing tx: %s", txCompareString(dbTx))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
matched := false
|
||||||
|
for _, dataTx := range candidates {
|
||||||
|
if txEqualWithoutHash(dbTx, dataTx) {
|
||||||
|
matched = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !matched {
|
||||||
|
diff++
|
||||||
|
log.Printf("tx diff hash=%s, program=%s, event:%s: %s, ", dbTx.TxHash, dbTx.Program, dbTx.Event, txCompareDiffString(dbTx, candidates[0]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("compare txs done: db=%d parsed=%d missing=%d diff=%d", len(dbTxs), len(dataTxs), missing, diff)
|
||||||
|
return diff, missing
|
||||||
|
}
|
||||||
|
|
||||||
|
func withinOnePercentDecimal(a decimal.Decimal, b decimal.Decimal) bool {
|
||||||
|
if a.IsZero() {
|
||||||
|
return b.IsZero()
|
||||||
|
}
|
||||||
|
diff := a.Sub(b).Abs()
|
||||||
|
threshold := a.Abs().Mul(decimal.NewFromFloat(0.03))
|
||||||
|
return diff.LessThanOrEqual(threshold)
|
||||||
|
}
|
||||||
|
|
||||||
|
func withinOnePercentStringDecimal(a string, b string) bool {
|
||||||
|
ad, errA := decimal.NewFromString(a)
|
||||||
|
bd, errB := decimal.NewFromString(b)
|
||||||
|
if errA != nil || errB != nil {
|
||||||
|
return a == b
|
||||||
|
}
|
||||||
|
return withinOnePercentDecimal(ad, bd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func txEqualWithoutHash(a Tx, b Tx) bool {
|
||||||
|
//mevMatch := a.MevAgent == b.MevAgent || (a.MevAgent == "none" && b.MevAgent == "unknown") || (a.MevAgent == "unknown" && b.MevAgent == "none")
|
||||||
|
//mevNone := a.MevAgent == "none" || a.MevAgent == "unknown"
|
||||||
|
|
||||||
|
return a.PairAddress == b.PairAddress &&
|
||||||
|
a.Token1Address == b.Token1Address &&
|
||||||
|
(a.Token0Address == "" || a.Token0Address == b.Token0Address) &&
|
||||||
|
//a.Maker == b.Maker &&
|
||||||
|
(a.Token0Address == "" || withinOnePercentDecimal(a.Token0Amount, b.Token0Amount)) &&
|
||||||
|
withinOnePercentDecimal(a.Token1Amount, b.Token1Amount) &&
|
||||||
|
a.Block == b.Block &&
|
||||||
|
a.BlockIndex == b.BlockIndex &&
|
||||||
|
a.Event == b.Event &&
|
||||||
|
a.TxIndex == b.TxIndex &&
|
||||||
|
a.Program == b.Program &&
|
||||||
|
(a.Program == solana_parser.SolProgramPumpAMM || a.Program == solana_parser.SolProgramPump || a.Program == solana_parser.SolProgramMeteoraPools || (a.Event == "create") || withinOnePercentStringDecimal(a.AfterReserve0, b.AfterReserve0)) &&
|
||||||
|
(a.Program == solana_parser.SolProgramPumpAMM || a.Program == solana_parser.SolProgramPump || a.Program == solana_parser.SolProgramMeteoraPools || (a.Event == "create") || withinOnePercentStringDecimal(a.AfterReserve1, b.AfterReserve1)) &&
|
||||||
|
// a.PositionChange == b.PositionChange &&
|
||||||
|
(a.Platform == b.Platform || (a.Platform == "photon" && b.Platform == "fake") || (a.Platform == "trojan" && b.Platform == "fake")) &&
|
||||||
|
a.CUPrice.String() == b.CUPrice.String() // &&
|
||||||
|
//mevMatch &&
|
||||||
|
//(mevNone || a.MevAgentFee.String() == b.MevAgentFee.String()) &&
|
||||||
|
//(a.Program == solana_parser.SolProgramRaydiumV4 || a.AfterSOLBalance.String() == b.AfterSOLBalance.String())
|
||||||
|
//&&
|
||||||
|
// a.EntryContract == b.EntryContract
|
||||||
|
}
|
||||||
|
|
||||||
|
func txCompareDiffString(a Tx, b Tx) string {
|
||||||
|
var diffs []string
|
||||||
|
if a.PairAddress != b.PairAddress {
|
||||||
|
diffs = append(diffs, fmt.Sprintf("PairAddress db=%s data=%s", a.PairAddress, b.PairAddress))
|
||||||
|
}
|
||||||
|
//if a.Maker != b.Maker {
|
||||||
|
// diffs = append(diffs, fmt.Sprintf("Maker db=%s, data=%s", a.Maker, b.Maker))
|
||||||
|
//}
|
||||||
|
if a.Token1Address != b.Token1Address {
|
||||||
|
diffs = append(diffs, fmt.Sprintf("Token1Address db=%s data=%s", a.Token1Address, b.Token1Address))
|
||||||
|
}
|
||||||
|
if a.Token0Address != b.Token0Address {
|
||||||
|
diffs = append(diffs, fmt.Sprintf("Token0Address db=%s data=%s", a.Token0Address, b.Token0Address))
|
||||||
|
}
|
||||||
|
if !withinOnePercentDecimal(a.Token0Amount, b.Token0Amount) {
|
||||||
|
diffs = append(diffs, fmt.Sprintf("Token0Amount db=%s data=%s", a.Token0Amount.String(), b.Token0Amount.String()))
|
||||||
|
}
|
||||||
|
if !withinOnePercentDecimal(a.Token1Amount, b.Token1Amount) {
|
||||||
|
diffs = append(diffs, fmt.Sprintf("Token1Amount db=%s data=%s", a.Token1Amount.String(), b.Token1Amount.String()))
|
||||||
|
}
|
||||||
|
if a.Block != b.Block {
|
||||||
|
diffs = append(diffs, fmt.Sprintf("Block db=%d data=%d", a.Block, b.Block))
|
||||||
|
}
|
||||||
|
if a.BlockIndex != b.BlockIndex {
|
||||||
|
diffs = append(diffs, fmt.Sprintf("BlockIndex db=%d data=%d", a.BlockIndex, b.BlockIndex))
|
||||||
|
}
|
||||||
|
if a.Event != b.Event {
|
||||||
|
diffs = append(diffs, fmt.Sprintf("Event db=%s data=%s", a.Event, b.Event))
|
||||||
|
}
|
||||||
|
if a.TxIndex != b.TxIndex {
|
||||||
|
diffs = append(diffs, fmt.Sprintf("TxIndex db=%d data=%d", a.TxIndex, b.TxIndex))
|
||||||
|
}
|
||||||
|
if a.Program != b.Program {
|
||||||
|
diffs = append(diffs, fmt.Sprintf("Program db=%s data=%s", a.Program, b.Program))
|
||||||
|
}
|
||||||
|
if !withinOnePercentStringDecimal(a.AfterReserve0, b.AfterReserve0) {
|
||||||
|
diffs = append(diffs, fmt.Sprintf("AfterReserve0 db=%s data=%s", a.AfterReserve0, b.AfterReserve0))
|
||||||
|
}
|
||||||
|
if !withinOnePercentStringDecimal(a.AfterReserve1, b.AfterReserve1) {
|
||||||
|
diffs = append(diffs, fmt.Sprintf("AfterReserve1 db=%s data=%s", a.AfterReserve1, b.AfterReserve1))
|
||||||
|
}
|
||||||
|
//if a.PositionChange != b.PositionChange {
|
||||||
|
// diffs = append(diffs, fmt.Sprintf("PositionChange db=%d data=%d", a.PositionChange, b.PositionChange))
|
||||||
|
//}
|
||||||
|
if a.Platform != b.Platform {
|
||||||
|
diffs = append(diffs, fmt.Sprintf("Platform db=%s data=%s", a.Platform, b.Platform))
|
||||||
|
}
|
||||||
|
if a.CUPrice.String() != b.CUPrice.String() {
|
||||||
|
diffs = append(diffs, fmt.Sprintf("CUPrice db=%s data=%s", a.CUPrice.String(), b.CUPrice.String()))
|
||||||
|
}
|
||||||
|
//if a.MevAgent != b.MevAgent {
|
||||||
|
// diffs = append(diffs, fmt.Sprintf("MevAgent db=%s data=%s", a.MevAgent, b.MevAgent))
|
||||||
|
//}
|
||||||
|
//if a.MevAgentFee.String() != b.MevAgentFee.String() {
|
||||||
|
// diffs = append(diffs, fmt.Sprintf("MevAgentFee db=%s data=%s", a.MevAgentFee.String(), b.MevAgentFee.String()))
|
||||||
|
//}
|
||||||
|
//if a.AfterSOLBalance.String() != b.AfterSOLBalance.String() {
|
||||||
|
// diffs = append(diffs, fmt.Sprintf("AfterSOLBalance db=%s data=%s", a.AfterSOLBalance.String(), b.AfterSOLBalance.String()))
|
||||||
|
//}
|
||||||
|
//if a.EntryContract != b.EntryContract {
|
||||||
|
// diffs = append(diffs, fmt.Sprintf("EntryContract db=%s data=%s", a.EntryContract, b.EntryContract))
|
||||||
|
//}
|
||||||
|
return strings.Join(diffs, "; ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareActions(dbActions []Action, dataActions []Action) (diff, missing int) {
|
||||||
|
dataByHash := make(map[string][]Action, len(dataActions))
|
||||||
|
for _, action := range dataActions {
|
||||||
|
dataByHash[action.TxHash] = append(dataByHash[action.TxHash], action)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dbAction := range dbActions {
|
||||||
|
candidates := dataByHash[dbAction.TxHash]
|
||||||
|
if len(candidates) == 0 {
|
||||||
|
missing++
|
||||||
|
log.Printf("missing action: %s", actionCompareString(dbAction))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
matched := false
|
||||||
|
for _, dataAction := range candidates {
|
||||||
|
if actionEqualWithoutHash(dbAction, dataAction) {
|
||||||
|
matched = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !matched {
|
||||||
|
diff++
|
||||||
|
log.Printf("action diff hash=%s: %s", dbAction.TxHash, actionCompareDiffString(dbAction, candidates[0]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("compare actions done: db=%d parsed=%d missing=%d diff=%d", len(dbActions), len(dataActions), missing, diff)
|
||||||
|
return diff, missing
|
||||||
|
}
|
||||||
|
|
||||||
|
func actionEqualWithoutHash(a Action, b Action) bool {
|
||||||
|
return a.Maker == b.Maker &&
|
||||||
|
a.Token == b.Token &&
|
||||||
|
a.Pair == b.Pair &&
|
||||||
|
a.Action == b.Action &&
|
||||||
|
a.Block == b.Block
|
||||||
|
}
|
||||||
|
|
||||||
|
func actionCompareDiffString(a Action, b Action) string {
|
||||||
|
var diffs []string
|
||||||
|
if a.Maker != b.Maker {
|
||||||
|
diffs = append(diffs, fmt.Sprintf("Maker db=%s data=%s", a.Maker, b.Maker))
|
||||||
|
}
|
||||||
|
if a.Token != b.Token {
|
||||||
|
diffs = append(diffs, fmt.Sprintf("Token db=%s data=%s", a.Token, b.Token))
|
||||||
|
}
|
||||||
|
if a.Pair != b.Pair {
|
||||||
|
diffs = append(diffs, fmt.Sprintf("Pair db=%s data=%s", a.Pair, b.Pair))
|
||||||
|
}
|
||||||
|
if a.Action != b.Action {
|
||||||
|
diffs = append(diffs, fmt.Sprintf("Action db=%s data=%s", a.Action, b.Action))
|
||||||
|
}
|
||||||
|
if a.Block != b.Block {
|
||||||
|
diffs = append(diffs, fmt.Sprintf("Block db=%d data=%d", a.Block, b.Block))
|
||||||
|
}
|
||||||
|
return strings.Join(diffs, "; ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func actionCompareString(action Action) string {
|
||||||
|
return fmt.Sprintf("Maker=%s Token=%s Pair=%s Action=%s Block=%d TxHash=%s", action.Maker, action.Token, action.Pair, action.Action, action.Block, action.TxHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func txCompareString(tx Tx) string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"tx.Program=%s TxHash=%s PairAddress=%s Token1Address=%s Token0Amount=%s Token1Amount=%s Block=%d BlockIndex=%d Event=%s TxIndex=%d AfterReserve0=%s AfterReserve1=%s PositionChange=%d Platform=%s CUPrice=%s MevAgent=%s MevAgentFee=%s AfterSOLBalance=%s EntryContract=%s",
|
||||||
|
tx.Program,
|
||||||
|
tx.TxHash,
|
||||||
|
tx.PairAddress,
|
||||||
|
tx.Token1Address,
|
||||||
|
tx.Token0Amount.String(),
|
||||||
|
tx.Token1Amount.String(),
|
||||||
|
tx.Block,
|
||||||
|
tx.BlockIndex,
|
||||||
|
tx.Event,
|
||||||
|
tx.TxIndex,
|
||||||
|
tx.AfterReserve0,
|
||||||
|
tx.AfterReserve1,
|
||||||
|
tx.PositionChange,
|
||||||
|
tx.Platform,
|
||||||
|
tx.CUPrice.String(),
|
||||||
|
tx.MevAgent,
|
||||||
|
tx.MevAgentFee.String(),
|
||||||
|
tx.AfterSOLBalance.String(),
|
||||||
|
tx.EntryContract,
|
||||||
|
)
|
||||||
|
}
|
||||||
14
meta.go
14
meta.go
@@ -81,11 +81,17 @@ var (
|
|||||||
meteoraDlmmAddLiquidity2Discriminator = calculateDiscriminator("global:add_liquidity2")
|
meteoraDlmmAddLiquidity2Discriminator = calculateDiscriminator("global:add_liquidity2")
|
||||||
meteoraDlmmAddLiquidityByStrategyDiscriminator = calculateDiscriminator("global:add_liquidity_by_strategy")
|
meteoraDlmmAddLiquidityByStrategyDiscriminator = calculateDiscriminator("global:add_liquidity_by_strategy")
|
||||||
meteoraDlmmAddLiquidityByStrategy2Discriminator = calculateDiscriminator("global:add_liquidity_by_strategy2")
|
meteoraDlmmAddLiquidityByStrategy2Discriminator = calculateDiscriminator("global:add_liquidity_by_strategy2")
|
||||||
|
meteoraDlmmClaimFeeDiscriminator = calculateDiscriminator("global:claim_fee")
|
||||||
|
meteoraDlmmClaimFee2Discriminator = calculateDiscriminator("global:claim_fee2")
|
||||||
|
meteoraDlmmRebalanceLiquidityDiscriminator = calculateDiscriminator("global:rebalance_liquidity")
|
||||||
meteoraDlmmRemoveLiquidityDiscriminator = calculateDiscriminator("global:remove_liquidity")
|
meteoraDlmmRemoveLiquidityDiscriminator = calculateDiscriminator("global:remove_liquidity")
|
||||||
meteoraDlmmRemoveLiquidity2Discriminator = calculateDiscriminator("global:remove_liquidity2")
|
meteoraDlmmRemoveLiquidity2Discriminator = calculateDiscriminator("global:remove_liquidity2")
|
||||||
meteoraDlmmRemoveLiquidityByRangeDiscriminator = calculateDiscriminator("global:remove_liquidity_by_range")
|
meteoraDlmmRemoveLiquidityByRangeDiscriminator = calculateDiscriminator("global:remove_liquidity_by_range")
|
||||||
meteoraDlmmRemoveLiquidityByRange2Discriminator = calculateDiscriminator("global:remove_liquidity_by_range2")
|
meteoraDlmmRemoveLiquidityByRange2Discriminator = calculateDiscriminator("global:remove_liquidity_by_range2")
|
||||||
meteoraDlmmAddLiquidityEventDiscriminator = calculateDiscriminator("event:AddLiquidity")
|
meteoraDlmmAddLiquidityEventDiscriminator = calculateDiscriminator("event:AddLiquidity")
|
||||||
|
meteoraDlmmClaimFeeEventDiscriminator = calculateDiscriminator("event:ClaimFee")
|
||||||
|
meteoraDlmmClaimFee2EventDiscriminator = calculateDiscriminator("event:ClaimFee2")
|
||||||
|
meteoraDlmmRebalancingEventDiscriminator = calculateDiscriminator("event:Rebalancing")
|
||||||
meteoraDlmmRemoveLiquidityEventDiscriminator = calculateDiscriminator("event:RemoveLiquidity")
|
meteoraDlmmRemoveLiquidityEventDiscriminator = calculateDiscriminator("event:RemoveLiquidity")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -200,8 +206,10 @@ var (
|
|||||||
const (
|
const (
|
||||||
raydiumV4InitializePoolDiscriminator = uint8(1)
|
raydiumV4InitializePoolDiscriminator = uint8(1)
|
||||||
|
|
||||||
raydiumV4SwapBaseInDiscriminator = uint8(9)
|
raydiumV4SwapBaseInDiscriminator = uint8(9)
|
||||||
raydiumV4SwapBaseOutDiscriminator = uint8(11)
|
raydiumV4SwapBaseOutDiscriminator = uint8(11)
|
||||||
|
raydiumV4SwapBaseInV2Discriminator = uint8(16)
|
||||||
|
raydiumV4SwapBaseOutV2Discriminator = uint8(17)
|
||||||
|
|
||||||
raydiumV4AddLiquidityDiscriminator = uint8(3)
|
raydiumV4AddLiquidityDiscriminator = uint8(3)
|
||||||
raydiumV4RemoveLiquidityDiscriminator = uint8(4)
|
raydiumV4RemoveLiquidityDiscriminator = uint8(4)
|
||||||
@@ -236,4 +244,6 @@ var createAccountWithSeedDiscriminator = uint32(3)
|
|||||||
var systemProgram = solana.MustPublicKeyFromBase58("11111111111111111111111111111111")
|
var systemProgram = solana.MustPublicKeyFromBase58("11111111111111111111111111111111")
|
||||||
var momoProgram = solana.MustPublicKeyFromBase58("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr")
|
var momoProgram = solana.MustPublicKeyFromBase58("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr")
|
||||||
|
|
||||||
|
var chainLinkProgram = solana.MustPublicKeyFromBase58("cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ")
|
||||||
|
|
||||||
var eventDiscriminator = [8]byte{228, 69, 165, 46, 81, 203, 154, 29}
|
var eventDiscriminator = [8]byte{228, 69, 165, 46, 81, 203, 154, 29}
|
||||||
|
|||||||
528
metaoradlmm.go
528
metaoradlmm.go
@@ -55,6 +55,51 @@ type dlmmRemoveLiquidityEvent struct {
|
|||||||
ActiveBinId int32
|
ActiveBinId int32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dlmmClaimFeeInnerEvent struct {
|
||||||
|
LbPair solana.PublicKey
|
||||||
|
Position solana.PublicKey
|
||||||
|
Owner solana.PublicKey
|
||||||
|
FeeX uint64
|
||||||
|
FeeY uint64
|
||||||
|
ActiveBinId int32
|
||||||
|
HasActiveBin bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type dlmmClaimFeeEvent struct {
|
||||||
|
LbPair solana.PublicKey
|
||||||
|
Position solana.PublicKey
|
||||||
|
Owner solana.PublicKey
|
||||||
|
FeeX uint64
|
||||||
|
FeeY uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type dlmmClaimFee2Event struct {
|
||||||
|
LbPair solana.PublicKey
|
||||||
|
Position solana.PublicKey
|
||||||
|
Owner solana.PublicKey
|
||||||
|
FeeX uint64
|
||||||
|
FeeY uint64
|
||||||
|
ActiveBinId int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type dlmmRebalancingEvent struct {
|
||||||
|
LbPair solana.PublicKey
|
||||||
|
Position solana.PublicKey
|
||||||
|
Owner solana.PublicKey
|
||||||
|
ActiveBinId int32
|
||||||
|
XWithdrawnAmount uint64
|
||||||
|
XAddedAmount uint64
|
||||||
|
YWithdrawnAmount uint64
|
||||||
|
YAddedAmount uint64
|
||||||
|
XFeeAmount uint64
|
||||||
|
YFeeAmount uint64
|
||||||
|
OldMinBinId int32
|
||||||
|
OldMaxBinId int32
|
||||||
|
NewMinBinId int32
|
||||||
|
NewMaxBinId int32
|
||||||
|
Rewards [2]uint64
|
||||||
|
}
|
||||||
|
|
||||||
type dlmmBinLiquidityDistribution struct {
|
type dlmmBinLiquidityDistribution struct {
|
||||||
BinId int32
|
BinId int32
|
||||||
DistributionX uint16
|
DistributionX uint16
|
||||||
@@ -207,6 +252,10 @@ func metaoradlmmParser(tx *Tx, instruction Instruction, innerInstructions InnerI
|
|||||||
case meteoraDlmmAddLiquidityDiscriminator, meteoraDlmmAddLiquidity2Discriminator,
|
case meteoraDlmmAddLiquidityDiscriminator, meteoraDlmmAddLiquidity2Discriminator,
|
||||||
meteoraDlmmAddLiquidityByStrategyDiscriminator, meteoraDlmmAddLiquidityByStrategy2Discriminator:
|
meteoraDlmmAddLiquidityByStrategyDiscriminator, meteoraDlmmAddLiquidityByStrategy2Discriminator:
|
||||||
return metaoradlmmAddLiquidityParser(tx, instruction, innerInstructions, offset)
|
return metaoradlmmAddLiquidityParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case meteoraDlmmClaimFeeDiscriminator, meteoraDlmmClaimFee2Discriminator:
|
||||||
|
return metaoradlmmClaimFeeParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case meteoraDlmmRebalanceLiquidityDiscriminator:
|
||||||
|
return metaoradlmmRebalanceLiquidityParser(tx, instruction, innerInstructions, offset)
|
||||||
case meteoraDlmmRemoveLiquidityDiscriminator, meteoraDlmmRemoveLiquidity2Discriminator,
|
case meteoraDlmmRemoveLiquidityDiscriminator, meteoraDlmmRemoveLiquidity2Discriminator,
|
||||||
meteoraDlmmRemoveLiquidityByRangeDiscriminator, meteoraDlmmRemoveLiquidityByRange2Discriminator:
|
meteoraDlmmRemoveLiquidityByRangeDiscriminator, meteoraDlmmRemoveLiquidityByRange2Discriminator:
|
||||||
return metaoradlmmRemoveLiquidityParser(tx, instruction, innerInstructions, offset)
|
return metaoradlmmRemoveLiquidityParser(tx, instruction, innerInstructions, offset)
|
||||||
@@ -634,7 +683,7 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
|
|||||||
|
|
||||||
swap := Swap{
|
swap := Swap{
|
||||||
Program: SolProgramMeteoraDLMM,
|
Program: SolProgramMeteoraDLMM,
|
||||||
Event: "add_liquidity",
|
Event: "add",
|
||||||
Pool: pool,
|
Pool: pool,
|
||||||
BaseMint: baseMint,
|
BaseMint: baseMint,
|
||||||
QuoteMint: quoteMint,
|
QuoteMint: quoteMint,
|
||||||
@@ -802,7 +851,7 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
|
|||||||
|
|
||||||
swap := Swap{
|
swap := Swap{
|
||||||
Program: SolProgramMeteoraDLMM,
|
Program: SolProgramMeteoraDLMM,
|
||||||
Event: "remove_liquidity",
|
Event: "remove",
|
||||||
Pool: pool,
|
Pool: pool,
|
||||||
BaseMint: baseMint,
|
BaseMint: baseMint,
|
||||||
QuoteMint: quoteMint,
|
QuoteMint: quoteMint,
|
||||||
@@ -826,6 +875,274 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
|
|||||||
return []Swap{swap}, offset, nil
|
return []Swap{swap}, offset, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func metaoradlmmClaimFeeParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
result := tx.rawTx
|
||||||
|
|
||||||
|
entryContract := result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
if instruction.StackHeight != nil && *instruction.StackHeight > 2 {
|
||||||
|
for _, innerInstr := range innerInstructions.Instructions {
|
||||||
|
if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 {
|
||||||
|
entryContract = result.accountList[innerInstr.ProgramIDIndex]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
accounts, err := resolveDlmmClaimFeeAccounts(result, instruction.Data, instruction.Accounts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm claim fee accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
claimEvent, nextOffset, err := dlmmClaimFeeEventFromInnerInstructions(innerInstructions, instruction, offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nextOffset, err
|
||||||
|
}
|
||||||
|
offset = nextOffset
|
||||||
|
if claimEvent.FeeX == 0 && claimEvent.FeeY == 0 {
|
||||||
|
return nil, offset, InstructionIgnoredError
|
||||||
|
}
|
||||||
|
|
||||||
|
pool := result.accountList[accounts.poolIdx]
|
||||||
|
tokenXMint := result.accountList[accounts.tokenXMintIdx]
|
||||||
|
tokenYMint := result.accountList[accounts.tokenYMintIdx]
|
||||||
|
tokenXProgram := result.accountList[accounts.tokenXProgramIdx]
|
||||||
|
tokenYProgram := result.accountList[accounts.tokenYProgramIdx]
|
||||||
|
|
||||||
|
baseMint, quoteMint, baseIsX := dlmmSelectBaseQuote(tokenXMint, tokenYMint)
|
||||||
|
baseTokenProgram := tokenXProgram
|
||||||
|
quoteTokenProgram := tokenYProgram
|
||||||
|
baseReserveIdx := accounts.reserveXIdx
|
||||||
|
quoteReserveIdx := accounts.reserveYIdx
|
||||||
|
userBaseIdx := accounts.userTokenXIdx
|
||||||
|
userQuoteIdx := accounts.userTokenYIdx
|
||||||
|
baseAmount := decimal.NewFromUint64(claimEvent.FeeX)
|
||||||
|
quoteAmount := decimal.NewFromUint64(claimEvent.FeeY)
|
||||||
|
if !baseIsX {
|
||||||
|
baseTokenProgram = tokenYProgram
|
||||||
|
quoteTokenProgram = tokenXProgram
|
||||||
|
baseReserveIdx = accounts.reserveYIdx
|
||||||
|
quoteReserveIdx = accounts.reserveXIdx
|
||||||
|
userBaseIdx = accounts.userTokenYIdx
|
||||||
|
userQuoteIdx = accounts.userTokenXIdx
|
||||||
|
baseAmount = decimal.NewFromUint64(claimEvent.FeeY)
|
||||||
|
quoteAmount = decimal.NewFromUint64(claimEvent.FeeX)
|
||||||
|
}
|
||||||
|
|
||||||
|
eventUser := result.accountList[accounts.userIdx]
|
||||||
|
if !claimEvent.Owner.IsZero() {
|
||||||
|
eventUser = claimEvent.Owner
|
||||||
|
}
|
||||||
|
|
||||||
|
baseDecimals, ok := dlmmTokenDecimals(result, baseReserveIdx)
|
||||||
|
if !ok {
|
||||||
|
baseDecimals, _ = dlmmTokenDecimals(result, userBaseIdx)
|
||||||
|
}
|
||||||
|
quoteDecimals, ok := dlmmTokenDecimals(result, quoteReserveIdx)
|
||||||
|
if !ok {
|
||||||
|
quoteDecimals, _ = dlmmTokenDecimals(result, userQuoteIdx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := tx.Token[baseMint]; !exists && !baseMint.Equals(wSolMint) {
|
||||||
|
tx.Token[baseMint] = TokenMeta{
|
||||||
|
Mint: baseMint,
|
||||||
|
Decimals: baseDecimals,
|
||||||
|
TokenProgram: baseTokenProgram,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, exists := tx.Token[quoteMint]; !exists && !quoteMint.Equals(wSolMint) {
|
||||||
|
tx.Token[quoteMint] = TokenMeta{
|
||||||
|
Mint: quoteMint,
|
||||||
|
Decimals: quoteDecimals,
|
||||||
|
TokenProgram: quoteTokenProgram,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
baseReserve := getAccountBalanceAfterTx(result, baseReserveIdx)
|
||||||
|
quoteReserve := getAccountBalanceAfterTx(result, quoteReserveIdx)
|
||||||
|
userBase := getAccountBalanceAfterTx(result, userBaseIdx)
|
||||||
|
userQuote := getAccountBalanceAfterTx(result, userQuoteIdx)
|
||||||
|
if quoteMint.Equals(wSolMint) {
|
||||||
|
if solAmount, err := GetSolAfterTx(result, accounts.userIdx); err == nil {
|
||||||
|
userQuote = userQuote.Add(decimal.NewFromUint64(solAmount))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
swap := Swap{
|
||||||
|
Program: SolProgramMeteoraDLMM,
|
||||||
|
Event: "claim_fee",
|
||||||
|
Pool: pool,
|
||||||
|
BaseMint: baseMint,
|
||||||
|
QuoteMint: quoteMint,
|
||||||
|
BaseTokenProgram: baseTokenProgram,
|
||||||
|
QuoteTokenProgram: quoteTokenProgram,
|
||||||
|
BaseMintDecimals: baseDecimals,
|
||||||
|
QuoteMintDecimals: quoteDecimals,
|
||||||
|
User: eventUser,
|
||||||
|
BaseAmount: baseAmount,
|
||||||
|
QuoteAmount: quoteAmount,
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
UserBaseBalance: userBase,
|
||||||
|
UserQuoteBalance: userQuote,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
}
|
||||||
|
if claimEvent.HasActiveBin {
|
||||||
|
swap.StartBinId = claimEvent.ActiveBinId
|
||||||
|
swap.EndBinId = claimEvent.ActiveBinId
|
||||||
|
}
|
||||||
|
|
||||||
|
return []Swap{swap}, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func metaoradlmmRebalanceLiquidityParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
result := tx.rawTx
|
||||||
|
|
||||||
|
entryContract := result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
if instruction.StackHeight != nil && *instruction.StackHeight > 2 {
|
||||||
|
for _, innerInstr := range innerInstructions.Instructions {
|
||||||
|
if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 {
|
||||||
|
entryContract = result.accountList[innerInstr.ProgramIDIndex]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
accounts, err := resolveDlmmRebalanceAccounts(result, instruction.Accounts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm rebalance liquidity accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
event, nextOffset, err := dlmmRebalancingEventFromInnerInstructions(innerInstructions, instruction, offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nextOffset, err
|
||||||
|
}
|
||||||
|
offset = nextOffset
|
||||||
|
|
||||||
|
pool := result.accountList[accounts.poolIdx]
|
||||||
|
tokenXMint := result.accountList[accounts.tokenXMintIdx]
|
||||||
|
tokenYMint := result.accountList[accounts.tokenYMintIdx]
|
||||||
|
tokenXProgram := result.accountList[accounts.tokenXProgramIdx]
|
||||||
|
tokenYProgram := result.accountList[accounts.tokenYProgramIdx]
|
||||||
|
|
||||||
|
baseMint, quoteMint, baseIsX := dlmmSelectBaseQuote(tokenXMint, tokenYMint)
|
||||||
|
baseTokenProgram := tokenXProgram
|
||||||
|
quoteTokenProgram := tokenYProgram
|
||||||
|
baseReserveIdx := accounts.reserveXIdx
|
||||||
|
quoteReserveIdx := accounts.reserveYIdx
|
||||||
|
userBaseIdx := accounts.userTokenXIdx
|
||||||
|
userQuoteIdx := accounts.userTokenYIdx
|
||||||
|
withdrawBase := event.XWithdrawnAmount
|
||||||
|
withdrawQuote := event.YWithdrawnAmount
|
||||||
|
addBase := event.XAddedAmount
|
||||||
|
addQuote := event.YAddedAmount
|
||||||
|
if !baseIsX {
|
||||||
|
baseTokenProgram = tokenYProgram
|
||||||
|
quoteTokenProgram = tokenXProgram
|
||||||
|
baseReserveIdx = accounts.reserveYIdx
|
||||||
|
quoteReserveIdx = accounts.reserveXIdx
|
||||||
|
userBaseIdx = accounts.userTokenYIdx
|
||||||
|
userQuoteIdx = accounts.userTokenXIdx
|
||||||
|
withdrawBase = event.YWithdrawnAmount
|
||||||
|
withdrawQuote = event.XWithdrawnAmount
|
||||||
|
addBase = event.YAddedAmount
|
||||||
|
addQuote = event.XAddedAmount
|
||||||
|
}
|
||||||
|
|
||||||
|
eventUser := result.accountList[accounts.userIdx]
|
||||||
|
if !event.Owner.IsZero() {
|
||||||
|
eventUser = event.Owner
|
||||||
|
}
|
||||||
|
|
||||||
|
baseDecimals, ok := dlmmTokenDecimals(result, baseReserveIdx)
|
||||||
|
if !ok {
|
||||||
|
baseDecimals, _ = dlmmTokenDecimals(result, userBaseIdx)
|
||||||
|
}
|
||||||
|
quoteDecimals, ok := dlmmTokenDecimals(result, quoteReserveIdx)
|
||||||
|
if !ok {
|
||||||
|
quoteDecimals, _ = dlmmTokenDecimals(result, userQuoteIdx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := tx.Token[baseMint]; !exists && !baseMint.Equals(wSolMint) {
|
||||||
|
tx.Token[baseMint] = TokenMeta{
|
||||||
|
Mint: baseMint,
|
||||||
|
Decimals: baseDecimals,
|
||||||
|
TokenProgram: baseTokenProgram,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, exists := tx.Token[quoteMint]; !exists && !quoteMint.Equals(wSolMint) {
|
||||||
|
tx.Token[quoteMint] = TokenMeta{
|
||||||
|
Mint: quoteMint,
|
||||||
|
Decimals: quoteDecimals,
|
||||||
|
TokenProgram: quoteTokenProgram,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
baseReserve := getAccountBalanceAfterTx(result, baseReserveIdx)
|
||||||
|
quoteReserve := getAccountBalanceAfterTx(result, quoteReserveIdx)
|
||||||
|
userBase := getAccountBalanceAfterTx(result, userBaseIdx)
|
||||||
|
userQuote := getAccountBalanceAfterTx(result, userQuoteIdx)
|
||||||
|
if quoteMint.Equals(wSolMint) {
|
||||||
|
if solAmount, err := GetSolAfterTx(result, accounts.userIdx); err == nil {
|
||||||
|
userQuote = userQuote.Add(decimal.NewFromUint64(solAmount))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var swaps []Swap
|
||||||
|
if withdrawBase > 0 || withdrawQuote > 0 {
|
||||||
|
swaps = append(swaps, Swap{
|
||||||
|
Program: SolProgramMeteoraDLMM,
|
||||||
|
Event: "remove",
|
||||||
|
Pool: pool,
|
||||||
|
BaseMint: baseMint,
|
||||||
|
QuoteMint: quoteMint,
|
||||||
|
BaseTokenProgram: baseTokenProgram,
|
||||||
|
QuoteTokenProgram: quoteTokenProgram,
|
||||||
|
BaseMintDecimals: baseDecimals,
|
||||||
|
QuoteMintDecimals: quoteDecimals,
|
||||||
|
User: eventUser,
|
||||||
|
BaseAmount: decimal.NewFromUint64(withdrawBase),
|
||||||
|
QuoteAmount: decimal.NewFromUint64(withdrawQuote),
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
UserBaseBalance: userBase,
|
||||||
|
UserQuoteBalance: userQuote,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
StartBinId: event.OldMinBinId,
|
||||||
|
EndBinId: event.OldMaxBinId,
|
||||||
|
BinChanges: dlmmBinChangesFromRange(event.OldMinBinId, event.OldMaxBinId, 0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if addBase > 0 || addQuote > 0 {
|
||||||
|
swaps = append(swaps, Swap{
|
||||||
|
Program: SolProgramMeteoraDLMM,
|
||||||
|
Event: "add",
|
||||||
|
Pool: pool,
|
||||||
|
BaseMint: baseMint,
|
||||||
|
QuoteMint: quoteMint,
|
||||||
|
BaseTokenProgram: baseTokenProgram,
|
||||||
|
QuoteTokenProgram: quoteTokenProgram,
|
||||||
|
BaseMintDecimals: baseDecimals,
|
||||||
|
QuoteMintDecimals: quoteDecimals,
|
||||||
|
User: eventUser,
|
||||||
|
BaseAmount: decimal.NewFromUint64(addBase),
|
||||||
|
QuoteAmount: decimal.NewFromUint64(addQuote),
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
UserBaseBalance: userBase,
|
||||||
|
UserQuoteBalance: userQuote,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
StartBinId: event.NewMinBinId,
|
||||||
|
EndBinId: event.NewMaxBinId,
|
||||||
|
BinChanges: dlmmBinChangesFromRange(event.NewMinBinId, event.NewMaxBinId, 0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if len(swaps) == 0 {
|
||||||
|
return nil, offset, InstructionIgnoredError
|
||||||
|
}
|
||||||
|
|
||||||
|
return swaps, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
func dlmmSelectBaseQuote(tokenX, tokenY solana.PublicKey) (baseMint solana.PublicKey, quoteMint solana.PublicKey, baseIsX bool) {
|
func dlmmSelectBaseQuote(tokenX, tokenY solana.PublicKey) (baseMint solana.PublicKey, quoteMint solana.PublicKey, baseIsX bool) {
|
||||||
priority := []solana.PublicKey{wSolMint, usdcMint, usd1Mint}
|
priority := []solana.PublicKey{wSolMint, usdcMint, usd1Mint}
|
||||||
for _, mint := range priority {
|
for _, mint := range priority {
|
||||||
@@ -887,6 +1204,68 @@ func dlmmRemoveLiquidityEventFromInnerInstructions(innerInstructions InnerInstru
|
|||||||
return dlmmRemoveLiquidityEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity event not found, offset, %d, %d", offset[0], prefixLen)
|
return dlmmRemoveLiquidityEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity event not found, offset, %d, %d", offset[0], prefixLen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dlmmClaimFeeEventFromInnerInstructions(innerInstructions InnerInstructions, instruction Instruction, offset [2]uint) (dlmmClaimFeeInnerEvent, [2]uint, error) {
|
||||||
|
var prefixLen = offset[1]
|
||||||
|
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
||||||
|
if err != nil {
|
||||||
|
return dlmmClaimFeeInnerEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm claim fee get inner instructions error: %v, offset, %d, %d", err, offset[0], prefixLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
found bool
|
||||||
|
event dlmmClaimFeeInnerEvent
|
||||||
|
matchedIdx int
|
||||||
|
)
|
||||||
|
for innerIndex, innerInstr := range inners {
|
||||||
|
if innerInstr.ProgramIDIndex != instruction.ProgramIDIndex {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
decoded, ok := dlmmDecodeClaimFeeEvent(innerInstr.Data)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
found = true
|
||||||
|
event = decoded
|
||||||
|
matchedIdx = innerIndex
|
||||||
|
if decoded.HasActiveBin {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return dlmmClaimFeeInnerEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm claim fee event not found, offset, %d, %d", offset[0], prefixLen)
|
||||||
|
}
|
||||||
|
if offset[1] == 0 {
|
||||||
|
offset[0] += 1
|
||||||
|
} else {
|
||||||
|
offset[1] = uint(matchedIdx) + 1 + prefixLen
|
||||||
|
}
|
||||||
|
return event, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dlmmRebalancingEventFromInnerInstructions(innerInstructions InnerInstructions, instruction Instruction, offset [2]uint) (dlmmRebalancingEvent, [2]uint, error) {
|
||||||
|
var prefixLen = offset[1]
|
||||||
|
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
||||||
|
if err != nil {
|
||||||
|
return dlmmRebalancingEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm rebalance liquidity get inner instructions error: %v, offset, %d, %d", err, offset[0], prefixLen)
|
||||||
|
}
|
||||||
|
for innerIndex, innerInstr := range inners {
|
||||||
|
if innerInstr.ProgramIDIndex != instruction.ProgramIDIndex {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event, ok := dlmmDecodeRebalancingEvent(innerInstr.Data)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if offset[1] == 0 {
|
||||||
|
offset[0] += 1
|
||||||
|
} else {
|
||||||
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
||||||
|
}
|
||||||
|
return event, offset, nil
|
||||||
|
}
|
||||||
|
return dlmmRebalancingEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm rebalance liquidity event not found, offset, %d, %d", offset[0], prefixLen)
|
||||||
|
}
|
||||||
|
|
||||||
func dlmmDecodeAddLiquidityEvent(data []byte) (dlmmAddLiquidityEvent, bool) {
|
func dlmmDecodeAddLiquidityEvent(data []byte) (dlmmAddLiquidityEvent, bool) {
|
||||||
switch {
|
switch {
|
||||||
case len(data) >= 8 && bytes.Equal(data[:8], meteoraDlmmAddLiquidityEventDiscriminator[:]):
|
case len(data) >= 8 && bytes.Equal(data[:8], meteoraDlmmAddLiquidityEventDiscriminator[:]):
|
||||||
@@ -929,6 +1308,64 @@ func dlmmDecodeRemoveLiquidityEvent(data []byte) (dlmmRemoveLiquidityEvent, bool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dlmmDecodeClaimFeeEvent(data []byte) (dlmmClaimFeeInnerEvent, bool) {
|
||||||
|
switch {
|
||||||
|
case len(data) >= 16 &&
|
||||||
|
bytes.Equal(data[:8], eventDiscriminator[:]) &&
|
||||||
|
bytes.Equal(data[8:16], meteoraDlmmClaimFee2EventDiscriminator[:]):
|
||||||
|
var event dlmmClaimFee2Event
|
||||||
|
if err := agbinary.NewBorshDecoder(data[16:]).Decode(&event); err != nil {
|
||||||
|
return dlmmClaimFeeInnerEvent{}, false
|
||||||
|
}
|
||||||
|
return dlmmClaimFeeInnerEvent{
|
||||||
|
LbPair: event.LbPair,
|
||||||
|
Position: event.Position,
|
||||||
|
Owner: event.Owner,
|
||||||
|
FeeX: event.FeeX,
|
||||||
|
FeeY: event.FeeY,
|
||||||
|
ActiveBinId: event.ActiveBinId,
|
||||||
|
HasActiveBin: true,
|
||||||
|
}, true
|
||||||
|
case len(data) >= 16 &&
|
||||||
|
bytes.Equal(data[:8], eventDiscriminator[:]) &&
|
||||||
|
bytes.Equal(data[8:16], meteoraDlmmClaimFeeEventDiscriminator[:]):
|
||||||
|
var event dlmmClaimFeeEvent
|
||||||
|
if err := agbinary.NewBorshDecoder(data[16:]).Decode(&event); err != nil {
|
||||||
|
return dlmmClaimFeeInnerEvent{}, false
|
||||||
|
}
|
||||||
|
return dlmmClaimFeeInnerEvent{
|
||||||
|
LbPair: event.LbPair,
|
||||||
|
Position: event.Position,
|
||||||
|
Owner: event.Owner,
|
||||||
|
FeeX: event.FeeX,
|
||||||
|
FeeY: event.FeeY,
|
||||||
|
}, true
|
||||||
|
default:
|
||||||
|
return dlmmClaimFeeInnerEvent{}, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dlmmDecodeRebalancingEvent(data []byte) (dlmmRebalancingEvent, bool) {
|
||||||
|
switch {
|
||||||
|
case len(data) >= 8 && bytes.Equal(data[:8], meteoraDlmmRebalancingEventDiscriminator[:]):
|
||||||
|
var event dlmmRebalancingEvent
|
||||||
|
if err := agbinary.NewBorshDecoder(data[8:]).Decode(&event); err != nil {
|
||||||
|
return dlmmRebalancingEvent{}, false
|
||||||
|
}
|
||||||
|
return event, true
|
||||||
|
case len(data) >= 16 &&
|
||||||
|
bytes.Equal(data[:8], eventDiscriminator[:]) &&
|
||||||
|
bytes.Equal(data[8:16], meteoraDlmmRebalancingEventDiscriminator[:]):
|
||||||
|
var event dlmmRebalancingEvent
|
||||||
|
if err := agbinary.NewBorshDecoder(data[16:]).Decode(&event); err != nil {
|
||||||
|
return dlmmRebalancingEvent{}, false
|
||||||
|
}
|
||||||
|
return event, true
|
||||||
|
default:
|
||||||
|
return dlmmRebalancingEvent{}, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func resolveDlmmSwapAccounts(result *RawTx, accounts []int) (dlmmSwapAccounts, error) {
|
func resolveDlmmSwapAccounts(result *RawTx, accounts []int) (dlmmSwapAccounts, error) {
|
||||||
if len(accounts) < 13 {
|
if len(accounts) < 13 {
|
||||||
return dlmmSwapAccounts{}, fmt.Errorf("accounts too short, expected at least 13")
|
return dlmmSwapAccounts{}, fmt.Errorf("accounts too short, expected at least 13")
|
||||||
@@ -1046,6 +1483,93 @@ func resolveDlmmLiquidityAccounts(result *RawTx, accounts []int) (dlmmLiquidityA
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resolveDlmmClaimFeeAccounts(result *RawTx, data []byte, accounts []int) (dlmmLiquidityAccounts, error) {
|
||||||
|
if len(data) < 8 {
|
||||||
|
return dlmmLiquidityAccounts{}, fmt.Errorf("instruction data too short")
|
||||||
|
}
|
||||||
|
discriminator := *(*[8]byte)(data[:8])
|
||||||
|
accountList := result.accountList
|
||||||
|
|
||||||
|
switch discriminator {
|
||||||
|
case meteoraDlmmClaimFee2Discriminator:
|
||||||
|
if len(accounts) < 14 {
|
||||||
|
return dlmmLiquidityAccounts{}, fmt.Errorf("accounts too short, expected at least 14")
|
||||||
|
}
|
||||||
|
if !accountList[accounts[12]].Equals(meteoraDlmmEventAuthority) {
|
||||||
|
return dlmmLiquidityAccounts{}, fmt.Errorf("event authority mismatch")
|
||||||
|
}
|
||||||
|
if !accountList[accounts[13]].Equals(meteoraDlmmProgram) {
|
||||||
|
return dlmmLiquidityAccounts{}, fmt.Errorf("program id mismatch")
|
||||||
|
}
|
||||||
|
return dlmmLiquidityAccounts{
|
||||||
|
positionIdx: accounts[1],
|
||||||
|
poolIdx: accounts[0],
|
||||||
|
userTokenXIdx: accounts[5],
|
||||||
|
userTokenYIdx: accounts[6],
|
||||||
|
reserveXIdx: accounts[3],
|
||||||
|
reserveYIdx: accounts[4],
|
||||||
|
tokenXMintIdx: accounts[7],
|
||||||
|
tokenYMintIdx: accounts[8],
|
||||||
|
userIdx: accounts[2],
|
||||||
|
tokenXProgramIdx: accounts[9],
|
||||||
|
tokenYProgramIdx: accounts[10],
|
||||||
|
}, nil
|
||||||
|
case meteoraDlmmClaimFeeDiscriminator:
|
||||||
|
if len(accounts) < 14 {
|
||||||
|
return dlmmLiquidityAccounts{}, fmt.Errorf("accounts too short, expected at least 14")
|
||||||
|
}
|
||||||
|
if !accountList[accounts[12]].Equals(meteoraDlmmEventAuthority) {
|
||||||
|
return dlmmLiquidityAccounts{}, fmt.Errorf("event authority mismatch")
|
||||||
|
}
|
||||||
|
if !accountList[accounts[13]].Equals(meteoraDlmmProgram) {
|
||||||
|
return dlmmLiquidityAccounts{}, fmt.Errorf("program id mismatch")
|
||||||
|
}
|
||||||
|
return dlmmLiquidityAccounts{
|
||||||
|
positionIdx: accounts[1],
|
||||||
|
poolIdx: accounts[0],
|
||||||
|
userTokenXIdx: accounts[7],
|
||||||
|
userTokenYIdx: accounts[8],
|
||||||
|
reserveXIdx: accounts[5],
|
||||||
|
reserveYIdx: accounts[6],
|
||||||
|
tokenXMintIdx: accounts[9],
|
||||||
|
tokenYMintIdx: accounts[10],
|
||||||
|
userIdx: accounts[4],
|
||||||
|
tokenXProgramIdx: accounts[11],
|
||||||
|
tokenYProgramIdx: accounts[11],
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
return dlmmLiquidityAccounts{}, fmt.Errorf("unsupported claim fee discriminator")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveDlmmRebalanceAccounts(result *RawTx, accounts []int) (dlmmLiquidityAccounts, error) {
|
||||||
|
if len(accounts) < 17 {
|
||||||
|
return dlmmLiquidityAccounts{}, fmt.Errorf("accounts too short, expected at least 17")
|
||||||
|
}
|
||||||
|
accountList := result.accountList
|
||||||
|
|
||||||
|
if !accountList[accounts[15]].Equals(meteoraDlmmEventAuthority) {
|
||||||
|
return dlmmLiquidityAccounts{}, fmt.Errorf("event authority mismatch")
|
||||||
|
}
|
||||||
|
if !accountList[accounts[16]].Equals(meteoraDlmmProgram) {
|
||||||
|
return dlmmLiquidityAccounts{}, fmt.Errorf("program id mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
return dlmmLiquidityAccounts{
|
||||||
|
positionIdx: accounts[0],
|
||||||
|
poolIdx: accounts[1],
|
||||||
|
userTokenXIdx: accounts[3],
|
||||||
|
userTokenYIdx: accounts[4],
|
||||||
|
reserveXIdx: accounts[5],
|
||||||
|
reserveYIdx: accounts[6],
|
||||||
|
tokenXMintIdx: accounts[7],
|
||||||
|
tokenYMintIdx: accounts[8],
|
||||||
|
userIdx: accounts[9],
|
||||||
|
tokenXProgramIdx: accounts[11],
|
||||||
|
tokenYProgramIdx: accounts[12],
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func dlmmTokenDecimals(result *RawTx, accountIndex int) (uint8, bool) {
|
func dlmmTokenDecimals(result *RawTx, accountIndex int) (uint8, bool) {
|
||||||
for _, meta := range result.Meta.PostTokenBalances {
|
for _, meta := range result.Meta.PostTokenBalances {
|
||||||
if meta.AccountIndex == accountIndex {
|
if meta.AccountIndex == accountIndex {
|
||||||
|
|||||||
69
parser.go
69
parser.go
@@ -2,7 +2,6 @@ package pump_parser
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"github.com/gagliardetto/solana-go"
|
"github.com/gagliardetto/solana-go"
|
||||||
@@ -14,6 +13,11 @@ var defaultSwapPrograms = map[solana.PublicKey]swapParser{
|
|||||||
pumpProgram: pumpParser,
|
pumpProgram: pumpParser,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errTxParserPrograms = map[solana.PublicKey]swapParser{
|
||||||
|
pumpAmmProgram: pumpAmmParser,
|
||||||
|
pumpProgram: pumpParser,
|
||||||
|
}
|
||||||
|
|
||||||
var swapPrograms = cloneSwapPrograms(defaultSwapPrograms)
|
var swapPrograms = cloneSwapPrograms(defaultSwapPrograms)
|
||||||
|
|
||||||
type ParserOption func(*parserConfig)
|
type ParserOption func(*parserConfig)
|
||||||
@@ -56,8 +60,9 @@ func WithMeteoraDlmm() ParserOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var actionPrograms = map[solana.PublicKey]actionParser{
|
var actionPrograms = map[solana.PublicKey]actionParser{
|
||||||
systemProgram: systemParser,
|
systemProgram: systemParser,
|
||||||
budgGetProgram: budgetParser,
|
budgGetProgram: budgetParser,
|
||||||
|
chainLinkProgram: chainLinkParser,
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseRawTx(rawTx *RawTx) (*Tx, error) {
|
func ParseRawTx(rawTx *RawTx) (*Tx, error) {
|
||||||
@@ -91,11 +96,55 @@ func (tx *Tx) Parser() error {
|
|||||||
tx.BlockAt = tx.rawTx.BlockTime
|
tx.BlockAt = tx.rawTx.BlockTime
|
||||||
tx.CuFee = decimal.NewFromUint64(tx.rawTx.Meta.Fee)
|
tx.CuFee = decimal.NewFromUint64(tx.rawTx.Meta.Fee)
|
||||||
|
|
||||||
|
tx.ComputeUnitsConsumed = tx.rawTx.Meta.ComputeUnitsConsumed
|
||||||
tx.BeforeSolBalance = decimal.NewFromUint64(tx.rawTx.Meta.PreBalances[0]).Div(decimal.NewFromInt(1e9))
|
tx.BeforeSolBalance = decimal.NewFromUint64(tx.rawTx.Meta.PreBalances[0]).Div(decimal.NewFromInt(1e9))
|
||||||
tx.AfterSOLBalance = decimal.NewFromUint64(tx.rawTx.Meta.PostBalances[0]).Div(decimal.NewFromInt(1e9))
|
tx.AfterSOLBalance = decimal.NewFromUint64(tx.rawTx.Meta.PostBalances[0]).Div(decimal.NewFromInt(1e9))
|
||||||
|
|
||||||
tx.Token = make(map[solana.PublicKey]TokenMeta)
|
tx.Token = make(map[solana.PublicKey]TokenMeta)
|
||||||
|
|
||||||
|
if tx.rawTx.Meta.Err != nil {
|
||||||
|
tx.Err = tx.rawTx.Meta.Err
|
||||||
|
if tx.Err.UnKnown != "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(tx.rawTx.Transaction.Message.Instructions) <= int(tx.Err.Index) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
programIdx := tx.rawTx.Transaction.Message.Instructions[tx.Err.Index].ProgramIDIndex
|
||||||
|
if len(accountList) <= programIdx {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
programAccount := accountList[programIdx]
|
||||||
|
parserFunc, exists := errTxParserPrograms[programAccount]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// parse failed tx
|
||||||
|
swaps, _, err := parserFunc(tx, tx.rawTx.Transaction.Message.Instructions[tx.Err.Index], InnerInstructions{}, [2]uint{uint(tx.Err.Index), uint(0)})
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
//fmt.Printf("parser failed tx error: %s, block: %d tx: %s\n", err, tx.Block, tx.GetTxHash())
|
||||||
|
}
|
||||||
|
if len(swaps) > 0 {
|
||||||
|
for i := range swaps {
|
||||||
|
swaps[i].InstrIdx = tx.Err.Index
|
||||||
|
}
|
||||||
|
tx.Swaps = swaps
|
||||||
|
}
|
||||||
|
for i, instr := range tx.rawTx.Transaction.Message.Instructions {
|
||||||
|
if p, exists := actionPrograms[accountList[instr.ProgramIDIndex]]; exists {
|
||||||
|
_, err := p(tx, instr, InnerInstructions{}, [2]uint{uint(i), uint(0)})
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, InstructionIgnoredError) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
var innersMap = make(map[int]InnerInstructions)
|
var innersMap = make(map[int]InnerInstructions)
|
||||||
for _, inner := range tx.rawTx.Meta.InnerInstructions {
|
for _, inner := range tx.rawTx.Meta.InnerInstructions {
|
||||||
innersMap[inner.Index] = inner
|
innersMap[inner.Index] = inner
|
||||||
@@ -126,6 +175,7 @@ func (tx *Tx) Parser() error {
|
|||||||
swap.AfterSOLBalance = decimal.NewFromUint64(tx.rawTx.Meta.PostBalances[userIdx]).Div(decimal.NewFromInt(1e9))
|
swap.AfterSOLBalance = decimal.NewFromUint64(tx.rawTx.Meta.PostBalances[userIdx]).Div(decimal.NewFromInt(1e9))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
swap.InstrIdx = uint8(i)
|
||||||
tx.Swaps = append(tx.Swaps, swap)
|
tx.Swaps = append(tx.Swaps, swap)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +193,7 @@ func (tx *Tx) Parser() error {
|
|||||||
innerLength := len(innersMap[i].Instructions)
|
innerLength := len(innersMap[i].Instructions)
|
||||||
for j := 1; j <= innerLength; {
|
for j := 1; j <= innerLength; {
|
||||||
if j <= 0 || j > innerLength {
|
if j <= 0 || j > innerLength {
|
||||||
log.Printf("inner instruction index is out if range, block: %d, tx: %s, outerIndex: %d, innerIndex: %d", tx.Block, tx.GetTxHash(), ii, j)
|
//log.Printf("inner instruction index is out if range, block: %d, tx: %s, outerIndex: %d, innerIndex: %d", tx.Block, tx.GetTxHash(), ii, j)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
innerInstr := innersMap[i].Instructions[j-1]
|
innerInstr := innersMap[i].Instructions[j-1]
|
||||||
@@ -173,11 +223,18 @@ func (tx *Tx) Parser() error {
|
|||||||
swap.AfterSOLBalance = decimal.NewFromUint64(tx.rawTx.Meta.PostBalances[userIdx]).Div(decimal.NewFromInt(1e9))
|
swap.AfterSOLBalance = decimal.NewFromUint64(tx.rawTx.Meta.PostBalances[userIdx]).Div(decimal.NewFromInt(1e9))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
swap.InstrIdx = uint8(i)
|
||||||
|
swap.InnerIdx = uint8(j)
|
||||||
tx.Swaps = append(tx.Swaps, swap)
|
tx.Swaps = append(tx.Swaps, swap)
|
||||||
}
|
}
|
||||||
// tx.Swaps = append(tx.Swaps, swaps...)
|
// tx.Swaps = append(tx.Swaps, swaps...)
|
||||||
j = int(offset[1])
|
if ii == int(offset[0]) && j == int(offset[1]) {
|
||||||
ii = int(offset[0])
|
j = j + 1
|
||||||
|
} else {
|
||||||
|
j = int(offset[1])
|
||||||
|
ii = int(offset[0])
|
||||||
|
}
|
||||||
|
|
||||||
} else if p, exists := actionPrograms[innerProgramAccount]; exists {
|
} else if p, exists := actionPrograms[innerProgramAccount]; exists {
|
||||||
offset, err := p(tx, innerInstr, innersMap[i], [2]uint{uint(i), uint(j)})
|
offset, err := p(tx, innerInstr, innersMap[i], [2]uint{uint(i), uint(j)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
143
pump.go
143
pump.go
@@ -34,10 +34,19 @@ func pumpParser(tx *Tx, instruction Instruction, innerInstructions InnerInstruct
|
|||||||
|
|
||||||
switch discriminator {
|
switch discriminator {
|
||||||
case pumpBuyV2Discriminator, pumpBuyDiscriminator, pumpSellDiscriminator:
|
case pumpBuyV2Discriminator, pumpBuyDiscriminator, pumpSellDiscriminator:
|
||||||
|
if tx.Err != nil {
|
||||||
|
return failedTxBuyOrSellParser(tx, instruction, innerInstructions, offset)
|
||||||
|
}
|
||||||
return BuyOrSellParser(tx, instruction, innerInstructions, offset)
|
return BuyOrSellParser(tx, instruction, innerInstructions, offset)
|
||||||
case pumpCreateDiscriminator, pumpCreateV2Discriminator:
|
case pumpCreateDiscriminator, pumpCreateV2Discriminator:
|
||||||
|
if tx.Err != nil {
|
||||||
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
||||||
|
}
|
||||||
return CreateParser(tx, instruction, innerInstructions, offset)
|
return CreateParser(tx, instruction, innerInstructions, offset)
|
||||||
case pumpMigrateDiscriminator:
|
case pumpMigrateDiscriminator:
|
||||||
|
if tx.Err != nil {
|
||||||
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
||||||
|
}
|
||||||
return MigrateParser(tx, instruction, innerInstructions, offset)
|
return MigrateParser(tx, instruction, innerInstructions, offset)
|
||||||
default:
|
default:
|
||||||
return nil, increaseOffset(offset), InstructionIgnoredError
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
||||||
@@ -79,6 +88,7 @@ type PumpCreateEvent struct {
|
|||||||
TokenTotalSupply uint64
|
TokenTotalSupply uint64
|
||||||
TokenProgram solana.PublicKey
|
TokenProgram solana.PublicKey
|
||||||
IsMayhemMode bool
|
IsMayhemMode bool
|
||||||
|
IsCashbackEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateParser(tx *Tx, instr Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
func CreateParser(tx *Tx, instr Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
@@ -148,6 +158,7 @@ func CreateParser(tx *Tx, instr Instruction, innerInstructions InnerInstructions
|
|||||||
BaseReserve: decimal.NewFromUint64(createEvent.RealTokenReserves),
|
BaseReserve: decimal.NewFromUint64(createEvent.RealTokenReserves),
|
||||||
QuoteReserve: decimal.Zero,
|
QuoteReserve: decimal.Zero,
|
||||||
Mayhem: createEvent.IsMayhemMode,
|
Mayhem: createEvent.IsMayhemMode,
|
||||||
|
Cashback: createEvent.IsCashbackEnabled,
|
||||||
UserBaseBalance: userBase,
|
UserBaseBalance: userBase,
|
||||||
UserQuoteBalance: decimal.NewFromUint64(userQuote),
|
UserQuoteBalance: decimal.NewFromUint64(userQuote),
|
||||||
EntryContract: entryContract,
|
EntryContract: entryContract,
|
||||||
@@ -176,6 +187,16 @@ type PumpTradeEvent struct {
|
|||||||
|
|
||||||
CreatorFeeBasisPoints uint64
|
CreatorFeeBasisPoints uint64
|
||||||
CreatorFee uint64
|
CreatorFee uint64
|
||||||
|
|
||||||
|
TrackVolume bool
|
||||||
|
TotalUnclaimedTokens uint64
|
||||||
|
TotalClaimedTokens uint64
|
||||||
|
CurrentSolVolume uint64
|
||||||
|
LastUpdateTimestamp int64
|
||||||
|
IxName string
|
||||||
|
MayhemMode bool
|
||||||
|
CashbackFeeBasisPoints uint64
|
||||||
|
Cashback uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
type PumpTradeFeeArg struct {
|
type PumpTradeFeeArg struct {
|
||||||
@@ -191,6 +212,112 @@ type CompleteEvent struct {
|
|||||||
Timestamp int64
|
Timestamp int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PumpTradeArgs struct {
|
||||||
|
Discriminator [8]byte
|
||||||
|
Amount1 uint64
|
||||||
|
Amount2 uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func failedTxBuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if tx.Err == nil || tx.Err.UnKnown != "" {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("tx pump failed but error is nil, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
if tx.Err.Variant != InstructionError {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump failed but error variant is not instruction error, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
if tx.Err.Enum != Custom && tx.Err.Enum != ComputationalBudgetExceeded && tx.Err.Enum != ProgramFailedToComplete {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump failed but error is not custom or computational budget exceeded, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
if tx.Err.Enum == Custom {
|
||||||
|
if !(tx.Err.CustomCode == 1 ||
|
||||||
|
tx.Err.CustomCode == 6042 ||
|
||||||
|
tx.Err.CustomCode == 6041 ||
|
||||||
|
tx.Err.CustomCode == 6040 ||
|
||||||
|
tx.Err.CustomCode == 6023 || tx.Err.CustomCode == 6021 || tx.Err.CustomCode == 6003 || tx.Err.CustomCode == 6002) {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump failed but custom error code is unexpected, offset, %d, %d, code: %d", offset[0], offset[1], tx.Err.CustomCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := tx.rawTx
|
||||||
|
var entryContract = result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
|
||||||
|
user := result.accountList[instruction.Accounts[6]]
|
||||||
|
ataUserIdx := instruction.Accounts[5]
|
||||||
|
userIndex := instruction.Accounts[6]
|
||||||
|
mint := result.accountList[instruction.Accounts[2]]
|
||||||
|
var args PumpTradeArgs
|
||||||
|
err := agbinary.NewBorshDecoder(instruction.Data[:]).Decode(&args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump buy/sell decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
var event string
|
||||||
|
var (
|
||||||
|
solAmount, tokenAmount uint64
|
||||||
|
)
|
||||||
|
if bytes.Equal(args.Discriminator[:], pumpBuyV2Discriminator[:]) {
|
||||||
|
event = "buy_failed"
|
||||||
|
solAmount = args.Amount1
|
||||||
|
tokenAmount = args.Amount2
|
||||||
|
} else if bytes.Equal(args.Discriminator[:], pumpBuyDiscriminator[:]) {
|
||||||
|
event = "buy_failed"
|
||||||
|
solAmount = args.Amount2
|
||||||
|
tokenAmount = args.Amount1
|
||||||
|
} else if bytes.Equal(args.Discriminator[:], pumpSellDiscriminator[:]) {
|
||||||
|
event = "sell_failed"
|
||||||
|
solAmount = args.Amount2
|
||||||
|
tokenAmount = args.Amount1
|
||||||
|
} else {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("unknown pump trade instruction discriminator, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
var baseTokenProgram solana.PublicKey
|
||||||
|
|
||||||
|
if event == "buy_failed" {
|
||||||
|
baseTokenProgram = result.accountList[instruction.Accounts[8]]
|
||||||
|
} else {
|
||||||
|
baseTokenProgram = result.accountList[instruction.Accounts[9]]
|
||||||
|
}
|
||||||
|
if !user.IsOnCurve() && (entryContract.Equals(okxDexRoutersV2) || entryContract.Equals(okxAggregatorV2)) {
|
||||||
|
userBaseAmount, ataIndex := tokenBalanceChange(result, 0, baseTokenProgram, mint)
|
||||||
|
//&& userBaseAmount.BigInt().Uint64() == tradeEvent.TokenAmount
|
||||||
|
if !userBaseAmount.IsZero() {
|
||||||
|
user = result.accountList[0]
|
||||||
|
userIndex = 0
|
||||||
|
ataUserIdx = ataIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userBase := getAccountBalanceAfterTx(result, ataUserIdx)
|
||||||
|
userQuote, _ := GetSolAfterTx(result, userIndex)
|
||||||
|
|
||||||
|
bcIdx := instruction.Accounts[3]
|
||||||
|
bcAtaIndex := instruction.Accounts[4]
|
||||||
|
solReserves, _ := GetSolAfterTx(result, bcIdx)
|
||||||
|
tokenReserves := getAccountBalanceAfterTx(result, bcAtaIndex)
|
||||||
|
swaps := []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramPump,
|
||||||
|
Event: event,
|
||||||
|
Pool: result.accountList[instruction.Accounts[3]],
|
||||||
|
BaseMint: mint,
|
||||||
|
QuoteMint: solana.PublicKey{},
|
||||||
|
BaseTokenProgram: baseTokenProgram,
|
||||||
|
QuoteTokenProgram: solana.PublicKey{},
|
||||||
|
BaseMintDecimals: 6,
|
||||||
|
QuoteMintDecimals: 9,
|
||||||
|
User: user,
|
||||||
|
BaseAmount: decimal.NewFromUint64(tokenAmount),
|
||||||
|
QuoteAmount: decimal.NewFromUint64(solAmount),
|
||||||
|
BaseReserve: tokenReserves,
|
||||||
|
QuoteReserve: decimal.NewFromUint64(solReserves),
|
||||||
|
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[1]]),
|
||||||
|
UserBaseBalance: userBase,
|
||||||
|
UserQuoteBalance: decimal.NewFromUint64(userQuote),
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return swaps, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
result := tx.rawTx
|
result := tx.rawTx
|
||||||
var entryContract = result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
var entryContract = result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
@@ -217,11 +344,13 @@ func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerIns
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, increaseOffset(offset), fmt.Errorf("pump create get inner instructions error: %v,offset, %d, %d", err, offset[0], offset[1])
|
return nil, increaseOffset(offset), fmt.Errorf("pump create get inner instructions error: %v,offset, %d, %d", err, offset[0], offset[1])
|
||||||
}
|
}
|
||||||
if instruction.StackHeight != nil && *instruction.StackHeight > 2 {
|
if !entryContract.Equals(axiomOuterContract) {
|
||||||
for _, innerInstr := range innerInstructions.Instructions {
|
if instruction.StackHeight != nil && *instruction.StackHeight > 2 {
|
||||||
if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 {
|
for _, innerInstr := range innerInstructions.Instructions {
|
||||||
entryContract = result.accountList[innerInstr.ProgramIDIndex]
|
if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 {
|
||||||
break
|
entryContract = result.accountList[innerInstr.ProgramIDIndex]
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -312,6 +441,7 @@ func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerIns
|
|||||||
solAmount = solAmount - fee
|
solAmount = solAmount - fee
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
isCashbackCoin := tradeEvent.CashbackFeeBasisPoints > 0 || tradeEvent.Cashback > 0
|
||||||
swaps := []Swap{
|
swaps := []Swap{
|
||||||
{
|
{
|
||||||
Program: SolProgramPump,
|
Program: SolProgramPump,
|
||||||
@@ -333,6 +463,7 @@ func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerIns
|
|||||||
UserBaseBalance: userBase,
|
UserBaseBalance: userBase,
|
||||||
UserQuoteBalance: decimal.NewFromUint64(userQuote),
|
UserQuoteBalance: decimal.NewFromUint64(userQuote),
|
||||||
EntryContract: entryContract,
|
EntryContract: entryContract,
|
||||||
|
Cashback: isCashbackCoin,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if completed {
|
if completed {
|
||||||
@@ -348,6 +479,8 @@ func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerIns
|
|||||||
BaseMintDecimals: 6,
|
BaseMintDecimals: 6,
|
||||||
QuoteMintDecimals: 9,
|
QuoteMintDecimals: 9,
|
||||||
User: user,
|
User: user,
|
||||||
|
BaseReserve: decimal.NewFromUint64(tradeEvent.RealTokenReserves),
|
||||||
|
QuoteReserve: decimal.NewFromUint64(tradeEvent.RealSolReserves),
|
||||||
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[1]]),
|
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[1]]),
|
||||||
UserBaseBalance: userBase,
|
UserBaseBalance: userBase,
|
||||||
UserQuoteBalance: decimal.NewFromUint64(userQuote),
|
UserQuoteBalance: decimal.NewFromUint64(userQuote),
|
||||||
|
|||||||
295
pumpamm.go
295
pumpamm.go
@@ -41,6 +41,8 @@ type ammBuyEvent struct {
|
|||||||
LastUpdateTimestamp int64
|
LastUpdateTimestamp int64
|
||||||
MinBaseAmountOut uint64
|
MinBaseAmountOut uint64
|
||||||
IxName string
|
IxName string
|
||||||
|
CashbackFeeBasisPoints uint64
|
||||||
|
Cashback uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
type ammCreatePoolEvent struct {
|
type ammCreatePoolEvent struct {
|
||||||
@@ -113,6 +115,8 @@ type ammSellEvent struct {
|
|||||||
CoinCreator solana.PublicKey
|
CoinCreator solana.PublicKey
|
||||||
CoinCreatorFeeBasisPoints uint64
|
CoinCreatorFeeBasisPoints uint64
|
||||||
CoinCreatorFee uint64
|
CoinCreatorFee uint64
|
||||||
|
CashbackFeeBasisPoints uint64
|
||||||
|
Cashback uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
type ammWithdrawEvent struct {
|
type ammWithdrawEvent struct {
|
||||||
@@ -148,14 +152,29 @@ func pumpAmmParser(tx *Tx, instruction Instruction, innerInstructions InnerInstr
|
|||||||
discriminator := *(*[8]byte)(decode[:8])
|
discriminator := *(*[8]byte)(decode[:8])
|
||||||
switch discriminator {
|
switch discriminator {
|
||||||
case pumpAmmCreateDiscriminator:
|
case pumpAmmCreateDiscriminator:
|
||||||
|
if tx.Err != nil {
|
||||||
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
||||||
|
}
|
||||||
return ammCreatePoolParser(tx, instruction, innerInstructions, offset)
|
return ammCreatePoolParser(tx, instruction, innerInstructions, offset)
|
||||||
case pumpAmmBuyDiscriminator, pumpAmmBuyV2Discriminator:
|
case pumpAmmBuyDiscriminator, pumpAmmBuyV2Discriminator:
|
||||||
|
if tx.Err != nil {
|
||||||
|
return failedTxAmmBuyParser(tx, instruction, innerInstructions, offset)
|
||||||
|
}
|
||||||
return ammBuyParser(tx, instruction, innerInstructions, offset)
|
return ammBuyParser(tx, instruction, innerInstructions, offset)
|
||||||
case pumpAmmSellDiscriminator:
|
case pumpAmmSellDiscriminator:
|
||||||
|
if tx.Err != nil {
|
||||||
|
return failedTxAmmSellParser(tx, instruction, innerInstructions, offset)
|
||||||
|
}
|
||||||
return ammSellParser(tx, instruction, innerInstructions, offset)
|
return ammSellParser(tx, instruction, innerInstructions, offset)
|
||||||
case pumpAmmDepositDiscriminator:
|
case pumpAmmDepositDiscriminator:
|
||||||
|
if tx.Err != nil {
|
||||||
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
||||||
|
}
|
||||||
return depositParse(tx, instruction, innerInstructions, offset)
|
return depositParse(tx, instruction, innerInstructions, offset)
|
||||||
case pumpAmmWithdrawDiscriminator:
|
case pumpAmmWithdrawDiscriminator:
|
||||||
|
if tx.Err != nil {
|
||||||
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
||||||
|
}
|
||||||
return withdrawParse(tx, instruction, innerInstructions, offset)
|
return withdrawParse(tx, instruction, innerInstructions, offset)
|
||||||
default:
|
default:
|
||||||
return nil, increaseOffset(offset), InstructionIgnoredError
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
||||||
@@ -236,6 +255,254 @@ func ammCreatePoolParser(tx *Tx, instruction Instruction, innerInstructions Inne
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PumpSwapArgs struct {
|
||||||
|
Discriminator [8]byte
|
||||||
|
Amount1 uint64
|
||||||
|
Amount2 uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func failedTxAmmBuyParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if tx.Err == nil || tx.Err.UnKnown != "" {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("tx pump amm sell failed but error is nil, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
if tx.Err.Variant != InstructionError {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm sell failed but error variant is not instruction error, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
if tx.Err.Enum != Custom && tx.Err.Enum != ComputationalBudgetExceeded && tx.Err.Enum != ProgramFailedToComplete {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm sell failed but error is not custom or computational budget exceeded, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
if tx.Err.Enum == Custom {
|
||||||
|
if !(tx.Err.CustomCode == 1 || tx.Err.CustomCode == 6004 ||
|
||||||
|
tx.Err.CustomCode == 6040 ||
|
||||||
|
tx.Err.CustomCode == 6039 ||
|
||||||
|
tx.Err.CustomCode == 6016 ||
|
||||||
|
tx.Err.CustomCode == 6014) {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm sell failed but custom error code is unexpected, offset, %d, %d, code: %d", offset[0], offset[1], tx.Err.CustomCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := tx.rawTx
|
||||||
|
var entryContract = result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
var args PumpSwapArgs
|
||||||
|
err := agbinary.NewBorshDecoder(instruction.Data[:]).Decode(&args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm buy failed decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
var event string
|
||||||
|
var (
|
||||||
|
quoteAmount, tokenAmount uint64
|
||||||
|
)
|
||||||
|
if bytes.Equal(args.Discriminator[:], pumpAmmBuyV2Discriminator[:]) {
|
||||||
|
event = "buy_failed"
|
||||||
|
quoteAmount = args.Amount1
|
||||||
|
tokenAmount = args.Amount2
|
||||||
|
} else if bytes.Equal(args.Discriminator[:], pumpAmmBuyDiscriminator[:]) {
|
||||||
|
event = "buy_failed"
|
||||||
|
quoteAmount = args.Amount2
|
||||||
|
tokenAmount = args.Amount1
|
||||||
|
} else {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("unknown pump amm trade instruction discriminator, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
baseMint := result.accountList[instruction.Accounts[3]]
|
||||||
|
quoteMint := result.accountList[instruction.Accounts[4]]
|
||||||
|
baseTokenProgram := result.accountList[instruction.Accounts[11]]
|
||||||
|
quoteTokenProgram := result.accountList[instruction.Accounts[12]]
|
||||||
|
|
||||||
|
poolBaseAccountIdx := instruction.Accounts[7]
|
||||||
|
poolQuoteAccountIdx := instruction.Accounts[8]
|
||||||
|
var (
|
||||||
|
baseMintDecimals uint8
|
||||||
|
quoteMintDecimals uint8
|
||||||
|
)
|
||||||
|
for _, meta := range result.Meta.PostTokenBalances {
|
||||||
|
if meta.AccountIndex == poolBaseAccountIdx {
|
||||||
|
baseMintDecimals = uint8(meta.UITokenAmount.Decimals)
|
||||||
|
} else if meta.AccountIndex == poolQuoteAccountIdx {
|
||||||
|
quoteMintDecimals = uint8(meta.UITokenAmount.Decimals)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, exists := tx.Token[baseMint]; !exists && !baseMint.Equals(wSolMint) {
|
||||||
|
tx.Token[baseMint] = TokenMeta{
|
||||||
|
Mint: baseMint,
|
||||||
|
Decimals: baseMintDecimals,
|
||||||
|
TokenProgram: baseTokenProgram,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := tx.Token[quoteMint]; !exists && !quoteMint.Equals(wSolMint) {
|
||||||
|
tx.Token[quoteMint] = TokenMeta{
|
||||||
|
Mint: quoteMint,
|
||||||
|
Decimals: quoteMintDecimals,
|
||||||
|
TokenProgram: quoteTokenProgram,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var eventUser = tx.rawTx.accountList[instruction.Accounts[1]]
|
||||||
|
|
||||||
|
baseMintAtaUserIdx := instruction.Accounts[5]
|
||||||
|
userIndex := instruction.Accounts[1]
|
||||||
|
if !eventUser.IsOnCurve() && (entryContract.Equals(okxDexRoutersV2) || entryContract.Equals(okxAggregatorV2)) {
|
||||||
|
userBaseAmount, ataIndex := tokenBalanceChange(result, 0, baseTokenProgram, baseMint)
|
||||||
|
// && userBaseAmount.BigInt().Uint64() == event.BaseAmountOut
|
||||||
|
if !userBaseAmount.IsZero() {
|
||||||
|
eventUser = result.accountList[0]
|
||||||
|
userIndex = 0
|
||||||
|
baseMintAtaUserIdx = ataIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userBase := getAccountBalanceAfterTx(result, baseMintAtaUserIdx)
|
||||||
|
userQuote := GetTokenBalanceAfterTx(result, userIndex, quoteTokenProgram, quoteMint)
|
||||||
|
|
||||||
|
if quoteMint.Equals(wSolMint) {
|
||||||
|
userBalance, _ := GetSolAfterTx(result, userIndex)
|
||||||
|
userQuote = userQuote.Add(decimal.NewFromUint64(userBalance))
|
||||||
|
}
|
||||||
|
baseReserve := getAccountBalanceAfterTx(result, instruction.Accounts[7])
|
||||||
|
quoteReserve := getAccountBalanceAfterTx(result, instruction.Accounts[8])
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramPumpAMM,
|
||||||
|
Event: event,
|
||||||
|
Pool: tx.rawTx.accountList[instruction.Accounts[0]],
|
||||||
|
BaseMint: baseMint,
|
||||||
|
QuoteMint: quoteMint,
|
||||||
|
BaseTokenProgram: baseTokenProgram,
|
||||||
|
QuoteTokenProgram: quoteTokenProgram,
|
||||||
|
BaseMintDecimals: baseMintDecimals,
|
||||||
|
QuoteMintDecimals: quoteMintDecimals,
|
||||||
|
User: eventUser,
|
||||||
|
BaseAmount: decimal.NewFromUint64(tokenAmount),
|
||||||
|
QuoteAmount: decimal.NewFromUint64(quoteAmount),
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[9]]),
|
||||||
|
UserBaseBalance: userBase,
|
||||||
|
UserQuoteBalance: userQuote,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func failedTxAmmSellParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if tx.Err == nil || tx.Err.UnKnown != "" {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("tx pump amm sell failed but error is nil, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
if tx.Err.Variant != InstructionError {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm sell failed but error variant is not instruction error, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
if tx.Err.Enum != Custom && tx.Err.Enum != ComputationalBudgetExceeded && tx.Err.Enum != ProgramFailedToComplete {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm sell failed but error is not custom or computational budget exceeded, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
if tx.Err.Enum == Custom {
|
||||||
|
if !(tx.Err.CustomCode == 1 || tx.Err.CustomCode == 6004 ||
|
||||||
|
tx.Err.CustomCode == 6040 ||
|
||||||
|
tx.Err.CustomCode == 6039 ||
|
||||||
|
tx.Err.CustomCode == 6016 ||
|
||||||
|
tx.Err.CustomCode == 6014) {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm sell failed but custom error code is unexpected, offset, %d, %d, code: %d", offset[0], offset[1], tx.Err.CustomCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result := tx.rawTx
|
||||||
|
var entryContract = result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
var args PumpSwapArgs
|
||||||
|
err := agbinary.NewBorshDecoder(instruction.Data[:]).Decode(&args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm buy failed decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
var event string
|
||||||
|
var (
|
||||||
|
quoteAmount, tokenAmount uint64
|
||||||
|
)
|
||||||
|
if bytes.Equal(args.Discriminator[:], pumpAmmSellDiscriminator[:]) {
|
||||||
|
event = "sell_failed"
|
||||||
|
tokenAmount = args.Amount1
|
||||||
|
quoteAmount = args.Amount2
|
||||||
|
} else {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("unknown pump amm trade instruction discriminator, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
baseMint := result.accountList[instruction.Accounts[3]]
|
||||||
|
quoteMint := result.accountList[instruction.Accounts[4]]
|
||||||
|
baseTokenProgram := result.accountList[instruction.Accounts[11]]
|
||||||
|
quoteTokenProgram := result.accountList[instruction.Accounts[12]]
|
||||||
|
|
||||||
|
poolBaseAccountIdx := instruction.Accounts[7]
|
||||||
|
poolQuoteAccountIdx := instruction.Accounts[8]
|
||||||
|
var (
|
||||||
|
baseMintDecimals uint8
|
||||||
|
quoteMintDecimals uint8
|
||||||
|
)
|
||||||
|
for _, meta := range result.Meta.PostTokenBalances {
|
||||||
|
if meta.AccountIndex == poolBaseAccountIdx {
|
||||||
|
baseMintDecimals = uint8(meta.UITokenAmount.Decimals)
|
||||||
|
} else if meta.AccountIndex == poolQuoteAccountIdx {
|
||||||
|
quoteMintDecimals = uint8(meta.UITokenAmount.Decimals)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, exists := tx.Token[baseMint]; !exists && !baseMint.Equals(wSolMint) {
|
||||||
|
tx.Token[baseMint] = TokenMeta{
|
||||||
|
Mint: baseMint,
|
||||||
|
Decimals: baseMintDecimals,
|
||||||
|
TokenProgram: baseTokenProgram,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := tx.Token[quoteMint]; !exists && !quoteMint.Equals(wSolMint) {
|
||||||
|
tx.Token[quoteMint] = TokenMeta{
|
||||||
|
Mint: quoteMint,
|
||||||
|
Decimals: quoteMintDecimals,
|
||||||
|
TokenProgram: quoteTokenProgram,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var eventUser = tx.rawTx.accountList[instruction.Accounts[1]]
|
||||||
|
|
||||||
|
baseMintAtaUserIdx := instruction.Accounts[5]
|
||||||
|
userIndex := instruction.Accounts[1]
|
||||||
|
if !eventUser.IsOnCurve() && (entryContract.Equals(okxDexRoutersV2) || entryContract.Equals(okxAggregatorV2)) {
|
||||||
|
userBaseAmount, ataIndex := tokenBalanceChange(result, 0, baseTokenProgram, baseMint)
|
||||||
|
// && userBaseAmount.BigInt().Uint64() == event.BaseAmountIn
|
||||||
|
if !userBaseAmount.IsZero() {
|
||||||
|
eventUser = result.accountList[0]
|
||||||
|
userIndex = 0
|
||||||
|
baseMintAtaUserIdx = ataIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userBase := getAccountBalanceAfterTx(result, baseMintAtaUserIdx)
|
||||||
|
userQuote := GetTokenBalanceAfterTx(result, userIndex, quoteTokenProgram, quoteMint)
|
||||||
|
|
||||||
|
if quoteMint.Equals(wSolMint) {
|
||||||
|
userBalance, _ := GetSolAfterTx(result, userIndex)
|
||||||
|
userQuote = userQuote.Add(decimal.NewFromUint64(userBalance))
|
||||||
|
}
|
||||||
|
baseReserve := getAccountBalanceAfterTx(result, instruction.Accounts[7])
|
||||||
|
quoteReserve := getAccountBalanceAfterTx(result, instruction.Accounts[8])
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramPumpAMM,
|
||||||
|
Event: event,
|
||||||
|
Pool: tx.rawTx.accountList[instruction.Accounts[0]],
|
||||||
|
BaseMint: baseMint,
|
||||||
|
QuoteMint: quoteMint,
|
||||||
|
BaseTokenProgram: baseTokenProgram,
|
||||||
|
QuoteTokenProgram: quoteTokenProgram,
|
||||||
|
BaseMintDecimals: baseMintDecimals,
|
||||||
|
QuoteMintDecimals: quoteMintDecimals,
|
||||||
|
User: eventUser,
|
||||||
|
BaseAmount: decimal.NewFromUint64(tokenAmount),
|
||||||
|
QuoteAmount: decimal.NewFromUint64(quoteAmount),
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[9]]),
|
||||||
|
UserBaseBalance: userBase,
|
||||||
|
UserQuoteBalance: userQuote,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
func ammBuyParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
func ammBuyParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
result := tx.rawTx
|
result := tx.rawTx
|
||||||
var entryContract = result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
var entryContract = result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
@@ -245,11 +512,13 @@ func ammBuyParser(tx *Tx, instruction Instruction, innerInstructions InnerInstru
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, increaseOffset(offset), fmt.Errorf("pumpamm create get inner instructions error: %v, offset: %d, %d", err, offset[0], prefixLen)
|
return nil, increaseOffset(offset), fmt.Errorf("pumpamm create get inner instructions error: %v, offset: %d, %d", err, offset[0], prefixLen)
|
||||||
}
|
}
|
||||||
if instruction.StackHeight != nil && *instruction.StackHeight > 2 {
|
if !entryContract.Equals(axiomOuterContract) {
|
||||||
for _, innerInstr := range innerInstructions.Instructions {
|
if instruction.StackHeight != nil && *instruction.StackHeight > 2 {
|
||||||
if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 {
|
for _, innerInstr := range innerInstructions.Instructions {
|
||||||
entryContract = result.accountList[innerInstr.ProgramIDIndex]
|
if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 {
|
||||||
break
|
entryContract = result.accountList[innerInstr.ProgramIDIndex]
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -329,6 +598,7 @@ func ammBuyParser(tx *Tx, instruction Instruction, innerInstructions InnerInstru
|
|||||||
userBalance, _ := GetSolAfterTx(result, userIndex)
|
userBalance, _ := GetSolAfterTx(result, userIndex)
|
||||||
userQuote = userQuote.Add(decimal.NewFromUint64(userBalance))
|
userQuote = userQuote.Add(decimal.NewFromUint64(userBalance))
|
||||||
}
|
}
|
||||||
|
isCashbackCoin := event.CashbackFeeBasisPoints > 0 || event.Cashback > 0
|
||||||
return []Swap{
|
return []Swap{
|
||||||
{
|
{
|
||||||
Program: SolProgramPumpAMM,
|
Program: SolProgramPumpAMM,
|
||||||
@@ -347,6 +617,7 @@ func ammBuyParser(tx *Tx, instruction Instruction, innerInstructions InnerInstru
|
|||||||
BaseReserve: decimal.NewFromUint64(event.PoolBaseTokenReserve - event.BaseAmountOut),
|
BaseReserve: decimal.NewFromUint64(event.PoolBaseTokenReserve - event.BaseAmountOut),
|
||||||
QuoteReserve: decimal.NewFromUint64(event.PoolQuoteTokenReserve + event.QuoteAmountIn),
|
QuoteReserve: decimal.NewFromUint64(event.PoolQuoteTokenReserve + event.QuoteAmountIn),
|
||||||
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[9]]),
|
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[9]]),
|
||||||
|
Cashback: isCashbackCoin,
|
||||||
UserBaseBalance: userBase,
|
UserBaseBalance: userBase,
|
||||||
UserQuoteBalance: userQuote,
|
UserQuoteBalance: userQuote,
|
||||||
EntryContract: entryContract,
|
EntryContract: entryContract,
|
||||||
@@ -364,11 +635,13 @@ func ammSellParser(tx *Tx, instruction Instruction, innerInstructions InnerInstr
|
|||||||
return nil, increaseOffset(offset), fmt.Errorf("pumpamm sell get inner instructions error: %v, offset: %d, %d", err, offset[0], prefixLen)
|
return nil, increaseOffset(offset), fmt.Errorf("pumpamm sell get inner instructions error: %v, offset: %d, %d", err, offset[0], prefixLen)
|
||||||
}
|
}
|
||||||
|
|
||||||
if instruction.StackHeight != nil && *instruction.StackHeight > 2 {
|
if !entryContract.Equals(axiomOuterContract) {
|
||||||
for _, innerInstr := range innerInstructions.Instructions {
|
if instruction.StackHeight != nil && *instruction.StackHeight > 2 {
|
||||||
if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 {
|
for _, innerInstr := range innerInstructions.Instructions {
|
||||||
entryContract = result.accountList[innerInstr.ProgramIDIndex]
|
if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 {
|
||||||
break
|
entryContract = result.accountList[innerInstr.ProgramIDIndex]
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -448,6 +721,7 @@ func ammSellParser(tx *Tx, instruction Instruction, innerInstructions InnerInstr
|
|||||||
userBalance, _ := GetSolAfterTx(result, userIndex)
|
userBalance, _ := GetSolAfterTx(result, userIndex)
|
||||||
userQuote = userQuote.Add(decimal.NewFromUint64(userBalance))
|
userQuote = userQuote.Add(decimal.NewFromUint64(userBalance))
|
||||||
}
|
}
|
||||||
|
isCashbackCoin := event.CashbackFeeBasisPoints > 0 || event.Cashback > 0
|
||||||
return []Swap{
|
return []Swap{
|
||||||
{
|
{
|
||||||
Program: SolProgramPumpAMM,
|
Program: SolProgramPumpAMM,
|
||||||
@@ -466,6 +740,7 @@ func ammSellParser(tx *Tx, instruction Instruction, innerInstructions InnerInstr
|
|||||||
BaseReserve: decimal.NewFromUint64(event.PoolBaseTokenReserves + event.BaseAmountIn),
|
BaseReserve: decimal.NewFromUint64(event.PoolBaseTokenReserves + event.BaseAmountIn),
|
||||||
QuoteReserve: decimal.NewFromUint64(event.PoolQuoteTokenReserves - event.QuoteAmountOut),
|
QuoteReserve: decimal.NewFromUint64(event.PoolQuoteTokenReserves - event.QuoteAmountOut),
|
||||||
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[9]]),
|
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[9]]),
|
||||||
|
Cashback: isCashbackCoin,
|
||||||
UserBaseBalance: userBase,
|
UserBaseBalance: userBase,
|
||||||
UserQuoteBalance: userQuote,
|
UserQuoteBalance: userQuote,
|
||||||
EntryContract: entryContract,
|
EntryContract: entryContract,
|
||||||
|
|||||||
107
rawtx.go
107
rawtx.go
@@ -73,6 +73,10 @@ func (tx *RawTx) GetAccountLust() []solana.PublicKey {
|
|||||||
return tx.getAccountList()
|
return tx.getAccountList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tx *RawTx) GetAccountList() []solana.PublicKey {
|
||||||
|
return tx.getAccountList()
|
||||||
|
}
|
||||||
|
|
||||||
func (tx *RawTx) TxHash() string {
|
func (tx *RawTx) TxHash() string {
|
||||||
if len(tx.Transaction.Signatures) > 0 {
|
if len(tx.Transaction.Signatures) > 0 {
|
||||||
return tx.Transaction.Signatures[0].String()
|
return tx.Transaction.Signatures[0].String()
|
||||||
@@ -146,16 +150,17 @@ func (tb *TokenBalance) ParseAccount() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Meta struct {
|
type Meta struct {
|
||||||
Err interface{} `json:"err"`
|
Err *TransactionParsedError `json:"err"`
|
||||||
Fee uint64 `json:"fee"`
|
Fee uint64 `json:"fee"`
|
||||||
InnerInstructions []InnerInstructions `json:"innerInstructions"`
|
InnerInstructions []InnerInstructions `json:"innerInstructions"`
|
||||||
LoadedAddresses LoadedAddresses `json:"loadedAddresses"`
|
LoadedAddresses LoadedAddresses `json:"loadedAddresses"`
|
||||||
LogMessages []string `json:"logMessages"`
|
LogMessages []string `json:"logMessages"`
|
||||||
PostBalances []uint64 `json:"postBalances"`
|
PostBalances []uint64 `json:"postBalances"`
|
||||||
PostTokenBalances []TokenBalance `json:"postTokenBalances"`
|
PostTokenBalances []TokenBalance `json:"postTokenBalances"`
|
||||||
PreBalances []uint64 `json:"preBalances"`
|
PreBalances []uint64 `json:"preBalances"`
|
||||||
PreTokenBalances []TokenBalance `json:"preTokenBalances"`
|
PreTokenBalances []TokenBalance `json:"preTokenBalances"`
|
||||||
Rewards []interface{} `json:"rewards"`
|
Rewards []interface{} `json:"rewards"`
|
||||||
|
ComputeUnitsConsumed uint64 `json:"computeUnitsConsumed"`
|
||||||
}
|
}
|
||||||
type Header struct {
|
type Header struct {
|
||||||
NumReadonlySignedAccounts int `json:"numReadonlySignedAccounts"`
|
NumReadonlySignedAccounts int `json:"numReadonlySignedAccounts"`
|
||||||
@@ -293,6 +298,16 @@ func InstructionsFromRpc(instructions []solana.CompiledInstruction) []Instructio
|
|||||||
return instrs
|
return instrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RpcTransactionErr []interface{}
|
||||||
|
|
||||||
|
func marshalRpcTransactionErr(err any) string {
|
||||||
|
e, _ := json.Marshal(err)
|
||||||
|
if len(e) == 0 {
|
||||||
|
return "UnKnown"
|
||||||
|
}
|
||||||
|
return string(e)
|
||||||
|
}
|
||||||
|
|
||||||
func FromRpcTransactionWithMeta(tx rpc.TransactionWithMeta, blockTime *uint64, slot uint64, index int64) (*RawTx, error) {
|
func FromRpcTransactionWithMeta(tx rpc.TransactionWithMeta, blockTime *uint64, slot uint64, index int64) (*RawTx, error) {
|
||||||
created := int64(0)
|
created := int64(0)
|
||||||
if blockTime != nil {
|
if blockTime != nil {
|
||||||
@@ -320,12 +335,68 @@ func FromRpcTransactionWithMeta(tx rpc.TransactionWithMeta, blockTime *uint64, s
|
|||||||
yTx, _ := tx.GetTransaction()
|
yTx, _ := tx.GetTransaction()
|
||||||
|
|
||||||
if meta.Err != nil {
|
if meta.Err != nil {
|
||||||
e, _ := json.Marshal(meta.Err)
|
if iErr, ok := meta.Err.(map[string]any); ok {
|
||||||
sTx.Meta.Err = string(e)
|
instructionError := iErr["InstructionError"]
|
||||||
|
if instructionError == nil {
|
||||||
|
sTx.Meta.Err = &TransactionParsedError{
|
||||||
|
UnKnown: marshalRpcTransactionErr(meta.Err),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if oErr, ok := instructionError.([]any); ok {
|
||||||
|
if len(oErr) <= 1 {
|
||||||
|
sTx.Meta.Err = &TransactionParsedError{
|
||||||
|
UnKnown: marshalRpcTransactionErr(meta.Err),
|
||||||
|
}
|
||||||
|
} else if instrIdx, ok := oErr[0].(float64); ok {
|
||||||
|
sTx.Meta.Err = &TransactionParsedError{
|
||||||
|
Index: uint8(instrIdx),
|
||||||
|
Variant: InstructionError,
|
||||||
|
}
|
||||||
|
errDetail, ok := oErr[1].(string)
|
||||||
|
if ok {
|
||||||
|
if errDetail == "ComputationalBudgetExceeded" || errDetail == "ProgramFailedToComplete" {
|
||||||
|
sTx.Meta.Err.Enum = ComputationalBudgetExceeded
|
||||||
|
} else {
|
||||||
|
sTx.Meta.Err.UnKnown = errDetail
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errDetail2, ok := oErr[1].(map[string]any)
|
||||||
|
if ok && len(errDetail2) > 0 && errDetail2["Custom"] != nil {
|
||||||
|
custom, ok := errDetail2["Custom"].(float64)
|
||||||
|
if ok {
|
||||||
|
sTx.Meta.Err.Enum = Custom
|
||||||
|
sTx.Meta.Err.CustomCode = uint32(custom)
|
||||||
|
} else {
|
||||||
|
sTx.Meta.Err.UnKnown = marshalRpcTransactionErr(meta.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
sTx.Meta.Err.UnKnown = marshalRpcTransactionErr(meta.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sTx.Meta.Err = &TransactionParsedError{
|
||||||
|
UnKnown: marshalRpcTransactionErr(meta.Err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sTx.Meta.Err = &TransactionParsedError{
|
||||||
|
UnKnown: marshalRpcTransactionErr(meta.Err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sTx.Meta.Err = &TransactionParsedError{
|
||||||
|
UnKnown: marshalRpcTransactionErr(meta.Err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
sTx.Meta.Fee = meta.Fee
|
sTx.Meta.Fee = meta.Fee
|
||||||
//sTx.Meta.InnerInstructions = meta.InnerInstructions
|
//sTx.Meta.InnerInstructions = meta.InnerInstructions
|
||||||
|
if meta.ComputeUnitsConsumed != nil {
|
||||||
|
sTx.Meta.ComputeUnitsConsumed = *meta.ComputeUnitsConsumed
|
||||||
|
}
|
||||||
for _, innerInstr := range meta.InnerInstructions {
|
for _, innerInstr := range meta.InnerInstructions {
|
||||||
var instrs []Instruction
|
var instrs []Instruction
|
||||||
for _, instr := range innerInstr.Instructions {
|
for _, instr := range innerInstr.Instructions {
|
||||||
@@ -513,6 +584,7 @@ func intSliceFromUint16Slice(in []uint16) []int {
|
|||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAtaIdxByOwner(result *RawTx, owner solana.PublicKey, mint solana.PublicKey) (int, error) {
|
func getAtaIdxByOwner(result *RawTx, owner solana.PublicKey, mint solana.PublicKey) (int, error) {
|
||||||
var preBalance *TokenBalance
|
var preBalance *TokenBalance
|
||||||
for _, pre := range result.Meta.PreTokenBalances {
|
for _, pre := range result.Meta.PreTokenBalances {
|
||||||
@@ -795,17 +867,12 @@ func ConvertYellowstoneGrpcTransactionToSolanaTransaction(y *pb.SubscribeUpdateT
|
|||||||
|
|
||||||
if meta.Err != nil && len(meta.Err.GetErr()) > 0 {
|
if meta.Err != nil && len(meta.Err.GetErr()) > 0 {
|
||||||
// If the transaction has an error, we set the error in the Meta
|
// If the transaction has an error, we set the error in the Meta
|
||||||
transError, err := DecodeTransactionError(meta.Err.GetErr())
|
sTx.Meta.Err = ParseTransactionErrorFromGeyser(meta.Err.GetErr())
|
||||||
if err != nil {
|
|
||||||
sTx.Meta.Err = err
|
|
||||||
} else {
|
|
||||||
sTx.Meta.Err = transError
|
|
||||||
}
|
|
||||||
// sTx.Meta.Err = meta.Err.GetErr()
|
// sTx.Meta.Err = meta.Err.GetErr()
|
||||||
}
|
}
|
||||||
sTx.Meta.Fee = meta.Fee
|
sTx.Meta.Fee = meta.Fee
|
||||||
//sTx.Meta.InnerInstructions = meta.InnerInstructions
|
//sTx.Meta.InnerInstructions = meta.InnerInstructions
|
||||||
|
sTx.Meta.ComputeUnitsConsumed = *meta.ComputeUnitsConsumed
|
||||||
for _, innerInstr := range meta.InnerInstructions {
|
for _, innerInstr := range meta.InnerInstructions {
|
||||||
var instrs []Instruction
|
var instrs []Instruction
|
||||||
for _, instr := range innerInstr.Instructions {
|
for _, instr := range innerInstr.Instructions {
|
||||||
|
|||||||
98
raydiumv4.go
98
raydiumv4.go
@@ -29,6 +29,8 @@ func raydiumV4Parser(tx *Tx, instruction Instruction, innerInstructions InnerIns
|
|||||||
return raydiumv4WithdrawPNLParser(tx, instruction, innerInstructions, offset)
|
return raydiumv4WithdrawPNLParser(tx, instruction, innerInstructions, offset)
|
||||||
case raydiumV4SwapBaseInDiscriminator, raydiumV4SwapBaseOutDiscriminator:
|
case raydiumV4SwapBaseInDiscriminator, raydiumV4SwapBaseOutDiscriminator:
|
||||||
return raydiumv4SwapParser(tx, instruction, innerInstructions, offset)
|
return raydiumv4SwapParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case raydiumV4SwapBaseInV2Discriminator, raydiumV4SwapBaseOutV2Discriminator:
|
||||||
|
return raydiumv4SwapV2Parser(tx, instruction, innerInstructions, offset)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, increaseOffset(offset), InstructionIgnoredError
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
||||||
@@ -397,3 +399,99 @@ func raydiumv4SwapParser(tx *Tx, instruction Instruction, innerInstructions Inne
|
|||||||
},
|
},
|
||||||
}, offset, nil
|
}, offset, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func raydiumv4SwapV2Parser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
accountsLen := len(instruction.Accounts)
|
||||||
|
if accountsLen != 8 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("invalid number of accounts for raydiumv4 swapv2 instruction, offset %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
|
||||||
|
ammAccount := tx.rawTx.accountList[instruction.Accounts[1]]
|
||||||
|
user := tx.rawTx.accountList[instruction.Accounts[7]]
|
||||||
|
userSourceTokenAccount := tx.rawTx.accountList[instruction.Accounts[5]]
|
||||||
|
userDestinationTokenAccount := tx.rawTx.accountList[instruction.Accounts[6]]
|
||||||
|
baseVaultIdx := instruction.Accounts[3]
|
||||||
|
quoteVaultIdx := instruction.Accounts[4]
|
||||||
|
|
||||||
|
baseTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultIdx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get base vault balance after tx: %v", err)
|
||||||
|
}
|
||||||
|
quoteTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultIdx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get quote vault balance after tx: %v", err)
|
||||||
|
}
|
||||||
|
inners, err := getInnerInstructions(innerInstructions, offset[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), err
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextIndex int
|
||||||
|
var srcFound, destFound bool
|
||||||
|
var baseAmount, quoteAmount decimal.Decimal
|
||||||
|
var event string
|
||||||
|
for i, inner := range inners {
|
||||||
|
from, to, amount, err := parseTokenTransfer(tx.rawTx, inner)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if from.Equals(userSourceTokenAccount) && !srcFound {
|
||||||
|
if to.Equals(tx.rawTx.accountList[baseVaultIdx]) {
|
||||||
|
event = "sell"
|
||||||
|
baseAmount = decimal.NewFromUint64(amount)
|
||||||
|
srcFound = true
|
||||||
|
} else if to.Equals(tx.rawTx.accountList[quoteVaultIdx]) {
|
||||||
|
event = "buy"
|
||||||
|
quoteAmount = decimal.NewFromUint64(amount)
|
||||||
|
srcFound = true
|
||||||
|
}
|
||||||
|
} else if to.Equals(userDestinationTokenAccount) && !destFound {
|
||||||
|
if from.Equals(tx.rawTx.accountList[quoteVaultIdx]) {
|
||||||
|
event = "sell"
|
||||||
|
quoteAmount = decimal.NewFromUint64(amount)
|
||||||
|
destFound = true
|
||||||
|
} else if from.Equals(tx.rawTx.accountList[baseVaultIdx]) {
|
||||||
|
event = "buy"
|
||||||
|
baseAmount = decimal.NewFromUint64(amount)
|
||||||
|
destFound = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if srcFound && destFound {
|
||||||
|
nextIndex = i + 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !srcFound || !destFound {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("raydiumv4 failed to find token transfer inner instruction for swapv2, offset %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
offset[1] += uint(nextIndex + 1)
|
||||||
|
|
||||||
|
userBase := getAccountBalanceAfterTx(tx.rawTx, instruction.Accounts[5])
|
||||||
|
userQuote := getAccountBalanceAfterTx(tx.rawTx, instruction.Accounts[6])
|
||||||
|
baseReserve, _ := decimal.NewFromString(baseTokenbalance.UITokenAmount.Amount)
|
||||||
|
quoteReserve, _ := decimal.NewFromString(quoteTokenbalance.UITokenAmount.Amount)
|
||||||
|
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramRaydiumV4,
|
||||||
|
Event: event,
|
||||||
|
Pool: ammAccount,
|
||||||
|
BaseMint: baseTokenbalance.MintAccount,
|
||||||
|
QuoteMint: quoteTokenbalance.MintAccount,
|
||||||
|
BaseTokenProgram: baseTokenbalance.ProgramIDAccount,
|
||||||
|
QuoteTokenProgram: quoteTokenbalance.ProgramIDAccount,
|
||||||
|
BaseMintDecimals: uint8(baseTokenbalance.UITokenAmount.Decimals),
|
||||||
|
QuoteMintDecimals: uint8(quoteTokenbalance.UITokenAmount.Decimals),
|
||||||
|
User: user,
|
||||||
|
BaseAmount: baseAmount,
|
||||||
|
QuoteAmount: quoteAmount,
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
Mayhem: false,
|
||||||
|
UserBaseBalance: userBase,
|
||||||
|
UserQuoteBalance: userQuote,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, offset, nil
|
||||||
|
}
|
||||||
|
|||||||
14
system.go
14
system.go
@@ -34,12 +34,14 @@ func TransferParser(result *RawTx, instruction Instruction, offset [2]uint, tx *
|
|||||||
from := result.accountList[result.Transaction.Message.Instructions[offset[0]].Accounts[0]]
|
from := result.accountList[result.Transaction.Message.Instructions[offset[0]].Accounts[0]]
|
||||||
to := result.accountList[instruction.Accounts[1]]
|
to := result.accountList[instruction.Accounts[1]]
|
||||||
|
|
||||||
if offset[1] == 0 {
|
if result.Meta.Err == nil {
|
||||||
tx.SolTransfer = append(tx.SolTransfer, SolTransfer{
|
if offset[1] == 0 {
|
||||||
From: from,
|
tx.SolTransfer = append(tx.SolTransfer, SolTransfer{
|
||||||
To: to,
|
From: from,
|
||||||
Amount: decimal.NewFromInt(int64(lamports)), // solana decimals
|
To: to,
|
||||||
})
|
Amount: decimal.NewFromInt(int64(lamports)), // solana decimals
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// load platform by to address
|
// load platform by to address
|
||||||
platform, ok := platformFeeAddresses[to]
|
platform, ok := platformFeeAddresses[to]
|
||||||
|
|||||||
28
tx.go
28
tx.go
@@ -12,6 +12,9 @@ type Swap struct {
|
|||||||
|
|
||||||
TxIndex int
|
TxIndex int
|
||||||
|
|
||||||
|
InstrIdx uint8
|
||||||
|
InnerIdx uint8
|
||||||
|
|
||||||
Pool solana.PublicKey
|
Pool solana.PublicKey
|
||||||
BaseMint solana.PublicKey
|
BaseMint solana.PublicKey
|
||||||
QuoteMint solana.PublicKey
|
QuoteMint solana.PublicKey
|
||||||
@@ -31,6 +34,7 @@ type Swap struct {
|
|||||||
BaseReserve decimal.Decimal
|
BaseReserve decimal.Decimal
|
||||||
QuoteReserve decimal.Decimal
|
QuoteReserve decimal.Decimal
|
||||||
Mayhem bool
|
Mayhem bool
|
||||||
|
Cashback bool
|
||||||
|
|
||||||
UserBaseBalance decimal.Decimal
|
UserBaseBalance decimal.Decimal
|
||||||
UserQuoteBalance decimal.Decimal
|
UserQuoteBalance decimal.Decimal
|
||||||
@@ -47,6 +51,8 @@ type Swap struct {
|
|||||||
StartBinId int32
|
StartBinId int32
|
||||||
EndBinId int32
|
EndBinId int32
|
||||||
BinChanges []DlmmBinLiquidityChange
|
BinChanges []DlmmBinLiquidityChange
|
||||||
|
|
||||||
|
ConsumeUnit uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
type DlmmBinLiquidityChange struct {
|
type DlmmBinLiquidityChange struct {
|
||||||
@@ -71,18 +77,23 @@ type SolTransfer struct {
|
|||||||
To solana.PublicKey
|
To solana.PublicKey
|
||||||
Amount decimal.Decimal
|
Amount decimal.Decimal
|
||||||
}
|
}
|
||||||
|
type ChainLink struct {
|
||||||
|
Timestamp int64
|
||||||
|
Price decimal.Decimal
|
||||||
|
}
|
||||||
|
|
||||||
type Tx struct {
|
type Tx struct {
|
||||||
rawTx *RawTx
|
rawTx *RawTx
|
||||||
Vote bool
|
Vote bool
|
||||||
Signer solana.PublicKey
|
Signer solana.PublicKey
|
||||||
Err interface{} `json:"err,omitempty"`
|
Err *TransactionParsedError `json:"err,omitempty"`
|
||||||
Swaps []Swap `json:"swaps,omitempty"`
|
Swaps []Swap `json:"swaps,omitempty"`
|
||||||
SolTransfer []SolTransfer `json:"sol_transfer,omitempty"`
|
SolTransfer []SolTransfer `json:"sol_transfer,omitempty"`
|
||||||
Block uint64 `json:"block"`
|
Block uint64 `json:"block"`
|
||||||
BlockIndex uint64 `json:"index"`
|
ChainLink ChainLink `json:"chain_link"`
|
||||||
TxHash *[64]byte `json:"-"`
|
BlockIndex uint64 `json:"index"`
|
||||||
BlockAt int64 `json:"block_at"`
|
TxHash *[64]byte `json:"-"`
|
||||||
|
BlockAt int64 `json:"block_at"`
|
||||||
|
|
||||||
CuFee decimal.Decimal `json:"cu_fee"`
|
CuFee decimal.Decimal `json:"cu_fee"`
|
||||||
|
|
||||||
@@ -98,6 +109,9 @@ type Tx struct {
|
|||||||
// update tokenInfo
|
// update tokenInfo
|
||||||
Token map[solana.PublicKey]TokenMeta `gorm:"-"`
|
Token map[solana.PublicKey]TokenMeta `gorm:"-"`
|
||||||
|
|
||||||
|
ComputeUnitsConsumed uint64 `json:"compute_units_consumed"`
|
||||||
|
CuLimit uint32 `json:"cu_limit"`
|
||||||
|
|
||||||
// todo pool info ??
|
// todo pool info ??
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user