Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab0e87a48a | ||
|
|
fb8d93f426 | ||
|
|
0cc843b370 | ||
|
|
d9a214b4b4 | ||
|
|
047b549d0f | ||
|
|
9327eab010 | ||
|
|
0ef57cf79a | ||
|
|
03030d817d | ||
|
|
401dca225a | ||
|
|
db8c8727f4 | ||
|
|
09de6ba649 | ||
|
|
7a82990770 | ||
|
|
e82bcb3c07 | ||
|
|
a74f769064 | ||
|
|
1e276e8bd2 | ||
|
|
eb2bde98ac | ||
| 66f0d247f5 | |||
| 879b7fefad | |||
| 149dfae378 | |||
| 8c4b43747c | |||
|
|
e9ba16766f | ||
|
|
cd1d681621 | ||
|
|
920c5ba25b | ||
|
|
3d447ef2e8 | ||
|
|
b0d4342fa2 | ||
|
|
972ddc7960 | ||
| bcd442195c | |||
| 0633707142 | |||
| 8e49f01054 | |||
|
|
62cc64a90a | ||
|
|
629ffe2ea7 | ||
|
|
56dac04a2a | ||
|
|
852ad4b382 | ||
|
|
3fdd4c4490 | ||
|
|
40012b531c | ||
|
|
0e30d6b35f | ||
|
|
70d91fdd30 | ||
|
|
9ece4aebb9 | ||
|
|
5da088ce13 | ||
|
|
0eb1628119 | ||
|
|
c25c856a47 | ||
|
|
b4906a2c20 | ||
|
|
21692c2ecc | ||
|
|
6b4cadb118 | ||
|
|
b76d2efc88 | ||
|
|
16b7461ac7 | ||
|
|
6bc84ce126 | ||
|
|
8128a325a9 | ||
|
|
dd76b04b19 | ||
| 91b70e23d6 | |||
|
|
dbfaa39432 |
@@ -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
|
||||||
|
}
|
||||||
368
checking.go
368
checking.go
@@ -1,368 +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")
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
121
cmd/rpc_parse/main.go
Normal file
121
cmd/rpc_parse/main.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/gagliardetto/solana-go"
|
||||||
|
"github.com/gagliardetto/solana-go/rpc"
|
||||||
|
pump_parser "github.com/thloyi/pump-parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
const rpcURL = "https://staked.helius-rpc.com?api-key=5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d"
|
||||||
|
txHash := os.Getenv("TX_HASH")
|
||||||
|
if txHash == "" {
|
||||||
|
txHash = "2AhpL5KhVmG3D38CwMzrHuRyTucEQ43GzBXL2mo5WiugdZMVmK1dtX8brGe3sxvvFDY6iSSviJTvqCtr4UL3Pc7J"
|
||||||
|
}
|
||||||
|
|
||||||
|
if txHash == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "txHash is empty; set it in cmd/rpc_parse/main.go")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := solana.SignatureFromBase58(txHash)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "invalid txHash: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := rpc.New(rpcURL)
|
||||||
|
maxSupportedVersion := uint64(0)
|
||||||
|
out, err := client.GetTransaction(context.Background(), sig, &rpc.GetTransactionOpts{
|
||||||
|
Encoding: solana.EncodingBase64,
|
||||||
|
Commitment: rpc.CommitmentConfirmed,
|
||||||
|
MaxSupportedTransactionVersion: &maxSupportedVersion,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "rpc getTransaction error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if out == nil || out.Transaction == nil || out.Meta == nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "rpc getTransaction returned empty response")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
rawBinary := out.Transaction.GetBinary()
|
||||||
|
if len(rawBinary) == 0 {
|
||||||
|
fmt.Fprintln(os.Stderr, "rpc getTransaction returned empty transaction data")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
txWithMeta := rpc.TransactionWithMeta{
|
||||||
|
Slot: out.Slot,
|
||||||
|
BlockTime: out.BlockTime,
|
||||||
|
Transaction: rpc.DataBytesOrJSONFromBytes(rawBinary),
|
||||||
|
Meta: out.Meta,
|
||||||
|
Version: out.Version,
|
||||||
|
}
|
||||||
|
|
||||||
|
var blockTime *uint64
|
||||||
|
if out.BlockTime != nil {
|
||||||
|
bt := uint64(*out.BlockTime)
|
||||||
|
blockTime = &bt
|
||||||
|
}
|
||||||
|
|
||||||
|
rawTx, err := pump_parser.FromRpcTransactionWithMeta(txWithMeta, blockTime, out.Slot, 0)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "convert rpc transaction error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pump_parser.EnableAllParsers()
|
||||||
|
|
||||||
|
parsed, err := pump_parser.ParseRawTx(rawTx)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "parse raw tx error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if len(parsed.Swaps) == 0 {
|
||||||
|
fmt.Println("no swaps parsed from tx")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, swap := range parsed.Swaps {
|
||||||
|
fmt.Printf("swap[%d]\n", i)
|
||||||
|
fmt.Printf(" program: %s\n", swap.Program)
|
||||||
|
fmt.Printf(" event: %s\n", swap.Event)
|
||||||
|
fmt.Printf(" pool: %s\n", swap.Pool)
|
||||||
|
fmt.Printf(" user: %s\n", swap.User)
|
||||||
|
fmt.Printf(" base_mint: %s (decimals=%d)\n", swap.BaseMint, swap.BaseMintDecimals)
|
||||||
|
fmt.Printf(" quote_mint: %s (decimals=%d)\n", swap.QuoteMint, swap.QuoteMintDecimals)
|
||||||
|
fmt.Printf(" base_amount: %s\n", swap.BaseAmount.String())
|
||||||
|
fmt.Printf(" quote_amount: %s\n", swap.QuoteAmount.String())
|
||||||
|
if !swap.FeeAmount.IsZero() || swap.FeeSide != "" {
|
||||||
|
fmt.Printf(" fee_amount: %s\n", swap.FeeAmount.String())
|
||||||
|
fmt.Printf(" lp_fee_amount: %s\n", swap.LpFeeAmount.String())
|
||||||
|
fmt.Printf(" fee_side: %s\n", swap.FeeSide)
|
||||||
|
fmt.Printf(" fee_mint: %s (decimals=%d)\n", swap.FeeMint, swap.FeeMintDecimals)
|
||||||
|
fmt.Printf(" fee_token_program: %s\n", swap.FeeTokenProgram)
|
||||||
|
}
|
||||||
|
fmt.Printf(" base_reserve: %s\n", swap.BaseReserve.String())
|
||||||
|
fmt.Printf(" quote_reserve: %s\n", swap.QuoteReserve.String())
|
||||||
|
fmt.Printf(" base_token_program: %s\n", swap.BaseTokenProgram)
|
||||||
|
fmt.Printf(" quote_token_program: %s\n", swap.QuoteTokenProgram)
|
||||||
|
fmt.Printf(" entry_contract: %s\n", swap.EntryContract)
|
||||||
|
fmt.Printf(" user_base_balance: %s\n", swap.UserBaseBalance.String())
|
||||||
|
fmt.Printf(" user_quote_balance: %s\n", swap.UserQuoteBalance.String())
|
||||||
|
fmt.Printf(" active_bin_id: %d\n", swap.ActiveBinId)
|
||||||
|
fmt.Printf(" start_bin_id: %d\n", swap.StartBinId)
|
||||||
|
fmt.Printf(" end_bin_id: %d\n", swap.EndBinId)
|
||||||
|
fmt.Printf(" remove_bp: %d\n", swap.RemoveBp)
|
||||||
|
fmt.Printf(" position_account: %s\n", swap.PositionAccount)
|
||||||
|
if swap.Mayhem {
|
||||||
|
fmt.Printf(" mayhem: true\n")
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" mayhem: false\n")
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
233
consts.go
233
consts.go
@@ -3,6 +3,7 @@ package pump_parser
|
|||||||
import "github.com/gagliardetto/solana-go"
|
import "github.com/gagliardetto/solana-go"
|
||||||
|
|
||||||
var platformFeeAddresses = map[solana.PublicKey]string{
|
var platformFeeAddresses = map[solana.PublicKey]string{
|
||||||
|
solana.MustPublicKeyFromBase58("HeZVpHj9jLwTVtMMbzQRf6mLtFPkWNSg11o68qrbUBa3"): PlatformGMGN,
|
||||||
solana.MustPublicKeyFromBase58("BB5dnY55FXS1e1NXqZDwCzgdYJdMCj3B92PU6Q5Fb6DT"): PlatformGMGN,
|
solana.MustPublicKeyFromBase58("BB5dnY55FXS1e1NXqZDwCzgdYJdMCj3B92PU6Q5Fb6DT"): PlatformGMGN,
|
||||||
solana.MustPublicKeyFromBase58("7sHXjs1j7sDJGVSMSPjD1b4v3FD6uRSvRWfhRdfv5BiA"): PlatformGMGN,
|
solana.MustPublicKeyFromBase58("7sHXjs1j7sDJGVSMSPjD1b4v3FD6uRSvRWfhRdfv5BiA"): PlatformGMGN,
|
||||||
solana.MustPublicKeyFromBase58("ByRRgnZenY6W2sddo1VJzX9o4sMU4gPDUkcmgrpGBxRy"): PlatformGMGN,
|
solana.MustPublicKeyFromBase58("ByRRgnZenY6W2sddo1VJzX9o4sMU4gPDUkcmgrpGBxRy"): PlatformGMGN,
|
||||||
@@ -12,6 +13,7 @@ var platformFeeAddresses = map[solana.PublicKey]string{
|
|||||||
solana.MustPublicKeyFromBase58("dBhdrmwBkRa66XxBuAK4WZeZnsZ6bHeHCCLXa3a8bTJ"): PlatformGMGN,
|
solana.MustPublicKeyFromBase58("dBhdrmwBkRa66XxBuAK4WZeZnsZ6bHeHCCLXa3a8bTJ"): PlatformGMGN,
|
||||||
solana.MustPublicKeyFromBase58("6TxjC5wJzuuZgTtnTMipwwULEbMPx5JPW3QwWkdTGnrn"): PlatformGMGN,
|
solana.MustPublicKeyFromBase58("6TxjC5wJzuuZgTtnTMipwwULEbMPx5JPW3QwWkdTGnrn"): PlatformGMGN,
|
||||||
solana.MustPublicKeyFromBase58("AVUCZyuT35YSuj4RH7fwiyPu82Djn2Hfg7y2ND2XcnZH"): PlatformPhoton,
|
solana.MustPublicKeyFromBase58("AVUCZyuT35YSuj4RH7fwiyPu82Djn2Hfg7y2ND2XcnZH"): PlatformPhoton,
|
||||||
|
solana.MustPublicKeyFromBase58("9yj3zvLS3fDMqi1F8zhkaWfq8TZpZWHe6cz1Sgt7djXf"): PlatformPhoton,
|
||||||
solana.MustPublicKeyFromBase58("7LCZckF6XXGQ1hDY6HFXBKWAtiUgL9QY5vj1C4Bn1Qjj"): PlatformAxiom,
|
solana.MustPublicKeyFromBase58("7LCZckF6XXGQ1hDY6HFXBKWAtiUgL9QY5vj1C4Bn1Qjj"): PlatformAxiom,
|
||||||
solana.MustPublicKeyFromBase58("4V65jvcDG9DSQioUVqVPiUcUY9v6sb6HKtMnsxSKEz5S"): PlatformAxiom,
|
solana.MustPublicKeyFromBase58("4V65jvcDG9DSQioUVqVPiUcUY9v6sb6HKtMnsxSKEz5S"): PlatformAxiom,
|
||||||
solana.MustPublicKeyFromBase58("CeA3sPZfWWToFEBmw5n1Y93tnV66Vmp8LacLzsVprgxZ"): PlatformAxiom,
|
solana.MustPublicKeyFromBase58("CeA3sPZfWWToFEBmw5n1Y93tnV66Vmp8LacLzsVprgxZ"): PlatformAxiom,
|
||||||
@@ -37,17 +39,35 @@ 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,
|
||||||
solana.MustPublicKeyFromBase58("BS3CyJ9rRC4Tp8G7f86r6hGvuu3XdrVGNVpbNM9U5WRZ"): PlatformMEVX,
|
solana.MustPublicKeyFromBase58("BS3CyJ9rRC4Tp8G7f86r6hGvuu3XdrVGNVpbNM9U5WRZ"): PlatformMEVX,
|
||||||
solana.MustPublicKeyFromBase58("97VmzkjX9w8gMFS2RnHTSjtMEDbifGXBq9pgosFdFnM"): PlatformTradeWiz,
|
solana.MustPublicKeyFromBase58("97VmzkjX9w8gMFS2RnHTSjtMEDbifGXBq9pgosFdFnM"): PlatformTradeWiz,
|
||||||
|
solana.MustPublicKeyFromBase58("9rxM513XS4ruBbrGqCaRWuztmE34uxkFoMmp8SAAL7ar"): PlatformTradeWiz,
|
||||||
solana.MustPublicKeyFromBase58("F34kcgMgCF7mYWkwLN3WN7KrFprr2NbwxuLvXx4fbztj"): PlatformSolTradingBot,
|
solana.MustPublicKeyFromBase58("F34kcgMgCF7mYWkwLN3WN7KrFprr2NbwxuLvXx4fbztj"): PlatformSolTradingBot,
|
||||||
solana.MustPublicKeyFromBase58("96aFQc9qyqpjMfqdUeurZVYRrrwPJG2uPV6pceu4B1yb"): PlatformSolTradingBot,
|
solana.MustPublicKeyFromBase58("96aFQc9qyqpjMfqdUeurZVYRrrwPJG2uPV6pceu4B1yb"): PlatformSolTradingBot,
|
||||||
solana.MustPublicKeyFromBase58("5wkyL2FLEcyUUgc3UeGntHTAfWfzDrVuxMnaMm7792Gk"): PlatformMoonshotMoney,
|
solana.MustPublicKeyFromBase58("5wkyL2FLEcyUUgc3UeGntHTAfWfzDrVuxMnaMm7792Gk"): PlatformMoonshotMoney,
|
||||||
solana.MustPublicKeyFromBase58("MaestroUL88UBnZr3wfoN7hqmNWFi3ZYCGqZoJJHE36"): PlatformMaestro,
|
solana.MustPublicKeyFromBase58("MaestroUL88UBnZr3wfoN7hqmNWFi3ZYCGqZoJJHE36"): PlatformMaestro,
|
||||||
solana.MustPublicKeyFromBase58("ZG98FUCjb8mJ824Gbs6RsgVmr1FhXb2oNiJHa2dwmPd"): PlatformBonkBot,
|
solana.MustPublicKeyFromBase58("ZG98FUCjb8mJ824Gbs6RsgVmr1FhXb2oNiJHa2dwmPd"): PlatformBonkBot,
|
||||||
solana.MustPublicKeyFromBase58("J5XGHmzrRmnYWbmw45DbYkdZAU2bwERFZ11qCDXPvFB5"): PlatformPadre,
|
solana.MustPublicKeyFromBase58("J5XGHmzrRmnYWbmw45DbYkdZAU2bwERFZ11qCDXPvFB5"): PlatformPadre,
|
||||||
|
solana.MustPublicKeyFromBase58("5vPNE6VFyXmCmzmWotdxmRk57LEWiXxuAfZL3hKbi2LH"): PlatformAxiom,
|
||||||
|
solana.MustPublicKeyFromBase58("ECDrSz47nXihe5kyK4oWEePPsPi9qz6u5d6Fa2sDj3uM"): PlatformAxiom,
|
||||||
|
solana.MustPublicKeyFromBase58("EqGzowSp6cKAsMSRyyrFTaBxnZEVeNY81LC18YFy8Cx9"): PlatformAxiom,
|
||||||
|
solana.MustPublicKeyFromBase58("3Tu1Y9aNveLFN4WTAwnAwXL6tbUp5MMe3RxyybG4jTAS"): PlatformAxiom,
|
||||||
|
solana.MustPublicKeyFromBase58("3PvqoztjnRxaAiFmLuEfqZkU4GSbjUareks8S2xCZaTa"): PlatformAxiom,
|
||||||
|
solana.MustPublicKeyFromBase58("HkJYryz2BNeMQfuuSWDYktWt5fZLV26eK6nqu7EJycoG"): PlatformAxiom,
|
||||||
|
solana.MustPublicKeyFromBase58("BfFX9rUm8qTZiZjmeq9BktWVTNuG3YWMc5AvkrCKJike"): PlatformAxiom,
|
||||||
|
solana.MustPublicKeyFromBase58("2ApLdwLrGayEmxgpLX9BTR47Q2QprfMg5SpjrLeaK8s7"): PlatformAxiom,
|
||||||
|
solana.MustPublicKeyFromBase58("AVahywMVNRYzdgWrufSWrtdGXAeNEvfpJFxhVFK516mT"): PlatformDexScreener,
|
||||||
}
|
}
|
||||||
|
|
||||||
var mevAgentFeeAddresses = map[solana.PublicKey]string{
|
var mevAgentFeeAddresses = map[solana.PublicKey]string{
|
||||||
@@ -161,6 +181,206 @@ var mevAgentFeeAddresses = map[solana.PublicKey]string{
|
|||||||
solana.MustPublicKeyFromBase58("BnGKHAC386n4Qmv9xtpBVbRaUTKixjBe3oagkPFKtoy6"): MevAgentBlockRazor,
|
solana.MustPublicKeyFromBase58("BnGKHAC386n4Qmv9xtpBVbRaUTKixjBe3oagkPFKtoy6"): MevAgentBlockRazor,
|
||||||
solana.MustPublicKeyFromBase58("Dd7K2Fp7AtoN8xCghKDRmyqr5U169t48Tw5fEd3wT9mq"): MevAgentBlockRazor,
|
solana.MustPublicKeyFromBase58("Dd7K2Fp7AtoN8xCghKDRmyqr5U169t48Tw5fEd3wT9mq"): MevAgentBlockRazor,
|
||||||
solana.MustPublicKeyFromBase58("AP6qExwrbRgBAVaehg4b5xHENX815sMabtBzUzVB4v8S"): MevAgentBlockRazor,
|
solana.MustPublicKeyFromBase58("AP6qExwrbRgBAVaehg4b5xHENX815sMabtBzUzVB4v8S"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("soyas4s6L8KWZ8rsSk1mF3d1mQScoTGGAgjk98bF8nP"): MevAgentSoyas,
|
||||||
|
solana.MustPublicKeyFromBase58("soyascXFW5wEEYiwfEmHy2pNwomqzvggJosGVD6TJdY"): MevAgentSoyas,
|
||||||
|
solana.MustPublicKeyFromBase58("soyasDBdKjADwPz3xk82U3TNPRDKEWJj7wWLajNHZ1L"): MevAgentSoyas,
|
||||||
|
solana.MustPublicKeyFromBase58("soyasE2abjBAynmHbGWgEwk4ctBy7JMTUCNrMbjcnyH"): MevAgentSoyas,
|
||||||
|
solana.MustPublicKeyFromBase58("soyasF3QPWPAKKmgA3GjfWax1kmTT1aoqSGxPzVLNUQ"): MevAgentSoyas,
|
||||||
|
solana.MustPublicKeyFromBase58("ste11JV3MLMM7x7EJUM2sXcJC1H7F4jBLnP9a9PG8PH"): MevAgentStellium,
|
||||||
|
solana.MustPublicKeyFromBase58("ste11MWPjXCRfQryCshzi86SGhuXjF4Lv6xMXD2AoSt"): MevAgentStellium,
|
||||||
|
solana.MustPublicKeyFromBase58("ste11p5x8tJ53H1NbNQsRBg1YNRd4GcVpxtDw8PBpmb"): MevAgentStellium,
|
||||||
|
solana.MustPublicKeyFromBase58("ste11p7e2KLYou5bwtt35H7BM6uMdo4pvioGjJXKFcN"): MevAgentStellium,
|
||||||
|
solana.MustPublicKeyFromBase58("ste11TMV68LMi1BguM4RQujtbNCZvf1sjsASpqgAvSX"): MevAgentStellium,
|
||||||
|
solana.MustPublicKeyFromBase58("astra4uejePWneqNaJKuFFA8oonqCE1sqF6b45kDMZm"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("astra9xWY93QyfG6yM8zwsKsRodscjQ2uU2HKNL5prk"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("astraRVUuTHjpwEVvNBeQEgwYx9w9CFyfxjYoobCZhL"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("astrazznxsGUhWShqgNtAdfrzP2G83DzcWVJDxwV9bF"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("ASde6y8pBCU1aityWHRpqT7pEAcEonjCgFUMeh5egRes"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("ASUv6G8Cj6zt71UAqD1aVtDC3CRn6FFddqF17ZiegrES"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("ASY4mvCtrACKFK8Jiuvqcu8fad9gGTzvfm5zp4megRes"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("astraubkDw81n4LuutzSQ8uzHCv4BhPVhfvTcYv8SKC"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("astraEJ2fEj8Xmy6KLG7B3VfbKfsHXhHrNdCQx7iGJK"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("B1ooMsWjc4SUVVuLyCu1ig2RdomQnHKgMzBMfmSo3DK"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("B1ooMZfUJmAvppzc5cr7eYG8Cenig4FbQGBytr4DGCh"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("b1ooMDLjzz4QqecNsJ8bBXzJTzfAPDCP3CxijTS2K93"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("b1oomst2baE3FqxFPHaA9JwhXgFG9HdTLmbNKDen1kK"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("b1ooMngj7WbNPMZpWpnYRjxQ96RcDZ9ZFpRfjw1g7tg"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("B1oomgV9SAeiUc7GMEg9WhqkZJGccJuHAnh15DbezcN"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("b1oom3jaRNoyJzvSdSVbvSbth5uB4rRYtbjHXT5c1eW"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("B1ooMauwuJPhHsXqt3uj7B92CAFG8kaD1Q2iGEmGYnx"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("B1ooMdjcY7zemxDWiH8jVZPxEMdHnE5AraWPHdHQoPj"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("B1ooMKzu6siJzQutP6a6oLiY3fpzgQnBZsAjxuAm9qo"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("AstrA1ejL4UeXC2SBP4cpeEmtcFPZVLxx3XGKXyCW6to"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("AsTra79FET4aCKWspPqeSFvjJNyp96SvAnrmyAxqg5b7"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("AsTRADtvb6tTmrsqULQ9Wji9PigDMjhfEMza6zkynEvV"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("AsTRAEoyMofR3vUPpf9k68Gsfb6ymTZttEtsAbv8Bk4d"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("AStrAJv2RN2hKCHxwUMtqmSxgdcNZbihCwc1mCSnG83W"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("Astran35aiQUF57XZsmkWMtNCtXGLzs8upfiqXxth2bz"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("AStRAnpi6kFrKypragExgeRoJ1QnKH7pbSjLAKQVWUum"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("ASTRaoF93eYt73TYvwtsv6fMWHWbGmMUZfVZPo3CRU9C"): MevAgentAstralane,
|
||||||
|
solana.MustPublicKeyFromBase58("Gu2UGEfze3Gg5cHuEC4jGbyCufgpev75RkVvBdKKtf12"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("E8wD3SMD1trozPrvSN9F6SyuUXD7rrFDuR3WexGziKG5"): MevAgent0slot,
|
||||||
|
solana.MustPublicKeyFromBase58("18hCV7f9CPmZRAH3QCNZaGHhHeNSfisQKeKuFkQsPLY"): MevAgent0slot,
|
||||||
|
solana.MustPublicKeyFromBase58("2sYKRWBNVY6UomMBi4juoMrrL98bqizDMn98cJ3cBmye"): MevAgent0slot,
|
||||||
|
solana.MustPublicKeyFromBase58("CZubxabMM7CPFSDAfMUhxNuvXRDLjDf6yVVq1RoJ66rk"): MevAgent0slot,
|
||||||
|
solana.MustPublicKeyFromBase58("Dz8rMcdokTLfbnNz2ZdYocZixgaA1TMqbA31xtwPgcxb"): MevAgent0slot,
|
||||||
|
solana.MustPublicKeyFromBase58("ForLDu55GfA2U1aTUaitmjzjs92vvVn1MSqzY3D9HtAK"): MevAgent0slot,
|
||||||
|
solana.MustPublicKeyFromBase58("6MgjyQU7G988jgL6EGAgfHYoeesCnwYMyPeh1fpJ71FP"): MevAgent0slot,
|
||||||
|
solana.MustPublicKeyFromBase58("12pHu2j2DDShyCVFU7vtSLXga74et9y83VD38mw6XYhB"): MevAgent0slot,
|
||||||
|
solana.MustPublicKeyFromBase58("5QuV4TS5TJFWPu7Yd56VaPvf4nKUicPvTfC3mwnb7dNW"): MevAgent0slot,
|
||||||
|
solana.MustPublicKeyFromBase58("4gh9m7RV7G4WwRftA6qV7RhDfytdepb3XbxFRfTtneYJ"): MevAgent0slot,
|
||||||
|
solana.MustPublicKeyFromBase58("AumQWSLrWwDXRq1yDEYPiw8vT5NUBYzrbdWCprJ4ZUa8"): MevAgent0slot,
|
||||||
|
solana.MustPublicKeyFromBase58("3vGEsQA5jzvN8TBgytuYEdZxW6P2pK1c6pq56JiFuygS"): MevAgent0slot,
|
||||||
|
solana.MustPublicKeyFromBase58("AsEF2SWSEZ1xpGZ5fdzDKaoka1XEtFSjGo39YUXkpvAh"): MevAgent0slot,
|
||||||
|
solana.MustPublicKeyFromBase58("2WoQNgmc4SEXrR3rKQypmeWmsxGqHHE6rApnVrP6Pt77"): MevAgent0slot,
|
||||||
|
solana.MustPublicKeyFromBase58("9vTpfGYN2jtjZgXQ7gihyHmN3FseLP7uW1CWMdsgcny"): MevAgentBlocxRoute,
|
||||||
|
solana.MustPublicKeyFromBase58("CyL8mfycXYbWHVoTTsfvnAfF2MvfcqeQAmmsqNQLxF7g"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("Eg85QSYLwtZfBBPF4CsNmijJDXUAeCMjoh36L1cwboqg"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("9gBzvLKedrs9HxaLPhBdkPaeFTxEDNDGfqJmqvHjfiZp"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("7BxoFqM3swL46Lt9EWzL9z2LeXYfmJL7MVzpFrDpLPei"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("91Ht2gq1CMPcLySuq8NjHaA1rXysm8zzoiiyfT4uSE7u"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("2zCYpNSWcHX9AzFndF1mcT1bMkG1EXMzzjFcBjSnJq9f"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("4Kfqkx3c8TxLX74J1nzfzfHCGdoDCuZ8k84sGpnVh1a4"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("GeiVfSfUBVxjJA6F2SNSASoK8JaSCiSmsC2hBrPLfpiv"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("DggsS83MWeUHZdrV2jyMUh8GDfLrU5P9Es36h7Uf3wRp"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("2d5viHZBHKt5DgEpMckXEfndR1CoZ1tHvcbL9fU4xqT7"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("73VnqgMJq29j4HMzF6GRdBeVpZgz7ibouyKQvyAKbVZy"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("HvgA9hTyrTQCU5869fhZ7My9WkkHK2yBo4Wu6ojHmMio"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("4xJEQnuMpoUNxhNew4AechRBo1DnpVfLyUe68BXTTF73"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("7ZKL8BAPfKKa6FNmds48QKFnckrcj4mkppRnsBAR2xVH"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("BYpBPSRkVSvutxHngtxnqeoTBrENZ8iM56Ywnsmy829w"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("4LEkLhb2u5qCUXS1Hc3eL2zTxk2kjSzQeFK4ZgWsV3EM"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("96Zc2GT7ZmMvF7rXgcwHAyJ7KmK8RaS4Z3VZw2b7GjJx"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("9Kaz3Q9KJ3x8SXvui37FK5m1AwcwqkYLvS9Xg1Why9Q1"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("muV321VhQ4XgJkVtsZP13zbCqg9HokT222bWS3DBxp3"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("Hth7qf5dv683k3ZJffjJvJ8gSU21dfPWy3mBEyRRhCiN"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("pD7KfmGkxHqQFNLqYv3zshSzkGaAB99vjNDKz6e7nGC"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("AQChXZ1ZWvPH8EjdPxXXsC8VqCaBmPVruJbswhE3xNZ8"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("2L2DgQ5ZXRYnv8K97NFDJvsNrA1MsrCGr3CvokPtDy8D"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("4BjQeBGZmGNWeHfQC4scHK5d4RtDr79h1hZNPcrLDS8C"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("E99TTcqBPAY1F4ZppMRkDX3pTqaSnRC24tUErfd2opNL"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("8zi6AG7oSKoswSEMaxNmXrwBYmDwuQ4GLiY4Q1j9Rayu"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("8GgU7tKJSA97G97kD9AbxYgsC9Hcjfg7RpAofWuA6oHt"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("JDxQoXGFRwEojWzkirDNeHz88SDEPzdDakjsobJ4YHrj"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("GQgHdPuDNcss3BoKrMfS6bgGekjitmKQRJxnuhUBu921"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("8FoxPbnucCZ3wuzhMofKE5VdYKcHfWmYNrnC2whVBAhS"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("3L9UZWLAprLtB2xddEHsCmgXbPc2PidgSjtHGZd2MzB3"): MevAgentBlockRazor,
|
||||||
|
solana.MustPublicKeyFromBase58("FAST3dMFZvESiEipBvLSiXq3QCV51o3xuoHScqRU6cB6"): MevAgentFast,
|
||||||
|
solana.MustPublicKeyFromBase58("FASTCKnwwY6iL3CknRgg3Zqir7jeagDDhxSnBQQy5a1C"): MevAgentFast,
|
||||||
|
solana.MustPublicKeyFromBase58("FASTHPW6akdGh9PFSdhMTbCuGkCSX7LsUjjnaB2RTQ4v"): MevAgentFast,
|
||||||
|
solana.MustPublicKeyFromBase58("FASTKL1AamNKrwnvbKwo4PU8434BBdqVrTtugM6oDU71"): MevAgentFast,
|
||||||
|
solana.MustPublicKeyFromBase58("FASTPB76TxKPMZ7Q29m8v4zJn8gUjbWyvTEQaaxhwN7M"): MevAgentFast,
|
||||||
|
solana.MustPublicKeyFromBase58("FASTYKWXRfAoty7SQCM1mGVrmPUyyNcF4tc3DUkLDAu9"): MevAgentFast,
|
||||||
|
solana.MustPublicKeyFromBase58("FASTYmSidNfLwdwiQEhCTtzghxEtaipeNSDSwh9xDPs3"): MevAgentFast,
|
||||||
|
solana.MustPublicKeyFromBase58("FASTs6ctgbsuZegMzUs4DPUYhRSZUPCjgCVnttHbpQAp"): MevAgentFast,
|
||||||
|
solana.MustPublicKeyFromBase58("node1FdMPnJBN7QTuhzNw3VS823nxFuDTizrrbcEqzp"): MevAgentNode1,
|
||||||
|
solana.MustPublicKeyFromBase58("node1zrVjcY2XB3Au8qYj5MxjbNfGu3baHaqZMkPM7Z"): MevAgentNode1,
|
||||||
|
solana.MustPublicKeyFromBase58("node1E3hguapYA18HCpEEkRHQmLNiyv9pdfE9s2zo5X"): MevAgentNode1,
|
||||||
|
solana.MustPublicKeyFromBase58("node1CVxtFas2Pw5Vcf86Pq89Hqx4jveo1ntY7ARFMK"): MevAgentNode1,
|
||||||
|
solana.MustPublicKeyFromBase58("node1EoLojAvoUmyDytcvgdXs6GPtY3zpQXPCRVncEA"): MevAgentNode1,
|
||||||
|
solana.MustPublicKeyFromBase58("node1VwH169UqyJHr5MYCH3EBuwrdvn5KHXAkhEEfav"): MevAgentNode1,
|
||||||
|
solana.MustPublicKeyFromBase58("node1JkDqyiEg7CDNj3ATPiRmWaAG2gnrAEiMJ4Rzcc"): MevAgentNode1,
|
||||||
|
solana.MustPublicKeyFromBase58("node1GS8pZnP6MzGSXwhA2MXH6EBfCpFaAE64G2ubpB"): MevAgentNode1,
|
||||||
|
solana.MustPublicKeyFromBase58("node1AVfbcSi98LAgGyAHUGS4eYkYTbS5vUPZYQnViF"): MevAgentNode1,
|
||||||
|
solana.MustPublicKeyFromBase58("node1L7Xat2tSkRNNi6TSuUScMYfj64ovhr2aceJm9g"): MevAgentNode1,
|
||||||
|
solana.MustPublicKeyFromBase58("node1kMY97W3LPXaKKV43yRa2Q3BLg4WZiT27VifUDc"): MevAgentNode1,
|
||||||
|
solana.MustPublicKeyFromBase58("node1Zi3r7hmGYwF9cJAkfCHh9EKWbkSrYdvcvLukF4"): MevAgentNode1,
|
||||||
|
solana.MustPublicKeyFromBase58("node1G3fmoCuEJzcPNF4hLbSZ2ypcUuh9CB3k9E7Q8k"): MevAgentNode1,
|
||||||
|
solana.MustPublicKeyFromBase58("node18nQgpjoKe1fM72GiV6tHXg5dMKbVPFGwRBD9MU"): MevAgentNode1,
|
||||||
|
solana.MustPublicKeyFromBase58("node1spgxXR8HCbm4LyZNoisFLmBXxy2qnZrv63WxMp"): MevAgentNode1,
|
||||||
|
solana.MustPublicKeyFromBase58("node1rmmFXeLh94mBGtDHbSwCrBJqDnc16xrURHRYD9"): MevAgentNode1,
|
||||||
|
solana.MustPublicKeyFromBase58("EnchantKMZ93cDKwsnyvnD5WCpZLFTLVRWozFjAUzTko"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("CJwbwPfVFZDPGKJKCtLkzDJPFrGyyroEPFjXigmJB6mr"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("3Thhhj3omvVFfbhEHdFe8djwDZT5oS6BQ4k5KrZkYt1r"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("6CQzBpGJn6XYcCkm77xNd944MpbjLHLsP6sCEWSZVUHS"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("DeMZbwKtu9kteFdxL1yh6aTWqDwYfH79DKzYrgfTwAc2"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("3Pn9ZFCsNTf9MvWbpemQccWuyHNMbBjxg1eW53ikHcpH"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("DjfRXWegRn9bWnBvZFxAnpu1jNikcoy8iiu6ZX9AxAd5"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("8mbjzuz8ka3zVGnry6xMEwm96tzk4yKnWgvwAT1LwEGx"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("5KpS5Q3nUtp1cUynUxzH2bA93SWzmx2y3GwU45AeEEP5"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("bgK8f5H4ocVdNrkUrspUFmAaEosGQtbc1JCMqLwvvRe"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("bg7BgfutLpjFdxDNcbwQFGFkLGQT9Kww9wv6EWUHQr3"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("bg9zUQnVkYLgAWJvL9MjP4tFDecCxbvmQRqrAuZpQUA"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("bgu2xgHEJocs4tggHDEwNnmgduftnXfJuWoLiUYfiLW"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("bgd4MvpBH3LaVz6sHvqFphoUex4taUe2E5mKuk4sVXn"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("bgSfpx2Pr3bHYev6ikwTqdBo2aaPGgjEseAWhjxp6F5"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("bgfaB4sngcm7cARjjiEvKfWE87owf2HuDfYDy8EyP45"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("bgzmGhu9qcyLW6qR1HKQuLTY6PWktNSAuzLNmo7aiQY"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("bgMTi1qFtbiFiHsURKW4Bfg4wjXtT8iJL7HC1z3gXsm"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("bgDRbhSLK62ApA2PbZs1W7SecodGhTFf6udU3MWDadu"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("bg67LJN9Ngvfq4hJbSmm7tZ2wqmn2f1pxXbXW5QfxRz"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("bgmnrKWgN5jE8pF3PbFxRWYaho1bjCtmcTZ9VfRbhxf"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("bgTZqxCX4ej98P1UyYJjgGmGDmst7nteSyUWDwzMxNj"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("bgauKkwFcT8w7SHau9NufDfvmq1cy79X52bRbL6yzEB"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("bg8WP8cEtWhdCjDd7rrwzsnz7K9f3oiEm2Qqu7TYmDn"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("DzrVK357ynzkPtdC7jzUbXgsUY8ULUeR2ihoPcX1JB3n"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("pfn2d1g6xkwhykkyjtoccFbC7r19ADf5dGB2YnT1Hgw"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("pfnXi2FdpFUUn6VyoxUohNyWk2Nup3ruguTgK8jaZaF"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("bg1rCzhyASbzib75ohpRfNY3mGJaX1k6v56WCrUkh3a"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("bgsouue9XeHUzNwwuAKqBj1Fk1RbJkcBjvs4zkmUhLc"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("mwGELGMgGGrNL1UibNCQeJHDE7qdPptWRYB6noUHmTj"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("pfn9b35be4L7xh7G8P2jUzWsJAigrKDSoBeRMiyg75p"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("pfnSbG36fCGpT8WsB1NEbQ2BH11iog6qjFqMEVCZZgV"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("pfnaxrmMoemfvbhXek6offTNXas11GtepGQMN9UF3gk"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("pfnKWwarhjuKKg3WV3nw3wAE8zuymigT3vuJHwZeL4s"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("pfnaZXdkjJq26auzzFKeQm7YKphuNCdDGcJVqqb6awr"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("pfnP85qobXv2wETniKjXBhxKvgivpfT8EGAcS8sb3bq"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("pfnSAoQWtJCDKnjmR8oduqbZYXr69Q4cFQ6VhgFkvgT"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("pfnSbziLrSSVNqPBD9tpx3Ud4VtbxwsXjdfYv9SmBDx"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("Vn7tfMvrvrymGYMnxhj1DV16Sz2R9YXmaXF3hiSAHuC"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("pfnTcJ1i4mRYzbqGduF71RsooUCFkPSpk8UE7drCkjh"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("pfnUxCuZcfP6yidkG3EsqyR5DTbyie3R74fGoA5oB3J"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("pfnEJvqLGddJxQTA9DcYLTbVwiFdT3KmLXo6UcnmcgC"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("pfn6dyanKiTTinHs887D7qe2S4727wzK7xi7ERGaizC"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("bgDETv6tnt9mwYqAKebLXY5B5o6akiKJmAdU7Gd9G7H"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("bgH7YhymSykyvMa3nAZpzvrn73owJHU5iB75S1aiLT9"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("pfngGVVQLiVRFbLWw3Ektiv17ef9NiRZbcgdAhh4ZEW"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("nEFs3jph8HJt7honu3k7XtGUufMnwAvSXmXcKSPxryP"): MevAgentNozomi,
|
||||||
|
solana.MustPublicKeyFromBase58("Fa1con11xLjPddfzRwRUB16sbFZggp2JeJkCeWREyR8X"): MevagentFa1con,
|
||||||
|
solana.MustPublicKeyFromBase58("Fa1con11TM1RuAQzbQzYjTy4Ekfap9Lnc9fnEbQYEd6Q"): MevagentFa1con,
|
||||||
|
solana.MustPublicKeyFromBase58("Fa1con113Bvi76nS5AzUiRDC2fqjfzkNMUNRLgQybMYt"): MevagentFa1con,
|
||||||
|
solana.MustPublicKeyFromBase58("Fa1con1QGHJK232s8yZpzZZwqPexnAKcoyKj626LNsMv"): MevagentFa1con,
|
||||||
|
solana.MustPublicKeyFromBase58("Fa1con1zUzb6qJVFz5tNkPq1Ahm8H1qKW7Q48252QbkQ"): MevagentFa1con,
|
||||||
|
solana.MustPublicKeyFromBase58("Fa1con16d3MSwd3SAiwvr2LwgkpE7ot8zntbpuec8HAx"): MevagentFa1con,
|
||||||
|
solana.MustPublicKeyFromBase58("Fa1con1i7mpa7Qc6epYJ6r4P9AbU77DFFz173r59Df1x"): MevagentFa1con,
|
||||||
|
solana.MustPublicKeyFromBase58("Fa1con18nWn8TdAGL7JX8PertfMUGVSc899NawokJ4Bq"): MevagentFa1con,
|
||||||
|
solana.MustPublicKeyFromBase58("Fa1con1GKusK2EqsfzrDzGPaYZSxQtFGzJiRMMU9Zm2g"): MevagentFa1con,
|
||||||
|
solana.MustPublicKeyFromBase58("Fa1con1RDwVwM9VrJ53CwVefD3VU9c58EMpDawV7fLMi"): MevagentFa1con,
|
||||||
|
solana.MustPublicKeyFromBase58("Sp1x2AqpQckPLaWnWCJUNg8k6qQexfaEWcSRKf5JcDV"): MevagentBlocksprint,
|
||||||
|
solana.MustPublicKeyFromBase58("Sp4JHSh9cksfzXbgK7Pq2ovtn8LirLQydaJKTsiNT77"): MevagentBlocksprint,
|
||||||
|
solana.MustPublicKeyFromBase58("Sp1xMS2cbw83SZDNr4AGqkBYYLjb3LvVnmDSrTMaHkr"): MevagentBlocksprint,
|
||||||
|
solana.MustPublicKeyFromBase58("SpagSJmnh8E9cGT5Y431xPPaS2c1xLREGGCWN9yDeUf"): MevagentBlocksprint,
|
||||||
|
solana.MustPublicKeyFromBase58("SpWrza9E63MQuHeGnnfzmtLVCs3pBdjyKPXUABPo9nq"): MevagentBlocksprint,
|
||||||
|
solana.MustPublicKeyFromBase58("moon17L6BgxXRX5uHKudAmqVF96xia9h8ygcmG2sL3F"): MevAgentMoon,
|
||||||
|
solana.MustPublicKeyFromBase58("moon26Sek222Md7ZydcAGxoKG832DK36CkLrS3PQY4c"): MevAgentMoon,
|
||||||
|
solana.MustPublicKeyFromBase58("moon7fwyajcVstMoBnVy7UBcTx87SBtNoGGAaH2Cb8V"): MevAgentMoon,
|
||||||
|
solana.MustPublicKeyFromBase58("moonBtH9HvLHjLqi9ivyrMVKgFUsSfrz9BwQ9khhn1u"): MevAgentMoon,
|
||||||
|
solana.MustPublicKeyFromBase58("moonCJg8476LNFLptX1qrK8PdRsA1HD1R6XWyu9MB93"): MevAgentMoon,
|
||||||
|
solana.MustPublicKeyFromBase58("moonF2sz7qwAtdETnrgxNbjonnhGGjd6r4W4UC9284s"): MevAgentMoon,
|
||||||
|
solana.MustPublicKeyFromBase58("moonKfftMiGSak3cezvhEqvkPSzwrmQxQHXuspC96yj"): MevAgentMoon,
|
||||||
|
solana.MustPublicKeyFromBase58("moonQBUKBpkifLcTd78bfxxt4PYLwmJ5admLW6cBBs8"): MevAgentMoon,
|
||||||
|
solana.MustPublicKeyFromBase58("moonXwpKwoVkMegt5Bc776cSW793X1irL5hHV1vJ3JA"): MevAgentMoon,
|
||||||
|
solana.MustPublicKeyFromBase58("moonZ6u9E2fgk6eWd82621eLPHt9zuJuYECXAYjMY1C"): MevAgentMoon,
|
||||||
|
solana.MustPublicKeyFromBase58("SpEEdz8S1KorkMZqjMUxfxrmWwofmp6ReNP2Nx6CUmq"): MevAgentSpeedlanding,
|
||||||
|
solana.MustPublicKeyFromBase58("SpeeDy3GJM4wcrQmk1itRFWgidvxX4rwjTLMv78wwjE"): MevAgentSpeedlanding,
|
||||||
|
solana.MustPublicKeyFromBase58("SPeEdva37vW8vRtqgYjprQs1g3965icfVN5Rt7SMAyh"): MevAgentSpeedlanding,
|
||||||
|
solana.MustPublicKeyFromBase58("speEdrSEpox5GUfHWcBc7tQjRuSfUin2yvB7qoYvvJh"): MevAgentSpeedlanding,
|
||||||
|
solana.MustPublicKeyFromBase58("SPeEDmkHkN3A2roSZf6aZyEMsmrGqTHKqwP51y2Y4rV"): MevAgentSpeedlanding,
|
||||||
|
solana.MustPublicKeyFromBase58("SpeedLdTJXh2RKpXEaP8JCxkWoUVXhtdPQ1EnxBJMxc"): MevAgentSpeedlanding,
|
||||||
|
solana.MustPublicKeyFromBase58("SpEediGKLbbXndSYTzwmz6Z3NDgHQLDcTDEvGFkSMH9"): MevAgentSpeedlanding,
|
||||||
|
solana.MustPublicKeyFromBase58("speede8xCcUq2Tiv1efXeTuE3k9TDNq8TnGKaKSc6J4"): MevAgentSpeedlanding,
|
||||||
|
solana.MustPublicKeyFromBase58("harkEpXoJv5qVzHaN7HSuUAd6PHjyMcFMcDYBMDJCEQ"): MevAgentAllenhark,
|
||||||
|
solana.MustPublicKeyFromBase58("harkm2BTWxZuszoNpZnfe84jRbQTg6KGHaQBmWzDGQQ"): MevAgentAllenhark,
|
||||||
|
solana.MustPublicKeyFromBase58("harkR2YJ4Dpt4UDJTcBirjnSPBhNpQFcoFkNpCkVqNk"): MevAgentAllenhark,
|
||||||
|
solana.MustPublicKeyFromBase58("t3QLYyXH4vZYbEifLqjD581t5dPVhq9LABxWceySzL2"): MevAgentRaiden,
|
||||||
|
solana.MustPublicKeyFromBase58("t46SqGmwStEffUMp1fr2xmv5uyR85TB9annJuLKLf83"): MevAgentRaiden,
|
||||||
|
solana.MustPublicKeyFromBase58("t1TcSg9biJsz4NjKjhopK8QZzPS4KzBgFSszu5QTGgF"): MevAgentRaiden,
|
||||||
|
solana.MustPublicKeyFromBase58("t2XFAFBaUkCzxJwEbLWFX9PKFjfBCp2tSyFtx5z4RZM"): MevAgentRaiden,
|
||||||
|
solana.MustPublicKeyFromBase58("t55hdzzftxWkYy3J8t32C9RRcZDuMZ4LDuBmbTzJFkU"): MevAgentRaiden,
|
||||||
|
solana.MustPublicKeyFromBase58("t6UtTQLUGHJJrzxAb8PBBZdZKra8SWUqvTv9zPnxKNz"): MevAgentRaiden,
|
||||||
|
solana.MustPublicKeyFromBase58("t7qUQU35sLpPydh42BcPmtEfTWW8gBe4Ry3gjwVnokJ"): MevAgentRaiden,
|
||||||
|
solana.MustPublicKeyFromBase58("t8pPgarSK3TnuLbbHmoE1RCQdLxfxuPqNTyFjBKahok"): MevAgentRaiden,
|
||||||
|
solana.MustPublicKeyFromBase58("t96GGdw3MiaGR993XN8PSsRpKGXx56t5Wf6zcF1hBpY"): MevAgentRaiden,
|
||||||
}
|
}
|
||||||
|
|
||||||
var entryContractAddresses = map[solana.PublicKey]string{
|
var entryContractAddresses = map[solana.PublicKey]string{
|
||||||
@@ -195,7 +415,20 @@ var entryContractAddresses = map[solana.PublicKey]string{
|
|||||||
solana.MustPublicKeyFromBase58("E6YoRP3adE5XYneSseLee15wJshDxCsmyD2WtLvAmfLi"): EntryContractTaggedSearcher,
|
solana.MustPublicKeyFromBase58("E6YoRP3adE5XYneSseLee15wJshDxCsmyD2WtLvAmfLi"): EntryContractTaggedSearcher,
|
||||||
solana.MustPublicKeyFromBase58("MAyhSmzXzV1pTf7LsNkrNwkWKTo4ougAJ1PPg47MD4e"): EntryContractMayhem,
|
solana.MustPublicKeyFromBase58("MAyhSmzXzV1pTf7LsNkrNwkWKTo4ougAJ1PPg47MD4e"): EntryContractMayhem,
|
||||||
solana.MustPublicKeyFromBase58("proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u"): EntryContractOKXDexRouterV2,
|
solana.MustPublicKeyFromBase58("proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u"): EntryContractOKXDexRouterV2,
|
||||||
|
solana.MustPublicKeyFromBase58("term9YPb9mzAsABaqN71A4xdbxHmpBNZavpBiQKZzN3"): EntryContractTerm,
|
||||||
|
solana.MustPublicKeyFromBase58("DF1ow4tspfHX9JwWJsAb9epbkA8hmpSEAtxXy1V27QBH"): EntryContractDFlow,
|
||||||
|
solana.MustPublicKeyFromBase58("MaestroAAe9ge5HTc64VbBQZ6fP77pwvrhM8i1XWSAx"): EntryContractMaestroBot,
|
||||||
|
solana.MustPublicKeyFromBase58("BBRouter1cVunVXvkcqeKkZQcBK7ruan37PPm3xzWaXD"): EntryContractBonkBot,
|
||||||
|
solana.MustPublicKeyFromBase58("B3111yJCeHBcA1bizdJjUFPALfhAfSRnAbJzGUtnt56A"): EntryContractBinanceWallet,
|
||||||
|
solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9"): EntryContractAxiom,
|
||||||
|
solana.MustPublicKeyFromBase58("F5tfvbLog9VdGUPqBDTT8rgXvTTcq7e5UiGnupL1zvBq"): EntryContractAxiom,
|
||||||
|
solana.MustPublicKeyFromBase58("Gz9VPiSLQYbvKyb3jZPjNfyA6n4T4qVFUuAukgL964nL"): EntryContractAxiom,
|
||||||
|
solana.MustPublicKeyFromBase58("B3jytJa6Tzpn4Ly7GNnDm3dMGqUin5aMRm5aPsJGU5G7"): EntryContractTradewiz,
|
||||||
|
solana.MustPublicKeyFromBase58("DBotWvSso9oD1ZB3aHx2LiD2ZoFpF8PbKjaT4uHKLLVs"): EntryContractDbot,
|
||||||
|
solana.MustPublicKeyFromBase58("term9YPb9mzAsABaqN71A4xdbxHmpBNZavpBiQKZzN3"): EntryContractPadre,
|
||||||
}
|
}
|
||||||
|
|
||||||
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")
|
||||||
|
|||||||
39
enum.go
39
enum.go
@@ -1,16 +1,26 @@
|
|||||||
package pump_parser
|
package pump_parser
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MevAgentJito = "jito"
|
MevAgentJito = "jito"
|
||||||
MevAgent0slot = "0slot"
|
MevAgent0slot = "0slot"
|
||||||
MevAgentBlocxRoute = "blocxroute"
|
MevAgentBlocxRoute = "blocxroute"
|
||||||
MevAgentNozomi = "nozomi"
|
MevAgentNozomi = "nozomi"
|
||||||
MevAgentNextBlock = "nextblock"
|
MevAgentNextBlock = "nextblock"
|
||||||
MevAgentHelius = "helius"
|
MevAgentHelius = "helius"
|
||||||
MevAgentNode1 = "node1"
|
MevAgentNode1 = "node1"
|
||||||
MevAgentFlashBlock = "flashBlock"
|
MevAgentFlashBlock = "flashBlock"
|
||||||
MevAgentUnknown = "unknown"
|
MevAgentUnknown = "unknown"
|
||||||
MevAgentBlockRazor = "blockrazor"
|
MevAgentBlockRazor = "blockrazor"
|
||||||
|
MevAgentFast = "fast"
|
||||||
|
MevAgentSoyas = "soyas"
|
||||||
|
MevAgentStellium = "stellium"
|
||||||
|
MevAgentAstralane = "astralane"
|
||||||
|
MevagentFa1con = "fa1con"
|
||||||
|
MevagentBlocksprint = "blocksprint"
|
||||||
|
MevAgentMoon = "moon"
|
||||||
|
MevAgentSpeedlanding = "speedlanding"
|
||||||
|
MevAgentAllenhark = "allenhark"
|
||||||
|
MevAgentRaiden = "raiden"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -42,7 +52,15 @@ const (
|
|||||||
EntryContractFluxbeamDEX = "fluxbeamDEX"
|
EntryContractFluxbeamDEX = "fluxbeamDEX"
|
||||||
EntryContractNovaBotsProgram = "novaBotsProgram"
|
EntryContractNovaBotsProgram = "novaBotsProgram"
|
||||||
EntryContractTaggedSearcher = "taggedSearcher"
|
EntryContractTaggedSearcher = "taggedSearcher"
|
||||||
|
EntryContractPadre = "padre"
|
||||||
|
EntryContractDFlow = "dflow"
|
||||||
|
EntryContractMaestroBot = "maestroBot"
|
||||||
|
EntryContractBonkBot = "bonkBot"
|
||||||
|
EntryContractBinanceWallet = "binanceWallet"
|
||||||
EntryContractMayhem = "pumpMayhem"
|
EntryContractMayhem = "pumpMayhem"
|
||||||
|
EntryContractTerm = "term"
|
||||||
|
EntryContractTradewiz = "tradewiz"
|
||||||
|
EntryContractDbot = "dbot"
|
||||||
EntryContractUnknown = "unknown"
|
EntryContractUnknown = "unknown"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -63,6 +81,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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
31
globals.go
31
globals.go
@@ -1,6 +1,7 @@
|
|||||||
package pump_parser
|
package pump_parser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
@@ -38,6 +39,36 @@ func getInnerInstructions(innerInstructions InnerInstructions, offset uint) ([]I
|
|||||||
return inners, nil
|
return inners, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseTokenTransfer(tx *RawTx, instr Instruction) (from solana.PublicKey, to solana.PublicKey, amount uint64, err error) {
|
||||||
|
if len(instr.Accounts) < 3 {
|
||||||
|
return solana.PublicKey{}, solana.PublicKey{}, 0, fmt.Errorf("not enough accounts for token transfer instruction")
|
||||||
|
}
|
||||||
|
programAccount := tx.accountList[instr.ProgramIDIndex]
|
||||||
|
if !programAccount.Equals(solana.TokenProgramID) && !programAccount.Equals(solana.Token2022ProgramID) {
|
||||||
|
return solana.PublicKey{}, solana.PublicKey{}, 0, fmt.Errorf("not a token program instruction")
|
||||||
|
}
|
||||||
|
if len(instr.Data) < 9 {
|
||||||
|
return solana.PublicKey{}, solana.PublicKey{}, 0, fmt.Errorf("invalid data length for token transfer instruction")
|
||||||
|
}
|
||||||
|
method := instr.Data[0]
|
||||||
|
if method != 3 && method != 12 { // Transfer instruction
|
||||||
|
return solana.PublicKey{}, solana.PublicKey{}, 0, fmt.Errorf("not a token transfer instruction")
|
||||||
|
}
|
||||||
|
if method == 3 {
|
||||||
|
// Transfer
|
||||||
|
amount = binary.LittleEndian.Uint64(instr.Data[1:9])
|
||||||
|
from = tx.accountList[instr.Accounts[0]]
|
||||||
|
to = tx.accountList[instr.Accounts[1]]
|
||||||
|
} else {
|
||||||
|
// TransferChecked
|
||||||
|
amount = binary.LittleEndian.Uint64(instr.Data[1:9])
|
||||||
|
from = tx.accountList[instr.Accounts[0]]
|
||||||
|
to = tx.accountList[instr.Accounts[2]]
|
||||||
|
}
|
||||||
|
|
||||||
|
return from, to, amount, nil
|
||||||
|
}
|
||||||
|
|
||||||
func isMayhemPump(feeAccount solana.PublicKey) bool {
|
func isMayhemPump(feeAccount solana.PublicKey) bool {
|
||||||
for _, mayhemFeeAccount := range mayhemFeeAccounts {
|
for _, mayhemFeeAccount := range mayhemFeeAccounts {
|
||||||
if feeAccount.Equals(mayhemFeeAccount) {
|
if feeAccount.Equals(mayhemFeeAccount) {
|
||||||
|
|||||||
47
go.mod
47
go.mod
@@ -9,20 +9,28 @@ require (
|
|||||||
github.com/mr-tron/base58 v1.2.0
|
github.com/mr-tron/base58 v1.2.0
|
||||||
github.com/shopspring/decimal v1.4.0
|
github.com/shopspring/decimal v1.4.0
|
||||||
go.onsig.ai/onsig/yellowstone-proto v1.0.0
|
go.onsig.ai/onsig/yellowstone-proto v1.0.0
|
||||||
google.golang.org/grpc v1.77.0
|
google.golang.org/grpc v1.78.0
|
||||||
|
gorm.io/driver/postgres v1.6.0
|
||||||
|
gorm.io/gorm v1.31.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
filippo.io/edwards25519 v1.0.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
|
github.com/benbjohnson/clock v1.3.5 // indirect
|
||||||
github.com/blendle/zapdriver v1.3.1 // indirect
|
github.com/blendle/zapdriver v1.3.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/fatih/color v1.9.0 // indirect
|
github.com/fatih/color v1.18.0 // indirect
|
||||||
github.com/gagliardetto/treeout v0.1.4 // indirect
|
github.com/gagliardetto/treeout v0.1.4 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/jackc/pgio v1.0.0 // indirect
|
github.com/jackc/pgio v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
|
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.13.6 // indirect
|
github.com/klauspost/compress v1.18.2 // indirect
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
|
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
@@ -30,18 +38,19 @@ require (
|
|||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 // indirect
|
github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 // indirect
|
||||||
github.com/streamingfast/logging v0.0.0-20250918142248-ac5a1e292845 // indirect
|
github.com/streamingfast/logging v0.0.0-20251216203033-fdad0a00f1ca // indirect
|
||||||
go.mongodb.org/mongo-driver v1.12.2 // indirect
|
go.mongodb.org/mongo-driver v1.17.6 // indirect
|
||||||
go.uber.org/atomic v1.7.0 // indirect
|
go.uber.org/atomic v1.11.0 // indirect
|
||||||
go.uber.org/multierr v1.6.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.uber.org/ratelimit v0.2.0 // indirect
|
go.uber.org/ratelimit v0.3.1 // indirect
|
||||||
go.uber.org/zap v1.21.0 // indirect
|
go.uber.org/zap v1.27.1 // indirect
|
||||||
golang.org/x/crypto v0.43.0 // indirect
|
golang.org/x/crypto v0.46.0 // indirect
|
||||||
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect
|
golang.org/x/net v0.48.0 // indirect
|
||||||
golang.org/x/sys v0.37.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/term v0.36.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.30.0 // indirect
|
golang.org/x/term v0.38.0 // indirect
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.10 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
|
||||||
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
110
go.sum
110
go.sum
@@ -1,13 +1,12 @@
|
|||||||
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI=
|
github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI=
|
||||||
github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE=
|
github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI=
|
|
||||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg=
|
|
||||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
|
||||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||||
|
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
|
||||||
|
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||||
github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE=
|
github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE=
|
||||||
github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc=
|
github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc=
|
||||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||||
@@ -17,8 +16,8 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||||
github.com/gagliardetto/binary v0.8.0 h1:U9ahc45v9HW0d15LoN++vIXSJyqR/pWw8DDlhd7zvxg=
|
github.com/gagliardetto/binary v0.8.0 h1:U9ahc45v9HW0d15LoN++vIXSJyqR/pWw8DDlhd7zvxg=
|
||||||
github.com/gagliardetto/binary v0.8.0/go.mod h1:2tfj51g5o9dnvsc+fL3Jxr22MuWzYXwx9wEoN0XQ7/c=
|
github.com/gagliardetto/binary v0.8.0/go.mod h1:2tfj51g5o9dnvsc+fL3Jxr22MuWzYXwx9wEoN0XQ7/c=
|
||||||
github.com/gagliardetto/solana-go v1.14.0 h1:3WfAi70jOOjAJ0deFMjdhFYlLXATF4tOQXsDNWJtOLw=
|
github.com/gagliardetto/solana-go v1.14.0 h1:3WfAi70jOOjAJ0deFMjdhFYlLXATF4tOQXsDNWJtOLw=
|
||||||
@@ -35,8 +34,6 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
|
|||||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
@@ -74,8 +71,9 @@ github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwX
|
|||||||
github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
|
github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
|
||||||
github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
||||||
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||||
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||||
@@ -89,16 +87,24 @@ github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQ
|
|||||||
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
||||||
github.com/jackc/pgx/v4 v4.18.2 h1:xVpYkNR5pk5bMCZGfClbO962UIqVABcAGt7ha1s/FeU=
|
github.com/jackc/pgx/v4 v4.18.2 h1:xVpYkNR5pk5bMCZGfClbO962UIqVABcAGt7ha1s/FeU=
|
||||||
github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
|
github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
|
||||||
|
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
|
||||||
|
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
|
||||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
|
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
@@ -114,14 +120,11 @@ github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
|||||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
|
||||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
|
||||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
@@ -132,7 +135,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
|||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
|
||||||
github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 h1:mPMvm6X6tf4w8y7j9YIt6V9jfWhL6QlbEc7CCmeQlWk=
|
github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 h1:mPMvm6X6tf4w8y7j9YIt6V9jfWhL6QlbEc7CCmeQlWk=
|
||||||
github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1/go.mod h1:ye2e/VUEtE2BHE+G/QcKkcLQVAEJoYRFj5VUOQatCRE=
|
github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1/go.mod h1:ye2e/VUEtE2BHE+G/QcKkcLQVAEJoYRFj5VUOQatCRE=
|
||||||
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
||||||
@@ -140,8 +142,6 @@ github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjW
|
|||||||
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
@@ -157,8 +157,8 @@ github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+D
|
|||||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091/go.mod h1:VlduQ80JcGJSargkRU4Sg9Xo63wZD/l8A5NC/Uo1/uU=
|
github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091/go.mod h1:VlduQ80JcGJSargkRU4Sg9Xo63wZD/l8A5NC/Uo1/uU=
|
||||||
github.com/streamingfast/logging v0.0.0-20250918142248-ac5a1e292845 h1:VMA0pZ3MI8BErRA3kh8dKJThP5d0Xh5vZVk5yFIgH/A=
|
github.com/streamingfast/logging v0.0.0-20251216203033-fdad0a00f1ca h1:D9r6WXATiqumhUTqSysurIi3N50z4orVBW+TEMp50Q4=
|
||||||
github.com/streamingfast/logging v0.0.0-20250918142248-ac5a1e292845/go.mod h1:BtDq81Tyc7H8up5aXNi/I95nPmG3C0PLEqGWY/iWQ2E=
|
github.com/streamingfast/logging v0.0.0-20251216203033-fdad0a00f1ca/go.mod h1:fJ5nP7ZSMB4MQQ6RM7cF+LiSQ43b5cVletcSUNL8z2M=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
@@ -168,7 +168,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
|||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
@@ -176,15 +175,11 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs
|
|||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
|
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
|
||||||
github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
|
github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
|
||||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
|
||||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
|
||||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||||
go.mongodb.org/mongo-driver v1.12.2 h1:gbWY1bJkkmUB9jjZzcdhOL8O85N9H+Vvsf2yFN0RDws=
|
go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss=
|
||||||
go.mongodb.org/mongo-driver v1.12.2/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ=
|
go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||||
go.onsig.ai/onsig/yellowstone-proto v1.0.0 h1:+XBNIoyl3HoQGBhgWCf8Ma3zNoUHKorFV8tR+HnE4Lw=
|
go.onsig.ai/onsig/yellowstone-proto v1.0.0 h1:+XBNIoyl3HoQGBhgWCf8Ma3zNoUHKorFV8tR+HnE4Lw=
|
||||||
go.onsig.ai/onsig/yellowstone-proto v1.0.0/go.mod h1:e5dlYkNpgNHtiXFwPmPDZRf4PrCsgNaSoA8iG4rfiKA=
|
go.onsig.ai/onsig/yellowstone-proto v1.0.0/go.mod h1:e5dlYkNpgNHtiXFwPmPDZRf4PrCsgNaSoA8iG4rfiKA=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
@@ -203,23 +198,27 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
|||||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
|
||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0=
|
||||||
|
go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk=
|
||||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||||
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
|
|
||||||
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||||
|
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||||
|
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
@@ -231,11 +230,10 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
|
|||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
|
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
|
||||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
@@ -253,12 +251,14 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
|||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo=
|
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||||
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -280,30 +280,29 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
@@ -320,17 +319,16 @@ golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8T
|
|||||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
@@ -343,4 +341,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||||
|
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||||
|
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||||
|
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -38,48 +46,14 @@ func main() {
|
|||||||
//if tx.Token0Address != "HRHLDjqFBhNeyTXUuZQE9gTy5z2112qeQBS9U79NHyyp" {
|
//if tx.Token0Address != "HRHLDjqFBhNeyTXUuZQE9gTy5z2112qeQBS9U79NHyyp" {
|
||||||
// continue
|
// continue
|
||||||
//}
|
//}
|
||||||
//if tx.Program != parser.SolProgramPump {
|
|
||||||
// continue
|
|
||||||
//}
|
|
||||||
//if currentBlock == ptx.Block {
|
//if currentBlock == ptx.Block {
|
||||||
// continue
|
// continue
|
||||||
//}
|
//}
|
||||||
|
|
||||||
// 处理交易
|
// 处理交易
|
||||||
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.SolProgramPump {
|
|
||||||
// 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, \n",
|
|
||||||
time.Now().Format(time.RFC3339Nano),
|
|
||||||
tx.Block, tx.GetTxHash(), tx.Maker, tx.Program, tx.Event, tx.Token0Amount, tx.EntryContract, tx.AfterSignerToken0Balance)
|
|
||||||
//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{
|
||||||
|
|||||||
866
internal/test/test.go
Normal file
866
internal/test/test.go
Normal file
@@ -0,0 +1,866 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"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 (
|
||||||
|
blockFlag = flag.Uint64("block", 0, "block number to process")
|
||||||
|
blockRange = flag.Uint64("range", 100, "number of blocks to compare")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
slot := *blockFlag
|
||||||
|
if slot == 0 {
|
||||||
|
fmt.Println("please provide a valid block number using -block flag")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client := rpc.New("http://127.0.0.1:18899")
|
||||||
|
dsn := "host=10.180.183.27 user=postgres password=123456789 dbname=solana port=5432 sslmode=disable TimeZone=UTC"
|
||||||
|
db := NewGorm(dsn)
|
||||||
|
for {
|
||||||
|
if slot > *blockFlag+*blockRange {
|
||||||
|
fmt.Printf("compare done for blocks %d to %d\n", *blockFlag, slot-1)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
dbTxs, err := getBlockTxsFromDb(db, slot)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("get block txs error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dbAction, err := getBlockActionsFromDb(db, slot)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("get block actions error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var data = NewBlockData(decimal.NewFromFloat(100.0))
|
||||||
|
|
||||||
|
var rewards = false
|
||||||
|
var version uint64 = 0
|
||||||
|
blocks, err := client.GetBlockWithOpts(context.Background(), slot, &rpc.GetBlockOpts{
|
||||||
|
TransactionDetails: rpc.TransactionDetailsFull,
|
||||||
|
Rewards: &rewards,
|
||||||
|
Commitment: rpc.CommitmentFinalized,
|
||||||
|
Encoding: solana.EncodingBase64,
|
||||||
|
MaxSupportedTransactionVersion: &version,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
slot++
|
||||||
|
fmt.Println("get block error:", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
solana_parser.EnableAllParsers()
|
||||||
|
|
||||||
|
var txs []*solana_parser.Tx
|
||||||
|
for i, tx := range blocks.Transactions {
|
||||||
|
var blockTime uint64
|
||||||
|
if blocks.BlockTime != nil {
|
||||||
|
blockTime = uint64(*blocks.BlockTime)
|
||||||
|
}
|
||||||
|
rawTx, err := solana_parser.FromRpcTransactionWithMeta(tx, &blockTime, slot, int64(i))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("from rpc tx error:", i, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if rawTx.Meta.Err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parsedTx, err := solana_parser.ParseRawTx(rawTx)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("parse tx error:", i, rawTx.TxHash(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
txs = append(txs, parsedTx)
|
||||||
|
}
|
||||||
|
var parseErr bool
|
||||||
|
for _, result := range txs {
|
||||||
|
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)
|
||||||
|
parseErr = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println("slot", slot, "tx count: ", len(data.Txs))
|
||||||
|
|
||||||
|
// compare db and parsed data
|
||||||
|
_, _ = compareTxs(dbTxs, data.Txs)
|
||||||
|
_, miss2 := compareActions(dbAction, data.Actions)
|
||||||
|
if miss2 > 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if parseErr {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond * 200)
|
||||||
|
slot++
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Program == solana_parser.SolProgramMeteoraBondingCurve && a.Event == "create") || 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)) &&
|
||||||
|
((a.Token1Amount.LessThan(decimal.NewFromInt(10)) && b.Token1Amount.LessThan(decimal.NewFromInt(10))) || 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,
|
||||||
|
)
|
||||||
|
}
|
||||||
826
internal/test2/test.go
Normal file
826
internal/test2/test.go
Normal file
@@ -0,0 +1,826 @@
|
|||||||
|
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 slot uint64 = 403021435
|
||||||
|
var data = NewBlockData(decimal.NewFromFloat(100.0))
|
||||||
|
client := rpc.New("https://staked.helius-rpc.com?api-key=5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d")
|
||||||
|
var rewards = false
|
||||||
|
var version uint64 = 0
|
||||||
|
blocks, err := client.GetBlockWithOpts(context.Background(), slot, &rpc.GetBlockOpts{
|
||||||
|
TransactionDetails: rpc.TransactionDetailsFull,
|
||||||
|
Rewards: &rewards,
|
||||||
|
Commitment: rpc.CommitmentFinalized,
|
||||||
|
Encoding: solana.EncodingBase64,
|
||||||
|
MaxSupportedTransactionVersion: &version,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
slot++
|
||||||
|
fmt.Println("get block error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
solana_parser.EnableAllParsers()
|
||||||
|
|
||||||
|
var txs []*solana_parser.Tx
|
||||||
|
for i, tx := range blocks.Transactions {
|
||||||
|
var blockTime uint64
|
||||||
|
if blocks.BlockTime != nil {
|
||||||
|
blockTime = uint64(*blocks.BlockTime)
|
||||||
|
}
|
||||||
|
rawTx, err := solana_parser.FromRpcTransactionWithMeta(tx, &blockTime, slot, int64(i))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("from rpc tx error:", i, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
//if rawTx.Meta.Err != nil {
|
||||||
|
// continue
|
||||||
|
//}
|
||||||
|
parsedTx, err := solana_parser.ParseRawTx(rawTx)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("parse tx error:", i, rawTx.TxHash(), err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
txs = append(txs, parsedTx)
|
||||||
|
}
|
||||||
|
for _, result := range txs {
|
||||||
|
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("slot", slot, "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,
|
||||||
|
)
|
||||||
|
}
|
||||||
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
197
meta.go
197
meta.go
@@ -35,8 +35,12 @@ var pumpMigrateEventDiscriminator = calculateDiscriminator("event:CompletePumpAm
|
|||||||
var pumpBuyEventDiscriminator = [8]byte{189, 219, 127, 211, 78, 230, 97, 238}
|
var pumpBuyEventDiscriminator = [8]byte{189, 219, 127, 211, 78, 230, 97, 238}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
pumpAmmProgram = solana.MustPublicKeyFromBase58("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
|
pumpAmmProgram = solana.MustPublicKeyFromBase58("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
|
||||||
wSolMint = solana.MustPublicKeyFromBase58("So11111111111111111111111111111111111111112")
|
wSolMint = solana.MustPublicKeyFromBase58("So11111111111111111111111111111111111111112")
|
||||||
|
usdcMint = solana.MustPublicKeyFromBase58("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")
|
||||||
|
usd1Mint = solana.MustPublicKeyFromBase58("USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB")
|
||||||
|
meteoraDlmmProgram = solana.MustPublicKeyFromBase58("LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo")
|
||||||
|
meteoraDammV2Program = solana.MustPublicKeyFromBase58("cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG")
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -63,6 +67,190 @@ var (
|
|||||||
pumpAmmDepositEventDiscriminator = calculateDiscriminator("event:DepositEvent")
|
pumpAmmDepositEventDiscriminator = calculateDiscriminator("event:DepositEvent")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
meteoraInitializeCustomizablePermissionlessLbPairDiscriminator = calculateDiscriminator("global:initialize_customizable_permissionless_lb_pair")
|
||||||
|
meteoraInitializeCustomizablePermissionlessLbPair2Discriminator = calculateDiscriminator("global:initialize_customizable_permissionless_lb_pair2")
|
||||||
|
meteoraInitializeLbPairDiscriminator = calculateDiscriminator("global:initialize_lb_pair")
|
||||||
|
meteoraInitializeLbPair2Discriminator = calculateDiscriminator("global:initialize_lb_pair2")
|
||||||
|
meteoraInitializePermissionLbPairDiscriminator = calculateDiscriminator("global:initialize_permission_lb_pair")
|
||||||
|
meteoraInitializeLbPairEventDiscriminator = calculateDiscriminator("event:LbPairCreate")
|
||||||
|
meteoraDlmmSwapDiscriminator = calculateDiscriminator("global:swap")
|
||||||
|
meteoraDlmmSwap2Discriminator = calculateDiscriminator("global:swap2")
|
||||||
|
meteoraDlmmSwapExactOutDiscriminator = calculateDiscriminator("global:swap_exact_out")
|
||||||
|
meteoraDlmmSwapExactOut2Discriminator = calculateDiscriminator("global:swap_exact_out2")
|
||||||
|
meteoraDlmmSwapWithPriceImpactDiscriminator = calculateDiscriminator("global:swap_with_price_impact")
|
||||||
|
meteoraDlmmSwapWithPriceImpact2Discriminator = calculateDiscriminator("global:swap_with_price_impact2")
|
||||||
|
meteoraDlmmInitializePositionDiscriminator = calculateDiscriminator("global:initialize_position")
|
||||||
|
meteoraDlmmInitializePosition2Discriminator = calculateDiscriminator("global:initialize_position2")
|
||||||
|
meteoraDlmmInitializePositionByOperatorDiscriminator = calculateDiscriminator("global:initialize_position_by_operator")
|
||||||
|
meteoraDlmmInitializePositionPdaDiscriminator = calculateDiscriminator("global:initialize_position_pda")
|
||||||
|
meteoraDlmmClosePositionDiscriminator = calculateDiscriminator("global:close_position")
|
||||||
|
meteoraDlmmClosePosition2Discriminator = calculateDiscriminator("global:close_position2")
|
||||||
|
meteoraDlmmClosePositionIfEmptyDiscriminator = calculateDiscriminator("global:close_position_if_empty")
|
||||||
|
meteoraDlmmSwapEventDiscriminator = calculateDiscriminator("event:Swap")
|
||||||
|
meteoraDlmmAddLiquidityDiscriminator = calculateDiscriminator("global:add_liquidity")
|
||||||
|
meteoraDlmmAddLiquidity2Discriminator = calculateDiscriminator("global:add_liquidity2")
|
||||||
|
meteoraDlmmAddLiquidityByStrategyDiscriminator = calculateDiscriminator("global:add_liquidity_by_strategy")
|
||||||
|
meteoraDlmmAddLiquidityByStrategy2Discriminator = calculateDiscriminator("global:add_liquidity_by_strategy2")
|
||||||
|
meteoraDlmmAddLiquidityByWeightDiscriminator = calculateDiscriminator("global:add_liquidity_by_weight")
|
||||||
|
meteoraDlmmAddLiquidityOneSideDiscriminator = calculateDiscriminator("global:add_liquidity_one_side")
|
||||||
|
meteoraDlmmAddLiquidityOneSidePreciseDiscriminator = calculateDiscriminator("global:add_liquidity_one_side_precise")
|
||||||
|
meteoraDlmmAddLiquidityOneSidePrecise2Discriminator = calculateDiscriminator("global:add_liquidity_one_side_precise2")
|
||||||
|
meteoraDlmmAddLiquidityByStrategyOneSideDiscriminator = calculateDiscriminator("global:add_liquidity_by_strategy_one_side")
|
||||||
|
meteoraDlmmClaimFeeDiscriminator = calculateDiscriminator("global:claim_fee")
|
||||||
|
meteoraDlmmClaimFee2Discriminator = calculateDiscriminator("global:claim_fee2")
|
||||||
|
meteoraDlmmRebalanceLiquidityDiscriminator = calculateDiscriminator("global:rebalance_liquidity")
|
||||||
|
meteoraDlmmRemoveAllLiquidityDiscriminator = calculateDiscriminator("global:remove_all_liquidity")
|
||||||
|
meteoraDlmmRemoveLiquidityDiscriminator = calculateDiscriminator("global:remove_liquidity")
|
||||||
|
meteoraDlmmRemoveLiquidity2Discriminator = calculateDiscriminator("global:remove_liquidity2")
|
||||||
|
meteoraDlmmRemoveLiquidityByRangeDiscriminator = calculateDiscriminator("global:remove_liquidity_by_range")
|
||||||
|
meteoraDlmmRemoveLiquidityByRange2Discriminator = calculateDiscriminator("global:remove_liquidity_by_range2")
|
||||||
|
meteoraDlmmAddLiquidityEventDiscriminator = calculateDiscriminator("event:AddLiquidity")
|
||||||
|
meteoraDlmmClaimFeeEventDiscriminator = calculateDiscriminator("event:ClaimFee")
|
||||||
|
meteoraDlmmClaimFee2EventDiscriminator = calculateDiscriminator("event:ClaimFee2")
|
||||||
|
meteoraDlmmPositionCloseEventDiscriminator = calculateDiscriminator("event:PositionClose")
|
||||||
|
meteoraDlmmPositionCreateEventDiscriminator = calculateDiscriminator("event:PositionCreate")
|
||||||
|
meteoraDlmmRebalancingEventDiscriminator = calculateDiscriminator("event:Rebalancing")
|
||||||
|
meteoraDlmmRemoveLiquidityEventDiscriminator = calculateDiscriminator("event:RemoveLiquidity")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// metaora pool
|
||||||
|
metaoraPoolProgramID = solana.MustPublicKeyFromBase58("Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB")
|
||||||
|
|
||||||
|
metaoraPoolInitializePermissionedPoolDiscriminator = calculateDiscriminator("global:initialize_permissioned_pool")
|
||||||
|
metaoraPoolInitializePermissionlessPoolDiscriminator = calculateDiscriminator("global:initialize_permissionless_pool")
|
||||||
|
metaoraPoolInitializePermissionlessPoolWithFeeTierDiscriminator = calculateDiscriminator("global:initialize_permissionless_pool_with_fee_tier")
|
||||||
|
metaoraPoolInitializePermissionlessConstantProductPoolWithConfigDiscriminator = calculateDiscriminator("global:initialize_permissionless_constant_product_pool_with_config")
|
||||||
|
metaoraPoolInitializePermissionlessConstantProductPoolWithConfig2Discriminator = calculateDiscriminator("global:initialize_permissionless_constant_product_pool_with_config2")
|
||||||
|
metaoraPoolInitializeCustomizablePermissionlessConstantProductPoolDiscriminator = calculateDiscriminator("global:initialize_customizable_permissionless_constant_product_pool")
|
||||||
|
|
||||||
|
metaoraPoolSwapDiscriminator = calculateDiscriminator("global:swap")
|
||||||
|
metaoraPoolAddImbalanceLiquidityDiscriminator = calculateDiscriminator("global:add_imbalance_liquidity")
|
||||||
|
metaoraPoolAddBalanceLiquidityDiscriminator = calculateDiscriminator("global:add_balance_liquidity")
|
||||||
|
metaoraPoolRemoveLiquiditySingleSideDiscriminator = calculateDiscriminator("global:remove_liquidity_single_side")
|
||||||
|
metaoraPoolRemoveBalanceLiquidityDiscriminator = calculateDiscriminator("global:remove_balance_liquidity")
|
||||||
|
metaoraPoolClaimFeeDiscriminator = calculateDiscriminator("global:claim_fee")
|
||||||
|
metaoraPoolBootstrapLiquidityDiscriminator = calculateDiscriminator("global:bootstrap_liquidity")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
metaoraBcProgramID = solana.MustPublicKeyFromBase58("dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN")
|
||||||
|
|
||||||
|
metaoraBcInitializedPoolDiscriminator = calculateDiscriminator("global:initialize_virtual_pool_with_spl_token")
|
||||||
|
metaoraBcInitialize2022PoolDiscriminator = calculateDiscriminator("global:initialize_virtual_pool_with_token2022")
|
||||||
|
metaoraBcMigrateMeteoraDammDiscriminator = calculateDiscriminator("global:migrate_meteora_damm")
|
||||||
|
metaoraBcMigrateMeteoraDammV2Discriminator = calculateDiscriminator("global:migration_damm_v2")
|
||||||
|
metaoraBcSwapDiscriminator = calculateDiscriminator("global:swap")
|
||||||
|
metaoraBcSwapV2Discriminator = calculateDiscriminator("global:swap2")
|
||||||
|
metaoraBcEventInitializePoolDiscriminator = [8]byte{228, 50, 246, 85, 203, 66, 134, 37}
|
||||||
|
metaoraBcEventSwapDiscriminator = [8]byte{27, 60, 21, 213, 138, 170, 187, 147}
|
||||||
|
metaoraBcEventSwap2Discriminator = [8]byte{189, 66, 51, 168, 38, 80, 117, 153}
|
||||||
|
metaoraBcEventCompleteDiscriminator = [8]byte{229, 231, 86, 84, 156, 134, 75, 24}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
meteoraDammV2AddLiquidityDiscriminator = calculateDiscriminator("global:add_liquidity")
|
||||||
|
meteoraDammV2RemoveLiquidityDiscriminator = calculateDiscriminator("global:remove_liquidity")
|
||||||
|
meteoraDammV2RemoveAllLiquidityDiscriminator = calculateDiscriminator("global:remove_all_liquidity")
|
||||||
|
meteoraDammV2SwapDiscriminator = calculateDiscriminator("global:swap")
|
||||||
|
meteoraDammV2SwapV2Discriminator = calculateDiscriminator("global:swap2")
|
||||||
|
meteoraDammV2InitializeCustomizablePoolDiscriminator = calculateDiscriminator("global:initialize_customizable_pool")
|
||||||
|
meteoraDammV2InitializePoolWithDynamicConfig = calculateDiscriminator("global:initialize_pool_with_dynamic_config")
|
||||||
|
meteoraDammV2InitializePoolDiscriminator = calculateDiscriminator("global:initialize_pool")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
orcaProgramID = solana.MustPublicKeyFromBase58("whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc")
|
||||||
|
|
||||||
|
orcaInitializePoolDiscriminator = calculateDiscriminator("global:initialize_pool")
|
||||||
|
orcaInitializePoolV2Discriminator = calculateDiscriminator("global:initialize_pool_v2")
|
||||||
|
orcaInitializePoolWithAdaptiveFeeDiscriminator = calculateDiscriminator("global:initialize_pool_with_adaptive_fee")
|
||||||
|
|
||||||
|
orcaIncreaseLiquidityDiscriminator = calculateDiscriminator("global:increase_liquidity")
|
||||||
|
orcaDecreaseLiquidityDiscriminator = calculateDiscriminator("global:decrease_liquidity")
|
||||||
|
orcaDecreaseLiquidityV2Discriminator = calculateDiscriminator("global:decrease_liquidity_v2")
|
||||||
|
orcaIncreaseLiquidityV2Discriminator = calculateDiscriminator("global:increase_liquidity_v2")
|
||||||
|
|
||||||
|
orcaCollectFeesDiscriminator = calculateDiscriminator("global:collect_fees")
|
||||||
|
orcaCollectProtocolFeesDiscriminator = calculateDiscriminator("global:collect_protocol_fees")
|
||||||
|
orcaCollectFeesV2Discriminator = calculateDiscriminator("global:collect_fees_v2")
|
||||||
|
orcaCollectProtocolFeesV2Discriminator = calculateDiscriminator("global:collect_protocol_fees_v2")
|
||||||
|
|
||||||
|
orcaSwapDiscriminator = calculateDiscriminator("global:swap")
|
||||||
|
orcaTwoHopSwapDiscriminator = calculateDiscriminator("global:two_hop_swap")
|
||||||
|
orcaSwapV2Discriminator = calculateDiscriminator("global:swap_v2")
|
||||||
|
orcaTwoHopSwapV2Discriminator = calculateDiscriminator("global:two_hop_swap_v2")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
raydiumClmmProgramID solana.PublicKey = solana.MustPublicKeyFromBase58("CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK")
|
||||||
|
|
||||||
|
raydiumClmmCreatePoolDiscriminator = calculateDiscriminator("global:create_pool")
|
||||||
|
raydiumClmmCollectProtocolFeeDiscriminator = calculateDiscriminator("global:collect_protocol_fee")
|
||||||
|
raydiumClmmCollectFundFeeDiscriminator = calculateDiscriminator("global:collect_fund_fee")
|
||||||
|
raydiumClmmOpenPositionDiscriminator = calculateDiscriminator("global:open_position")
|
||||||
|
raydiumClmmOpenPositionV2Discriminator = calculateDiscriminator("global:open_position_v2")
|
||||||
|
raydiumClmmOpenPositionWithToken22NftDiscriminator = calculateDiscriminator("global:open_position_with_token22_nft")
|
||||||
|
|
||||||
|
raydiumClmmIncreaseLiquidityDiscriminator = calculateDiscriminator("global:increase_liquidity")
|
||||||
|
raydiumClmmDecreaseLiquidityDiscriminator = calculateDiscriminator("global:decrease_liquidity")
|
||||||
|
raydiumClmmIncreaseLiquidityV2Discriminator = calculateDiscriminator("global:increase_liquidity_v2")
|
||||||
|
raydiumClmmDecreaseLiquidityV2Discriminator = calculateDiscriminator("global:decrease_liquidity_v2")
|
||||||
|
raydiumClmmSwapDiscriminator = calculateDiscriminator("global:swap")
|
||||||
|
raydiumClmmSwapV2Discriminator = calculateDiscriminator("global:swap_v2")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
raydiumCPmmProgramID = solana.MustPublicKeyFromBase58("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C")
|
||||||
|
|
||||||
|
raydiumCPmmSwapBaseInputDiscriminator = [8]byte{143, 190, 90, 218, 196, 30, 51, 222}
|
||||||
|
raydiumCPmmSwapBaseOutputDiscriminator = [8]byte{55, 217, 98, 86, 163, 74, 180, 173}
|
||||||
|
|
||||||
|
raydiumCPmmWithdrawDiscriminator = [8]byte{183, 18, 70, 156, 148, 109, 161, 34}
|
||||||
|
|
||||||
|
raydiumCPmmDepositDiscriminator = [8]byte{242, 35, 198, 137, 82, 225, 242, 182}
|
||||||
|
|
||||||
|
raydiumCPmmCollectProtocolFeeDiscriminator = [8]byte{136, 136, 252, 221, 194, 66, 126, 89}
|
||||||
|
raydiumCPmmCollectFundFeeDiscriminator = [8]byte{167, 138, 78, 149, 223, 194, 6, 126}
|
||||||
|
|
||||||
|
raydiumCPmmInitializeDiscriminator = [8]byte{175, 175, 109, 31, 13, 152, 155, 237}
|
||||||
|
raydiumCPmmInitializeWithPermissionDiscriminator = calculateDiscriminator("global:initialize_with_permission")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
raydiumV4Program = solana.MustPublicKeyFromBase58("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
raydiumV4InitializePoolDiscriminator = uint8(1)
|
||||||
|
|
||||||
|
raydiumV4SwapBaseInDiscriminator = uint8(9)
|
||||||
|
raydiumV4SwapBaseOutDiscriminator = uint8(11)
|
||||||
|
raydiumV4SwapBaseInV2Discriminator = uint8(16)
|
||||||
|
raydiumV4SwapBaseOutV2Discriminator = uint8(17)
|
||||||
|
|
||||||
|
raydiumV4AddLiquidityDiscriminator = uint8(3)
|
||||||
|
raydiumV4RemoveLiquidityDiscriminator = uint8(4)
|
||||||
|
raydiumV4WithdrawPNLDiscriminator = uint8(7)
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
raydiumLaunchLabProgramID = solana.MustPublicKeyFromBase58("LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj")
|
||||||
|
bonkPlatformConfig = solana.MustPublicKeyFromBase58("FfYek5vEz23cMkWsdJwG2oa6EphsvXSHrGpdALN4g6W1")
|
||||||
|
|
||||||
|
raydiumLaunchLabCreatePoolEvnet = [8]byte{151, 215, 226, 9, 118, 161, 115, 174}
|
||||||
|
raydiumLaunchLabTradeEvnet = [8]byte{189, 219, 127, 211, 78, 230, 97, 238}
|
||||||
|
raydiumLaunchLabInitializeV2PoolDiscriminator = [8]byte{67, 153, 175, 39, 218, 16, 38, 32}
|
||||||
|
raydiumLaunchLabInitializeWithToken2022PoolDiscriminator = [8]byte{37, 190, 126, 222, 44, 154, 171, 17}
|
||||||
|
raydiumLaunchLabSellExactInDiscriminator = [8]byte{0x95, 0x27, 0xde, 0x9b, 0xd3, 0x7c, 0x98, 0x1a}
|
||||||
|
raydiumLaunchLabSellExactOutDiscriminator = [8]byte{0x5f, 0xc8, 0x47, 0x22, 0x08, 0x09, 0x0b, 0xa6}
|
||||||
|
raydiumLaunchLabBuyExactInDiscriminator = [8]byte{0xfa, 0xea, 0x0d, 0x7b, 0xd5, 0x9c, 0x13, 0xec}
|
||||||
|
raydiumLaunchLabBuyExactOutDiscriminator = [8]byte{0x18, 0xd3, 0x74, 0x28, 0x69, 0x03, 0x99, 0x38}
|
||||||
|
raydiumLaunchLabMigrateToAmmDiscriminator = [8]byte{0xcf, 0x52, 0xc0, 0x91, 0xfe, 0xcf, 0x91, 0xdf}
|
||||||
|
raydiumLaunchLabMigrateToCpmmDiscriminator = [8]byte{0x88, 0x5c, 0xc8, 0x67, 0x1c, 0xda, 0x90, 0x8c}
|
||||||
|
)
|
||||||
|
|
||||||
// Program PumpAmm program ID
|
// Program PumpAmm program ID
|
||||||
|
|
||||||
var budgGetProgram = solana.MustPublicKeyFromBase58("ComputeBudget111111111111111111111111111111")
|
var budgGetProgram = solana.MustPublicKeyFromBase58("ComputeBudget111111111111111111111111111111")
|
||||||
@@ -73,5 +261,8 @@ var transferDiscriminator = uint32(2)
|
|||||||
var createAccountWithSeedDiscriminator = uint32(3)
|
var createAccountWithSeedDiscriminator = uint32(3)
|
||||||
|
|
||||||
var systemProgram = solana.MustPublicKeyFromBase58("11111111111111111111111111111111")
|
var systemProgram = solana.MustPublicKeyFromBase58("11111111111111111111111111111111")
|
||||||
|
var momoProgram = solana.MustPublicKeyFromBase58("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr")
|
||||||
|
|
||||||
var raydiumLaunchLabProgramID = solana.MustPublicKeyFromBase58("LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj")
|
var chainLinkProgram = solana.MustPublicKeyFromBase58("cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ")
|
||||||
|
|
||||||
|
var eventDiscriminator = [8]byte{228, 69, 165, 46, 81, 203, 154, 29}
|
||||||
|
|||||||
2535
metaoradlmm.go
Normal file
2535
metaoradlmm.go
Normal file
File diff suppressed because it is too large
Load Diff
424
metaoradlmm_test.go
Normal file
424
metaoradlmm_test.go
Normal file
@@ -0,0 +1,424 @@
|
|||||||
|
package pump_parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
agbinary "github.com/gagliardetto/binary"
|
||||||
|
"github.com/gagliardetto/solana-go"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testPublicKey(seed byte) solana.PublicKey {
|
||||||
|
buf := make([]byte, solana.PublicKeyLength)
|
||||||
|
for i := range buf {
|
||||||
|
buf[i] = seed
|
||||||
|
}
|
||||||
|
return solana.PublicKeyFromBytes(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func seqInts(n int) []int {
|
||||||
|
out := make([]int, n)
|
||||||
|
for i := range out {
|
||||||
|
out[i] = i
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustBorshEncode(t *testing.T, value any) []byte {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := agbinary.NewBorshEncoder(&buf).Encode(value); err != nil {
|
||||||
|
t.Fatalf("borsh encode failed: %v", err)
|
||||||
|
}
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMeteoraDlmmInitializeParserCompatibility(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
discriminator [8]byte
|
||||||
|
accountCount int
|
||||||
|
wantPoolPos int
|
||||||
|
wantBaseMintPos int
|
||||||
|
wantQuoteMintPos int
|
||||||
|
wantUserPos int
|
||||||
|
wantBaseProgramPos int
|
||||||
|
wantQuoteProgramPos int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "initialize_lb_pair",
|
||||||
|
discriminator: meteoraInitializeLbPairDiscriminator,
|
||||||
|
accountCount: 14,
|
||||||
|
wantPoolPos: 0,
|
||||||
|
wantBaseMintPos: 2,
|
||||||
|
wantQuoteMintPos: 3,
|
||||||
|
wantUserPos: 8,
|
||||||
|
wantBaseProgramPos: 9,
|
||||||
|
wantQuoteProgramPos: 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "initialize_lb_pair2",
|
||||||
|
discriminator: meteoraInitializeLbPair2Discriminator,
|
||||||
|
accountCount: 16,
|
||||||
|
wantPoolPos: 0,
|
||||||
|
wantBaseMintPos: 2,
|
||||||
|
wantQuoteMintPos: 3,
|
||||||
|
wantUserPos: 8,
|
||||||
|
wantBaseProgramPos: 11,
|
||||||
|
wantQuoteProgramPos: 12,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "initialize_customizable_permissionless_lb_pair",
|
||||||
|
discriminator: meteoraInitializeCustomizablePermissionlessLbPairDiscriminator,
|
||||||
|
accountCount: 14,
|
||||||
|
wantPoolPos: 0,
|
||||||
|
wantBaseMintPos: 2,
|
||||||
|
wantQuoteMintPos: 3,
|
||||||
|
wantUserPos: 8,
|
||||||
|
wantBaseProgramPos: 9,
|
||||||
|
wantQuoteProgramPos: 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "initialize_customizable_permissionless_lb_pair2",
|
||||||
|
discriminator: meteoraInitializeCustomizablePermissionlessLbPair2Discriminator,
|
||||||
|
accountCount: 17,
|
||||||
|
wantPoolPos: 0,
|
||||||
|
wantBaseMintPos: 2,
|
||||||
|
wantQuoteMintPos: 3,
|
||||||
|
wantUserPos: 8,
|
||||||
|
wantBaseProgramPos: 11,
|
||||||
|
wantQuoteProgramPos: 12,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "initialize_permission_lb_pair",
|
||||||
|
discriminator: meteoraInitializePermissionLbPairDiscriminator,
|
||||||
|
accountCount: 17,
|
||||||
|
wantPoolPos: 1,
|
||||||
|
wantBaseMintPos: 3,
|
||||||
|
wantQuoteMintPos: 4,
|
||||||
|
wantUserPos: 8,
|
||||||
|
wantBaseProgramPos: 11,
|
||||||
|
wantQuoteProgramPos: 12,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
accountList := make([]solana.PublicKey, 32)
|
||||||
|
for i := range accountList {
|
||||||
|
accountList[i] = testPublicKey(byte(i + 1))
|
||||||
|
}
|
||||||
|
programIndex := 30
|
||||||
|
accountList[programIndex] = meteoraDlmmProgram
|
||||||
|
|
||||||
|
instruction := Instruction{
|
||||||
|
Accounts: seqInts(tc.accountCount),
|
||||||
|
Data: solana.Base58(tc.discriminator[:]),
|
||||||
|
ProgramIDIndex: programIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
rawTx := &RawTx{
|
||||||
|
accountList: accountList,
|
||||||
|
Meta: Meta{
|
||||||
|
PostTokenBalances: []TokenBalance{
|
||||||
|
{
|
||||||
|
MintAccount: accountList[tc.wantBaseMintPos],
|
||||||
|
UITokenAmount: UITokenAmount{
|
||||||
|
Decimals: 6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MintAccount: accountList[tc.wantQuoteMintPos],
|
||||||
|
UITokenAmount: UITokenAmount{
|
||||||
|
Decimals: 9,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Transaction: Transaction{
|
||||||
|
Message: Message{
|
||||||
|
Instructions: []Instruction{instruction},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tx := &Tx{rawTx: rawTx}
|
||||||
|
|
||||||
|
swaps, _, err := metaoradlmmParser(tx, instruction, InnerInstructions{}, [2]uint{0, 0})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("metaoradlmmParser() error = %v", err)
|
||||||
|
}
|
||||||
|
if len(swaps) != 1 {
|
||||||
|
t.Fatalf("metaoradlmmParser() swaps len = %d, want 1", len(swaps))
|
||||||
|
}
|
||||||
|
|
||||||
|
swap := swaps[0]
|
||||||
|
if !swap.Pool.Equals(accountList[tc.wantPoolPos]) {
|
||||||
|
t.Fatalf("swap.Pool = %s, want %s", swap.Pool, accountList[tc.wantPoolPos])
|
||||||
|
}
|
||||||
|
if !swap.BaseMint.Equals(accountList[tc.wantBaseMintPos]) {
|
||||||
|
t.Fatalf("swap.BaseMint = %s, want %s", swap.BaseMint, accountList[tc.wantBaseMintPos])
|
||||||
|
}
|
||||||
|
if !swap.QuoteMint.Equals(accountList[tc.wantQuoteMintPos]) {
|
||||||
|
t.Fatalf("swap.QuoteMint = %s, want %s", swap.QuoteMint, accountList[tc.wantQuoteMintPos])
|
||||||
|
}
|
||||||
|
if !swap.User.Equals(accountList[tc.wantUserPos]) {
|
||||||
|
t.Fatalf("swap.User = %s, want %s", swap.User, accountList[tc.wantUserPos])
|
||||||
|
}
|
||||||
|
if !swap.BaseTokenProgram.Equals(accountList[tc.wantBaseProgramPos]) {
|
||||||
|
t.Fatalf("swap.BaseTokenProgram = %s, want %s", swap.BaseTokenProgram, accountList[tc.wantBaseProgramPos])
|
||||||
|
}
|
||||||
|
if !swap.QuoteTokenProgram.Equals(accountList[tc.wantQuoteProgramPos]) {
|
||||||
|
t.Fatalf("swap.QuoteTokenProgram = %s, want %s", swap.QuoteTokenProgram, accountList[tc.wantQuoteProgramPos])
|
||||||
|
}
|
||||||
|
if swap.BaseMintDecimals != 6 {
|
||||||
|
t.Fatalf("swap.BaseMintDecimals = %d, want 6", swap.BaseMintDecimals)
|
||||||
|
}
|
||||||
|
if swap.QuoteMintDecimals != 9 {
|
||||||
|
t.Fatalf("swap.QuoteMintDecimals = %d, want 9", swap.QuoteMintDecimals)
|
||||||
|
}
|
||||||
|
if !swap.EntryContract.Equals(meteoraDlmmProgram) {
|
||||||
|
t.Fatalf("swap.EntryContract = %s, want %s", swap.EntryContract, meteoraDlmmProgram)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDlmmDecodeLbPairCreateEvent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
event := dlmmLbPairCreateEvent{
|
||||||
|
LbPair: testPublicKey(90),
|
||||||
|
BinStep: 42,
|
||||||
|
TokenX: testPublicKey(91),
|
||||||
|
TokenY: testPublicKey(92),
|
||||||
|
}
|
||||||
|
|
||||||
|
body := mustBorshEncode(t, event)
|
||||||
|
|
||||||
|
barePayload := append(append([]byte{}, meteoraInitializeLbPairEventDiscriminator[:]...), body...)
|
||||||
|
decodedBare, ok := dlmmDecodeLbPairCreateEvent(barePayload)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("dlmmDecodeLbPairCreateEvent() failed for bare payload")
|
||||||
|
}
|
||||||
|
if decodedBare != event {
|
||||||
|
t.Fatalf("decoded bare event = %+v, want %+v", decodedBare, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
anchorPayload := append(append(append([]byte{}, eventDiscriminator[:]...), meteoraInitializeLbPairEventDiscriminator[:]...), body...)
|
||||||
|
decodedAnchor, ok := dlmmDecodeLbPairCreateEvent(anchorPayload)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("dlmmDecodeLbPairCreateEvent() failed for anchor payload")
|
||||||
|
}
|
||||||
|
if decodedAnchor != event {
|
||||||
|
t.Fatalf("decoded anchor event = %+v, want %+v", decodedAnchor, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMeteoraDlmmInitializeParserUsesLbPairCreateEvent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
accountList := make([]solana.PublicKey, 32)
|
||||||
|
for i := range accountList {
|
||||||
|
accountList[i] = testPublicKey(byte(i + 1))
|
||||||
|
}
|
||||||
|
programIndex := 30
|
||||||
|
accountList[programIndex] = meteoraDlmmProgram
|
||||||
|
|
||||||
|
instruction := Instruction{
|
||||||
|
Accounts: seqInts(16),
|
||||||
|
Data: solana.Base58(meteoraInitializeLbPair2Discriminator[:]),
|
||||||
|
ProgramIDIndex: programIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
event := dlmmLbPairCreateEvent{
|
||||||
|
LbPair: testPublicKey(111),
|
||||||
|
BinStep: 25,
|
||||||
|
TokenX: testPublicKey(112),
|
||||||
|
TokenY: testPublicKey(113),
|
||||||
|
}
|
||||||
|
innerEventData := append(
|
||||||
|
append(append([]byte{}, eventDiscriminator[:]...), meteoraInitializeLbPairEventDiscriminator[:]...),
|
||||||
|
mustBorshEncode(t, event)...,
|
||||||
|
)
|
||||||
|
|
||||||
|
rawTx := &RawTx{
|
||||||
|
accountList: accountList,
|
||||||
|
Meta: Meta{
|
||||||
|
PostTokenBalances: []TokenBalance{
|
||||||
|
{
|
||||||
|
MintAccount: accountList[2],
|
||||||
|
UITokenAmount: UITokenAmount{
|
||||||
|
Decimals: 6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MintAccount: accountList[3],
|
||||||
|
UITokenAmount: UITokenAmount{
|
||||||
|
Decimals: 9,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
InnerInstructions: []InnerInstructions{
|
||||||
|
{
|
||||||
|
Index: 0,
|
||||||
|
Instructions: []Instruction{
|
||||||
|
{
|
||||||
|
ProgramIDIndex: programIndex,
|
||||||
|
Data: solana.Base58(innerEventData),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Transaction: Transaction{
|
||||||
|
Message: Message{
|
||||||
|
Instructions: []Instruction{instruction},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tx := &Tx{rawTx: rawTx}
|
||||||
|
|
||||||
|
swaps, nextOffset, err := metaoradlmmParser(tx, instruction, rawTx.Meta.InnerInstructions[0], [2]uint{0, 0})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("metaoradlmmParser() error = %v", err)
|
||||||
|
}
|
||||||
|
if len(swaps) != 1 {
|
||||||
|
t.Fatalf("metaoradlmmParser() swaps len = %d, want 1", len(swaps))
|
||||||
|
}
|
||||||
|
|
||||||
|
swap := swaps[0]
|
||||||
|
if !swap.Pool.Equals(event.LbPair) {
|
||||||
|
t.Fatalf("swap.Pool = %s, want event %s", swap.Pool, event.LbPair)
|
||||||
|
}
|
||||||
|
if !swap.BaseMint.Equals(event.TokenX) {
|
||||||
|
t.Fatalf("swap.BaseMint = %s, want event %s", swap.BaseMint, event.TokenX)
|
||||||
|
}
|
||||||
|
if !swap.QuoteMint.Equals(event.TokenY) {
|
||||||
|
t.Fatalf("swap.QuoteMint = %s, want event %s", swap.QuoteMint, event.TokenY)
|
||||||
|
}
|
||||||
|
if nextOffset != ([2]uint{1, 0}) {
|
||||||
|
t.Fatalf("nextOffset = %#v, want [2]uint{1, 0}", nextOffset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDlmmSwapFeeInfo(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
baseMint := testPublicKey(1)
|
||||||
|
quoteMint := testPublicKey(2)
|
||||||
|
baseProgram := testPublicKey(3)
|
||||||
|
quoteProgram := testPublicKey(4)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
baseIsX bool
|
||||||
|
swapForY bool
|
||||||
|
wantFeeSide string
|
||||||
|
wantFeeMint solana.PublicKey
|
||||||
|
wantFeeProg solana.PublicKey
|
||||||
|
wantDecimals uint8
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "x is base and input is x",
|
||||||
|
baseIsX: true,
|
||||||
|
swapForY: true,
|
||||||
|
wantFeeSide: "base",
|
||||||
|
wantFeeMint: baseMint,
|
||||||
|
wantFeeProg: baseProgram,
|
||||||
|
wantDecimals: 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "x is base and input is y",
|
||||||
|
baseIsX: true,
|
||||||
|
swapForY: false,
|
||||||
|
wantFeeSide: "quote",
|
||||||
|
wantFeeMint: quoteMint,
|
||||||
|
wantFeeProg: quoteProgram,
|
||||||
|
wantDecimals: 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "y is base and input is x",
|
||||||
|
baseIsX: false,
|
||||||
|
swapForY: true,
|
||||||
|
wantFeeSide: "quote",
|
||||||
|
wantFeeMint: quoteMint,
|
||||||
|
wantFeeProg: quoteProgram,
|
||||||
|
wantDecimals: 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "y is base and input is y",
|
||||||
|
baseIsX: false,
|
||||||
|
swapForY: false,
|
||||||
|
wantFeeSide: "base",
|
||||||
|
wantFeeMint: baseMint,
|
||||||
|
wantFeeProg: baseProgram,
|
||||||
|
wantDecimals: 6,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
feeAmount, feeSide, feeMint, feeProgram, feeDecimals := dlmmSwapFeeInfo(
|
||||||
|
tc.baseIsX,
|
||||||
|
tc.swapForY,
|
||||||
|
123,
|
||||||
|
baseMint,
|
||||||
|
quoteMint,
|
||||||
|
baseProgram,
|
||||||
|
quoteProgram,
|
||||||
|
6,
|
||||||
|
9,
|
||||||
|
)
|
||||||
|
if !feeAmount.Equal(decimal.NewFromInt(123)) {
|
||||||
|
t.Fatalf("feeAmount = %s, want 123", feeAmount)
|
||||||
|
}
|
||||||
|
if feeSide != tc.wantFeeSide {
|
||||||
|
t.Fatalf("feeSide = %s, want %s", feeSide, tc.wantFeeSide)
|
||||||
|
}
|
||||||
|
if !feeMint.Equals(tc.wantFeeMint) {
|
||||||
|
t.Fatalf("feeMint = %s, want %s", feeMint, tc.wantFeeMint)
|
||||||
|
}
|
||||||
|
if !feeProgram.Equals(tc.wantFeeProg) {
|
||||||
|
t.Fatalf("feeProgram = %s, want %s", feeProgram, tc.wantFeeProg)
|
||||||
|
}
|
||||||
|
if feeDecimals != tc.wantDecimals {
|
||||||
|
t.Fatalf("feeDecimals = %d, want %d", feeDecimals, tc.wantDecimals)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDlmmSwapLpFeeAmount(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
lpFee := dlmmSwapLpFeeAmount(100, 15, 5)
|
||||||
|
if !lpFee.Equal(decimal.NewFromInt(80)) {
|
||||||
|
t.Fatalf("lpFee = %s, want 80", lpFee)
|
||||||
|
}
|
||||||
|
|
||||||
|
lpFee = dlmmSwapLpFeeAmount(10, 8, 5)
|
||||||
|
if !lpFee.IsZero() {
|
||||||
|
t.Fatalf("lpFee should floor at zero, got %s", lpFee)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDlmmSwapFeeBpsString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
feeBps := agbinary.Uint128{Lo: 12345}
|
||||||
|
if got := dlmmSwapFeeBpsString(feeBps); got != "12345" {
|
||||||
|
t.Fatalf("dlmmSwapFeeBpsString() = %s, want 12345", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
878
metaorapool.go
Normal file
878
metaorapool.go
Normal file
@@ -0,0 +1,878 @@
|
|||||||
|
package pump_parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
agbinary "github.com/gagliardetto/binary"
|
||||||
|
"github.com/gagliardetto/solana-go"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type metaoraPoolInitializePoolData struct {
|
||||||
|
TokenAAmount uint64 `json:"tokenAAmount"`
|
||||||
|
TokenBAmount uint64 `json:"tokenBAmount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
meteoraVaultProgram = solana.MustPublicKeyFromBase58("24Uqj9JCLxUeoC3hGfh5W3s9FM9uCHDS2SG3LYwBpyTi")
|
||||||
|
meteoraVaultDepositDiscriminator = []byte{0xf2, 0x23, 0xc6, 0x89, 0x52, 0xe1, 0xf2, 0xb6}
|
||||||
|
meteoraVaultWithdrawDiscriminator = []byte{0xb7, 0x12, 0x46, 0x9c, 0x94, 0x6d, 0xa1, 0x22}
|
||||||
|
|
||||||
|
tokenProgramMintToDiscriminator = []byte{0x07}
|
||||||
|
tokenProgramTransferDiscriminator = []byte{0x03}
|
||||||
|
tokenProgramBurnDiscriminator = []byte{0x08}
|
||||||
|
)
|
||||||
|
|
||||||
|
func metaoraPoolParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if !tx.rawTx.accountList[instruction.ProgramIDIndex].Equals(metaoraPoolProgramID) {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("metaoraPool program instruction not found, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
decode := instruction.Data
|
||||||
|
if len(decode) < 8 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("metaoraPool program instruction data too short, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
discriminator := *(*[8]byte)(decode[:8])
|
||||||
|
|
||||||
|
switch discriminator {
|
||||||
|
case metaoraPoolInitializePermissionlessConstantProductPoolWithConfigDiscriminator,
|
||||||
|
metaoraPoolInitializePermissionlessConstantProductPoolWithConfig2Discriminator:
|
||||||
|
return metaoraPoolInitializePermissionlessConstantProductPoolWithConfig(tx, instruction, innerInstructions, offset)
|
||||||
|
case metaoraPoolInitializePermissionlessPoolDiscriminator,
|
||||||
|
metaoraPoolInitializePermissionlessPoolWithFeeTierDiscriminator,
|
||||||
|
metaoraPoolInitializeCustomizablePermissionlessConstantProductPoolDiscriminator:
|
||||||
|
return metaoraPoolInitializePermissionlessPool(tx, instruction, innerInstructions, offset)
|
||||||
|
case metaoraPoolInitializePermissionedPoolDiscriminator:
|
||||||
|
return metaoraPoolInitializePermissionedPool(tx, instruction, innerInstructions, offset)
|
||||||
|
case metaoraPoolSwapDiscriminator:
|
||||||
|
return metaoraPoolSwap(tx, instruction, innerInstructions, offset)
|
||||||
|
case metaoraPoolAddImbalanceLiquidityDiscriminator,
|
||||||
|
metaoraPoolAddBalanceLiquidityDiscriminator,
|
||||||
|
metaoraPoolBootstrapLiquidityDiscriminator:
|
||||||
|
return metaoraPoolAddLiquidity(tx, instruction, innerInstructions, offset)
|
||||||
|
case metaoraPoolRemoveLiquiditySingleSideDiscriminator,
|
||||||
|
metaoraPoolRemoveBalanceLiquidityDiscriminator,
|
||||||
|
metaoraPoolClaimFeeDiscriminator:
|
||||||
|
return metaoraPoolRemoveLiquidity(tx, instruction, innerInstructions, offset)
|
||||||
|
default:
|
||||||
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitializePermissionlessConstantProductPoolWithConfig
|
||||||
|
// InitializePermissionlessConstantProductPoolWithConfig2
|
||||||
|
func metaoraPoolInitializePermissionlessConstantProductPoolWithConfig(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
var data metaoraPoolInitializePoolData
|
||||||
|
err := agbinary.NewBorshDecoder(instruction.Data[8:]).Decode(&data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to deserialize initialize pool data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(instruction.Accounts) < 20 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for initialize instruction")
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenAMint := tx.rawTx.accountList[instruction.Accounts[3]]
|
||||||
|
tokenBMint := tx.rawTx.accountList[instruction.Accounts[4]]
|
||||||
|
|
||||||
|
baseVaultAccountIndex := instruction.Accounts[7]
|
||||||
|
quoteVaultAccountIndex := instruction.Accounts[8]
|
||||||
|
baseVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultAccountIndex)
|
||||||
|
quoteVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultAccountIndex)
|
||||||
|
|
||||||
|
if baseVaultTokenBalance == nil || quoteVaultTokenBalance == nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token balances")
|
||||||
|
}
|
||||||
|
|
||||||
|
baseReserve, err := decimal.NewFromString(baseVaultTokenBalance.UITokenAmount.Amount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to parse base reserve: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
quoteReserve, err := decimal.NewFromString(quoteVaultTokenBalance.UITokenAmount.Amount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to parse quote reserve: %w", err)
|
||||||
|
}
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
var prefixLen = offset[1]
|
||||||
|
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta pools initial get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
var meteoraVaultProgramId int
|
||||||
|
for i, acc := range tx.rawTx.accountList {
|
||||||
|
if acc.Equals(meteoraVaultProgram) {
|
||||||
|
meteoraVaultProgramId = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseFound, quoteFound bool
|
||||||
|
|
||||||
|
if meteoraVaultProgramId > 0 {
|
||||||
|
for innerIndex, innerInstr := range inners {
|
||||||
|
if innerInstr.ProgramIDIndex == meteoraVaultProgramId && len(innerInstr.Data) >= 16 && bytes.Equal(innerInstr.Data[:8], pumpEventDiscriminator[:]) && bytes.Equal(innerInstr.Data[8:16], meteoraVaultDepositDiscriminator[:]) {
|
||||||
|
if len(innerInstr.Accounts) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !baseFound && innerInstr.Accounts[1] == baseVaultAccountIndex {
|
||||||
|
baseFound = true
|
||||||
|
}
|
||||||
|
if !quoteFound && innerInstr.Accounts[1] == quoteVaultAccountIndex {
|
||||||
|
quoteFound = true
|
||||||
|
}
|
||||||
|
if baseFound && quoteFound {
|
||||||
|
if offset[1] == 0 {
|
||||||
|
offset[0] += 1
|
||||||
|
} else {
|
||||||
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramMeteoraPools,
|
||||||
|
Event: "create",
|
||||||
|
Pool: tx.rawTx.accountList[instruction.Accounts[0]],
|
||||||
|
BaseMint: tokenAMint,
|
||||||
|
QuoteMint: tokenBMint,
|
||||||
|
BaseTokenProgram: baseVaultTokenBalance.ProgramIDAccount,
|
||||||
|
QuoteTokenProgram: quoteVaultTokenBalance.ProgramIDAccount,
|
||||||
|
Creator: tx.rawTx.accountList[0],
|
||||||
|
BaseMintDecimals: uint8(baseVaultTokenBalance.UITokenAmount.Decimals),
|
||||||
|
QuoteMintDecimals: uint8(quoteVaultTokenBalance.UITokenAmount.Decimals),
|
||||||
|
User: tx.rawTx.accountList[instruction.Accounts[18]],
|
||||||
|
BaseAmount: decimal.NewFromUint64(data.TokenAAmount),
|
||||||
|
QuoteAmount: decimal.NewFromUint64(data.TokenBAmount),
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, offset, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitializePermissionlessPool
|
||||||
|
// InitializePermissionlessPoolWithFeeTier
|
||||||
|
func metaoraPoolInitializePermissionlessPool(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
// discriminator + tokenA amount + tokenB amount
|
||||||
|
if len(instruction.Data) < 24 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("not enough data for initialize instruction")
|
||||||
|
}
|
||||||
|
|
||||||
|
var data metaoraPoolInitializePoolData
|
||||||
|
err := agbinary.NewBorshDecoder(instruction.Data[len(instruction.Data)-16:]).Decode(&data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to deserialize initialize pool data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(instruction.Accounts) < 20 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for initialize instruction")
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenAMint := tx.rawTx.accountList[instruction.Accounts[2]]
|
||||||
|
tokenBMint := tx.rawTx.accountList[instruction.Accounts[3]]
|
||||||
|
|
||||||
|
baseVaultAccountIndex := instruction.Accounts[6]
|
||||||
|
quoteVaultAccountIndex := instruction.Accounts[7]
|
||||||
|
baseVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultAccountIndex)
|
||||||
|
quoteVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultAccountIndex)
|
||||||
|
|
||||||
|
if baseVaultTokenBalance == nil || quoteVaultTokenBalance == nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token balances")
|
||||||
|
}
|
||||||
|
|
||||||
|
baseReserve, err := decimal.NewFromString(baseVaultTokenBalance.UITokenAmount.Amount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to parse base reserve: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
quoteReserve, err := decimal.NewFromString(quoteVaultTokenBalance.UITokenAmount.Amount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to parse quote reserve: %w", err)
|
||||||
|
}
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
var prefixLen = offset[1]
|
||||||
|
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta pools initial get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
var meteoraVaultProgramId int
|
||||||
|
for i, acc := range tx.rawTx.accountList {
|
||||||
|
if acc.Equals(meteoraVaultProgram) {
|
||||||
|
meteoraVaultProgramId = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseFound, quoteFound bool
|
||||||
|
|
||||||
|
if meteoraVaultProgramId > 0 {
|
||||||
|
for innerIndex, innerInstr := range inners {
|
||||||
|
if innerInstr.ProgramIDIndex == meteoraVaultProgramId && len(innerInstr.Data) >= 16 && bytes.Equal(innerInstr.Data[:8], pumpEventDiscriminator[:]) && bytes.Equal(innerInstr.Data[8:16], meteoraVaultDepositDiscriminator[:]) {
|
||||||
|
if len(innerInstr.Accounts) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !baseFound && innerInstr.Accounts[1] == baseVaultAccountIndex {
|
||||||
|
baseFound = true
|
||||||
|
}
|
||||||
|
if !quoteFound && innerInstr.Accounts[1] == quoteVaultAccountIndex {
|
||||||
|
quoteFound = true
|
||||||
|
}
|
||||||
|
if baseFound && quoteFound {
|
||||||
|
if offset[1] == 0 {
|
||||||
|
offset[0] += 1
|
||||||
|
} else {
|
||||||
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramMeteoraPools,
|
||||||
|
Event: "create",
|
||||||
|
Pool: tx.rawTx.accountList[instruction.Accounts[0]],
|
||||||
|
BaseMint: tokenAMint,
|
||||||
|
QuoteMint: tokenBMint,
|
||||||
|
BaseTokenProgram: baseVaultTokenBalance.ProgramIDAccount,
|
||||||
|
QuoteTokenProgram: quoteVaultTokenBalance.ProgramIDAccount,
|
||||||
|
Creator: tx.rawTx.accountList[0],
|
||||||
|
BaseMintDecimals: uint8(baseVaultTokenBalance.UITokenAmount.Decimals),
|
||||||
|
QuoteMintDecimals: uint8(quoteVaultTokenBalance.UITokenAmount.Decimals),
|
||||||
|
User: tx.rawTx.accountList[instruction.Accounts[18]],
|
||||||
|
BaseAmount: decimal.NewFromUint64(data.TokenAAmount),
|
||||||
|
QuoteAmount: decimal.NewFromUint64(data.TokenBAmount),
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func metaoraPoolInitializePermissionedPool(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
|
||||||
|
// discriminator + tokenA amount + tokenB amount
|
||||||
|
if len(instruction.Data) < 24 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("not enough data for initialize instruction")
|
||||||
|
}
|
||||||
|
|
||||||
|
var data metaoraPoolInitializePoolData
|
||||||
|
err := agbinary.NewBorshDecoder(instruction.Data[len(instruction.Data)-16:]).Decode(&data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to deserialize initialize pool data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(instruction.Accounts) < 20 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for initialize instruction")
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenAMint := tx.rawTx.accountList[instruction.Accounts[2]]
|
||||||
|
tokenBMint := tx.rawTx.accountList[instruction.Accounts[3]]
|
||||||
|
|
||||||
|
baseVaultAccountIndex := instruction.Accounts[10]
|
||||||
|
quoteVaultAccountIndex := instruction.Accounts[11]
|
||||||
|
baseVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultAccountIndex)
|
||||||
|
quoteVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultAccountIndex)
|
||||||
|
|
||||||
|
if baseVaultTokenBalance == nil || quoteVaultTokenBalance == nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token balances")
|
||||||
|
}
|
||||||
|
|
||||||
|
baseReserve, err := decimal.NewFromString(baseVaultTokenBalance.UITokenAmount.Amount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to parse base reserve: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
quoteReserve, err := decimal.NewFromString(quoteVaultTokenBalance.UITokenAmount.Amount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to parse quote reserve: %w", err)
|
||||||
|
}
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
var prefixLen = offset[1]
|
||||||
|
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta pools initial get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
var meteoraVaultProgramId int
|
||||||
|
for i, acc := range tx.rawTx.accountList {
|
||||||
|
if acc.Equals(meteoraVaultProgram) {
|
||||||
|
meteoraVaultProgramId = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseFound, quoteFound bool
|
||||||
|
|
||||||
|
if meteoraVaultProgramId > 0 {
|
||||||
|
for innerIndex, innerInstr := range inners {
|
||||||
|
if innerInstr.ProgramIDIndex == meteoraVaultProgramId && len(innerInstr.Data) >= 16 && bytes.Equal(innerInstr.Data[:8], pumpEventDiscriminator[:]) && bytes.Equal(innerInstr.Data[8:16], meteoraVaultDepositDiscriminator[:]) {
|
||||||
|
if len(innerInstr.Accounts) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !baseFound && innerInstr.Accounts[1] == baseVaultAccountIndex {
|
||||||
|
baseFound = true
|
||||||
|
}
|
||||||
|
if !quoteFound && innerInstr.Accounts[1] == quoteVaultAccountIndex {
|
||||||
|
quoteFound = true
|
||||||
|
}
|
||||||
|
if baseFound && quoteFound {
|
||||||
|
if offset[1] == 0 {
|
||||||
|
offset[0] += 1
|
||||||
|
} else {
|
||||||
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramMeteoraPools,
|
||||||
|
Event: "create",
|
||||||
|
Pool: tx.rawTx.accountList[instruction.Accounts[0]],
|
||||||
|
BaseMint: tokenAMint,
|
||||||
|
QuoteMint: tokenBMint,
|
||||||
|
BaseTokenProgram: baseVaultTokenBalance.ProgramIDAccount,
|
||||||
|
QuoteTokenProgram: quoteVaultTokenBalance.ProgramIDAccount,
|
||||||
|
Creator: tx.rawTx.accountList[0],
|
||||||
|
BaseMintDecimals: uint8(baseVaultTokenBalance.UITokenAmount.Decimals),
|
||||||
|
QuoteMintDecimals: uint8(quoteVaultTokenBalance.UITokenAmount.Decimals),
|
||||||
|
User: tx.rawTx.accountList[instruction.Accounts[18]],
|
||||||
|
BaseAmount: decimal.NewFromUint64(data.TokenAAmount),
|
||||||
|
QuoteAmount: decimal.NewFromUint64(data.TokenBAmount),
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BootstrapLiquidity
|
||||||
|
// AddImbalanceLiquidity
|
||||||
|
// AddBalanceLiquidity
|
||||||
|
func metaoraPoolAddLiquidity(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if len(instruction.Accounts) < 14 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("invalid instruction accounts length")
|
||||||
|
}
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
|
||||||
|
pool := tx.rawTx.accountList[instruction.Accounts[0]]
|
||||||
|
lpMint := tx.rawTx.accountList[instruction.Accounts[1]]
|
||||||
|
payer := tx.rawTx.accountList[instruction.Accounts[13]]
|
||||||
|
userPoolLp := tx.rawTx.accountList[instruction.Accounts[2]]
|
||||||
|
// vault for storing real tokens
|
||||||
|
// NOTE: because meteora pools will put assets of different pairs together,
|
||||||
|
// we cannot directly use the vault balance to calculate liquidity
|
||||||
|
var meteoraVaultProgramId int
|
||||||
|
for i, acc := range tx.rawTx.accountList {
|
||||||
|
if acc.Equals(meteoraVaultProgram) {
|
||||||
|
meteoraVaultProgramId = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if meteoraVaultProgramId == 0 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meteora vault program not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7, 8
|
||||||
|
baseVaultLpAccountBalance, _ := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[7])
|
||||||
|
quoteVaultLpAccountBalance, _ := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[8])
|
||||||
|
if baseVaultLpAccountBalance == nil || quoteVaultLpAccountBalance == nil {
|
||||||
|
return nil, increaseOffset(offset), InstructionIgnoredError //fmt.Errorf("failed to get vault lp account balances")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9,10
|
||||||
|
baseVaultAccountBalance, _ := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[9])
|
||||||
|
quoteVaultAccountBalance, _ := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[10])
|
||||||
|
if baseVaultAccountBalance == nil || quoteVaultAccountBalance == nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get vault lp account balances")
|
||||||
|
}
|
||||||
|
var prefixLen = offset[1]
|
||||||
|
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta pools initial get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
var baseFound, quoteFound bool
|
||||||
|
|
||||||
|
var baseAmount, quoteAmount decimal.Decimal
|
||||||
|
var (
|
||||||
|
baseMint = solana.PublicKey{}
|
||||||
|
quoteMint = solana.PublicKey{}
|
||||||
|
baseTokenProgram = solana.PublicKey{}
|
||||||
|
quoteTokenProgram = solana.PublicKey{}
|
||||||
|
baseDecimals uint8
|
||||||
|
quoteDecimals uint8
|
||||||
|
baseReserve decimal.Decimal
|
||||||
|
quoteReserve decimal.Decimal
|
||||||
|
)
|
||||||
|
baseMint = baseVaultAccountBalance.MintAccount
|
||||||
|
quoteMint = quoteVaultAccountBalance.MintAccount
|
||||||
|
quoteTokenProgram = quoteVaultAccountBalance.ProgramIDAccount
|
||||||
|
baseTokenProgram = baseVaultAccountBalance.ProgramIDAccount
|
||||||
|
|
||||||
|
baseDecimals = uint8(baseVaultAccountBalance.UITokenAmount.Decimals)
|
||||||
|
baseReserve, err = decimal.NewFromString(baseVaultLpAccountBalance.UITokenAmount.Amount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to parse base reserve: %w", err)
|
||||||
|
}
|
||||||
|
if baseDecimals != uint8(baseVaultLpAccountBalance.UITokenAmount.Decimals) {
|
||||||
|
decimalDiff := int(baseDecimals) - int(baseVaultLpAccountBalance.UITokenAmount.Decimals)
|
||||||
|
baseReserve = baseReserve.Mul(decimal.NewFromInt(10).Pow(decimal.NewFromInt(int64(decimalDiff))))
|
||||||
|
}
|
||||||
|
|
||||||
|
quoteDecimals = uint8(quoteVaultAccountBalance.UITokenAmount.Decimals)
|
||||||
|
quoteReserve, err = decimal.NewFromString(quoteVaultLpAccountBalance.UITokenAmount.Amount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to parse quote reserve: %w", err)
|
||||||
|
}
|
||||||
|
if quoteDecimals != uint8(quoteVaultLpAccountBalance.UITokenAmount.Decimals) {
|
||||||
|
decimalDiff := int(quoteDecimals) - int(quoteVaultLpAccountBalance.UITokenAmount.Decimals)
|
||||||
|
quoteReserve = quoteReserve.Mul(decimal.NewFromInt(10).Pow(decimal.NewFromInt(int64(decimalDiff))))
|
||||||
|
}
|
||||||
|
|
||||||
|
for innerIndex := 0; innerIndex < len(inners); innerIndex++ {
|
||||||
|
innerInstr := inners[innerIndex]
|
||||||
|
|
||||||
|
if innerInstr.ProgramIDIndex == meteoraVaultProgramId &&
|
||||||
|
len(innerInstr.Data) >= 16 &&
|
||||||
|
bytes.Equal(innerInstr.Data[:8], eventDiscriminator[:]) &&
|
||||||
|
bytes.Equal(innerInstr.Data[8:16], meteoraVaultDepositDiscriminator[:]) {
|
||||||
|
if len(innerInstr.Accounts) < 6 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if innerIndex+1 >= len(inners) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
transferInstr := inners[innerIndex+1]
|
||||||
|
_, to, amount, err := parseTokenTransfer(tx.rawTx, transferInstr)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
innerIndex++ // skip transfer instruction
|
||||||
|
if !baseFound && to.Equals(tx.rawTx.accountList[baseVaultAccountBalance.AccountIndex]) {
|
||||||
|
baseFound = true
|
||||||
|
baseAmount = decimal.NewFromUint64(amount)
|
||||||
|
} else if !quoteFound && to.Equals(tx.rawTx.accountList[quoteVaultAccountBalance.AccountIndex]) {
|
||||||
|
quoteFound = true
|
||||||
|
quoteAmount = decimal.NewFromUint64(amount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (baseFound || quoteFound) && (tx.rawTx.accountList[innerInstr.ProgramIDIndex] == solana.TokenProgramID ||
|
||||||
|
tx.rawTx.accountList[innerInstr.ProgramIDIndex] == solana.Token2022ProgramID) && innerInstr.Data[0] == 7 {
|
||||||
|
if len(innerInstr.Accounts) < 3 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// mint lp token
|
||||||
|
if tx.rawTx.accountList[innerInstr.Accounts[0]] == lpMint && tx.rawTx.accountList[innerInstr.Accounts[1]] == userPoolLp {
|
||||||
|
if offset[1] == 0 {
|
||||||
|
offset[0] += 1
|
||||||
|
} else {
|
||||||
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !baseFound && !quoteFound {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to find deposit instructions")
|
||||||
|
}
|
||||||
|
|
||||||
|
var event = "add_liquidity_one_side"
|
||||||
|
if baseFound && quoteFound {
|
||||||
|
// both sides
|
||||||
|
event = "add_liquidity"
|
||||||
|
}
|
||||||
|
swap := Swap{
|
||||||
|
Program: SolProgramMeteoraPools,
|
||||||
|
Event: event,
|
||||||
|
Pool: pool,
|
||||||
|
BaseMint: baseMint,
|
||||||
|
QuoteMint: quoteMint,
|
||||||
|
BaseTokenProgram: baseTokenProgram,
|
||||||
|
QuoteTokenProgram: quoteTokenProgram,
|
||||||
|
BaseMintDecimals: baseDecimals,
|
||||||
|
QuoteMintDecimals: quoteDecimals,
|
||||||
|
User: payer,
|
||||||
|
BaseAmount: baseAmount,
|
||||||
|
QuoteAmount: quoteAmount,
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
}
|
||||||
|
|
||||||
|
return []Swap{swap}, offset, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveLiquiditySingleSide
|
||||||
|
// ClaimFee
|
||||||
|
// RemoveBalanceLiquidity
|
||||||
|
func metaoraPoolRemoveLiquidity(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if len(instruction.Accounts) < 14 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("invalid instruction accounts length")
|
||||||
|
}
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
|
||||||
|
pool := tx.rawTx.accountList[instruction.Accounts[0]]
|
||||||
|
lpMint := tx.rawTx.accountList[instruction.Accounts[1]]
|
||||||
|
var (
|
||||||
|
userPoolLp solana.PublicKey
|
||||||
|
baseVaultIdx int
|
||||||
|
quoteVaultIdx int
|
||||||
|
baseLpVaultIdx int
|
||||||
|
quoteLpVaultIdx int
|
||||||
|
userIdx int
|
||||||
|
)
|
||||||
|
if bytes.Equal(instruction.Data[:8], metaoraPoolRemoveLiquiditySingleSideDiscriminator[:]) {
|
||||||
|
userPoolLp = tx.rawTx.accountList[instruction.Accounts[2]]
|
||||||
|
//userBaseAccountIdx = 11
|
||||||
|
//userQuoteAccountIdx = 12
|
||||||
|
baseVaultIdx = 9
|
||||||
|
quoteVaultIdx = 10
|
||||||
|
baseLpVaultIdx = 3
|
||||||
|
quoteLpVaultIdx = 4
|
||||||
|
userIdx = 12
|
||||||
|
} else if bytes.Equal(instruction.Data[:8], metaoraPoolClaimFeeDiscriminator[:]) {
|
||||||
|
if len(instruction.Accounts) < 16 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("invalid instruction accounts length")
|
||||||
|
}
|
||||||
|
userPoolLp = tx.rawTx.accountList[instruction.Accounts[5]]
|
||||||
|
//userBaseAccountIdx = 15
|
||||||
|
//userQuoteAccountIdx = 16
|
||||||
|
|
||||||
|
baseVaultIdx = 7
|
||||||
|
quoteVaultIdx = 8
|
||||||
|
baseLpVaultIdx = 11
|
||||||
|
quoteLpVaultIdx = 12
|
||||||
|
userIdx = 3
|
||||||
|
} else if bytes.Equal(instruction.Data[:8], metaoraPoolRemoveBalanceLiquidityDiscriminator[:]) {
|
||||||
|
userPoolLp = tx.rawTx.accountList[instruction.Accounts[2]]
|
||||||
|
//userBaseAccountIdx = 11
|
||||||
|
//userQuoteAccountIdx = 12
|
||||||
|
|
||||||
|
baseVaultIdx = 9
|
||||||
|
quoteVaultIdx = 10
|
||||||
|
baseLpVaultIdx = 3
|
||||||
|
quoteLpVaultIdx = 4
|
||||||
|
userIdx = 12
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("invalid remove liquidity instruction discriminator")
|
||||||
|
}
|
||||||
|
// vault for storing real tokens
|
||||||
|
// NOTE: because meteora pools will put assets of different pairs together,
|
||||||
|
// we cannot directly use the vault balance to calculate liquidity
|
||||||
|
var meteoraVaultProgramId int
|
||||||
|
for i, acc := range tx.rawTx.accountList {
|
||||||
|
if acc.Equals(meteoraVaultProgram) {
|
||||||
|
meteoraVaultProgramId = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if meteoraVaultProgramId == 0 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meteora vault program not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7, 8
|
||||||
|
baseVaultLpAccountBalance, _ := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[baseLpVaultIdx])
|
||||||
|
quoteVaultLpAccountBalance, _ := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[quoteLpVaultIdx])
|
||||||
|
if baseVaultLpAccountBalance == nil || quoteVaultLpAccountBalance == nil {
|
||||||
|
return nil, increaseOffset(offset), InstructionIgnoredError // fmt.Errorf("failed to get vault lp account balances")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9,10
|
||||||
|
baseVaultAccountBalance, _ := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[baseVaultIdx])
|
||||||
|
quoteVaultAccountBalance, _ := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[quoteVaultIdx])
|
||||||
|
if baseVaultAccountBalance == nil || quoteVaultAccountBalance == nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get vault lp account balances")
|
||||||
|
}
|
||||||
|
var prefixLen = offset[1]
|
||||||
|
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta pools initial get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
var baseFound, quoteFound bool
|
||||||
|
|
||||||
|
var baseAmount, quoteAmount decimal.Decimal
|
||||||
|
var (
|
||||||
|
baseMint = solana.PublicKey{}
|
||||||
|
quoteMint = solana.PublicKey{}
|
||||||
|
baseTokenProgram = solana.PublicKey{}
|
||||||
|
quoteTokenProgram = solana.PublicKey{}
|
||||||
|
baseDecimals uint8
|
||||||
|
quoteDecimals uint8
|
||||||
|
baseReserve decimal.Decimal
|
||||||
|
quoteReserve decimal.Decimal
|
||||||
|
)
|
||||||
|
|
||||||
|
baseMint = baseVaultAccountBalance.MintAccount
|
||||||
|
quoteMint = quoteVaultAccountBalance.MintAccount
|
||||||
|
baseTokenProgram = baseVaultAccountBalance.ProgramIDAccount
|
||||||
|
quoteTokenProgram = quoteVaultAccountBalance.ProgramIDAccount
|
||||||
|
|
||||||
|
baseDecimals = uint8(baseVaultAccountBalance.UITokenAmount.Decimals)
|
||||||
|
baseReserve, err = decimal.NewFromString(baseVaultLpAccountBalance.UITokenAmount.Amount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to parse base reserve: %w", err)
|
||||||
|
}
|
||||||
|
if baseDecimals != uint8(baseVaultLpAccountBalance.UITokenAmount.Decimals) {
|
||||||
|
decimalDiff := int(baseDecimals) - int(baseVaultLpAccountBalance.UITokenAmount.Decimals)
|
||||||
|
baseReserve = baseReserve.Mul(decimal.NewFromInt(10).Pow(decimal.NewFromInt(int64(decimalDiff))))
|
||||||
|
}
|
||||||
|
|
||||||
|
quoteDecimals = uint8(quoteVaultAccountBalance.UITokenAmount.Decimals)
|
||||||
|
quoteReserve, err = decimal.NewFromString(quoteVaultLpAccountBalance.UITokenAmount.Amount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to parse quote reserve: %w", err)
|
||||||
|
}
|
||||||
|
if quoteDecimals != uint8(quoteVaultLpAccountBalance.UITokenAmount.Decimals) {
|
||||||
|
decimalDiff := int(quoteDecimals) - int(quoteVaultLpAccountBalance.UITokenAmount.Decimals)
|
||||||
|
quoteReserve = quoteReserve.Mul(decimal.NewFromInt(10).Pow(decimal.NewFromInt(int64(decimalDiff))))
|
||||||
|
}
|
||||||
|
|
||||||
|
for innerIndex := 0; innerIndex < len(inners); innerIndex++ {
|
||||||
|
innerInstr := inners[innerIndex]
|
||||||
|
|
||||||
|
if innerInstr.ProgramIDIndex == meteoraVaultProgramId && len(innerInstr.Data) >= 8 &&
|
||||||
|
bytes.Equal(innerInstr.Data[:8], meteoraVaultWithdrawDiscriminator[:]) {
|
||||||
|
if len(innerInstr.Accounts) < 6 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if innerIndex+1 >= len(inners) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
transferInstr := inners[innerIndex+1]
|
||||||
|
|
||||||
|
from, _, amount, err := parseTokenTransfer(tx.rawTx, transferInstr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("parse tx error:", err, tx.GetTxHash(), transferInstr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
innerIndex++ // skip transfer instruction
|
||||||
|
|
||||||
|
if !baseFound && from.Equals(tx.rawTx.accountList[instruction.Accounts[baseVaultIdx]]) {
|
||||||
|
//base
|
||||||
|
baseFound = true
|
||||||
|
baseAmount = decimal.NewFromUint64(amount)
|
||||||
|
} else if !quoteFound && from.Equals(tx.rawTx.accountList[instruction.Accounts[quoteVaultIdx]]) {
|
||||||
|
// quote
|
||||||
|
quoteFound = true
|
||||||
|
quoteAmount = decimal.NewFromUint64(amount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (baseFound || quoteFound) && (tx.rawTx.accountList[innerInstr.ProgramIDIndex] == solana.TokenProgramID ||
|
||||||
|
tx.rawTx.accountList[innerInstr.ProgramIDIndex] == solana.Token2022ProgramID) && innerInstr.Data[0] == 8 {
|
||||||
|
if len(innerInstr.Accounts) < 3 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// mint lp token
|
||||||
|
if tx.rawTx.accountList[innerInstr.Accounts[1]] == lpMint && tx.rawTx.accountList[innerInstr.Accounts[0]] == userPoolLp {
|
||||||
|
if offset[1] == 0 {
|
||||||
|
offset[0] += 1
|
||||||
|
} else {
|
||||||
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !baseFound && !quoteFound {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to find withdraw instructions, baseFound: %v, quoteFound: %v", baseFound, quoteFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
var event = "remove_liquidity_one_side"
|
||||||
|
if baseFound && quoteFound {
|
||||||
|
event = "remove_liquidity"
|
||||||
|
}
|
||||||
|
swap := Swap{
|
||||||
|
Program: SolProgramMeteoraPools,
|
||||||
|
Event: event,
|
||||||
|
Pool: pool,
|
||||||
|
BaseMint: baseMint,
|
||||||
|
QuoteMint: quoteMint,
|
||||||
|
BaseTokenProgram: baseTokenProgram,
|
||||||
|
QuoteTokenProgram: quoteTokenProgram,
|
||||||
|
BaseMintDecimals: baseDecimals,
|
||||||
|
QuoteMintDecimals: quoteDecimals,
|
||||||
|
User: tx.rawTx.accountList[userIdx],
|
||||||
|
BaseAmount: baseAmount,
|
||||||
|
QuoteAmount: quoteAmount,
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
}
|
||||||
|
|
||||||
|
return []Swap{swap}, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func metaoraPoolSwap(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
pool := tx.rawTx.accountList[instruction.Accounts[0]]
|
||||||
|
payer := tx.rawTx.accountList[instruction.Accounts[12]]
|
||||||
|
|
||||||
|
sourceAccountIndex := instruction.Accounts[1]
|
||||||
|
destinationAccountIndex := instruction.Accounts[2]
|
||||||
|
|
||||||
|
// vault for storing real tokens
|
||||||
|
// NOTE: because meteora pools will put assets of different pairs together,
|
||||||
|
// we cannot directly use the vault balance to calculate liquidity
|
||||||
|
|
||||||
|
//parse reserves from vault accounts
|
||||||
|
baseVaultIdx := instruction.Accounts[6]
|
||||||
|
quoteVaultIdx := instruction.Accounts[5]
|
||||||
|
baseVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultIdx)
|
||||||
|
quoteVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultIdx)
|
||||||
|
if baseVaultTokenBalance == nil || quoteVaultTokenBalance == nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get vault token balances")
|
||||||
|
}
|
||||||
|
|
||||||
|
baseVaultLpBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[10])
|
||||||
|
quoteVaultLpBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[9])
|
||||||
|
if baseVaultLpBalance == nil || quoteVaultLpBalance == nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get vault lp balances")
|
||||||
|
}
|
||||||
|
baseTokenProgram := baseVaultTokenBalance.ProgramIDAccount
|
||||||
|
quoteTokenProgram := quoteVaultTokenBalance.ProgramIDAccount
|
||||||
|
baseMint := baseVaultTokenBalance.MintAccount
|
||||||
|
quoteMint := quoteVaultTokenBalance.MintAccount
|
||||||
|
baseDecimals := uint8(baseVaultTokenBalance.UITokenAmount.Decimals)
|
||||||
|
quoteDecimals := uint8(quoteVaultTokenBalance.UITokenAmount.Decimals)
|
||||||
|
|
||||||
|
baseReserve := decimal.Zero
|
||||||
|
quoteReserve := decimal.Zero
|
||||||
|
if baseVaultLpBalance.UITokenAmount.Decimals == baseVaultTokenBalance.UITokenAmount.Decimals {
|
||||||
|
baseReserve, _ = decimal.NewFromString(baseVaultLpBalance.UITokenAmount.Amount)
|
||||||
|
} else {
|
||||||
|
decimalsDiff := int32(baseVaultTokenBalance.UITokenAmount.Decimals) - int32(baseVaultLpBalance.UITokenAmount.Decimals)
|
||||||
|
multiplier := decimal.NewFromInt(10).Pow(decimal.NewFromInt32(decimalsDiff))
|
||||||
|
baseLpAmount, _ := decimal.NewFromString(baseVaultLpBalance.UITokenAmount.Amount)
|
||||||
|
baseReserve = baseLpAmount.Mul(multiplier)
|
||||||
|
}
|
||||||
|
|
||||||
|
if quoteVaultLpBalance.UITokenAmount.Decimals == quoteVaultTokenBalance.UITokenAmount.Decimals {
|
||||||
|
quoteReserve, _ = decimal.NewFromString(quoteVaultLpBalance.UITokenAmount.Amount)
|
||||||
|
} else {
|
||||||
|
decimalsDiff := int32(quoteVaultTokenBalance.UITokenAmount.Decimals) - int32(quoteVaultLpBalance.UITokenAmount.Decimals)
|
||||||
|
multiplier := decimal.NewFromInt(10).Pow(decimal.NewFromInt32(decimalsDiff))
|
||||||
|
quoteLpAmount, _ := decimal.NewFromString(quoteVaultLpBalance.UITokenAmount.Amount)
|
||||||
|
quoteReserve = quoteLpAmount.Mul(multiplier)
|
||||||
|
}
|
||||||
|
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
var prefixLen = offset[1]
|
||||||
|
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta pools initial get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
var meteoraVaultProgramId int
|
||||||
|
for i, acc := range tx.rawTx.accountList {
|
||||||
|
if acc.Equals(meteoraVaultProgram) {
|
||||||
|
meteoraVaultProgramId = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseFound, quoteFound bool
|
||||||
|
var (
|
||||||
|
baseAmount decimal.Decimal
|
||||||
|
quoteAmount decimal.Decimal
|
||||||
|
event string
|
||||||
|
)
|
||||||
|
for innerIndex := 0; innerIndex < len(inners); innerIndex++ {
|
||||||
|
innerInstr := inners[innerIndex]
|
||||||
|
//
|
||||||
|
if innerInstr.ProgramIDIndex == meteoraVaultProgramId && len(innerInstr.Data) >= 8 &&
|
||||||
|
(bytes.Equal(innerInstr.Data[:8], meteoraVaultWithdrawDiscriminator[:]) ||
|
||||||
|
bytes.Equal(innerInstr.Data[:8], meteoraVaultDepositDiscriminator[:])) {
|
||||||
|
if len(innerInstr.Accounts) < 6 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if innerIndex+1 >= len(inners) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
transferInstr := inners[innerIndex+1]
|
||||||
|
if (tx.rawTx.accountList[transferInstr.ProgramIDIndex] != solana.TokenProgramID &&
|
||||||
|
tx.rawTx.accountList[transferInstr.ProgramIDIndex] != solana.Token2022ProgramID) || transferInstr.Data[0] != 3 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
innerIndex++ // skip transfer instruction
|
||||||
|
if len(innerInstr.Accounts) == 7 &&
|
||||||
|
(bytes.Equal(innerInstr.Data[:8], meteoraVaultWithdrawDiscriminator[:]) || bytes.Equal(innerInstr.Data[:8], meteoraVaultDepositDiscriminator[:])) {
|
||||||
|
if innerInstr.Accounts[1] == baseVaultIdx {
|
||||||
|
//base
|
||||||
|
baseFound = true
|
||||||
|
baseAmount = decimal.NewFromUint64(binary.LittleEndian.Uint64(transferInstr.Data[1:9]))
|
||||||
|
if bytes.Equal(innerInstr.Data[:8], meteoraVaultWithdrawDiscriminator[:]) {
|
||||||
|
event = "buy"
|
||||||
|
} else {
|
||||||
|
event = "sell"
|
||||||
|
}
|
||||||
|
} else if innerInstr.Accounts[1] == quoteVaultIdx {
|
||||||
|
// quote
|
||||||
|
quoteFound = true
|
||||||
|
quoteAmount = decimal.NewFromUint64(binary.LittleEndian.Uint64(transferInstr.Data[1:9]))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if baseFound && quoteFound {
|
||||||
|
if offset[1] == 0 {
|
||||||
|
offset[0] += 1
|
||||||
|
} else {
|
||||||
|
offset[1] = uint(innerIndex) + 1 + prefixLen + 1 // +1 for mint or withdraw instruction,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !baseFound || !quoteFound {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to find meteora pool event in inner instructions")
|
||||||
|
}
|
||||||
|
|
||||||
|
userBase := getAccountBalanceAfterTx(tx.rawTx, sourceAccountIndex)
|
||||||
|
userQuote := getAccountBalanceAfterTx(tx.rawTx, destinationAccountIndex)
|
||||||
|
|
||||||
|
swaps := []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramMeteoraPools,
|
||||||
|
Event: event,
|
||||||
|
Pool: pool,
|
||||||
|
BaseMint: baseMint,
|
||||||
|
QuoteMint: quoteMint,
|
||||||
|
BaseTokenProgram: baseTokenProgram,
|
||||||
|
QuoteTokenProgram: quoteTokenProgram,
|
||||||
|
Creator: solana.PublicKey{},
|
||||||
|
BaseMintDecimals: baseDecimals,
|
||||||
|
QuoteMintDecimals: quoteDecimals,
|
||||||
|
User: payer,
|
||||||
|
BaseAmount: baseAmount,
|
||||||
|
QuoteAmount: quoteAmount,
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
UserBaseBalance: userBase,
|
||||||
|
UserQuoteBalance: userQuote,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return swaps, offset, nil
|
||||||
|
}
|
||||||
389
meteora_bonding_curve.go
Normal file
389
meteora_bonding_curve.go
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
package pump_parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
agbinary "github.com/gagliardetto/binary"
|
||||||
|
"github.com/gagliardetto/solana-go"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MetaoraBcEvtInitializePool struct {
|
||||||
|
Pool solana.PublicKey
|
||||||
|
Config solana.PublicKey
|
||||||
|
Creator solana.PublicKey
|
||||||
|
BaseMint solana.PublicKey
|
||||||
|
//PoolType uint8
|
||||||
|
//ActivationPoint uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type MetaoraBcSwapEvent struct {
|
||||||
|
Pool solana.PublicKey `json:"pool"`
|
||||||
|
Config solana.PublicKey `json:"config"`
|
||||||
|
TradeDirection uint8 `json:"tradeDirection"`
|
||||||
|
HasReferral bool `json:"hasReferral"`
|
||||||
|
Params *struct {
|
||||||
|
AmountIn uint64 `json:"amountIn"`
|
||||||
|
MinimumAmountOut uint64 `json:"minimumAmountOut"`
|
||||||
|
} `json:"params"`
|
||||||
|
SwapResult *struct {
|
||||||
|
ActualInputAmount uint64 `json:"actualInputAmount"`
|
||||||
|
OutputAmount uint64 `json:"outputAmount"`
|
||||||
|
NextSqrtPrice [16]byte `json:"nextSqrtPrice"`
|
||||||
|
TradingFee uint64 `json:"tradingFee"`
|
||||||
|
ProtocolFee uint64 `json:"protocolFee"`
|
||||||
|
ReferralFee uint64 `json:"referralFee"`
|
||||||
|
} `json:"swapResult"`
|
||||||
|
AmountIn uint64 `json:"amountIn"`
|
||||||
|
CurrentTimestamp uint64 `json:"currentTimestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func metaoraBcParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if !tx.rawTx.accountList[instruction.ProgramIDIndex].Equals(metaoraBcProgramID) {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("metaora Bonding Curve program instruction not found, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
decode := instruction.Data
|
||||||
|
if len(decode) < 8 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("metaora Bonding Curve program instruction data too short, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
discriminator := *(*[8]byte)(decode[:8])
|
||||||
|
|
||||||
|
switch discriminator {
|
||||||
|
case metaoraBcInitialize2022PoolDiscriminator,
|
||||||
|
metaoraBcInitializedPoolDiscriminator:
|
||||||
|
return metaBcInitializePoolParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case metaoraBcMigrateMeteoraDammDiscriminator:
|
||||||
|
return metaBcMigrateParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case metaoraBcMigrateMeteoraDammV2Discriminator:
|
||||||
|
return metaBcMigrateV2Parser(tx, instruction, innerInstructions, offset)
|
||||||
|
case metaoraBcSwapDiscriminator:
|
||||||
|
return metaBcSwapParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case metaoraBcSwapV2Discriminator:
|
||||||
|
return metaBcSwapParser(tx, instruction, innerInstructions, offset)
|
||||||
|
default:
|
||||||
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MetaoraCreateData struct {
|
||||||
|
Name string
|
||||||
|
Symbol string
|
||||||
|
Uri string
|
||||||
|
}
|
||||||
|
|
||||||
|
func metaBcInitializePoolParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if len(instruction.Accounts) < 14 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initialize pool not enough accounts, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
var createData MetaoraCreateData
|
||||||
|
err := agbinary.NewBorshDecoder(instruction.Data[8:]).Decode(&createData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initialize pool parse create data error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
var prefixLen = offset[1]
|
||||||
|
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initial get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[6])
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initialize pool get base token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[7])
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initialize pool get quote token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
baseReserve, err := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initialize pool parse base reserve error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
quoteReserve, err := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initialize pool parse quote reserve error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
var user solana.PublicKey
|
||||||
|
if bytes.Equal(instruction.Data[:8], metaoraBcInitialize2022PoolDiscriminator[:]) {
|
||||||
|
user = tx.rawTx.accountList[instruction.Accounts[8]]
|
||||||
|
} else if bytes.Equal(instruction.Data[:8], metaoraBcInitializedPoolDiscriminator[:]) {
|
||||||
|
user = tx.rawTx.accountList[instruction.Accounts[10]]
|
||||||
|
} else {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initialize pool unknown discriminator, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
baseTokenProgram := baseTokenBalance.ProgramIDAccount
|
||||||
|
quoteTokenProgram := quoteTokenBalance.ProgramIDAccount
|
||||||
|
baseMintDecimals := uint8(baseTokenBalance.UITokenAmount.Decimals)
|
||||||
|
quoteMintDecimals := uint8(quoteTokenBalance.UITokenAmount.Decimals)
|
||||||
|
var (
|
||||||
|
pool solana.PublicKey
|
||||||
|
baseMint solana.PublicKey
|
||||||
|
creator solana.PublicKey
|
||||||
|
totalSupply *decimal.Decimal
|
||||||
|
)
|
||||||
|
for innerIndex, innerInstr := range inners {
|
||||||
|
if tx.rawTx.accountList[innerInstr.ProgramIDIndex].Equals(baseMint) &&
|
||||||
|
len(innerInstr.Data) >= 9 && innerInstr.Data[0] == 7 &&
|
||||||
|
len(innerInstr.Accounts) == 3 && tx.rawTx.accountList[innerInstr.Accounts[0]].Equals(baseMint) &&
|
||||||
|
innerInstr.Accounts[1] == instruction.Accounts[6] {
|
||||||
|
supply := decimal.NewFromUint64(binary.LittleEndian.Uint64(innerInstr.Data[1:9]))
|
||||||
|
totalSupply = &supply
|
||||||
|
}
|
||||||
|
if innerInstr.ProgramIDIndex == instruction.ProgramIDIndex &&
|
||||||
|
len(innerInstr.Data) >= 16 &&
|
||||||
|
bytes.Equal(innerInstr.Data[0:8], pumpEventDiscriminator[:]) &&
|
||||||
|
bytes.Equal(innerInstr.Data[8:16], metaoraBcEventInitializePoolDiscriminator[:]) {
|
||||||
|
|
||||||
|
var event MetaoraBcEvtInitializePool
|
||||||
|
if offset[1] == 0 {
|
||||||
|
offset[0] += 1
|
||||||
|
} else {
|
||||||
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
||||||
|
}
|
||||||
|
err := agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&event)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initialize pool deserialize event error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
pool = event.Pool
|
||||||
|
baseMint = event.BaseMint
|
||||||
|
creator = event.Creator
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pool.IsZero() {
|
||||||
|
return nil, offset, fmt.Errorf("meta Bonding Curve initialize pool event not found, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
quoteMint := tx.rawTx.accountList[instruction.Accounts[4]]
|
||||||
|
|
||||||
|
tx.Token[baseMint] = TokenMeta{
|
||||||
|
Mint: baseMint,
|
||||||
|
TokenProgram: baseTokenProgram,
|
||||||
|
Decimals: baseMintDecimals,
|
||||||
|
Name: createData.Name,
|
||||||
|
Symbol: createData.Symbol,
|
||||||
|
Url: createData.Uri,
|
||||||
|
TotalSupply: totalSupply,
|
||||||
|
}
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramMeteoraBondingCurve,
|
||||||
|
Event: "create",
|
||||||
|
Pool: pool,
|
||||||
|
BaseMint: baseMint,
|
||||||
|
QuoteMint: quoteMint,
|
||||||
|
BaseTokenProgram: baseTokenProgram,
|
||||||
|
QuoteTokenProgram: quoteTokenProgram,
|
||||||
|
Creator: creator,
|
||||||
|
BaseMintDecimals: baseMintDecimals,
|
||||||
|
QuoteMintDecimals: quoteMintDecimals,
|
||||||
|
User: user,
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func metaBcMigrateV2Parser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if len(instruction.Accounts) < 25 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for migrate instruction")
|
||||||
|
}
|
||||||
|
|
||||||
|
baseVaultBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[17])
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve migrate get base vault balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
quoteVaultBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[18])
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve migrate get quote vault balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
swaps := []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramMeteoraBondingCurve,
|
||||||
|
Event: "migrate",
|
||||||
|
Pool: tx.rawTx.accountList[instruction.Accounts[0]],
|
||||||
|
BaseMint: tx.rawTx.accountList[instruction.Accounts[13]],
|
||||||
|
QuoteMint: tx.rawTx.accountList[instruction.Accounts[14]],
|
||||||
|
BaseTokenProgram: tx.rawTx.accountList[instruction.Accounts[20]],
|
||||||
|
QuoteTokenProgram: tx.rawTx.accountList[instruction.Accounts[21]],
|
||||||
|
BaseMintDecimals: uint8(baseVaultBalance.UITokenAmount.Decimals),
|
||||||
|
QuoteMintDecimals: uint8(quoteVaultBalance.UITokenAmount.Decimals),
|
||||||
|
User: tx.rawTx.accountList[instruction.Accounts[19]],
|
||||||
|
//BaseAmount: decimal.Decimal{},
|
||||||
|
//QuoteAmount: decimal.Decimal{},
|
||||||
|
//BaseReserve: decimal.NewFromUint64(migrateEvent.MintAmount),
|
||||||
|
//QuoteReserve: decimal.NewFromUint64(migrateEvent.SolAmount),
|
||||||
|
MigrateTopProgram: meteoraDammV2Program,
|
||||||
|
MigrateToPool: tx.rawTx.accountList[instruction.Accounts[4]],
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return swaps, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func metaBcMigrateParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if len(instruction.Accounts) < 23 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for migrate instruction")
|
||||||
|
}
|
||||||
|
|
||||||
|
baseVaultBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[17])
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve migrate get base vault balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
quoteVaultBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[18])
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve migrate get quote vault balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
swaps := []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramMeteoraBondingCurve,
|
||||||
|
Event: "migrate",
|
||||||
|
Pool: tx.rawTx.accountList[instruction.Accounts[0]],
|
||||||
|
BaseMint: tx.rawTx.accountList[instruction.Accounts[7]],
|
||||||
|
QuoteMint: tx.rawTx.accountList[instruction.Accounts[8]],
|
||||||
|
BaseTokenProgram: baseVaultBalance.ProgramIDAccount,
|
||||||
|
QuoteTokenProgram: baseVaultBalance.ProgramIDAccount,
|
||||||
|
BaseMintDecimals: uint8(baseVaultBalance.UITokenAmount.Decimals),
|
||||||
|
QuoteMintDecimals: uint8(quoteVaultBalance.UITokenAmount.Decimals),
|
||||||
|
User: tx.rawTx.accountList[instruction.Accounts[22]],
|
||||||
|
//BaseAmount: decimal.Decimal{},
|
||||||
|
//QuoteAmount: decimal.Decimal{},
|
||||||
|
//BaseReserve: decimal.NewFromUint64(migrateEvent.MintAmount),
|
||||||
|
//QuoteReserve: decimal.NewFromUint64(migrateEvent.SolAmount),
|
||||||
|
MigrateTopProgram: metaoraPoolProgramID,
|
||||||
|
MigrateToPool: tx.rawTx.accountList[instruction.Accounts[4]],
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return swaps, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func metaBcSwapParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if len(instruction.Accounts) < 15 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve swap not enough accounts, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
|
||||||
|
userBase := getAccountBalanceAfterTx(tx.rawTx, instruction.Accounts[3])
|
||||||
|
userQuote := getAccountBalanceAfterTx(tx.rawTx, instruction.Accounts[4])
|
||||||
|
inputToken := tx.rawTx.accountList[instruction.Accounts[3]]
|
||||||
|
outputToken := tx.rawTx.accountList[instruction.Accounts[4]]
|
||||||
|
|
||||||
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[5])
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve swap get base token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[6])
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve swap get quote token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
baseReserve, err := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve swap parse base reserve error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
quoteReserve, err := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve swap parse quote reserve error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
baseTokenProgram := baseTokenBalance.ProgramIDAccount
|
||||||
|
quoteTokenProgram := quoteTokenBalance.ProgramIDAccount
|
||||||
|
baseMintDecimals := uint8(baseTokenBalance.UITokenAmount.Decimals)
|
||||||
|
quoteMintDecimals := uint8(quoteTokenBalance.UITokenAmount.Decimals)
|
||||||
|
var prefixLen = offset[1]
|
||||||
|
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initial get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
swapEvent MetaoraBcSwapEvent
|
||||||
|
eventLoaded bool
|
||||||
|
event string
|
||||||
|
)
|
||||||
|
for innerIndex, innerInstr := range inners {
|
||||||
|
from, to, _, err := parseTokenTransfer(tx.rawTx, innerInstr)
|
||||||
|
if err == nil {
|
||||||
|
if from.Equals(inputToken) && to.Equals(tx.rawTx.accountList[quoteTokenBalance.AccountIndex]) {
|
||||||
|
event = "buy"
|
||||||
|
} else if from.Equals(tx.rawTx.accountList[quoteTokenBalance.AccountIndex]) && to.Equals(outputToken) {
|
||||||
|
event = "sell"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if innerInstr.ProgramIDIndex == instruction.ProgramIDIndex && len(innerInstr.Data) >= 16 &&
|
||||||
|
bytes.Equal(innerInstr.Data[0:8], pumpEventDiscriminator[:]) &&
|
||||||
|
bytes.Equal(innerInstr.Data[8:16], metaoraBcEventSwapDiscriminator[:]) {
|
||||||
|
if offset[1] == 0 {
|
||||||
|
offset[0] += 1
|
||||||
|
} else {
|
||||||
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
||||||
|
}
|
||||||
|
err := agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&swapEvent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve pool swap event deserialize event error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
eventLoaded = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !eventLoaded {
|
||||||
|
return nil, offset, fmt.Errorf("meta Bonding Curve swap event not found, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
baseMint := tx.rawTx.accountList[instruction.Accounts[7]]
|
||||||
|
quoteMint := tx.rawTx.accountList[instruction.Accounts[8]]
|
||||||
|
user := tx.rawTx.accountList[instruction.Accounts[9]]
|
||||||
|
pool := tx.rawTx.accountList[instruction.Accounts[2]]
|
||||||
|
var (
|
||||||
|
baseMintAmount decimal.Decimal
|
||||||
|
quoteMintAmount decimal.Decimal
|
||||||
|
)
|
||||||
|
if swapEvent.TradeDirection == 0 {
|
||||||
|
// A -> B
|
||||||
|
if event == "sell" {
|
||||||
|
baseMintAmount = decimal.NewFromUint64(swapEvent.SwapResult.ActualInputAmount)
|
||||||
|
quoteMintAmount = decimal.NewFromUint64(swapEvent.SwapResult.OutputAmount)
|
||||||
|
} else {
|
||||||
|
baseMintAmount = decimal.NewFromUint64(swapEvent.SwapResult.OutputAmount)
|
||||||
|
quoteMintAmount = decimal.NewFromUint64(swapEvent.SwapResult.ActualInputAmount)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// B -> A
|
||||||
|
if event == "buy" {
|
||||||
|
baseMintAmount = decimal.NewFromUint64(swapEvent.SwapResult.OutputAmount)
|
||||||
|
quoteMintAmount = decimal.NewFromUint64(swapEvent.SwapResult.ActualInputAmount)
|
||||||
|
} else {
|
||||||
|
baseMintAmount = decimal.NewFromUint64(swapEvent.SwapResult.ActualInputAmount)
|
||||||
|
quoteMintAmount = decimal.NewFromUint64(swapEvent.SwapResult.OutputAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
swaps := []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramMeteoraBondingCurve,
|
||||||
|
Event: event,
|
||||||
|
Pool: pool,
|
||||||
|
BaseMint: baseMint,
|
||||||
|
QuoteMint: quoteMint,
|
||||||
|
BaseTokenProgram: baseTokenProgram,
|
||||||
|
QuoteTokenProgram: quoteTokenProgram,
|
||||||
|
Creator: solana.PublicKey{},
|
||||||
|
BaseMintDecimals: baseMintDecimals,
|
||||||
|
QuoteMintDecimals: quoteMintDecimals,
|
||||||
|
User: user,
|
||||||
|
BaseAmount: baseMintAmount,
|
||||||
|
QuoteAmount: quoteMintAmount,
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
UserBaseBalance: userBase,
|
||||||
|
UserQuoteBalance: userQuote,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return swaps, offset, nil
|
||||||
|
}
|
||||||
486
meteoradamm.go
Normal file
486
meteoradamm.go
Normal file
@@ -0,0 +1,486 @@
|
|||||||
|
package pump_parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
agbinary "github.com/gagliardetto/binary"
|
||||||
|
"github.com/gagliardetto/solana-go"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func metaoraDammParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if !tx.rawTx.accountList[instruction.ProgramIDIndex].Equals(meteoraDammV2Program) {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("metaora damm program instruction not found, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
decode := instruction.Data
|
||||||
|
if len(decode) < 8 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("metaora damm program instruction data too short, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
discriminator := *(*[8]byte)(decode[:8])
|
||||||
|
|
||||||
|
switch discriminator {
|
||||||
|
case meteoraDammV2InitializeCustomizablePoolDiscriminator,
|
||||||
|
meteoraDammV2InitializePoolWithDynamicConfig,
|
||||||
|
meteoraDammV2InitializePoolDiscriminator:
|
||||||
|
return meteoraDammV2InitializePoolParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case meteoraDammV2SwapDiscriminator, meteoraDammV2SwapV2Discriminator:
|
||||||
|
return meteoraDammV2Swap(tx, instruction, innerInstructions, offset)
|
||||||
|
case meteoraDammV2AddLiquidityDiscriminator:
|
||||||
|
return meteoraDammV2AddLiquidityParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case meteoraDammV2RemoveLiquidityDiscriminator, meteoraDammV2RemoveAllLiquidityDiscriminator:
|
||||||
|
return meteoraDammV2RemoveLiquidityParser(tx, instruction, innerInstructions, offset)
|
||||||
|
default:
|
||||||
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
metaoraDammInitializePoolDiscriminator = []byte{228, 69, 165, 46, 81, 203, 154, 29, 228, 50, 246, 85, 203, 66, 134, 37}
|
||||||
|
meteoraDammSwapDiscriminator = []byte{228, 69, 165, 46, 81, 203, 154, 29, 189, 66, 51, 168, 38, 80, 117, 153}
|
||||||
|
// EvtLiquidityChange
|
||||||
|
meteoraDammAddLiquidityDiscriminator = []byte{228, 69, 165, 46, 81, 203, 154, 29, 197, 171, 78, 127, 224, 211, 87, 13}
|
||||||
|
meteoraDammRemoveLiquidityDiscriminator = []byte{228, 69, 165, 46, 81, 203, 154, 29, 197, 171, 78, 127, 224, 211, 87, 13}
|
||||||
|
)
|
||||||
|
|
||||||
|
type MetaoraDammDynamicFeeParameters struct {
|
||||||
|
BinStep uint16
|
||||||
|
BinStepU128 [16]byte
|
||||||
|
FilterPeriod uint16
|
||||||
|
DecayPeriod uint16
|
||||||
|
ReductionFactor uint16
|
||||||
|
MaxVolatilityAccumulator uint32
|
||||||
|
VariableFeeControl uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type MetaoraDammInitializePoolEvent struct {
|
||||||
|
Pool solana.PublicKey `json:"pool"`
|
||||||
|
TokenAMint solana.PublicKey `json:"tokenAMint"`
|
||||||
|
TokenBMint solana.PublicKey `json:"tokenBMint"`
|
||||||
|
Creator solana.PublicKey `json:"creator"`
|
||||||
|
Payer solana.PublicKey `json:"payer"`
|
||||||
|
AlphaVault solana.PublicKey `json:"alphaVault"`
|
||||||
|
//PoolFees *struct {
|
||||||
|
// BaseFee [30]byte
|
||||||
|
// DynamicFee *MetaoraDammDynamicFeeParameters `json:"dynamicFee"`
|
||||||
|
//} `json:"poolFees"`
|
||||||
|
//SqrtMinPrice [16]byte `json:"sqrtMinPrice"`
|
||||||
|
//SqrtMaxPrice [16]byte `json:"sqrtMaxPrice"`
|
||||||
|
//ActivationType uint8 `json:"activationType"`
|
||||||
|
//CollectFeeMode uint8 `json:"collectFeeMode"`
|
||||||
|
//Liquidity [16]byte `json:"liquidity"`
|
||||||
|
//SqrtPrice [16]byte `json:"sqrtPrice"`
|
||||||
|
//ActivationPoint uint64 `json:"activationPoint"`
|
||||||
|
//TokenAFlag uint8 `json:"tokenAFlag"`
|
||||||
|
//TokenBFlag uint8 `json:"tokenBFlag"`
|
||||||
|
//TokenAAmount uint64 `json:"tokenAAmount"`
|
||||||
|
//TokenBAmount uint64 `json:"tokenBAmount"`
|
||||||
|
//TotalAmountA uint64 `json:"totalAmountA"`
|
||||||
|
//TotalAmountB uint64 `json:"totalAmountB"`
|
||||||
|
//PoolType uint8 `json:"poolType"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func meteoraDammV2InitializePoolParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if len(instruction.Accounts) < 12 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("invalid instruction accounts length")
|
||||||
|
}
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
var prefixLen = offset[1]
|
||||||
|
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta damm initial get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
var loadedEvent bool
|
||||||
|
var initializePoolEvent MetaoraDammInitializePoolEvent
|
||||||
|
for i, innerInstruction := range inners {
|
||||||
|
if innerInstruction.ProgramIDIndex == instruction.ProgramIDIndex && len(innerInstruction.Data) >= 16 && bytes.Equal(innerInstruction.Data[:16], metaoraDammInitializePoolDiscriminator) {
|
||||||
|
err := agbinary.NewBorshDecoder(innerInstruction.Data[16:]).Decode(&initializePoolEvent)
|
||||||
|
if err != nil {
|
||||||
|
if offset[1] == 0 {
|
||||||
|
offset[0] += 1
|
||||||
|
} else {
|
||||||
|
offset[1] = uint(i) + 1 + prefixLen
|
||||||
|
}
|
||||||
|
return nil, offset, fmt.Errorf("failed to deserialize initialize pool event: %w", err)
|
||||||
|
}
|
||||||
|
if offset[1] == 0 {
|
||||||
|
offset[0] += 1
|
||||||
|
} else {
|
||||||
|
offset[1] = uint(i) + 1 + prefixLen
|
||||||
|
}
|
||||||
|
loadedEvent = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !loadedEvent {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get initialize pool event")
|
||||||
|
}
|
||||||
|
baseVaultAccountIndex := instruction.Accounts[10]
|
||||||
|
quoteVaultAccountIndex := instruction.Accounts[11]
|
||||||
|
if bytes.Equal(instruction.Data[:8], meteoraDammV2InitializePoolWithDynamicConfig[:]) {
|
||||||
|
baseVaultAccountIndex = instruction.Accounts[11]
|
||||||
|
quoteVaultAccountIndex = instruction.Accounts[12]
|
||||||
|
} else if bytes.Equal(instruction.Data[:8], meteoraDammV2InitializeCustomizablePoolDiscriminator[:]) {
|
||||||
|
baseVaultAccountIndex = instruction.Accounts[9]
|
||||||
|
quoteVaultAccountIndex = instruction.Accounts[10]
|
||||||
|
}
|
||||||
|
|
||||||
|
baseVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultAccountIndex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("metaora damm get base vault token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
quoteVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultAccountIndex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("metaora damm get quote vault token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
baseReserve, _ := decimal.NewFromString(baseVaultTokenBalance.UITokenAmount.Amount)
|
||||||
|
quoteReserve, _ := decimal.NewFromString(quoteVaultTokenBalance.UITokenAmount.Amount)
|
||||||
|
|
||||||
|
swap := Swap{
|
||||||
|
Program: SolProgramMeteoraAmmV2,
|
||||||
|
Event: "create",
|
||||||
|
Pool: initializePoolEvent.Pool,
|
||||||
|
BaseMint: initializePoolEvent.TokenAMint,
|
||||||
|
QuoteMint: initializePoolEvent.TokenBMint,
|
||||||
|
BaseTokenProgram: baseVaultTokenBalance.ProgramIDAccount,
|
||||||
|
QuoteTokenProgram: quoteVaultTokenBalance.ProgramIDAccount,
|
||||||
|
Creator: initializePoolEvent.Creator,
|
||||||
|
BaseMintDecimals: uint8(baseVaultTokenBalance.UITokenAmount.Decimals),
|
||||||
|
QuoteMintDecimals: uint8(quoteVaultTokenBalance.UITokenAmount.Decimals),
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
User: tx.rawTx.accountList[instruction.Accounts[0]],
|
||||||
|
LpMint: tx.rawTx.accountList[instruction.Accounts[1]],
|
||||||
|
EntryContract: entryContract,
|
||||||
|
}
|
||||||
|
return []Swap{swap}, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type meteoraDammSwapEvent struct {
|
||||||
|
Pool solana.PublicKey
|
||||||
|
TradeDirection uint8
|
||||||
|
CollectFeeMode uint8
|
||||||
|
HasReferral bool
|
||||||
|
|
||||||
|
Params *struct {
|
||||||
|
Amount0 uint64
|
||||||
|
Amount1 uint64
|
||||||
|
SwapMode uint8
|
||||||
|
}
|
||||||
|
SwapResult *struct {
|
||||||
|
IncludedFeeInputAmount uint64
|
||||||
|
ExcludedFeeInputAmount uint64
|
||||||
|
AmountLeft uint64
|
||||||
|
OutputAmount uint64
|
||||||
|
NextSqrtPrice [16]byte
|
||||||
|
TradingFee uint64
|
||||||
|
ProtocolFee uint64
|
||||||
|
PartnerFee uint64
|
||||||
|
ReferralFee uint64
|
||||||
|
}
|
||||||
|
IncludedTransferFeeAmountIn uint64
|
||||||
|
IncludedTransferFeeAmountOut uint64
|
||||||
|
ExcludedTransferFeeAmountOut uint64
|
||||||
|
CurrentTimestamp uint64
|
||||||
|
ReserveAAmount uint64
|
||||||
|
ReserveBAmount uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func meteoraDammV2Swap(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if len(instruction.Accounts) < 9 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("invalid instruction accounts length")
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceAccountIndex := instruction.Accounts[2]
|
||||||
|
destinationAccountIndex := instruction.Accounts[3]
|
||||||
|
baseVaultAccountIndex := instruction.Accounts[4]
|
||||||
|
quoteVaultAccountIndex := instruction.Accounts[5]
|
||||||
|
tokenAMint := tx.rawTx.accountList[instruction.Accounts[6]]
|
||||||
|
tokenBMint := tx.rawTx.accountList[instruction.Accounts[7]]
|
||||||
|
payer := tx.rawTx.accountList[instruction.Accounts[8]]
|
||||||
|
|
||||||
|
baseVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultAccountIndex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("metaora damm get base vault token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
quoteVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultAccountIndex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("metaora damm get quote vault token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
baseReserve, _ := decimal.NewFromString(baseVaultTokenBalance.UITokenAmount.Amount)
|
||||||
|
quoteReserve, _ := decimal.NewFromString(quoteVaultTokenBalance.UITokenAmount.Amount)
|
||||||
|
|
||||||
|
baseMint := tokenAMint
|
||||||
|
quoteMint := tokenBMint
|
||||||
|
baseTokenProgram := baseVaultTokenBalance.ProgramIDAccount
|
||||||
|
quoteTokenProgram := quoteVaultTokenBalance.ProgramIDAccount
|
||||||
|
baseMintDecimals := uint8(baseVaultTokenBalance.UITokenAmount.Decimals)
|
||||||
|
quoteMintDecimals := uint8(quoteVaultTokenBalance.UITokenAmount.Decimals)
|
||||||
|
|
||||||
|
userInputTokenBalance := getAccountBalanceAfterTx(tx.rawTx, sourceAccountIndex)
|
||||||
|
userOutputTokenBalance := getAccountBalanceAfterTx(tx.rawTx, destinationAccountIndex)
|
||||||
|
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
var prefixLen = offset[1]
|
||||||
|
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initial get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
var loadedEvent bool
|
||||||
|
var swapEvent meteoraDammSwapEvent
|
||||||
|
for i, innerInstruction := range inners {
|
||||||
|
if innerInstruction.ProgramIDIndex == instruction.ProgramIDIndex && len(innerInstruction.Data) >= 16 && bytes.Equal(innerInstruction.Data[:16], meteoraDammSwapDiscriminator) {
|
||||||
|
err := agbinary.NewBorshDecoder(innerInstruction.Data[16:]).Decode(&swapEvent)
|
||||||
|
if offset[1] == 0 {
|
||||||
|
offset[0] += 1
|
||||||
|
} else {
|
||||||
|
offset[1] = uint(i) + 1 + prefixLen
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, offset, fmt.Errorf("failed to deserialize swap event: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedEvent = true
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !loadedEvent {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get swap event")
|
||||||
|
}
|
||||||
|
var baseAmount decimal.Decimal
|
||||||
|
var quoteAmount decimal.Decimal
|
||||||
|
var userBase decimal.Decimal
|
||||||
|
var userQuote decimal.Decimal
|
||||||
|
event := "buy"
|
||||||
|
if swapEvent.TradeDirection == 0 {
|
||||||
|
// A -> B
|
||||||
|
// sell base/A; buy quote/B
|
||||||
|
event = "sell"
|
||||||
|
userBase = userInputTokenBalance
|
||||||
|
userQuote = userOutputTokenBalance
|
||||||
|
baseAmount = decimal.NewFromUint64(swapEvent.SwapResult.IncludedFeeInputAmount)
|
||||||
|
quoteAmount = decimal.NewFromUint64(swapEvent.ExcludedTransferFeeAmountOut)
|
||||||
|
|
||||||
|
} else if swapEvent.TradeDirection == 1 {
|
||||||
|
// B -> A
|
||||||
|
// sell quote/B; buy base/A
|
||||||
|
userBase = userOutputTokenBalance
|
||||||
|
userQuote = userInputTokenBalance
|
||||||
|
baseAmount = decimal.NewFromUint64(swapEvent.ExcludedTransferFeeAmountOut)
|
||||||
|
quoteAmount = decimal.NewFromUint64(swapEvent.SwapResult.IncludedFeeInputAmount)
|
||||||
|
} else {
|
||||||
|
return nil, offset, fmt.Errorf("invalid trade direction")
|
||||||
|
}
|
||||||
|
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramMeteoraAmmV2,
|
||||||
|
Event: event,
|
||||||
|
Pool: swapEvent.Pool,
|
||||||
|
BaseMint: baseMint,
|
||||||
|
QuoteMint: quoteMint,
|
||||||
|
BaseTokenProgram: baseTokenProgram,
|
||||||
|
QuoteTokenProgram: quoteTokenProgram,
|
||||||
|
Creator: solana.PublicKey{},
|
||||||
|
BaseMintDecimals: baseMintDecimals,
|
||||||
|
QuoteMintDecimals: quoteMintDecimals,
|
||||||
|
User: payer,
|
||||||
|
BaseAmount: baseAmount,
|
||||||
|
QuoteAmount: quoteAmount,
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
UserBaseBalance: userBase,
|
||||||
|
UserQuoteBalance: userQuote,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, offset, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type MeteoraDammV2LiquidityData struct {
|
||||||
|
LiquidityDelta [16]byte `json:"liquidityDelta"`
|
||||||
|
TokenAAmounthreshold uint64 `json:"tokenAAmounthreshold"`
|
||||||
|
TokenBAmounthreshold uint64 `json:"tokenBAmounthreshold"`
|
||||||
|
}
|
||||||
|
type MeteoraDammV2AddLiquidityEvent struct {
|
||||||
|
Pool solana.PublicKey `json:"pool"`
|
||||||
|
Position solana.PublicKey `json:"position"`
|
||||||
|
Owner solana.PublicKey `json:"owner"`
|
||||||
|
Params *MeteoraDammV2LiquidityData `json:"params"`
|
||||||
|
TokenAAmount uint64 `json:"tokenAAmount"`
|
||||||
|
TokenBAmount uint64 `json:"tokenBAmount"`
|
||||||
|
TotalAmountA uint64 `json:"totalAmountA"`
|
||||||
|
TotalAmountB uint64 `json:"totalAmountB"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MeteoraDammV2RemoveLiquidityEvent struct {
|
||||||
|
Pool solana.PublicKey `json:"pool"`
|
||||||
|
Position solana.PublicKey `json:"position"`
|
||||||
|
Owner solana.PublicKey `json:"owner"`
|
||||||
|
Params *MeteoraDammV2LiquidityData `json:"params"`
|
||||||
|
TokenAAmount uint64 `json:"tokenAAmount"`
|
||||||
|
TokenBAmount uint64 `json:"tokenBAmount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func meteoraDammV2AddLiquidityParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
|
||||||
|
if len(instruction.Accounts) < 8 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("invalid instruction accounts length")
|
||||||
|
}
|
||||||
|
tokenAMint := tx.rawTx.accountList[instruction.Accounts[6]]
|
||||||
|
tokenBMint := tx.rawTx.accountList[instruction.Accounts[7]]
|
||||||
|
|
||||||
|
baseVaultAccountIndex := instruction.Accounts[4]
|
||||||
|
quoteVaultAccountIndex := instruction.Accounts[5]
|
||||||
|
|
||||||
|
baseVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultAccountIndex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("metaora damm get base vault token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
quoteVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultAccountIndex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("metaora damm get quote vault token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
baseReserve, _ := decimal.NewFromString(baseVaultTokenBalance.UITokenAmount.Amount)
|
||||||
|
quoteReserve, _ := decimal.NewFromString(quoteVaultTokenBalance.UITokenAmount.Amount)
|
||||||
|
|
||||||
|
baseTokenProgram := baseVaultTokenBalance.ProgramIDAccount
|
||||||
|
quoteTokenProgram := quoteVaultTokenBalance.ProgramIDAccount
|
||||||
|
baseMintDecimals := uint8(baseVaultTokenBalance.UITokenAmount.Decimals)
|
||||||
|
quoteMintDecimals := uint8(quoteVaultTokenBalance.UITokenAmount.Decimals)
|
||||||
|
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
var prefixLen = offset[1]
|
||||||
|
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initial get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
var loadedEvent bool
|
||||||
|
var liquidityEvent MeteoraDammV2AddLiquidityEvent
|
||||||
|
for i, innerInstruction := range inners {
|
||||||
|
if innerInstruction.ProgramIDIndex == instruction.ProgramIDIndex && len(innerInstruction.Data) >= 16 && bytes.Equal(innerInstruction.Data[:16], meteoraDammAddLiquidityDiscriminator[:]) {
|
||||||
|
err := agbinary.NewBorshDecoder(innerInstruction.Data[16:]).Decode(&liquidityEvent)
|
||||||
|
if err != nil {
|
||||||
|
if offset[1] == 0 {
|
||||||
|
offset[0] += 1
|
||||||
|
} else {
|
||||||
|
offset[1] = uint(i) + 1 + prefixLen
|
||||||
|
}
|
||||||
|
return nil, offset, fmt.Errorf("failed to deserialize add liquidity event: %w", err)
|
||||||
|
}
|
||||||
|
if offset[1] == 0 {
|
||||||
|
offset[0] += 1
|
||||||
|
} else {
|
||||||
|
offset[1] = uint(i) + 1 + prefixLen
|
||||||
|
}
|
||||||
|
loadedEvent = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !loadedEvent {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get add liquidity event")
|
||||||
|
}
|
||||||
|
swap := Swap{
|
||||||
|
Program: SolProgramMeteoraDLMM,
|
||||||
|
Event: "add_liquidity",
|
||||||
|
Pool: liquidityEvent.Pool,
|
||||||
|
BaseMint: tokenAMint,
|
||||||
|
QuoteMint: tokenBMint,
|
||||||
|
BaseTokenProgram: baseTokenProgram,
|
||||||
|
QuoteTokenProgram: quoteTokenProgram,
|
||||||
|
BaseMintDecimals: baseMintDecimals,
|
||||||
|
QuoteMintDecimals: quoteMintDecimals,
|
||||||
|
User: liquidityEvent.Owner,
|
||||||
|
BaseAmount: decimal.NewFromUint64(liquidityEvent.TokenAAmount),
|
||||||
|
QuoteAmount: decimal.NewFromUint64(liquidityEvent.TokenBAmount),
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
}
|
||||||
|
|
||||||
|
return []Swap{swap}, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func meteoraDammV2RemoveLiquidityParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if len(instruction.Accounts) < 8 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("invalid instruction accounts length")
|
||||||
|
}
|
||||||
|
tokenAMint := tx.rawTx.accountList[instruction.Accounts[7]]
|
||||||
|
tokenBMint := tx.rawTx.accountList[instruction.Accounts[8]]
|
||||||
|
|
||||||
|
baseVaultAccountIndex := instruction.Accounts[5]
|
||||||
|
quoteVaultAccountIndex := instruction.Accounts[6]
|
||||||
|
|
||||||
|
baseVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultAccountIndex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("metaora damm get base vault token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
quoteVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultAccountIndex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("metaora damm get quote vault token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
baseReserve, _ := decimal.NewFromString(baseVaultTokenBalance.UITokenAmount.Amount)
|
||||||
|
quoteReserve, _ := decimal.NewFromString(quoteVaultTokenBalance.UITokenAmount.Amount)
|
||||||
|
|
||||||
|
baseTokenProgram := baseVaultTokenBalance.ProgramIDAccount
|
||||||
|
quoteTokenProgram := quoteVaultTokenBalance.ProgramIDAccount
|
||||||
|
baseMintDecimals := uint8(baseVaultTokenBalance.UITokenAmount.Decimals)
|
||||||
|
quoteMintDecimals := uint8(quoteVaultTokenBalance.UITokenAmount.Decimals)
|
||||||
|
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
var prefixLen = offset[1]
|
||||||
|
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meta damm get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
var loadedEvent bool
|
||||||
|
var liquidityEvent MeteoraDammV2RemoveLiquidityEvent
|
||||||
|
for i, innerInstruction := range inners {
|
||||||
|
if innerInstruction.ProgramIDIndex == instruction.ProgramIDIndex && len(innerInstruction.Data) >= 16 && bytes.Equal(innerInstruction.Data[:16], meteoraDammRemoveLiquidityDiscriminator[:]) {
|
||||||
|
err := agbinary.NewBorshDecoder(innerInstruction.Data[16:]).Decode(&liquidityEvent)
|
||||||
|
if err != nil {
|
||||||
|
if offset[1] == 0 {
|
||||||
|
offset[0] += 1
|
||||||
|
} else {
|
||||||
|
offset[1] = uint(i) + 1 + prefixLen
|
||||||
|
}
|
||||||
|
return nil, offset, fmt.Errorf("failed to deserialize remove liquidity event: %w", err)
|
||||||
|
}
|
||||||
|
if offset[1] == 0 {
|
||||||
|
offset[0] += 1
|
||||||
|
} else {
|
||||||
|
offset[1] = uint(i) + 1 + prefixLen
|
||||||
|
}
|
||||||
|
loadedEvent = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !loadedEvent {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get remove liquidity event")
|
||||||
|
}
|
||||||
|
swap := Swap{
|
||||||
|
Program: SolProgramMeteoraDLMM,
|
||||||
|
Event: "remove_liquidity",
|
||||||
|
Pool: liquidityEvent.Pool,
|
||||||
|
BaseMint: tokenAMint,
|
||||||
|
QuoteMint: tokenBMint,
|
||||||
|
BaseTokenProgram: baseTokenProgram,
|
||||||
|
QuoteTokenProgram: quoteTokenProgram,
|
||||||
|
BaseMintDecimals: baseMintDecimals,
|
||||||
|
QuoteMintDecimals: quoteMintDecimals,
|
||||||
|
User: liquidityEvent.Owner,
|
||||||
|
BaseAmount: decimal.NewFromUint64(liquidityEvent.TokenAAmount),
|
||||||
|
QuoteAmount: decimal.NewFromUint64(liquidityEvent.TokenBAmount),
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
}
|
||||||
|
|
||||||
|
return []Swap{swap}, offset, nil
|
||||||
|
}
|
||||||
1262
orcawhirpool.go
Normal file
1262
orcawhirpool.go
Normal file
File diff suppressed because it is too large
Load Diff
212
parser.go
212
parser.go
@@ -2,20 +2,67 @@ package pump_parser
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
"slices"
|
||||||
|
|
||||||
"github.com/gagliardetto/solana-go"
|
"github.com/gagliardetto/solana-go"
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
)
|
)
|
||||||
|
|
||||||
var swapPrograms = map[solana.PublicKey]swapParser{
|
var defaultSwapPrograms = map[solana.PublicKey]swapParser{
|
||||||
pumpAmmProgram: pumpAmmParser,
|
pumpAmmProgram: pumpAmmParser,
|
||||||
pumpProgram: pumpParser,
|
pumpProgram: pumpParser,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errTxParserPrograms = map[solana.PublicKey]swapParser{
|
||||||
|
pumpAmmProgram: pumpAmmParser,
|
||||||
|
pumpProgram: pumpParser,
|
||||||
|
}
|
||||||
|
|
||||||
|
var swapPrograms = cloneSwapPrograms(defaultSwapPrograms)
|
||||||
|
|
||||||
|
type ParserOption func(*parserConfig)
|
||||||
|
|
||||||
|
type parserConfig struct {
|
||||||
|
enableMeteoraDlmm bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func EnableAllParsers() {
|
||||||
|
programs := cloneSwapPrograms(defaultSwapPrograms)
|
||||||
|
programs[meteoraDlmmProgram] = metaoradlmmParser
|
||||||
|
programs[metaoraPoolProgramID] = metaoraPoolParser
|
||||||
|
programs[metaoraBcProgramID] = metaoraBcParser
|
||||||
|
programs[meteoraDammV2Program] = metaoraDammParser
|
||||||
|
programs[orcaProgramID] = orcaWhirPoolParser
|
||||||
|
programs[raydiumV4Program] = raydiumV4Parser
|
||||||
|
programs[raydiumClmmProgramID] = raydiumClmmParser
|
||||||
|
programs[raydiumCPmmProgramID] = raydiumCPmmParser
|
||||||
|
programs[raydiumLaunchLabProgramID] = raydiumLaunchLabParser
|
||||||
|
swapPrograms = programs
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitParser(opts ...ParserOption) {
|
||||||
|
cfg := parserConfig{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
programs := cloneSwapPrograms(defaultSwapPrograms)
|
||||||
|
if cfg.enableMeteoraDlmm {
|
||||||
|
programs[meteoraDlmmProgram] = metaoradlmmParser
|
||||||
|
}
|
||||||
|
swapPrograms = programs
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithMeteoraDlmm() ParserOption {
|
||||||
|
return func(cfg *parserConfig) {
|
||||||
|
cfg.enableMeteoraDlmm = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
||||||
@@ -34,23 +81,80 @@ func (tx *Tx) Parser() error {
|
|||||||
return errors.New("rawTx is nil")
|
return errors.New("rawTx is nil")
|
||||||
}
|
}
|
||||||
accountList := tx.rawTx.getAccountList()
|
accountList := tx.rawTx.getAccountList()
|
||||||
|
if tx.rawTx.Meta.Err == nil {
|
||||||
|
for _, acc := range tx.rawTx.Transaction.Message.Instructions {
|
||||||
|
if accountList[acc.ProgramIDIndex] == solana.VoteProgramID {
|
||||||
|
tx.Vote = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tx.TxHash = (*[64]byte)((tx.rawTx.Transaction.Signatures[0][:]))
|
tx.TxHash = (*[64]byte)((tx.rawTx.Transaction.Signatures[0][:]))
|
||||||
tx.Signer = tx.rawTx.GetSigner()
|
tx.Signer = tx.rawTx.GetSigner()
|
||||||
tx.Block = tx.rawTx.Slot
|
tx.Block = tx.rawTx.Slot
|
||||||
tx.BlockIndex = uint64(tx.rawTx.IndexWithinBlock)
|
tx.BlockIndex = uint64(tx.rawTx.IndexWithinBlock)
|
||||||
tx.BlockAt = tx.rawTx.BlockTime
|
tx.BlockAt = tx.rawTx.BlockTime
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
txIndex := 0
|
||||||
for i, instr := range tx.rawTx.Transaction.Message.Instructions {
|
for i, instr := range tx.rawTx.Transaction.Message.Instructions {
|
||||||
|
txIndex += 1
|
||||||
|
if i > 0 {
|
||||||
|
txIndex += len(innersMap[i-1].Instructions)
|
||||||
|
}
|
||||||
programAccount := accountList[instr.ProgramIDIndex]
|
programAccount := accountList[instr.ProgramIDIndex]
|
||||||
if p, exists := swapPrograms[programAccount]; exists {
|
if p, exists := swapPrograms[programAccount]; exists {
|
||||||
swaps, _, err := p(tx, instr, innersMap[i], [2]uint{uint(i), uint(0)})
|
swaps, _, err := p(tx, instr, innersMap[i], [2]uint{uint(i), uint(0)})
|
||||||
@@ -60,7 +164,21 @@ func (tx *Tx) Parser() error {
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tx.Swaps = append(tx.Swaps, swaps...)
|
for k, swap := range swaps {
|
||||||
|
swap.TxIndex = txIndex + k
|
||||||
|
if !swap.User.IsOnCurve() {
|
||||||
|
swap.AfterSOLBalance = tx.AfterSOLBalance
|
||||||
|
swap.User = tx.rawTx.accountList[0]
|
||||||
|
} else {
|
||||||
|
userIdx := slices.Index(tx.rawTx.accountList, swap.User)
|
||||||
|
if userIdx >= 0 {
|
||||||
|
swap.AfterSOLBalance = decimal.NewFromUint64(tx.rawTx.Meta.PostBalances[userIdx]).Div(decimal.NewFromInt(1e9))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
swap.InstrIdx = uint8(i)
|
||||||
|
tx.Swaps = append(tx.Swaps, swap)
|
||||||
|
}
|
||||||
|
|
||||||
} else if p, exists := actionPrograms[programAccount]; exists {
|
} else if p, exists := actionPrograms[programAccount]; exists {
|
||||||
_, err := p(tx, instr, innersMap[i], [2]uint{uint(i), uint(0)})
|
_, err := p(tx, instr, innersMap[i], [2]uint{uint(i), uint(0)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -75,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]
|
||||||
@@ -90,9 +208,33 @@ func (tx *Tx) Parser() error {
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tx.Swaps = append(tx.Swaps, swaps...)
|
for k, swap := range swaps {
|
||||||
j = int(offset[1])
|
swap.TxIndex = txIndex + k + j
|
||||||
ii = int(offset[0])
|
// identify okxDexRoutersV2 and okxAggregatorV2 is user
|
||||||
|
//if !swap.User.IsOnCurve() && (swap.EntryContract.Equals(okxDexRoutersV2) || swap.EntryContract.Equals(okxAggregatorV2)) {
|
||||||
|
// swap.User = tx.rawTx.accountList[0]
|
||||||
|
//}
|
||||||
|
if !swap.User.IsOnCurve() {
|
||||||
|
swap.AfterSOLBalance = tx.AfterSOLBalance
|
||||||
|
swap.User = tx.rawTx.accountList[0]
|
||||||
|
} else {
|
||||||
|
userIdx := slices.Index(tx.rawTx.accountList, swap.User)
|
||||||
|
if userIdx >= 0 {
|
||||||
|
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, swaps...)
|
||||||
|
if ii == int(offset[0]) && j == int(offset[1]) {
|
||||||
|
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 {
|
||||||
@@ -113,6 +255,58 @@ func (tx *Tx) Parser() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// update swaps same program+pair with last reserve balance
|
||||||
|
if len(tx.Swaps) > 1 {
|
||||||
|
pairKey := func(s Swap) solana.PublicKey {
|
||||||
|
// Match pair selection used by downstream consumers.
|
||||||
|
if s.Program == SolProgramPump {
|
||||||
|
return s.BaseMint
|
||||||
|
}
|
||||||
|
return s.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
lastReserve := make(map[solana.PublicKey]reserveSnapshot, len(tx.Swaps))
|
||||||
|
for _, swap := range tx.Swaps {
|
||||||
|
lastReserve[pairKey(swap)] = reserveSnapshot{
|
||||||
|
baseMint: swap.BaseMint,
|
||||||
|
quoteMint: swap.QuoteMint,
|
||||||
|
baseReserve: swap.BaseReserve,
|
||||||
|
quoteReserve: swap.QuoteReserve,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range tx.Swaps {
|
||||||
|
key := pairKey(tx.Swaps[i])
|
||||||
|
if v, ok := lastReserve[key]; ok {
|
||||||
|
if tx.Swaps[i].BaseMint == v.baseMint && tx.Swaps[i].QuoteMint == v.quoteMint {
|
||||||
|
tx.Swaps[i].BaseReserve = v.baseReserve
|
||||||
|
tx.Swaps[i].QuoteReserve = v.quoteReserve
|
||||||
|
} else if tx.Swaps[i].BaseMint == v.quoteMint && tx.Swaps[i].QuoteMint == v.baseMint {
|
||||||
|
tx.Swaps[i].BaseReserve = v.quoteReserve
|
||||||
|
tx.Swaps[i].QuoteReserve = v.baseReserve
|
||||||
|
}
|
||||||
|
//else {
|
||||||
|
// tx.Swaps[i].BaseReserve = v.baseReserve
|
||||||
|
// tx.Swaps[i].QuoteReserve = v.quoteReserve
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type reserveSnapshot struct {
|
||||||
|
baseMint solana.PublicKey
|
||||||
|
quoteMint solana.PublicKey
|
||||||
|
baseReserve decimal.Decimal
|
||||||
|
quoteReserve decimal.Decimal
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloneSwapPrograms(src map[solana.PublicKey]swapParser) map[solana.PublicKey]swapParser {
|
||||||
|
dst := make(map[solana.PublicKey]swapParser, len(src))
|
||||||
|
for k, v := range src {
|
||||||
|
dst[k] = v
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|||||||
159
pump.go
159
pump.go
@@ -27,7 +27,6 @@ func pumpParser(tx *Tx, instruction Instruction, innerInstructions InnerInstruct
|
|||||||
|
|
||||||
decode := instruction.Data
|
decode := instruction.Data
|
||||||
if len(decode) < 8 {
|
if len(decode) < 8 {
|
||||||
offset[1] += 1
|
|
||||||
return nil, increaseOffset(offset), fmt.Errorf("pump program instruction data too short, offset, %d, %d", offset[0], offset[1])
|
return nil, increaseOffset(offset), fmt.Errorf("pump program instruction data too short, offset, %d, %d", offset[0], offset[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,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
|
||||||
@@ -80,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) {
|
||||||
@@ -101,7 +110,7 @@ func CreateParser(tx *Tx, instr Instruction, innerInstructions InnerInstructions
|
|||||||
if offset[1] == 0 {
|
if offset[1] == 0 {
|
||||||
offset[0] += 1
|
offset[0] += 1
|
||||||
} else {
|
} else {
|
||||||
offset[1] += uint(innerIndex) + 1 + prefixLen
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, offset, fmt.Errorf("pump create event decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
return nil, offset, fmt.Errorf("pump create event decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
@@ -149,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,
|
||||||
@@ -177,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 {
|
||||||
@@ -192,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]
|
||||||
@@ -218,10 +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 inners {
|
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 {
|
||||||
|
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),
|
||||||
@@ -470,12 +603,14 @@ func MigrateParser(tx *Tx, instr Instruction, innerInstructions InnerInstruction
|
|||||||
User: migrateEvent.User,
|
User: migrateEvent.User,
|
||||||
//BaseAmount: decimal.Decimal{},
|
//BaseAmount: decimal.Decimal{},
|
||||||
//QuoteAmount: decimal.Decimal{},
|
//QuoteAmount: decimal.Decimal{},
|
||||||
BaseReserve: decimal.NewFromUint64(migrateEvent.MintAmount),
|
BaseReserve: decimal.NewFromUint64(migrateEvent.MintAmount),
|
||||||
QuoteReserve: decimal.NewFromUint64(migrateEvent.SolAmount),
|
QuoteReserve: decimal.NewFromUint64(migrateEvent.SolAmount),
|
||||||
Mayhem: createEvent.IsMayhemMode,
|
Mayhem: createEvent.IsMayhemMode,
|
||||||
UserBaseBalance: userBase,
|
MigrateTopProgram: pumpAmmProgram,
|
||||||
UserQuoteBalance: decimal.NewFromUint64(userQuote),
|
MigrateToPool: migrateEvent.Pool,
|
||||||
EntryContract: entryContract,
|
UserBaseBalance: userBase,
|
||||||
|
UserQuoteBalance: decimal.NewFromUint64(userQuote),
|
||||||
|
EntryContract: entryContract,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
swaps = append(swaps, Swap{
|
swaps = append(swaps, Swap{
|
||||||
|
|||||||
14
pump_test.go
14
pump_test.go
@@ -1,11 +1,13 @@
|
|||||||
package pump_parser
|
package pump_parser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
agbinary "github.com/gagliardetto/binary"
|
agbinary "github.com/gagliardetto/binary"
|
||||||
|
"github.com/gagliardetto/solana-go"
|
||||||
"github.com/mr-tron/base58"
|
"github.com/mr-tron/base58"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,3 +31,15 @@ func TestTradeEvent(t *testing.T) {
|
|||||||
fmt.Println(len(xx), err)
|
fmt.Println(len(xx), err)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
func TestCal(t *testing.T) {
|
||||||
|
//e445a52e51cb9a1db94afc7d1bd7bc6f5e99e54b
|
||||||
|
// . b94afc7d1bd7bc6f
|
||||||
|
s := calculateDiscriminator("global:initialize_with_permission")
|
||||||
|
fmt.Println(hex.EncodeToString(s[:]))
|
||||||
|
|
||||||
|
s2, _ := base58.Decode("6ApXSNCamGdm")
|
||||||
|
s3 := binary.LittleEndian.Uint64(s2[1:])
|
||||||
|
fmt.Println(s2, s3)
|
||||||
|
|
||||||
|
fmt.Println(solana.MustPublicKeyFromBase58("BM9CcyErJcu2mjrFvUsRRrD3snGeHDDVirJLvL6EjvMN").IsOnCurve())
|
||||||
|
}
|
||||||
|
|||||||
303
pumpamm.go
303
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
|
||||||
@@ -180,7 +199,7 @@ func ammCreatePoolParser(tx *Tx, instruction Instruction, innerInstructions Inne
|
|||||||
if offset[1] == 0 {
|
if offset[1] == 0 {
|
||||||
offset[0] += 1
|
offset[0] += 1
|
||||||
} else {
|
} else {
|
||||||
offset[1] += uint(innerIndex) + 1 + prefixLen
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, offset, fmt.Errorf("pump amm create pool event decode error: %v, offset: %d, %d", err, offset[0], prefixLen)
|
return nil, offset, fmt.Errorf("pump amm create pool event decode error: %v, offset: %d, %d", err, offset[0], prefixLen)
|
||||||
@@ -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,10 +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 inners {
|
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 {
|
||||||
|
entryContract = result.accountList[innerInstr.ProgramIDIndex]
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -262,7 +532,7 @@ func ammBuyParser(tx *Tx, instruction Instruction, innerInstructions InnerInstru
|
|||||||
if offset[1] == 0 {
|
if offset[1] == 0 {
|
||||||
offset[0] += 1
|
offset[0] += 1
|
||||||
} else {
|
} else {
|
||||||
offset[1] += uint(innerIndex) + 1 + prefixLen
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, offset, fmt.Errorf("pump amm buy pool event decode error: %v, offset: %d, %d", err, offset[0], prefixLen)
|
return nil, offset, fmt.Errorf("pump amm buy pool event decode error: %v, offset: %d, %d", err, offset[0], prefixLen)
|
||||||
@@ -328,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,
|
||||||
@@ -346,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,
|
||||||
@@ -363,10 +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 inners {
|
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 {
|
||||||
|
entryContract = result.accountList[innerInstr.ProgramIDIndex]
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -380,7 +655,7 @@ func ammSellParser(tx *Tx, instruction Instruction, innerInstructions InnerInstr
|
|||||||
if offset[1] == 0 {
|
if offset[1] == 0 {
|
||||||
offset[0] += 1
|
offset[0] += 1
|
||||||
} else {
|
} else {
|
||||||
offset[1] += uint(innerIndex) + 1 + prefixLen
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, offset, fmt.Errorf("pump amm sell pool event decode error: %v, offset: %d, %d", err, offset[0], prefixLen)
|
return nil, offset, fmt.Errorf("pump amm sell pool event decode error: %v, offset: %d, %d", err, offset[0], prefixLen)
|
||||||
@@ -446,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,
|
||||||
@@ -464,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,
|
||||||
@@ -491,7 +768,7 @@ func depositParse(tx *Tx, instruction Instruction, innerInstructions InnerInstru
|
|||||||
if offset[1] == 0 {
|
if offset[1] == 0 {
|
||||||
offset[0] += 1
|
offset[0] += 1
|
||||||
} else {
|
} else {
|
||||||
offset[1] += uint(innerIndex) + 1 + prefixLen
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, offset, fmt.Errorf("pump amm deposit pool event decode error: %v, offset: %d, %d", err, offset[0], prefixLen)
|
return nil, offset, fmt.Errorf("pump amm deposit pool event decode error: %v, offset: %d, %d", err, offset[0], prefixLen)
|
||||||
@@ -589,7 +866,7 @@ func withdrawParse(tx *Tx, instruction Instruction, innerInstructions InnerInstr
|
|||||||
if offset[1] == 0 {
|
if offset[1] == 0 {
|
||||||
offset[0] += 1
|
offset[0] += 1
|
||||||
} else {
|
} else {
|
||||||
offset[1] += uint(innerIndex) + 1 + prefixLen
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, offset, fmt.Errorf("pump amm withdraw pool event decode error: %v, offset: %d, %d", err, offset[0], prefixLen)
|
return nil, offset, fmt.Errorf("pump amm withdraw pool event decode error: %v, offset: %d, %d", err, offset[0], prefixLen)
|
||||||
|
|||||||
194
rawtx.go
194
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 {
|
||||||
@@ -514,6 +585,79 @@ func intSliceFromUint16Slice(in []uint16) []int {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getAtaIdxByOwner(result *RawTx, owner solana.PublicKey, mint solana.PublicKey) (int, error) {
|
||||||
|
var preBalance *TokenBalance
|
||||||
|
for _, pre := range result.Meta.PreTokenBalances {
|
||||||
|
if pre.MintAccount == mint && pre.OwnerAccount != nil && pre.OwnerAccount.Equals(owner) {
|
||||||
|
preBalance = &pre
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var postBalance *TokenBalance
|
||||||
|
|
||||||
|
for _, post := range result.Meta.PostTokenBalances {
|
||||||
|
if post.MintAccount == mint && post.OwnerAccount != nil && post.OwnerAccount.Equals(owner) {
|
||||||
|
// post.ParseAccount()
|
||||||
|
postBalance = &post
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if preBalance == nil && postBalance == nil {
|
||||||
|
return 0, fmt.Errorf("account not found")
|
||||||
|
}
|
||||||
|
if preBalance != nil && postBalance != nil && preBalance.AccountIndex != postBalance.AccountIndex {
|
||||||
|
return 0, fmt.Errorf("ata index not match")
|
||||||
|
}
|
||||||
|
if postBalance == nil {
|
||||||
|
return preBalance.AccountIndex, nil
|
||||||
|
}
|
||||||
|
return postBalance.AccountIndex, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAtaByOwner(result *RawTx, owner solana.PublicKey, mint solana.PublicKey) (*TokenBalance, error) {
|
||||||
|
var preBalance *TokenBalance
|
||||||
|
for _, pre := range result.Meta.PreTokenBalances {
|
||||||
|
if pre.MintAccount == mint && pre.OwnerAccount != nil && pre.OwnerAccount.Equals(owner) {
|
||||||
|
preBalance = &pre
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var postBalance *TokenBalance
|
||||||
|
|
||||||
|
for _, post := range result.Meta.PostTokenBalances {
|
||||||
|
if post.MintAccount == mint && post.OwnerAccount != nil && post.OwnerAccount.Equals(owner) {
|
||||||
|
// post.ParseAccount()
|
||||||
|
postBalance = &post
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if preBalance == nil && postBalance == nil {
|
||||||
|
return nil, fmt.Errorf("account not found")
|
||||||
|
}
|
||||||
|
if preBalance != nil && postBalance != nil && preBalance.AccountIndex != postBalance.AccountIndex {
|
||||||
|
return nil, fmt.Errorf("ata index not match")
|
||||||
|
}
|
||||||
|
if postBalance == nil {
|
||||||
|
preBalance.ParseAccount()
|
||||||
|
return &TokenBalance{
|
||||||
|
AccountIndex: preBalance.AccountIndex,
|
||||||
|
MintAccount: preBalance.MintAccount,
|
||||||
|
OwnerAccount: preBalance.OwnerAccount,
|
||||||
|
ProgramIDAccount: preBalance.ProgramIDAccount,
|
||||||
|
Mint: preBalance.Mint,
|
||||||
|
Owner: preBalance.Owner,
|
||||||
|
ProgramID: preBalance.ProgramID,
|
||||||
|
UITokenAmount: UITokenAmount{
|
||||||
|
Amount: "0",
|
||||||
|
Decimals: preBalance.UITokenAmount.Decimals,
|
||||||
|
UIAmount: 0,
|
||||||
|
UIAmountString: "0",
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return postBalance, nil
|
||||||
|
}
|
||||||
|
|
||||||
func getTokenBalanceAfterTx(result *RawTx, accountIndex int) (*TokenBalance, error) {
|
func getTokenBalanceAfterTx(result *RawTx, accountIndex int) (*TokenBalance, error) {
|
||||||
var preBalance *TokenBalance
|
var preBalance *TokenBalance
|
||||||
for _, pre := range result.Meta.PreTokenBalances {
|
for _, pre := range result.Meta.PreTokenBalances {
|
||||||
@@ -581,8 +725,12 @@ func tokenBalanceChange(result *RawTx, accountIndex int, tokenProgram, mint sola
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var err error
|
||||||
if ataIndex == 0 {
|
if ataIndex == 0 {
|
||||||
return decimal.Zero, ataIndex
|
ataIndex, err = getAtaIdxByOwner(result, result.accountList[accountIndex], mint)
|
||||||
|
if err != nil {
|
||||||
|
return decimal.Zero, ataIndex
|
||||||
|
}
|
||||||
}
|
}
|
||||||
before := decimal.Zero
|
before := decimal.Zero
|
||||||
after := decimal.Zero
|
after := decimal.Zero
|
||||||
@@ -625,10 +773,13 @@ func GetTokenBalanceAfterTx(result *RawTx, accountIndex int, tokenProgram, mint
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var x *TokenBalance
|
||||||
|
var err error
|
||||||
if ataIndex == 0 {
|
if ataIndex == 0 {
|
||||||
return decimal.Zero
|
x, err = getAtaByOwner(result, result.accountList[accountIndex], mint)
|
||||||
|
} else {
|
||||||
|
x, err = getTokenBalanceAfterTx(result, ataIndex)
|
||||||
}
|
}
|
||||||
x, err := getTokenBalanceAfterTx(result, ataIndex)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return decimal.Zero
|
return decimal.Zero
|
||||||
}
|
}
|
||||||
@@ -716,17 +867,14 @@ 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
|
||||||
|
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 {
|
||||||
|
|||||||
375
raydiumclmm.go
Normal file
375
raydiumclmm.go
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
package pump_parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gagliardetto/solana-go"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func raydiumClmmParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if !tx.rawTx.accountList[instruction.ProgramIDIndex].Equals(raydiumClmmProgramID) {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("raydiumClmm instruction not found, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
decode := instruction.Data
|
||||||
|
if len(decode) < 8 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("raydiumClmm program instruction data too short, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
discriminator := *(*[8]byte)(decode[:8])
|
||||||
|
|
||||||
|
switch discriminator {
|
||||||
|
case raydiumClmmCreatePoolDiscriminator:
|
||||||
|
return raydiumClmmCreatePoolParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case raydiumClmmIncreaseLiquidityDiscriminator, raydiumClmmIncreaseLiquidityV2Discriminator, raydiumClmmOpenPositionDiscriminator, raydiumClmmOpenPositionV2Discriminator, raydiumClmmOpenPositionWithToken22NftDiscriminator:
|
||||||
|
return raydiumClmmAddLiquidityParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case raydiumClmmDecreaseLiquidityDiscriminator, raydiumClmmDecreaseLiquidityV2Discriminator:
|
||||||
|
return raydiumClmmDecreaseLiquidityParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case raydiumClmmCollectFundFeeDiscriminator, raydiumClmmCollectProtocolFeeDiscriminator:
|
||||||
|
return raydiumClmmCollectFeeParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case raydiumClmmSwapDiscriminator, raydiumClmmSwapV2Discriminator:
|
||||||
|
return raydiumClmmSwapParser(tx, instruction, innerInstructions, offset)
|
||||||
|
default:
|
||||||
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func raydiumClmmCreatePoolParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if len(instruction.Accounts) < 13 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for raydiumClmm create pool instruction, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
|
||||||
|
pool := tx.rawTx.accountList[instruction.Accounts[2]]
|
||||||
|
creator := tx.rawTx.accountList[instruction.Accounts[0]]
|
||||||
|
|
||||||
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[5])
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token0 vault balance after tx: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[6])
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token1 vault balance after tx: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount)
|
||||||
|
quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount)
|
||||||
|
offset[1] += 9
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramRaydiumCLMM,
|
||||||
|
Event: "create",
|
||||||
|
Pool: pool,
|
||||||
|
BaseMint: baseTokenBalance.MintAccount,
|
||||||
|
QuoteMint: quoteTokenBalance.MintAccount,
|
||||||
|
BaseTokenProgram: baseTokenBalance.ProgramIDAccount,
|
||||||
|
QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount,
|
||||||
|
Creator: creator,
|
||||||
|
BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals),
|
||||||
|
QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals),
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
User: tx.rawTx.accountList[instruction.Accounts[0]],
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func raydiumClmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
discriminator := *(*[8]byte)(instruction.Data[:8])
|
||||||
|
var (
|
||||||
|
accountMin int
|
||||||
|
market solana.PublicKey
|
||||||
|
//token0 solana.PublicKey
|
||||||
|
//token1 solana.PublicKey
|
||||||
|
lpToken solana.PublicKey
|
||||||
|
vault0 int
|
||||||
|
vault1 int
|
||||||
|
)
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
|
||||||
|
switch discriminator {
|
||||||
|
case raydiumClmmIncreaseLiquidityDiscriminator:
|
||||||
|
accountMin = 12
|
||||||
|
market = tx.rawTx.accountList[instruction.Accounts[2]]
|
||||||
|
vault0 = instruction.Accounts[9]
|
||||||
|
vault1 = instruction.Accounts[10]
|
||||||
|
case raydiumClmmIncreaseLiquidityV2Discriminator:
|
||||||
|
accountMin = 15
|
||||||
|
market = tx.rawTx.accountList[instruction.Accounts[2]]
|
||||||
|
vault0 = instruction.Accounts[9]
|
||||||
|
vault1 = instruction.Accounts[10]
|
||||||
|
//token0 = tx.rawTx.accountList[instruction.Accounts[13]]
|
||||||
|
//token1 = tx.rawTx.accountList[instruction.Accounts[14]]
|
||||||
|
case raydiumClmmOpenPositionDiscriminator:
|
||||||
|
accountMin = 19
|
||||||
|
market = tx.rawTx.accountList[instruction.Accounts[5]]
|
||||||
|
vault0 = instruction.Accounts[12]
|
||||||
|
vault1 = instruction.Accounts[13]
|
||||||
|
lpToken = tx.rawTx.accountList[instruction.Accounts[2]]
|
||||||
|
case raydiumClmmOpenPositionV2Discriminator:
|
||||||
|
accountMin = 22
|
||||||
|
market = tx.rawTx.accountList[instruction.Accounts[5]]
|
||||||
|
vault0 = instruction.Accounts[12]
|
||||||
|
vault1 = instruction.Accounts[13]
|
||||||
|
lpToken = tx.rawTx.accountList[instruction.Accounts[2]]
|
||||||
|
//token0 = tx.rawTx.accountList[instruction.Accounts[20]]
|
||||||
|
//token1 = tx.rawTx.accountList[instruction.Accounts[21]]
|
||||||
|
case raydiumClmmOpenPositionWithToken22NftDiscriminator:
|
||||||
|
accountMin = 20
|
||||||
|
market = tx.rawTx.accountList[instruction.Accounts[4]]
|
||||||
|
vault0 = instruction.Accounts[11]
|
||||||
|
vault1 = instruction.Accounts[12]
|
||||||
|
lpToken = tx.rawTx.accountList[instruction.Accounts[2]]
|
||||||
|
//token0 = tx.rawTx.accountList[instruction.Accounts[18]]
|
||||||
|
//token1 = tx.rawTx.accountList[instruction.Accounts[19]]
|
||||||
|
default:
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("invalid discriminator")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(instruction.Accounts) < accountMin {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for raydiumClmm add liquidity instruction, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, vault0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token0 vault balance after tx: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, vault1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token1 vault balance after tx: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount)
|
||||||
|
quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount)
|
||||||
|
|
||||||
|
offset[1] += 2
|
||||||
|
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramRaydiumCLMM,
|
||||||
|
Event: "add_liquidity",
|
||||||
|
Pool: market,
|
||||||
|
BaseMint: baseTokenBalance.MintAccount,
|
||||||
|
QuoteMint: quoteTokenBalance.MintAccount,
|
||||||
|
BaseTokenProgram: baseTokenBalance.ProgramIDAccount,
|
||||||
|
QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount,
|
||||||
|
LpMint: lpToken,
|
||||||
|
BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals),
|
||||||
|
QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals),
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
User: tx.rawTx.accountList[instruction.Accounts[0]],
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func raydiumClmmDecreaseLiquidityParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
discriminator := *(*[8]byte)(instruction.Data[:8])
|
||||||
|
var (
|
||||||
|
accountMin int
|
||||||
|
market solana.PublicKey
|
||||||
|
vault0 int
|
||||||
|
vault1 int
|
||||||
|
)
|
||||||
|
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
if discriminator == raydiumClmmDecreaseLiquidityDiscriminator {
|
||||||
|
accountMin = 14
|
||||||
|
} else if discriminator == raydiumClmmDecreaseLiquidityV2Discriminator {
|
||||||
|
accountMin = 16
|
||||||
|
}
|
||||||
|
if len(instruction.Accounts) < accountMin {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for decrease liquidity instruction")
|
||||||
|
}
|
||||||
|
market = tx.rawTx.accountList[instruction.Accounts[3]]
|
||||||
|
vault0 = instruction.Accounts[5]
|
||||||
|
vault1 = instruction.Accounts[6]
|
||||||
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, vault0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token0 vault balance after tx: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, vault1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token1 vault balance after tx: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount)
|
||||||
|
quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount)
|
||||||
|
|
||||||
|
offset[1] += 2
|
||||||
|
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramRaydiumCLMM,
|
||||||
|
Event: "remove_liquidity",
|
||||||
|
Pool: market,
|
||||||
|
BaseMint: baseTokenBalance.MintAccount,
|
||||||
|
QuoteMint: quoteTokenBalance.MintAccount,
|
||||||
|
BaseTokenProgram: baseTokenBalance.ProgramIDAccount,
|
||||||
|
QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount,
|
||||||
|
BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals),
|
||||||
|
QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals),
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
User: tx.rawTx.accountList[instruction.Accounts[0]],
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func raydiumClmmCollectFeeParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if len(instruction.Accounts) < 11 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for CollectFeeParser instruction")
|
||||||
|
}
|
||||||
|
pool := tx.rawTx.accountList[instruction.Accounts[1]]
|
||||||
|
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
vault0 := instruction.Accounts[3]
|
||||||
|
vault1 := instruction.Accounts[4]
|
||||||
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, vault0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token0 vault balance after tx: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, vault1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token1 vault balance after tx: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount)
|
||||||
|
quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount)
|
||||||
|
|
||||||
|
offset[1] += 2
|
||||||
|
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramRaydiumCLMM,
|
||||||
|
Event: "remove_liquidity",
|
||||||
|
Pool: pool,
|
||||||
|
BaseMint: baseTokenBalance.MintAccount,
|
||||||
|
QuoteMint: quoteTokenBalance.MintAccount,
|
||||||
|
BaseTokenProgram: baseTokenBalance.ProgramIDAccount,
|
||||||
|
QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount,
|
||||||
|
BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals),
|
||||||
|
QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals),
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
User: tx.rawTx.accountList[instruction.Accounts[0]],
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func raydiumClmmSwapParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
discriminator := *(*[8]byte)(instruction.Data[:8])
|
||||||
|
var (
|
||||||
|
pool solana.PublicKey
|
||||||
|
|
||||||
|
accountMin int
|
||||||
|
tokenInVault int
|
||||||
|
tokenOutVault int
|
||||||
|
userTokenInAccount int
|
||||||
|
userTokenOutAccount int
|
||||||
|
)
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
if discriminator == raydiumClmmSwapDiscriminator {
|
||||||
|
accountMin = 9
|
||||||
|
pool = tx.rawTx.accountList[instruction.Accounts[2]]
|
||||||
|
userTokenInAccount = instruction.Accounts[3]
|
||||||
|
userTokenOutAccount = instruction.Accounts[4]
|
||||||
|
tokenInVault = instruction.Accounts[5]
|
||||||
|
tokenOutVault = instruction.Accounts[6]
|
||||||
|
} else if discriminator == raydiumClmmSwapV2Discriminator {
|
||||||
|
accountMin = 13
|
||||||
|
pool = tx.rawTx.accountList[instruction.Accounts[2]]
|
||||||
|
userTokenInAccount = instruction.Accounts[3]
|
||||||
|
userTokenOutAccount = instruction.Accounts[4]
|
||||||
|
tokenInVault = instruction.Accounts[5]
|
||||||
|
tokenOutVault = instruction.Accounts[6]
|
||||||
|
}
|
||||||
|
if len(instruction.Accounts) < accountMin {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for swap instruction")
|
||||||
|
}
|
||||||
|
|
||||||
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, tokenInVault)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get tokenIn vault balance after tx: %w", err)
|
||||||
|
}
|
||||||
|
quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, tokenOutVault)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get tokenOut vault balance after tx: %w", err)
|
||||||
|
}
|
||||||
|
baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount)
|
||||||
|
quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount)
|
||||||
|
baseTokenProgram := baseTokenBalance.ProgramIDAccount
|
||||||
|
quoteTokenProgram := quoteTokenBalance.ProgramIDAccount
|
||||||
|
baseMint := baseTokenBalance.MintAccount
|
||||||
|
quoteMint := quoteTokenBalance.MintAccount
|
||||||
|
baseMintDecimals := uint8(baseTokenBalance.UITokenAmount.Decimals)
|
||||||
|
quoteMintDecimals := uint8(quoteTokenBalance.UITokenAmount.Decimals)
|
||||||
|
|
||||||
|
userBase := getAccountBalanceAfterTx(tx.rawTx, userTokenInAccount)
|
||||||
|
userQuote := getAccountBalanceAfterTx(tx.rawTx, userTokenOutAccount)
|
||||||
|
|
||||||
|
inners, err := getInnerInstructions(innerInstructions, offset[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get inner instructions: %w", err)
|
||||||
|
}
|
||||||
|
if len(inners) < 2 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("not enough inner instructions for swap instruction")
|
||||||
|
}
|
||||||
|
baseVaultAccount := tx.rawTx.accountList[tokenInVault]
|
||||||
|
quoteVaultAccount := tx.rawTx.accountList[tokenOutVault]
|
||||||
|
userBaseAccount := tx.rawTx.accountList[userTokenInAccount]
|
||||||
|
userQuoteAccount := tx.rawTx.accountList[userTokenOutAccount]
|
||||||
|
var baseAmount, quoteAmount decimal.Decimal
|
||||||
|
var baseFound, quoteFound bool
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
inner := inners[i]
|
||||||
|
from, to, amount, err := parseTokenTransfer(tx.rawTx, inner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to parse token transfer: %w", err)
|
||||||
|
}
|
||||||
|
if from.Equals(userBaseAccount) && to.Equals(baseVaultAccount) && !baseFound {
|
||||||
|
baseAmount = decimal.NewFromUint64(amount)
|
||||||
|
baseFound = true
|
||||||
|
} else if from.Equals(quoteVaultAccount) && to.Equals(userQuoteAccount) && !quoteFound {
|
||||||
|
quoteAmount = decimal.NewFromUint64(amount)
|
||||||
|
quoteFound = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !baseFound || !quoteFound {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to find token transfer in inner instructions")
|
||||||
|
}
|
||||||
|
|
||||||
|
offset[1] += 2
|
||||||
|
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramRaydiumCLMM,
|
||||||
|
Event: "sell",
|
||||||
|
Pool: pool,
|
||||||
|
BaseMint: baseMint,
|
||||||
|
QuoteMint: quoteMint,
|
||||||
|
BaseTokenProgram: baseTokenProgram,
|
||||||
|
QuoteTokenProgram: quoteTokenProgram,
|
||||||
|
BaseMintDecimals: baseMintDecimals,
|
||||||
|
QuoteMintDecimals: quoteMintDecimals,
|
||||||
|
User: tx.rawTx.accountList[instruction.Accounts[0]],
|
||||||
|
BaseAmount: baseAmount,
|
||||||
|
QuoteAmount: quoteAmount,
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
UserBaseBalance: userBase,
|
||||||
|
UserQuoteBalance: userQuote,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, offset, nil
|
||||||
|
|
||||||
|
}
|
||||||
408
raydiumcpmm.go
Normal file
408
raydiumcpmm.go
Normal file
@@ -0,0 +1,408 @@
|
|||||||
|
package pump_parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func raydiumCPmmParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if !tx.rawTx.accountList[instruction.ProgramIDIndex].Equals(raydiumCPmmProgramID) {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("raydiumCPmm instruction not found, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
decode := instruction.Data
|
||||||
|
if len(decode) < 8 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("raydiumCPmm program instruction data too short, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
discriminator := *(*[8]byte)(decode[:8])
|
||||||
|
|
||||||
|
switch discriminator {
|
||||||
|
case raydiumCPmmInitializeDiscriminator, raydiumCPmmInitializeWithPermissionDiscriminator:
|
||||||
|
return raydiumCPmmCreatePoolParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case raydiumCPmmDepositDiscriminator:
|
||||||
|
return raydiumCPmmDepositParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case raydiumCPmmWithdrawDiscriminator:
|
||||||
|
return raydiumCPmmWithdrawParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case raydiumCPmmCollectProtocolFeeDiscriminator, raydiumCPmmCollectFundFeeDiscriminator:
|
||||||
|
return raydiumCPmmCollectParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case raydiumCPmmSwapBaseInputDiscriminator, raydiumCPmmSwapBaseOutputDiscriminator:
|
||||||
|
return raydiumCPmmSwapParser(tx, instruction, innerInstructions, offset)
|
||||||
|
default:
|
||||||
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func raydiumCPmmCreatePoolParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if len(instruction.Accounts) < 20 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for initialize instruction")
|
||||||
|
}
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
|
||||||
|
pool := tx.rawTx.accountList[instruction.Accounts[3]]
|
||||||
|
lpMint := tx.rawTx.accountList[instruction.Accounts[6]]
|
||||||
|
creator := tx.rawTx.accountList[instruction.Accounts[0]]
|
||||||
|
vault0 := instruction.Accounts[10]
|
||||||
|
vault1 := instruction.Accounts[11]
|
||||||
|
if bytes.Equal(instruction.Data[:8], raydiumCPmmInitializeWithPermissionDiscriminator[:]) {
|
||||||
|
pool = tx.rawTx.accountList[instruction.Accounts[4]]
|
||||||
|
lpMint = tx.rawTx.accountList[instruction.Accounts[7]]
|
||||||
|
creator = tx.rawTx.accountList[instruction.Accounts[1]]
|
||||||
|
vault0 = instruction.Accounts[11]
|
||||||
|
vault1 = instruction.Accounts[12]
|
||||||
|
}
|
||||||
|
|
||||||
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, vault0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token0 vault balance after tx: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, vault1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token1 vault balance after tx: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount)
|
||||||
|
quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount)
|
||||||
|
offset[1] += 13
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramRaydiumCPMM,
|
||||||
|
Event: "create",
|
||||||
|
Pool: pool,
|
||||||
|
BaseMint: baseTokenBalance.MintAccount,
|
||||||
|
QuoteMint: quoteTokenBalance.MintAccount,
|
||||||
|
BaseTokenProgram: baseTokenBalance.ProgramIDAccount,
|
||||||
|
QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount,
|
||||||
|
Creator: creator,
|
||||||
|
BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals),
|
||||||
|
QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals),
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
LpMint: lpMint,
|
||||||
|
User: tx.rawTx.accountList[instruction.Accounts[0]],
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, increaseOffset(offset), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func raydiumCPmmDepositParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if len(instruction.Accounts) < 13 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for deposit instruction")
|
||||||
|
}
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
market := tx.rawTx.accountList[instruction.Accounts[2]]
|
||||||
|
token0User := tx.rawTx.accountList[instruction.Accounts[4]]
|
||||||
|
token1User := tx.rawTx.accountList[instruction.Accounts[5]]
|
||||||
|
|
||||||
|
token0Vault := tx.rawTx.accountList[instruction.Accounts[6]]
|
||||||
|
token1Vault := tx.rawTx.accountList[instruction.Accounts[7]]
|
||||||
|
|
||||||
|
token0 := tx.rawTx.accountList[instruction.Accounts[10]]
|
||||||
|
token1 := tx.rawTx.accountList[instruction.Accounts[11]]
|
||||||
|
lpTokenMint := tx.rawTx.accountList[instruction.Accounts[12]]
|
||||||
|
|
||||||
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[6])
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token0 vault balance after tx: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[7])
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token1 vault balance after tx: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount)
|
||||||
|
quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount)
|
||||||
|
|
||||||
|
inners, err := getInnerInstructions(innerInstructions, offset[1])
|
||||||
|
|
||||||
|
var baseFound, quoteFound bool
|
||||||
|
var baseAmount, quoteAmount decimal.Decimal
|
||||||
|
for _, inner := range inners {
|
||||||
|
from, to, amount, err := parseTokenTransfer(tx.rawTx, inner)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if from.Equals(token0User) && to.Equals(token0Vault) && !baseFound {
|
||||||
|
baseFound = true
|
||||||
|
baseAmount = decimal.NewFromUint64(amount)
|
||||||
|
} else if from.Equals(token1User) && to.Equals(token1Vault) && !quoteFound {
|
||||||
|
quoteFound = true
|
||||||
|
quoteAmount = decimal.NewFromUint64(amount)
|
||||||
|
}
|
||||||
|
if baseFound && quoteFound {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !baseFound || !quoteFound {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to find token transfer in inner instructions")
|
||||||
|
}
|
||||||
|
offset[1] += 3
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramRaydiumCPMM,
|
||||||
|
Event: "add_liquidity",
|
||||||
|
Pool: market,
|
||||||
|
BaseMint: token0,
|
||||||
|
QuoteMint: token1,
|
||||||
|
BaseTokenProgram: baseTokenBalance.ProgramIDAccount,
|
||||||
|
QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount,
|
||||||
|
BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals),
|
||||||
|
QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals),
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
BaseAmount: baseAmount,
|
||||||
|
QuoteAmount: quoteAmount,
|
||||||
|
LpMint: lpTokenMint,
|
||||||
|
User: tx.rawTx.accountList[instruction.Accounts[0]],
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func raydiumCPmmWithdrawParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if len(instruction.Accounts) < 13 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for deposit instruction")
|
||||||
|
}
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
market := tx.rawTx.accountList[instruction.Accounts[2]]
|
||||||
|
token0User := tx.rawTx.accountList[instruction.Accounts[4]]
|
||||||
|
token1User := tx.rawTx.accountList[instruction.Accounts[5]]
|
||||||
|
|
||||||
|
token0Vault := tx.rawTx.accountList[instruction.Accounts[6]]
|
||||||
|
token1Vault := tx.rawTx.accountList[instruction.Accounts[7]]
|
||||||
|
|
||||||
|
token0 := tx.rawTx.accountList[instruction.Accounts[10]]
|
||||||
|
token1 := tx.rawTx.accountList[instruction.Accounts[11]]
|
||||||
|
lpTokenMint := tx.rawTx.accountList[instruction.Accounts[12]]
|
||||||
|
|
||||||
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[6])
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token0 vault balance after tx: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[7])
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token1 vault balance after tx: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount)
|
||||||
|
quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount)
|
||||||
|
|
||||||
|
inners, err := getInnerInstructions(innerInstructions, offset[1])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get inner instructions: %v", err)
|
||||||
|
}
|
||||||
|
var baseFound, quoteFound bool
|
||||||
|
var baseAmount, quoteAmount decimal.Decimal
|
||||||
|
for _, inner := range inners {
|
||||||
|
from, to, amount, err := parseTokenTransfer(tx.rawTx, inner)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if to.Equals(token0User) && from.Equals(token0Vault) && !baseFound {
|
||||||
|
baseFound = true
|
||||||
|
baseAmount = decimal.NewFromUint64(amount)
|
||||||
|
} else if to.Equals(token1User) && from.Equals(token1Vault) && !quoteFound {
|
||||||
|
quoteFound = true
|
||||||
|
quoteAmount = decimal.NewFromUint64(amount)
|
||||||
|
}
|
||||||
|
if baseFound && quoteFound {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !baseFound || !quoteFound {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to find token transfer in inner instructions")
|
||||||
|
}
|
||||||
|
offset[1] += 3
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramRaydiumCPMM,
|
||||||
|
Event: "remove_liquidity",
|
||||||
|
Pool: market,
|
||||||
|
BaseMint: token0,
|
||||||
|
QuoteMint: token1,
|
||||||
|
BaseTokenProgram: baseTokenBalance.ProgramIDAccount,
|
||||||
|
QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount,
|
||||||
|
BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals),
|
||||||
|
QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals),
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
BaseAmount: baseAmount,
|
||||||
|
QuoteAmount: quoteAmount,
|
||||||
|
LpMint: lpTokenMint,
|
||||||
|
User: tx.rawTx.accountList[instruction.Accounts[0]],
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func raydiumCPmmCollectParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if len(instruction.Accounts) < 12 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for deposit instruction")
|
||||||
|
}
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
market := tx.rawTx.accountList[instruction.Accounts[2]]
|
||||||
|
token0User := tx.rawTx.accountList[instruction.Accounts[8]]
|
||||||
|
token1User := tx.rawTx.accountList[instruction.Accounts[9]]
|
||||||
|
|
||||||
|
token0Vault := tx.rawTx.accountList[instruction.Accounts[4]]
|
||||||
|
token1Vault := tx.rawTx.accountList[instruction.Accounts[5]]
|
||||||
|
|
||||||
|
token0 := tx.rawTx.accountList[instruction.Accounts[6]]
|
||||||
|
token1 := tx.rawTx.accountList[instruction.Accounts[7]]
|
||||||
|
|
||||||
|
inners, err := getInnerInstructions(innerInstructions, offset[1])
|
||||||
|
|
||||||
|
var baseFound, quoteFound bool
|
||||||
|
var baseAmount, quoteAmount decimal.Decimal
|
||||||
|
for _, inner := range inners {
|
||||||
|
from, to, amount, err := parseTokenTransfer(tx.rawTx, inner)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if to.Equals(token0User) && from.Equals(token0Vault) && !baseFound {
|
||||||
|
baseFound = true
|
||||||
|
baseAmount = decimal.NewFromUint64(amount)
|
||||||
|
} else if to.Equals(token1User) && from.Equals(token1Vault) && !quoteFound {
|
||||||
|
quoteFound = true
|
||||||
|
quoteAmount = decimal.NewFromUint64(amount)
|
||||||
|
}
|
||||||
|
if baseFound && quoteFound {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !baseFound && !quoteFound {
|
||||||
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
||||||
|
}
|
||||||
|
|
||||||
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[4])
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token0 vault balance after tx: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[5])
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token1 vault balance after tx: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount)
|
||||||
|
quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount)
|
||||||
|
|
||||||
|
event := "remove_liquidity"
|
||||||
|
if !baseFound || !quoteFound {
|
||||||
|
offset[1] += 1
|
||||||
|
event = "remove_liquidity_one_side"
|
||||||
|
} else {
|
||||||
|
offset[1] += 2
|
||||||
|
}
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramRaydiumCPMM,
|
||||||
|
Event: event,
|
||||||
|
Pool: market,
|
||||||
|
BaseMint: token0,
|
||||||
|
QuoteMint: token1,
|
||||||
|
BaseTokenProgram: baseTokenBalance.ProgramIDAccount,
|
||||||
|
QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount,
|
||||||
|
BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals),
|
||||||
|
QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals),
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
BaseAmount: baseAmount,
|
||||||
|
QuoteAmount: quoteAmount,
|
||||||
|
User: tx.rawTx.accountList[instruction.Accounts[0]],
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func raydiumCPmmSwapParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if len(instruction.Accounts) < 13 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for SwapBaseInputParser instruction")
|
||||||
|
}
|
||||||
|
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
market := tx.rawTx.accountList[instruction.Accounts[3]]
|
||||||
|
// Get token accounts from instruction
|
||||||
|
tokenIn := tx.rawTx.accountList[instruction.Accounts[4]]
|
||||||
|
tokenOut := tx.rawTx.accountList[instruction.Accounts[5]]
|
||||||
|
user := tx.rawTx.accountList[instruction.Accounts[0]]
|
||||||
|
|
||||||
|
user0 := instruction.Accounts[4]
|
||||||
|
user1 := instruction.Accounts[5]
|
||||||
|
|
||||||
|
inputVault := tx.rawTx.accountList[instruction.Accounts[6]]
|
||||||
|
outputVault := tx.rawTx.accountList[instruction.Accounts[7]]
|
||||||
|
|
||||||
|
vault0 := instruction.Accounts[6]
|
||||||
|
vault1 := instruction.Accounts[7]
|
||||||
|
|
||||||
|
inputTokenMint := tx.rawTx.accountList[instruction.Accounts[10]]
|
||||||
|
outputTokenMint := tx.rawTx.accountList[instruction.Accounts[11]]
|
||||||
|
|
||||||
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, vault0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get input amount: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, vault1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get output amount: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount)
|
||||||
|
quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount)
|
||||||
|
userBase := getAccountBalanceAfterTx(tx.rawTx, user0)
|
||||||
|
userQuote := getAccountBalanceAfterTx(tx.rawTx, user1)
|
||||||
|
|
||||||
|
inners, err := getInnerInstructions(innerInstructions, offset[1])
|
||||||
|
|
||||||
|
var baseFound, quoteFound bool
|
||||||
|
var baseAmount, quoteAmount decimal.Decimal
|
||||||
|
for _, inner := range inners {
|
||||||
|
from, to, amount, err := parseTokenTransfer(tx.rawTx, inner)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if from.Equals(tokenIn) && to.Equals(inputVault) && !baseFound {
|
||||||
|
baseFound = true
|
||||||
|
baseAmount = decimal.NewFromUint64(amount)
|
||||||
|
} else if from.Equals(outputVault) && to.Equals(tokenOut) && !quoteFound {
|
||||||
|
quoteFound = true
|
||||||
|
quoteAmount = decimal.NewFromUint64(amount)
|
||||||
|
}
|
||||||
|
if baseFound && quoteFound {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !baseFound || !quoteFound {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to find token transfer in inner instructions")
|
||||||
|
}
|
||||||
|
offset[1] += 2
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramRaydiumCPMM,
|
||||||
|
Event: "sell",
|
||||||
|
Pool: market,
|
||||||
|
BaseMint: inputTokenMint,
|
||||||
|
QuoteMint: outputTokenMint,
|
||||||
|
BaseTokenProgram: baseTokenBalance.ProgramIDAccount,
|
||||||
|
QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount,
|
||||||
|
BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals),
|
||||||
|
QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals),
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
BaseAmount: baseAmount,
|
||||||
|
QuoteAmount: quoteAmount,
|
||||||
|
User: user,
|
||||||
|
UserBaseBalance: userBase,
|
||||||
|
UserQuoteBalance: userQuote,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, offset, nil
|
||||||
|
}
|
||||||
470
raydiumlaunchlab.go
Normal file
470
raydiumlaunchlab.go
Normal file
@@ -0,0 +1,470 @@
|
|||||||
|
package pump_parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
agbinary "github.com/gagliardetto/binary"
|
||||||
|
"github.com/gagliardetto/solana-go"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func raydiumLaunchLabParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if !tx.rawTx.accountList[instruction.ProgramIDIndex].Equals(raydiumLaunchLabProgramID) {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("raydiumLaunchLab instruction not found, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
decode := instruction.Data
|
||||||
|
if len(decode) < 8 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("raydiumLaunchLab program instruction data too short, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
discriminator := *(*[8]byte)(decode[:8])
|
||||||
|
|
||||||
|
switch discriminator {
|
||||||
|
case raydiumLaunchLabInitializeWithToken2022PoolDiscriminator, raydiumLaunchLabInitializeV2PoolDiscriminator:
|
||||||
|
return raydiumLaunchLabInitializeParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case raydiumLaunchLabMigrateToAmmDiscriminator:
|
||||||
|
return raydiumLaunchLabMigrateToAmmParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case raydiumLaunchLabMigrateToCpmmDiscriminator:
|
||||||
|
return raydiumLaunchLabMigrateToCpmmParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case raydiumLaunchLabSellExactInDiscriminator,
|
||||||
|
raydiumLaunchLabSellExactOutDiscriminator,
|
||||||
|
raydiumLaunchLabBuyExactInDiscriminator,
|
||||||
|
raydiumLaunchLabBuyExactOutDiscriminator:
|
||||||
|
return raydiumLaunchLabSwapParser(tx, instruction, innerInstructions, offset)
|
||||||
|
default:
|
||||||
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type VestingParam struct {
|
||||||
|
TotalLockedAmount uint64
|
||||||
|
CliffPeriod uint64
|
||||||
|
UnlockPeriod uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type CurveParamKind uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
CurveParamConstant CurveParamKind = 0
|
||||||
|
CurveParamFixed CurveParamKind = 1
|
||||||
|
CurveParamLinear CurveParamKind = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type CurveParam struct {
|
||||||
|
// rust enum ConstantCurve/FixedCurve/LinearCurve
|
||||||
|
Kind CurveParamKind
|
||||||
|
Constant *ConstantCurve
|
||||||
|
Fixed *FixedCurve
|
||||||
|
Linear *LinearCurve
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CurveParam) TotalSupply() uint64 {
|
||||||
|
switch c.Kind {
|
||||||
|
case CurveParamConstant:
|
||||||
|
return c.Constant.TotalSupply
|
||||||
|
case CurveParamFixed:
|
||||||
|
return c.Fixed.TotalSupply
|
||||||
|
case CurveParamLinear:
|
||||||
|
return c.Linear.TotalSupply
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalWithDecoder 让 agbinary/borsh 解码时走自定义逻辑
|
||||||
|
func (c *CurveParam) UnmarshalWithDecoder(dec *agbinary.Decoder) error {
|
||||||
|
var tag uint8
|
||||||
|
if err := dec.Decode(&tag); err != nil {
|
||||||
|
return fmt.Errorf("decode CurveParam tag: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Kind = CurveParamKind(tag)
|
||||||
|
c.Constant, c.Fixed, c.Linear = nil, nil, nil
|
||||||
|
|
||||||
|
switch c.Kind {
|
||||||
|
case CurveParamConstant:
|
||||||
|
var v ConstantCurve
|
||||||
|
if err := dec.Decode(&v); err != nil {
|
||||||
|
return fmt.Errorf("decode ConstantCurve: %w", err)
|
||||||
|
}
|
||||||
|
c.Constant = &v
|
||||||
|
case CurveParamFixed:
|
||||||
|
var v FixedCurve
|
||||||
|
if err := dec.Decode(&v); err != nil {
|
||||||
|
return fmt.Errorf("decode FixedCurve: %w", err)
|
||||||
|
}
|
||||||
|
c.Fixed = &v
|
||||||
|
case CurveParamLinear:
|
||||||
|
var v LinearCurve
|
||||||
|
if err := dec.Decode(&v); err != nil {
|
||||||
|
return fmt.Errorf("decode LinearCurve: %w", err)
|
||||||
|
}
|
||||||
|
c.Linear = &v
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown CurveParam tag: %d", tag)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConstantCurve struct {
|
||||||
|
TotalSupply uint64
|
||||||
|
TotalBaseSell uint64
|
||||||
|
TotalQuoteFundRaising uint64
|
||||||
|
MigrateType uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
type FixedCurve struct {
|
||||||
|
TotalSupply uint64
|
||||||
|
TotalQuoteFundRaising uint64
|
||||||
|
MigrateType uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
type LinearCurve struct {
|
||||||
|
TotalSupply uint64
|
||||||
|
TotalQuoteFundRaising uint64
|
||||||
|
MigrateType uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseMintParam struct {
|
||||||
|
Decimals uint8
|
||||||
|
Name string
|
||||||
|
Symbol string
|
||||||
|
Uri string
|
||||||
|
}
|
||||||
|
type RaydiumLaunchLabCreateEvent struct {
|
||||||
|
Pool solana.PublicKey
|
||||||
|
Creator solana.PublicKey
|
||||||
|
Config solana.PublicKey
|
||||||
|
BaseMintParam BaseMintParam
|
||||||
|
CurveParam CurveParam
|
||||||
|
VestingParam VestingParam
|
||||||
|
ammFeeOn uint8 // 0 or 1, QuoteToken/BaseToken fee on amm swap
|
||||||
|
}
|
||||||
|
|
||||||
|
func raydiumLaunchLabInitializeParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if len(instruction.Accounts) < 15 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for initialize instruction")
|
||||||
|
}
|
||||||
|
user := tx.rawTx.accountList[instruction.Accounts[0]]
|
||||||
|
creator := tx.rawTx.accountList[instruction.Accounts[1]]
|
||||||
|
pool := tx.rawTx.accountList[instruction.Accounts[5]]
|
||||||
|
platformConfig := tx.rawTx.accountList[instruction.Accounts[3]]
|
||||||
|
baseMint := tx.rawTx.accountList[instruction.Accounts[6]]
|
||||||
|
quoteMint := tx.rawTx.accountList[instruction.Accounts[7]]
|
||||||
|
baseVaultIdx := instruction.Accounts[8]
|
||||||
|
quoteVaultIdx := instruction.Accounts[9]
|
||||||
|
var (
|
||||||
|
baseTokenProgram solana.PublicKey
|
||||||
|
quoteTokenProgram solana.PublicKey
|
||||||
|
)
|
||||||
|
if bytes.Equal(instruction.Data[:8], raydiumLaunchLabInitializeWithToken2022PoolDiscriminator[:]) {
|
||||||
|
baseTokenProgram = tx.rawTx.accountList[instruction.Accounts[10]]
|
||||||
|
quoteTokenProgram = tx.rawTx.accountList[instruction.Accounts[11]]
|
||||||
|
} else if bytes.Equal(instruction.Data[:8], raydiumLaunchLabInitializeV2PoolDiscriminator[:]) {
|
||||||
|
baseTokenProgram = tx.rawTx.accountList[instruction.Accounts[11]]
|
||||||
|
quoteTokenProgram = tx.rawTx.accountList[instruction.Accounts[12]]
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
var programName string
|
||||||
|
if platformConfig.Equals(bonkPlatformConfig) {
|
||||||
|
programName = SolProgramRaydiumLaunchLabBonk
|
||||||
|
} else {
|
||||||
|
programName = SolProgramRaydiumLaunchLab
|
||||||
|
}
|
||||||
|
|
||||||
|
baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount)
|
||||||
|
quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount)
|
||||||
|
baseDecimals := uint8(baseTokenBalance.UITokenAmount.Decimals)
|
||||||
|
var createEvent RaydiumLaunchLabCreateEvent
|
||||||
|
inners, err := getInnerInstructions(innerInstructions, offset[1])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get inner instructions: %v", err)
|
||||||
|
}
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
loadedEvent := false
|
||||||
|
var prefixLen uint = offset[1]
|
||||||
|
for innerIndex, innerInstruction := range inners {
|
||||||
|
if innerInstruction.ProgramIDIndex == instruction.ProgramIDIndex && len(innerInstruction.Data) >= 16 &&
|
||||||
|
bytes.Equal(innerInstruction.Data[:8], pumpEventDiscriminator[:]) &&
|
||||||
|
bytes.Equal(innerInstruction.Data[8:16], raydiumLaunchLabCreatePoolEvnet[:]) &&
|
||||||
|
len(innerInstruction.Accounts) == 1 {
|
||||||
|
if offset[1] == 0 {
|
||||||
|
offset[0] += 1
|
||||||
|
} else {
|
||||||
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
||||||
|
}
|
||||||
|
err := agbinary.NewBorshDecoder(innerInstruction.Data[16:]).Decode(&createEvent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to deserialize create event: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedEvent = true
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !loadedEvent {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get create event")
|
||||||
|
}
|
||||||
|
totalSupply := decimal.NewFromUint64(createEvent.CurveParam.TotalSupply()).Div(decimal.New(1, int32(baseDecimals)))
|
||||||
|
tx.Token[baseMint] = TokenMeta{
|
||||||
|
Mint: baseMint,
|
||||||
|
TokenProgram: baseTokenProgram,
|
||||||
|
Decimals: baseDecimals,
|
||||||
|
Name: createEvent.BaseMintParam.Name,
|
||||||
|
Symbol: createEvent.BaseMintParam.Symbol,
|
||||||
|
Url: createEvent.BaseMintParam.Uri,
|
||||||
|
TotalSupply: &totalSupply,
|
||||||
|
}
|
||||||
|
return []Swap{{
|
||||||
|
Program: programName,
|
||||||
|
Event: "create",
|
||||||
|
Pool: pool,
|
||||||
|
BaseMint: baseMint,
|
||||||
|
QuoteMint: quoteMint,
|
||||||
|
BaseTokenProgram: baseTokenProgram,
|
||||||
|
QuoteTokenProgram: quoteTokenProgram,
|
||||||
|
Creator: creator,
|
||||||
|
BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals),
|
||||||
|
QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals),
|
||||||
|
User: user,
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
}}, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func raydiumLaunchLabMigrateToCpmmParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if len(instruction.Accounts) < 27 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for migrate instruction")
|
||||||
|
}
|
||||||
|
var entryContract solana.PublicKey = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
platformConfig := tx.rawTx.accountList[instruction.Accounts[3]]
|
||||||
|
var programName string
|
||||||
|
if platformConfig.Equals(bonkPlatformConfig) {
|
||||||
|
programName = SolProgramRaydiumLaunchLabBonk
|
||||||
|
} else {
|
||||||
|
programName = SolProgramRaydiumLaunchLab
|
||||||
|
}
|
||||||
|
baseTokenProgram := tx.rawTx.accountList[instruction.Accounts[22]]
|
||||||
|
quoteTokenProgram := tx.rawTx.accountList[instruction.Accounts[23]]
|
||||||
|
|
||||||
|
baseMint := tx.rawTx.accountList[instruction.Accounts[1]]
|
||||||
|
quoteMint := tx.rawTx.accountList[instruction.Accounts[2]]
|
||||||
|
pool := tx.rawTx.accountList[instruction.Accounts[17]]
|
||||||
|
|
||||||
|
baseVaultIdx := instruction.Accounts[19]
|
||||||
|
quoteVaultIdx := instruction.Accounts[20]
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
offset[1] += 1
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: programName,
|
||||||
|
Event: "migrate",
|
||||||
|
Pool: pool,
|
||||||
|
BaseMint: baseMint,
|
||||||
|
QuoteMint: quoteMint,
|
||||||
|
BaseTokenProgram: baseTokenProgram,
|
||||||
|
QuoteTokenProgram: quoteTokenProgram,
|
||||||
|
BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals),
|
||||||
|
QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals),
|
||||||
|
User: tx.rawTx.accountList[instruction.Accounts[0]],
|
||||||
|
//BaseAmount: decimal.Decimal{},
|
||||||
|
//QuoteAmount: decimal.Decimal{},
|
||||||
|
MigrateTopProgram: tx.rawTx.accountList[instruction.Accounts[4]],
|
||||||
|
MigrateToPool: tx.rawTx.accountList[instruction.Accounts[5]],
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func raydiumLaunchLabMigrateToAmmParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if len(instruction.Accounts) < 27 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for migrate instruction")
|
||||||
|
}
|
||||||
|
platformConfig := tx.rawTx.accountList[instruction.Accounts[3]]
|
||||||
|
var programName string
|
||||||
|
if platformConfig.Equals(bonkPlatformConfig) {
|
||||||
|
programName = SolProgramRaydiumLaunchLabBonk
|
||||||
|
} else {
|
||||||
|
programName = SolProgramRaydiumLaunchLab
|
||||||
|
}
|
||||||
|
var entryContract solana.PublicKey = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
|
||||||
|
baseMint := tx.rawTx.accountList[instruction.Accounts[1]]
|
||||||
|
quoteMint := tx.rawTx.accountList[instruction.Accounts[2]]
|
||||||
|
pool := tx.rawTx.accountList[instruction.Accounts[23]]
|
||||||
|
|
||||||
|
baseVaultIdx := instruction.Accounts[25]
|
||||||
|
quoteVaultIdx := instruction.Accounts[26]
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
baseTokenProgram := baseTokenBalance.ProgramIDAccount
|
||||||
|
quoteTokenProgram := quoteTokenBalance.ProgramIDAccount
|
||||||
|
offset[1] += 1
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: programName,
|
||||||
|
Event: "migrate",
|
||||||
|
Pool: pool,
|
||||||
|
BaseMint: baseMint,
|
||||||
|
QuoteMint: quoteMint,
|
||||||
|
BaseTokenProgram: baseTokenProgram,
|
||||||
|
QuoteTokenProgram: quoteTokenProgram,
|
||||||
|
BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals),
|
||||||
|
QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals),
|
||||||
|
User: tx.rawTx.accountList[instruction.Accounts[0]],
|
||||||
|
//BaseAmount: decimal.Decimal{},
|
||||||
|
//QuoteAmount: decimal.Decimal{},
|
||||||
|
MigrateTopProgram: tx.rawTx.accountList[instruction.Accounts[12]],
|
||||||
|
MigrateToPool: tx.rawTx.accountList[instruction.Accounts[13]],
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type RaydiumLaunchLabSwapEvent struct {
|
||||||
|
PoolState solana.PublicKey
|
||||||
|
TotalBaseSell uint64
|
||||||
|
VirtualBase uint64
|
||||||
|
VirtualQuote uint64
|
||||||
|
RealBaseBefore uint64
|
||||||
|
RealQuoteBefore uint64
|
||||||
|
RealBaseAfter uint64
|
||||||
|
RealQuoteAfter uint64
|
||||||
|
AmountIn uint64
|
||||||
|
AmountOut uint64
|
||||||
|
ProtocolFee uint64
|
||||||
|
PlatformFee uint64
|
||||||
|
CreatorFee uint64
|
||||||
|
ShareFee uint64
|
||||||
|
TradeDirection uint8 // 0: buy 1: sell
|
||||||
|
PoolStatus uint8 // 0 Fund, 1 Migrate, 2 Trade
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func raydiumLaunchLabSwapParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
platformConfig := tx.rawTx.accountList[instruction.Accounts[3]]
|
||||||
|
var programName string
|
||||||
|
if platformConfig.Equals(bonkPlatformConfig) {
|
||||||
|
programName = SolProgramRaydiumLaunchLabBonk
|
||||||
|
} else {
|
||||||
|
programName = SolProgramRaydiumLaunchLab
|
||||||
|
}
|
||||||
|
var entryContract solana.PublicKey = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
user := tx.rawTx.accountList[instruction.Accounts[0]]
|
||||||
|
pool := tx.rawTx.accountList[instruction.Accounts[4]]
|
||||||
|
userBaseIdx := instruction.Accounts[5]
|
||||||
|
userQuoteIdx := instruction.Accounts[6]
|
||||||
|
baseVaultIdx := instruction.Accounts[7]
|
||||||
|
quoteVaultIdx := instruction.Accounts[8]
|
||||||
|
baseMint := tx.rawTx.accountList[instruction.Accounts[9]]
|
||||||
|
quoteMint := tx.rawTx.accountList[instruction.Accounts[10]]
|
||||||
|
baseTokenProgram := tx.rawTx.accountList[instruction.Accounts[11]]
|
||||||
|
quoteTokenProgram := tx.rawTx.accountList[instruction.Accounts[12]]
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
baseMintDecimals := uint8(baseTokenBalance.UITokenAmount.Decimals)
|
||||||
|
quoteMintDecimals := uint8(quoteTokenBalance.UITokenAmount.Decimals)
|
||||||
|
inners, err := getInnerInstructions(innerInstructions, offset[1])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get inner instructions: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var swapEvent RaydiumLaunchLabSwapEvent
|
||||||
|
loadedEvent := false
|
||||||
|
var prefixLen uint = offset[1]
|
||||||
|
for innerIndex, innerInstruction := range inners {
|
||||||
|
if innerInstruction.ProgramIDIndex == instruction.ProgramIDIndex && len(innerInstruction.Data) >= 16 &&
|
||||||
|
bytes.Equal(innerInstruction.Data[:8], pumpEventDiscriminator[:]) &&
|
||||||
|
bytes.Equal(innerInstruction.Data[8:16], raydiumLaunchLabTradeEvnet[:]) &&
|
||||||
|
len(innerInstruction.Accounts) == 1 {
|
||||||
|
if offset[1] == 0 {
|
||||||
|
offset[0] += 1
|
||||||
|
} else {
|
||||||
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
||||||
|
}
|
||||||
|
err := agbinary.NewBorshDecoder(innerInstruction.Data[16:]).Decode(&swapEvent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to deserialize swap event: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedEvent = true
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !loadedEvent {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get swap event")
|
||||||
|
}
|
||||||
|
|
||||||
|
var event string
|
||||||
|
var baseAmount, quoteAmount decimal.Decimal
|
||||||
|
if swapEvent.TradeDirection == 0 {
|
||||||
|
event = "buy"
|
||||||
|
baseAmount = decimal.NewFromInt(int64(swapEvent.AmountOut))
|
||||||
|
quoteAmount = decimal.NewFromInt(int64(swapEvent.AmountIn))
|
||||||
|
} else {
|
||||||
|
event = "sell"
|
||||||
|
baseAmount = decimal.NewFromInt(int64(swapEvent.AmountIn))
|
||||||
|
quoteAmount = decimal.NewFromInt(int64(swapEvent.AmountOut))
|
||||||
|
}
|
||||||
|
baseReserve := decimal.NewFromInt(int64(swapEvent.RealBaseAfter))
|
||||||
|
quoteReserve := decimal.NewFromInt(int64(swapEvent.RealQuoteAfter))
|
||||||
|
userBase := getAccountBalanceAfterTx(tx.rawTx, userBaseIdx)
|
||||||
|
userQuote := getAccountBalanceAfterTx(tx.rawTx, userQuoteIdx)
|
||||||
|
|
||||||
|
return []Swap{{
|
||||||
|
Program: programName,
|
||||||
|
Event: event,
|
||||||
|
Pool: pool,
|
||||||
|
BaseMint: baseMint,
|
||||||
|
QuoteMint: quoteMint,
|
||||||
|
BaseTokenProgram: baseTokenProgram,
|
||||||
|
QuoteTokenProgram: quoteTokenProgram,
|
||||||
|
BaseMintDecimals: baseMintDecimals,
|
||||||
|
QuoteMintDecimals: quoteMintDecimals,
|
||||||
|
User: user,
|
||||||
|
BaseAmount: baseAmount,
|
||||||
|
QuoteAmount: quoteAmount,
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
Mayhem: false,
|
||||||
|
UserBaseBalance: userBase,
|
||||||
|
UserQuoteBalance: userQuote,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
}}, offset, nil
|
||||||
|
}
|
||||||
500
raydiumv4.go
Normal file
500
raydiumv4.go
Normal file
@@ -0,0 +1,500 @@
|
|||||||
|
package pump_parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func raydiumV4Parser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
if !tx.rawTx.accountList[instruction.ProgramIDIndex].Equals(raydiumV4Program) {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("raydiumv4 instruction not found, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
decode := instruction.Data
|
||||||
|
if len(decode) < 1 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("raydiumv4 program instruction data too short, offset, %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
discriminator := decode[0]
|
||||||
|
|
||||||
|
switch discriminator {
|
||||||
|
case raydiumV4InitializePoolDiscriminator:
|
||||||
|
return raydiumv4InitializeParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case raydiumV4AddLiquidityDiscriminator:
|
||||||
|
return raydiumv4AddLiquidityParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case raydiumV4RemoveLiquidityDiscriminator:
|
||||||
|
return raydiumv4RemoveLiquidityParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case raydiumV4WithdrawPNLDiscriminator:
|
||||||
|
return raydiumv4WithdrawPNLParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case raydiumV4SwapBaseInDiscriminator, raydiumV4SwapBaseOutDiscriminator:
|
||||||
|
return raydiumv4SwapParser(tx, instruction, innerInstructions, offset)
|
||||||
|
case raydiumV4SwapBaseInV2Discriminator, raydiumV4SwapBaseOutV2Discriminator:
|
||||||
|
return raydiumv4SwapV2Parser(tx, instruction, innerInstructions, offset)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func raydiumv4InitializeParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
accountsLen := len(instruction.Accounts)
|
||||||
|
if accountsLen != 21 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("invalid number of accounts for raydiumv4 initialize instruction, offset %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
//who := tx.rawTx.accountList[instruction.Accounts[accountsLen-1]]
|
||||||
|
user := tx.rawTx.accountList[instruction.Accounts[accountsLen-4]]
|
||||||
|
baseVaultIdx := instruction.Accounts[10]
|
||||||
|
quoteVaultIdx := instruction.Accounts[11]
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
baseReserve, _ := decimal.NewFromString(baseTokenbalance.UITokenAmount.Amount)
|
||||||
|
quoteReserve, _ := decimal.NewFromString(quoteTokenbalance.UITokenAmount.Amount)
|
||||||
|
offset[1] += 30
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramRaydiumV4,
|
||||||
|
Event: "create",
|
||||||
|
Pool: tx.rawTx.accountList[instruction.Accounts[4]],
|
||||||
|
BaseMint: baseTokenbalance.MintAccount,
|
||||||
|
QuoteMint: quoteTokenbalance.MintAccount,
|
||||||
|
BaseTokenProgram: baseTokenbalance.ProgramIDAccount,
|
||||||
|
QuoteTokenProgram: quoteTokenbalance.ProgramIDAccount,
|
||||||
|
Creator: user,
|
||||||
|
BaseMintDecimals: uint8(baseTokenbalance.UITokenAmount.Decimals),
|
||||||
|
QuoteMintDecimals: uint8(quoteTokenbalance.UITokenAmount.Decimals),
|
||||||
|
User: user,
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func raydiumv4AddLiquidityParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
accountsLen := len(instruction.Accounts)
|
||||||
|
if accountsLen != 14 && accountsLen != 15 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("invalid number of accounts for raydiumv4 add liquidity instruction, offset %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
|
||||||
|
baseVaultAccountIndex := instruction.Accounts[6]
|
||||||
|
quoteVaultAccountIndex := instruction.Accounts[7]
|
||||||
|
|
||||||
|
userBaseVaultAccountIndex := instruction.Accounts[9]
|
||||||
|
userQuoteVaultAccountIndex := instruction.Accounts[10]
|
||||||
|
|
||||||
|
baseTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultAccountIndex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get base vault balance after tx: %v", err)
|
||||||
|
}
|
||||||
|
quoteTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultAccountIndex)
|
||||||
|
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 baseFound, quoteFound bool
|
||||||
|
var baseAmount, quoteAmount decimal.Decimal
|
||||||
|
for i, inner := range inners {
|
||||||
|
from, to, amount, err := parseTokenTransfer(tx.rawTx, inner)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if from.Equals(tx.rawTx.accountList[userBaseVaultAccountIndex]) && to.Equals(tx.rawTx.accountList[baseVaultAccountIndex]) && !baseFound {
|
||||||
|
baseAmount = decimal.NewFromUint64(amount)
|
||||||
|
baseFound = true
|
||||||
|
} else if from.Equals(tx.rawTx.accountList[userQuoteVaultAccountIndex]) && to.Equals(tx.rawTx.accountList[quoteVaultAccountIndex]) && !quoteFound {
|
||||||
|
quoteAmount = decimal.NewFromUint64(amount)
|
||||||
|
quoteFound = true
|
||||||
|
}
|
||||||
|
if baseFound && quoteFound {
|
||||||
|
nextIndex = i + 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !baseFound || !quoteFound {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to find token transfer inner instruction for add liquidity, offset %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
offset[1] += uint(nextIndex + 1)
|
||||||
|
baseReserve, _ := decimal.NewFromString(baseTokenbalance.UITokenAmount.Amount)
|
||||||
|
quoteReserve, _ := decimal.NewFromString(quoteTokenbalance.UITokenAmount.Amount)
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramRaydiumV4,
|
||||||
|
Event: "remove_liquidity",
|
||||||
|
Pool: tx.rawTx.accountList[instruction.Accounts[1]],
|
||||||
|
BaseMint: baseTokenbalance.MintAccount,
|
||||||
|
QuoteMint: quoteTokenbalance.MintAccount,
|
||||||
|
BaseTokenProgram: baseTokenbalance.ProgramIDAccount,
|
||||||
|
QuoteTokenProgram: quoteTokenbalance.ProgramIDAccount,
|
||||||
|
User: tx.rawTx.accountList[instruction.Accounts[12]],
|
||||||
|
BaseMintDecimals: uint8(baseTokenbalance.UITokenAmount.Decimals),
|
||||||
|
QuoteMintDecimals: uint8(quoteTokenbalance.UITokenAmount.Decimals),
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
BaseAmount: baseAmount,
|
||||||
|
QuoteAmount: quoteAmount,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func raydiumv4RemoveLiquidityParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
accountsLen := len(instruction.Accounts)
|
||||||
|
const AccountLen = 20
|
||||||
|
if accountsLen != AccountLen && accountsLen != AccountLen+1 && accountsLen != AccountLen+2 && accountsLen != AccountLen+3 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("invalid number of accounts for raydiumv4 add liquidity instruction, offset %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
baseVaultAccountIndex := instruction.Accounts[6]
|
||||||
|
quoteVaultAccountIndex := instruction.Accounts[7]
|
||||||
|
userBaseVaultAccountIndex := instruction.Accounts[14]
|
||||||
|
userQuoteVaultAccountIndex := instruction.Accounts[16]
|
||||||
|
|
||||||
|
if accountsLen == AccountLen+2 || accountsLen == AccountLen+3 {
|
||||||
|
userBaseVaultAccountIndex = instruction.Accounts[16]
|
||||||
|
userQuoteVaultAccountIndex = instruction.Accounts[17]
|
||||||
|
}
|
||||||
|
|
||||||
|
baseTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultAccountIndex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get base vault balance after tx: %v", err)
|
||||||
|
}
|
||||||
|
quoteTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultAccountIndex)
|
||||||
|
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 baseFound, quoteFound bool
|
||||||
|
var baseAmount, quoteAmount decimal.Decimal
|
||||||
|
for i, inner := range inners {
|
||||||
|
from, to, amount, err := parseTokenTransfer(tx.rawTx, inner)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if to.Equals(tx.rawTx.accountList[userBaseVaultAccountIndex]) && from.Equals(tx.rawTx.accountList[baseVaultAccountIndex]) && !baseFound {
|
||||||
|
baseAmount = decimal.NewFromUint64(amount)
|
||||||
|
baseFound = true
|
||||||
|
} else if to.Equals(tx.rawTx.accountList[userQuoteVaultAccountIndex]) && from.Equals(tx.rawTx.accountList[quoteVaultAccountIndex]) && !quoteFound {
|
||||||
|
quoteAmount = decimal.NewFromUint64(amount)
|
||||||
|
quoteFound = true
|
||||||
|
}
|
||||||
|
if baseFound && quoteFound {
|
||||||
|
nextIndex = i + 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !baseFound || !quoteFound {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to find token transfer inner instruction for add liquidity, offset %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
offset[1] += uint(nextIndex + 1)
|
||||||
|
baseReserve, _ := decimal.NewFromString(baseTokenbalance.UITokenAmount.Amount)
|
||||||
|
quoteReserve, _ := decimal.NewFromString(quoteTokenbalance.UITokenAmount.Amount)
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramRaydiumV4,
|
||||||
|
Event: "remove_liquidity",
|
||||||
|
Pool: tx.rawTx.accountList[instruction.Accounts[1]],
|
||||||
|
BaseMint: baseTokenbalance.MintAccount,
|
||||||
|
QuoteMint: quoteTokenbalance.MintAccount,
|
||||||
|
BaseTokenProgram: baseTokenbalance.ProgramIDAccount,
|
||||||
|
QuoteTokenProgram: quoteTokenbalance.ProgramIDAccount,
|
||||||
|
User: tx.rawTx.accountList[0],
|
||||||
|
BaseMintDecimals: uint8(baseTokenbalance.UITokenAmount.Decimals),
|
||||||
|
QuoteMintDecimals: uint8(quoteTokenbalance.UITokenAmount.Decimals),
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
BaseAmount: baseAmount,
|
||||||
|
QuoteAmount: quoteAmount,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func raydiumv4WithdrawPNLParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
accountsLen := len(instruction.Accounts)
|
||||||
|
if accountsLen != 17 && accountsLen != 18 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("invalid number of accounts for raydiumv4 WithdrawPNL instruction, offset %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
|
||||||
|
baseVaultAccountIndex := instruction.Accounts[5]
|
||||||
|
quoteVaultAccountIndex := instruction.Accounts[6]
|
||||||
|
userBaseVaultAccountIndex := instruction.Accounts[7]
|
||||||
|
userQuoteVaultAccountIndex := instruction.Accounts[8]
|
||||||
|
|
||||||
|
baseTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultAccountIndex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get base vault balance after tx: %v", err)
|
||||||
|
}
|
||||||
|
quoteTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultAccountIndex)
|
||||||
|
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 baseFound, quoteFound bool
|
||||||
|
var baseAmount, quoteAmount decimal.Decimal
|
||||||
|
for i, inner := range inners {
|
||||||
|
from, to, amount, err := parseTokenTransfer(tx.rawTx, inner)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if to.Equals(tx.rawTx.accountList[userBaseVaultAccountIndex]) && from.Equals(tx.rawTx.accountList[baseVaultAccountIndex]) && !baseFound {
|
||||||
|
baseAmount = decimal.NewFromUint64(amount)
|
||||||
|
baseFound = true
|
||||||
|
} else if to.Equals(tx.rawTx.accountList[userQuoteVaultAccountIndex]) && from.Equals(tx.rawTx.accountList[quoteVaultAccountIndex]) && !quoteFound {
|
||||||
|
quoteAmount = decimal.NewFromUint64(amount)
|
||||||
|
quoteFound = true
|
||||||
|
}
|
||||||
|
if baseFound && quoteFound {
|
||||||
|
nextIndex = i + 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !baseFound || !quoteFound {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to find token transfer inner instruction for with pnl, offset %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
offset[1] += uint(nextIndex + 1)
|
||||||
|
|
||||||
|
baseReserve, _ := decimal.NewFromString(baseTokenbalance.UITokenAmount.Amount)
|
||||||
|
quoteReserve, _ := decimal.NewFromString(quoteTokenbalance.UITokenAmount.Amount)
|
||||||
|
return []Swap{
|
||||||
|
{
|
||||||
|
Program: SolProgramRaydiumV4,
|
||||||
|
Event: "remove_liquidity",
|
||||||
|
Pool: tx.rawTx.accountList[instruction.Accounts[1]],
|
||||||
|
BaseMint: baseTokenbalance.MintAccount,
|
||||||
|
QuoteMint: quoteTokenbalance.MintAccount,
|
||||||
|
BaseTokenProgram: baseTokenbalance.ProgramIDAccount,
|
||||||
|
QuoteTokenProgram: quoteTokenbalance.ProgramIDAccount,
|
||||||
|
User: tx.rawTx.accountList[instruction.Accounts[9]],
|
||||||
|
BaseMintDecimals: uint8(baseTokenbalance.UITokenAmount.Decimals),
|
||||||
|
QuoteMintDecimals: uint8(quoteTokenbalance.UITokenAmount.Decimals),
|
||||||
|
BaseReserve: baseReserve,
|
||||||
|
QuoteReserve: quoteReserve,
|
||||||
|
BaseAmount: baseAmount,
|
||||||
|
QuoteAmount: quoteAmount,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
},
|
||||||
|
}, offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func raydiumv4SwapParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
|
accountsLen := len(instruction.Accounts)
|
||||||
|
if accountsLen != 17 && accountsLen != 18 {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("invalid number of accounts for raydiumv4 swap instruction, offset %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
user := tx.rawTx.accountList[instruction.Accounts[accountsLen-1]]
|
||||||
|
userSrcIdx := instruction.Accounts[accountsLen-3]
|
||||||
|
userDestIdx := instruction.Accounts[accountsLen-2]
|
||||||
|
vaultBaseIdx := instruction.Accounts[4]
|
||||||
|
vaultQuoteIdx := instruction.Accounts[5]
|
||||||
|
if accountsLen == 18 {
|
||||||
|
vaultBaseIdx = instruction.Accounts[5]
|
||||||
|
vaultQuoteIdx = instruction.Accounts[6]
|
||||||
|
}
|
||||||
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
|
||||||
|
ammAccount := tx.rawTx.accountList[instruction.Accounts[1]]
|
||||||
|
|
||||||
|
userSourceTokenAccount := tx.rawTx.accountList[userSrcIdx]
|
||||||
|
userDestinationTokenAccount := tx.rawTx.accountList[userDestIdx]
|
||||||
|
baseTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, vaultBaseIdx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get base vault balance after tx: %v", err)
|
||||||
|
}
|
||||||
|
quoteTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, vaultQuoteIdx)
|
||||||
|
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[vaultBaseIdx]) {
|
||||||
|
event = "sell"
|
||||||
|
baseAmount = decimal.NewFromUint64(amount)
|
||||||
|
srcFound = true
|
||||||
|
} else if to.Equals(tx.rawTx.accountList[vaultQuoteIdx]) {
|
||||||
|
event = "buy"
|
||||||
|
quoteAmount = decimal.NewFromUint64(amount)
|
||||||
|
srcFound = true
|
||||||
|
}
|
||||||
|
} else if to.Equals(userDestinationTokenAccount) && !destFound {
|
||||||
|
if from.Equals(tx.rawTx.accountList[vaultQuoteIdx]) {
|
||||||
|
event = "sell"
|
||||||
|
quoteAmount = decimal.NewFromUint64(amount)
|
||||||
|
destFound = true
|
||||||
|
} else if from.Equals(tx.rawTx.accountList[vaultBaseIdx]) {
|
||||||
|
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 swap, offset %d, %d", offset[0], offset[1])
|
||||||
|
}
|
||||||
|
offset[1] += uint(nextIndex + 1)
|
||||||
|
userBase := getAccountBalanceAfterTx(tx.rawTx, userSrcIdx)
|
||||||
|
userQuote := getAccountBalanceAfterTx(tx.rawTx, userDestIdx)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
// Raydium's documented V2 layout uses the first 8 accounts. Routed CPI calls
|
||||||
|
// may append extra readonly accounts (for example the Raydium program id) at
|
||||||
|
// the tail, so we only require the canonical prefix here.
|
||||||
|
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
|
||||||
|
}
|
||||||
136
raydiumv4_test.go
Normal file
136
raydiumv4_test.go
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
package pump_parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gagliardetto/solana-go"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func transferInstructionData(amount uint64) solana.Base58 {
|
||||||
|
data := make([]byte, 9)
|
||||||
|
data[0] = 3
|
||||||
|
binary.LittleEndian.PutUint64(data[1:], amount)
|
||||||
|
return solana.Base58(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRaydiumV4SwapV2ParserAllowsTrailingReadonlyAccounts(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
accountList := make([]solana.PublicKey, 32)
|
||||||
|
for i := range accountList {
|
||||||
|
accountList[i] = testPublicKey(byte(i + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
accountList[0] = solana.TokenProgramID
|
||||||
|
accountList[8] = raydiumV4Program
|
||||||
|
accountList[20] = testPublicKey(200)
|
||||||
|
accountList[21] = testPublicKey(201)
|
||||||
|
accountList[22] = testPublicKey(202)
|
||||||
|
|
||||||
|
outerInstruction := Instruction{ProgramIDIndex: 20}
|
||||||
|
swapInstruction := Instruction{
|
||||||
|
Accounts: []int{0, 1, 2, 3, 4, 5, 6, 7, 8},
|
||||||
|
ProgramIDIndex: 8,
|
||||||
|
Data: solana.Base58([]byte{raydiumV4SwapBaseInV2Discriminator}),
|
||||||
|
}
|
||||||
|
innerInstructions := InnerInstructions{
|
||||||
|
Index: 0,
|
||||||
|
Instructions: []Instruction{
|
||||||
|
swapInstruction,
|
||||||
|
{
|
||||||
|
Accounts: []int{5, 4, 7},
|
||||||
|
ProgramIDIndex: 0,
|
||||||
|
Data: transferInstructionData(55),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Accounts: []int{3, 6, 2},
|
||||||
|
ProgramIDIndex: 0,
|
||||||
|
Data: transferInstructionData(42),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rawTx := &RawTx{
|
||||||
|
accountList: accountList,
|
||||||
|
Meta: Meta{
|
||||||
|
PostTokenBalances: []TokenBalance{
|
||||||
|
{
|
||||||
|
AccountIndex: 3,
|
||||||
|
MintAccount: accountList[21],
|
||||||
|
ProgramIDAccount: solana.TokenProgramID,
|
||||||
|
UITokenAmount: UITokenAmount{
|
||||||
|
Amount: "1000",
|
||||||
|
Decimals: 6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
AccountIndex: 4,
|
||||||
|
MintAccount: accountList[22],
|
||||||
|
ProgramIDAccount: solana.TokenProgramID,
|
||||||
|
UITokenAmount: UITokenAmount{
|
||||||
|
Amount: "2000",
|
||||||
|
Decimals: 9,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
AccountIndex: 5,
|
||||||
|
MintAccount: accountList[22],
|
||||||
|
ProgramIDAccount: solana.TokenProgramID,
|
||||||
|
UITokenAmount: UITokenAmount{
|
||||||
|
Amount: "300",
|
||||||
|
Decimals: 9,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
AccountIndex: 6,
|
||||||
|
MintAccount: accountList[21],
|
||||||
|
ProgramIDAccount: solana.TokenProgramID,
|
||||||
|
UITokenAmount: UITokenAmount{
|
||||||
|
Amount: "400",
|
||||||
|
Decimals: 6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Transaction: Transaction{
|
||||||
|
Message: Message{
|
||||||
|
Instructions: []Instruction{outerInstruction},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tx := &Tx{rawTx: rawTx}
|
||||||
|
|
||||||
|
swaps, nextOffset, err := raydiumv4SwapV2Parser(tx, swapInstruction, innerInstructions, [2]uint{0, 1})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("raydiumv4SwapV2Parser() error = %v", err)
|
||||||
|
}
|
||||||
|
if len(swaps) != 1 {
|
||||||
|
t.Fatalf("raydiumv4SwapV2Parser() swaps len = %d, want 1", len(swaps))
|
||||||
|
}
|
||||||
|
if nextOffset != [2]uint{0, 4} {
|
||||||
|
t.Fatalf("raydiumv4SwapV2Parser() nextOffset = %v, want [0 4]", nextOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
swap := swaps[0]
|
||||||
|
if swap.Event != "buy" {
|
||||||
|
t.Fatalf("swap.Event = %q, want %q", swap.Event, "buy")
|
||||||
|
}
|
||||||
|
if !swap.Pool.Equals(accountList[1]) {
|
||||||
|
t.Fatalf("swap.Pool = %s, want %s", swap.Pool, accountList[1])
|
||||||
|
}
|
||||||
|
if !swap.User.Equals(accountList[7]) {
|
||||||
|
t.Fatalf("swap.User = %s, want %s", swap.User, accountList[7])
|
||||||
|
}
|
||||||
|
if !swap.EntryContract.Equals(accountList[20]) {
|
||||||
|
t.Fatalf("swap.EntryContract = %s, want %s", swap.EntryContract, accountList[20])
|
||||||
|
}
|
||||||
|
if !swap.BaseAmount.Equal(decimal.NewFromInt(42)) {
|
||||||
|
t.Fatalf("swap.BaseAmount = %s, want 42", swap.BaseAmount)
|
||||||
|
}
|
||||||
|
if !swap.QuoteAmount.Equal(decimal.NewFromInt(55)) {
|
||||||
|
t.Fatalf("swap.QuoteAmount = %s, want 55", swap.QuoteAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
11
system.go
11
system.go
@@ -31,9 +31,18 @@ func TransferParser(result *RawTx, instruction Instruction, offset [2]uint, tx *
|
|||||||
}
|
}
|
||||||
var lamports uint64 = binary.LittleEndian.Uint64(decodeData)
|
var lamports uint64 = binary.LittleEndian.Uint64(decodeData)
|
||||||
|
|
||||||
//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 result.Meta.Err == nil {
|
||||||
|
if offset[1] == 0 {
|
||||||
|
tx.SolTransfer = append(tx.SolTransfer, SolTransfer{
|
||||||
|
From: from,
|
||||||
|
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]
|
||||||
if ok {
|
if ok {
|
||||||
|
|||||||
129
tx.go
129
tx.go
@@ -10,6 +10,11 @@ type Swap struct {
|
|||||||
Program string
|
Program string
|
||||||
Event string
|
Event string
|
||||||
|
|
||||||
|
TxIndex int
|
||||||
|
|
||||||
|
InstrIdx uint8
|
||||||
|
InnerIdx uint8
|
||||||
|
|
||||||
Pool solana.PublicKey
|
Pool solana.PublicKey
|
||||||
BaseMint solana.PublicKey
|
BaseMint solana.PublicKey
|
||||||
QuoteMint solana.PublicKey
|
QuoteMint solana.PublicKey
|
||||||
@@ -29,10 +34,34 @@ 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
|
||||||
EntryContract solana.PublicKey
|
EntryContract solana.PublicKey
|
||||||
|
|
||||||
|
MigrateToPool solana.PublicKey
|
||||||
|
MigrateTopProgram solana.PublicKey
|
||||||
|
|
||||||
|
LpMint solana.PublicKey
|
||||||
|
|
||||||
|
AfterSOLBalance decimal.Decimal
|
||||||
|
|
||||||
|
//For meteora dlmm
|
||||||
|
ActiveBinId int32
|
||||||
|
StartBinId int32
|
||||||
|
EndBinId int32
|
||||||
|
RemoveBp int32
|
||||||
|
PositionAccount solana.PublicKey
|
||||||
|
FeeAmount decimal.Decimal
|
||||||
|
FeeBps string
|
||||||
|
LpFeeAmount decimal.Decimal
|
||||||
|
FeeSide string
|
||||||
|
FeeMint solana.PublicKey
|
||||||
|
FeeTokenProgram solana.PublicKey
|
||||||
|
FeeMintDecimals uint8
|
||||||
|
|
||||||
|
ConsumeUnit uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
type platformInfo struct {
|
type platformInfo struct {
|
||||||
@@ -45,15 +74,30 @@ type mevInfo struct {
|
|||||||
MevAgentFee decimal.Decimal
|
MevAgentFee decimal.Decimal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SolTransfer struct {
|
||||||
|
From solana.PublicKey
|
||||||
|
To solana.PublicKey
|
||||||
|
Amount decimal.Decimal
|
||||||
|
}
|
||||||
|
type ChainLink struct {
|
||||||
|
Timestamp int64
|
||||||
|
Price decimal.Decimal
|
||||||
|
}
|
||||||
|
|
||||||
type Tx struct {
|
type Tx struct {
|
||||||
rawTx *RawTx
|
rawTx *RawTx
|
||||||
Signer solana.PublicKey
|
Vote bool
|
||||||
Err interface{} `json:"err,omitempty"`
|
Signer solana.PublicKey
|
||||||
Swaps []Swap `json:"swaps,omitempty"`
|
Err *TransactionParsedError `json:"err,omitempty"`
|
||||||
Block uint64 `json:"block"`
|
Swaps []Swap `json:"swaps,omitempty"`
|
||||||
BlockIndex uint64 `json:"index"`
|
SolTransfer []SolTransfer `json:"sol_transfer,omitempty"`
|
||||||
TxHash *[64]byte `json:"-"`
|
Block uint64 `json:"block"`
|
||||||
BlockAt int64 `json:"block_at"`
|
ChainLink ChainLink `json:"chain_link"`
|
||||||
|
BlockIndex uint64 `json:"index"`
|
||||||
|
TxHash *[64]byte `json:"-"`
|
||||||
|
BlockAt int64 `json:"block_at"`
|
||||||
|
|
||||||
|
CuFee decimal.Decimal `json:"cu_fee"`
|
||||||
|
|
||||||
cachedTxHash string
|
cachedTxHash string
|
||||||
|
|
||||||
@@ -67,9 +111,16 @@ 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 ??
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tx *Tx) GetRawTx() *RawTx {
|
||||||
|
return tx.rawTx
|
||||||
|
}
|
||||||
|
|
||||||
func (tx *Tx) SetRawTx(t *RawTx) {
|
func (tx *Tx) SetRawTx(t *RawTx) {
|
||||||
tx.rawTx = t
|
tx.rawTx = t
|
||||||
}
|
}
|
||||||
@@ -103,7 +154,6 @@ func (tx *Tx) GetTxHash() string {
|
|||||||
|
|
||||||
func (tx *Tx) CheckPlatform(swap Swap) (string, decimal.Decimal) {
|
func (tx *Tx) CheckPlatform(swap Swap) (string, decimal.Decimal) {
|
||||||
// hasSolProgramRaydiumLaunchLabBonk
|
// hasSolProgramRaydiumLaunchLabBonk
|
||||||
rawTx := tx.rawTx
|
|
||||||
var platform string
|
var platform string
|
||||||
var platformFee decimal.Decimal
|
var platformFee decimal.Decimal
|
||||||
if len(tx.Platform) == 0 {
|
if len(tx.Platform) == 0 {
|
||||||
@@ -115,32 +165,14 @@ func (tx *Tx) CheckPlatform(swap Swap) (string, decimal.Decimal) {
|
|||||||
platformFee = info.PlatformFee
|
platformFee = info.PlatformFee
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if swap.Event == "buy" && swap.Program == SolProgramRaydiumLaunchLabBonk {
|
quoteAmount := swap.QuoteAmount
|
||||||
for _, p := range tx.Platform {
|
if swap.BaseMint.Equals(solana.WrappedSol) {
|
||||||
switch p.Platform {
|
quoteAmount = swap.BaseAmount
|
||||||
case PlatformAxiom:
|
|
||||||
if !checkBonkAxiomBuy(rawTx) {
|
|
||||||
platform = PlatformFake
|
|
||||||
}
|
|
||||||
case PlatformGMGN:
|
|
||||||
if !checkBonkGmgnBuy(rawTx) {
|
|
||||||
platform = PlatformFake
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if platform != "" &&
|
if platform != "" &&
|
||||||
platform != PlatformFake {
|
platform != PlatformFake &&
|
||||||
if (swap.QuoteMint.Equals(wSolMint) || swap.QuoteMint.IsZero()) &&
|
platformFee.LessThan(quoteAmount.Mul(decimal.NewFromInt(9)).Div(decimal.New(10000, 9))) {
|
||||||
platformFee.LessThan(swap.QuoteAmount.Div(decimal.New(1, int32(swap.QuoteMintDecimals))).Div(decimal.NewFromInt(10000)).Mul(decimal.NewFromInt(9))) {
|
platform = PlatformFake
|
||||||
platform = PlatformFake
|
|
||||||
} else if swap.BaseMint.Equals(wSolMint) &&
|
|
||||||
platformFee.LessThan(swap.QuoteAmount.Div(decimal.New(1, int32(swap.QuoteMintDecimals))).Div(decimal.NewFromInt(10000)).Mul(decimal.NewFromInt(9))) {
|
|
||||||
platform = PlatformFake
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
if platform == "" {
|
if platform == "" {
|
||||||
platform = PlatformNone
|
platform = PlatformNone
|
||||||
@@ -174,3 +206,34 @@ func (s Swap) CheckEntryContract() string {
|
|||||||
}
|
}
|
||||||
return EntryContractUnknown
|
return EntryContractUnknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tx *Tx) LoadAfterSOLBalance(swap Swap) decimal.Decimal {
|
||||||
|
if swap.User.Equals(tx.Signer) {
|
||||||
|
return tx.AfterSOLBalance
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
makerIndex := 0
|
||||||
|
for i, account := range tx.rawTx.getAccountList() {
|
||||||
|
if account == swap.User {
|
||||||
|
found = true
|
||||||
|
makerIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found && makerIndex < len(tx.rawTx.Meta.PostBalances) {
|
||||||
|
return decimal.NewFromInt(
|
||||||
|
int64(tx.rawTx.Meta.PostBalances[makerIndex]),
|
||||||
|
).Div(decimal.NewFromInt(1000000000)) // sol decimals
|
||||||
|
}
|
||||||
|
return decimal.Zero
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Swap) CheckEntryContractV2() string {
|
||||||
|
name, ok := entryContractAddresses[s.EntryContract]
|
||||||
|
if ok {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return s.EntryContract.String()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user