Compare commits

..

2 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
5 changed files with 857 additions and 170 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()
}
}

84
meta.go
View File

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

View File

@@ -66,6 +66,13 @@ type dlmmPositionCloseEvent struct {
Owner solana.PublicKey
}
type dlmmLbPairCreateEvent struct {
LbPair solana.PublicKey
BinStep uint16
TokenX solana.PublicKey
TokenY solana.PublicKey
}
type dlmmClaimFeeInnerEvent struct {
LbPair solana.PublicKey
Position solana.PublicKey
@@ -340,7 +347,11 @@ func metaoradlmmParser(tx *Tx, instruction Instruction, innerInstructions InnerI
discriminator := *(*[8]byte)(decode[:8])
switch discriminator {
case meteoraInitializeLbPairDiscriminator:
case meteoraInitializeCustomizablePermissionlessLbPairDiscriminator,
meteoraInitializeCustomizablePermissionlessLbPair2Discriminator,
meteoraInitializeLbPairDiscriminator,
meteoraInitializeLbPair2Discriminator,
meteoraInitializePermissionLbPairDiscriminator:
return metaoradlmmInitializeParser(tx, instruction, innerInstructions, offset)
case meteoraDlmmInitializePositionDiscriminator, meteoraDlmmInitializePosition2Discriminator,
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) {
market := tx.rawTx.accountList[instruction.Accounts[0]]
token0 := tx.rawTx.accountList[instruction.Accounts[2]]
token1 := tx.rawTx.accountList[instruction.Accounts[3]]
accounts, err := resolveDlmmInitializeAccounts(tx.rawTx, instruction.Data, instruction.Accounts)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm initialize accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1])
}
entryContract := tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
var baseDecimals uint8
var quoteDecimals uint8
for _, acc := range tx.rawTx.Meta.PostTokenBalances {
if acc.MintAccount.Equals(token0) {
baseDecimals = uint8(acc.UITokenAmount.Decimals)
}
if acc.MintAccount.Equals(token1) {
quoteDecimals = uint8(acc.UITokenAmount.Decimals)
findMintDecimals := func(mint solana.PublicKey) uint8 {
for _, acc := range tx.rawTx.Meta.PostTokenBalances {
if acc.MintAccount.Equals(mint) {
return uint8(acc.UITokenAmount.Decimals)
}
}
return 0
}
swap := Swap{
Program: SolProgramMeteoraDLMM,
Event: "create",
Pool: market,
BaseMint: token0,
QuoteMint: token1,
BaseTokenProgram: tx.rawTx.accountList[instruction.Accounts[11]],
QuoteTokenProgram: tx.rawTx.accountList[instruction.Accounts[12]],
Pool: accounts.pool,
BaseMint: accounts.token0,
QuoteMint: accounts.token1,
BaseTokenProgram: accounts.baseTokenProgram,
QuoteTokenProgram: accounts.quoteTokenProgram,
Creator: tx.rawTx.accountList[0],
BaseMintDecimals: baseDecimals,
QuoteMintDecimals: quoteDecimals,
User: tx.rawTx.accountList[instruction.Accounts[8]],
BaseMintDecimals: findMintDecimals(accounts.token0),
QuoteMintDecimals: findMintDecimals(accounts.token1),
User: accounts.user,
EntryContract: entryContract,
}
var prefixLen = offset[1]
inners, err := getInnerInstructions(innerInstructions, prefixLen)
createEvent, nextOffset, found, err := dlmmLbPairCreateEventFromInnerInstructions(innerInstructions, instruction, offset)
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
for innerIndex, innerInstr := range inners {
if innerInstr.ProgramIDIndex == programIndex && len(innerInstr.Data) >= 16 && bytes.Equal(innerInstr.Data[:8], pumpEventDiscriminator[:]) && bytes.Equal(innerInstr.Data[8:16], meteoraInitializeLbPairEventDiscriminator[:]) {
if offset[1] == 0 {
offset[0] += 1
} else {
offset[1] = uint(innerIndex) + 1 + prefixLen
}
break
if found {
offset = nextOffset
if !createEvent.LbPair.IsZero() {
swap.Pool = createEvent.LbPair
}
if !createEvent.TokenX.IsZero() {
swap.BaseMint = createEvent.TokenX
}
if !createEvent.TokenY.IsZero() {
swap.QuoteMint = createEvent.TokenY
}
swap.BaseMintDecimals = findMintDecimals(swap.BaseMint)
swap.QuoteMintDecimals = findMintDecimals(swap.QuoteMint)
}
return []Swap{swap}, offset, nil
}
@@ -729,6 +818,18 @@ func metaoradlmmSwapParser(tx *Tx, instruction Instruction, innerInstructions In
userQuote = userQuote.Add(decimal.NewFromUint64(solAmount))
}
}
feeAmount, feeSide, feeMint, feeTokenProgram, feeDecimals := dlmmSwapFeeInfo(
baseIsX,
swapForY,
swapEvent.Fee,
baseMint,
quoteMint,
baseTokenProgram,
quoteTokenProgram,
baseDecimals,
quoteDecimals,
)
lpFeeAmount := dlmmSwapLpFeeAmount(swapEvent.Fee, swapEvent.ProtocolFee, swapEvent.HostFee)
swap := Swap{
Program: SolProgramMeteoraDLMM,
@@ -744,6 +845,13 @@ func metaoradlmmSwapParser(tx *Tx, instruction Instruction, innerInstructions In
User: eventUser,
BaseAmount: baseAmount,
QuoteAmount: quoteAmount,
FeeAmount: feeAmount,
FeeBps: dlmmSwapFeeBpsString(swapEvent.FeeBps),
LpFeeAmount: lpFeeAmount,
FeeSide: feeSide,
FeeMint: feeMint,
FeeTokenProgram: feeTokenProgram,
FeeMintDecimals: feeDecimals,
BaseReserve: baseReserve,
QuoteReserve: quoteReserve,
UserBaseBalance: userBase,
@@ -761,6 +869,39 @@ func metaoradlmmSwap2Parser(tx *Tx, instruction Instruction, innerInstructions I
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) {
result := tx.rawTx
@@ -788,7 +929,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
weightDist []dlmmBinLiquidityDistributionByWeight
startBinId int32
endBinId int32
hasRange bool
oneSide bool
)
@@ -802,7 +942,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
amountY = args.LiquidityParameter.AmountY
binDist = args.LiquidityParameter.BinLiquidityDist
startBinId, endBinId = dlmmMinMaxBinIdFromDistribution(binDist)
hasRange = len(binDist) > 0
case meteoraDlmmAddLiquidity2Discriminator:
var args dlmmAddLiquidity2Args
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
@@ -812,7 +951,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
amountY = args.LiquidityParameter.AmountY
binDist = args.LiquidityParameter.BinLiquidityDist
startBinId, endBinId = dlmmMinMaxBinIdFromDistribution(binDist)
hasRange = len(binDist) > 0
case meteoraDlmmAddLiquidityByStrategyDiscriminator:
var args dlmmAddLiquidityByStrategyArgs
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
@@ -822,7 +960,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
amountY = args.LiquidityParameter.AmountY
startBinId = args.LiquidityParameter.StrategyParameters.MinBinId
endBinId = args.LiquidityParameter.StrategyParameters.MaxBinId
hasRange = true
case meteoraDlmmAddLiquidityByStrategy2Discriminator:
var args dlmmAddLiquidityByStrategy2Args
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
@@ -832,7 +969,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
amountY = args.LiquidityParameter.AmountY
startBinId = args.LiquidityParameter.StrategyParameters.MinBinId
endBinId = args.LiquidityParameter.StrategyParameters.MaxBinId
hasRange = true
case meteoraDlmmAddLiquidityByWeightDiscriminator:
var args dlmmAddLiquidityByWeightArgs
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
@@ -842,7 +978,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
amountY = args.LiquidityParameter.AmountY
weightDist = args.LiquidityParameter.BinLiquidityDist
startBinId, endBinId = dlmmMinMaxBinIdFromWeightDistribution(weightDist)
hasRange = len(weightDist) > 0
case meteoraDlmmAddLiquidityOneSideDiscriminator:
var args dlmmAddLiquidityOneSideArgs
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
@@ -850,7 +985,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
}
weightDist = args.LiquidityParameter.BinLiquidityDist
startBinId, endBinId = dlmmMinMaxBinIdFromWeightDistribution(weightDist)
hasRange = len(weightDist) > 0
oneSide = true
case meteoraDlmmAddLiquidityOneSidePreciseDiscriminator:
var args dlmmAddLiquidityOneSidePreciseArgs
@@ -858,7 +992,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])
}
startBinId, endBinId = dlmmMinMaxBinIDFromCompressedDeposits(args.Parameter.Bins)
hasRange = len(args.Parameter.Bins) > 0
oneSide = true
case meteoraDlmmAddLiquidityOneSidePrecise2Discriminator:
var args dlmmAddLiquidityOneSidePrecise2Args
@@ -866,7 +999,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])
}
startBinId, endBinId = dlmmMinMaxBinIDFromCompressedDeposits(args.LiquidityParameter.Bins)
hasRange = len(args.LiquidityParameter.Bins) > 0
oneSide = true
case meteoraDlmmAddLiquidityByStrategyOneSideDiscriminator:
var args dlmmAddLiquidityByStrategyOneSideArgs
@@ -875,7 +1007,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
}
startBinId = args.LiquidityParameter.StrategyParameters.MinBinId
endBinId = args.LiquidityParameter.StrategyParameters.MaxBinId
hasRange = true
oneSide = true
default:
return nil, increaseOffset(offset), InstructionIgnoredError
@@ -890,7 +1021,7 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
amountY = addEvent.Amounts[1]
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 {
return nil, offset, err
}
@@ -902,16 +1033,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])
}
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]
tokenXMint := result.accountList[accounts.tokenXMintIdx]
tokenYMint := result.accountList[accounts.tokenYMintIdx]
@@ -942,7 +1063,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
baseAmount = amountYDec
quoteAmount = amountXDec
}
eventUser := result.accountList[accounts.userIdx]
if !addEvent.From.IsZero() {
eventUser = addEvent.From
@@ -1003,7 +1123,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
ActiveBinId: addEvent.ActiveBinId,
StartBinId: startBinId,
EndBinId: endBinId,
BinChanges: binChanges,
PositionAccount: result.accountList[accounts.positionIdx],
}
@@ -1031,7 +1150,6 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
discriminator := *(*[8]byte)(decode[:8])
var (
binChanges []DlmmBinLiquidityChange
startBinId int32
endBinId int32
removeBp int32
@@ -1045,7 +1163,6 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
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])
}
binChanges = dlmmBinChangesFromReduction(args.BinLiquidityRemoval)
startBinId, endBinId = dlmmMinMaxBinIdFromReduction(args.BinLiquidityRemoval)
removeBp = dlmmCommonRemoveBp(args.BinLiquidityRemoval)
case meteoraDlmmRemoveLiquidity2Discriminator:
@@ -1053,7 +1170,6 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
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])
}
binChanges = dlmmBinChangesFromReduction(args.BinLiquidityRemoval)
startBinId, endBinId = dlmmMinMaxBinIdFromReduction(args.BinLiquidityRemoval)
removeBp = dlmmCommonRemoveBp(args.BinLiquidityRemoval)
case meteoraDlmmRemoveLiquidityByRangeDiscriminator:
@@ -1064,7 +1180,6 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
startBinId = args.FromBinId
endBinId = args.ToBinId
removeBp = int32(args.BpsToRemove)
binChanges = dlmmBinChangesFromRange(startBinId, endBinId, args.BpsToRemove)
case meteoraDlmmRemoveLiquidityByRange2Discriminator:
var args dlmmRemoveLiquidityByRange2Args
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
@@ -1073,7 +1188,6 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
startBinId = args.FromBinId
endBinId = args.ToBinId
removeBp = int32(args.BpsToRemove)
binChanges = dlmmBinChangesFromRange(startBinId, endBinId, args.BpsToRemove)
default:
return nil, increaseOffset(offset), InstructionIgnoredError
}
@@ -1119,7 +1233,6 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
baseAmount = amountYDec
quoteAmount = amountXDec
}
eventUser := result.accountList[accounts.userIdx]
if !removeEvent.From.IsZero() {
eventUser = removeEvent.From
@@ -1181,7 +1294,6 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
StartBinId: startBinId,
EndBinId: endBinId,
RemoveBp: removeBp,
BinChanges: binChanges,
PositionAccount: result.accountList[accounts.positionIdx],
}
@@ -1425,7 +1537,6 @@ func metaoradlmmRebalanceLiquidityParser(tx *Tx, instruction Instruction, innerI
ActiveBinId: event.ActiveBinId,
StartBinId: event.OldMinBinId,
EndBinId: event.OldMaxBinId,
BinChanges: dlmmBinChangesFromRange(event.OldMinBinId, event.OldMaxBinId, 0),
PositionAccount: result.accountList[accounts.positionIdx],
})
}
@@ -1451,7 +1562,6 @@ func metaoradlmmRebalanceLiquidityParser(tx *Tx, instruction Instruction, innerI
ActiveBinId: event.ActiveBinId,
StartBinId: event.NewMinBinId,
EndBinId: event.NewMaxBinId,
BinChanges: dlmmBinChangesFromRange(event.NewMinBinId, event.NewMaxBinId, 0),
PositionAccount: result.accountList[accounts.positionIdx],
})
}
@@ -1633,6 +1743,51 @@ func dlmmPositionCloseEventFromInnerInstructions(innerInstructions InnerInstruct
return dlmmPositionCloseEvent{}, increaseOffset(offset), false, nil
}
func dlmmLbPairCreateEventFromInnerInstructions(innerInstructions InnerInstructions, instruction Instruction, offset [2]uint) (dlmmLbPairCreateEvent, [2]uint, bool, error) {
var prefixLen = offset[1]
inners, err := getInnerInstructions(innerInstructions, prefixLen)
if err != nil {
return dlmmLbPairCreateEvent{}, increaseOffset(offset), false, fmt.Errorf("meteora dlmm create get inner instructions error: %v, offset, %d, %d", err, offset[0], prefixLen)
}
for innerIndex, innerInstr := range inners {
if innerInstr.ProgramIDIndex != instruction.ProgramIDIndex {
continue
}
event, ok := dlmmDecodeLbPairCreateEvent(innerInstr.Data)
if !ok {
continue
}
if offset[1] == 0 {
offset[0] += 1
} else {
offset[1] = uint(innerIndex) + 1 + prefixLen
}
return event, offset, true, nil
}
return dlmmLbPairCreateEvent{}, increaseOffset(offset), false, nil
}
func dlmmDecodeLbPairCreateEvent(data []byte) (dlmmLbPairCreateEvent, bool) {
switch {
case len(data) >= 8 && bytes.Equal(data[:8], meteoraInitializeLbPairEventDiscriminator[:]):
var event dlmmLbPairCreateEvent
if err := agbinary.NewBorshDecoder(data[8:]).Decode(&event); err != nil {
return dlmmLbPairCreateEvent{}, false
}
return event, true
case len(data) >= 16 &&
bytes.Equal(data[:8], eventDiscriminator[:]) &&
bytes.Equal(data[8:16], meteoraInitializeLbPairEventDiscriminator[:]):
var event dlmmLbPairCreateEvent
if err := agbinary.NewBorshDecoder(data[16:]).Decode(&event); err != nil {
return dlmmLbPairCreateEvent{}, false
}
return event, true
default:
return dlmmLbPairCreateEvent{}, false
}
}
func dlmmDecodeAddLiquidityEvent(data []byte) (dlmmAddLiquidityEvent, bool) {
switch {
case len(data) >= 8 && bytes.Equal(data[:8], meteoraDlmmAddLiquidityEventDiscriminator[:]):
@@ -1973,10 +2128,8 @@ func dlmmBuildOneSideAddSwap(
tx *Tx,
instruction Instruction,
addEvent dlmmAddLiquidityEvent,
weightDist []dlmmBinLiquidityDistributionByWeight,
startBinId int32,
endBinId int32,
hasRange bool,
entryContract solana.PublicKey,
) ([]Swap, error) {
result := tx.rawTx
@@ -1999,13 +2152,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]
if !addEvent.From.IsZero() {
eventUser = addEvent.From
@@ -2024,7 +2170,6 @@ func dlmmBuildOneSideAddSwap(
ActiveBinId: addEvent.ActiveBinId,
StartBinId: startBinId,
EndBinId: endBinId,
BinChanges: binChanges,
PositionAccount: positionAccount,
}
@@ -2255,56 +2400,50 @@ func dlmmTokenBalanceMeta(result *RawTx, accountIndex int) (TokenBalance, bool)
return TokenBalance{}, false
}
func dlmmBinChangesFromDistribution(amountX, amountY uint64, dist []dlmmBinLiquidityDistribution) []DlmmBinLiquidityChange {
if len(dist) == 0 {
func dlmmAllocateByWeights(total uint64, weights []uint64) []decimal.Decimal {
if len(weights) == 0 {
return nil
}
totalX := decimal.NewFromUint64(amountX)
totalY := decimal.NewFromUint64(amountY)
denom := decimal.NewFromInt(10000)
changes := make([]DlmmBinLiquidityChange, 0, len(dist))
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,
})
sumWeights := uint64(0)
for _, weight := range weights {
sumWeights += weight
}
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 {
if len(reduction) == 0 {
return nil
func dlmmApplySignedAllocation(values []decimal.Decimal, negative bool) []decimal.Decimal {
if !negative {
return values
}
changes := make([]DlmmBinLiquidityChange, 0, len(reduction))
for _, item := range reduction {
changes = append(changes, DlmmBinLiquidityChange{
BinId: item.BinId,
BpsToRemove: item.BpsToRemove,
})
out := make([]decimal.Decimal, len(values))
for i, value := range values {
out[i] = value.Neg()
}
return changes
}
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
return out
}
func dlmmMinMaxBinIDFromCompressedDeposits(bins []dlmmCompressedBinDepositAmount) (startBinID, endBinID 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)
}
}

23
tx.go
View File

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