all parser
This commit is contained in:
880
metaorapool.go
Normal file
880
metaorapool.go
Normal file
@@ -0,0 +1,880 @@
|
||||
package pump_parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
agbinary "github.com/gagliardetto/binary"
|
||||
"github.com/gagliardetto/solana-go"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type metaoraPoolInitializePoolData struct {
|
||||
TokenAAmount uint64 `json:"tokenAAmount"`
|
||||
TokenBAmount uint64 `json:"tokenBAmount"`
|
||||
}
|
||||
|
||||
var (
|
||||
meteoraVaultProgram = solana.MustPublicKeyFromBase58("24Uqj9JCLxUeoC3hGfh5W3s9FM9uCHDS2SG3LYwBpyTi")
|
||||
meteoraVaultDepositDiscriminator = []byte{0xf2, 0x23, 0xc6, 0x89, 0x52, 0xe1, 0xf2, 0xb6}
|
||||
meteoraVaultWithdrawDiscriminator = []byte{0xb7, 0x12, 0x46, 0x9c, 0x94, 0x6d, 0xa1, 0x22}
|
||||
|
||||
tokenProgramMintToDiscriminator = []byte{0x07}
|
||||
tokenProgramTransferDiscriminator = []byte{0x03}
|
||||
tokenProgramBurnDiscriminator = []byte{0x08}
|
||||
)
|
||||
|
||||
func metaoraPoolParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||
if !tx.rawTx.accountList[instruction.ProgramIDIndex].Equals(metaoraPoolProgramID) {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("metaoraPool program instruction not found, offset, %d, %d", offset[0], offset[1])
|
||||
}
|
||||
|
||||
decode := instruction.Data
|
||||
if len(decode) < 8 {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("metaoraPool program instruction data too short, offset, %d, %d", offset[0], offset[1])
|
||||
}
|
||||
|
||||
discriminator := *(*[8]byte)(decode[:8])
|
||||
|
||||
switch discriminator {
|
||||
case metaoraPoolInitializePermissionlessConstantProductPoolWithConfigDiscriminator,
|
||||
metaoraPoolInitializePermissionlessConstantProductPoolWithConfig2Discriminator:
|
||||
return metaoraPoolInitializePermissionlessConstantProductPoolWithConfig(tx, instruction, innerInstructions, offset)
|
||||
case metaoraPoolInitializePermissionlessPoolDiscriminator,
|
||||
metaoraPoolInitializePermissionlessPoolWithFeeTierDiscriminator,
|
||||
metaoraPoolInitializeCustomizablePermissionlessConstantProductPoolDiscriminator:
|
||||
return metaoraPoolInitializePermissionlessPool(tx, instruction, innerInstructions, offset)
|
||||
case metaoraPoolInitializePermissionedPoolDiscriminator:
|
||||
return metaoraPoolInitializePermissionedPool(tx, instruction, innerInstructions, offset)
|
||||
case metaoraPoolSwapDiscriminator:
|
||||
return metaoraPoolSwap(tx, instruction, innerInstructions, offset)
|
||||
case metaoraPoolAddImbalanceLiquidityDiscriminator,
|
||||
metaoraPoolAddBalanceLiquidityDiscriminator,
|
||||
metaoraPoolBootstrapLiquidityDiscriminator:
|
||||
return metaoraPoolAddLiquidity(tx, instruction, innerInstructions, offset)
|
||||
case metaoraPoolRemoveLiquiditySingleSideDiscriminator,
|
||||
metaoraPoolRemoveBalanceLiquidityDiscriminator,
|
||||
metaoraPoolClaimFeeDiscriminator:
|
||||
return metaoraPoolRemoveLiquidity(tx, instruction, innerInstructions, offset)
|
||||
default:
|
||||
return nil, increaseOffset(offset), InstructionIgnoredError
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// InitializePermissionlessConstantProductPoolWithConfig
|
||||
// InitializePermissionlessConstantProductPoolWithConfig2
|
||||
func metaoraPoolInitializePermissionlessConstantProductPoolWithConfig(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||
var data metaoraPoolInitializePoolData
|
||||
err := agbinary.NewBorshDecoder(instruction.Data[8:]).Decode(&data)
|
||||
if err != nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("failed to deserialize initialize pool data: %w", err)
|
||||
}
|
||||
|
||||
if len(instruction.Accounts) < 20 {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for initialize instruction")
|
||||
}
|
||||
|
||||
tokenAMint := tx.rawTx.accountList[instruction.Accounts[3]]
|
||||
tokenBMint := tx.rawTx.accountList[instruction.Accounts[4]]
|
||||
|
||||
baseVaultAccountIndex := instruction.Accounts[7]
|
||||
quoteVaultAccountIndex := instruction.Accounts[8]
|
||||
baseVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultAccountIndex)
|
||||
quoteVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultAccountIndex)
|
||||
|
||||
if baseVaultTokenBalance == nil || quoteVaultTokenBalance == nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("failed to get token balances")
|
||||
}
|
||||
|
||||
baseReserve, err := decimal.NewFromString(baseVaultTokenBalance.UITokenAmount.Amount)
|
||||
if err != nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("failed to parse base reserve: %w", err)
|
||||
}
|
||||
|
||||
quoteReserve, err := decimal.NewFromString(quoteVaultTokenBalance.UITokenAmount.Amount)
|
||||
if err != nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("failed to parse quote reserve: %w", err)
|
||||
}
|
||||
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||
var prefixLen = offset[1]
|
||||
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
||||
if err != nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("meta pools initial get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||
}
|
||||
|
||||
var meteoraVaultProgramId int
|
||||
for i, acc := range tx.rawTx.accountList {
|
||||
if acc.Equals(meteoraVaultProgram) {
|
||||
meteoraVaultProgramId = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var baseFound, quoteFound bool
|
||||
|
||||
if meteoraVaultProgramId > 0 {
|
||||
for innerIndex, innerInstr := range inners {
|
||||
if innerInstr.ProgramIDIndex == meteoraVaultProgramId && len(innerInstr.Data) >= 16 && bytes.Equal(innerInstr.Data[:8], pumpEventDiscriminator[:]) && bytes.Equal(innerInstr.Data[8:16], meteoraVaultDepositDiscriminator[:]) {
|
||||
if len(innerInstr.Accounts) < 2 {
|
||||
continue
|
||||
}
|
||||
if !baseFound && innerInstr.Accounts[1] == baseVaultAccountIndex {
|
||||
baseFound = true
|
||||
}
|
||||
if !quoteFound && innerInstr.Accounts[1] == quoteVaultAccountIndex {
|
||||
quoteFound = true
|
||||
}
|
||||
if baseFound && quoteFound {
|
||||
if offset[1] == 0 {
|
||||
offset[0] += 1
|
||||
} else {
|
||||
offset[1] = uint(innerIndex) + 1 + prefixLen
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return []Swap{
|
||||
{
|
||||
Program: SolProgramMeteoraPools,
|
||||
Event: "create",
|
||||
Pool: tx.rawTx.accountList[instruction.Accounts[0]],
|
||||
BaseMint: tokenAMint,
|
||||
QuoteMint: tokenBMint,
|
||||
BaseTokenProgram: baseVaultTokenBalance.ProgramIDAccount,
|
||||
QuoteTokenProgram: quoteVaultTokenBalance.ProgramIDAccount,
|
||||
Creator: tx.rawTx.accountList[0],
|
||||
BaseMintDecimals: uint8(baseVaultTokenBalance.UITokenAmount.Decimals),
|
||||
QuoteMintDecimals: uint8(quoteVaultTokenBalance.UITokenAmount.Decimals),
|
||||
User: tx.rawTx.accountList[instruction.Accounts[18]],
|
||||
BaseAmount: decimal.NewFromUint64(data.TokenAAmount),
|
||||
QuoteAmount: decimal.NewFromUint64(data.TokenBAmount),
|
||||
BaseReserve: baseReserve,
|
||||
QuoteReserve: quoteReserve,
|
||||
EntryContract: entryContract,
|
||||
},
|
||||
}, offset, nil
|
||||
|
||||
}
|
||||
|
||||
// InitializePermissionlessPool
|
||||
// InitializePermissionlessPoolWithFeeTier
|
||||
func metaoraPoolInitializePermissionlessPool(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||
// discriminator + tokenA amount + tokenB amount
|
||||
if len(instruction.Data) < 24 {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("not enough data for initialize instruction")
|
||||
}
|
||||
|
||||
var data metaoraPoolInitializePoolData
|
||||
err := agbinary.NewBorshDecoder(instruction.Data[len(instruction.Data)-16:]).Decode(&data)
|
||||
if err != nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("failed to deserialize initialize pool data: %w", err)
|
||||
}
|
||||
|
||||
if len(instruction.Accounts) < 20 {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for initialize instruction")
|
||||
}
|
||||
|
||||
tokenAMint := tx.rawTx.accountList[instruction.Accounts[2]]
|
||||
tokenBMint := tx.rawTx.accountList[instruction.Accounts[3]]
|
||||
|
||||
baseVaultAccountIndex := instruction.Accounts[6]
|
||||
quoteVaultAccountIndex := instruction.Accounts[7]
|
||||
baseVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultAccountIndex)
|
||||
quoteVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultAccountIndex)
|
||||
|
||||
if baseVaultTokenBalance == nil || quoteVaultTokenBalance == nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("failed to get token balances")
|
||||
}
|
||||
|
||||
baseReserve, err := decimal.NewFromString(baseVaultTokenBalance.UITokenAmount.Amount)
|
||||
if err != nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("failed to parse base reserve: %w", err)
|
||||
}
|
||||
|
||||
quoteReserve, err := decimal.NewFromString(quoteVaultTokenBalance.UITokenAmount.Amount)
|
||||
if err != nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("failed to parse quote reserve: %w", err)
|
||||
}
|
||||
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||
var prefixLen = offset[1]
|
||||
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
||||
if err != nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("meta pools initial get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||
}
|
||||
|
||||
var meteoraVaultProgramId int
|
||||
for i, acc := range tx.rawTx.accountList {
|
||||
if acc.Equals(meteoraVaultProgram) {
|
||||
meteoraVaultProgramId = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var baseFound, quoteFound bool
|
||||
|
||||
if meteoraVaultProgramId > 0 {
|
||||
for innerIndex, innerInstr := range inners {
|
||||
if innerInstr.ProgramIDIndex == meteoraVaultProgramId && len(innerInstr.Data) >= 16 && bytes.Equal(innerInstr.Data[:8], pumpEventDiscriminator[:]) && bytes.Equal(innerInstr.Data[8:16], meteoraVaultDepositDiscriminator[:]) {
|
||||
if len(innerInstr.Accounts) < 2 {
|
||||
continue
|
||||
}
|
||||
if !baseFound && innerInstr.Accounts[1] == baseVaultAccountIndex {
|
||||
baseFound = true
|
||||
}
|
||||
if !quoteFound && innerInstr.Accounts[1] == quoteVaultAccountIndex {
|
||||
quoteFound = true
|
||||
}
|
||||
if baseFound && quoteFound {
|
||||
if offset[1] == 0 {
|
||||
offset[0] += 1
|
||||
} else {
|
||||
offset[1] = uint(innerIndex) + 1 + prefixLen
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return []Swap{
|
||||
{
|
||||
Program: SolProgramMeteoraPools,
|
||||
Event: "create",
|
||||
Pool: tx.rawTx.accountList[instruction.Accounts[0]],
|
||||
BaseMint: tokenAMint,
|
||||
QuoteMint: tokenBMint,
|
||||
BaseTokenProgram: baseVaultTokenBalance.ProgramIDAccount,
|
||||
QuoteTokenProgram: quoteVaultTokenBalance.ProgramIDAccount,
|
||||
Creator: tx.rawTx.accountList[0],
|
||||
BaseMintDecimals: uint8(baseVaultTokenBalance.UITokenAmount.Decimals),
|
||||
QuoteMintDecimals: uint8(quoteVaultTokenBalance.UITokenAmount.Decimals),
|
||||
User: tx.rawTx.accountList[instruction.Accounts[18]],
|
||||
BaseAmount: decimal.NewFromUint64(data.TokenAAmount),
|
||||
QuoteAmount: decimal.NewFromUint64(data.TokenBAmount),
|
||||
BaseReserve: baseReserve,
|
||||
QuoteReserve: quoteReserve,
|
||||
EntryContract: entryContract,
|
||||
},
|
||||
}, offset, nil
|
||||
}
|
||||
|
||||
func metaoraPoolInitializePermissionedPool(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||
|
||||
// discriminator + tokenA amount + tokenB amount
|
||||
if len(instruction.Data) < 24 {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("not enough data for initialize instruction")
|
||||
}
|
||||
|
||||
var data metaoraPoolInitializePoolData
|
||||
err := agbinary.NewBorshDecoder(instruction.Data[len(instruction.Data)-16:]).Decode(&data)
|
||||
if err != nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("failed to deserialize initialize pool data: %w", err)
|
||||
}
|
||||
|
||||
if len(instruction.Accounts) < 20 {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for initialize instruction")
|
||||
}
|
||||
|
||||
tokenAMint := tx.rawTx.accountList[instruction.Accounts[2]]
|
||||
tokenBMint := tx.rawTx.accountList[instruction.Accounts[3]]
|
||||
|
||||
baseVaultAccountIndex := instruction.Accounts[10]
|
||||
quoteVaultAccountIndex := instruction.Accounts[11]
|
||||
baseVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultAccountIndex)
|
||||
quoteVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultAccountIndex)
|
||||
|
||||
if baseVaultTokenBalance == nil || quoteVaultTokenBalance == nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("failed to get token balances")
|
||||
}
|
||||
|
||||
baseReserve, err := decimal.NewFromString(baseVaultTokenBalance.UITokenAmount.Amount)
|
||||
if err != nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("failed to parse base reserve: %w", err)
|
||||
}
|
||||
|
||||
quoteReserve, err := decimal.NewFromString(quoteVaultTokenBalance.UITokenAmount.Amount)
|
||||
if err != nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("failed to parse quote reserve: %w", err)
|
||||
}
|
||||
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||
var prefixLen = offset[1]
|
||||
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
||||
if err != nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("meta pools initial get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||
}
|
||||
|
||||
var meteoraVaultProgramId int
|
||||
for i, acc := range tx.rawTx.accountList {
|
||||
if acc.Equals(meteoraVaultProgram) {
|
||||
meteoraVaultProgramId = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var baseFound, quoteFound bool
|
||||
|
||||
if meteoraVaultProgramId > 0 {
|
||||
for innerIndex, innerInstr := range inners {
|
||||
if innerInstr.ProgramIDIndex == meteoraVaultProgramId && len(innerInstr.Data) >= 16 && bytes.Equal(innerInstr.Data[:8], pumpEventDiscriminator[:]) && bytes.Equal(innerInstr.Data[8:16], meteoraVaultDepositDiscriminator[:]) {
|
||||
if len(innerInstr.Accounts) < 2 {
|
||||
continue
|
||||
}
|
||||
if !baseFound && innerInstr.Accounts[1] == baseVaultAccountIndex {
|
||||
baseFound = true
|
||||
}
|
||||
if !quoteFound && innerInstr.Accounts[1] == quoteVaultAccountIndex {
|
||||
quoteFound = true
|
||||
}
|
||||
if baseFound && quoteFound {
|
||||
if offset[1] == 0 {
|
||||
offset[0] += 1
|
||||
} else {
|
||||
offset[1] = uint(innerIndex) + 1 + prefixLen
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return []Swap{
|
||||
{
|
||||
Program: SolProgramMeteoraPools,
|
||||
Event: "create",
|
||||
Pool: tx.rawTx.accountList[instruction.Accounts[0]],
|
||||
BaseMint: tokenAMint,
|
||||
QuoteMint: tokenBMint,
|
||||
BaseTokenProgram: baseVaultTokenBalance.ProgramIDAccount,
|
||||
QuoteTokenProgram: quoteVaultTokenBalance.ProgramIDAccount,
|
||||
Creator: tx.rawTx.accountList[0],
|
||||
BaseMintDecimals: uint8(baseVaultTokenBalance.UITokenAmount.Decimals),
|
||||
QuoteMintDecimals: uint8(quoteVaultTokenBalance.UITokenAmount.Decimals),
|
||||
User: tx.rawTx.accountList[instruction.Accounts[18]],
|
||||
BaseAmount: decimal.NewFromUint64(data.TokenAAmount),
|
||||
QuoteAmount: decimal.NewFromUint64(data.TokenBAmount),
|
||||
BaseReserve: baseReserve,
|
||||
QuoteReserve: quoteReserve,
|
||||
EntryContract: entryContract,
|
||||
},
|
||||
}, offset, nil
|
||||
}
|
||||
|
||||
// BootstrapLiquidity
|
||||
// AddImbalanceLiquidity
|
||||
// AddBalanceLiquidity
|
||||
func metaoraPoolAddLiquidity(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||
if len(instruction.Accounts) < 14 {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("invalid instruction accounts length")
|
||||
}
|
||||
|
||||
pool := tx.rawTx.accountList[instruction.Accounts[0]]
|
||||
lpMint := tx.rawTx.accountList[instruction.Accounts[1]]
|
||||
payer := tx.rawTx.accountList[instruction.Accounts[13]]
|
||||
userPoolLp := tx.rawTx.accountList[instruction.Accounts[2]]
|
||||
// vault for storing real tokens
|
||||
// NOTE: because meteora pools will put assets of different pairs together,
|
||||
// we cannot directly use the vault balance to calculate liquidity
|
||||
var meteoraVaultProgramId int
|
||||
for i, acc := range tx.rawTx.accountList {
|
||||
if acc.Equals(meteoraVaultProgram) {
|
||||
meteoraVaultProgramId = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if meteoraVaultProgramId == 0 {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("meteora vault program not found")
|
||||
}
|
||||
|
||||
// 7, 8
|
||||
baseVaultLpAccountBalance, _ := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[7])
|
||||
quoteVaultLpAccountBalance, _ := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[8])
|
||||
if baseVaultLpAccountBalance == nil || quoteVaultLpAccountBalance == nil {
|
||||
return nil, increaseOffset(offset), InstructionIgnoredError //fmt.Errorf("failed to get vault lp account balances")
|
||||
}
|
||||
|
||||
// 9,10
|
||||
baseVaultAccountBalance, _ := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[9])
|
||||
quoteVaultAccountBalance, _ := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[10])
|
||||
if baseVaultAccountBalance == nil || quoteVaultAccountBalance == nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("failed to get vault lp account balances")
|
||||
}
|
||||
var prefixLen = offset[1]
|
||||
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
||||
if err != nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("meta pools initial get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||
}
|
||||
var baseFound, quoteFound bool
|
||||
|
||||
var baseAmount, quoteAmount decimal.Decimal
|
||||
var (
|
||||
baseMint = solana.PublicKey{}
|
||||
quoteMint = solana.PublicKey{}
|
||||
baseTokenProgram = solana.PublicKey{}
|
||||
quoteTokenProgram = solana.PublicKey{}
|
||||
baseDecimals uint8
|
||||
quoteDecimals uint8
|
||||
baseReserve decimal.Decimal
|
||||
quoteReserve decimal.Decimal
|
||||
)
|
||||
baseMint = baseVaultAccountBalance.MintAccount
|
||||
quoteMint = quoteVaultAccountBalance.MintAccount
|
||||
quoteTokenProgram = quoteVaultAccountBalance.ProgramIDAccount
|
||||
baseTokenProgram = baseVaultAccountBalance.ProgramIDAccount
|
||||
|
||||
baseDecimals = uint8(baseVaultAccountBalance.UITokenAmount.Decimals)
|
||||
baseReserve, err = decimal.NewFromString(baseVaultLpAccountBalance.UITokenAmount.Amount)
|
||||
if err != nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("failed to parse base reserve: %w", err)
|
||||
}
|
||||
if baseDecimals != uint8(baseVaultLpAccountBalance.UITokenAmount.Decimals) {
|
||||
decimalDiff := int(baseDecimals) - int(baseVaultLpAccountBalance.UITokenAmount.Decimals)
|
||||
baseReserve = baseReserve.Mul(decimal.NewFromInt(10).Pow(decimal.NewFromInt(int64(decimalDiff))))
|
||||
}
|
||||
|
||||
quoteDecimals = uint8(quoteVaultAccountBalance.UITokenAmount.Decimals)
|
||||
quoteReserve, err = decimal.NewFromString(quoteVaultLpAccountBalance.UITokenAmount.Amount)
|
||||
if err != nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("failed to parse quote reserve: %w", err)
|
||||
}
|
||||
if quoteDecimals != uint8(quoteVaultLpAccountBalance.UITokenAmount.Decimals) {
|
||||
decimalDiff := int(quoteDecimals) - int(quoteVaultLpAccountBalance.UITokenAmount.Decimals)
|
||||
quoteReserve = quoteReserve.Mul(decimal.NewFromInt(10).Pow(decimal.NewFromInt(int64(decimalDiff))))
|
||||
}
|
||||
|
||||
for innerIndex := 0; innerIndex < len(inners); innerIndex++ {
|
||||
innerInstr := inners[innerIndex]
|
||||
|
||||
if innerInstr.ProgramIDIndex == meteoraVaultProgramId &&
|
||||
len(innerInstr.Data) >= 16 &&
|
||||
bytes.Equal(innerInstr.Data[:8], eventDiscriminator[:]) &&
|
||||
bytes.Equal(innerInstr.Data[8:16], meteoraVaultDepositDiscriminator[:]) {
|
||||
if len(innerInstr.Accounts) < 6 {
|
||||
continue
|
||||
}
|
||||
if innerIndex+1 >= len(inners) {
|
||||
continue
|
||||
}
|
||||
transferInstr := inners[innerIndex+1]
|
||||
_, to, amount, err := parseTokenTransfer(tx.rawTx, transferInstr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
innerIndex++ // skip transfer instruction
|
||||
if !baseFound && to.Equals(tx.rawTx.accountList[baseVaultAccountBalance.AccountIndex]) {
|
||||
baseFound = true
|
||||
baseAmount = decimal.NewFromUint64(amount)
|
||||
} else if !quoteFound && to.Equals(tx.rawTx.accountList[quoteVaultAccountBalance.AccountIndex]) {
|
||||
quoteFound = true
|
||||
quoteAmount = decimal.NewFromUint64(amount)
|
||||
}
|
||||
}
|
||||
if (baseFound || quoteFound) && (tx.rawTx.accountList[innerInstr.ProgramIDIndex] == solana.TokenProgramID ||
|
||||
tx.rawTx.accountList[innerInstr.ProgramIDIndex] == solana.Token2022ProgramID) && innerInstr.Data[0] == 7 {
|
||||
if len(innerInstr.Accounts) < 3 {
|
||||
continue
|
||||
}
|
||||
// mint lp token
|
||||
if tx.rawTx.accountList[innerInstr.Accounts[0]] == lpMint && tx.rawTx.accountList[innerInstr.Accounts[1]] == userPoolLp {
|
||||
if offset[1] == 0 {
|
||||
offset[0] += 1
|
||||
} else {
|
||||
offset[1] = uint(innerIndex) + 1 + prefixLen
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !baseFound && !quoteFound {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("failed to find deposit instructions")
|
||||
}
|
||||
|
||||
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||
|
||||
var event = "add_liquidity_one_side"
|
||||
if baseFound && quoteFound {
|
||||
// both sides
|
||||
event = "add_liquidity"
|
||||
}
|
||||
swap := Swap{
|
||||
Program: SolProgramMeteoraPools,
|
||||
Event: event,
|
||||
Pool: pool,
|
||||
BaseMint: baseMint,
|
||||
QuoteMint: quoteMint,
|
||||
BaseTokenProgram: baseTokenProgram,
|
||||
QuoteTokenProgram: quoteTokenProgram,
|
||||
BaseMintDecimals: baseDecimals,
|
||||
QuoteMintDecimals: quoteDecimals,
|
||||
User: payer,
|
||||
BaseAmount: baseAmount,
|
||||
QuoteAmount: quoteAmount,
|
||||
BaseReserve: baseReserve,
|
||||
QuoteReserve: quoteReserve,
|
||||
EntryContract: entryContract,
|
||||
}
|
||||
|
||||
return []Swap{swap}, offset, nil
|
||||
|
||||
}
|
||||
|
||||
// RemoveLiquiditySingleSide
|
||||
// ClaimFee
|
||||
// RemoveBalanceLiquidity
|
||||
func metaoraPoolRemoveLiquidity(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||
if len(instruction.Accounts) < 14 {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("invalid instruction accounts length")
|
||||
}
|
||||
|
||||
pool := tx.rawTx.accountList[instruction.Accounts[0]]
|
||||
lpMint := tx.rawTx.accountList[instruction.Accounts[1]]
|
||||
var (
|
||||
userPoolLp solana.PublicKey
|
||||
baseVaultIdx int
|
||||
quoteVaultIdx int
|
||||
baseLpVaultIdx int
|
||||
quoteLpVaultIdx int
|
||||
userIdx int
|
||||
)
|
||||
if bytes.Equal(instruction.Data[:8], metaoraPoolRemoveLiquiditySingleSideDiscriminator[:]) {
|
||||
userPoolLp = tx.rawTx.accountList[instruction.Accounts[2]]
|
||||
//userBaseAccountIdx = 11
|
||||
//userQuoteAccountIdx = 12
|
||||
baseVaultIdx = 9
|
||||
quoteVaultIdx = 10
|
||||
baseLpVaultIdx = 3
|
||||
quoteLpVaultIdx = 4
|
||||
userIdx = 12
|
||||
} else if bytes.Equal(instruction.Data[:8], metaoraPoolClaimFeeDiscriminator[:]) {
|
||||
if len(instruction.Accounts) < 16 {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("invalid instruction accounts length")
|
||||
}
|
||||
userPoolLp = tx.rawTx.accountList[instruction.Accounts[5]]
|
||||
//userBaseAccountIdx = 15
|
||||
//userQuoteAccountIdx = 16
|
||||
|
||||
baseVaultIdx = 7
|
||||
quoteVaultIdx = 8
|
||||
baseLpVaultIdx = 11
|
||||
quoteLpVaultIdx = 12
|
||||
userIdx = 3
|
||||
} else if bytes.Equal(instruction.Data[:8], metaoraPoolRemoveBalanceLiquidityDiscriminator[:]) {
|
||||
userPoolLp = tx.rawTx.accountList[instruction.Accounts[2]]
|
||||
//userBaseAccountIdx = 11
|
||||
//userQuoteAccountIdx = 12
|
||||
|
||||
baseVaultIdx = 9
|
||||
quoteVaultIdx = 10
|
||||
baseLpVaultIdx = 3
|
||||
quoteLpVaultIdx = 4
|
||||
userIdx = 12
|
||||
|
||||
} else {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("invalid remove liquidity instruction discriminator")
|
||||
}
|
||||
// vault for storing real tokens
|
||||
// NOTE: because meteora pools will put assets of different pairs together,
|
||||
// we cannot directly use the vault balance to calculate liquidity
|
||||
var meteoraVaultProgramId int
|
||||
for i, acc := range tx.rawTx.accountList {
|
||||
if acc.Equals(meteoraVaultProgram) {
|
||||
meteoraVaultProgramId = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if meteoraVaultProgramId == 0 {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("meteora vault program not found")
|
||||
}
|
||||
|
||||
// 7, 8
|
||||
baseVaultLpAccountBalance, _ := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[baseLpVaultIdx])
|
||||
quoteVaultLpAccountBalance, _ := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[quoteLpVaultIdx])
|
||||
if baseVaultLpAccountBalance == nil || quoteVaultLpAccountBalance == nil {
|
||||
return nil, increaseOffset(offset), InstructionIgnoredError // fmt.Errorf("failed to get vault lp account balances")
|
||||
}
|
||||
|
||||
// 9,10
|
||||
baseVaultAccountBalance, _ := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[baseVaultIdx])
|
||||
quoteVaultAccountBalance, _ := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[quoteVaultIdx])
|
||||
if baseVaultAccountBalance == nil || quoteVaultAccountBalance == nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("failed to get vault lp account balances")
|
||||
}
|
||||
var prefixLen = offset[1]
|
||||
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
||||
if err != nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("meta pools initial get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||
}
|
||||
var baseFound, quoteFound bool
|
||||
|
||||
var baseAmount, quoteAmount decimal.Decimal
|
||||
var (
|
||||
baseMint = solana.PublicKey{}
|
||||
quoteMint = solana.PublicKey{}
|
||||
baseTokenProgram = solana.PublicKey{}
|
||||
quoteTokenProgram = solana.PublicKey{}
|
||||
baseDecimals uint8
|
||||
quoteDecimals uint8
|
||||
baseReserve decimal.Decimal
|
||||
quoteReserve decimal.Decimal
|
||||
)
|
||||
|
||||
baseMint = baseVaultAccountBalance.MintAccount
|
||||
quoteMint = quoteVaultAccountBalance.MintAccount
|
||||
baseTokenProgram = baseVaultAccountBalance.ProgramIDAccount
|
||||
quoteTokenProgram = quoteVaultAccountBalance.ProgramIDAccount
|
||||
|
||||
baseDecimals = uint8(baseVaultAccountBalance.UITokenAmount.Decimals)
|
||||
baseReserve, err = decimal.NewFromString(baseVaultLpAccountBalance.UITokenAmount.Amount)
|
||||
if err != nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("failed to parse base reserve: %w", err)
|
||||
}
|
||||
if baseDecimals != uint8(baseVaultLpAccountBalance.UITokenAmount.Decimals) {
|
||||
decimalDiff := int(baseDecimals) - int(baseVaultLpAccountBalance.UITokenAmount.Decimals)
|
||||
baseReserve = baseReserve.Mul(decimal.NewFromInt(10).Pow(decimal.NewFromInt(int64(decimalDiff))))
|
||||
}
|
||||
|
||||
quoteDecimals = uint8(quoteVaultAccountBalance.UITokenAmount.Decimals)
|
||||
quoteReserve, err = decimal.NewFromString(quoteVaultLpAccountBalance.UITokenAmount.Amount)
|
||||
if err != nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("failed to parse quote reserve: %w", err)
|
||||
}
|
||||
if quoteDecimals != uint8(quoteVaultLpAccountBalance.UITokenAmount.Decimals) {
|
||||
decimalDiff := int(quoteDecimals) - int(quoteVaultLpAccountBalance.UITokenAmount.Decimals)
|
||||
quoteReserve = quoteReserve.Mul(decimal.NewFromInt(10).Pow(decimal.NewFromInt(int64(decimalDiff))))
|
||||
}
|
||||
|
||||
for innerIndex := 0; innerIndex < len(inners); innerIndex++ {
|
||||
innerInstr := inners[innerIndex]
|
||||
|
||||
if innerInstr.ProgramIDIndex == meteoraVaultProgramId && len(innerInstr.Data) >= 8 &&
|
||||
bytes.Equal(innerInstr.Data[:8], meteoraVaultWithdrawDiscriminator[:]) {
|
||||
if len(innerInstr.Accounts) < 6 {
|
||||
continue
|
||||
}
|
||||
if innerIndex+1 >= len(inners) {
|
||||
continue
|
||||
}
|
||||
|
||||
transferInstr := inners[innerIndex+1]
|
||||
|
||||
from, _, amount, err := parseTokenTransfer(tx.rawTx, transferInstr)
|
||||
if err != nil {
|
||||
fmt.Println("parse tx error:", err, tx.GetTxHash(), transferInstr)
|
||||
continue
|
||||
}
|
||||
|
||||
innerIndex++ // skip transfer instruction
|
||||
|
||||
if !baseFound && from.Equals(tx.rawTx.accountList[instruction.Accounts[baseVaultIdx]]) {
|
||||
//base
|
||||
baseFound = true
|
||||
baseAmount = decimal.NewFromUint64(amount)
|
||||
} else if !quoteFound && from.Equals(tx.rawTx.accountList[instruction.Accounts[quoteVaultIdx]]) {
|
||||
// quote
|
||||
quoteFound = true
|
||||
quoteAmount = decimal.NewFromUint64(amount)
|
||||
}
|
||||
}
|
||||
if (baseFound || quoteFound) && (tx.rawTx.accountList[innerInstr.ProgramIDIndex] == solana.TokenProgramID ||
|
||||
tx.rawTx.accountList[innerInstr.ProgramIDIndex] == solana.Token2022ProgramID) && innerInstr.Data[0] == 8 {
|
||||
if len(innerInstr.Accounts) < 3 {
|
||||
continue
|
||||
}
|
||||
// mint lp token
|
||||
if tx.rawTx.accountList[innerInstr.Accounts[1]] == lpMint && tx.rawTx.accountList[innerInstr.Accounts[0]] == userPoolLp {
|
||||
if offset[1] == 0 {
|
||||
offset[0] += 1
|
||||
} else {
|
||||
offset[1] = uint(innerIndex) + 1 + prefixLen
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !baseFound && !quoteFound {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("failed to find withdraw instructions, baseFound: %v, quoteFound: %v", baseFound, quoteFound)
|
||||
}
|
||||
|
||||
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||
|
||||
var event = "remove_liquidity_one_side"
|
||||
if baseFound && quoteFound {
|
||||
event = "remove_liquidity"
|
||||
}
|
||||
swap := Swap{
|
||||
Program: SolProgramMeteoraPools,
|
||||
Event: event,
|
||||
Pool: pool,
|
||||
BaseMint: baseMint,
|
||||
QuoteMint: quoteMint,
|
||||
BaseTokenProgram: baseTokenProgram,
|
||||
QuoteTokenProgram: quoteTokenProgram,
|
||||
BaseMintDecimals: baseDecimals,
|
||||
QuoteMintDecimals: quoteDecimals,
|
||||
User: tx.rawTx.accountList[userIdx],
|
||||
BaseAmount: baseAmount,
|
||||
QuoteAmount: quoteAmount,
|
||||
BaseReserve: baseReserve,
|
||||
QuoteReserve: quoteReserve,
|
||||
EntryContract: entryContract,
|
||||
}
|
||||
|
||||
return []Swap{swap}, offset, nil
|
||||
}
|
||||
|
||||
func metaoraPoolSwap(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||
pool := tx.rawTx.accountList[instruction.Accounts[0]]
|
||||
payer := tx.rawTx.accountList[instruction.Accounts[12]]
|
||||
|
||||
sourceAccountIndex := instruction.Accounts[1]
|
||||
destinationAccountIndex := instruction.Accounts[2]
|
||||
|
||||
// vault for storing real tokens
|
||||
// NOTE: because meteora pools will put assets of different pairs together,
|
||||
// we cannot directly use the vault balance to calculate liquidity
|
||||
|
||||
//parse reserves from vault accounts
|
||||
baseVaultIdx := instruction.Accounts[6]
|
||||
quoteVaultIdx := instruction.Accounts[5]
|
||||
baseVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultIdx)
|
||||
quoteVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultIdx)
|
||||
if baseVaultTokenBalance == nil || quoteVaultTokenBalance == nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("failed to get vault token balances")
|
||||
}
|
||||
|
||||
baseVaultLpBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[10])
|
||||
quoteVaultLpBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[9])
|
||||
if baseVaultLpBalance == nil || quoteVaultLpBalance == nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("failed to get vault lp balances")
|
||||
}
|
||||
baseTokenProgram := baseVaultTokenBalance.ProgramIDAccount
|
||||
quoteTokenProgram := quoteVaultTokenBalance.ProgramIDAccount
|
||||
baseMint := baseVaultTokenBalance.MintAccount
|
||||
quoteMint := quoteVaultTokenBalance.MintAccount
|
||||
baseDecimals := uint8(baseVaultTokenBalance.UITokenAmount.Decimals)
|
||||
quoteDecimals := uint8(quoteVaultTokenBalance.UITokenAmount.Decimals)
|
||||
|
||||
baseReserve := decimal.Zero
|
||||
quoteReserve := decimal.Zero
|
||||
if baseVaultLpBalance.UITokenAmount.Decimals == baseVaultTokenBalance.UITokenAmount.Decimals {
|
||||
baseReserve, _ = decimal.NewFromString(baseVaultLpBalance.UITokenAmount.Amount)
|
||||
} else {
|
||||
decimalsDiff := int32(baseVaultTokenBalance.UITokenAmount.Decimals) - int32(baseVaultLpBalance.UITokenAmount.Decimals)
|
||||
multiplier := decimal.NewFromInt(10).Pow(decimal.NewFromInt32(decimalsDiff))
|
||||
baseLpAmount, _ := decimal.NewFromString(baseVaultLpBalance.UITokenAmount.Amount)
|
||||
baseReserve = baseLpAmount.Mul(multiplier)
|
||||
}
|
||||
|
||||
if quoteVaultLpBalance.UITokenAmount.Decimals == quoteVaultTokenBalance.UITokenAmount.Decimals {
|
||||
quoteReserve, _ = decimal.NewFromString(quoteVaultLpBalance.UITokenAmount.Amount)
|
||||
} else {
|
||||
decimalsDiff := int32(quoteVaultTokenBalance.UITokenAmount.Decimals) - int32(quoteVaultLpBalance.UITokenAmount.Decimals)
|
||||
multiplier := decimal.NewFromInt(10).Pow(decimal.NewFromInt32(decimalsDiff))
|
||||
quoteLpAmount, _ := decimal.NewFromString(quoteVaultLpBalance.UITokenAmount.Amount)
|
||||
quoteReserve = quoteLpAmount.Mul(multiplier)
|
||||
}
|
||||
|
||||
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||
var prefixLen = offset[1]
|
||||
inners, err := getInnerInstructions(innerInstructions, prefixLen)
|
||||
if err != nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("meta pools initial get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||
}
|
||||
var meteoraVaultProgramId int
|
||||
for i, acc := range tx.rawTx.accountList {
|
||||
if acc.Equals(meteoraVaultProgram) {
|
||||
meteoraVaultProgramId = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var baseFound, quoteFound bool
|
||||
var (
|
||||
baseAmount decimal.Decimal
|
||||
quoteAmount decimal.Decimal
|
||||
event string
|
||||
)
|
||||
for innerIndex := 0; innerIndex < len(inners); innerIndex++ {
|
||||
innerInstr := inners[innerIndex]
|
||||
//
|
||||
if innerInstr.ProgramIDIndex == meteoraVaultProgramId && len(innerInstr.Data) >= 8 &&
|
||||
(bytes.Equal(innerInstr.Data[:8], meteoraVaultWithdrawDiscriminator[:]) ||
|
||||
bytes.Equal(innerInstr.Data[:8], meteoraVaultDepositDiscriminator[:])) {
|
||||
if len(innerInstr.Accounts) < 6 {
|
||||
continue
|
||||
}
|
||||
if innerIndex+1 >= len(inners) {
|
||||
continue
|
||||
}
|
||||
|
||||
transferInstr := inners[innerIndex+1]
|
||||
if (tx.rawTx.accountList[transferInstr.ProgramIDIndex] != solana.TokenProgramID &&
|
||||
tx.rawTx.accountList[transferInstr.ProgramIDIndex] != solana.Token2022ProgramID) || transferInstr.Data[0] != 3 {
|
||||
continue
|
||||
}
|
||||
innerIndex++ // skip transfer instruction
|
||||
if len(innerInstr.Accounts) == 7 &&
|
||||
(bytes.Equal(innerInstr.Data[:8], meteoraVaultWithdrawDiscriminator[:]) || bytes.Equal(innerInstr.Data[:8], meteoraVaultDepositDiscriminator[:])) {
|
||||
if innerInstr.Accounts[1] == baseVaultIdx {
|
||||
//base
|
||||
baseFound = true
|
||||
baseAmount = decimal.NewFromUint64(binary.LittleEndian.Uint64(transferInstr.Data[1:9]))
|
||||
if bytes.Equal(innerInstr.Data[:8], meteoraVaultWithdrawDiscriminator[:]) {
|
||||
event = "buy"
|
||||
} else {
|
||||
event = "sell"
|
||||
}
|
||||
} else if innerInstr.Accounts[1] == quoteVaultIdx {
|
||||
// quote
|
||||
quoteFound = true
|
||||
quoteAmount = decimal.NewFromUint64(binary.LittleEndian.Uint64(transferInstr.Data[1:9]))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
if baseFound && quoteFound {
|
||||
if offset[1] == 0 {
|
||||
offset[0] += 1
|
||||
} else {
|
||||
offset[1] = uint(innerIndex) + 1 + prefixLen + 1 // +1 for mint or withdraw instruction,
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !baseFound || !quoteFound {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("failed to find meteora pool event in inner instructions")
|
||||
}
|
||||
|
||||
userBase := getAccountBalanceAfterTx(tx.rawTx, sourceAccountIndex)
|
||||
userQuote := getAccountBalanceAfterTx(tx.rawTx, destinationAccountIndex)
|
||||
|
||||
swaps := []Swap{
|
||||
{
|
||||
Program: SolProgramMeteoraPools,
|
||||
Event: event,
|
||||
Pool: pool,
|
||||
BaseMint: baseMint,
|
||||
QuoteMint: quoteMint,
|
||||
BaseTokenProgram: baseTokenProgram,
|
||||
QuoteTokenProgram: quoteTokenProgram,
|
||||
Creator: solana.PublicKey{},
|
||||
BaseMintDecimals: baseDecimals,
|
||||
QuoteMintDecimals: quoteDecimals,
|
||||
User: payer,
|
||||
BaseAmount: baseAmount,
|
||||
QuoteAmount: quoteAmount,
|
||||
BaseReserve: baseReserve,
|
||||
QuoteReserve: quoteReserve,
|
||||
UserBaseBalance: userBase,
|
||||
UserQuoteBalance: userQuote,
|
||||
EntryContract: entryContract,
|
||||
},
|
||||
}
|
||||
return swaps, offset, nil
|
||||
}
|
||||
Reference in New Issue
Block a user