Update dlmm fee

This commit is contained in:
bijianing97
2026-04-11 08:27:34 +08:00
parent d9a214b4b4
commit 0cc843b370
5 changed files with 845 additions and 173 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()
}
}

View File

@@ -68,7 +68,11 @@ var (
) )
var ( var (
meteoraInitializeLbPairDiscriminator = calculateDiscriminator("global:initialize_lb_pair2") meteoraInitializeCustomizablePermissionlessLbPairDiscriminator = calculateDiscriminator("global:initialize_customizable_permissionless_lb_pair")
meteoraInitializeCustomizablePermissionlessLbPair2Discriminator = calculateDiscriminator("global:initialize_customizable_permissionless_lb_pair2")
meteoraInitializeLbPairDiscriminator = calculateDiscriminator("global:initialize_lb_pair")
meteoraInitializeLbPair2Discriminator = calculateDiscriminator("global:initialize_lb_pair2")
meteoraInitializePermissionLbPairDiscriminator = calculateDiscriminator("global:initialize_permission_lb_pair")
meteoraInitializeLbPairEventDiscriminator = calculateDiscriminator("event:LbPairCreate") meteoraInitializeLbPairEventDiscriminator = calculateDiscriminator("event:LbPairCreate")
meteoraDlmmSwapDiscriminator = calculateDiscriminator("global:swap") meteoraDlmmSwapDiscriminator = calculateDiscriminator("global:swap")
meteoraDlmmSwap2Discriminator = calculateDiscriminator("global:swap2") meteoraDlmmSwap2Discriminator = calculateDiscriminator("global:swap2")

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
@@ -340,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:
@@ -369,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(token0) { if acc.MintAccount.Equals(mint) {
baseDecimals = uint8(acc.UITokenAmount.Decimals) return 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
} }
@@ -729,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,
@@ -744,6 +845,12 @@ func metaoradlmmSwapParser(tx *Tx, instruction Instruction, innerInstructions In
User: eventUser, User: eventUser,
BaseAmount: baseAmount, BaseAmount: baseAmount,
QuoteAmount: quoteAmount, QuoteAmount: quoteAmount,
FeeAmount: feeAmount,
LpFeeAmount: lpFeeAmount,
FeeSide: feeSide,
FeeMint: feeMint,
FeeTokenProgram: feeTokenProgram,
FeeMintDecimals: feeDecimals,
BaseReserve: baseReserve, BaseReserve: baseReserve,
QuoteReserve: quoteReserve, QuoteReserve: quoteReserve,
UserBaseBalance: userBase, UserBaseBalance: userBase,
@@ -761,6 +868,35 @@ 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 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
@@ -788,7 +924,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
weightDist []dlmmBinLiquidityDistributionByWeight weightDist []dlmmBinLiquidityDistributionByWeight
startBinId int32 startBinId int32
endBinId int32 endBinId int32
hasRange bool
oneSide bool oneSide bool
) )
@@ -802,7 +937,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 {
@@ -812,7 +946,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 {
@@ -822,7 +955,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 {
@@ -832,7 +964,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 {
@@ -842,7 +973,6 @@ 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: case meteoraDlmmAddLiquidityOneSideDiscriminator:
var args dlmmAddLiquidityOneSideArgs var args dlmmAddLiquidityOneSideArgs
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
@@ -850,7 +980,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
} }
weightDist = args.LiquidityParameter.BinLiquidityDist weightDist = args.LiquidityParameter.BinLiquidityDist
startBinId, endBinId = dlmmMinMaxBinIdFromWeightDistribution(weightDist) startBinId, endBinId = dlmmMinMaxBinIdFromWeightDistribution(weightDist)
hasRange = len(weightDist) > 0
oneSide = true oneSide = true
case meteoraDlmmAddLiquidityOneSidePreciseDiscriminator: case meteoraDlmmAddLiquidityOneSidePreciseDiscriminator:
var args dlmmAddLiquidityOneSidePreciseArgs var args dlmmAddLiquidityOneSidePreciseArgs
@@ -858,7 +987,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity one side precise decode error: %v, offset, %d, %d", err, offset[0], offset[1]) 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) startBinId, endBinId = dlmmMinMaxBinIDFromCompressedDeposits(args.Parameter.Bins)
hasRange = len(args.Parameter.Bins) > 0
oneSide = true oneSide = true
case meteoraDlmmAddLiquidityOneSidePrecise2Discriminator: case meteoraDlmmAddLiquidityOneSidePrecise2Discriminator:
var args dlmmAddLiquidityOneSidePrecise2Args var args dlmmAddLiquidityOneSidePrecise2Args
@@ -866,7 +994,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity one side precise2 decode error: %v, offset, %d, %d", err, offset[0], offset[1]) 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) startBinId, endBinId = dlmmMinMaxBinIDFromCompressedDeposits(args.LiquidityParameter.Bins)
hasRange = len(args.LiquidityParameter.Bins) > 0
oneSide = true oneSide = true
case meteoraDlmmAddLiquidityByStrategyOneSideDiscriminator: case meteoraDlmmAddLiquidityByStrategyOneSideDiscriminator:
var args dlmmAddLiquidityByStrategyOneSideArgs var args dlmmAddLiquidityByStrategyOneSideArgs
@@ -875,7 +1002,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
} }
startBinId = args.LiquidityParameter.StrategyParameters.MinBinId startBinId = args.LiquidityParameter.StrategyParameters.MinBinId
endBinId = args.LiquidityParameter.StrategyParameters.MaxBinId endBinId = args.LiquidityParameter.StrategyParameters.MaxBinId
hasRange = true
oneSide = true oneSide = true
default: default:
return nil, increaseOffset(offset), InstructionIgnoredError return nil, increaseOffset(offset), InstructionIgnoredError
@@ -890,7 +1016,7 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
amountY = addEvent.Amounts[1] amountY = addEvent.Amounts[1]
if oneSide { if oneSide {
swaps, err := dlmmBuildOneSideAddSwap(tx, instruction, addEvent, weightDist, startBinId, endBinId, hasRange, entryContract) swaps, err := dlmmBuildOneSideAddSwap(tx, instruction, addEvent, startBinId, endBinId, entryContract)
if err != nil { if err != nil {
return nil, offset, err return nil, offset, err
} }
@@ -902,16 +1028,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1]) return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1])
} }
binChanges := []DlmmBinLiquidityChange(nil)
if len(binDist) > 0 {
binChanges = dlmmBinChangesFromDistribution(amountX, amountY, binDist)
} else if len(weightDist) > 0 {
// Weight-only params do not preserve per-side amounts for each bin, so keep the affected range only.
binChanges = dlmmBinChangesFromRange(startBinId, endBinId, 0)
} else if hasRange {
binChanges = dlmmBinChangesFromRange(startBinId, endBinId, 0)
}
pool := result.accountList[accounts.poolIdx] pool := result.accountList[accounts.poolIdx]
tokenXMint := result.accountList[accounts.tokenXMintIdx] tokenXMint := result.accountList[accounts.tokenXMintIdx]
tokenYMint := result.accountList[accounts.tokenYMintIdx] tokenYMint := result.accountList[accounts.tokenYMintIdx]
@@ -942,7 +1058,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
@@ -1003,7 +1118,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],
} }
@@ -1031,7 +1145,6 @@ 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
@@ -1045,7 +1158,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 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:
@@ -1053,7 +1165,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:
@@ -1064,7 +1175,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 {
@@ -1073,7 +1183,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
} }
@@ -1119,7 +1228,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
@@ -1181,7 +1289,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],
} }
@@ -1425,7 +1532,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],
}) })
} }
@@ -1451,7 +1557,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],
}) })
} }
@@ -1633,6 +1738,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[:]):
@@ -1973,10 +2123,8 @@ func dlmmBuildOneSideAddSwap(
tx *Tx, tx *Tx,
instruction Instruction, instruction Instruction,
addEvent dlmmAddLiquidityEvent, addEvent dlmmAddLiquidityEvent,
weightDist []dlmmBinLiquidityDistributionByWeight,
startBinId int32, startBinId int32,
endBinId int32, endBinId int32,
hasRange bool,
entryContract solana.PublicKey, entryContract solana.PublicKey,
) ([]Swap, error) { ) ([]Swap, error) {
result := tx.rawTx result := tx.rawTx
@@ -1999,13 +2147,6 @@ func dlmmBuildOneSideAddSwap(
} }
} }
binChanges := []DlmmBinLiquidityChange(nil)
if len(weightDist) > 0 {
binChanges = dlmmBinChangesFromRange(startBinId, endBinId, 0)
} else if hasRange {
binChanges = dlmmBinChangesFromRange(startBinId, endBinId, 0)
}
eventUser := result.accountList[accounts.userIdx] eventUser := result.accountList[accounts.userIdx]
if !addEvent.From.IsZero() { if !addEvent.From.IsZero() {
eventUser = addEvent.From eventUser = addEvent.From
@@ -2024,7 +2165,6 @@ func dlmmBuildOneSideAddSwap(
ActiveBinId: addEvent.ActiveBinId, ActiveBinId: addEvent.ActiveBinId,
StartBinId: startBinId, StartBinId: startBinId,
EndBinId: endBinId, EndBinId: endBinId,
BinChanges: binChanges,
PositionAccount: positionAccount, PositionAccount: positionAccount,
} }
@@ -2255,56 +2395,50 @@ 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 {
if startBinId > endBinId {
startBinId, endBinId = endBinId, startBinId
}
count := int(endBinId-startBinId) + 1
if count <= 0 {
return nil
}
changes := make([]DlmmBinLiquidityChange, 0, count)
for binId := startBinId; binId <= endBinId; binId++ {
changes = append(changes, DlmmBinLiquidityChange{
BinId: binId,
BpsToRemove: bpsToRemove,
})
}
return changes
} }
func dlmmMinMaxBinIDFromCompressedDeposits(bins []dlmmCompressedBinDepositAmount) (startBinID, endBinID int32) { func dlmmMinMaxBinIDFromCompressedDeposits(bins []dlmmCompressedBinDepositAmount) (startBinID, endBinID int32) {

415
metaoradlmm_test.go Normal file
View File

@@ -0,0 +1,415 @@
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)
}
}

14
tx.go
View File

@@ -30,6 +30,12 @@ type Swap struct {
User solana.PublicKey User solana.PublicKey
BaseAmount decimal.Decimal BaseAmount decimal.Decimal
QuoteAmount decimal.Decimal QuoteAmount decimal.Decimal
FeeAmount decimal.Decimal
LpFeeAmount decimal.Decimal
FeeSide string
FeeMint solana.PublicKey
FeeTokenProgram solana.PublicKey
FeeMintDecimals uint8
BaseReserve decimal.Decimal BaseReserve decimal.Decimal
QuoteReserve decimal.Decimal QuoteReserve decimal.Decimal
@@ -52,19 +58,11 @@ type Swap struct {
StartBinId int32 StartBinId int32
EndBinId int32 EndBinId int32
RemoveBp int32 RemoveBp int32
BinChanges []DlmmBinLiquidityChange
PositionAccount solana.PublicKey PositionAccount solana.PublicKey
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