2026-02-09 14:46:19 +08:00
|
|
|
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) {
|
2026-02-11 14:58:12 +08:00
|
|
|
if len(instruction.Accounts) < 15 {
|
2026-02-09 14:46:19 +08:00
|
|
|
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
|
|
|
|
|
}
|