Compare commits

..

5 Commits

Author SHA1 Message Date
bijianing97
fb8d93f426 Update dlmm fee 2026-04-11 08:34:21 +08:00
bijianing97
0cc843b370 Update dlmm fee 2026-04-11 08:27:34 +08:00
bijianing97
d9a214b4b4 Add dlmm add liquidity one side function 2026-03-25 11:34:46 +08:00
thloyi
047b549d0f option ComputeUnitsConsumed 2026-03-23 20:20:21 +08:00
bijianing97
9327eab010 Fix dlmm parser 2026-03-23 15:30:43 +08:00
6 changed files with 1133 additions and 157 deletions

121
cmd/rpc_parse/main.go Normal file
View File

@@ -0,0 +1,121 @@
package main
import (
"context"
"fmt"
"os"
"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc"
pump_parser "github.com/thloyi/pump-parser"
)
func main() {
const rpcURL = "https://staked.helius-rpc.com?api-key=5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d"
txHash := os.Getenv("TX_HASH")
if txHash == "" {
txHash = "2AhpL5KhVmG3D38CwMzrHuRyTucEQ43GzBXL2mo5WiugdZMVmK1dtX8brGe3sxvvFDY6iSSviJTvqCtr4UL3Pc7J"
}
if txHash == "" {
fmt.Fprintln(os.Stderr, "txHash is empty; set it in cmd/rpc_parse/main.go")
os.Exit(1)
}
sig, err := solana.SignatureFromBase58(txHash)
if err != nil {
fmt.Fprintf(os.Stderr, "invalid txHash: %v\n", err)
os.Exit(1)
}
client := rpc.New(rpcURL)
maxSupportedVersion := uint64(0)
out, err := client.GetTransaction(context.Background(), sig, &rpc.GetTransactionOpts{
Encoding: solana.EncodingBase64,
Commitment: rpc.CommitmentConfirmed,
MaxSupportedTransactionVersion: &maxSupportedVersion,
})
if err != nil {
fmt.Fprintf(os.Stderr, "rpc getTransaction error: %v\n", err)
os.Exit(1)
}
if out == nil || out.Transaction == nil || out.Meta == nil {
fmt.Fprintln(os.Stderr, "rpc getTransaction returned empty response")
os.Exit(1)
}
rawBinary := out.Transaction.GetBinary()
if len(rawBinary) == 0 {
fmt.Fprintln(os.Stderr, "rpc getTransaction returned empty transaction data")
os.Exit(1)
}
txWithMeta := rpc.TransactionWithMeta{
Slot: out.Slot,
BlockTime: out.BlockTime,
Transaction: rpc.DataBytesOrJSONFromBytes(rawBinary),
Meta: out.Meta,
Version: out.Version,
}
var blockTime *uint64
if out.BlockTime != nil {
bt := uint64(*out.BlockTime)
blockTime = &bt
}
rawTx, err := pump_parser.FromRpcTransactionWithMeta(txWithMeta, blockTime, out.Slot, 0)
if err != nil {
fmt.Fprintf(os.Stderr, "convert rpc transaction error: %v\n", err)
os.Exit(1)
}
pump_parser.EnableAllParsers()
parsed, err := pump_parser.ParseRawTx(rawTx)
if err != nil {
fmt.Fprintf(os.Stderr, "parse raw tx error: %v\n", err)
os.Exit(1)
}
if len(parsed.Swaps) == 0 {
fmt.Println("no swaps parsed from tx")
return
}
for i, swap := range parsed.Swaps {
fmt.Printf("swap[%d]\n", i)
fmt.Printf(" program: %s\n", swap.Program)
fmt.Printf(" event: %s\n", swap.Event)
fmt.Printf(" pool: %s\n", swap.Pool)
fmt.Printf(" user: %s\n", swap.User)
fmt.Printf(" base_mint: %s (decimals=%d)\n", swap.BaseMint, swap.BaseMintDecimals)
fmt.Printf(" quote_mint: %s (decimals=%d)\n", swap.QuoteMint, swap.QuoteMintDecimals)
fmt.Printf(" base_amount: %s\n", swap.BaseAmount.String())
fmt.Printf(" quote_amount: %s\n", swap.QuoteAmount.String())
if !swap.FeeAmount.IsZero() || swap.FeeSide != "" {
fmt.Printf(" fee_amount: %s\n", swap.FeeAmount.String())
fmt.Printf(" lp_fee_amount: %s\n", swap.LpFeeAmount.String())
fmt.Printf(" fee_side: %s\n", swap.FeeSide)
fmt.Printf(" fee_mint: %s (decimals=%d)\n", swap.FeeMint, swap.FeeMintDecimals)
fmt.Printf(" fee_token_program: %s\n", swap.FeeTokenProgram)
}
fmt.Printf(" base_reserve: %s\n", swap.BaseReserve.String())
fmt.Printf(" quote_reserve: %s\n", swap.QuoteReserve.String())
fmt.Printf(" base_token_program: %s\n", swap.BaseTokenProgram)
fmt.Printf(" quote_token_program: %s\n", swap.QuoteTokenProgram)
fmt.Printf(" entry_contract: %s\n", swap.EntryContract)
fmt.Printf(" user_base_balance: %s\n", swap.UserBaseBalance.String())
fmt.Printf(" user_quote_balance: %s\n", swap.UserQuoteBalance.String())
fmt.Printf(" active_bin_id: %d\n", swap.ActiveBinId)
fmt.Printf(" start_bin_id: %d\n", swap.StartBinId)
fmt.Printf(" end_bin_id: %d\n", swap.EndBinId)
fmt.Printf(" remove_bp: %d\n", swap.RemoveBp)
fmt.Printf(" position_account: %s\n", swap.PositionAccount)
if swap.Mayhem {
fmt.Printf(" mayhem: true\n")
} else {
fmt.Printf(" mayhem: false\n")
}
fmt.Println()
}
}

79
meta.go
View File

@@ -68,41 +68,50 @@ var (
) )
var ( var (
meteoraInitializeLbPairDiscriminator = calculateDiscriminator("global:initialize_lb_pair2") meteoraInitializeCustomizablePermissionlessLbPairDiscriminator = calculateDiscriminator("global:initialize_customizable_permissionless_lb_pair")
meteoraInitializeLbPairEventDiscriminator = calculateDiscriminator("event:LbPairCreate") meteoraInitializeCustomizablePermissionlessLbPair2Discriminator = calculateDiscriminator("global:initialize_customizable_permissionless_lb_pair2")
meteoraDlmmSwapDiscriminator = calculateDiscriminator("global:swap") meteoraInitializeLbPairDiscriminator = calculateDiscriminator("global:initialize_lb_pair")
meteoraDlmmSwap2Discriminator = calculateDiscriminator("global:swap2") meteoraInitializeLbPair2Discriminator = calculateDiscriminator("global:initialize_lb_pair2")
meteoraDlmmSwapExactOutDiscriminator = calculateDiscriminator("global:swap_exact_out") meteoraInitializePermissionLbPairDiscriminator = calculateDiscriminator("global:initialize_permission_lb_pair")
meteoraDlmmSwapExactOut2Discriminator = calculateDiscriminator("global:swap_exact_out2") meteoraInitializeLbPairEventDiscriminator = calculateDiscriminator("event:LbPairCreate")
meteoraDlmmSwapWithPriceImpactDiscriminator = calculateDiscriminator("global:swap_with_price_impact") meteoraDlmmSwapDiscriminator = calculateDiscriminator("global:swap")
meteoraDlmmSwapWithPriceImpact2Discriminator = calculateDiscriminator("global:swap_with_price_impact2") meteoraDlmmSwap2Discriminator = calculateDiscriminator("global:swap2")
meteoraDlmmInitializePositionDiscriminator = calculateDiscriminator("global:initialize_position") meteoraDlmmSwapExactOutDiscriminator = calculateDiscriminator("global:swap_exact_out")
meteoraDlmmInitializePosition2Discriminator = calculateDiscriminator("global:initialize_position2") meteoraDlmmSwapExactOut2Discriminator = calculateDiscriminator("global:swap_exact_out2")
meteoraDlmmInitializePositionByOperatorDiscriminator = calculateDiscriminator("global:initialize_position_by_operator") meteoraDlmmSwapWithPriceImpactDiscriminator = calculateDiscriminator("global:swap_with_price_impact")
meteoraDlmmInitializePositionPdaDiscriminator = calculateDiscriminator("global:initialize_position_pda") meteoraDlmmSwapWithPriceImpact2Discriminator = calculateDiscriminator("global:swap_with_price_impact2")
meteoraDlmmClosePositionDiscriminator = calculateDiscriminator("global:close_position") meteoraDlmmInitializePositionDiscriminator = calculateDiscriminator("global:initialize_position")
meteoraDlmmClosePosition2Discriminator = calculateDiscriminator("global:close_position2") meteoraDlmmInitializePosition2Discriminator = calculateDiscriminator("global:initialize_position2")
meteoraDlmmClosePositionIfEmptyDiscriminator = calculateDiscriminator("global:close_position_if_empty") meteoraDlmmInitializePositionByOperatorDiscriminator = calculateDiscriminator("global:initialize_position_by_operator")
meteoraDlmmSwapEventDiscriminator = calculateDiscriminator("event:Swap") meteoraDlmmInitializePositionPdaDiscriminator = calculateDiscriminator("global:initialize_position_pda")
meteoraDlmmAddLiquidityDiscriminator = calculateDiscriminator("global:add_liquidity") meteoraDlmmClosePositionDiscriminator = calculateDiscriminator("global:close_position")
meteoraDlmmAddLiquidity2Discriminator = calculateDiscriminator("global:add_liquidity2") meteoraDlmmClosePosition2Discriminator = calculateDiscriminator("global:close_position2")
meteoraDlmmAddLiquidityByStrategyDiscriminator = calculateDiscriminator("global:add_liquidity_by_strategy") meteoraDlmmClosePositionIfEmptyDiscriminator = calculateDiscriminator("global:close_position_if_empty")
meteoraDlmmAddLiquidityByStrategy2Discriminator = calculateDiscriminator("global:add_liquidity_by_strategy2") meteoraDlmmSwapEventDiscriminator = calculateDiscriminator("event:Swap")
meteoraDlmmAddLiquidityByWeightDiscriminator = calculateDiscriminator("global:add_liquidity_by_weight") meteoraDlmmAddLiquidityDiscriminator = calculateDiscriminator("global:add_liquidity")
meteoraDlmmClaimFeeDiscriminator = calculateDiscriminator("global:claim_fee") meteoraDlmmAddLiquidity2Discriminator = calculateDiscriminator("global:add_liquidity2")
meteoraDlmmClaimFee2Discriminator = calculateDiscriminator("global:claim_fee2") meteoraDlmmAddLiquidityByStrategyDiscriminator = calculateDiscriminator("global:add_liquidity_by_strategy")
meteoraDlmmRebalanceLiquidityDiscriminator = calculateDiscriminator("global:rebalance_liquidity") meteoraDlmmAddLiquidityByStrategy2Discriminator = calculateDiscriminator("global:add_liquidity_by_strategy2")
meteoraDlmmRemoveLiquidityDiscriminator = calculateDiscriminator("global:remove_liquidity") meteoraDlmmAddLiquidityByWeightDiscriminator = calculateDiscriminator("global:add_liquidity_by_weight")
meteoraDlmmRemoveLiquidity2Discriminator = calculateDiscriminator("global:remove_liquidity2") meteoraDlmmAddLiquidityOneSideDiscriminator = calculateDiscriminator("global:add_liquidity_one_side")
meteoraDlmmRemoveLiquidityByRangeDiscriminator = calculateDiscriminator("global:remove_liquidity_by_range") meteoraDlmmAddLiquidityOneSidePreciseDiscriminator = calculateDiscriminator("global:add_liquidity_one_side_precise")
meteoraDlmmRemoveLiquidityByRange2Discriminator = calculateDiscriminator("global:remove_liquidity_by_range2") meteoraDlmmAddLiquidityOneSidePrecise2Discriminator = calculateDiscriminator("global:add_liquidity_one_side_precise2")
meteoraDlmmAddLiquidityEventDiscriminator = calculateDiscriminator("event:AddLiquidity") meteoraDlmmAddLiquidityByStrategyOneSideDiscriminator = calculateDiscriminator("global:add_liquidity_by_strategy_one_side")
meteoraDlmmClaimFeeEventDiscriminator = calculateDiscriminator("event:ClaimFee") meteoraDlmmClaimFeeDiscriminator = calculateDiscriminator("global:claim_fee")
meteoraDlmmClaimFee2EventDiscriminator = calculateDiscriminator("event:ClaimFee2") meteoraDlmmClaimFee2Discriminator = calculateDiscriminator("global:claim_fee2")
meteoraDlmmPositionCloseEventDiscriminator = calculateDiscriminator("event:PositionClose") meteoraDlmmRebalanceLiquidityDiscriminator = calculateDiscriminator("global:rebalance_liquidity")
meteoraDlmmPositionCreateEventDiscriminator = calculateDiscriminator("event:PositionCreate") meteoraDlmmRemoveAllLiquidityDiscriminator = calculateDiscriminator("global:remove_all_liquidity")
meteoraDlmmRebalancingEventDiscriminator = calculateDiscriminator("event:Rebalancing") meteoraDlmmRemoveLiquidityDiscriminator = calculateDiscriminator("global:remove_liquidity")
meteoraDlmmRemoveLiquidityEventDiscriminator = calculateDiscriminator("event:RemoveLiquidity") meteoraDlmmRemoveLiquidity2Discriminator = calculateDiscriminator("global:remove_liquidity2")
meteoraDlmmRemoveLiquidityByRangeDiscriminator = calculateDiscriminator("global:remove_liquidity_by_range")
meteoraDlmmRemoveLiquidityByRange2Discriminator = calculateDiscriminator("global:remove_liquidity_by_range2")
meteoraDlmmAddLiquidityEventDiscriminator = calculateDiscriminator("event:AddLiquidity")
meteoraDlmmClaimFeeEventDiscriminator = calculateDiscriminator("event:ClaimFee")
meteoraDlmmClaimFee2EventDiscriminator = calculateDiscriminator("event:ClaimFee2")
meteoraDlmmPositionCloseEventDiscriminator = calculateDiscriminator("event:PositionClose")
meteoraDlmmPositionCreateEventDiscriminator = calculateDiscriminator("event:PositionCreate")
meteoraDlmmRebalancingEventDiscriminator = calculateDiscriminator("event:Rebalancing")
meteoraDlmmRemoveLiquidityEventDiscriminator = calculateDiscriminator("event:RemoveLiquidity")
) )
var ( var (

View File

@@ -66,6 +66,13 @@ type dlmmPositionCloseEvent struct {
Owner solana.PublicKey Owner solana.PublicKey
} }
type dlmmLbPairCreateEvent struct {
LbPair solana.PublicKey
BinStep uint16
TokenX solana.PublicKey
TokenY solana.PublicKey
}
type dlmmClaimFeeInnerEvent struct { type dlmmClaimFeeInnerEvent struct {
LbPair solana.PublicKey LbPair solana.PublicKey
Position solana.PublicKey Position solana.PublicKey
@@ -178,6 +185,53 @@ type dlmmAddLiquidityByWeightArgs struct {
LiquidityParameter dlmmLiquidityParameterByWeight 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 { type dlmmRemoveLiquidityArgs struct {
BinLiquidityRemoval []dlmmBinLiquidityReduction BinLiquidityRemoval []dlmmBinLiquidityReduction
} }
@@ -264,6 +318,16 @@ type dlmmLiquidityAccounts struct {
tokenYProgramIdx 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 { var meteoraDlmmEventAuthority = func() solana.PublicKey {
key, _, err := solana.FindProgramAddress([][]byte{[]byte("__event_authority")}, meteoraDlmmProgram) key, _, err := solana.FindProgramAddress([][]byte{[]byte("__event_authority")}, meteoraDlmmProgram)
if err != nil { if err != nil {
@@ -283,7 +347,11 @@ func metaoradlmmParser(tx *Tx, instruction Instruction, innerInstructions InnerI
discriminator := *(*[8]byte)(decode[:8]) discriminator := *(*[8]byte)(decode[:8])
switch discriminator { switch discriminator {
case meteoraInitializeLbPairDiscriminator: case meteoraInitializeCustomizablePermissionlessLbPairDiscriminator,
meteoraInitializeCustomizablePermissionlessLbPair2Discriminator,
meteoraInitializeLbPairDiscriminator,
meteoraInitializeLbPair2Discriminator,
meteoraInitializePermissionLbPairDiscriminator:
return metaoradlmmInitializeParser(tx, instruction, innerInstructions, offset) return metaoradlmmInitializeParser(tx, instruction, innerInstructions, offset)
case meteoraDlmmInitializePositionDiscriminator, meteoraDlmmInitializePosition2Discriminator, case meteoraDlmmInitializePositionDiscriminator, meteoraDlmmInitializePosition2Discriminator,
meteoraDlmmInitializePositionByOperatorDiscriminator, meteoraDlmmInitializePositionPdaDiscriminator: meteoraDlmmInitializePositionByOperatorDiscriminator, meteoraDlmmInitializePositionPdaDiscriminator:
@@ -294,13 +362,15 @@ func metaoradlmmParser(tx *Tx, instruction Instruction, innerInstructions InnerI
return metaoradlmmSwap2Parser(tx, instruction, innerInstructions, offset) return metaoradlmmSwap2Parser(tx, instruction, innerInstructions, offset)
case meteoraDlmmAddLiquidityDiscriminator, meteoraDlmmAddLiquidity2Discriminator, case meteoraDlmmAddLiquidityDiscriminator, meteoraDlmmAddLiquidity2Discriminator,
meteoraDlmmAddLiquidityByStrategyDiscriminator, meteoraDlmmAddLiquidityByStrategy2Discriminator, meteoraDlmmAddLiquidityByStrategyDiscriminator, meteoraDlmmAddLiquidityByStrategy2Discriminator,
meteoraDlmmAddLiquidityByWeightDiscriminator: meteoraDlmmAddLiquidityByWeightDiscriminator, meteoraDlmmAddLiquidityOneSideDiscriminator,
meteoraDlmmAddLiquidityOneSidePreciseDiscriminator, meteoraDlmmAddLiquidityOneSidePrecise2Discriminator,
meteoraDlmmAddLiquidityByStrategyOneSideDiscriminator:
return metaoradlmmAddLiquidityParser(tx, instruction, innerInstructions, offset) return metaoradlmmAddLiquidityParser(tx, instruction, innerInstructions, offset)
case meteoraDlmmClaimFeeDiscriminator, meteoraDlmmClaimFee2Discriminator: case meteoraDlmmClaimFeeDiscriminator, meteoraDlmmClaimFee2Discriminator:
return metaoradlmmClaimFeeParser(tx, instruction, innerInstructions, offset) return metaoradlmmClaimFeeParser(tx, instruction, innerInstructions, offset)
case meteoraDlmmRebalanceLiquidityDiscriminator: case meteoraDlmmRebalanceLiquidityDiscriminator:
return metaoradlmmRebalanceLiquidityParser(tx, instruction, innerInstructions, offset) return metaoradlmmRebalanceLiquidityParser(tx, instruction, innerInstructions, offset)
case meteoraDlmmRemoveLiquidityDiscriminator, meteoraDlmmRemoveLiquidity2Discriminator, case meteoraDlmmRemoveAllLiquidityDiscriminator, meteoraDlmmRemoveLiquidityDiscriminator, meteoraDlmmRemoveLiquidity2Discriminator,
meteoraDlmmRemoveLiquidityByRangeDiscriminator, meteoraDlmmRemoveLiquidityByRange2Discriminator: meteoraDlmmRemoveLiquidityByRangeDiscriminator, meteoraDlmmRemoveLiquidityByRange2Discriminator:
return metaoradlmmRemoveLiquidityParser(tx, instruction, innerInstructions, offset) return metaoradlmmRemoveLiquidityParser(tx, instruction, innerInstructions, offset)
case meteoraDlmmClosePositionDiscriminator, meteoraDlmmClosePosition2Discriminator, meteoraDlmmClosePositionIfEmptyDiscriminator: case meteoraDlmmClosePositionDiscriminator, meteoraDlmmClosePosition2Discriminator, meteoraDlmmClosePositionIfEmptyDiscriminator:
@@ -310,53 +380,131 @@ func metaoradlmmParser(tx *Tx, instruction Instruction, innerInstructions InnerI
} }
} }
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) { func metaoradlmmInitializeParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
market := tx.rawTx.accountList[instruction.Accounts[0]] accounts, err := resolveDlmmInitializeAccounts(tx.rawTx, instruction.Data, instruction.Accounts)
token0 := tx.rawTx.accountList[instruction.Accounts[2]] if err != nil {
token1 := tx.rawTx.accountList[instruction.Accounts[3]] 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] entryContract := tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
var baseDecimals uint8 findMintDecimals := func(mint solana.PublicKey) uint8 {
var quoteDecimals uint8 for _, acc := range tx.rawTx.Meta.PostTokenBalances {
for _, acc := range tx.rawTx.Meta.PostTokenBalances { if acc.MintAccount.Equals(mint) {
if acc.MintAccount.Equals(token0) { return uint8(acc.UITokenAmount.Decimals)
baseDecimals = uint8(acc.UITokenAmount.Decimals) }
}
if acc.MintAccount.Equals(token1) {
quoteDecimals = uint8(acc.UITokenAmount.Decimals)
} }
return 0
} }
swap := Swap{ swap := Swap{
Program: SolProgramMeteoraDLMM, Program: SolProgramMeteoraDLMM,
Event: "create", Event: "create",
Pool: market, Pool: accounts.pool,
BaseMint: token0, BaseMint: accounts.token0,
QuoteMint: token1, QuoteMint: accounts.token1,
BaseTokenProgram: tx.rawTx.accountList[instruction.Accounts[11]], BaseTokenProgram: accounts.baseTokenProgram,
QuoteTokenProgram: tx.rawTx.accountList[instruction.Accounts[12]], QuoteTokenProgram: accounts.quoteTokenProgram,
Creator: tx.rawTx.accountList[0], Creator: tx.rawTx.accountList[0],
BaseMintDecimals: baseDecimals, BaseMintDecimals: findMintDecimals(accounts.token0),
QuoteMintDecimals: quoteDecimals, QuoteMintDecimals: findMintDecimals(accounts.token1),
User: tx.rawTx.accountList[instruction.Accounts[8]], User: accounts.user,
EntryContract: entryContract, EntryContract: entryContract,
} }
var prefixLen = offset[1] createEvent, nextOffset, found, err := dlmmLbPairCreateEventFromInnerInstructions(innerInstructions, instruction, offset)
inners, err := getInnerInstructions(innerInstructions, prefixLen)
if err != nil { if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("pump create get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1]) return nil, nextOffset, err
} }
var programIndex = instruction.ProgramIDIndex if found {
offset = nextOffset
for innerIndex, innerInstr := range inners { if !createEvent.LbPair.IsZero() {
if innerInstr.ProgramIDIndex == programIndex && len(innerInstr.Data) >= 16 && bytes.Equal(innerInstr.Data[:8], pumpEventDiscriminator[:]) && bytes.Equal(innerInstr.Data[8:16], meteoraInitializeLbPairEventDiscriminator[:]) { swap.Pool = createEvent.LbPair
if offset[1] == 0 {
offset[0] += 1
} else {
offset[1] = uint(innerIndex) + 1 + prefixLen
}
break
} }
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 return []Swap{swap}, offset, nil
} }
@@ -410,10 +558,13 @@ func metaoradlmmPositionCreateParser(tx *Tx, instruction Instruction, innerInstr
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm create position accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1]) return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm create position accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1])
} }
createEvent, nextOffset, err := dlmmPositionCreateEventFromInnerInstructions(innerInstructions, instruction, offset) createEvent, nextOffset, found, err := dlmmPositionCreateEventFromInnerInstructions(innerInstructions, instruction, offset)
if err != nil { if err != nil {
return nil, nextOffset, err return nil, nextOffset, err
} }
if !found {
return nil, nextOffset, InstructionIgnoredError
}
offset = nextOffset offset = nextOffset
if !createEvent.LbPair.IsZero() { if !createEvent.LbPair.IsZero() {
@@ -667,6 +818,18 @@ func metaoradlmmSwapParser(tx *Tx, instruction Instruction, innerInstructions In
userQuote = userQuote.Add(decimal.NewFromUint64(solAmount)) 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{ swap := Swap{
Program: SolProgramMeteoraDLMM, Program: SolProgramMeteoraDLMM,
@@ -682,6 +845,13 @@ func metaoradlmmSwapParser(tx *Tx, instruction Instruction, innerInstructions In
User: eventUser, User: eventUser,
BaseAmount: baseAmount, BaseAmount: baseAmount,
QuoteAmount: quoteAmount, QuoteAmount: quoteAmount,
FeeAmount: feeAmount,
FeeBps: dlmmSwapFeeBpsString(swapEvent.FeeBps),
LpFeeAmount: lpFeeAmount,
FeeSide: feeSide,
FeeMint: feeMint,
FeeTokenProgram: feeTokenProgram,
FeeMintDecimals: feeDecimals,
BaseReserve: baseReserve, BaseReserve: baseReserve,
QuoteReserve: quoteReserve, QuoteReserve: quoteReserve,
UserBaseBalance: userBase, UserBaseBalance: userBase,
@@ -699,6 +869,39 @@ func metaoradlmmSwap2Parser(tx *Tx, instruction Instruction, innerInstructions I
return metaoradlmmSwapParser(tx, instruction, innerInstructions, offset) 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 dlmmSwapFeeBpsString(feeBps agbinary.Uint128) string {
return feeBps.DecimalString()
}
func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
result := tx.rawTx result := tx.rawTx
@@ -726,7 +929,7 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
weightDist []dlmmBinLiquidityDistributionByWeight weightDist []dlmmBinLiquidityDistributionByWeight
startBinId int32 startBinId int32
endBinId int32 endBinId int32
hasRange bool oneSide bool
) )
switch discriminator { switch discriminator {
@@ -739,7 +942,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
amountY = args.LiquidityParameter.AmountY amountY = args.LiquidityParameter.AmountY
binDist = args.LiquidityParameter.BinLiquidityDist binDist = args.LiquidityParameter.BinLiquidityDist
startBinId, endBinId = dlmmMinMaxBinIdFromDistribution(binDist) startBinId, endBinId = dlmmMinMaxBinIdFromDistribution(binDist)
hasRange = len(binDist) > 0
case meteoraDlmmAddLiquidity2Discriminator: case meteoraDlmmAddLiquidity2Discriminator:
var args dlmmAddLiquidity2Args var args dlmmAddLiquidity2Args
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
@@ -749,7 +951,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
amountY = args.LiquidityParameter.AmountY amountY = args.LiquidityParameter.AmountY
binDist = args.LiquidityParameter.BinLiquidityDist binDist = args.LiquidityParameter.BinLiquidityDist
startBinId, endBinId = dlmmMinMaxBinIdFromDistribution(binDist) startBinId, endBinId = dlmmMinMaxBinIdFromDistribution(binDist)
hasRange = len(binDist) > 0
case meteoraDlmmAddLiquidityByStrategyDiscriminator: case meteoraDlmmAddLiquidityByStrategyDiscriminator:
var args dlmmAddLiquidityByStrategyArgs var args dlmmAddLiquidityByStrategyArgs
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
@@ -759,7 +960,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
amountY = args.LiquidityParameter.AmountY amountY = args.LiquidityParameter.AmountY
startBinId = args.LiquidityParameter.StrategyParameters.MinBinId startBinId = args.LiquidityParameter.StrategyParameters.MinBinId
endBinId = args.LiquidityParameter.StrategyParameters.MaxBinId endBinId = args.LiquidityParameter.StrategyParameters.MaxBinId
hasRange = true
case meteoraDlmmAddLiquidityByStrategy2Discriminator: case meteoraDlmmAddLiquidityByStrategy2Discriminator:
var args dlmmAddLiquidityByStrategy2Args var args dlmmAddLiquidityByStrategy2Args
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
@@ -769,7 +969,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
amountY = args.LiquidityParameter.AmountY amountY = args.LiquidityParameter.AmountY
startBinId = args.LiquidityParameter.StrategyParameters.MinBinId startBinId = args.LiquidityParameter.StrategyParameters.MinBinId
endBinId = args.LiquidityParameter.StrategyParameters.MaxBinId endBinId = args.LiquidityParameter.StrategyParameters.MaxBinId
hasRange = true
case meteoraDlmmAddLiquidityByWeightDiscriminator: case meteoraDlmmAddLiquidityByWeightDiscriminator:
var args dlmmAddLiquidityByWeightArgs var args dlmmAddLiquidityByWeightArgs
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
@@ -779,16 +978,40 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
amountY = args.LiquidityParameter.AmountY amountY = args.LiquidityParameter.AmountY
weightDist = args.LiquidityParameter.BinLiquidityDist weightDist = args.LiquidityParameter.BinLiquidityDist
startBinId, endBinId = dlmmMinMaxBinIdFromWeightDistribution(weightDist) startBinId, endBinId = dlmmMinMaxBinIdFromWeightDistribution(weightDist)
hasRange = len(weightDist) > 0 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: default:
return nil, increaseOffset(offset), InstructionIgnoredError return nil, increaseOffset(offset), InstructionIgnoredError
} }
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])
}
addEvent, nextOffset, err := dlmmAddLiquidityEventFromInnerInstructions(innerInstructions, instruction, offset) addEvent, nextOffset, err := dlmmAddLiquidityEventFromInnerInstructions(innerInstructions, instruction, offset)
if err != nil { if err != nil {
return nil, nextOffset, err return nil, nextOffset, err
@@ -797,14 +1020,17 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
amountX = addEvent.Amounts[0] amountX = addEvent.Amounts[0]
amountY = addEvent.Amounts[1] amountY = addEvent.Amounts[1]
binChanges := []DlmmBinLiquidityChange(nil) if oneSide {
if len(binDist) > 0 { swaps, err := dlmmBuildOneSideAddSwap(tx, instruction, addEvent, startBinId, endBinId, entryContract)
binChanges = dlmmBinChangesFromDistribution(amountX, amountY, binDist) if err != nil {
} else if len(weightDist) > 0 { return nil, offset, err
// Weight-only params do not preserve per-side amounts for each bin, so keep the affected range only. }
binChanges = dlmmBinChangesFromRange(startBinId, endBinId, 0) return swaps, offset, nil
} else if hasRange { }
binChanges = dlmmBinChangesFromRange(startBinId, endBinId, 0)
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] pool := result.accountList[accounts.poolIdx]
@@ -837,7 +1063,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
baseAmount = amountYDec baseAmount = amountYDec
quoteAmount = amountXDec quoteAmount = amountXDec
} }
eventUser := result.accountList[accounts.userIdx] eventUser := result.accountList[accounts.userIdx]
if !addEvent.From.IsZero() { if !addEvent.From.IsZero() {
eventUser = addEvent.From eventUser = addEvent.From
@@ -898,7 +1123,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
ActiveBinId: addEvent.ActiveBinId, ActiveBinId: addEvent.ActiveBinId,
StartBinId: startBinId, StartBinId: startBinId,
EndBinId: endBinId, EndBinId: endBinId,
BinChanges: binChanges,
PositionAccount: result.accountList[accounts.positionIdx], PositionAccount: result.accountList[accounts.positionIdx],
} }
@@ -926,19 +1150,19 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
discriminator := *(*[8]byte)(decode[:8]) discriminator := *(*[8]byte)(decode[:8])
var ( var (
binChanges []DlmmBinLiquidityChange
startBinId int32 startBinId int32
endBinId int32 endBinId int32
removeBp int32 removeBp int32
) )
switch discriminator { switch discriminator {
case meteoraDlmmRemoveAllLiquidityDiscriminator:
removeBp = 10000
case meteoraDlmmRemoveLiquidityDiscriminator: case meteoraDlmmRemoveLiquidityDiscriminator:
var args dlmmRemoveLiquidityArgs var args dlmmRemoveLiquidityArgs
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { 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]) return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity decode error: %v, offset, %d, %d", err, offset[0], offset[1])
} }
binChanges = dlmmBinChangesFromReduction(args.BinLiquidityRemoval)
startBinId, endBinId = dlmmMinMaxBinIdFromReduction(args.BinLiquidityRemoval) startBinId, endBinId = dlmmMinMaxBinIdFromReduction(args.BinLiquidityRemoval)
removeBp = dlmmCommonRemoveBp(args.BinLiquidityRemoval) removeBp = dlmmCommonRemoveBp(args.BinLiquidityRemoval)
case meteoraDlmmRemoveLiquidity2Discriminator: case meteoraDlmmRemoveLiquidity2Discriminator:
@@ -946,7 +1170,6 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { 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]) return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity2 decode error: %v, offset, %d, %d", err, offset[0], offset[1])
} }
binChanges = dlmmBinChangesFromReduction(args.BinLiquidityRemoval)
startBinId, endBinId = dlmmMinMaxBinIdFromReduction(args.BinLiquidityRemoval) startBinId, endBinId = dlmmMinMaxBinIdFromReduction(args.BinLiquidityRemoval)
removeBp = dlmmCommonRemoveBp(args.BinLiquidityRemoval) removeBp = dlmmCommonRemoveBp(args.BinLiquidityRemoval)
case meteoraDlmmRemoveLiquidityByRangeDiscriminator: case meteoraDlmmRemoveLiquidityByRangeDiscriminator:
@@ -957,7 +1180,6 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
startBinId = args.FromBinId startBinId = args.FromBinId
endBinId = args.ToBinId endBinId = args.ToBinId
removeBp = int32(args.BpsToRemove) removeBp = int32(args.BpsToRemove)
binChanges = dlmmBinChangesFromRange(startBinId, endBinId, args.BpsToRemove)
case meteoraDlmmRemoveLiquidityByRange2Discriminator: case meteoraDlmmRemoveLiquidityByRange2Discriminator:
var args dlmmRemoveLiquidityByRange2Args var args dlmmRemoveLiquidityByRange2Args
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
@@ -966,7 +1188,6 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
startBinId = args.FromBinId startBinId = args.FromBinId
endBinId = args.ToBinId endBinId = args.ToBinId
removeBp = int32(args.BpsToRemove) removeBp = int32(args.BpsToRemove)
binChanges = dlmmBinChangesFromRange(startBinId, endBinId, args.BpsToRemove)
default: default:
return nil, increaseOffset(offset), InstructionIgnoredError return nil, increaseOffset(offset), InstructionIgnoredError
} }
@@ -1012,7 +1233,6 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
baseAmount = amountYDec baseAmount = amountYDec
quoteAmount = amountXDec quoteAmount = amountXDec
} }
eventUser := result.accountList[accounts.userIdx] eventUser := result.accountList[accounts.userIdx]
if !removeEvent.From.IsZero() { if !removeEvent.From.IsZero() {
eventUser = removeEvent.From eventUser = removeEvent.From
@@ -1074,7 +1294,6 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
StartBinId: startBinId, StartBinId: startBinId,
EndBinId: endBinId, EndBinId: endBinId,
RemoveBp: removeBp, RemoveBp: removeBp,
BinChanges: binChanges,
PositionAccount: result.accountList[accounts.positionIdx], PositionAccount: result.accountList[accounts.positionIdx],
} }
@@ -1318,7 +1537,6 @@ func metaoradlmmRebalanceLiquidityParser(tx *Tx, instruction Instruction, innerI
ActiveBinId: event.ActiveBinId, ActiveBinId: event.ActiveBinId,
StartBinId: event.OldMinBinId, StartBinId: event.OldMinBinId,
EndBinId: event.OldMaxBinId, EndBinId: event.OldMaxBinId,
BinChanges: dlmmBinChangesFromRange(event.OldMinBinId, event.OldMaxBinId, 0),
PositionAccount: result.accountList[accounts.positionIdx], PositionAccount: result.accountList[accounts.positionIdx],
}) })
} }
@@ -1344,7 +1562,6 @@ func metaoradlmmRebalanceLiquidityParser(tx *Tx, instruction Instruction, innerI
ActiveBinId: event.ActiveBinId, ActiveBinId: event.ActiveBinId,
StartBinId: event.NewMinBinId, StartBinId: event.NewMinBinId,
EndBinId: event.NewMaxBinId, EndBinId: event.NewMaxBinId,
BinChanges: dlmmBinChangesFromRange(event.NewMinBinId, event.NewMaxBinId, 0),
PositionAccount: result.accountList[accounts.positionIdx], PositionAccount: result.accountList[accounts.positionIdx],
}) })
} }
@@ -1478,11 +1695,11 @@ func dlmmRebalancingEventFromInnerInstructions(innerInstructions InnerInstructio
return dlmmRebalancingEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm rebalance liquidity event not found, offset, %d, %d", offset[0], prefixLen) 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, error) { func dlmmPositionCreateEventFromInnerInstructions(innerInstructions InnerInstructions, instruction Instruction, offset [2]uint) (dlmmPositionCreateEvent, [2]uint, bool, error) {
var prefixLen = offset[1] var prefixLen = offset[1]
inners, err := getInnerInstructions(innerInstructions, prefixLen) inners, err := getInnerInstructions(innerInstructions, prefixLen)
if err != nil { if err != nil {
return dlmmPositionCreateEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm create position get inner instructions error: %v, offset, %d, %d", err, offset[0], prefixLen) 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 { for innerIndex, innerInstr := range inners {
if innerInstr.ProgramIDIndex != instruction.ProgramIDIndex { if innerInstr.ProgramIDIndex != instruction.ProgramIDIndex {
@@ -1497,9 +1714,9 @@ func dlmmPositionCreateEventFromInnerInstructions(innerInstructions InnerInstruc
} else { } else {
offset[1] = uint(innerIndex) + 1 + prefixLen offset[1] = uint(innerIndex) + 1 + prefixLen
} }
return event, offset, nil return event, offset, true, nil
} }
return dlmmPositionCreateEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm create position event not found, offset, %d, %d", offset[0], prefixLen) return dlmmPositionCreateEvent{}, increaseOffset(offset), false, nil
} }
func dlmmPositionCloseEventFromInnerInstructions(innerInstructions InnerInstructions, instruction Instruction, offset [2]uint) (dlmmPositionCloseEvent, [2]uint, bool, error) { func dlmmPositionCloseEventFromInnerInstructions(innerInstructions InnerInstructions, instruction Instruction, offset [2]uint) (dlmmPositionCloseEvent, [2]uint, bool, error) {
@@ -1526,6 +1743,51 @@ func dlmmPositionCloseEventFromInnerInstructions(innerInstructions InnerInstruct
return dlmmPositionCloseEvent{}, increaseOffset(offset), false, 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) { func dlmmDecodeAddLiquidityEvent(data []byte) (dlmmAddLiquidityEvent, bool) {
switch { switch {
case len(data) >= 8 && bytes.Equal(data[:8], meteoraDlmmAddLiquidityEventDiscriminator[:]): case len(data) >= 8 && bytes.Equal(data[:8], meteoraDlmmAddLiquidityEventDiscriminator[:]):
@@ -1819,6 +2081,154 @@ func resolveDlmmLiquidityAccounts(result *RawTx, accounts []int) (dlmmLiquidityA
}, nil }, 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) { func resolveDlmmClaimFeeAccounts(result *RawTx, data []byte, accounts []int) (dlmmLiquidityAccounts, error) {
if len(data) < 8 { if len(data) < 8 {
return dlmmLiquidityAccounts{}, fmt.Errorf("instruction data too short") return dlmmLiquidityAccounts{}, fmt.Errorf("instruction data too short")
@@ -1990,56 +2400,67 @@ func dlmmTokenBalanceMeta(result *RawTx, accountIndex int) (TokenBalance, bool)
return TokenBalance{}, false return TokenBalance{}, false
} }
func dlmmBinChangesFromDistribution(amountX, amountY uint64, dist []dlmmBinLiquidityDistribution) []DlmmBinLiquidityChange { func dlmmAllocateByWeights(total uint64, weights []uint64) []decimal.Decimal {
if len(dist) == 0 { if len(weights) == 0 {
return nil return nil
} }
totalX := decimal.NewFromUint64(amountX)
totalY := decimal.NewFromUint64(amountY) sumWeights := uint64(0)
denom := decimal.NewFromInt(10000) for _, weight := range weights {
changes := make([]DlmmBinLiquidityChange, 0, len(dist)) sumWeights += weight
for _, item := range dist {
x := totalX.Mul(decimal.NewFromInt(int64(item.DistributionX))).Div(denom).Truncate(0)
y := totalY.Mul(decimal.NewFromInt(int64(item.DistributionY))).Div(denom).Truncate(0)
changes = append(changes, DlmmBinLiquidityChange{
BinId: item.BinId,
AmountX: x,
AmountY: y,
})
} }
return changes 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 dlmmBinChangesFromReduction(reduction []dlmmBinLiquidityReduction) []DlmmBinLiquidityChange { func dlmmApplySignedAllocation(values []decimal.Decimal, negative bool) []decimal.Decimal {
if len(reduction) == 0 { if !negative {
return nil return values
} }
changes := make([]DlmmBinLiquidityChange, 0, len(reduction)) out := make([]decimal.Decimal, len(values))
for _, item := range reduction { for i, value := range values {
changes = append(changes, DlmmBinLiquidityChange{ out[i] = value.Neg()
BinId: item.BinId,
BpsToRemove: item.BpsToRemove,
})
} }
return changes return out
} }
func dlmmBinChangesFromRange(startBinId, endBinId int32, bpsToRemove uint16) []DlmmBinLiquidityChange { func dlmmMinMaxBinIDFromCompressedDeposits(bins []dlmmCompressedBinDepositAmount) (startBinID, endBinID int32) {
if startBinId > endBinId { if len(bins) == 0 {
startBinId, endBinId = endBinId, startBinId return 0, 0
} }
count := int(endBinId-startBinId) + 1 startBinID = bins[0].BinID
if count <= 0 { endBinID = bins[0].BinID
return nil for _, bin := range bins[1:] {
if bin.BinID < startBinID {
startBinID = bin.BinID
}
if bin.BinID > endBinID {
endBinID = bin.BinID
}
} }
changes := make([]DlmmBinLiquidityChange, 0, count) return startBinID, endBinID
for binId := startBinId; binId <= endBinId; binId++ {
changes = append(changes, DlmmBinLiquidityChange{
BinId: binId,
BpsToRemove: bpsToRemove,
})
}
return changes
} }
func dlmmCommonRemoveBp(reduction []dlmmBinLiquidityReduction) int32 { func dlmmCommonRemoveBp(reduction []dlmmBinLiquidityReduction) int32 {

424
metaoradlmm_test.go Normal file
View File

@@ -0,0 +1,424 @@
package pump_parser
import (
"bytes"
"testing"
agbinary "github.com/gagliardetto/binary"
"github.com/gagliardetto/solana-go"
"github.com/shopspring/decimal"
)
func testPublicKey(seed byte) solana.PublicKey {
buf := make([]byte, solana.PublicKeyLength)
for i := range buf {
buf[i] = seed
}
return solana.PublicKeyFromBytes(buf)
}
func seqInts(n int) []int {
out := make([]int, n)
for i := range out {
out[i] = i
}
return out
}
func mustBorshEncode(t *testing.T, value any) []byte {
t.Helper()
var buf bytes.Buffer
if err := agbinary.NewBorshEncoder(&buf).Encode(value); err != nil {
t.Fatalf("borsh encode failed: %v", err)
}
return buf.Bytes()
}
func TestMeteoraDlmmInitializeParserCompatibility(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
discriminator [8]byte
accountCount int
wantPoolPos int
wantBaseMintPos int
wantQuoteMintPos int
wantUserPos int
wantBaseProgramPos int
wantQuoteProgramPos int
}{
{
name: "initialize_lb_pair",
discriminator: meteoraInitializeLbPairDiscriminator,
accountCount: 14,
wantPoolPos: 0,
wantBaseMintPos: 2,
wantQuoteMintPos: 3,
wantUserPos: 8,
wantBaseProgramPos: 9,
wantQuoteProgramPos: 9,
},
{
name: "initialize_lb_pair2",
discriminator: meteoraInitializeLbPair2Discriminator,
accountCount: 16,
wantPoolPos: 0,
wantBaseMintPos: 2,
wantQuoteMintPos: 3,
wantUserPos: 8,
wantBaseProgramPos: 11,
wantQuoteProgramPos: 12,
},
{
name: "initialize_customizable_permissionless_lb_pair",
discriminator: meteoraInitializeCustomizablePermissionlessLbPairDiscriminator,
accountCount: 14,
wantPoolPos: 0,
wantBaseMintPos: 2,
wantQuoteMintPos: 3,
wantUserPos: 8,
wantBaseProgramPos: 9,
wantQuoteProgramPos: 9,
},
{
name: "initialize_customizable_permissionless_lb_pair2",
discriminator: meteoraInitializeCustomizablePermissionlessLbPair2Discriminator,
accountCount: 17,
wantPoolPos: 0,
wantBaseMintPos: 2,
wantQuoteMintPos: 3,
wantUserPos: 8,
wantBaseProgramPos: 11,
wantQuoteProgramPos: 12,
},
{
name: "initialize_permission_lb_pair",
discriminator: meteoraInitializePermissionLbPairDiscriminator,
accountCount: 17,
wantPoolPos: 1,
wantBaseMintPos: 3,
wantQuoteMintPos: 4,
wantUserPos: 8,
wantBaseProgramPos: 11,
wantQuoteProgramPos: 12,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
accountList := make([]solana.PublicKey, 32)
for i := range accountList {
accountList[i] = testPublicKey(byte(i + 1))
}
programIndex := 30
accountList[programIndex] = meteoraDlmmProgram
instruction := Instruction{
Accounts: seqInts(tc.accountCount),
Data: solana.Base58(tc.discriminator[:]),
ProgramIDIndex: programIndex,
}
rawTx := &RawTx{
accountList: accountList,
Meta: Meta{
PostTokenBalances: []TokenBalance{
{
MintAccount: accountList[tc.wantBaseMintPos],
UITokenAmount: UITokenAmount{
Decimals: 6,
},
},
{
MintAccount: accountList[tc.wantQuoteMintPos],
UITokenAmount: UITokenAmount{
Decimals: 9,
},
},
},
},
Transaction: Transaction{
Message: Message{
Instructions: []Instruction{instruction},
},
},
}
tx := &Tx{rawTx: rawTx}
swaps, _, err := metaoradlmmParser(tx, instruction, InnerInstructions{}, [2]uint{0, 0})
if err != nil {
t.Fatalf("metaoradlmmParser() error = %v", err)
}
if len(swaps) != 1 {
t.Fatalf("metaoradlmmParser() swaps len = %d, want 1", len(swaps))
}
swap := swaps[0]
if !swap.Pool.Equals(accountList[tc.wantPoolPos]) {
t.Fatalf("swap.Pool = %s, want %s", swap.Pool, accountList[tc.wantPoolPos])
}
if !swap.BaseMint.Equals(accountList[tc.wantBaseMintPos]) {
t.Fatalf("swap.BaseMint = %s, want %s", swap.BaseMint, accountList[tc.wantBaseMintPos])
}
if !swap.QuoteMint.Equals(accountList[tc.wantQuoteMintPos]) {
t.Fatalf("swap.QuoteMint = %s, want %s", swap.QuoteMint, accountList[tc.wantQuoteMintPos])
}
if !swap.User.Equals(accountList[tc.wantUserPos]) {
t.Fatalf("swap.User = %s, want %s", swap.User, accountList[tc.wantUserPos])
}
if !swap.BaseTokenProgram.Equals(accountList[tc.wantBaseProgramPos]) {
t.Fatalf("swap.BaseTokenProgram = %s, want %s", swap.BaseTokenProgram, accountList[tc.wantBaseProgramPos])
}
if !swap.QuoteTokenProgram.Equals(accountList[tc.wantQuoteProgramPos]) {
t.Fatalf("swap.QuoteTokenProgram = %s, want %s", swap.QuoteTokenProgram, accountList[tc.wantQuoteProgramPos])
}
if swap.BaseMintDecimals != 6 {
t.Fatalf("swap.BaseMintDecimals = %d, want 6", swap.BaseMintDecimals)
}
if swap.QuoteMintDecimals != 9 {
t.Fatalf("swap.QuoteMintDecimals = %d, want 9", swap.QuoteMintDecimals)
}
if !swap.EntryContract.Equals(meteoraDlmmProgram) {
t.Fatalf("swap.EntryContract = %s, want %s", swap.EntryContract, meteoraDlmmProgram)
}
})
}
}
func TestDlmmDecodeLbPairCreateEvent(t *testing.T) {
t.Parallel()
event := dlmmLbPairCreateEvent{
LbPair: testPublicKey(90),
BinStep: 42,
TokenX: testPublicKey(91),
TokenY: testPublicKey(92),
}
body := mustBorshEncode(t, event)
barePayload := append(append([]byte{}, meteoraInitializeLbPairEventDiscriminator[:]...), body...)
decodedBare, ok := dlmmDecodeLbPairCreateEvent(barePayload)
if !ok {
t.Fatalf("dlmmDecodeLbPairCreateEvent() failed for bare payload")
}
if decodedBare != event {
t.Fatalf("decoded bare event = %+v, want %+v", decodedBare, event)
}
anchorPayload := append(append(append([]byte{}, eventDiscriminator[:]...), meteoraInitializeLbPairEventDiscriminator[:]...), body...)
decodedAnchor, ok := dlmmDecodeLbPairCreateEvent(anchorPayload)
if !ok {
t.Fatalf("dlmmDecodeLbPairCreateEvent() failed for anchor payload")
}
if decodedAnchor != event {
t.Fatalf("decoded anchor event = %+v, want %+v", decodedAnchor, event)
}
}
func TestMeteoraDlmmInitializeParserUsesLbPairCreateEvent(t *testing.T) {
t.Parallel()
accountList := make([]solana.PublicKey, 32)
for i := range accountList {
accountList[i] = testPublicKey(byte(i + 1))
}
programIndex := 30
accountList[programIndex] = meteoraDlmmProgram
instruction := Instruction{
Accounts: seqInts(16),
Data: solana.Base58(meteoraInitializeLbPair2Discriminator[:]),
ProgramIDIndex: programIndex,
}
event := dlmmLbPairCreateEvent{
LbPair: testPublicKey(111),
BinStep: 25,
TokenX: testPublicKey(112),
TokenY: testPublicKey(113),
}
innerEventData := append(
append(append([]byte{}, eventDiscriminator[:]...), meteoraInitializeLbPairEventDiscriminator[:]...),
mustBorshEncode(t, event)...,
)
rawTx := &RawTx{
accountList: accountList,
Meta: Meta{
PostTokenBalances: []TokenBalance{
{
MintAccount: accountList[2],
UITokenAmount: UITokenAmount{
Decimals: 6,
},
},
{
MintAccount: accountList[3],
UITokenAmount: UITokenAmount{
Decimals: 9,
},
},
},
InnerInstructions: []InnerInstructions{
{
Index: 0,
Instructions: []Instruction{
{
ProgramIDIndex: programIndex,
Data: solana.Base58(innerEventData),
},
},
},
},
},
Transaction: Transaction{
Message: Message{
Instructions: []Instruction{instruction},
},
},
}
tx := &Tx{rawTx: rawTx}
swaps, nextOffset, err := metaoradlmmParser(tx, instruction, rawTx.Meta.InnerInstructions[0], [2]uint{0, 0})
if err != nil {
t.Fatalf("metaoradlmmParser() error = %v", err)
}
if len(swaps) != 1 {
t.Fatalf("metaoradlmmParser() swaps len = %d, want 1", len(swaps))
}
swap := swaps[0]
if !swap.Pool.Equals(event.LbPair) {
t.Fatalf("swap.Pool = %s, want event %s", swap.Pool, event.LbPair)
}
if !swap.BaseMint.Equals(event.TokenX) {
t.Fatalf("swap.BaseMint = %s, want event %s", swap.BaseMint, event.TokenX)
}
if !swap.QuoteMint.Equals(event.TokenY) {
t.Fatalf("swap.QuoteMint = %s, want event %s", swap.QuoteMint, event.TokenY)
}
if nextOffset != ([2]uint{1, 0}) {
t.Fatalf("nextOffset = %#v, want [2]uint{1, 0}", nextOffset)
}
}
func TestDlmmSwapFeeInfo(t *testing.T) {
t.Parallel()
baseMint := testPublicKey(1)
quoteMint := testPublicKey(2)
baseProgram := testPublicKey(3)
quoteProgram := testPublicKey(4)
testCases := []struct {
name string
baseIsX bool
swapForY bool
wantFeeSide string
wantFeeMint solana.PublicKey
wantFeeProg solana.PublicKey
wantDecimals uint8
}{
{
name: "x is base and input is x",
baseIsX: true,
swapForY: true,
wantFeeSide: "base",
wantFeeMint: baseMint,
wantFeeProg: baseProgram,
wantDecimals: 6,
},
{
name: "x is base and input is y",
baseIsX: true,
swapForY: false,
wantFeeSide: "quote",
wantFeeMint: quoteMint,
wantFeeProg: quoteProgram,
wantDecimals: 9,
},
{
name: "y is base and input is x",
baseIsX: false,
swapForY: true,
wantFeeSide: "quote",
wantFeeMint: quoteMint,
wantFeeProg: quoteProgram,
wantDecimals: 9,
},
{
name: "y is base and input is y",
baseIsX: false,
swapForY: false,
wantFeeSide: "base",
wantFeeMint: baseMint,
wantFeeProg: baseProgram,
wantDecimals: 6,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
feeAmount, feeSide, feeMint, feeProgram, feeDecimals := dlmmSwapFeeInfo(
tc.baseIsX,
tc.swapForY,
123,
baseMint,
quoteMint,
baseProgram,
quoteProgram,
6,
9,
)
if !feeAmount.Equal(decimal.NewFromInt(123)) {
t.Fatalf("feeAmount = %s, want 123", feeAmount)
}
if feeSide != tc.wantFeeSide {
t.Fatalf("feeSide = %s, want %s", feeSide, tc.wantFeeSide)
}
if !feeMint.Equals(tc.wantFeeMint) {
t.Fatalf("feeMint = %s, want %s", feeMint, tc.wantFeeMint)
}
if !feeProgram.Equals(tc.wantFeeProg) {
t.Fatalf("feeProgram = %s, want %s", feeProgram, tc.wantFeeProg)
}
if feeDecimals != tc.wantDecimals {
t.Fatalf("feeDecimals = %d, want %d", feeDecimals, tc.wantDecimals)
}
})
}
}
func TestDlmmSwapLpFeeAmount(t *testing.T) {
t.Parallel()
lpFee := dlmmSwapLpFeeAmount(100, 15, 5)
if !lpFee.Equal(decimal.NewFromInt(80)) {
t.Fatalf("lpFee = %s, want 80", lpFee)
}
lpFee = dlmmSwapLpFeeAmount(10, 8, 5)
if !lpFee.IsZero() {
t.Fatalf("lpFee should floor at zero, got %s", lpFee)
}
}
func TestDlmmSwapFeeBpsString(t *testing.T) {
t.Parallel()
feeBps := agbinary.Uint128{Lo: 12345}
if got := dlmmSwapFeeBpsString(feeBps); got != "12345" {
t.Fatalf("dlmmSwapFeeBpsString() = %s, want 12345", got)
}
}

View File

@@ -872,7 +872,9 @@ func ConvertYellowstoneGrpcTransactionToSolanaTransaction(y *pb.SubscribeUpdateT
} }
sTx.Meta.Fee = meta.Fee sTx.Meta.Fee = meta.Fee
//sTx.Meta.InnerInstructions = meta.InnerInstructions //sTx.Meta.InnerInstructions = meta.InnerInstructions
sTx.Meta.ComputeUnitsConsumed = *meta.ComputeUnitsConsumed if meta.ComputeUnitsConsumed != nil {
sTx.Meta.ComputeUnitsConsumed = *meta.ComputeUnitsConsumed
}
for _, innerInstr := range meta.InnerInstructions { for _, innerInstr := range meta.InnerInstructions {
var instrs []Instruction var instrs []Instruction
for _, instr := range innerInstr.Instructions { for _, instr := range innerInstr.Instructions {

23
tx.go
View File

@@ -48,23 +48,22 @@ type Swap struct {
AfterSOLBalance decimal.Decimal AfterSOLBalance decimal.Decimal
//For meteora dlmm //For meteora dlmm
ActiveBinId int32 ActiveBinId int32
StartBinId int32 StartBinId int32
EndBinId int32 EndBinId int32
RemoveBp int32 RemoveBp int32
BinChanges []DlmmBinLiquidityChange
PositionAccount solana.PublicKey PositionAccount solana.PublicKey
FeeAmount decimal.Decimal
FeeBps string
LpFeeAmount decimal.Decimal
FeeSide string
FeeMint solana.PublicKey
FeeTokenProgram solana.PublicKey
FeeMintDecimals uint8
ConsumeUnit uint64 ConsumeUnit uint64
} }
type DlmmBinLiquidityChange struct {
BinId int32
AmountX decimal.Decimal
AmountY decimal.Decimal
BpsToRemove uint16
}
type platformInfo struct { type platformInfo struct {
Platform string Platform string
PlatformFee decimal.Decimal PlatformFee decimal.Decimal