Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb8d93f426 | ||
|
|
0cc843b370 | ||
|
|
d9a214b4b4 |
121
cmd/rpc_parse/main.go
Normal file
121
cmd/rpc_parse/main.go
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
11
meta.go
11
meta.go
@@ -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")
|
||||||
@@ -89,9 +93,14 @@ var (
|
|||||||
meteoraDlmmAddLiquidityByStrategyDiscriminator = calculateDiscriminator("global:add_liquidity_by_strategy")
|
meteoraDlmmAddLiquidityByStrategyDiscriminator = calculateDiscriminator("global:add_liquidity_by_strategy")
|
||||||
meteoraDlmmAddLiquidityByStrategy2Discriminator = calculateDiscriminator("global:add_liquidity_by_strategy2")
|
meteoraDlmmAddLiquidityByStrategy2Discriminator = calculateDiscriminator("global:add_liquidity_by_strategy2")
|
||||||
meteoraDlmmAddLiquidityByWeightDiscriminator = calculateDiscriminator("global:add_liquidity_by_weight")
|
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")
|
meteoraDlmmClaimFeeDiscriminator = calculateDiscriminator("global:claim_fee")
|
||||||
meteoraDlmmClaimFee2Discriminator = calculateDiscriminator("global:claim_fee2")
|
meteoraDlmmClaimFee2Discriminator = calculateDiscriminator("global:claim_fee2")
|
||||||
meteoraDlmmRebalanceLiquidityDiscriminator = calculateDiscriminator("global:rebalance_liquidity")
|
meteoraDlmmRebalanceLiquidityDiscriminator = calculateDiscriminator("global:rebalance_liquidity")
|
||||||
|
meteoraDlmmRemoveAllLiquidityDiscriminator = calculateDiscriminator("global:remove_all_liquidity")
|
||||||
meteoraDlmmRemoveLiquidityDiscriminator = calculateDiscriminator("global:remove_liquidity")
|
meteoraDlmmRemoveLiquidityDiscriminator = calculateDiscriminator("global:remove_liquidity")
|
||||||
meteoraDlmmRemoveLiquidity2Discriminator = calculateDiscriminator("global:remove_liquidity2")
|
meteoraDlmmRemoveLiquidity2Discriminator = calculateDiscriminator("global:remove_liquidity2")
|
||||||
meteoraDlmmRemoveLiquidityByRangeDiscriminator = calculateDiscriminator("global:remove_liquidity_by_range")
|
meteoraDlmmRemoveLiquidityByRangeDiscriminator = calculateDiscriminator("global:remove_liquidity_by_range")
|
||||||
|
|||||||
620
metaoradlmm.go
620
metaoradlmm.go
@@ -66,6 +66,13 @@ type dlmmPositionCloseEvent struct {
|
|||||||
Owner solana.PublicKey
|
Owner solana.PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dlmmLbPairCreateEvent struct {
|
||||||
|
LbPair solana.PublicKey
|
||||||
|
BinStep uint16
|
||||||
|
TokenX solana.PublicKey
|
||||||
|
TokenY solana.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
type dlmmClaimFeeInnerEvent struct {
|
type dlmmClaimFeeInnerEvent struct {
|
||||||
LbPair solana.PublicKey
|
LbPair solana.PublicKey
|
||||||
Position solana.PublicKey
|
Position solana.PublicKey
|
||||||
@@ -178,6 +185,53 @@ type dlmmAddLiquidityByWeightArgs struct {
|
|||||||
LiquidityParameter dlmmLiquidityParameterByWeight
|
LiquidityParameter dlmmLiquidityParameterByWeight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dlmmLiquidityOneSideParameter struct {
|
||||||
|
Amount uint64
|
||||||
|
ActiveID int32
|
||||||
|
MaxActiveBinSlippage int32
|
||||||
|
BinLiquidityDist []dlmmBinLiquidityDistributionByWeight
|
||||||
|
}
|
||||||
|
|
||||||
|
type dlmmLiquidityParameterByStrategyOneSide struct {
|
||||||
|
Amount uint64
|
||||||
|
ActiveID int32
|
||||||
|
MaxActiveBinSlippage int32
|
||||||
|
StrategyParameters dlmmStrategyParameters
|
||||||
|
}
|
||||||
|
|
||||||
|
type dlmmAddLiquidityOneSideArgs struct {
|
||||||
|
LiquidityParameter dlmmLiquidityOneSideParameter
|
||||||
|
}
|
||||||
|
|
||||||
|
type dlmmAddLiquidityByStrategyOneSideArgs struct {
|
||||||
|
LiquidityParameter dlmmLiquidityParameterByStrategyOneSide
|
||||||
|
}
|
||||||
|
|
||||||
|
type dlmmCompressedBinDepositAmount struct {
|
||||||
|
BinID int32
|
||||||
|
Amount uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type dlmmAddLiquiditySingleSidePreciseParameter struct {
|
||||||
|
Bins []dlmmCompressedBinDepositAmount
|
||||||
|
DecompressMultiplier uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type dlmmAddLiquiditySingleSidePreciseParameter2 struct {
|
||||||
|
Bins []dlmmCompressedBinDepositAmount
|
||||||
|
DecompressMultiplier uint64
|
||||||
|
MaxAmount uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type dlmmAddLiquidityOneSidePreciseArgs struct {
|
||||||
|
Parameter dlmmAddLiquiditySingleSidePreciseParameter
|
||||||
|
}
|
||||||
|
|
||||||
|
type dlmmAddLiquidityOneSidePrecise2Args struct {
|
||||||
|
LiquidityParameter dlmmAddLiquiditySingleSidePreciseParameter2
|
||||||
|
RemainingAccountsInfo dlmmRemainingAccountsInfo
|
||||||
|
}
|
||||||
|
|
||||||
type dlmmRemoveLiquidityArgs struct {
|
type dlmmRemoveLiquidityArgs struct {
|
||||||
BinLiquidityRemoval []dlmmBinLiquidityReduction
|
BinLiquidityRemoval []dlmmBinLiquidityReduction
|
||||||
}
|
}
|
||||||
@@ -264,6 +318,16 @@ type dlmmLiquidityAccounts struct {
|
|||||||
tokenYProgramIdx int
|
tokenYProgramIdx int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dlmmOneSideLiquidityAccounts struct {
|
||||||
|
positionIdx int
|
||||||
|
poolIdx int
|
||||||
|
userTokenIdx int
|
||||||
|
reserveIdx int
|
||||||
|
tokenMintIdx int
|
||||||
|
userIdx int
|
||||||
|
tokenProgramIdx int
|
||||||
|
}
|
||||||
|
|
||||||
var meteoraDlmmEventAuthority = func() solana.PublicKey {
|
var meteoraDlmmEventAuthority = func() solana.PublicKey {
|
||||||
key, _, err := solana.FindProgramAddress([][]byte{[]byte("__event_authority")}, meteoraDlmmProgram)
|
key, _, err := solana.FindProgramAddress([][]byte{[]byte("__event_authority")}, meteoraDlmmProgram)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -283,7 +347,11 @@ func metaoradlmmParser(tx *Tx, instruction Instruction, innerInstructions InnerI
|
|||||||
|
|
||||||
discriminator := *(*[8]byte)(decode[:8])
|
discriminator := *(*[8]byte)(decode[:8])
|
||||||
switch discriminator {
|
switch discriminator {
|
||||||
case meteoraInitializeLbPairDiscriminator:
|
case meteoraInitializeCustomizablePermissionlessLbPairDiscriminator,
|
||||||
|
meteoraInitializeCustomizablePermissionlessLbPair2Discriminator,
|
||||||
|
meteoraInitializeLbPairDiscriminator,
|
||||||
|
meteoraInitializeLbPair2Discriminator,
|
||||||
|
meteoraInitializePermissionLbPairDiscriminator:
|
||||||
return metaoradlmmInitializeParser(tx, instruction, innerInstructions, offset)
|
return metaoradlmmInitializeParser(tx, instruction, innerInstructions, offset)
|
||||||
case meteoraDlmmInitializePositionDiscriminator, meteoraDlmmInitializePosition2Discriminator,
|
case meteoraDlmmInitializePositionDiscriminator, meteoraDlmmInitializePosition2Discriminator,
|
||||||
meteoraDlmmInitializePositionByOperatorDiscriminator, meteoraDlmmInitializePositionPdaDiscriminator:
|
meteoraDlmmInitializePositionByOperatorDiscriminator, meteoraDlmmInitializePositionPdaDiscriminator:
|
||||||
@@ -294,13 +362,15 @@ func metaoradlmmParser(tx *Tx, instruction Instruction, innerInstructions InnerI
|
|||||||
return metaoradlmmSwap2Parser(tx, instruction, innerInstructions, offset)
|
return metaoradlmmSwap2Parser(tx, instruction, innerInstructions, offset)
|
||||||
case meteoraDlmmAddLiquidityDiscriminator, meteoraDlmmAddLiquidity2Discriminator,
|
case meteoraDlmmAddLiquidityDiscriminator, meteoraDlmmAddLiquidity2Discriminator,
|
||||||
meteoraDlmmAddLiquidityByStrategyDiscriminator, meteoraDlmmAddLiquidityByStrategy2Discriminator,
|
meteoraDlmmAddLiquidityByStrategyDiscriminator, meteoraDlmmAddLiquidityByStrategy2Discriminator,
|
||||||
meteoraDlmmAddLiquidityByWeightDiscriminator:
|
meteoraDlmmAddLiquidityByWeightDiscriminator, meteoraDlmmAddLiquidityOneSideDiscriminator,
|
||||||
|
meteoraDlmmAddLiquidityOneSidePreciseDiscriminator, meteoraDlmmAddLiquidityOneSidePrecise2Discriminator,
|
||||||
|
meteoraDlmmAddLiquidityByStrategyOneSideDiscriminator:
|
||||||
return metaoradlmmAddLiquidityParser(tx, instruction, innerInstructions, offset)
|
return metaoradlmmAddLiquidityParser(tx, instruction, innerInstructions, offset)
|
||||||
case meteoraDlmmClaimFeeDiscriminator, meteoraDlmmClaimFee2Discriminator:
|
case meteoraDlmmClaimFeeDiscriminator, meteoraDlmmClaimFee2Discriminator:
|
||||||
return metaoradlmmClaimFeeParser(tx, instruction, innerInstructions, offset)
|
return metaoradlmmClaimFeeParser(tx, instruction, innerInstructions, offset)
|
||||||
case meteoraDlmmRebalanceLiquidityDiscriminator:
|
case meteoraDlmmRebalanceLiquidityDiscriminator:
|
||||||
return metaoradlmmRebalanceLiquidityParser(tx, instruction, innerInstructions, offset)
|
return metaoradlmmRebalanceLiquidityParser(tx, instruction, innerInstructions, offset)
|
||||||
case meteoraDlmmRemoveLiquidityDiscriminator, meteoraDlmmRemoveLiquidity2Discriminator,
|
case meteoraDlmmRemoveAllLiquidityDiscriminator, meteoraDlmmRemoveLiquidityDiscriminator, meteoraDlmmRemoveLiquidity2Discriminator,
|
||||||
meteoraDlmmRemoveLiquidityByRangeDiscriminator, meteoraDlmmRemoveLiquidityByRange2Discriminator:
|
meteoraDlmmRemoveLiquidityByRangeDiscriminator, meteoraDlmmRemoveLiquidityByRange2Discriminator:
|
||||||
return metaoradlmmRemoveLiquidityParser(tx, instruction, innerInstructions, offset)
|
return metaoradlmmRemoveLiquidityParser(tx, instruction, innerInstructions, offset)
|
||||||
case meteoraDlmmClosePositionDiscriminator, meteoraDlmmClosePosition2Discriminator, meteoraDlmmClosePositionIfEmptyDiscriminator:
|
case meteoraDlmmClosePositionDiscriminator, meteoraDlmmClosePosition2Discriminator, meteoraDlmmClosePositionIfEmptyDiscriminator:
|
||||||
@@ -310,53 +380,131 @@ func metaoradlmmParser(tx *Tx, instruction Instruction, innerInstructions InnerI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dlmmInitializeAccounts struct {
|
||||||
|
pool solana.PublicKey
|
||||||
|
token0 solana.PublicKey
|
||||||
|
token1 solana.PublicKey
|
||||||
|
baseTokenProgram solana.PublicKey
|
||||||
|
quoteTokenProgram solana.PublicKey
|
||||||
|
user solana.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveDlmmInitializeAccounts(result *RawTx, data []byte, accounts []int) (dlmmInitializeAccounts, error) {
|
||||||
|
if len(data) < 8 {
|
||||||
|
return dlmmInitializeAccounts{}, fmt.Errorf("instruction data too short")
|
||||||
|
}
|
||||||
|
|
||||||
|
accountList := result.getAccountList()
|
||||||
|
resolveAt := func(position int) (solana.PublicKey, error) {
|
||||||
|
if position < 0 || position >= len(accounts) {
|
||||||
|
return solana.PublicKey{}, fmt.Errorf("accounts too short, missing position %d", position)
|
||||||
|
}
|
||||||
|
accountIndex := accounts[position]
|
||||||
|
if accountIndex < 0 || accountIndex >= len(accountList) {
|
||||||
|
return solana.PublicKey{}, fmt.Errorf("account index out of range at position %d", position)
|
||||||
|
}
|
||||||
|
return accountList[accountIndex], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveCommon := func(poolPos, token0Pos, token1Pos, userPos, baseTokenProgramPos, quoteTokenProgramPos int) (dlmmInitializeAccounts, error) {
|
||||||
|
pool, err := resolveAt(poolPos)
|
||||||
|
if err != nil {
|
||||||
|
return dlmmInitializeAccounts{}, err
|
||||||
|
}
|
||||||
|
token0, err := resolveAt(token0Pos)
|
||||||
|
if err != nil {
|
||||||
|
return dlmmInitializeAccounts{}, err
|
||||||
|
}
|
||||||
|
token1, err := resolveAt(token1Pos)
|
||||||
|
if err != nil {
|
||||||
|
return dlmmInitializeAccounts{}, err
|
||||||
|
}
|
||||||
|
baseTokenProgram, err := resolveAt(baseTokenProgramPos)
|
||||||
|
if err != nil {
|
||||||
|
return dlmmInitializeAccounts{}, err
|
||||||
|
}
|
||||||
|
quoteTokenProgram, err := resolveAt(quoteTokenProgramPos)
|
||||||
|
if err != nil {
|
||||||
|
return dlmmInitializeAccounts{}, err
|
||||||
|
}
|
||||||
|
user, err := resolveAt(userPos)
|
||||||
|
if err != nil {
|
||||||
|
return dlmmInitializeAccounts{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dlmmInitializeAccounts{
|
||||||
|
pool: pool,
|
||||||
|
token0: token0,
|
||||||
|
token1: token1,
|
||||||
|
baseTokenProgram: baseTokenProgram,
|
||||||
|
quoteTokenProgram: quoteTokenProgram,
|
||||||
|
user: user,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
discriminator := *(*[8]byte)(data[:8])
|
||||||
|
switch discriminator {
|
||||||
|
case meteoraInitializeLbPairDiscriminator,
|
||||||
|
meteoraInitializeCustomizablePermissionlessLbPairDiscriminator:
|
||||||
|
return resolveCommon(0, 2, 3, 8, 9, 9)
|
||||||
|
case meteoraInitializeLbPair2Discriminator,
|
||||||
|
meteoraInitializeCustomizablePermissionlessLbPair2Discriminator:
|
||||||
|
return resolveCommon(0, 2, 3, 8, 11, 12)
|
||||||
|
case meteoraInitializePermissionLbPairDiscriminator:
|
||||||
|
return resolveCommon(1, 3, 4, 8, 11, 12)
|
||||||
|
default:
|
||||||
|
return dlmmInitializeAccounts{}, fmt.Errorf("unsupported initialize discriminator")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func metaoradlmmInitializeParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
func metaoradlmmInitializeParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
market := tx.rawTx.accountList[instruction.Accounts[0]]
|
accounts, err := resolveDlmmInitializeAccounts(tx.rawTx, instruction.Data, instruction.Accounts)
|
||||||
token0 := tx.rawTx.accountList[instruction.Accounts[2]]
|
if err != nil {
|
||||||
token1 := tx.rawTx.accountList[instruction.Accounts[3]]
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm initialize accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
|
||||||
entryContract := tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
entryContract := tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||||
|
|
||||||
var baseDecimals uint8
|
findMintDecimals := func(mint solana.PublicKey) uint8 {
|
||||||
var quoteDecimals uint8
|
|
||||||
for _, acc := range tx.rawTx.Meta.PostTokenBalances {
|
for _, acc := range tx.rawTx.Meta.PostTokenBalances {
|
||||||
if acc.MintAccount.Equals(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
|
||||||
}
|
}
|
||||||
@@ -670,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,
|
||||||
@@ -685,6 +845,13 @@ func metaoradlmmSwapParser(tx *Tx, instruction Instruction, innerInstructions In
|
|||||||
User: eventUser,
|
User: eventUser,
|
||||||
BaseAmount: baseAmount,
|
BaseAmount: baseAmount,
|
||||||
QuoteAmount: quoteAmount,
|
QuoteAmount: quoteAmount,
|
||||||
|
FeeAmount: feeAmount,
|
||||||
|
FeeBps: dlmmSwapFeeBpsString(swapEvent.FeeBps),
|
||||||
|
LpFeeAmount: lpFeeAmount,
|
||||||
|
FeeSide: feeSide,
|
||||||
|
FeeMint: feeMint,
|
||||||
|
FeeTokenProgram: feeTokenProgram,
|
||||||
|
FeeMintDecimals: feeDecimals,
|
||||||
BaseReserve: baseReserve,
|
BaseReserve: baseReserve,
|
||||||
QuoteReserve: quoteReserve,
|
QuoteReserve: quoteReserve,
|
||||||
UserBaseBalance: userBase,
|
UserBaseBalance: userBase,
|
||||||
@@ -702,6 +869,39 @@ func metaoradlmmSwap2Parser(tx *Tx, instruction Instruction, innerInstructions I
|
|||||||
return metaoradlmmSwapParser(tx, instruction, innerInstructions, offset)
|
return metaoradlmmSwapParser(tx, instruction, innerInstructions, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dlmmSwapFeeInfo(
|
||||||
|
baseIsX bool,
|
||||||
|
swapForY bool,
|
||||||
|
fee uint64,
|
||||||
|
baseMint solana.PublicKey,
|
||||||
|
quoteMint solana.PublicKey,
|
||||||
|
baseTokenProgram solana.PublicKey,
|
||||||
|
quoteTokenProgram solana.PublicKey,
|
||||||
|
baseDecimals uint8,
|
||||||
|
quoteDecimals uint8,
|
||||||
|
) (decimal.Decimal, string, solana.PublicKey, solana.PublicKey, uint8) {
|
||||||
|
feeAmount := decimal.NewFromUint64(fee)
|
||||||
|
if baseIsX == swapForY {
|
||||||
|
return feeAmount, "base", baseMint, baseTokenProgram, baseDecimals
|
||||||
|
}
|
||||||
|
return feeAmount, "quote", quoteMint, quoteTokenProgram, quoteDecimals
|
||||||
|
}
|
||||||
|
|
||||||
|
func dlmmSwapLpFeeAmount(fee, protocolFee, hostFee uint64) decimal.Decimal {
|
||||||
|
total := decimal.NewFromUint64(fee)
|
||||||
|
protocol := decimal.NewFromUint64(protocolFee)
|
||||||
|
host := decimal.NewFromUint64(hostFee)
|
||||||
|
lpFee := total.Sub(protocol).Sub(host)
|
||||||
|
if lpFee.IsNegative() {
|
||||||
|
return decimal.Zero
|
||||||
|
}
|
||||||
|
return lpFee
|
||||||
|
}
|
||||||
|
|
||||||
|
func dlmmSwapFeeBpsString(feeBps agbinary.Uint128) string {
|
||||||
|
return feeBps.DecimalString()
|
||||||
|
}
|
||||||
|
|
||||||
func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||||
result := tx.rawTx
|
result := tx.rawTx
|
||||||
|
|
||||||
@@ -729,7 +929,7 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
|
|||||||
weightDist []dlmmBinLiquidityDistributionByWeight
|
weightDist []dlmmBinLiquidityDistributionByWeight
|
||||||
startBinId int32
|
startBinId int32
|
||||||
endBinId int32
|
endBinId int32
|
||||||
hasRange bool
|
oneSide bool
|
||||||
)
|
)
|
||||||
|
|
||||||
switch discriminator {
|
switch discriminator {
|
||||||
@@ -742,7 +942,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
|
|||||||
amountY = args.LiquidityParameter.AmountY
|
amountY = args.LiquidityParameter.AmountY
|
||||||
binDist = args.LiquidityParameter.BinLiquidityDist
|
binDist = args.LiquidityParameter.BinLiquidityDist
|
||||||
startBinId, endBinId = dlmmMinMaxBinIdFromDistribution(binDist)
|
startBinId, endBinId = dlmmMinMaxBinIdFromDistribution(binDist)
|
||||||
hasRange = len(binDist) > 0
|
|
||||||
case meteoraDlmmAddLiquidity2Discriminator:
|
case meteoraDlmmAddLiquidity2Discriminator:
|
||||||
var args dlmmAddLiquidity2Args
|
var args dlmmAddLiquidity2Args
|
||||||
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
||||||
@@ -752,7 +951,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
|
|||||||
amountY = args.LiquidityParameter.AmountY
|
amountY = args.LiquidityParameter.AmountY
|
||||||
binDist = args.LiquidityParameter.BinLiquidityDist
|
binDist = args.LiquidityParameter.BinLiquidityDist
|
||||||
startBinId, endBinId = dlmmMinMaxBinIdFromDistribution(binDist)
|
startBinId, endBinId = dlmmMinMaxBinIdFromDistribution(binDist)
|
||||||
hasRange = len(binDist) > 0
|
|
||||||
case meteoraDlmmAddLiquidityByStrategyDiscriminator:
|
case meteoraDlmmAddLiquidityByStrategyDiscriminator:
|
||||||
var args dlmmAddLiquidityByStrategyArgs
|
var args dlmmAddLiquidityByStrategyArgs
|
||||||
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
||||||
@@ -762,7 +960,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
|
|||||||
amountY = args.LiquidityParameter.AmountY
|
amountY = args.LiquidityParameter.AmountY
|
||||||
startBinId = args.LiquidityParameter.StrategyParameters.MinBinId
|
startBinId = args.LiquidityParameter.StrategyParameters.MinBinId
|
||||||
endBinId = args.LiquidityParameter.StrategyParameters.MaxBinId
|
endBinId = args.LiquidityParameter.StrategyParameters.MaxBinId
|
||||||
hasRange = true
|
|
||||||
case meteoraDlmmAddLiquidityByStrategy2Discriminator:
|
case meteoraDlmmAddLiquidityByStrategy2Discriminator:
|
||||||
var args dlmmAddLiquidityByStrategy2Args
|
var args dlmmAddLiquidityByStrategy2Args
|
||||||
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
||||||
@@ -772,7 +969,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
|
|||||||
amountY = args.LiquidityParameter.AmountY
|
amountY = args.LiquidityParameter.AmountY
|
||||||
startBinId = args.LiquidityParameter.StrategyParameters.MinBinId
|
startBinId = args.LiquidityParameter.StrategyParameters.MinBinId
|
||||||
endBinId = args.LiquidityParameter.StrategyParameters.MaxBinId
|
endBinId = args.LiquidityParameter.StrategyParameters.MaxBinId
|
||||||
hasRange = true
|
|
||||||
case meteoraDlmmAddLiquidityByWeightDiscriminator:
|
case meteoraDlmmAddLiquidityByWeightDiscriminator:
|
||||||
var args dlmmAddLiquidityByWeightArgs
|
var args dlmmAddLiquidityByWeightArgs
|
||||||
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
||||||
@@ -782,16 +978,40 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
|
|||||||
amountY = args.LiquidityParameter.AmountY
|
amountY = args.LiquidityParameter.AmountY
|
||||||
weightDist = args.LiquidityParameter.BinLiquidityDist
|
weightDist = args.LiquidityParameter.BinLiquidityDist
|
||||||
startBinId, endBinId = dlmmMinMaxBinIdFromWeightDistribution(weightDist)
|
startBinId, endBinId = dlmmMinMaxBinIdFromWeightDistribution(weightDist)
|
||||||
hasRange = len(weightDist) > 0
|
case meteoraDlmmAddLiquidityOneSideDiscriminator:
|
||||||
|
var args dlmmAddLiquidityOneSideArgs
|
||||||
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity one side decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
weightDist = args.LiquidityParameter.BinLiquidityDist
|
||||||
|
startBinId, endBinId = dlmmMinMaxBinIdFromWeightDistribution(weightDist)
|
||||||
|
oneSide = true
|
||||||
|
case meteoraDlmmAddLiquidityOneSidePreciseDiscriminator:
|
||||||
|
var args dlmmAddLiquidityOneSidePreciseArgs
|
||||||
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity one side precise decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
startBinId, endBinId = dlmmMinMaxBinIDFromCompressedDeposits(args.Parameter.Bins)
|
||||||
|
oneSide = true
|
||||||
|
case meteoraDlmmAddLiquidityOneSidePrecise2Discriminator:
|
||||||
|
var args dlmmAddLiquidityOneSidePrecise2Args
|
||||||
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity one side precise2 decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
startBinId, endBinId = dlmmMinMaxBinIDFromCompressedDeposits(args.LiquidityParameter.Bins)
|
||||||
|
oneSide = true
|
||||||
|
case meteoraDlmmAddLiquidityByStrategyOneSideDiscriminator:
|
||||||
|
var args dlmmAddLiquidityByStrategyOneSideArgs
|
||||||
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity by strategy one side decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
|
}
|
||||||
|
startBinId = args.LiquidityParameter.StrategyParameters.MinBinId
|
||||||
|
endBinId = args.LiquidityParameter.StrategyParameters.MaxBinId
|
||||||
|
oneSide = true
|
||||||
default:
|
default:
|
||||||
return nil, increaseOffset(offset), InstructionIgnoredError
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
||||||
}
|
}
|
||||||
|
|
||||||
accounts, err := resolveDlmmLiquidityAccounts(result, instruction.Accounts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
addEvent, nextOffset, err := dlmmAddLiquidityEventFromInnerInstructions(innerInstructions, instruction, offset)
|
addEvent, nextOffset, err := dlmmAddLiquidityEventFromInnerInstructions(innerInstructions, instruction, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nextOffset, err
|
return nil, nextOffset, err
|
||||||
@@ -800,14 +1020,17 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
|
|||||||
amountX = addEvent.Amounts[0]
|
amountX = addEvent.Amounts[0]
|
||||||
amountY = addEvent.Amounts[1]
|
amountY = addEvent.Amounts[1]
|
||||||
|
|
||||||
binChanges := []DlmmBinLiquidityChange(nil)
|
if oneSide {
|
||||||
if len(binDist) > 0 {
|
swaps, err := dlmmBuildOneSideAddSwap(tx, instruction, addEvent, startBinId, endBinId, entryContract)
|
||||||
binChanges = dlmmBinChangesFromDistribution(amountX, amountY, binDist)
|
if err != nil {
|
||||||
} else if len(weightDist) > 0 {
|
return nil, offset, err
|
||||||
// Weight-only params do not preserve per-side amounts for each bin, so keep the affected range only.
|
}
|
||||||
binChanges = dlmmBinChangesFromRange(startBinId, endBinId, 0)
|
return swaps, offset, nil
|
||||||
} else if hasRange {
|
}
|
||||||
binChanges = dlmmBinChangesFromRange(startBinId, endBinId, 0)
|
|
||||||
|
accounts, err := resolveDlmmLiquidityAccounts(result, instruction.Accounts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
pool := result.accountList[accounts.poolIdx]
|
pool := result.accountList[accounts.poolIdx]
|
||||||
@@ -840,7 +1063,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
|
|||||||
baseAmount = amountYDec
|
baseAmount = amountYDec
|
||||||
quoteAmount = amountXDec
|
quoteAmount = amountXDec
|
||||||
}
|
}
|
||||||
|
|
||||||
eventUser := result.accountList[accounts.userIdx]
|
eventUser := result.accountList[accounts.userIdx]
|
||||||
if !addEvent.From.IsZero() {
|
if !addEvent.From.IsZero() {
|
||||||
eventUser = addEvent.From
|
eventUser = addEvent.From
|
||||||
@@ -901,7 +1123,6 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
|
|||||||
ActiveBinId: addEvent.ActiveBinId,
|
ActiveBinId: addEvent.ActiveBinId,
|
||||||
StartBinId: startBinId,
|
StartBinId: startBinId,
|
||||||
EndBinId: endBinId,
|
EndBinId: endBinId,
|
||||||
BinChanges: binChanges,
|
|
||||||
PositionAccount: result.accountList[accounts.positionIdx],
|
PositionAccount: result.accountList[accounts.positionIdx],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -929,19 +1150,19 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
|
|||||||
|
|
||||||
discriminator := *(*[8]byte)(decode[:8])
|
discriminator := *(*[8]byte)(decode[:8])
|
||||||
var (
|
var (
|
||||||
binChanges []DlmmBinLiquidityChange
|
|
||||||
startBinId int32
|
startBinId int32
|
||||||
endBinId int32
|
endBinId int32
|
||||||
removeBp int32
|
removeBp int32
|
||||||
)
|
)
|
||||||
|
|
||||||
switch discriminator {
|
switch discriminator {
|
||||||
|
case meteoraDlmmRemoveAllLiquidityDiscriminator:
|
||||||
|
removeBp = 10000
|
||||||
case meteoraDlmmRemoveLiquidityDiscriminator:
|
case meteoraDlmmRemoveLiquidityDiscriminator:
|
||||||
var args dlmmRemoveLiquidityArgs
|
var args dlmmRemoveLiquidityArgs
|
||||||
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
||||||
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
}
|
}
|
||||||
binChanges = dlmmBinChangesFromReduction(args.BinLiquidityRemoval)
|
|
||||||
startBinId, endBinId = dlmmMinMaxBinIdFromReduction(args.BinLiquidityRemoval)
|
startBinId, endBinId = dlmmMinMaxBinIdFromReduction(args.BinLiquidityRemoval)
|
||||||
removeBp = dlmmCommonRemoveBp(args.BinLiquidityRemoval)
|
removeBp = dlmmCommonRemoveBp(args.BinLiquidityRemoval)
|
||||||
case meteoraDlmmRemoveLiquidity2Discriminator:
|
case meteoraDlmmRemoveLiquidity2Discriminator:
|
||||||
@@ -949,7 +1170,6 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
|
|||||||
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
||||||
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity2 decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity2 decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||||
}
|
}
|
||||||
binChanges = dlmmBinChangesFromReduction(args.BinLiquidityRemoval)
|
|
||||||
startBinId, endBinId = dlmmMinMaxBinIdFromReduction(args.BinLiquidityRemoval)
|
startBinId, endBinId = dlmmMinMaxBinIdFromReduction(args.BinLiquidityRemoval)
|
||||||
removeBp = dlmmCommonRemoveBp(args.BinLiquidityRemoval)
|
removeBp = dlmmCommonRemoveBp(args.BinLiquidityRemoval)
|
||||||
case meteoraDlmmRemoveLiquidityByRangeDiscriminator:
|
case meteoraDlmmRemoveLiquidityByRangeDiscriminator:
|
||||||
@@ -960,7 +1180,6 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
|
|||||||
startBinId = args.FromBinId
|
startBinId = args.FromBinId
|
||||||
endBinId = args.ToBinId
|
endBinId = args.ToBinId
|
||||||
removeBp = int32(args.BpsToRemove)
|
removeBp = int32(args.BpsToRemove)
|
||||||
binChanges = dlmmBinChangesFromRange(startBinId, endBinId, args.BpsToRemove)
|
|
||||||
case meteoraDlmmRemoveLiquidityByRange2Discriminator:
|
case meteoraDlmmRemoveLiquidityByRange2Discriminator:
|
||||||
var args dlmmRemoveLiquidityByRange2Args
|
var args dlmmRemoveLiquidityByRange2Args
|
||||||
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
|
||||||
@@ -969,7 +1188,6 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
|
|||||||
startBinId = args.FromBinId
|
startBinId = args.FromBinId
|
||||||
endBinId = args.ToBinId
|
endBinId = args.ToBinId
|
||||||
removeBp = int32(args.BpsToRemove)
|
removeBp = int32(args.BpsToRemove)
|
||||||
binChanges = dlmmBinChangesFromRange(startBinId, endBinId, args.BpsToRemove)
|
|
||||||
default:
|
default:
|
||||||
return nil, increaseOffset(offset), InstructionIgnoredError
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
||||||
}
|
}
|
||||||
@@ -1015,7 +1233,6 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
|
|||||||
baseAmount = amountYDec
|
baseAmount = amountYDec
|
||||||
quoteAmount = amountXDec
|
quoteAmount = amountXDec
|
||||||
}
|
}
|
||||||
|
|
||||||
eventUser := result.accountList[accounts.userIdx]
|
eventUser := result.accountList[accounts.userIdx]
|
||||||
if !removeEvent.From.IsZero() {
|
if !removeEvent.From.IsZero() {
|
||||||
eventUser = removeEvent.From
|
eventUser = removeEvent.From
|
||||||
@@ -1077,7 +1294,6 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
|
|||||||
StartBinId: startBinId,
|
StartBinId: startBinId,
|
||||||
EndBinId: endBinId,
|
EndBinId: endBinId,
|
||||||
RemoveBp: removeBp,
|
RemoveBp: removeBp,
|
||||||
BinChanges: binChanges,
|
|
||||||
PositionAccount: result.accountList[accounts.positionIdx],
|
PositionAccount: result.accountList[accounts.positionIdx],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1321,7 +1537,6 @@ func metaoradlmmRebalanceLiquidityParser(tx *Tx, instruction Instruction, innerI
|
|||||||
ActiveBinId: event.ActiveBinId,
|
ActiveBinId: event.ActiveBinId,
|
||||||
StartBinId: event.OldMinBinId,
|
StartBinId: event.OldMinBinId,
|
||||||
EndBinId: event.OldMaxBinId,
|
EndBinId: event.OldMaxBinId,
|
||||||
BinChanges: dlmmBinChangesFromRange(event.OldMinBinId, event.OldMaxBinId, 0),
|
|
||||||
PositionAccount: result.accountList[accounts.positionIdx],
|
PositionAccount: result.accountList[accounts.positionIdx],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1347,7 +1562,6 @@ func metaoradlmmRebalanceLiquidityParser(tx *Tx, instruction Instruction, innerI
|
|||||||
ActiveBinId: event.ActiveBinId,
|
ActiveBinId: event.ActiveBinId,
|
||||||
StartBinId: event.NewMinBinId,
|
StartBinId: event.NewMinBinId,
|
||||||
EndBinId: event.NewMaxBinId,
|
EndBinId: event.NewMaxBinId,
|
||||||
BinChanges: dlmmBinChangesFromRange(event.NewMinBinId, event.NewMaxBinId, 0),
|
|
||||||
PositionAccount: result.accountList[accounts.positionIdx],
|
PositionAccount: result.accountList[accounts.positionIdx],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1529,6 +1743,51 @@ func dlmmPositionCloseEventFromInnerInstructions(innerInstructions InnerInstruct
|
|||||||
return dlmmPositionCloseEvent{}, increaseOffset(offset), false, nil
|
return dlmmPositionCloseEvent{}, increaseOffset(offset), false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dlmmLbPairCreateEventFromInnerInstructions(innerInstructions InnerInstructions, instruction Instruction, offset [2]uint) (dlmmLbPairCreateEvent, [2]uint, bool, error) {
|
||||||
|
var prefixLen = offset[1]
|
||||||
|
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
||||||
|
if err != nil {
|
||||||
|
return dlmmLbPairCreateEvent{}, increaseOffset(offset), false, fmt.Errorf("meteora dlmm create get inner instructions error: %v, offset, %d, %d", err, offset[0], prefixLen)
|
||||||
|
}
|
||||||
|
for innerIndex, innerInstr := range inners {
|
||||||
|
if innerInstr.ProgramIDIndex != instruction.ProgramIDIndex {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event, ok := dlmmDecodeLbPairCreateEvent(innerInstr.Data)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if offset[1] == 0 {
|
||||||
|
offset[0] += 1
|
||||||
|
} else {
|
||||||
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
||||||
|
}
|
||||||
|
return event, offset, true, nil
|
||||||
|
}
|
||||||
|
return dlmmLbPairCreateEvent{}, increaseOffset(offset), false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dlmmDecodeLbPairCreateEvent(data []byte) (dlmmLbPairCreateEvent, bool) {
|
||||||
|
switch {
|
||||||
|
case len(data) >= 8 && bytes.Equal(data[:8], meteoraInitializeLbPairEventDiscriminator[:]):
|
||||||
|
var event dlmmLbPairCreateEvent
|
||||||
|
if err := agbinary.NewBorshDecoder(data[8:]).Decode(&event); err != nil {
|
||||||
|
return dlmmLbPairCreateEvent{}, false
|
||||||
|
}
|
||||||
|
return event, true
|
||||||
|
case len(data) >= 16 &&
|
||||||
|
bytes.Equal(data[:8], eventDiscriminator[:]) &&
|
||||||
|
bytes.Equal(data[8:16], meteoraInitializeLbPairEventDiscriminator[:]):
|
||||||
|
var event dlmmLbPairCreateEvent
|
||||||
|
if err := agbinary.NewBorshDecoder(data[16:]).Decode(&event); err != nil {
|
||||||
|
return dlmmLbPairCreateEvent{}, false
|
||||||
|
}
|
||||||
|
return event, true
|
||||||
|
default:
|
||||||
|
return dlmmLbPairCreateEvent{}, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func dlmmDecodeAddLiquidityEvent(data []byte) (dlmmAddLiquidityEvent, bool) {
|
func dlmmDecodeAddLiquidityEvent(data []byte) (dlmmAddLiquidityEvent, bool) {
|
||||||
switch {
|
switch {
|
||||||
case len(data) >= 8 && bytes.Equal(data[:8], meteoraDlmmAddLiquidityEventDiscriminator[:]):
|
case len(data) >= 8 && bytes.Equal(data[:8], meteoraDlmmAddLiquidityEventDiscriminator[:]):
|
||||||
@@ -1822,6 +2081,154 @@ func resolveDlmmLiquidityAccounts(result *RawTx, accounts []int) (dlmmLiquidityA
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resolveDlmmOneSideLiquidityAccounts(result *RawTx, accounts []int) (dlmmOneSideLiquidityAccounts, error) {
|
||||||
|
if len(accounts) < 10 {
|
||||||
|
return dlmmOneSideLiquidityAccounts{}, fmt.Errorf("accounts too short, expected at least 10")
|
||||||
|
}
|
||||||
|
accountList := result.accountList
|
||||||
|
|
||||||
|
eventAuthorityPos := -1
|
||||||
|
for i, idx := range accounts {
|
||||||
|
if idx < 0 || idx >= len(accountList) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if accountList[idx].Equals(meteoraDlmmEventAuthority) {
|
||||||
|
eventAuthorityPos = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if eventAuthorityPos == -1 {
|
||||||
|
return dlmmOneSideLiquidityAccounts{}, fmt.Errorf("event authority not found")
|
||||||
|
}
|
||||||
|
if eventAuthorityPos+1 >= len(accounts) || !accountList[accounts[eventAuthorityPos+1]].Equals(meteoraDlmmProgram) {
|
||||||
|
return dlmmOneSideLiquidityAccounts{}, fmt.Errorf("program id not found after event authority")
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenProgramPos := eventAuthorityPos - 1
|
||||||
|
userPos := eventAuthorityPos - 2
|
||||||
|
if tokenProgramPos < 0 || userPos < 0 {
|
||||||
|
return dlmmOneSideLiquidityAccounts{}, fmt.Errorf("one side liquidity account positions invalid")
|
||||||
|
}
|
||||||
|
if len(accounts) < 6 {
|
||||||
|
return dlmmOneSideLiquidityAccounts{}, fmt.Errorf("accounts too short for one side liquidity parsing")
|
||||||
|
}
|
||||||
|
|
||||||
|
return dlmmOneSideLiquidityAccounts{
|
||||||
|
positionIdx: accounts[0],
|
||||||
|
poolIdx: accounts[1],
|
||||||
|
userTokenIdx: accounts[3],
|
||||||
|
reserveIdx: accounts[4],
|
||||||
|
tokenMintIdx: accounts[5],
|
||||||
|
userIdx: accounts[userPos],
|
||||||
|
tokenProgramIdx: accounts[tokenProgramPos],
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dlmmBuildOneSideAddSwap(
|
||||||
|
tx *Tx,
|
||||||
|
instruction Instruction,
|
||||||
|
addEvent dlmmAddLiquidityEvent,
|
||||||
|
startBinId int32,
|
||||||
|
endBinId int32,
|
||||||
|
entryContract solana.PublicKey,
|
||||||
|
) ([]Swap, error) {
|
||||||
|
result := tx.rawTx
|
||||||
|
accounts, err := resolveDlmmOneSideLiquidityAccounts(result, instruction.Accounts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
knownMint := result.accountList[accounts.tokenMintIdx]
|
||||||
|
knownTokenProgram := result.accountList[accounts.tokenProgramIdx]
|
||||||
|
knownDecimals, ok := dlmmTokenDecimals(result, accounts.reserveIdx)
|
||||||
|
if !ok {
|
||||||
|
knownDecimals, _ = dlmmTokenDecimals(result, accounts.userTokenIdx)
|
||||||
|
}
|
||||||
|
knownReserveBalance := getAccountBalanceAfterTx(result, accounts.reserveIdx)
|
||||||
|
knownUserBalance := getAccountBalanceAfterTx(result, accounts.userTokenIdx)
|
||||||
|
if knownMint.Equals(wSolMint) {
|
||||||
|
if solAmount, err := GetSolAfterTx(result, accounts.userIdx); err == nil {
|
||||||
|
knownUserBalance = knownUserBalance.Add(decimal.NewFromUint64(solAmount))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eventUser := result.accountList[accounts.userIdx]
|
||||||
|
if !addEvent.From.IsZero() {
|
||||||
|
eventUser = addEvent.From
|
||||||
|
}
|
||||||
|
positionAccount := result.accountList[accounts.positionIdx]
|
||||||
|
if !addEvent.Position.IsZero() {
|
||||||
|
positionAccount = addEvent.Position
|
||||||
|
}
|
||||||
|
|
||||||
|
swap := Swap{
|
||||||
|
Program: SolProgramMeteoraDLMM,
|
||||||
|
Event: "add",
|
||||||
|
Pool: result.accountList[accounts.poolIdx],
|
||||||
|
User: eventUser,
|
||||||
|
EntryContract: entryContract,
|
||||||
|
ActiveBinId: addEvent.ActiveBinId,
|
||||||
|
StartBinId: startBinId,
|
||||||
|
EndBinId: endBinId,
|
||||||
|
PositionAccount: positionAccount,
|
||||||
|
}
|
||||||
|
|
||||||
|
knownIsX := dlmmInferOneSideLiquidityAxis(result, accounts, addEvent)
|
||||||
|
if knownIsX {
|
||||||
|
swap.BaseMint = knownMint
|
||||||
|
swap.BaseTokenProgram = knownTokenProgram
|
||||||
|
swap.BaseMintDecimals = knownDecimals
|
||||||
|
swap.BaseAmount = decimal.NewFromUint64(addEvent.Amounts[0])
|
||||||
|
swap.BaseReserve = knownReserveBalance
|
||||||
|
swap.UserBaseBalance = knownUserBalance
|
||||||
|
if _, exists := tx.Token[knownMint]; !exists && !knownMint.Equals(wSolMint) {
|
||||||
|
tx.Token[knownMint] = TokenMeta{
|
||||||
|
Mint: knownMint,
|
||||||
|
Decimals: knownDecimals,
|
||||||
|
TokenProgram: knownTokenProgram,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
swap.QuoteMint = knownMint
|
||||||
|
swap.QuoteTokenProgram = knownTokenProgram
|
||||||
|
swap.QuoteMintDecimals = knownDecimals
|
||||||
|
swap.QuoteAmount = decimal.NewFromUint64(addEvent.Amounts[1])
|
||||||
|
swap.QuoteReserve = knownReserveBalance
|
||||||
|
swap.UserQuoteBalance = knownUserBalance
|
||||||
|
if _, exists := tx.Token[knownMint]; !exists && !knownMint.Equals(wSolMint) {
|
||||||
|
tx.Token[knownMint] = TokenMeta{
|
||||||
|
Mint: knownMint,
|
||||||
|
Decimals: knownDecimals,
|
||||||
|
TokenProgram: knownTokenProgram,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []Swap{swap}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dlmmInferOneSideLiquidityAxis(result *RawTx, accounts dlmmOneSideLiquidityAccounts, addEvent dlmmAddLiquidityEvent) bool {
|
||||||
|
knownAmount, ok := dlmmTokenDelta(result, accounts.reserveIdx)
|
||||||
|
if !ok || knownAmount.IsZero() {
|
||||||
|
knownAmount, _ = dlmmTokenDelta(result, accounts.userTokenIdx)
|
||||||
|
}
|
||||||
|
|
||||||
|
amountX := decimal.NewFromUint64(addEvent.Amounts[0])
|
||||||
|
amountY := decimal.NewFromUint64(addEvent.Amounts[1])
|
||||||
|
switch {
|
||||||
|
case !knownAmount.IsZero() && knownAmount.Equal(amountX) && !knownAmount.Equal(amountY):
|
||||||
|
return true
|
||||||
|
case !knownAmount.IsZero() && knownAmount.Equal(amountY) && !knownAmount.Equal(amountX):
|
||||||
|
return false
|
||||||
|
case addEvent.Amounts[0] > 0 && addEvent.Amounts[1] == 0:
|
||||||
|
return true
|
||||||
|
case addEvent.Amounts[1] > 0 && addEvent.Amounts[0] == 0:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func resolveDlmmClaimFeeAccounts(result *RawTx, data []byte, accounts []int) (dlmmLiquidityAccounts, error) {
|
func resolveDlmmClaimFeeAccounts(result *RawTx, data []byte, accounts []int) (dlmmLiquidityAccounts, error) {
|
||||||
if len(data) < 8 {
|
if len(data) < 8 {
|
||||||
return dlmmLiquidityAccounts{}, fmt.Errorf("instruction data too short")
|
return dlmmLiquidityAccounts{}, fmt.Errorf("instruction data too short")
|
||||||
@@ -1993,56 +2400,67 @@ func dlmmTokenBalanceMeta(result *RawTx, accountIndex int) (TokenBalance, bool)
|
|||||||
return TokenBalance{}, false
|
return TokenBalance{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func dlmmBinChangesFromDistribution(amountX, amountY uint64, dist []dlmmBinLiquidityDistribution) []DlmmBinLiquidityChange {
|
func dlmmAllocateByWeights(total uint64, weights []uint64) []decimal.Decimal {
|
||||||
if len(dist) == 0 {
|
if len(weights) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
totalX := decimal.NewFromUint64(amountX)
|
|
||||||
totalY := decimal.NewFromUint64(amountY)
|
sumWeights := uint64(0)
|
||||||
denom := decimal.NewFromInt(10000)
|
for _, weight := range weights {
|
||||||
changes := make([]DlmmBinLiquidityChange, 0, len(dist))
|
sumWeights += weight
|
||||||
for _, item := range dist {
|
|
||||||
x := totalX.Mul(decimal.NewFromInt(int64(item.DistributionX))).Div(denom).Truncate(0)
|
|
||||||
y := totalY.Mul(decimal.NewFromInt(int64(item.DistributionY))).Div(denom).Truncate(0)
|
|
||||||
changes = append(changes, DlmmBinLiquidityChange{
|
|
||||||
BinId: item.BinId,
|
|
||||||
AmountX: x,
|
|
||||||
AmountY: y,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return changes
|
if sumWeights == 0 {
|
||||||
|
sumWeights = uint64(len(weights))
|
||||||
|
weights = append([]uint64(nil), weights...)
|
||||||
|
for i := range weights {
|
||||||
|
weights[i] = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allocations := make([]decimal.Decimal, len(weights))
|
||||||
|
remaining := total
|
||||||
|
for i, weight := range weights {
|
||||||
|
amount := uint64(0)
|
||||||
|
if i == len(weights)-1 {
|
||||||
|
amount = remaining
|
||||||
|
} else if sumWeights > 0 {
|
||||||
|
amount = total * weight / sumWeights
|
||||||
|
if amount > remaining {
|
||||||
|
amount = remaining
|
||||||
|
}
|
||||||
|
remaining -= amount
|
||||||
|
}
|
||||||
|
allocations[i] = decimal.NewFromUint64(amount)
|
||||||
|
}
|
||||||
|
return allocations
|
||||||
}
|
}
|
||||||
|
|
||||||
func dlmmBinChangesFromReduction(reduction []dlmmBinLiquidityReduction) []DlmmBinLiquidityChange {
|
func dlmmApplySignedAllocation(values []decimal.Decimal, negative bool) []decimal.Decimal {
|
||||||
if len(reduction) == 0 {
|
if !negative {
|
||||||
return nil
|
return values
|
||||||
}
|
}
|
||||||
changes := make([]DlmmBinLiquidityChange, 0, len(reduction))
|
out := make([]decimal.Decimal, len(values))
|
||||||
for _, item := range reduction {
|
for i, value := range values {
|
||||||
changes = append(changes, DlmmBinLiquidityChange{
|
out[i] = value.Neg()
|
||||||
BinId: item.BinId,
|
|
||||||
BpsToRemove: item.BpsToRemove,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return changes
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func dlmmBinChangesFromRange(startBinId, endBinId int32, bpsToRemove uint16) []DlmmBinLiquidityChange {
|
func dlmmMinMaxBinIDFromCompressedDeposits(bins []dlmmCompressedBinDepositAmount) (startBinID, endBinID int32) {
|
||||||
if startBinId > endBinId {
|
if len(bins) == 0 {
|
||||||
startBinId, endBinId = endBinId, startBinId
|
return 0, 0
|
||||||
}
|
}
|
||||||
count := int(endBinId-startBinId) + 1
|
startBinID = bins[0].BinID
|
||||||
if count <= 0 {
|
endBinID = bins[0].BinID
|
||||||
return nil
|
for _, bin := range bins[1:] {
|
||||||
|
if bin.BinID < startBinID {
|
||||||
|
startBinID = bin.BinID
|
||||||
}
|
}
|
||||||
changes := make([]DlmmBinLiquidityChange, 0, count)
|
if bin.BinID > endBinID {
|
||||||
for binId := startBinId; binId <= endBinId; binId++ {
|
endBinID = bin.BinID
|
||||||
changes = append(changes, DlmmBinLiquidityChange{
|
|
||||||
BinId: binId,
|
|
||||||
BpsToRemove: bpsToRemove,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return changes
|
}
|
||||||
|
return startBinID, endBinID
|
||||||
}
|
}
|
||||||
|
|
||||||
func dlmmCommonRemoveBp(reduction []dlmmBinLiquidityReduction) int32 {
|
func dlmmCommonRemoveBp(reduction []dlmmBinLiquidityReduction) int32 {
|
||||||
|
|||||||
424
metaoradlmm_test.go
Normal file
424
metaoradlmm_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
15
tx.go
15
tx.go
@@ -52,19 +52,18 @@ type Swap struct {
|
|||||||
StartBinId int32
|
StartBinId int32
|
||||||
EndBinId int32
|
EndBinId int32
|
||||||
RemoveBp int32
|
RemoveBp int32
|
||||||
BinChanges []DlmmBinLiquidityChange
|
|
||||||
PositionAccount solana.PublicKey
|
PositionAccount solana.PublicKey
|
||||||
|
FeeAmount decimal.Decimal
|
||||||
|
FeeBps string
|
||||||
|
LpFeeAmount decimal.Decimal
|
||||||
|
FeeSide string
|
||||||
|
FeeMint solana.PublicKey
|
||||||
|
FeeTokenProgram solana.PublicKey
|
||||||
|
FeeMintDecimals uint8
|
||||||
|
|
||||||
ConsumeUnit uint64
|
ConsumeUnit uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
type DlmmBinLiquidityChange struct {
|
|
||||||
BinId int32
|
|
||||||
AmountX decimal.Decimal
|
|
||||||
AmountY decimal.Decimal
|
|
||||||
BpsToRemove uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type platformInfo struct {
|
type platformInfo struct {
|
||||||
Platform string
|
Platform string
|
||||||
PlatformFee decimal.Decimal
|
PlatformFee decimal.Decimal
|
||||||
|
|||||||
Reference in New Issue
Block a user