2531 lines
84 KiB
Go
2531 lines
84 KiB
Go
package pump_parser
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
|
|
agbinary "github.com/gagliardetto/binary"
|
|
"github.com/gagliardetto/solana-go"
|
|
"github.com/shopspring/decimal"
|
|
)
|
|
|
|
type meteoraDlmmSwapArgs struct {
|
|
AmountIn uint64
|
|
MinAmountOut uint64
|
|
}
|
|
|
|
type meteoraDlmmSwapExactOutArgs struct {
|
|
MaxInAmount uint64
|
|
OutAmount uint64
|
|
}
|
|
|
|
type meteoraDlmmSwapWithPriceImpactArgs struct {
|
|
AmountIn uint64
|
|
ActiveID *int32 `bin:"optional"`
|
|
MaxPriceImpactBps uint16
|
|
}
|
|
|
|
type dlmmSwapEvent struct {
|
|
LbPair solana.PublicKey
|
|
From solana.PublicKey
|
|
StartBinId int32
|
|
EndBinId int32
|
|
AmountIn uint64
|
|
AmountOut uint64
|
|
SwapForY bool
|
|
Fee uint64
|
|
ProtocolFee uint64
|
|
FeeBps agbinary.Uint128
|
|
HostFee uint64
|
|
}
|
|
|
|
type dlmmAddLiquidityEvent struct {
|
|
LbPair solana.PublicKey
|
|
From solana.PublicKey
|
|
Position solana.PublicKey
|
|
Amounts [2]uint64
|
|
ActiveBinId int32
|
|
}
|
|
|
|
type dlmmRemoveLiquidityEvent struct {
|
|
LbPair solana.PublicKey
|
|
From solana.PublicKey
|
|
Position solana.PublicKey
|
|
Amounts [2]uint64
|
|
ActiveBinId int32
|
|
}
|
|
|
|
type dlmmPositionCreateEvent struct {
|
|
LbPair solana.PublicKey
|
|
Position solana.PublicKey
|
|
Owner solana.PublicKey
|
|
}
|
|
|
|
type dlmmPositionCloseEvent struct {
|
|
Position solana.PublicKey
|
|
Owner solana.PublicKey
|
|
}
|
|
|
|
type dlmmLbPairCreateEvent struct {
|
|
LbPair solana.PublicKey
|
|
BinStep uint16
|
|
TokenX solana.PublicKey
|
|
TokenY solana.PublicKey
|
|
}
|
|
|
|
type dlmmClaimFeeInnerEvent struct {
|
|
LbPair solana.PublicKey
|
|
Position solana.PublicKey
|
|
Owner solana.PublicKey
|
|
FeeX uint64
|
|
FeeY uint64
|
|
ActiveBinId int32
|
|
HasActiveBin bool
|
|
}
|
|
|
|
type dlmmClaimFeeEvent struct {
|
|
LbPair solana.PublicKey
|
|
Position solana.PublicKey
|
|
Owner solana.PublicKey
|
|
FeeX uint64
|
|
FeeY uint64
|
|
}
|
|
|
|
type dlmmClaimFee2Event struct {
|
|
LbPair solana.PublicKey
|
|
Position solana.PublicKey
|
|
Owner solana.PublicKey
|
|
FeeX uint64
|
|
FeeY uint64
|
|
ActiveBinId int32
|
|
}
|
|
|
|
type dlmmRebalancingEvent struct {
|
|
LbPair solana.PublicKey
|
|
Position solana.PublicKey
|
|
Owner solana.PublicKey
|
|
ActiveBinId int32
|
|
XWithdrawnAmount uint64
|
|
XAddedAmount uint64
|
|
YWithdrawnAmount uint64
|
|
YAddedAmount uint64
|
|
XFeeAmount uint64
|
|
YFeeAmount uint64
|
|
OldMinBinId int32
|
|
OldMaxBinId int32
|
|
NewMinBinId int32
|
|
NewMaxBinId int32
|
|
Rewards [2]uint64
|
|
}
|
|
|
|
type dlmmBinLiquidityDistribution struct {
|
|
BinId int32
|
|
DistributionX uint16
|
|
DistributionY uint16
|
|
}
|
|
|
|
type dlmmBinLiquidityDistributionByWeight struct {
|
|
BinId int32
|
|
Weight uint16
|
|
}
|
|
|
|
type dlmmBinLiquidityReduction struct {
|
|
BinId int32
|
|
BpsToRemove uint16
|
|
}
|
|
|
|
type dlmmLiquidityParameter struct {
|
|
AmountX uint64
|
|
AmountY uint64
|
|
BinLiquidityDist []dlmmBinLiquidityDistribution
|
|
}
|
|
|
|
type dlmmStrategyParameters struct {
|
|
MinBinId int32
|
|
MaxBinId int32
|
|
StrategyType uint8
|
|
Parameters [64]byte
|
|
}
|
|
|
|
type dlmmLiquidityParameterByStrategy struct {
|
|
AmountX uint64
|
|
AmountY uint64
|
|
ActiveID int32
|
|
MaxActiveBinSlippage int32
|
|
StrategyParameters dlmmStrategyParameters
|
|
}
|
|
|
|
type dlmmLiquidityParameterByWeight struct {
|
|
AmountX uint64
|
|
AmountY uint64
|
|
ActiveID int32
|
|
MaxActiveBinSlippage int32
|
|
BinLiquidityDist []dlmmBinLiquidityDistributionByWeight
|
|
}
|
|
|
|
type dlmmAddLiquidityArgs struct {
|
|
LiquidityParameter dlmmLiquidityParameter
|
|
}
|
|
|
|
type dlmmAddLiquidity2Args struct {
|
|
LiquidityParameter dlmmLiquidityParameter
|
|
RemainingAccountsInfo dlmmRemainingAccountsInfo
|
|
}
|
|
|
|
type dlmmAddLiquidityByStrategyArgs struct {
|
|
LiquidityParameter dlmmLiquidityParameterByStrategy
|
|
}
|
|
|
|
type dlmmAddLiquidityByStrategy2Args struct {
|
|
LiquidityParameter dlmmLiquidityParameterByStrategy
|
|
RemainingAccountsInfo dlmmRemainingAccountsInfo
|
|
}
|
|
|
|
type dlmmAddLiquidityByWeightArgs struct {
|
|
LiquidityParameter dlmmLiquidityParameterByWeight
|
|
}
|
|
|
|
type dlmmLiquidityOneSideParameter struct {
|
|
Amount uint64
|
|
ActiveID int32
|
|
MaxActiveBinSlippage int32
|
|
BinLiquidityDist []dlmmBinLiquidityDistributionByWeight
|
|
}
|
|
|
|
type dlmmLiquidityParameterByStrategyOneSide struct {
|
|
Amount uint64
|
|
ActiveID int32
|
|
MaxActiveBinSlippage int32
|
|
StrategyParameters dlmmStrategyParameters
|
|
}
|
|
|
|
type dlmmAddLiquidityOneSideArgs struct {
|
|
LiquidityParameter dlmmLiquidityOneSideParameter
|
|
}
|
|
|
|
type dlmmAddLiquidityByStrategyOneSideArgs struct {
|
|
LiquidityParameter dlmmLiquidityParameterByStrategyOneSide
|
|
}
|
|
|
|
type dlmmCompressedBinDepositAmount struct {
|
|
BinID int32
|
|
Amount uint32
|
|
}
|
|
|
|
type dlmmAddLiquiditySingleSidePreciseParameter struct {
|
|
Bins []dlmmCompressedBinDepositAmount
|
|
DecompressMultiplier uint64
|
|
}
|
|
|
|
type dlmmAddLiquiditySingleSidePreciseParameter2 struct {
|
|
Bins []dlmmCompressedBinDepositAmount
|
|
DecompressMultiplier uint64
|
|
MaxAmount uint64
|
|
}
|
|
|
|
type dlmmAddLiquidityOneSidePreciseArgs struct {
|
|
Parameter dlmmAddLiquiditySingleSidePreciseParameter
|
|
}
|
|
|
|
type dlmmAddLiquidityOneSidePrecise2Args struct {
|
|
LiquidityParameter dlmmAddLiquiditySingleSidePreciseParameter2
|
|
RemainingAccountsInfo dlmmRemainingAccountsInfo
|
|
}
|
|
|
|
type dlmmRemoveLiquidityArgs struct {
|
|
BinLiquidityRemoval []dlmmBinLiquidityReduction
|
|
}
|
|
|
|
type dlmmRemoveLiquidity2Args struct {
|
|
BinLiquidityRemoval []dlmmBinLiquidityReduction
|
|
RemainingAccountsInfo dlmmRemainingAccountsInfo
|
|
}
|
|
|
|
type dlmmRemoveLiquidityByRangeArgs struct {
|
|
FromBinId int32
|
|
ToBinId int32
|
|
BpsToRemove uint16
|
|
}
|
|
|
|
type dlmmRemoveLiquidityByRange2Args struct {
|
|
FromBinId int32
|
|
ToBinId int32
|
|
BpsToRemove uint16
|
|
RemainingAccountsInfo dlmmRemainingAccountsInfo
|
|
}
|
|
|
|
type dlmmInitializePositionArgs struct {
|
|
LowerBinId int32
|
|
Width int32
|
|
}
|
|
|
|
type dlmmInitializePositionByOperatorArgs struct {
|
|
LowerBinId int32
|
|
Width int32
|
|
FeeOwner solana.PublicKey
|
|
LockReleasePoint uint64
|
|
}
|
|
|
|
type dlmmRemainingAccountsInfo struct{}
|
|
|
|
func (dlmmRemainingAccountsInfo) UnmarshalWithDecoder(decoder *agbinary.Decoder) error {
|
|
count, err := decoder.ReadUint32(agbinary.LE)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for i := uint32(0); i < count; i++ {
|
|
variant, err := decoder.ReadUint8()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if variant == 3 {
|
|
if _, err := decoder.ReadUint8(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if _, err := decoder.ReadUint8(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type dlmmSwapAccounts struct {
|
|
poolIdx int
|
|
reserveXIdx int
|
|
reserveYIdx int
|
|
userTokenInIdx int
|
|
userTokenOutIdx int
|
|
tokenXMintIdx int
|
|
tokenYMintIdx int
|
|
oracleIdx int
|
|
userIdx int
|
|
tokenXProgramIdx int
|
|
tokenYProgramIdx int
|
|
}
|
|
|
|
type dlmmLiquidityAccounts struct {
|
|
positionIdx int
|
|
poolIdx int
|
|
userTokenXIdx int
|
|
userTokenYIdx int
|
|
reserveXIdx int
|
|
reserveYIdx int
|
|
tokenXMintIdx int
|
|
tokenYMintIdx int
|
|
userIdx int
|
|
tokenXProgramIdx int
|
|
tokenYProgramIdx int
|
|
}
|
|
|
|
type dlmmOneSideLiquidityAccounts struct {
|
|
positionIdx int
|
|
poolIdx int
|
|
userTokenIdx int
|
|
reserveIdx int
|
|
tokenMintIdx int
|
|
userIdx int
|
|
tokenProgramIdx int
|
|
}
|
|
|
|
var meteoraDlmmEventAuthority = func() solana.PublicKey {
|
|
key, _, err := solana.FindProgramAddress([][]byte{[]byte("__event_authority")}, meteoraDlmmProgram)
|
|
if err != nil {
|
|
return solana.PublicKey{}
|
|
}
|
|
return key
|
|
}()
|
|
|
|
func metaoradlmmParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
if !tx.rawTx.accountList[instruction.ProgramIDIndex].Equals(meteoraDlmmProgram) {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm program instruction not found, offset, %d, %d", offset[0], offset[1])
|
|
}
|
|
decode := instruction.Data
|
|
if len(decode) < 8 {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm program instruction data too short, offset, %d, %d", offset[0], offset[1])
|
|
}
|
|
|
|
discriminator := *(*[8]byte)(decode[:8])
|
|
switch discriminator {
|
|
case meteoraInitializeCustomizablePermissionlessLbPairDiscriminator,
|
|
meteoraInitializeCustomizablePermissionlessLbPair2Discriminator,
|
|
meteoraInitializeLbPairDiscriminator,
|
|
meteoraInitializeLbPair2Discriminator,
|
|
meteoraInitializePermissionLbPairDiscriminator:
|
|
return metaoradlmmInitializeParser(tx, instruction, innerInstructions, offset)
|
|
case meteoraDlmmInitializePositionDiscriminator, meteoraDlmmInitializePosition2Discriminator,
|
|
meteoraDlmmInitializePositionByOperatorDiscriminator, meteoraDlmmInitializePositionPdaDiscriminator:
|
|
return metaoradlmmPositionCreateParser(tx, instruction, innerInstructions, offset)
|
|
case meteoraDlmmSwapDiscriminator, meteoraDlmmSwapExactOutDiscriminator, meteoraDlmmSwapWithPriceImpactDiscriminator:
|
|
return metaoradlmmSwapParser(tx, instruction, innerInstructions, offset)
|
|
case meteoraDlmmSwap2Discriminator, meteoraDlmmSwapExactOut2Discriminator, meteoraDlmmSwapWithPriceImpact2Discriminator:
|
|
return metaoradlmmSwap2Parser(tx, instruction, innerInstructions, offset)
|
|
case meteoraDlmmAddLiquidityDiscriminator, meteoraDlmmAddLiquidity2Discriminator,
|
|
meteoraDlmmAddLiquidityByStrategyDiscriminator, meteoraDlmmAddLiquidityByStrategy2Discriminator,
|
|
meteoraDlmmAddLiquidityByWeightDiscriminator, meteoraDlmmAddLiquidityOneSideDiscriminator,
|
|
meteoraDlmmAddLiquidityOneSidePreciseDiscriminator, meteoraDlmmAddLiquidityOneSidePrecise2Discriminator,
|
|
meteoraDlmmAddLiquidityByStrategyOneSideDiscriminator:
|
|
return metaoradlmmAddLiquidityParser(tx, instruction, innerInstructions, offset)
|
|
case meteoraDlmmClaimFeeDiscriminator, meteoraDlmmClaimFee2Discriminator:
|
|
return metaoradlmmClaimFeeParser(tx, instruction, innerInstructions, offset)
|
|
case meteoraDlmmRebalanceLiquidityDiscriminator:
|
|
return metaoradlmmRebalanceLiquidityParser(tx, instruction, innerInstructions, offset)
|
|
case meteoraDlmmRemoveAllLiquidityDiscriminator, meteoraDlmmRemoveLiquidityDiscriminator, meteoraDlmmRemoveLiquidity2Discriminator,
|
|
meteoraDlmmRemoveLiquidityByRangeDiscriminator, meteoraDlmmRemoveLiquidityByRange2Discriminator:
|
|
return metaoradlmmRemoveLiquidityParser(tx, instruction, innerInstructions, offset)
|
|
case meteoraDlmmClosePositionDiscriminator, meteoraDlmmClosePosition2Discriminator, meteoraDlmmClosePositionIfEmptyDiscriminator:
|
|
return metaoradlmmPositionCloseParser(tx, instruction, innerInstructions, offset)
|
|
default:
|
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
|
}
|
|
}
|
|
|
|
type dlmmInitializeAccounts struct {
|
|
pool solana.PublicKey
|
|
token0 solana.PublicKey
|
|
token1 solana.PublicKey
|
|
baseTokenProgram solana.PublicKey
|
|
quoteTokenProgram solana.PublicKey
|
|
user solana.PublicKey
|
|
}
|
|
|
|
func resolveDlmmInitializeAccounts(result *RawTx, data []byte, accounts []int) (dlmmInitializeAccounts, error) {
|
|
if len(data) < 8 {
|
|
return dlmmInitializeAccounts{}, fmt.Errorf("instruction data too short")
|
|
}
|
|
|
|
accountList := result.getAccountList()
|
|
resolveAt := func(position int) (solana.PublicKey, error) {
|
|
if position < 0 || position >= len(accounts) {
|
|
return solana.PublicKey{}, fmt.Errorf("accounts too short, missing position %d", position)
|
|
}
|
|
accountIndex := accounts[position]
|
|
if accountIndex < 0 || accountIndex >= len(accountList) {
|
|
return solana.PublicKey{}, fmt.Errorf("account index out of range at position %d", position)
|
|
}
|
|
return accountList[accountIndex], nil
|
|
}
|
|
|
|
resolveCommon := func(poolPos, token0Pos, token1Pos, userPos, baseTokenProgramPos, quoteTokenProgramPos int) (dlmmInitializeAccounts, error) {
|
|
pool, err := resolveAt(poolPos)
|
|
if err != nil {
|
|
return dlmmInitializeAccounts{}, err
|
|
}
|
|
token0, err := resolveAt(token0Pos)
|
|
if err != nil {
|
|
return dlmmInitializeAccounts{}, err
|
|
}
|
|
token1, err := resolveAt(token1Pos)
|
|
if err != nil {
|
|
return dlmmInitializeAccounts{}, err
|
|
}
|
|
baseTokenProgram, err := resolveAt(baseTokenProgramPos)
|
|
if err != nil {
|
|
return dlmmInitializeAccounts{}, err
|
|
}
|
|
quoteTokenProgram, err := resolveAt(quoteTokenProgramPos)
|
|
if err != nil {
|
|
return dlmmInitializeAccounts{}, err
|
|
}
|
|
user, err := resolveAt(userPos)
|
|
if err != nil {
|
|
return dlmmInitializeAccounts{}, err
|
|
}
|
|
|
|
return dlmmInitializeAccounts{
|
|
pool: pool,
|
|
token0: token0,
|
|
token1: token1,
|
|
baseTokenProgram: baseTokenProgram,
|
|
quoteTokenProgram: quoteTokenProgram,
|
|
user: user,
|
|
}, nil
|
|
}
|
|
|
|
discriminator := *(*[8]byte)(data[:8])
|
|
switch discriminator {
|
|
case meteoraInitializeLbPairDiscriminator,
|
|
meteoraInitializeCustomizablePermissionlessLbPairDiscriminator:
|
|
return resolveCommon(0, 2, 3, 8, 9, 9)
|
|
case meteoraInitializeLbPair2Discriminator,
|
|
meteoraInitializeCustomizablePermissionlessLbPair2Discriminator:
|
|
return resolveCommon(0, 2, 3, 8, 11, 12)
|
|
case meteoraInitializePermissionLbPairDiscriminator:
|
|
return resolveCommon(1, 3, 4, 8, 11, 12)
|
|
default:
|
|
return dlmmInitializeAccounts{}, fmt.Errorf("unsupported initialize discriminator")
|
|
}
|
|
}
|
|
|
|
func metaoradlmmInitializeParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
accounts, err := resolveDlmmInitializeAccounts(tx.rawTx, instruction.Data, instruction.Accounts)
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm initialize accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
|
|
entryContract := tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
|
|
|
findMintDecimals := func(mint solana.PublicKey) uint8 {
|
|
for _, acc := range tx.rawTx.Meta.PostTokenBalances {
|
|
if acc.MintAccount.Equals(mint) {
|
|
return uint8(acc.UITokenAmount.Decimals)
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
swap := Swap{
|
|
Program: SolProgramMeteoraDLMM,
|
|
Event: "create",
|
|
Pool: accounts.pool,
|
|
BaseMint: accounts.token0,
|
|
QuoteMint: accounts.token1,
|
|
BaseTokenProgram: accounts.baseTokenProgram,
|
|
QuoteTokenProgram: accounts.quoteTokenProgram,
|
|
Creator: tx.rawTx.accountList[0],
|
|
BaseMintDecimals: findMintDecimals(accounts.token0),
|
|
QuoteMintDecimals: findMintDecimals(accounts.token1),
|
|
User: accounts.user,
|
|
EntryContract: entryContract,
|
|
}
|
|
createEvent, nextOffset, found, err := dlmmLbPairCreateEventFromInnerInstructions(innerInstructions, instruction, offset)
|
|
if err != nil {
|
|
return nil, nextOffset, err
|
|
}
|
|
if found {
|
|
offset = nextOffset
|
|
if !createEvent.LbPair.IsZero() {
|
|
swap.Pool = createEvent.LbPair
|
|
}
|
|
if !createEvent.TokenX.IsZero() {
|
|
swap.BaseMint = createEvent.TokenX
|
|
}
|
|
if !createEvent.TokenY.IsZero() {
|
|
swap.QuoteMint = createEvent.TokenY
|
|
}
|
|
swap.BaseMintDecimals = findMintDecimals(swap.BaseMint)
|
|
swap.QuoteMintDecimals = findMintDecimals(swap.QuoteMint)
|
|
}
|
|
return []Swap{swap}, offset, nil
|
|
}
|
|
|
|
func metaoradlmmPositionCreateParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
result := tx.rawTx
|
|
|
|
entryContract := result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
|
if instruction.StackHeight != nil && *instruction.StackHeight > 2 {
|
|
for _, innerInstr := range innerInstructions.Instructions {
|
|
if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 {
|
|
entryContract = result.accountList[innerInstr.ProgramIDIndex]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
decode := instruction.Data
|
|
if len(decode) < 8 {
|
|
offset[1] += 1
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm create position instruction data too short, offset, %d, %d", offset[0], offset[1])
|
|
}
|
|
|
|
discriminator := *(*[8]byte)(decode[:8])
|
|
var (
|
|
lowerBinId int32
|
|
width int32
|
|
)
|
|
|
|
switch discriminator {
|
|
case meteoraDlmmInitializePositionDiscriminator, meteoraDlmmInitializePosition2Discriminator, meteoraDlmmInitializePositionPdaDiscriminator:
|
|
var args dlmmInitializePositionArgs
|
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm create position decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
lowerBinId = args.LowerBinId
|
|
width = args.Width
|
|
case meteoraDlmmInitializePositionByOperatorDiscriminator:
|
|
var args dlmmInitializePositionByOperatorArgs
|
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm create position by operator decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
lowerBinId = args.LowerBinId
|
|
width = args.Width
|
|
default:
|
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
|
}
|
|
|
|
pool, positionAccount, eventUser, err := dlmmPositionCreateInstructionAccounts(result, discriminator, instruction.Accounts)
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm create position accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
|
|
createEvent, nextOffset, found, err := dlmmPositionCreateEventFromInnerInstructions(innerInstructions, instruction, offset)
|
|
if err != nil {
|
|
return nil, nextOffset, err
|
|
}
|
|
if !found {
|
|
return nil, nextOffset, InstructionIgnoredError
|
|
}
|
|
offset = nextOffset
|
|
|
|
if !createEvent.LbPair.IsZero() {
|
|
pool = createEvent.LbPair
|
|
}
|
|
if !createEvent.Position.IsZero() {
|
|
positionAccount = createEvent.Position
|
|
}
|
|
if !createEvent.Owner.IsZero() {
|
|
eventUser = createEvent.Owner
|
|
}
|
|
|
|
swap := Swap{
|
|
Program: SolProgramMeteoraDLMM,
|
|
Event: "open",
|
|
Pool: pool,
|
|
User: eventUser,
|
|
EntryContract: entryContract,
|
|
StartBinId: lowerBinId,
|
|
EndBinId: dlmmPositionUpperBinId(lowerBinId, width),
|
|
PositionAccount: positionAccount,
|
|
}
|
|
|
|
return []Swap{swap}, offset, nil
|
|
}
|
|
|
|
func metaoradlmmPositionCloseParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
result := tx.rawTx
|
|
|
|
entryContract := result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
|
if instruction.StackHeight != nil && *instruction.StackHeight > 2 {
|
|
for _, innerInstr := range innerInstructions.Instructions {
|
|
if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 {
|
|
entryContract = result.accountList[innerInstr.ProgramIDIndex]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
decode := instruction.Data
|
|
if len(decode) < 8 {
|
|
offset[1] += 1
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm close position instruction data too short, offset, %d, %d", offset[0], offset[1])
|
|
}
|
|
|
|
discriminator := *(*[8]byte)(decode[:8])
|
|
pool, positionAccount, eventUser, err := dlmmPositionCloseInstructionAccounts(result, discriminator, instruction.Accounts)
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm close position accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
|
|
closeEvent, nextOffset, found, err := dlmmPositionCloseEventFromInnerInstructions(innerInstructions, instruction, offset)
|
|
if err != nil {
|
|
return nil, nextOffset, err
|
|
}
|
|
if !found {
|
|
if discriminator == meteoraDlmmClosePositionIfEmptyDiscriminator {
|
|
return nil, nextOffset, InstructionIgnoredError
|
|
}
|
|
return nil, nextOffset, fmt.Errorf("meteora dlmm close position event not found, offset, %d, %d", nextOffset[0], nextOffset[1])
|
|
}
|
|
offset = nextOffset
|
|
|
|
if !closeEvent.Position.IsZero() {
|
|
positionAccount = closeEvent.Position
|
|
}
|
|
if !closeEvent.Owner.IsZero() {
|
|
eventUser = closeEvent.Owner
|
|
}
|
|
|
|
swap := Swap{
|
|
Program: SolProgramMeteoraDLMM,
|
|
Event: "close",
|
|
Pool: pool,
|
|
User: eventUser,
|
|
EntryContract: entryContract,
|
|
PositionAccount: positionAccount,
|
|
}
|
|
|
|
return []Swap{swap}, offset, nil
|
|
}
|
|
|
|
func metaoradlmmSwapParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
result := tx.rawTx
|
|
|
|
entryContract := result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
|
if instruction.StackHeight != nil && *instruction.StackHeight > 2 {
|
|
for _, innerInstr := range innerInstructions.Instructions {
|
|
if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 {
|
|
entryContract = result.accountList[innerInstr.ProgramIDIndex]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
decode := instruction.Data
|
|
if len(decode) < 8 {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm swap instruction data too short, offset, %d, %d", offset[0], offset[1])
|
|
}
|
|
|
|
discriminator := *(*[8]byte)(decode[:8])
|
|
switch discriminator {
|
|
case meteoraDlmmSwapDiscriminator, meteoraDlmmSwap2Discriminator:
|
|
var args meteoraDlmmSwapArgs
|
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm swap decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
case meteoraDlmmSwapExactOutDiscriminator, meteoraDlmmSwapExactOut2Discriminator:
|
|
var args meteoraDlmmSwapExactOutArgs
|
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm swap_exact_out decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
case meteoraDlmmSwapWithPriceImpactDiscriminator, meteoraDlmmSwapWithPriceImpact2Discriminator:
|
|
var args meteoraDlmmSwapWithPriceImpactArgs
|
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm swap_with_price_impact decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
default:
|
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
|
}
|
|
|
|
accounts, err := resolveDlmmSwapAccounts(result, instruction.Accounts)
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm swap accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
pool := result.accountList[accounts.poolIdx]
|
|
reserveXIdx := accounts.reserveXIdx
|
|
reserveYIdx := accounts.reserveYIdx
|
|
userTokenInIdx := accounts.userTokenInIdx
|
|
userTokenOutIdx := accounts.userTokenOutIdx
|
|
tokenXMint := result.accountList[accounts.tokenXMintIdx]
|
|
tokenYMint := result.accountList[accounts.tokenYMintIdx]
|
|
userIdx := accounts.userIdx
|
|
tokenXProgram := result.accountList[accounts.tokenXProgramIdx]
|
|
tokenYProgram := result.accountList[accounts.tokenYProgramIdx]
|
|
|
|
var prefixLen = offset[1]
|
|
var swapEvent dlmmSwapEvent
|
|
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm swap get inner instructions error: %v, offset, %d, %d", err, offset[0], prefixLen)
|
|
}
|
|
for innerIndex, innerInstr := range inners {
|
|
if innerInstr.ProgramIDIndex != instruction.ProgramIDIndex {
|
|
continue
|
|
}
|
|
if len(innerInstr.Data) < 16 || !bytes.Equal(innerInstr.Data[:8], eventDiscriminator[:]) || !bytes.Equal(innerInstr.Data[8:16], meteoraDlmmSwapEventDiscriminator[:]) {
|
|
continue
|
|
}
|
|
|
|
if offset[1] == 0 {
|
|
offset[0] += 1
|
|
} else {
|
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
|
}
|
|
|
|
if err := agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&swapEvent); err != nil {
|
|
return nil, offset, fmt.Errorf("meteora dlmm swap event decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
break
|
|
}
|
|
|
|
baseMint, quoteMint, baseIsX := dlmmSelectBaseQuote(tokenXMint, tokenYMint)
|
|
baseTokenProgram := tokenXProgram
|
|
quoteTokenProgram := tokenYProgram
|
|
baseReserveIdx := reserveXIdx
|
|
quoteReserveIdx := reserveYIdx
|
|
if !baseIsX {
|
|
baseTokenProgram = tokenYProgram
|
|
quoteTokenProgram = tokenXProgram
|
|
baseReserveIdx = reserveYIdx
|
|
quoteReserveIdx = reserveXIdx
|
|
}
|
|
|
|
swapForY := swapEvent.SwapForY
|
|
inputIsX := swapForY
|
|
amountIn := decimal.NewFromUint64(swapEvent.AmountIn)
|
|
amountOut := decimal.NewFromUint64(swapEvent.AmountOut)
|
|
|
|
event := "buy"
|
|
if baseIsX == inputIsX {
|
|
event = "sell"
|
|
}
|
|
|
|
userBaseIdx := userTokenOutIdx
|
|
userQuoteIdx := userTokenInIdx
|
|
if baseIsX == inputIsX {
|
|
userBaseIdx = userTokenInIdx
|
|
userQuoteIdx = userTokenOutIdx
|
|
}
|
|
|
|
baseAmount := amountOut
|
|
quoteAmount := amountIn
|
|
if baseIsX {
|
|
if swapForY {
|
|
baseAmount = amountIn
|
|
quoteAmount = amountOut
|
|
}
|
|
} else {
|
|
if !swapForY {
|
|
baseAmount = amountIn
|
|
quoteAmount = amountOut
|
|
}
|
|
}
|
|
|
|
eventUser := result.accountList[userIdx]
|
|
if !swapEvent.From.IsZero() {
|
|
eventUser = swapEvent.From
|
|
}
|
|
if !eventUser.IsOnCurve() && (entryContract.Equals(okxDexRoutersV2) || entryContract.Equals(okxAggregatorV2)) {
|
|
userBaseAmount, ataIndex := tokenBalanceChange(result, 0, baseTokenProgram, baseMint)
|
|
if !userBaseAmount.IsZero() {
|
|
eventUser = result.accountList[0]
|
|
userIdx = 0
|
|
if ataIndex > 0 {
|
|
userBaseIdx = ataIndex
|
|
}
|
|
}
|
|
}
|
|
|
|
baseDecimals, ok := dlmmTokenDecimals(result, baseReserveIdx)
|
|
if !ok {
|
|
baseDecimals, _ = dlmmTokenDecimals(result, userBaseIdx)
|
|
}
|
|
quoteDecimals, ok := dlmmTokenDecimals(result, quoteReserveIdx)
|
|
if !ok {
|
|
quoteDecimals, _ = dlmmTokenDecimals(result, userQuoteIdx)
|
|
}
|
|
|
|
if _, exists := tx.Token[baseMint]; !exists && !baseMint.Equals(wSolMint) {
|
|
tx.Token[baseMint] = TokenMeta{
|
|
Mint: baseMint,
|
|
Decimals: baseDecimals,
|
|
TokenProgram: baseTokenProgram,
|
|
}
|
|
}
|
|
if _, exists := tx.Token[quoteMint]; !exists && !quoteMint.Equals(wSolMint) {
|
|
tx.Token[quoteMint] = TokenMeta{
|
|
Mint: quoteMint,
|
|
Decimals: quoteDecimals,
|
|
TokenProgram: quoteTokenProgram,
|
|
}
|
|
}
|
|
|
|
baseReserve := getAccountBalanceAfterTx(result, baseReserveIdx)
|
|
quoteReserve := getAccountBalanceAfterTx(result, quoteReserveIdx)
|
|
userBase := getAccountBalanceAfterTx(result, userBaseIdx)
|
|
userQuote := GetTokenBalanceAfterTx(result, userIdx, quoteTokenProgram, quoteMint)
|
|
if quoteMint.Equals(wSolMint) {
|
|
if solAmount, err := GetSolAfterTx(result, userIdx); err == nil {
|
|
userQuote = userQuote.Add(decimal.NewFromUint64(solAmount))
|
|
}
|
|
}
|
|
feeAmount, feeSide, feeMint, feeTokenProgram, feeDecimals := dlmmSwapFeeInfo(
|
|
baseIsX,
|
|
swapForY,
|
|
swapEvent.Fee,
|
|
baseMint,
|
|
quoteMint,
|
|
baseTokenProgram,
|
|
quoteTokenProgram,
|
|
baseDecimals,
|
|
quoteDecimals,
|
|
)
|
|
lpFeeAmount := dlmmSwapLpFeeAmount(swapEvent.Fee, swapEvent.ProtocolFee, swapEvent.HostFee)
|
|
|
|
swap := Swap{
|
|
Program: SolProgramMeteoraDLMM,
|
|
Event: event,
|
|
Pool: pool,
|
|
BaseMint: baseMint,
|
|
QuoteMint: quoteMint,
|
|
BaseTokenProgram: baseTokenProgram,
|
|
QuoteTokenProgram: quoteTokenProgram,
|
|
Creator: solana.PublicKey{},
|
|
BaseMintDecimals: baseDecimals,
|
|
QuoteMintDecimals: quoteDecimals,
|
|
User: eventUser,
|
|
BaseAmount: baseAmount,
|
|
QuoteAmount: quoteAmount,
|
|
FeeAmount: feeAmount,
|
|
LpFeeAmount: lpFeeAmount,
|
|
FeeSide: feeSide,
|
|
FeeMint: feeMint,
|
|
FeeTokenProgram: feeTokenProgram,
|
|
FeeMintDecimals: feeDecimals,
|
|
BaseReserve: baseReserve,
|
|
QuoteReserve: quoteReserve,
|
|
UserBaseBalance: userBase,
|
|
UserQuoteBalance: userQuote,
|
|
EntryContract: entryContract,
|
|
ActiveBinId: swapEvent.EndBinId,
|
|
StartBinId: swapEvent.StartBinId,
|
|
EndBinId: swapEvent.EndBinId,
|
|
}
|
|
|
|
return []Swap{swap}, offset, nil
|
|
}
|
|
|
|
func metaoradlmmSwap2Parser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
return metaoradlmmSwapParser(tx, instruction, innerInstructions, offset)
|
|
}
|
|
|
|
func dlmmSwapFeeInfo(
|
|
baseIsX bool,
|
|
swapForY bool,
|
|
fee uint64,
|
|
baseMint solana.PublicKey,
|
|
quoteMint solana.PublicKey,
|
|
baseTokenProgram solana.PublicKey,
|
|
quoteTokenProgram solana.PublicKey,
|
|
baseDecimals uint8,
|
|
quoteDecimals uint8,
|
|
) (decimal.Decimal, string, solana.PublicKey, solana.PublicKey, uint8) {
|
|
feeAmount := decimal.NewFromUint64(fee)
|
|
if baseIsX == swapForY {
|
|
return feeAmount, "base", baseMint, baseTokenProgram, baseDecimals
|
|
}
|
|
return feeAmount, "quote", quoteMint, quoteTokenProgram, quoteDecimals
|
|
}
|
|
|
|
func dlmmSwapLpFeeAmount(fee, protocolFee, hostFee uint64) decimal.Decimal {
|
|
total := decimal.NewFromUint64(fee)
|
|
protocol := decimal.NewFromUint64(protocolFee)
|
|
host := decimal.NewFromUint64(hostFee)
|
|
lpFee := total.Sub(protocol).Sub(host)
|
|
if lpFee.IsNegative() {
|
|
return decimal.Zero
|
|
}
|
|
return lpFee
|
|
}
|
|
|
|
func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
result := tx.rawTx
|
|
|
|
entryContract := result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
|
if instruction.StackHeight != nil && *instruction.StackHeight > 2 {
|
|
for _, innerInstr := range innerInstructions.Instructions {
|
|
if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 {
|
|
entryContract = result.accountList[innerInstr.ProgramIDIndex]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
decode := instruction.Data
|
|
if len(decode) < 8 {
|
|
offset[1] += 1
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity instruction data too short, offset, %d, %d", offset[0], offset[1])
|
|
}
|
|
|
|
discriminator := *(*[8]byte)(decode[:8])
|
|
var (
|
|
amountX uint64
|
|
amountY uint64
|
|
binDist []dlmmBinLiquidityDistribution
|
|
weightDist []dlmmBinLiquidityDistributionByWeight
|
|
startBinId int32
|
|
endBinId int32
|
|
oneSide bool
|
|
)
|
|
|
|
switch discriminator {
|
|
case meteoraDlmmAddLiquidityDiscriminator:
|
|
var args dlmmAddLiquidityArgs
|
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
amountX = args.LiquidityParameter.AmountX
|
|
amountY = args.LiquidityParameter.AmountY
|
|
binDist = args.LiquidityParameter.BinLiquidityDist
|
|
startBinId, endBinId = dlmmMinMaxBinIdFromDistribution(binDist)
|
|
case meteoraDlmmAddLiquidity2Discriminator:
|
|
var args dlmmAddLiquidity2Args
|
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity2 decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
amountX = args.LiquidityParameter.AmountX
|
|
amountY = args.LiquidityParameter.AmountY
|
|
binDist = args.LiquidityParameter.BinLiquidityDist
|
|
startBinId, endBinId = dlmmMinMaxBinIdFromDistribution(binDist)
|
|
case meteoraDlmmAddLiquidityByStrategyDiscriminator:
|
|
var args dlmmAddLiquidityByStrategyArgs
|
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity by strategy decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
amountX = args.LiquidityParameter.AmountX
|
|
amountY = args.LiquidityParameter.AmountY
|
|
startBinId = args.LiquidityParameter.StrategyParameters.MinBinId
|
|
endBinId = args.LiquidityParameter.StrategyParameters.MaxBinId
|
|
case meteoraDlmmAddLiquidityByStrategy2Discriminator:
|
|
var args dlmmAddLiquidityByStrategy2Args
|
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity by strategy2 decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
amountX = args.LiquidityParameter.AmountX
|
|
amountY = args.LiquidityParameter.AmountY
|
|
startBinId = args.LiquidityParameter.StrategyParameters.MinBinId
|
|
endBinId = args.LiquidityParameter.StrategyParameters.MaxBinId
|
|
case meteoraDlmmAddLiquidityByWeightDiscriminator:
|
|
var args dlmmAddLiquidityByWeightArgs
|
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity by weight decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
amountX = args.LiquidityParameter.AmountX
|
|
amountY = args.LiquidityParameter.AmountY
|
|
weightDist = args.LiquidityParameter.BinLiquidityDist
|
|
startBinId, endBinId = dlmmMinMaxBinIdFromWeightDistribution(weightDist)
|
|
case meteoraDlmmAddLiquidityOneSideDiscriminator:
|
|
var args dlmmAddLiquidityOneSideArgs
|
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity one side decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
weightDist = args.LiquidityParameter.BinLiquidityDist
|
|
startBinId, endBinId = dlmmMinMaxBinIdFromWeightDistribution(weightDist)
|
|
oneSide = true
|
|
case meteoraDlmmAddLiquidityOneSidePreciseDiscriminator:
|
|
var args dlmmAddLiquidityOneSidePreciseArgs
|
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity one side precise decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
startBinId, endBinId = dlmmMinMaxBinIDFromCompressedDeposits(args.Parameter.Bins)
|
|
oneSide = true
|
|
case meteoraDlmmAddLiquidityOneSidePrecise2Discriminator:
|
|
var args dlmmAddLiquidityOneSidePrecise2Args
|
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity one side precise2 decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
startBinId, endBinId = dlmmMinMaxBinIDFromCompressedDeposits(args.LiquidityParameter.Bins)
|
|
oneSide = true
|
|
case meteoraDlmmAddLiquidityByStrategyOneSideDiscriminator:
|
|
var args dlmmAddLiquidityByStrategyOneSideArgs
|
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity by strategy one side decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
startBinId = args.LiquidityParameter.StrategyParameters.MinBinId
|
|
endBinId = args.LiquidityParameter.StrategyParameters.MaxBinId
|
|
oneSide = true
|
|
default:
|
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
|
}
|
|
|
|
addEvent, nextOffset, err := dlmmAddLiquidityEventFromInnerInstructions(innerInstructions, instruction, offset)
|
|
if err != nil {
|
|
return nil, nextOffset, err
|
|
}
|
|
offset = nextOffset
|
|
amountX = addEvent.Amounts[0]
|
|
amountY = addEvent.Amounts[1]
|
|
|
|
if oneSide {
|
|
swaps, err := dlmmBuildOneSideAddSwap(tx, instruction, addEvent, startBinId, endBinId, entryContract)
|
|
if err != nil {
|
|
return nil, offset, err
|
|
}
|
|
return swaps, offset, nil
|
|
}
|
|
|
|
accounts, err := resolveDlmmLiquidityAccounts(result, instruction.Accounts)
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
|
|
pool := result.accountList[accounts.poolIdx]
|
|
tokenXMint := result.accountList[accounts.tokenXMintIdx]
|
|
tokenYMint := result.accountList[accounts.tokenYMintIdx]
|
|
tokenXProgram := result.accountList[accounts.tokenXProgramIdx]
|
|
tokenYProgram := result.accountList[accounts.tokenYProgramIdx]
|
|
|
|
baseMint, quoteMint, baseIsX := dlmmSelectBaseQuote(tokenXMint, tokenYMint)
|
|
baseTokenProgram := tokenXProgram
|
|
quoteTokenProgram := tokenYProgram
|
|
baseReserveIdx := accounts.reserveXIdx
|
|
quoteReserveIdx := accounts.reserveYIdx
|
|
userBaseIdx := accounts.userTokenXIdx
|
|
userQuoteIdx := accounts.userTokenYIdx
|
|
if !baseIsX {
|
|
baseTokenProgram = tokenYProgram
|
|
quoteTokenProgram = tokenXProgram
|
|
baseReserveIdx = accounts.reserveYIdx
|
|
quoteReserveIdx = accounts.reserveXIdx
|
|
userBaseIdx = accounts.userTokenYIdx
|
|
userQuoteIdx = accounts.userTokenXIdx
|
|
}
|
|
|
|
amountXDec := decimal.NewFromUint64(amountX)
|
|
amountYDec := decimal.NewFromUint64(amountY)
|
|
baseAmount := amountXDec
|
|
quoteAmount := amountYDec
|
|
if !baseIsX {
|
|
baseAmount = amountYDec
|
|
quoteAmount = amountXDec
|
|
}
|
|
eventUser := result.accountList[accounts.userIdx]
|
|
if !addEvent.From.IsZero() {
|
|
eventUser = addEvent.From
|
|
}
|
|
|
|
baseDecimals, ok := dlmmTokenDecimals(result, baseReserveIdx)
|
|
if !ok {
|
|
baseDecimals, _ = dlmmTokenDecimals(result, userBaseIdx)
|
|
}
|
|
quoteDecimals, ok := dlmmTokenDecimals(result, quoteReserveIdx)
|
|
if !ok {
|
|
quoteDecimals, _ = dlmmTokenDecimals(result, userQuoteIdx)
|
|
}
|
|
|
|
if _, exists := tx.Token[baseMint]; !exists && !baseMint.Equals(wSolMint) {
|
|
tx.Token[baseMint] = TokenMeta{
|
|
Mint: baseMint,
|
|
Decimals: baseDecimals,
|
|
TokenProgram: baseTokenProgram,
|
|
}
|
|
}
|
|
if _, exists := tx.Token[quoteMint]; !exists && !quoteMint.Equals(wSolMint) {
|
|
tx.Token[quoteMint] = TokenMeta{
|
|
Mint: quoteMint,
|
|
Decimals: quoteDecimals,
|
|
TokenProgram: quoteTokenProgram,
|
|
}
|
|
}
|
|
|
|
baseReserve := getAccountBalanceAfterTx(result, baseReserveIdx)
|
|
quoteReserve := getAccountBalanceAfterTx(result, quoteReserveIdx)
|
|
userBase := getAccountBalanceAfterTx(result, userBaseIdx)
|
|
userQuote := getAccountBalanceAfterTx(result, userQuoteIdx)
|
|
if quoteMint.Equals(wSolMint) {
|
|
if solAmount, err := GetSolAfterTx(result, accounts.userIdx); err == nil {
|
|
userQuote = userQuote.Add(decimal.NewFromUint64(solAmount))
|
|
}
|
|
}
|
|
|
|
swap := Swap{
|
|
Program: SolProgramMeteoraDLMM,
|
|
Event: "add",
|
|
Pool: pool,
|
|
BaseMint: baseMint,
|
|
QuoteMint: quoteMint,
|
|
BaseTokenProgram: baseTokenProgram,
|
|
QuoteTokenProgram: quoteTokenProgram,
|
|
BaseMintDecimals: baseDecimals,
|
|
QuoteMintDecimals: quoteDecimals,
|
|
User: eventUser,
|
|
BaseAmount: baseAmount,
|
|
QuoteAmount: quoteAmount,
|
|
BaseReserve: baseReserve,
|
|
QuoteReserve: quoteReserve,
|
|
UserBaseBalance: userBase,
|
|
UserQuoteBalance: userQuote,
|
|
EntryContract: entryContract,
|
|
ActiveBinId: addEvent.ActiveBinId,
|
|
StartBinId: startBinId,
|
|
EndBinId: endBinId,
|
|
PositionAccount: result.accountList[accounts.positionIdx],
|
|
}
|
|
|
|
return []Swap{swap}, offset, nil
|
|
}
|
|
|
|
func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
result := tx.rawTx
|
|
|
|
entryContract := result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
|
if instruction.StackHeight != nil && *instruction.StackHeight > 2 {
|
|
for _, innerInstr := range innerInstructions.Instructions {
|
|
if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 {
|
|
entryContract = result.accountList[innerInstr.ProgramIDIndex]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
decode := instruction.Data
|
|
if len(decode) < 8 {
|
|
offset[1] += 1
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity instruction data too short, offset, %d, %d", offset[0], offset[1])
|
|
}
|
|
|
|
discriminator := *(*[8]byte)(decode[:8])
|
|
var (
|
|
startBinId int32
|
|
endBinId int32
|
|
removeBp int32
|
|
)
|
|
|
|
switch discriminator {
|
|
case meteoraDlmmRemoveAllLiquidityDiscriminator:
|
|
removeBp = 10000
|
|
case meteoraDlmmRemoveLiquidityDiscriminator:
|
|
var args dlmmRemoveLiquidityArgs
|
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
startBinId, endBinId = dlmmMinMaxBinIdFromReduction(args.BinLiquidityRemoval)
|
|
removeBp = dlmmCommonRemoveBp(args.BinLiquidityRemoval)
|
|
case meteoraDlmmRemoveLiquidity2Discriminator:
|
|
var args dlmmRemoveLiquidity2Args
|
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity2 decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
startBinId, endBinId = dlmmMinMaxBinIdFromReduction(args.BinLiquidityRemoval)
|
|
removeBp = dlmmCommonRemoveBp(args.BinLiquidityRemoval)
|
|
case meteoraDlmmRemoveLiquidityByRangeDiscriminator:
|
|
var args dlmmRemoveLiquidityByRangeArgs
|
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity by range decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
startBinId = args.FromBinId
|
|
endBinId = args.ToBinId
|
|
removeBp = int32(args.BpsToRemove)
|
|
case meteoraDlmmRemoveLiquidityByRange2Discriminator:
|
|
var args dlmmRemoveLiquidityByRange2Args
|
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity by range2 decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
startBinId = args.FromBinId
|
|
endBinId = args.ToBinId
|
|
removeBp = int32(args.BpsToRemove)
|
|
default:
|
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
|
}
|
|
|
|
accounts, err := resolveDlmmLiquidityAccounts(result, instruction.Accounts)
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
|
|
removeEvent, nextOffset, err := dlmmRemoveLiquidityEventFromInnerInstructions(innerInstructions, instruction, offset)
|
|
if err != nil {
|
|
return nil, nextOffset, err
|
|
}
|
|
offset = nextOffset
|
|
|
|
pool := result.accountList[accounts.poolIdx]
|
|
tokenXMint := result.accountList[accounts.tokenXMintIdx]
|
|
tokenYMint := result.accountList[accounts.tokenYMintIdx]
|
|
tokenXProgram := result.accountList[accounts.tokenXProgramIdx]
|
|
tokenYProgram := result.accountList[accounts.tokenYProgramIdx]
|
|
|
|
baseMint, quoteMint, baseIsX := dlmmSelectBaseQuote(tokenXMint, tokenYMint)
|
|
baseTokenProgram := tokenXProgram
|
|
quoteTokenProgram := tokenYProgram
|
|
baseReserveIdx := accounts.reserveXIdx
|
|
quoteReserveIdx := accounts.reserveYIdx
|
|
userBaseIdx := accounts.userTokenXIdx
|
|
userQuoteIdx := accounts.userTokenYIdx
|
|
if !baseIsX {
|
|
baseTokenProgram = tokenYProgram
|
|
quoteTokenProgram = tokenXProgram
|
|
baseReserveIdx = accounts.reserveYIdx
|
|
quoteReserveIdx = accounts.reserveXIdx
|
|
userBaseIdx = accounts.userTokenYIdx
|
|
userQuoteIdx = accounts.userTokenXIdx
|
|
}
|
|
|
|
amountXDec := decimal.NewFromUint64(removeEvent.Amounts[0])
|
|
amountYDec := decimal.NewFromUint64(removeEvent.Amounts[1])
|
|
baseAmount := amountXDec
|
|
quoteAmount := amountYDec
|
|
if !baseIsX {
|
|
baseAmount = amountYDec
|
|
quoteAmount = amountXDec
|
|
}
|
|
eventUser := result.accountList[accounts.userIdx]
|
|
if !removeEvent.From.IsZero() {
|
|
eventUser = removeEvent.From
|
|
}
|
|
|
|
baseDecimals, ok := dlmmTokenDecimals(result, baseReserveIdx)
|
|
if !ok {
|
|
baseDecimals, _ = dlmmTokenDecimals(result, userBaseIdx)
|
|
}
|
|
quoteDecimals, ok := dlmmTokenDecimals(result, quoteReserveIdx)
|
|
if !ok {
|
|
quoteDecimals, _ = dlmmTokenDecimals(result, userQuoteIdx)
|
|
}
|
|
|
|
if _, exists := tx.Token[baseMint]; !exists && !baseMint.Equals(wSolMint) {
|
|
tx.Token[baseMint] = TokenMeta{
|
|
Mint: baseMint,
|
|
Decimals: baseDecimals,
|
|
TokenProgram: baseTokenProgram,
|
|
}
|
|
}
|
|
if _, exists := tx.Token[quoteMint]; !exists && !quoteMint.Equals(wSolMint) {
|
|
tx.Token[quoteMint] = TokenMeta{
|
|
Mint: quoteMint,
|
|
Decimals: quoteDecimals,
|
|
TokenProgram: quoteTokenProgram,
|
|
}
|
|
}
|
|
|
|
baseReserve := getAccountBalanceAfterTx(result, baseReserveIdx)
|
|
quoteReserve := getAccountBalanceAfterTx(result, quoteReserveIdx)
|
|
userBase := getAccountBalanceAfterTx(result, userBaseIdx)
|
|
userQuote := getAccountBalanceAfterTx(result, userQuoteIdx)
|
|
if quoteMint.Equals(wSolMint) {
|
|
if solAmount, err := GetSolAfterTx(result, accounts.userIdx); err == nil {
|
|
userQuote = userQuote.Add(decimal.NewFromUint64(solAmount))
|
|
}
|
|
}
|
|
|
|
swap := Swap{
|
|
Program: SolProgramMeteoraDLMM,
|
|
Event: "remove",
|
|
Pool: pool,
|
|
BaseMint: baseMint,
|
|
QuoteMint: quoteMint,
|
|
BaseTokenProgram: baseTokenProgram,
|
|
QuoteTokenProgram: quoteTokenProgram,
|
|
BaseMintDecimals: baseDecimals,
|
|
QuoteMintDecimals: quoteDecimals,
|
|
User: eventUser,
|
|
BaseAmount: baseAmount,
|
|
QuoteAmount: quoteAmount,
|
|
BaseReserve: baseReserve,
|
|
QuoteReserve: quoteReserve,
|
|
UserBaseBalance: userBase,
|
|
UserQuoteBalance: userQuote,
|
|
EntryContract: entryContract,
|
|
ActiveBinId: removeEvent.ActiveBinId,
|
|
StartBinId: startBinId,
|
|
EndBinId: endBinId,
|
|
RemoveBp: removeBp,
|
|
PositionAccount: result.accountList[accounts.positionIdx],
|
|
}
|
|
|
|
return []Swap{swap}, offset, nil
|
|
}
|
|
|
|
func metaoradlmmClaimFeeParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
result := tx.rawTx
|
|
|
|
entryContract := result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
|
if instruction.StackHeight != nil && *instruction.StackHeight > 2 {
|
|
for _, innerInstr := range innerInstructions.Instructions {
|
|
if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 {
|
|
entryContract = result.accountList[innerInstr.ProgramIDIndex]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
accounts, err := resolveDlmmClaimFeeAccounts(result, instruction.Data, instruction.Accounts)
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm claim fee accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
|
|
claimEvent, nextOffset, err := dlmmClaimFeeEventFromInnerInstructions(innerInstructions, instruction, offset)
|
|
if err != nil {
|
|
return nil, nextOffset, err
|
|
}
|
|
offset = nextOffset
|
|
if claimEvent.FeeX == 0 && claimEvent.FeeY == 0 {
|
|
return nil, offset, InstructionIgnoredError
|
|
}
|
|
|
|
pool := result.accountList[accounts.poolIdx]
|
|
tokenXMint := result.accountList[accounts.tokenXMintIdx]
|
|
tokenYMint := result.accountList[accounts.tokenYMintIdx]
|
|
tokenXProgram := result.accountList[accounts.tokenXProgramIdx]
|
|
tokenYProgram := result.accountList[accounts.tokenYProgramIdx]
|
|
|
|
baseMint, quoteMint, baseIsX := dlmmSelectBaseQuote(tokenXMint, tokenYMint)
|
|
baseTokenProgram := tokenXProgram
|
|
quoteTokenProgram := tokenYProgram
|
|
baseReserveIdx := accounts.reserveXIdx
|
|
quoteReserveIdx := accounts.reserveYIdx
|
|
userBaseIdx := accounts.userTokenXIdx
|
|
userQuoteIdx := accounts.userTokenYIdx
|
|
baseAmount := decimal.NewFromUint64(claimEvent.FeeX)
|
|
quoteAmount := decimal.NewFromUint64(claimEvent.FeeY)
|
|
if !baseIsX {
|
|
baseTokenProgram = tokenYProgram
|
|
quoteTokenProgram = tokenXProgram
|
|
baseReserveIdx = accounts.reserveYIdx
|
|
quoteReserveIdx = accounts.reserveXIdx
|
|
userBaseIdx = accounts.userTokenYIdx
|
|
userQuoteIdx = accounts.userTokenXIdx
|
|
baseAmount = decimal.NewFromUint64(claimEvent.FeeY)
|
|
quoteAmount = decimal.NewFromUint64(claimEvent.FeeX)
|
|
}
|
|
|
|
eventUser := result.accountList[accounts.userIdx]
|
|
if !claimEvent.Owner.IsZero() {
|
|
eventUser = claimEvent.Owner
|
|
}
|
|
|
|
baseDecimals, ok := dlmmTokenDecimals(result, baseReserveIdx)
|
|
if !ok {
|
|
baseDecimals, _ = dlmmTokenDecimals(result, userBaseIdx)
|
|
}
|
|
quoteDecimals, ok := dlmmTokenDecimals(result, quoteReserveIdx)
|
|
if !ok {
|
|
quoteDecimals, _ = dlmmTokenDecimals(result, userQuoteIdx)
|
|
}
|
|
|
|
if _, exists := tx.Token[baseMint]; !exists && !baseMint.Equals(wSolMint) {
|
|
tx.Token[baseMint] = TokenMeta{
|
|
Mint: baseMint,
|
|
Decimals: baseDecimals,
|
|
TokenProgram: baseTokenProgram,
|
|
}
|
|
}
|
|
if _, exists := tx.Token[quoteMint]; !exists && !quoteMint.Equals(wSolMint) {
|
|
tx.Token[quoteMint] = TokenMeta{
|
|
Mint: quoteMint,
|
|
Decimals: quoteDecimals,
|
|
TokenProgram: quoteTokenProgram,
|
|
}
|
|
}
|
|
|
|
baseReserve := getAccountBalanceAfterTx(result, baseReserveIdx)
|
|
quoteReserve := getAccountBalanceAfterTx(result, quoteReserveIdx)
|
|
userBase := getAccountBalanceAfterTx(result, userBaseIdx)
|
|
userQuote := getAccountBalanceAfterTx(result, userQuoteIdx)
|
|
if quoteMint.Equals(wSolMint) {
|
|
if solAmount, err := GetSolAfterTx(result, accounts.userIdx); err == nil {
|
|
userQuote = userQuote.Add(decimal.NewFromUint64(solAmount))
|
|
}
|
|
}
|
|
|
|
swap := Swap{
|
|
Program: SolProgramMeteoraDLMM,
|
|
Event: "claim_fee",
|
|
Pool: pool,
|
|
BaseMint: baseMint,
|
|
QuoteMint: quoteMint,
|
|
BaseTokenProgram: baseTokenProgram,
|
|
QuoteTokenProgram: quoteTokenProgram,
|
|
BaseMintDecimals: baseDecimals,
|
|
QuoteMintDecimals: quoteDecimals,
|
|
User: eventUser,
|
|
BaseAmount: baseAmount,
|
|
QuoteAmount: quoteAmount,
|
|
BaseReserve: baseReserve,
|
|
QuoteReserve: quoteReserve,
|
|
UserBaseBalance: userBase,
|
|
UserQuoteBalance: userQuote,
|
|
EntryContract: entryContract,
|
|
PositionAccount: result.accountList[accounts.positionIdx],
|
|
}
|
|
if claimEvent.HasActiveBin {
|
|
swap.ActiveBinId = claimEvent.ActiveBinId
|
|
swap.StartBinId = claimEvent.ActiveBinId
|
|
swap.EndBinId = claimEvent.ActiveBinId
|
|
}
|
|
|
|
return []Swap{swap}, offset, nil
|
|
}
|
|
|
|
func metaoradlmmRebalanceLiquidityParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
result := tx.rawTx
|
|
|
|
entryContract := result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
|
if instruction.StackHeight != nil && *instruction.StackHeight > 2 {
|
|
for _, innerInstr := range innerInstructions.Instructions {
|
|
if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 {
|
|
entryContract = result.accountList[innerInstr.ProgramIDIndex]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
accounts, err := resolveDlmmRebalanceAccounts(result, instruction.Accounts)
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm rebalance liquidity accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
|
|
event, nextOffset, err := dlmmRebalancingEventFromInnerInstructions(innerInstructions, instruction, offset)
|
|
if err != nil {
|
|
return nil, nextOffset, err
|
|
}
|
|
offset = nextOffset
|
|
|
|
pool := result.accountList[accounts.poolIdx]
|
|
tokenXMint := result.accountList[accounts.tokenXMintIdx]
|
|
tokenYMint := result.accountList[accounts.tokenYMintIdx]
|
|
tokenXProgram := result.accountList[accounts.tokenXProgramIdx]
|
|
tokenYProgram := result.accountList[accounts.tokenYProgramIdx]
|
|
|
|
baseMint, quoteMint, baseIsX := dlmmSelectBaseQuote(tokenXMint, tokenYMint)
|
|
baseTokenProgram := tokenXProgram
|
|
quoteTokenProgram := tokenYProgram
|
|
baseReserveIdx := accounts.reserveXIdx
|
|
quoteReserveIdx := accounts.reserveYIdx
|
|
userBaseIdx := accounts.userTokenXIdx
|
|
userQuoteIdx := accounts.userTokenYIdx
|
|
withdrawBase := event.XWithdrawnAmount
|
|
withdrawQuote := event.YWithdrawnAmount
|
|
addBase := event.XAddedAmount
|
|
addQuote := event.YAddedAmount
|
|
if !baseIsX {
|
|
baseTokenProgram = tokenYProgram
|
|
quoteTokenProgram = tokenXProgram
|
|
baseReserveIdx = accounts.reserveYIdx
|
|
quoteReserveIdx = accounts.reserveXIdx
|
|
userBaseIdx = accounts.userTokenYIdx
|
|
userQuoteIdx = accounts.userTokenXIdx
|
|
withdrawBase = event.YWithdrawnAmount
|
|
withdrawQuote = event.XWithdrawnAmount
|
|
addBase = event.YAddedAmount
|
|
addQuote = event.XAddedAmount
|
|
}
|
|
|
|
eventUser := result.accountList[accounts.userIdx]
|
|
if !event.Owner.IsZero() {
|
|
eventUser = event.Owner
|
|
}
|
|
|
|
baseDecimals, ok := dlmmTokenDecimals(result, baseReserveIdx)
|
|
if !ok {
|
|
baseDecimals, _ = dlmmTokenDecimals(result, userBaseIdx)
|
|
}
|
|
quoteDecimals, ok := dlmmTokenDecimals(result, quoteReserveIdx)
|
|
if !ok {
|
|
quoteDecimals, _ = dlmmTokenDecimals(result, userQuoteIdx)
|
|
}
|
|
|
|
if _, exists := tx.Token[baseMint]; !exists && !baseMint.Equals(wSolMint) {
|
|
tx.Token[baseMint] = TokenMeta{
|
|
Mint: baseMint,
|
|
Decimals: baseDecimals,
|
|
TokenProgram: baseTokenProgram,
|
|
}
|
|
}
|
|
if _, exists := tx.Token[quoteMint]; !exists && !quoteMint.Equals(wSolMint) {
|
|
tx.Token[quoteMint] = TokenMeta{
|
|
Mint: quoteMint,
|
|
Decimals: quoteDecimals,
|
|
TokenProgram: quoteTokenProgram,
|
|
}
|
|
}
|
|
|
|
baseReserve := getAccountBalanceAfterTx(result, baseReserveIdx)
|
|
quoteReserve := getAccountBalanceAfterTx(result, quoteReserveIdx)
|
|
userBase := getAccountBalanceAfterTx(result, userBaseIdx)
|
|
userQuote := getAccountBalanceAfterTx(result, userQuoteIdx)
|
|
if quoteMint.Equals(wSolMint) {
|
|
if solAmount, err := GetSolAfterTx(result, accounts.userIdx); err == nil {
|
|
userQuote = userQuote.Add(decimal.NewFromUint64(solAmount))
|
|
}
|
|
}
|
|
|
|
var swaps []Swap
|
|
if withdrawBase > 0 || withdrawQuote > 0 {
|
|
swaps = append(swaps, Swap{
|
|
Program: SolProgramMeteoraDLMM,
|
|
Event: "remove",
|
|
Pool: pool,
|
|
BaseMint: baseMint,
|
|
QuoteMint: quoteMint,
|
|
BaseTokenProgram: baseTokenProgram,
|
|
QuoteTokenProgram: quoteTokenProgram,
|
|
BaseMintDecimals: baseDecimals,
|
|
QuoteMintDecimals: quoteDecimals,
|
|
User: eventUser,
|
|
BaseAmount: decimal.NewFromUint64(withdrawBase),
|
|
QuoteAmount: decimal.NewFromUint64(withdrawQuote),
|
|
BaseReserve: baseReserve,
|
|
QuoteReserve: quoteReserve,
|
|
UserBaseBalance: userBase,
|
|
UserQuoteBalance: userQuote,
|
|
EntryContract: entryContract,
|
|
ActiveBinId: event.ActiveBinId,
|
|
StartBinId: event.OldMinBinId,
|
|
EndBinId: event.OldMaxBinId,
|
|
PositionAccount: result.accountList[accounts.positionIdx],
|
|
})
|
|
}
|
|
if addBase > 0 || addQuote > 0 {
|
|
swaps = append(swaps, Swap{
|
|
Program: SolProgramMeteoraDLMM,
|
|
Event: "add",
|
|
Pool: pool,
|
|
BaseMint: baseMint,
|
|
QuoteMint: quoteMint,
|
|
BaseTokenProgram: baseTokenProgram,
|
|
QuoteTokenProgram: quoteTokenProgram,
|
|
BaseMintDecimals: baseDecimals,
|
|
QuoteMintDecimals: quoteDecimals,
|
|
User: eventUser,
|
|
BaseAmount: decimal.NewFromUint64(addBase),
|
|
QuoteAmount: decimal.NewFromUint64(addQuote),
|
|
BaseReserve: baseReserve,
|
|
QuoteReserve: quoteReserve,
|
|
UserBaseBalance: userBase,
|
|
UserQuoteBalance: userQuote,
|
|
EntryContract: entryContract,
|
|
ActiveBinId: event.ActiveBinId,
|
|
StartBinId: event.NewMinBinId,
|
|
EndBinId: event.NewMaxBinId,
|
|
PositionAccount: result.accountList[accounts.positionIdx],
|
|
})
|
|
}
|
|
if len(swaps) == 0 {
|
|
return nil, offset, InstructionIgnoredError
|
|
}
|
|
|
|
return swaps, offset, nil
|
|
}
|
|
|
|
func dlmmSelectBaseQuote(tokenX, tokenY solana.PublicKey) (baseMint solana.PublicKey, quoteMint solana.PublicKey, baseIsX bool) {
|
|
priority := []solana.PublicKey{wSolMint, usdcMint, usd1Mint}
|
|
for _, mint := range priority {
|
|
if tokenX.Equals(mint) {
|
|
return tokenY, tokenX, false
|
|
}
|
|
if tokenY.Equals(mint) {
|
|
return tokenX, tokenY, true
|
|
}
|
|
}
|
|
return tokenX, tokenY, true
|
|
}
|
|
|
|
func dlmmAddLiquidityEventFromInnerInstructions(innerInstructions InnerInstructions, instruction Instruction, offset [2]uint) (dlmmAddLiquidityEvent, [2]uint, error) {
|
|
var prefixLen = offset[1]
|
|
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
|
if err != nil {
|
|
return dlmmAddLiquidityEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity get inner instructions error: %v, offset, %d, %d", err, offset[0], prefixLen)
|
|
}
|
|
for innerIndex, innerInstr := range inners {
|
|
if innerInstr.ProgramIDIndex != instruction.ProgramIDIndex {
|
|
continue
|
|
}
|
|
event, ok := dlmmDecodeAddLiquidityEvent(innerInstr.Data)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if offset[1] == 0 {
|
|
offset[0] += 1
|
|
} else {
|
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
|
}
|
|
return event, offset, nil
|
|
}
|
|
return dlmmAddLiquidityEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity event not found, offset, %d, %d", offset[0], prefixLen)
|
|
}
|
|
|
|
func dlmmRemoveLiquidityEventFromInnerInstructions(innerInstructions InnerInstructions, instruction Instruction, offset [2]uint) (dlmmRemoveLiquidityEvent, [2]uint, error) {
|
|
var prefixLen = offset[1]
|
|
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
|
if err != nil {
|
|
return dlmmRemoveLiquidityEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity get inner instructions error: %v, offset, %d, %d", err, offset[0], prefixLen)
|
|
}
|
|
for innerIndex, innerInstr := range inners {
|
|
if innerInstr.ProgramIDIndex != instruction.ProgramIDIndex {
|
|
continue
|
|
}
|
|
event, ok := dlmmDecodeRemoveLiquidityEvent(innerInstr.Data)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if offset[1] == 0 {
|
|
offset[0] += 1
|
|
} else {
|
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
|
}
|
|
return event, offset, nil
|
|
}
|
|
return dlmmRemoveLiquidityEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity event not found, offset, %d, %d", offset[0], prefixLen)
|
|
}
|
|
|
|
func dlmmClaimFeeEventFromInnerInstructions(innerInstructions InnerInstructions, instruction Instruction, offset [2]uint) (dlmmClaimFeeInnerEvent, [2]uint, error) {
|
|
var prefixLen = offset[1]
|
|
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
|
if err != nil {
|
|
return dlmmClaimFeeInnerEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm claim fee get inner instructions error: %v, offset, %d, %d", err, offset[0], prefixLen)
|
|
}
|
|
|
|
var (
|
|
found bool
|
|
event dlmmClaimFeeInnerEvent
|
|
matchedIdx int
|
|
)
|
|
for innerIndex, innerInstr := range inners {
|
|
if innerInstr.ProgramIDIndex != instruction.ProgramIDIndex {
|
|
continue
|
|
}
|
|
decoded, ok := dlmmDecodeClaimFeeEvent(innerInstr.Data)
|
|
if !ok {
|
|
continue
|
|
}
|
|
found = true
|
|
event = decoded
|
|
matchedIdx = innerIndex
|
|
if decoded.HasActiveBin {
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return dlmmClaimFeeInnerEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm claim fee event not found, offset, %d, %d", offset[0], prefixLen)
|
|
}
|
|
if offset[1] == 0 {
|
|
offset[0] += 1
|
|
} else {
|
|
offset[1] = uint(matchedIdx) + 1 + prefixLen
|
|
}
|
|
return event, offset, nil
|
|
}
|
|
|
|
func dlmmRebalancingEventFromInnerInstructions(innerInstructions InnerInstructions, instruction Instruction, offset [2]uint) (dlmmRebalancingEvent, [2]uint, error) {
|
|
var prefixLen = offset[1]
|
|
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
|
if err != nil {
|
|
return dlmmRebalancingEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm rebalance liquidity get inner instructions error: %v, offset, %d, %d", err, offset[0], prefixLen)
|
|
}
|
|
for innerIndex, innerInstr := range inners {
|
|
if innerInstr.ProgramIDIndex != instruction.ProgramIDIndex {
|
|
continue
|
|
}
|
|
event, ok := dlmmDecodeRebalancingEvent(innerInstr.Data)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if offset[1] == 0 {
|
|
offset[0] += 1
|
|
} else {
|
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
|
}
|
|
return event, offset, nil
|
|
}
|
|
return dlmmRebalancingEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm rebalance liquidity event not found, offset, %d, %d", offset[0], prefixLen)
|
|
}
|
|
|
|
func dlmmPositionCreateEventFromInnerInstructions(innerInstructions InnerInstructions, instruction Instruction, offset [2]uint) (dlmmPositionCreateEvent, [2]uint, bool, error) {
|
|
var prefixLen = offset[1]
|
|
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
|
if err != nil {
|
|
return dlmmPositionCreateEvent{}, increaseOffset(offset), false, fmt.Errorf("meteora dlmm create position get inner instructions error: %v, offset, %d, %d", err, offset[0], prefixLen)
|
|
}
|
|
for innerIndex, innerInstr := range inners {
|
|
if innerInstr.ProgramIDIndex != instruction.ProgramIDIndex {
|
|
continue
|
|
}
|
|
event, ok := dlmmDecodePositionCreateEvent(innerInstr.Data)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if offset[1] == 0 {
|
|
offset[0] += 1
|
|
} else {
|
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
|
}
|
|
return event, offset, true, nil
|
|
}
|
|
return dlmmPositionCreateEvent{}, increaseOffset(offset), false, nil
|
|
}
|
|
|
|
func dlmmPositionCloseEventFromInnerInstructions(innerInstructions InnerInstructions, instruction Instruction, offset [2]uint) (dlmmPositionCloseEvent, [2]uint, bool, error) {
|
|
var prefixLen = offset[1]
|
|
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
|
if err != nil {
|
|
return dlmmPositionCloseEvent{}, increaseOffset(offset), false, fmt.Errorf("meteora dlmm close position get inner instructions error: %v, offset, %d, %d", err, offset[0], prefixLen)
|
|
}
|
|
for innerIndex, innerInstr := range inners {
|
|
if innerInstr.ProgramIDIndex != instruction.ProgramIDIndex {
|
|
continue
|
|
}
|
|
event, ok := dlmmDecodePositionCloseEvent(innerInstr.Data)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if offset[1] == 0 {
|
|
offset[0] += 1
|
|
} else {
|
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
|
}
|
|
return event, offset, true, nil
|
|
}
|
|
return dlmmPositionCloseEvent{}, increaseOffset(offset), false, nil
|
|
}
|
|
|
|
func dlmmLbPairCreateEventFromInnerInstructions(innerInstructions InnerInstructions, instruction Instruction, offset [2]uint) (dlmmLbPairCreateEvent, [2]uint, bool, error) {
|
|
var prefixLen = offset[1]
|
|
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
|
if err != nil {
|
|
return dlmmLbPairCreateEvent{}, increaseOffset(offset), false, fmt.Errorf("meteora dlmm create get inner instructions error: %v, offset, %d, %d", err, offset[0], prefixLen)
|
|
}
|
|
for innerIndex, innerInstr := range inners {
|
|
if innerInstr.ProgramIDIndex != instruction.ProgramIDIndex {
|
|
continue
|
|
}
|
|
event, ok := dlmmDecodeLbPairCreateEvent(innerInstr.Data)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if offset[1] == 0 {
|
|
offset[0] += 1
|
|
} else {
|
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
|
}
|
|
return event, offset, true, nil
|
|
}
|
|
return dlmmLbPairCreateEvent{}, increaseOffset(offset), false, nil
|
|
}
|
|
|
|
func dlmmDecodeLbPairCreateEvent(data []byte) (dlmmLbPairCreateEvent, bool) {
|
|
switch {
|
|
case len(data) >= 8 && bytes.Equal(data[:8], meteoraInitializeLbPairEventDiscriminator[:]):
|
|
var event dlmmLbPairCreateEvent
|
|
if err := agbinary.NewBorshDecoder(data[8:]).Decode(&event); err != nil {
|
|
return dlmmLbPairCreateEvent{}, false
|
|
}
|
|
return event, true
|
|
case len(data) >= 16 &&
|
|
bytes.Equal(data[:8], eventDiscriminator[:]) &&
|
|
bytes.Equal(data[8:16], meteoraInitializeLbPairEventDiscriminator[:]):
|
|
var event dlmmLbPairCreateEvent
|
|
if err := agbinary.NewBorshDecoder(data[16:]).Decode(&event); err != nil {
|
|
return dlmmLbPairCreateEvent{}, false
|
|
}
|
|
return event, true
|
|
default:
|
|
return dlmmLbPairCreateEvent{}, false
|
|
}
|
|
}
|
|
|
|
func dlmmDecodeAddLiquidityEvent(data []byte) (dlmmAddLiquidityEvent, bool) {
|
|
switch {
|
|
case len(data) >= 8 && bytes.Equal(data[:8], meteoraDlmmAddLiquidityEventDiscriminator[:]):
|
|
var event dlmmAddLiquidityEvent
|
|
if err := agbinary.NewBorshDecoder(data[8:]).Decode(&event); err != nil {
|
|
return dlmmAddLiquidityEvent{}, false
|
|
}
|
|
return event, true
|
|
case len(data) >= 16 &&
|
|
bytes.Equal(data[:8], eventDiscriminator[:]) &&
|
|
bytes.Equal(data[8:16], meteoraDlmmAddLiquidityEventDiscriminator[:]):
|
|
var event dlmmAddLiquidityEvent
|
|
if err := agbinary.NewBorshDecoder(data[16:]).Decode(&event); err != nil {
|
|
return dlmmAddLiquidityEvent{}, false
|
|
}
|
|
return event, true
|
|
default:
|
|
return dlmmAddLiquidityEvent{}, false
|
|
}
|
|
}
|
|
|
|
func dlmmDecodeRemoveLiquidityEvent(data []byte) (dlmmRemoveLiquidityEvent, bool) {
|
|
switch {
|
|
case len(data) >= 8 && bytes.Equal(data[:8], meteoraDlmmRemoveLiquidityEventDiscriminator[:]):
|
|
var event dlmmRemoveLiquidityEvent
|
|
if err := agbinary.NewBorshDecoder(data[8:]).Decode(&event); err != nil {
|
|
return dlmmRemoveLiquidityEvent{}, false
|
|
}
|
|
return event, true
|
|
case len(data) >= 16 &&
|
|
bytes.Equal(data[:8], eventDiscriminator[:]) &&
|
|
bytes.Equal(data[8:16], meteoraDlmmRemoveLiquidityEventDiscriminator[:]):
|
|
var event dlmmRemoveLiquidityEvent
|
|
if err := agbinary.NewBorshDecoder(data[16:]).Decode(&event); err != nil {
|
|
return dlmmRemoveLiquidityEvent{}, false
|
|
}
|
|
return event, true
|
|
default:
|
|
return dlmmRemoveLiquidityEvent{}, false
|
|
}
|
|
}
|
|
|
|
func dlmmDecodePositionCreateEvent(data []byte) (dlmmPositionCreateEvent, bool) {
|
|
switch {
|
|
case len(data) >= 8 && bytes.Equal(data[:8], meteoraDlmmPositionCreateEventDiscriminator[:]):
|
|
var event dlmmPositionCreateEvent
|
|
if err := agbinary.NewBorshDecoder(data[8:]).Decode(&event); err != nil {
|
|
return dlmmPositionCreateEvent{}, false
|
|
}
|
|
return event, true
|
|
case len(data) >= 16 &&
|
|
bytes.Equal(data[:8], eventDiscriminator[:]) &&
|
|
bytes.Equal(data[8:16], meteoraDlmmPositionCreateEventDiscriminator[:]):
|
|
var event dlmmPositionCreateEvent
|
|
if err := agbinary.NewBorshDecoder(data[16:]).Decode(&event); err != nil {
|
|
return dlmmPositionCreateEvent{}, false
|
|
}
|
|
return event, true
|
|
default:
|
|
return dlmmPositionCreateEvent{}, false
|
|
}
|
|
}
|
|
|
|
func dlmmDecodePositionCloseEvent(data []byte) (dlmmPositionCloseEvent, bool) {
|
|
switch {
|
|
case len(data) >= 8 && bytes.Equal(data[:8], meteoraDlmmPositionCloseEventDiscriminator[:]):
|
|
var event dlmmPositionCloseEvent
|
|
if err := agbinary.NewBorshDecoder(data[8:]).Decode(&event); err != nil {
|
|
return dlmmPositionCloseEvent{}, false
|
|
}
|
|
return event, true
|
|
case len(data) >= 16 &&
|
|
bytes.Equal(data[:8], eventDiscriminator[:]) &&
|
|
bytes.Equal(data[8:16], meteoraDlmmPositionCloseEventDiscriminator[:]):
|
|
var event dlmmPositionCloseEvent
|
|
if err := agbinary.NewBorshDecoder(data[16:]).Decode(&event); err != nil {
|
|
return dlmmPositionCloseEvent{}, false
|
|
}
|
|
return event, true
|
|
default:
|
|
return dlmmPositionCloseEvent{}, false
|
|
}
|
|
}
|
|
|
|
func dlmmDecodeClaimFeeEvent(data []byte) (dlmmClaimFeeInnerEvent, bool) {
|
|
switch {
|
|
case len(data) >= 16 &&
|
|
bytes.Equal(data[:8], eventDiscriminator[:]) &&
|
|
bytes.Equal(data[8:16], meteoraDlmmClaimFee2EventDiscriminator[:]):
|
|
var event dlmmClaimFee2Event
|
|
if err := agbinary.NewBorshDecoder(data[16:]).Decode(&event); err != nil {
|
|
return dlmmClaimFeeInnerEvent{}, false
|
|
}
|
|
return dlmmClaimFeeInnerEvent{
|
|
LbPair: event.LbPair,
|
|
Position: event.Position,
|
|
Owner: event.Owner,
|
|
FeeX: event.FeeX,
|
|
FeeY: event.FeeY,
|
|
ActiveBinId: event.ActiveBinId,
|
|
HasActiveBin: true,
|
|
}, true
|
|
case len(data) >= 16 &&
|
|
bytes.Equal(data[:8], eventDiscriminator[:]) &&
|
|
bytes.Equal(data[8:16], meteoraDlmmClaimFeeEventDiscriminator[:]):
|
|
var event dlmmClaimFeeEvent
|
|
if err := agbinary.NewBorshDecoder(data[16:]).Decode(&event); err != nil {
|
|
return dlmmClaimFeeInnerEvent{}, false
|
|
}
|
|
return dlmmClaimFeeInnerEvent{
|
|
LbPair: event.LbPair,
|
|
Position: event.Position,
|
|
Owner: event.Owner,
|
|
FeeX: event.FeeX,
|
|
FeeY: event.FeeY,
|
|
}, true
|
|
default:
|
|
return dlmmClaimFeeInnerEvent{}, false
|
|
}
|
|
}
|
|
|
|
func dlmmDecodeRebalancingEvent(data []byte) (dlmmRebalancingEvent, bool) {
|
|
switch {
|
|
case len(data) >= 8 && bytes.Equal(data[:8], meteoraDlmmRebalancingEventDiscriminator[:]):
|
|
var event dlmmRebalancingEvent
|
|
if err := agbinary.NewBorshDecoder(data[8:]).Decode(&event); err != nil {
|
|
return dlmmRebalancingEvent{}, false
|
|
}
|
|
return event, true
|
|
case len(data) >= 16 &&
|
|
bytes.Equal(data[:8], eventDiscriminator[:]) &&
|
|
bytes.Equal(data[8:16], meteoraDlmmRebalancingEventDiscriminator[:]):
|
|
var event dlmmRebalancingEvent
|
|
if err := agbinary.NewBorshDecoder(data[16:]).Decode(&event); err != nil {
|
|
return dlmmRebalancingEvent{}, false
|
|
}
|
|
return event, true
|
|
default:
|
|
return dlmmRebalancingEvent{}, false
|
|
}
|
|
}
|
|
|
|
func dlmmPositionCreateInstructionAccounts(result *RawTx, discriminator [8]byte, accounts []int) (pool, position, owner solana.PublicKey, err error) {
|
|
switch discriminator {
|
|
case meteoraDlmmInitializePositionDiscriminator, meteoraDlmmInitializePosition2Discriminator:
|
|
if len(accounts) < 4 {
|
|
return solana.PublicKey{}, solana.PublicKey{}, solana.PublicKey{}, fmt.Errorf("accounts too short, expected at least 4")
|
|
}
|
|
return result.accountList[accounts[2]], result.accountList[accounts[1]], result.accountList[accounts[3]], nil
|
|
case meteoraDlmmInitializePositionByOperatorDiscriminator, meteoraDlmmInitializePositionPdaDiscriminator:
|
|
if len(accounts) < 5 {
|
|
return solana.PublicKey{}, solana.PublicKey{}, solana.PublicKey{}, fmt.Errorf("accounts too short, expected at least 5")
|
|
}
|
|
return result.accountList[accounts[3]], result.accountList[accounts[2]], result.accountList[accounts[4]], nil
|
|
default:
|
|
return solana.PublicKey{}, solana.PublicKey{}, solana.PublicKey{}, fmt.Errorf("unsupported create position discriminator")
|
|
}
|
|
}
|
|
|
|
func dlmmPositionCloseInstructionAccounts(result *RawTx, discriminator [8]byte, accounts []int) (pool, position, owner solana.PublicKey, err error) {
|
|
switch discriminator {
|
|
case meteoraDlmmClosePositionDiscriminator:
|
|
if len(accounts) < 5 {
|
|
return solana.PublicKey{}, solana.PublicKey{}, solana.PublicKey{}, fmt.Errorf("accounts too short, expected at least 5")
|
|
}
|
|
return result.accountList[accounts[1]], result.accountList[accounts[0]], result.accountList[accounts[4]], nil
|
|
case meteoraDlmmClosePosition2Discriminator, meteoraDlmmClosePositionIfEmptyDiscriminator:
|
|
if len(accounts) < 2 {
|
|
return solana.PublicKey{}, solana.PublicKey{}, solana.PublicKey{}, fmt.Errorf("accounts too short, expected at least 2")
|
|
}
|
|
return solana.PublicKey{}, result.accountList[accounts[0]], result.accountList[accounts[1]], nil
|
|
default:
|
|
return solana.PublicKey{}, solana.PublicKey{}, solana.PublicKey{}, fmt.Errorf("unsupported close position discriminator")
|
|
}
|
|
}
|
|
|
|
func resolveDlmmSwapAccounts(result *RawTx, accounts []int) (dlmmSwapAccounts, error) {
|
|
if len(accounts) < 13 {
|
|
return dlmmSwapAccounts{}, fmt.Errorf("accounts too short, expected at least 13")
|
|
}
|
|
accountList := result.accountList
|
|
|
|
basePosCandidates := []int{1, 2}
|
|
for _, basePos := range basePosCandidates {
|
|
if basePos+6 >= len(accounts) {
|
|
continue
|
|
}
|
|
oraclePos := basePos + 6
|
|
|
|
userPos := oraclePos + 1
|
|
hostFeePresent := true
|
|
if userPos < len(accounts) && dlmmIsSigner(result, accounts[userPos]) {
|
|
hostFeePresent = false
|
|
} else {
|
|
userPos = oraclePos + 2
|
|
}
|
|
if userPos+2 >= len(accounts) {
|
|
continue
|
|
}
|
|
tokenXProgramPos := userPos + 1
|
|
tokenYProgramPos := userPos + 2
|
|
|
|
eventAuthorityPos := tokenYProgramPos + 1
|
|
if eventAuthorityPos < len(accounts) && accountList[accounts[eventAuthorityPos]].Equals(solana.MemoProgramID) {
|
|
eventAuthorityPos++
|
|
}
|
|
programPos := eventAuthorityPos + 1
|
|
if programPos >= len(accounts) {
|
|
continue
|
|
}
|
|
if !accountList[accounts[eventAuthorityPos]].Equals(meteoraDlmmEventAuthority) {
|
|
continue
|
|
}
|
|
if !accountList[accounts[programPos]].Equals(meteoraDlmmProgram) {
|
|
continue
|
|
}
|
|
|
|
if hostFeePresent && oraclePos+1 < len(accounts) && dlmmIsSigner(result, accounts[oraclePos+1]) {
|
|
continue
|
|
}
|
|
|
|
return dlmmSwapAccounts{
|
|
poolIdx: accounts[0],
|
|
reserveXIdx: accounts[oraclePos-6],
|
|
reserveYIdx: accounts[oraclePos-5],
|
|
userTokenInIdx: accounts[oraclePos-4],
|
|
userTokenOutIdx: accounts[oraclePos-3],
|
|
tokenXMintIdx: accounts[oraclePos-2],
|
|
tokenYMintIdx: accounts[oraclePos-1],
|
|
oracleIdx: accounts[oraclePos],
|
|
userIdx: accounts[userPos],
|
|
tokenXProgramIdx: accounts[tokenXProgramPos],
|
|
tokenYProgramIdx: accounts[tokenYProgramPos],
|
|
}, nil
|
|
}
|
|
|
|
return dlmmSwapAccounts{}, fmt.Errorf("accounts layout invalid")
|
|
}
|
|
|
|
func resolveDlmmLiquidityAccounts(result *RawTx, accounts []int) (dlmmLiquidityAccounts, error) {
|
|
if len(accounts) < 9 {
|
|
return dlmmLiquidityAccounts{}, fmt.Errorf("accounts too short, expected at least 9")
|
|
}
|
|
accountList := result.accountList
|
|
|
|
eventAuthorityPos := -1
|
|
for i, idx := range accounts {
|
|
if idx < 0 || idx >= len(accountList) {
|
|
continue
|
|
}
|
|
if accountList[idx].Equals(meteoraDlmmEventAuthority) {
|
|
eventAuthorityPos = i
|
|
break
|
|
}
|
|
}
|
|
if eventAuthorityPos == -1 {
|
|
return dlmmLiquidityAccounts{}, fmt.Errorf("event authority not found")
|
|
}
|
|
if eventAuthorityPos+1 >= len(accounts) || !accountList[accounts[eventAuthorityPos+1]].Equals(meteoraDlmmProgram) {
|
|
return dlmmLiquidityAccounts{}, fmt.Errorf("program id not found after event authority")
|
|
}
|
|
|
|
tokenYProgramPos := eventAuthorityPos - 1
|
|
if tokenYProgramPos < 0 {
|
|
return dlmmLiquidityAccounts{}, fmt.Errorf("token program position invalid")
|
|
}
|
|
if accountList[accounts[tokenYProgramPos]].Equals(solana.MemoProgramID) {
|
|
tokenYProgramPos--
|
|
}
|
|
if tokenYProgramPos < 1 {
|
|
return dlmmLiquidityAccounts{}, fmt.Errorf("token program positions invalid")
|
|
}
|
|
tokenXProgramPos := tokenYProgramPos - 1
|
|
userPos := tokenXProgramPos - 1
|
|
if userPos < 0 {
|
|
return dlmmLiquidityAccounts{}, fmt.Errorf("user position invalid")
|
|
}
|
|
|
|
return dlmmLiquidityAccounts{
|
|
positionIdx: accounts[0],
|
|
poolIdx: accounts[1],
|
|
userTokenXIdx: accounts[3],
|
|
userTokenYIdx: accounts[4],
|
|
reserveXIdx: accounts[5],
|
|
reserveYIdx: accounts[6],
|
|
tokenXMintIdx: accounts[7],
|
|
tokenYMintIdx: accounts[8],
|
|
userIdx: accounts[userPos],
|
|
tokenXProgramIdx: accounts[tokenXProgramPos],
|
|
tokenYProgramIdx: accounts[tokenYProgramPos],
|
|
}, nil
|
|
}
|
|
|
|
func resolveDlmmOneSideLiquidityAccounts(result *RawTx, accounts []int) (dlmmOneSideLiquidityAccounts, error) {
|
|
if len(accounts) < 10 {
|
|
return dlmmOneSideLiquidityAccounts{}, fmt.Errorf("accounts too short, expected at least 10")
|
|
}
|
|
accountList := result.accountList
|
|
|
|
eventAuthorityPos := -1
|
|
for i, idx := range accounts {
|
|
if idx < 0 || idx >= len(accountList) {
|
|
continue
|
|
}
|
|
if accountList[idx].Equals(meteoraDlmmEventAuthority) {
|
|
eventAuthorityPos = i
|
|
break
|
|
}
|
|
}
|
|
if eventAuthorityPos == -1 {
|
|
return dlmmOneSideLiquidityAccounts{}, fmt.Errorf("event authority not found")
|
|
}
|
|
if eventAuthorityPos+1 >= len(accounts) || !accountList[accounts[eventAuthorityPos+1]].Equals(meteoraDlmmProgram) {
|
|
return dlmmOneSideLiquidityAccounts{}, fmt.Errorf("program id not found after event authority")
|
|
}
|
|
|
|
tokenProgramPos := eventAuthorityPos - 1
|
|
userPos := eventAuthorityPos - 2
|
|
if tokenProgramPos < 0 || userPos < 0 {
|
|
return dlmmOneSideLiquidityAccounts{}, fmt.Errorf("one side liquidity account positions invalid")
|
|
}
|
|
if len(accounts) < 6 {
|
|
return dlmmOneSideLiquidityAccounts{}, fmt.Errorf("accounts too short for one side liquidity parsing")
|
|
}
|
|
|
|
return dlmmOneSideLiquidityAccounts{
|
|
positionIdx: accounts[0],
|
|
poolIdx: accounts[1],
|
|
userTokenIdx: accounts[3],
|
|
reserveIdx: accounts[4],
|
|
tokenMintIdx: accounts[5],
|
|
userIdx: accounts[userPos],
|
|
tokenProgramIdx: accounts[tokenProgramPos],
|
|
}, nil
|
|
}
|
|
|
|
func dlmmBuildOneSideAddSwap(
|
|
tx *Tx,
|
|
instruction Instruction,
|
|
addEvent dlmmAddLiquidityEvent,
|
|
startBinId int32,
|
|
endBinId int32,
|
|
entryContract solana.PublicKey,
|
|
) ([]Swap, error) {
|
|
result := tx.rawTx
|
|
accounts, err := resolveDlmmOneSideLiquidityAccounts(result, instruction.Accounts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
knownMint := result.accountList[accounts.tokenMintIdx]
|
|
knownTokenProgram := result.accountList[accounts.tokenProgramIdx]
|
|
knownDecimals, ok := dlmmTokenDecimals(result, accounts.reserveIdx)
|
|
if !ok {
|
|
knownDecimals, _ = dlmmTokenDecimals(result, accounts.userTokenIdx)
|
|
}
|
|
knownReserveBalance := getAccountBalanceAfterTx(result, accounts.reserveIdx)
|
|
knownUserBalance := getAccountBalanceAfterTx(result, accounts.userTokenIdx)
|
|
if knownMint.Equals(wSolMint) {
|
|
if solAmount, err := GetSolAfterTx(result, accounts.userIdx); err == nil {
|
|
knownUserBalance = knownUserBalance.Add(decimal.NewFromUint64(solAmount))
|
|
}
|
|
}
|
|
|
|
eventUser := result.accountList[accounts.userIdx]
|
|
if !addEvent.From.IsZero() {
|
|
eventUser = addEvent.From
|
|
}
|
|
positionAccount := result.accountList[accounts.positionIdx]
|
|
if !addEvent.Position.IsZero() {
|
|
positionAccount = addEvent.Position
|
|
}
|
|
|
|
swap := Swap{
|
|
Program: SolProgramMeteoraDLMM,
|
|
Event: "add",
|
|
Pool: result.accountList[accounts.poolIdx],
|
|
User: eventUser,
|
|
EntryContract: entryContract,
|
|
ActiveBinId: addEvent.ActiveBinId,
|
|
StartBinId: startBinId,
|
|
EndBinId: endBinId,
|
|
PositionAccount: positionAccount,
|
|
}
|
|
|
|
knownIsX := dlmmInferOneSideLiquidityAxis(result, accounts, addEvent)
|
|
if knownIsX {
|
|
swap.BaseMint = knownMint
|
|
swap.BaseTokenProgram = knownTokenProgram
|
|
swap.BaseMintDecimals = knownDecimals
|
|
swap.BaseAmount = decimal.NewFromUint64(addEvent.Amounts[0])
|
|
swap.BaseReserve = knownReserveBalance
|
|
swap.UserBaseBalance = knownUserBalance
|
|
if _, exists := tx.Token[knownMint]; !exists && !knownMint.Equals(wSolMint) {
|
|
tx.Token[knownMint] = TokenMeta{
|
|
Mint: knownMint,
|
|
Decimals: knownDecimals,
|
|
TokenProgram: knownTokenProgram,
|
|
}
|
|
}
|
|
} else {
|
|
swap.QuoteMint = knownMint
|
|
swap.QuoteTokenProgram = knownTokenProgram
|
|
swap.QuoteMintDecimals = knownDecimals
|
|
swap.QuoteAmount = decimal.NewFromUint64(addEvent.Amounts[1])
|
|
swap.QuoteReserve = knownReserveBalance
|
|
swap.UserQuoteBalance = knownUserBalance
|
|
if _, exists := tx.Token[knownMint]; !exists && !knownMint.Equals(wSolMint) {
|
|
tx.Token[knownMint] = TokenMeta{
|
|
Mint: knownMint,
|
|
Decimals: knownDecimals,
|
|
TokenProgram: knownTokenProgram,
|
|
}
|
|
}
|
|
}
|
|
|
|
return []Swap{swap}, nil
|
|
}
|
|
|
|
func dlmmInferOneSideLiquidityAxis(result *RawTx, accounts dlmmOneSideLiquidityAccounts, addEvent dlmmAddLiquidityEvent) bool {
|
|
knownAmount, ok := dlmmTokenDelta(result, accounts.reserveIdx)
|
|
if !ok || knownAmount.IsZero() {
|
|
knownAmount, _ = dlmmTokenDelta(result, accounts.userTokenIdx)
|
|
}
|
|
|
|
amountX := decimal.NewFromUint64(addEvent.Amounts[0])
|
|
amountY := decimal.NewFromUint64(addEvent.Amounts[1])
|
|
switch {
|
|
case !knownAmount.IsZero() && knownAmount.Equal(amountX) && !knownAmount.Equal(amountY):
|
|
return true
|
|
case !knownAmount.IsZero() && knownAmount.Equal(amountY) && !knownAmount.Equal(amountX):
|
|
return false
|
|
case addEvent.Amounts[0] > 0 && addEvent.Amounts[1] == 0:
|
|
return true
|
|
case addEvent.Amounts[1] > 0 && addEvent.Amounts[0] == 0:
|
|
return false
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
func resolveDlmmClaimFeeAccounts(result *RawTx, data []byte, accounts []int) (dlmmLiquidityAccounts, error) {
|
|
if len(data) < 8 {
|
|
return dlmmLiquidityAccounts{}, fmt.Errorf("instruction data too short")
|
|
}
|
|
discriminator := *(*[8]byte)(data[:8])
|
|
accountList := result.accountList
|
|
|
|
switch discriminator {
|
|
case meteoraDlmmClaimFee2Discriminator:
|
|
if len(accounts) < 14 {
|
|
return dlmmLiquidityAccounts{}, fmt.Errorf("accounts too short, expected at least 14")
|
|
}
|
|
if !accountList[accounts[12]].Equals(meteoraDlmmEventAuthority) {
|
|
return dlmmLiquidityAccounts{}, fmt.Errorf("event authority mismatch")
|
|
}
|
|
if !accountList[accounts[13]].Equals(meteoraDlmmProgram) {
|
|
return dlmmLiquidityAccounts{}, fmt.Errorf("program id mismatch")
|
|
}
|
|
return dlmmLiquidityAccounts{
|
|
positionIdx: accounts[1],
|
|
poolIdx: accounts[0],
|
|
userTokenXIdx: accounts[5],
|
|
userTokenYIdx: accounts[6],
|
|
reserveXIdx: accounts[3],
|
|
reserveYIdx: accounts[4],
|
|
tokenXMintIdx: accounts[7],
|
|
tokenYMintIdx: accounts[8],
|
|
userIdx: accounts[2],
|
|
tokenXProgramIdx: accounts[9],
|
|
tokenYProgramIdx: accounts[10],
|
|
}, nil
|
|
case meteoraDlmmClaimFeeDiscriminator:
|
|
if len(accounts) < 14 {
|
|
return dlmmLiquidityAccounts{}, fmt.Errorf("accounts too short, expected at least 14")
|
|
}
|
|
if !accountList[accounts[12]].Equals(meteoraDlmmEventAuthority) {
|
|
return dlmmLiquidityAccounts{}, fmt.Errorf("event authority mismatch")
|
|
}
|
|
if !accountList[accounts[13]].Equals(meteoraDlmmProgram) {
|
|
return dlmmLiquidityAccounts{}, fmt.Errorf("program id mismatch")
|
|
}
|
|
return dlmmLiquidityAccounts{
|
|
positionIdx: accounts[1],
|
|
poolIdx: accounts[0],
|
|
userTokenXIdx: accounts[7],
|
|
userTokenYIdx: accounts[8],
|
|
reserveXIdx: accounts[5],
|
|
reserveYIdx: accounts[6],
|
|
tokenXMintIdx: accounts[9],
|
|
tokenYMintIdx: accounts[10],
|
|
userIdx: accounts[4],
|
|
tokenXProgramIdx: accounts[11],
|
|
tokenYProgramIdx: accounts[11],
|
|
}, nil
|
|
default:
|
|
return dlmmLiquidityAccounts{}, fmt.Errorf("unsupported claim fee discriminator")
|
|
}
|
|
}
|
|
|
|
func resolveDlmmRebalanceAccounts(result *RawTx, accounts []int) (dlmmLiquidityAccounts, error) {
|
|
if len(accounts) < 17 {
|
|
return dlmmLiquidityAccounts{}, fmt.Errorf("accounts too short, expected at least 17")
|
|
}
|
|
accountList := result.accountList
|
|
|
|
if !accountList[accounts[15]].Equals(meteoraDlmmEventAuthority) {
|
|
return dlmmLiquidityAccounts{}, fmt.Errorf("event authority mismatch")
|
|
}
|
|
if !accountList[accounts[16]].Equals(meteoraDlmmProgram) {
|
|
return dlmmLiquidityAccounts{}, fmt.Errorf("program id mismatch")
|
|
}
|
|
|
|
return dlmmLiquidityAccounts{
|
|
positionIdx: accounts[0],
|
|
poolIdx: accounts[1],
|
|
userTokenXIdx: accounts[3],
|
|
userTokenYIdx: accounts[4],
|
|
reserveXIdx: accounts[5],
|
|
reserveYIdx: accounts[6],
|
|
tokenXMintIdx: accounts[7],
|
|
tokenYMintIdx: accounts[8],
|
|
userIdx: accounts[9],
|
|
tokenXProgramIdx: accounts[11],
|
|
tokenYProgramIdx: accounts[12],
|
|
}, nil
|
|
}
|
|
|
|
func dlmmTokenDecimals(result *RawTx, accountIndex int) (uint8, bool) {
|
|
for _, meta := range result.Meta.PostTokenBalances {
|
|
if meta.AccountIndex == accountIndex {
|
|
return uint8(meta.UITokenAmount.Decimals), true
|
|
}
|
|
}
|
|
for _, meta := range result.Meta.PreTokenBalances {
|
|
if meta.AccountIndex == accountIndex {
|
|
return uint8(meta.UITokenAmount.Decimals), true
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func dlmmTokenDelta(result *RawTx, accountIndex int) (decimal.Decimal, bool) {
|
|
before, okBefore := dlmmTokenAmount(result, accountIndex, false)
|
|
after, okAfter := dlmmTokenAmount(result, accountIndex, true)
|
|
if !okBefore && !okAfter {
|
|
return decimal.Zero, false
|
|
}
|
|
if !okBefore {
|
|
before = decimal.Zero
|
|
}
|
|
if !okAfter {
|
|
after = decimal.Zero
|
|
}
|
|
return after.Sub(before).Abs(), true
|
|
}
|
|
|
|
func dlmmTokenDeltaSigned(result *RawTx, accountIndex int) (decimal.Decimal, bool) {
|
|
before, okBefore := dlmmTokenAmount(result, accountIndex, false)
|
|
after, okAfter := dlmmTokenAmount(result, accountIndex, true)
|
|
if !okBefore && !okAfter {
|
|
return decimal.Zero, false
|
|
}
|
|
if !okBefore {
|
|
before = decimal.Zero
|
|
}
|
|
if !okAfter {
|
|
after = decimal.Zero
|
|
}
|
|
return after.Sub(before), true
|
|
}
|
|
|
|
func dlmmTokenAmount(result *RawTx, accountIndex int, post bool) (decimal.Decimal, bool) {
|
|
var balances []TokenBalance
|
|
if post {
|
|
balances = result.Meta.PostTokenBalances
|
|
} else {
|
|
balances = result.Meta.PreTokenBalances
|
|
}
|
|
for _, meta := range balances {
|
|
if meta.AccountIndex == accountIndex {
|
|
amount, err := decimal.NewFromString(meta.UITokenAmount.Amount)
|
|
if err != nil {
|
|
return decimal.Zero, false
|
|
}
|
|
return amount, true
|
|
}
|
|
}
|
|
return decimal.Zero, false
|
|
}
|
|
|
|
func dlmmIsSigner(result *RawTx, accountIndex int) bool {
|
|
if accountIndex < 0 || accountIndex >= len(result.Transaction.Message.AccountKeys) {
|
|
return false
|
|
}
|
|
return accountIndex < result.Transaction.Message.Header.NumRequiredSignatures
|
|
}
|
|
|
|
func dlmmTokenBalanceMeta(result *RawTx, accountIndex int) (TokenBalance, bool) {
|
|
for _, meta := range result.Meta.PostTokenBalances {
|
|
if meta.AccountIndex == accountIndex {
|
|
return meta, true
|
|
}
|
|
}
|
|
for _, meta := range result.Meta.PreTokenBalances {
|
|
if meta.AccountIndex == accountIndex {
|
|
return meta, true
|
|
}
|
|
}
|
|
return TokenBalance{}, false
|
|
}
|
|
|
|
func dlmmAllocateByWeights(total uint64, weights []uint64) []decimal.Decimal {
|
|
if len(weights) == 0 {
|
|
return nil
|
|
}
|
|
|
|
sumWeights := uint64(0)
|
|
for _, weight := range weights {
|
|
sumWeights += weight
|
|
}
|
|
if sumWeights == 0 {
|
|
sumWeights = uint64(len(weights))
|
|
weights = append([]uint64(nil), weights...)
|
|
for i := range weights {
|
|
weights[i] = 1
|
|
}
|
|
}
|
|
|
|
allocations := make([]decimal.Decimal, len(weights))
|
|
remaining := total
|
|
for i, weight := range weights {
|
|
amount := uint64(0)
|
|
if i == len(weights)-1 {
|
|
amount = remaining
|
|
} else if sumWeights > 0 {
|
|
amount = total * weight / sumWeights
|
|
if amount > remaining {
|
|
amount = remaining
|
|
}
|
|
remaining -= amount
|
|
}
|
|
allocations[i] = decimal.NewFromUint64(amount)
|
|
}
|
|
return allocations
|
|
}
|
|
|
|
func dlmmApplySignedAllocation(values []decimal.Decimal, negative bool) []decimal.Decimal {
|
|
if !negative {
|
|
return values
|
|
}
|
|
out := make([]decimal.Decimal, len(values))
|
|
for i, value := range values {
|
|
out[i] = value.Neg()
|
|
}
|
|
return out
|
|
}
|
|
|
|
func dlmmMinMaxBinIDFromCompressedDeposits(bins []dlmmCompressedBinDepositAmount) (startBinID, endBinID int32) {
|
|
if len(bins) == 0 {
|
|
return 0, 0
|
|
}
|
|
startBinID = bins[0].BinID
|
|
endBinID = bins[0].BinID
|
|
for _, bin := range bins[1:] {
|
|
if bin.BinID < startBinID {
|
|
startBinID = bin.BinID
|
|
}
|
|
if bin.BinID > endBinID {
|
|
endBinID = bin.BinID
|
|
}
|
|
}
|
|
return startBinID, endBinID
|
|
}
|
|
|
|
func dlmmCommonRemoveBp(reduction []dlmmBinLiquidityReduction) int32 {
|
|
if len(reduction) == 0 {
|
|
return 0
|
|
}
|
|
bpsToRemove := reduction[0].BpsToRemove
|
|
for _, item := range reduction[1:] {
|
|
if item.BpsToRemove != bpsToRemove {
|
|
return 0
|
|
}
|
|
}
|
|
return int32(bpsToRemove)
|
|
}
|
|
|
|
func dlmmPositionUpperBinId(lowerBinId, width int32) int32 {
|
|
if width <= 0 {
|
|
return lowerBinId
|
|
}
|
|
return lowerBinId + width - 1
|
|
}
|
|
|
|
func dlmmMinMaxBinIdFromDistribution(dist []dlmmBinLiquidityDistribution) (int32, int32) {
|
|
if len(dist) == 0 {
|
|
return 0, 0
|
|
}
|
|
min := dist[0].BinId
|
|
max := dist[0].BinId
|
|
for _, item := range dist[1:] {
|
|
if item.BinId < min {
|
|
min = item.BinId
|
|
}
|
|
if item.BinId > max {
|
|
max = item.BinId
|
|
}
|
|
}
|
|
return min, max
|
|
}
|
|
|
|
func dlmmMinMaxBinIdFromWeightDistribution(dist []dlmmBinLiquidityDistributionByWeight) (int32, int32) {
|
|
if len(dist) == 0 {
|
|
return 0, 0
|
|
}
|
|
min := dist[0].BinId
|
|
max := dist[0].BinId
|
|
for _, item := range dist[1:] {
|
|
if item.BinId < min {
|
|
min = item.BinId
|
|
}
|
|
if item.BinId > max {
|
|
max = item.BinId
|
|
}
|
|
}
|
|
return min, max
|
|
}
|
|
|
|
func dlmmMinMaxBinIdFromReduction(reduction []dlmmBinLiquidityReduction) (int32, int32) {
|
|
if len(reduction) == 0 {
|
|
return 0, 0
|
|
}
|
|
min := reduction[0].BinId
|
|
max := reduction[0].BinId
|
|
for _, item := range reduction[1:] {
|
|
if item.BinId < min {
|
|
min = item.BinId
|
|
}
|
|
if item.BinId > max {
|
|
max = item.BinId
|
|
}
|
|
}
|
|
return min, max
|
|
}
|