diff --git a/internal/test3/test.go b/internal/test3/test.go new file mode 100644 index 0000000..8c11d2f --- /dev/null +++ b/internal/test3/test.go @@ -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, + ) +} diff --git a/pump.go b/pump.go index bea2583..6bbf1f0 100644 --- a/pump.go +++ b/pump.go @@ -348,6 +348,8 @@ func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerIns BaseMintDecimals: 6, QuoteMintDecimals: 9, User: user, + BaseReserve: decimal.NewFromUint64(tradeEvent.RealTokenReserves), + QuoteReserve: decimal.NewFromUint64(tradeEvent.RealSolReserves), Mayhem: isMayhemPump(result.accountList[instruction.Accounts[1]]), UserBaseBalance: userBase, UserQuoteBalance: decimal.NewFromUint64(userQuote),