1322 lines
38 KiB
TypeScript
1322 lines
38 KiB
TypeScript
|
import {
|
||
|
createAccountInstruction,
|
||
|
nativeToUi,
|
||
|
uiToNative,
|
||
|
zeroKey,
|
||
|
} from '@blockworks-foundation/mango-client/lib/utils'
|
||
|
import {
|
||
|
Account,
|
||
|
Connection,
|
||
|
PublicKey,
|
||
|
SYSVAR_CLOCK_PUBKEY,
|
||
|
SYSVAR_RENT_PUBKEY,
|
||
|
Transaction,
|
||
|
TransactionInstruction,
|
||
|
TransactionSignature,
|
||
|
} from '@solana/web3.js'
|
||
|
import Wallet from '@project-serum/sol-wallet-adapter'
|
||
|
import {
|
||
|
MangoGroup,
|
||
|
MangoSrmAccountLayout,
|
||
|
MarginAccount,
|
||
|
MarginAccountLayout,
|
||
|
} from '@blockworks-foundation/mango-client'
|
||
|
import {
|
||
|
encodeMangoInstruction,
|
||
|
NUM_MARKETS,
|
||
|
NUM_TOKENS,
|
||
|
} from '@blockworks-foundation/mango-client/lib/layout'
|
||
|
import {
|
||
|
makeSettleBorrowInstruction,
|
||
|
makeSettleFundsInstruction,
|
||
|
} from '@blockworks-foundation/mango-client/lib/instruction'
|
||
|
import { sendTransaction } from './send'
|
||
|
import { TOKEN_PROGRAM_ID } from './tokens'
|
||
|
import BN from 'bn.js'
|
||
|
import {
|
||
|
getFeeRates,
|
||
|
getFeeTier,
|
||
|
Market,
|
||
|
OpenOrders,
|
||
|
} from '@project-serum/serum'
|
||
|
import { Order } from '@project-serum/serum/lib/market'
|
||
|
import { SRM_DECIMALS } from '@project-serum/serum/lib/token-instructions'
|
||
|
import { MangoSrmAccount } from '@blockworks-foundation/mango-client/lib/client'
|
||
|
|
||
|
export const DEFAULT_MANGO_GROUP = 'BTC_ETH_USDT'
|
||
|
|
||
|
export async function initMarginAccount(
|
||
|
connection: Connection,
|
||
|
programId: PublicKey,
|
||
|
mangoGroup: MangoGroup,
|
||
|
wallet: Wallet
|
||
|
): Promise<PublicKey> {
|
||
|
// Create a Solana account for the MarginAccount and allocate space
|
||
|
const accInstr = await createAccountInstruction(
|
||
|
connection,
|
||
|
wallet.publicKey,
|
||
|
MarginAccountLayout.span,
|
||
|
programId
|
||
|
)
|
||
|
|
||
|
// Specify the accounts this instruction takes in (see program/src/instruction.rs)
|
||
|
const keys = [
|
||
|
{ isSigner: false, isWritable: false, pubkey: mangoGroup.publicKey },
|
||
|
{ isSigner: false, isWritable: true, pubkey: accInstr.account.publicKey },
|
||
|
{ isSigner: true, isWritable: false, pubkey: wallet.publicKey },
|
||
|
{ isSigner: false, isWritable: false, pubkey: SYSVAR_RENT_PUBKEY },
|
||
|
]
|
||
|
|
||
|
// Encode and create instruction for actual initMarginAccount instruction
|
||
|
const data = encodeMangoInstruction({ InitMarginAccount: {} })
|
||
|
const initMarginAccountInstruction = new TransactionInstruction({
|
||
|
keys,
|
||
|
data,
|
||
|
programId,
|
||
|
})
|
||
|
|
||
|
// Add all instructions to one atomic transaction
|
||
|
const transaction = new Transaction()
|
||
|
transaction.add(accInstr.instruction)
|
||
|
transaction.add(initMarginAccountInstruction)
|
||
|
|
||
|
// Specify signers in addition to the wallet
|
||
|
const signers = [accInstr.account]
|
||
|
|
||
|
const functionName = 'InitMarginAccount'
|
||
|
const sendingMessage = `sending ${functionName} instruction...`
|
||
|
const sentMessage = `${functionName} instruction sent`
|
||
|
const successMessage = `${functionName} instruction success`
|
||
|
|
||
|
await sendTransaction({
|
||
|
transaction,
|
||
|
wallet,
|
||
|
signers,
|
||
|
connection,
|
||
|
sendingMessage,
|
||
|
sentMessage,
|
||
|
successMessage,
|
||
|
})
|
||
|
|
||
|
return accInstr.account.publicKey
|
||
|
}
|
||
|
|
||
|
export async function deposit(
|
||
|
connection: Connection,
|
||
|
programId: PublicKey,
|
||
|
mangoGroup: MangoGroup,
|
||
|
marginAccount: MarginAccount,
|
||
|
wallet: Wallet,
|
||
|
token: PublicKey,
|
||
|
tokenAcc: PublicKey,
|
||
|
|
||
|
quantity: number
|
||
|
): Promise<TransactionSignature> {
|
||
|
const tokenIndex = mangoGroup.getTokenIndex(token)
|
||
|
const nativeQuantity = uiToNative(
|
||
|
quantity,
|
||
|
mangoGroup.mintDecimals[tokenIndex]
|
||
|
)
|
||
|
|
||
|
const keys = [
|
||
|
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey },
|
||
|
{ isSigner: false, isWritable: true, pubkey: marginAccount.publicKey },
|
||
|
{ isSigner: true, isWritable: false, pubkey: wallet.publicKey },
|
||
|
{ isSigner: false, isWritable: true, pubkey: tokenAcc },
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: mangoGroup.vaults[tokenIndex],
|
||
|
},
|
||
|
{ isSigner: false, isWritable: false, pubkey: TOKEN_PROGRAM_ID },
|
||
|
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY },
|
||
|
]
|
||
|
const data = encodeMangoInstruction({
|
||
|
Deposit: { quantity: nativeQuantity },
|
||
|
})
|
||
|
|
||
|
const instruction = new TransactionInstruction({ keys, data, programId })
|
||
|
|
||
|
const transaction = new Transaction()
|
||
|
transaction.add(instruction)
|
||
|
const signers = []
|
||
|
|
||
|
const functionName = 'Deposit'
|
||
|
const sendingMessage = `sending ${functionName} instruction...`
|
||
|
const sentMessage = `${functionName} instruction sent`
|
||
|
const successMessage = `${functionName} instruction success`
|
||
|
return await sendTransaction({
|
||
|
transaction,
|
||
|
wallet,
|
||
|
signers,
|
||
|
connection,
|
||
|
sendingMessage,
|
||
|
sentMessage,
|
||
|
successMessage,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function initMarginAccountAndDeposit(
|
||
|
connection: Connection,
|
||
|
programId: PublicKey,
|
||
|
mangoGroup: MangoGroup,
|
||
|
wallet: Wallet,
|
||
|
token: PublicKey,
|
||
|
tokenAcc: PublicKey,
|
||
|
quantity: number
|
||
|
): Promise<Array<any>> {
|
||
|
// Create a Solana account for the MarginAccount and allocate spac
|
||
|
|
||
|
const accInstr = await createAccountInstruction(
|
||
|
connection,
|
||
|
wallet.publicKey,
|
||
|
MarginAccountLayout.span,
|
||
|
programId
|
||
|
)
|
||
|
|
||
|
// Specify the accounts this instruction takes in (see program/src/instruction.rs)
|
||
|
const keys = [
|
||
|
{ isSigner: false, isWritable: false, pubkey: mangoGroup.publicKey },
|
||
|
{ isSigner: false, isWritable: true, pubkey: accInstr.account.publicKey },
|
||
|
{ isSigner: true, isWritable: false, pubkey: wallet.publicKey },
|
||
|
{ isSigner: false, isWritable: false, pubkey: SYSVAR_RENT_PUBKEY },
|
||
|
]
|
||
|
|
||
|
// Encode and create instruction for actual initMarginAccount instruction
|
||
|
const data = encodeMangoInstruction({ InitMarginAccount: {} })
|
||
|
const initMarginAccountInstruction = new TransactionInstruction({
|
||
|
keys,
|
||
|
data,
|
||
|
programId,
|
||
|
})
|
||
|
|
||
|
// Add all instructions to one atomic transaction
|
||
|
const transaction = new Transaction()
|
||
|
transaction.add(accInstr.instruction)
|
||
|
transaction.add(initMarginAccountInstruction)
|
||
|
|
||
|
const tokenIndex = mangoGroup.getTokenIndex(token)
|
||
|
const nativeQuantity = uiToNative(
|
||
|
quantity,
|
||
|
mangoGroup.mintDecimals[tokenIndex]
|
||
|
)
|
||
|
|
||
|
const depositKeys = [
|
||
|
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey },
|
||
|
{ isSigner: false, isWritable: true, pubkey: accInstr.account.publicKey },
|
||
|
{ isSigner: true, isWritable: false, pubkey: wallet.publicKey },
|
||
|
{ isSigner: false, isWritable: true, pubkey: tokenAcc },
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: mangoGroup.vaults[tokenIndex],
|
||
|
},
|
||
|
{ isSigner: false, isWritable: false, pubkey: TOKEN_PROGRAM_ID },
|
||
|
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY },
|
||
|
]
|
||
|
const depositData = encodeMangoInstruction({
|
||
|
Deposit: { quantity: nativeQuantity },
|
||
|
})
|
||
|
|
||
|
const instruction = new TransactionInstruction({
|
||
|
keys: depositKeys,
|
||
|
data: depositData,
|
||
|
programId,
|
||
|
})
|
||
|
transaction.add(instruction)
|
||
|
|
||
|
// Specify signers in addition to the wallet
|
||
|
const signers = [accInstr.account]
|
||
|
const functionName = 'InitMarginAccount'
|
||
|
const sendingMessage = `sending ${functionName} instruction...`
|
||
|
const sentMessage = `${functionName} instruction sent`
|
||
|
const successMessage = `${functionName} instruction success`
|
||
|
|
||
|
const trxHash = await sendTransaction({
|
||
|
transaction,
|
||
|
wallet,
|
||
|
signers,
|
||
|
connection,
|
||
|
sendingMessage,
|
||
|
sentMessage,
|
||
|
successMessage,
|
||
|
})
|
||
|
return [accInstr.account, trxHash]
|
||
|
}
|
||
|
|
||
|
export async function withdraw(
|
||
|
connection: Connection,
|
||
|
programId: PublicKey,
|
||
|
mangoGroup: MangoGroup,
|
||
|
marginAccount: MarginAccount,
|
||
|
wallet: Wallet,
|
||
|
token: PublicKey,
|
||
|
tokenAcc: PublicKey,
|
||
|
|
||
|
quantity: number
|
||
|
): Promise<TransactionSignature> {
|
||
|
const tokenIndex = mangoGroup.getTokenIndex(token)
|
||
|
const nativeQuantity = uiToNative(
|
||
|
quantity,
|
||
|
mangoGroup.mintDecimals[tokenIndex]
|
||
|
)
|
||
|
|
||
|
const keys = [
|
||
|
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey },
|
||
|
{ isSigner: false, isWritable: true, pubkey: marginAccount.publicKey },
|
||
|
{ isSigner: true, isWritable: false, pubkey: wallet.publicKey },
|
||
|
{ isSigner: false, isWritable: true, pubkey: tokenAcc },
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: mangoGroup.vaults[tokenIndex],
|
||
|
},
|
||
|
{ isSigner: false, isWritable: false, pubkey: mangoGroup.signerKey },
|
||
|
{ isSigner: false, isWritable: false, pubkey: TOKEN_PROGRAM_ID },
|
||
|
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY },
|
||
|
...marginAccount.openOrders.map((pubkey) => ({
|
||
|
isSigner: false,
|
||
|
isWritable: false,
|
||
|
pubkey,
|
||
|
})),
|
||
|
...mangoGroup.oracles.map((pubkey) => ({
|
||
|
isSigner: false,
|
||
|
isWritable: false,
|
||
|
pubkey,
|
||
|
})),
|
||
|
]
|
||
|
const data = encodeMangoInstruction({
|
||
|
Withdraw: { quantity: nativeQuantity },
|
||
|
})
|
||
|
const instruction = new TransactionInstruction({ keys, data, programId })
|
||
|
const transaction = new Transaction()
|
||
|
transaction.add(instruction)
|
||
|
const signers = []
|
||
|
const functionName = 'Withdraw'
|
||
|
const sendingMessage = `sending ${functionName} instruction...`
|
||
|
const sentMessage = `${functionName} instruction sent`
|
||
|
const successMessage = `${functionName} instruction success`
|
||
|
return await sendTransaction({
|
||
|
transaction,
|
||
|
wallet,
|
||
|
signers,
|
||
|
connection,
|
||
|
sendingMessage,
|
||
|
sentMessage,
|
||
|
successMessage,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function borrow(
|
||
|
connection: Connection,
|
||
|
programId: PublicKey,
|
||
|
mangoGroup: MangoGroup,
|
||
|
marginAccount: MarginAccount,
|
||
|
wallet: Wallet,
|
||
|
token: PublicKey,
|
||
|
|
||
|
quantity: number
|
||
|
): Promise<TransactionSignature> {
|
||
|
const tokenIndex = mangoGroup.getTokenIndex(token)
|
||
|
const nativeQuantity = uiToNative(
|
||
|
quantity,
|
||
|
mangoGroup.mintDecimals[tokenIndex]
|
||
|
)
|
||
|
|
||
|
const keys = [
|
||
|
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey },
|
||
|
{ isSigner: false, isWritable: true, pubkey: marginAccount.publicKey },
|
||
|
{ isSigner: true, isWritable: false, pubkey: wallet.publicKey },
|
||
|
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY },
|
||
|
...marginAccount.openOrders.map((pubkey) => ({
|
||
|
isSigner: false,
|
||
|
isWritable: false,
|
||
|
pubkey,
|
||
|
})),
|
||
|
...mangoGroup.oracles.map((pubkey) => ({
|
||
|
isSigner: false,
|
||
|
isWritable: false,
|
||
|
pubkey,
|
||
|
})),
|
||
|
]
|
||
|
const data = encodeMangoInstruction({
|
||
|
Borrow: { tokenIndex: new BN(tokenIndex), quantity: nativeQuantity },
|
||
|
})
|
||
|
|
||
|
const instruction = new TransactionInstruction({ keys, data, programId })
|
||
|
|
||
|
const transaction = new Transaction()
|
||
|
transaction.add(instruction)
|
||
|
const signers = []
|
||
|
const functionName = 'Borrow'
|
||
|
const sendingMessage = `sending ${functionName} instruction...`
|
||
|
const sentMessage = `${functionName} instruction sent`
|
||
|
const successMessage = `${functionName} instruction success`
|
||
|
return await sendTransaction({
|
||
|
transaction,
|
||
|
wallet,
|
||
|
signers,
|
||
|
connection,
|
||
|
sendingMessage,
|
||
|
sentMessage,
|
||
|
successMessage,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function settleBorrow(
|
||
|
connection: Connection,
|
||
|
programId: PublicKey,
|
||
|
mangoGroup: MangoGroup,
|
||
|
marginAccount: MarginAccount,
|
||
|
wallet: Wallet,
|
||
|
|
||
|
token: PublicKey,
|
||
|
quantity: number
|
||
|
): Promise<TransactionSignature> {
|
||
|
const tokenIndex = mangoGroup.getTokenIndex(token)
|
||
|
const nativeQuantity = uiToNative(
|
||
|
quantity,
|
||
|
mangoGroup.mintDecimals[tokenIndex]
|
||
|
)
|
||
|
const keys = [
|
||
|
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey },
|
||
|
{ isSigner: false, isWritable: true, pubkey: marginAccount.publicKey },
|
||
|
{ isSigner: true, isWritable: false, pubkey: wallet.publicKey },
|
||
|
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY },
|
||
|
]
|
||
|
const data = encodeMangoInstruction({
|
||
|
SettleBorrow: { tokenIndex: new BN(tokenIndex), quantity: nativeQuantity },
|
||
|
})
|
||
|
const instruction = new TransactionInstruction({ keys, data, programId })
|
||
|
|
||
|
const transaction = new Transaction()
|
||
|
transaction.add(instruction)
|
||
|
return await packageAndSend(
|
||
|
transaction,
|
||
|
connection,
|
||
|
wallet,
|
||
|
[],
|
||
|
'SettleBorrow'
|
||
|
)
|
||
|
}
|
||
|
|
||
|
// Settle all borrows in one transaction
|
||
|
export async function settleAllBorrows(
|
||
|
connection: Connection,
|
||
|
programId: PublicKey,
|
||
|
mangoGroup: MangoGroup,
|
||
|
marginAccount: MarginAccount,
|
||
|
wallet: Wallet,
|
||
|
|
||
|
token: Array<PublicKey>,
|
||
|
quantity: Array<number>
|
||
|
): Promise<TransactionSignature> {
|
||
|
// Pack all transaction into one transaction
|
||
|
const transaction = new Transaction()
|
||
|
// Signer of the transaction
|
||
|
const signers = []
|
||
|
// Add each token into transaction
|
||
|
token.forEach((tok: PublicKey, i: number) => {
|
||
|
const tokenIndex = mangoGroup.getTokenIndex(tok)
|
||
|
const nativeQuantity = uiToNative(
|
||
|
quantity[i],
|
||
|
mangoGroup.mintDecimals[tokenIndex]
|
||
|
)
|
||
|
const keys = [
|
||
|
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey },
|
||
|
{ isSigner: false, isWritable: true, pubkey: marginAccount.publicKey },
|
||
|
{ isSigner: true, isWritable: false, pubkey: wallet.publicKey },
|
||
|
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY },
|
||
|
]
|
||
|
const data = encodeMangoInstruction({
|
||
|
SettleBorrow: {
|
||
|
tokenIndex: new BN(tokenIndex),
|
||
|
quantity: nativeQuantity,
|
||
|
},
|
||
|
})
|
||
|
const instruction = new TransactionInstruction({ keys, data, programId })
|
||
|
|
||
|
transaction.add(instruction)
|
||
|
})
|
||
|
const functionName = 'SettleBorrows'
|
||
|
const sendingMessage = `sending ${functionName} instruction...`
|
||
|
const sentMessage = `${functionName} instruction sent`
|
||
|
const successMessage = `${functionName} instruction success`
|
||
|
|
||
|
return await sendTransaction({
|
||
|
transaction,
|
||
|
connection,
|
||
|
wallet,
|
||
|
signers,
|
||
|
sendingMessage,
|
||
|
sentMessage,
|
||
|
successMessage,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* If there is no mangoSrmAccount provided, it will create one in the same transaction
|
||
|
*/
|
||
|
export async function depositSrm(
|
||
|
connection: Connection,
|
||
|
programId: PublicKey,
|
||
|
mangoGroup: MangoGroup,
|
||
|
wallet: Wallet,
|
||
|
srmAccount: PublicKey,
|
||
|
quantity: number,
|
||
|
|
||
|
mangoSrmAccount?: PublicKey
|
||
|
): Promise<PublicKey> {
|
||
|
const transaction = new Transaction()
|
||
|
const additionalSigners: Account[] = []
|
||
|
if (!mangoSrmAccount) {
|
||
|
const accInstr = await createAccountInstruction(
|
||
|
connection,
|
||
|
wallet.publicKey,
|
||
|
MangoSrmAccountLayout.span,
|
||
|
programId
|
||
|
)
|
||
|
|
||
|
transaction.add(accInstr.instruction)
|
||
|
additionalSigners.push(accInstr.account)
|
||
|
mangoSrmAccount = accInstr.account.publicKey
|
||
|
}
|
||
|
|
||
|
const nativeQuantity = uiToNative(quantity, SRM_DECIMALS)
|
||
|
|
||
|
const keys = [
|
||
|
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey },
|
||
|
{ isSigner: false, isWritable: true, pubkey: mangoSrmAccount },
|
||
|
{ isSigner: true, isWritable: false, pubkey: wallet.publicKey },
|
||
|
{ isSigner: false, isWritable: true, pubkey: srmAccount },
|
||
|
{ isSigner: false, isWritable: true, pubkey: mangoGroup.srmVault },
|
||
|
{ isSigner: false, isWritable: false, pubkey: TOKEN_PROGRAM_ID },
|
||
|
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY },
|
||
|
{ isSigner: false, isWritable: false, pubkey: SYSVAR_RENT_PUBKEY },
|
||
|
]
|
||
|
const data = encodeMangoInstruction({
|
||
|
DepositSrm: { quantity: nativeQuantity },
|
||
|
})
|
||
|
const instruction = new TransactionInstruction({ keys, data, programId })
|
||
|
transaction.add(instruction)
|
||
|
|
||
|
await packageAndSend(
|
||
|
transaction,
|
||
|
connection,
|
||
|
wallet,
|
||
|
additionalSigners,
|
||
|
'Deposit SRM'
|
||
|
)
|
||
|
return mangoSrmAccount
|
||
|
}
|
||
|
|
||
|
export async function withdrawSrm(
|
||
|
connection: Connection,
|
||
|
programId: PublicKey,
|
||
|
mangoGroup: MangoGroup,
|
||
|
mangoSrmAccount: MangoSrmAccount,
|
||
|
wallet: Wallet,
|
||
|
srmAccount: PublicKey,
|
||
|
|
||
|
quantity: number
|
||
|
): Promise<TransactionSignature> {
|
||
|
const nativeQuantity = uiToNative(quantity, SRM_DECIMALS)
|
||
|
|
||
|
const keys = [
|
||
|
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey },
|
||
|
{ isSigner: false, isWritable: true, pubkey: mangoSrmAccount.publicKey },
|
||
|
{ isSigner: true, isWritable: false, pubkey: wallet.publicKey },
|
||
|
{ isSigner: false, isWritable: true, pubkey: srmAccount },
|
||
|
{ isSigner: false, isWritable: true, pubkey: mangoGroup.srmVault },
|
||
|
{ isSigner: false, isWritable: false, pubkey: mangoGroup.signerKey },
|
||
|
{ isSigner: false, isWritable: false, pubkey: TOKEN_PROGRAM_ID },
|
||
|
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY },
|
||
|
]
|
||
|
const data = encodeMangoInstruction({
|
||
|
WithdrawSrm: { quantity: nativeQuantity },
|
||
|
})
|
||
|
const instruction = new TransactionInstruction({ keys, data, programId })
|
||
|
|
||
|
const transaction = new Transaction()
|
||
|
transaction.add(instruction)
|
||
|
return await packageAndSend(
|
||
|
transaction,
|
||
|
connection,
|
||
|
wallet,
|
||
|
[],
|
||
|
'WithdrawSrm'
|
||
|
)
|
||
|
}
|
||
|
|
||
|
export async function placeOrderAndSettle(
|
||
|
connection: Connection,
|
||
|
programId: PublicKey,
|
||
|
mangoGroup: MangoGroup,
|
||
|
marginAccount: MarginAccount,
|
||
|
spotMarket: Market,
|
||
|
wallet: Wallet,
|
||
|
|
||
|
side: 'buy' | 'sell',
|
||
|
price: number,
|
||
|
size: number,
|
||
|
orderType?: 'limit' | 'ioc' | 'postOnly',
|
||
|
clientId?: BN
|
||
|
): Promise<TransactionSignature> {
|
||
|
orderType = orderType == undefined ? 'limit' : orderType
|
||
|
// orderType = orderType ?? 'limit'
|
||
|
const limitPrice = spotMarket.priceNumberToLots(price)
|
||
|
const maxBaseQuantity = spotMarket.baseSizeNumberToLots(size)
|
||
|
|
||
|
const feeTier = getFeeTier(
|
||
|
0,
|
||
|
nativeToUi(mangoGroup.nativeSrm || 0, SRM_DECIMALS)
|
||
|
)
|
||
|
const rates = getFeeRates(feeTier)
|
||
|
const maxQuoteQuantity = new BN(
|
||
|
spotMarket['_decoded'].quoteLotSize.toNumber() * (1 + rates.taker)
|
||
|
).mul(
|
||
|
spotMarket
|
||
|
.baseSizeNumberToLots(size)
|
||
|
.mul(spotMarket.priceNumberToLots(price))
|
||
|
)
|
||
|
|
||
|
if (maxBaseQuantity.lte(new BN(0))) {
|
||
|
throw new Error('size too small')
|
||
|
}
|
||
|
if (limitPrice.lte(new BN(0))) {
|
||
|
throw new Error('invalid price')
|
||
|
}
|
||
|
const selfTradeBehavior = 'decrementTake'
|
||
|
const marketIndex = mangoGroup.getMarketIndex(spotMarket)
|
||
|
const vaultIndex = side === 'buy' ? mangoGroup.vaults.length - 1 : marketIndex
|
||
|
|
||
|
// Add all instructions to one atomic transaction
|
||
|
const transaction = new Transaction()
|
||
|
|
||
|
// Specify signers in addition to the wallet
|
||
|
const signers: Account[] = []
|
||
|
|
||
|
// Create a Solana account for the open orders account if it's missing
|
||
|
const openOrdersKeys: PublicKey[] = []
|
||
|
for (let i = 0; i < marginAccount.openOrders.length; i++) {
|
||
|
if (
|
||
|
i === marketIndex &&
|
||
|
marginAccount.openOrders[marketIndex].equals(zeroKey)
|
||
|
) {
|
||
|
// open orders missing for this market; create a new one now
|
||
|
const openOrdersSpace = OpenOrders.getLayout(mangoGroup.dexProgramId).span
|
||
|
const openOrdersLamports = await connection.getMinimumBalanceForRentExemption(
|
||
|
openOrdersSpace,
|
||
|
'singleGossip'
|
||
|
)
|
||
|
const accInstr = await createAccountInstruction(
|
||
|
connection,
|
||
|
wallet.publicKey,
|
||
|
openOrdersSpace,
|
||
|
mangoGroup.dexProgramId,
|
||
|
openOrdersLamports
|
||
|
)
|
||
|
|
||
|
transaction.add(accInstr.instruction)
|
||
|
signers.push(accInstr.account)
|
||
|
openOrdersKeys.push(accInstr.account.publicKey)
|
||
|
} else {
|
||
|
openOrdersKeys.push(marginAccount.openOrders[i])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const keys = [
|
||
|
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey },
|
||
|
{ isSigner: true, isWritable: false, pubkey: wallet.publicKey },
|
||
|
{ isSigner: false, isWritable: true, pubkey: marginAccount.publicKey },
|
||
|
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY },
|
||
|
{ isSigner: false, isWritable: false, pubkey: spotMarket.programId },
|
||
|
{ isSigner: false, isWritable: true, pubkey: spotMarket.publicKey },
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: spotMarket['_decoded'].requestQueue,
|
||
|
},
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: spotMarket['_decoded'].eventQueue,
|
||
|
},
|
||
|
{ isSigner: false, isWritable: true, pubkey: spotMarket['_decoded'].bids },
|
||
|
{ isSigner: false, isWritable: true, pubkey: spotMarket['_decoded'].asks },
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: mangoGroup.vaults[vaultIndex],
|
||
|
},
|
||
|
{ isSigner: false, isWritable: false, pubkey: mangoGroup.signerKey },
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: spotMarket['_decoded'].baseVault,
|
||
|
},
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: spotMarket['_decoded'].quoteVault,
|
||
|
},
|
||
|
{ isSigner: false, isWritable: false, pubkey: TOKEN_PROGRAM_ID },
|
||
|
{ isSigner: false, isWritable: false, pubkey: SYSVAR_RENT_PUBKEY },
|
||
|
{ isSigner: false, isWritable: true, pubkey: mangoGroup.srmVault },
|
||
|
...openOrdersKeys.map((pubkey) => ({
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey,
|
||
|
})),
|
||
|
...mangoGroup.oracles.map((pubkey) => ({
|
||
|
isSigner: false,
|
||
|
isWritable: false,
|
||
|
pubkey,
|
||
|
})),
|
||
|
]
|
||
|
|
||
|
const data = encodeMangoInstruction({
|
||
|
PlaceOrder: clientId
|
||
|
? {
|
||
|
side,
|
||
|
limitPrice,
|
||
|
maxBaseQuantity,
|
||
|
maxQuoteQuantity,
|
||
|
selfTradeBehavior,
|
||
|
orderType,
|
||
|
clientId,
|
||
|
limit: 65535,
|
||
|
}
|
||
|
: {
|
||
|
side,
|
||
|
limitPrice,
|
||
|
maxBaseQuantity,
|
||
|
maxQuoteQuantity,
|
||
|
selfTradeBehavior,
|
||
|
orderType,
|
||
|
limit: 65535,
|
||
|
},
|
||
|
})
|
||
|
|
||
|
const placeOrderInstruction = new TransactionInstruction({
|
||
|
keys,
|
||
|
data,
|
||
|
programId,
|
||
|
})
|
||
|
transaction.add(placeOrderInstruction)
|
||
|
|
||
|
const dexSigner = await PublicKey.createProgramAddress(
|
||
|
[
|
||
|
spotMarket.publicKey.toBuffer(),
|
||
|
spotMarket['_decoded'].vaultSignerNonce.toArrayLike(Buffer, 'le', 8),
|
||
|
],
|
||
|
spotMarket.programId
|
||
|
)
|
||
|
const settleFundsIns = makeSettleFundsInstruction(
|
||
|
programId,
|
||
|
mangoGroup.publicKey,
|
||
|
wallet.publicKey,
|
||
|
marginAccount.publicKey,
|
||
|
spotMarket.programId,
|
||
|
spotMarket.publicKey,
|
||
|
openOrdersKeys[marketIndex],
|
||
|
mangoGroup.signerKey,
|
||
|
spotMarket['_decoded'].baseVault,
|
||
|
spotMarket['_decoded'].quoteVault,
|
||
|
mangoGroup.vaults[marketIndex],
|
||
|
mangoGroup.vaults[mangoGroup.vaults.length - 1],
|
||
|
dexSigner
|
||
|
)
|
||
|
transaction.add(settleFundsIns)
|
||
|
|
||
|
const baseTokenIndex = marketIndex
|
||
|
const quoteTokenIndex = NUM_TOKENS - 1
|
||
|
const tokenIndex = side === 'buy' ? baseTokenIndex : quoteTokenIndex
|
||
|
const quantity = marginAccount.getUiBorrow(mangoGroup, tokenIndex)
|
||
|
const nativeQuantity = uiToNative(
|
||
|
quantity,
|
||
|
mangoGroup.mintDecimals[tokenIndex]
|
||
|
)
|
||
|
|
||
|
const settleBorrowIns = makeSettleBorrowInstruction(
|
||
|
programId,
|
||
|
mangoGroup.publicKey,
|
||
|
marginAccount.publicKey,
|
||
|
wallet.publicKey,
|
||
|
tokenIndex,
|
||
|
nativeQuantity
|
||
|
)
|
||
|
|
||
|
transaction.add(settleBorrowIns)
|
||
|
|
||
|
return await packageAndSend(
|
||
|
transaction,
|
||
|
connection,
|
||
|
wallet,
|
||
|
signers,
|
||
|
'PlaceOrder'
|
||
|
)
|
||
|
}
|
||
|
|
||
|
export async function placeAndSettle(
|
||
|
connection: Connection,
|
||
|
programId: PublicKey,
|
||
|
mangoGroup: MangoGroup,
|
||
|
marginAccount: MarginAccount,
|
||
|
spotMarket: Market,
|
||
|
wallet: Wallet,
|
||
|
|
||
|
side: 'buy' | 'sell',
|
||
|
price: number,
|
||
|
size: number,
|
||
|
orderType?: 'limit' | 'ioc' | 'postOnly',
|
||
|
clientId?: BN
|
||
|
): Promise<TransactionSignature> {
|
||
|
orderType = orderType == undefined ? 'limit' : orderType
|
||
|
// orderType = orderType ?? 'limit'
|
||
|
const limitPrice = spotMarket.priceNumberToLots(price)
|
||
|
const maxBaseQuantity = spotMarket.baseSizeNumberToLots(size)
|
||
|
|
||
|
const feeTier = getFeeTier(
|
||
|
0,
|
||
|
nativeToUi(mangoGroup.nativeSrm || 0, SRM_DECIMALS)
|
||
|
)
|
||
|
const rates = getFeeRates(feeTier)
|
||
|
const maxQuoteQuantity = new BN(
|
||
|
spotMarket['_decoded'].quoteLotSize.toNumber() * (1 + rates.taker)
|
||
|
).mul(
|
||
|
spotMarket
|
||
|
.baseSizeNumberToLots(size)
|
||
|
.mul(spotMarket.priceNumberToLots(price))
|
||
|
)
|
||
|
|
||
|
if (maxBaseQuantity.lte(new BN(0))) {
|
||
|
throw new Error('size too small')
|
||
|
}
|
||
|
if (limitPrice.lte(new BN(0))) {
|
||
|
throw new Error('invalid price')
|
||
|
}
|
||
|
const selfTradeBehavior = 'decrementTake'
|
||
|
const marketIndex = mangoGroup.getMarketIndex(spotMarket)
|
||
|
// const vaultIndex = side === 'buy' ? mangoGroup.vaults.length - 1 : marketIndex
|
||
|
|
||
|
// Add all instructions to one atomic transaction
|
||
|
const transaction = new Transaction()
|
||
|
|
||
|
// Specify signers in addition to the wallet
|
||
|
const signers: Account[] = []
|
||
|
|
||
|
const dexSigner = await PublicKey.createProgramAddress(
|
||
|
[
|
||
|
spotMarket.publicKey.toBuffer(),
|
||
|
spotMarket['_decoded'].vaultSignerNonce.toArrayLike(Buffer, 'le', 8),
|
||
|
],
|
||
|
spotMarket.programId
|
||
|
)
|
||
|
|
||
|
// Create a Solana account for the open orders account if it's missing
|
||
|
const openOrdersKeys: PublicKey[] = []
|
||
|
for (let i = 0; i < marginAccount.openOrders.length; i++) {
|
||
|
if (
|
||
|
i === marketIndex &&
|
||
|
marginAccount.openOrders[marketIndex].equals(zeroKey)
|
||
|
) {
|
||
|
// open orders missing for this market; create a new one now
|
||
|
const openOrdersSpace = OpenOrders.getLayout(mangoGroup.dexProgramId).span
|
||
|
const openOrdersLamports = await connection.getMinimumBalanceForRentExemption(
|
||
|
openOrdersSpace,
|
||
|
'singleGossip'
|
||
|
)
|
||
|
const accInstr = await createAccountInstruction(
|
||
|
connection,
|
||
|
wallet.publicKey,
|
||
|
openOrdersSpace,
|
||
|
mangoGroup.dexProgramId,
|
||
|
openOrdersLamports
|
||
|
)
|
||
|
|
||
|
transaction.add(accInstr.instruction)
|
||
|
signers.push(accInstr.account)
|
||
|
openOrdersKeys.push(accInstr.account.publicKey)
|
||
|
} else {
|
||
|
openOrdersKeys.push(marginAccount.openOrders[i])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const keys = [
|
||
|
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey },
|
||
|
{ isSigner: true, isWritable: false, pubkey: wallet.publicKey },
|
||
|
{ isSigner: false, isWritable: true, pubkey: marginAccount.publicKey },
|
||
|
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY },
|
||
|
{ isSigner: false, isWritable: false, pubkey: spotMarket.programId },
|
||
|
{ isSigner: false, isWritable: true, pubkey: spotMarket.publicKey },
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: spotMarket['_decoded'].requestQueue,
|
||
|
},
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: spotMarket['_decoded'].eventQueue,
|
||
|
},
|
||
|
{ isSigner: false, isWritable: true, pubkey: spotMarket['_decoded'].bids },
|
||
|
{ isSigner: false, isWritable: true, pubkey: spotMarket['_decoded'].asks },
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: mangoGroup.vaults[marketIndex],
|
||
|
},
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: mangoGroup.vaults[NUM_TOKENS - 1],
|
||
|
},
|
||
|
{ isSigner: false, isWritable: false, pubkey: mangoGroup.signerKey },
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: spotMarket['_decoded'].baseVault,
|
||
|
},
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: spotMarket['_decoded'].quoteVault,
|
||
|
},
|
||
|
{ isSigner: false, isWritable: false, pubkey: TOKEN_PROGRAM_ID },
|
||
|
{ isSigner: false, isWritable: false, pubkey: SYSVAR_RENT_PUBKEY },
|
||
|
{ isSigner: false, isWritable: true, pubkey: mangoGroup.srmVault },
|
||
|
{ isSigner: false, isWritable: false, pubkey: dexSigner },
|
||
|
...openOrdersKeys.map((pubkey) => ({
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey,
|
||
|
})),
|
||
|
...mangoGroup.oracles.map((pubkey) => ({
|
||
|
isSigner: false,
|
||
|
isWritable: false,
|
||
|
pubkey,
|
||
|
})),
|
||
|
]
|
||
|
|
||
|
const data = encodeMangoInstruction({
|
||
|
PlaceAndSettle: clientId
|
||
|
? {
|
||
|
side,
|
||
|
limitPrice,
|
||
|
maxBaseQuantity,
|
||
|
maxQuoteQuantity,
|
||
|
selfTradeBehavior,
|
||
|
orderType,
|
||
|
clientId,
|
||
|
limit: 65535,
|
||
|
}
|
||
|
: {
|
||
|
side,
|
||
|
limitPrice,
|
||
|
maxBaseQuantity,
|
||
|
maxQuoteQuantity,
|
||
|
selfTradeBehavior,
|
||
|
orderType,
|
||
|
limit: 65535,
|
||
|
},
|
||
|
})
|
||
|
|
||
|
const placeAndSettleInstruction = new TransactionInstruction({
|
||
|
keys,
|
||
|
data,
|
||
|
programId,
|
||
|
})
|
||
|
transaction.add(placeAndSettleInstruction)
|
||
|
|
||
|
return await packageAndSend(
|
||
|
transaction,
|
||
|
connection,
|
||
|
wallet,
|
||
|
signers,
|
||
|
'PlaceAndSettle'
|
||
|
)
|
||
|
}
|
||
|
|
||
|
export async function settleFundsAndBorrows(
|
||
|
connection: Connection,
|
||
|
programId: PublicKey,
|
||
|
mangoGroup: MangoGroup,
|
||
|
marginAccount: MarginAccount,
|
||
|
wallet: Wallet,
|
||
|
spotMarket: Market
|
||
|
): Promise<TransactionSignature> {
|
||
|
const transaction = new Transaction()
|
||
|
const marketIndex = mangoGroup.getMarketIndex(spotMarket)
|
||
|
const dexSigner = await PublicKey.createProgramAddress(
|
||
|
[
|
||
|
spotMarket.publicKey.toBuffer(),
|
||
|
spotMarket['_decoded'].vaultSignerNonce.toArrayLike(Buffer, 'le', 8),
|
||
|
],
|
||
|
spotMarket.programId
|
||
|
)
|
||
|
const settleFundsIns = await makeSettleFundsInstruction(
|
||
|
programId,
|
||
|
mangoGroup.publicKey,
|
||
|
wallet.publicKey,
|
||
|
marginAccount.publicKey,
|
||
|
spotMarket.programId,
|
||
|
spotMarket.publicKey,
|
||
|
marginAccount.openOrders[marketIndex],
|
||
|
mangoGroup.signerKey,
|
||
|
spotMarket['_decoded'].baseVault,
|
||
|
spotMarket['_decoded'].quoteVault,
|
||
|
mangoGroup.vaults[marketIndex],
|
||
|
mangoGroup.vaults[mangoGroup.vaults.length - 1],
|
||
|
dexSigner
|
||
|
)
|
||
|
transaction.add(settleFundsIns)
|
||
|
|
||
|
const tokenIndex = marketIndex
|
||
|
const quantity = marginAccount.getUiBorrow(mangoGroup, tokenIndex)
|
||
|
const nativeQuantity = uiToNative(
|
||
|
quantity,
|
||
|
mangoGroup.mintDecimals[tokenIndex]
|
||
|
)
|
||
|
|
||
|
const settleBorrowIns = await makeSettleBorrowInstruction(
|
||
|
programId,
|
||
|
mangoGroup.publicKey,
|
||
|
marginAccount.publicKey,
|
||
|
wallet.publicKey,
|
||
|
tokenIndex,
|
||
|
nativeQuantity
|
||
|
)
|
||
|
|
||
|
transaction.add(settleBorrowIns)
|
||
|
const signers = []
|
||
|
return await packageAndSend(
|
||
|
transaction,
|
||
|
connection,
|
||
|
wallet,
|
||
|
signers,
|
||
|
'Settle Funds'
|
||
|
)
|
||
|
}
|
||
|
|
||
|
export async function settleFunds(
|
||
|
connection: Connection,
|
||
|
programId: PublicKey,
|
||
|
mangoGroup: MangoGroup,
|
||
|
marginAccount: MarginAccount,
|
||
|
wallet: Wallet,
|
||
|
spotMarket: Market
|
||
|
): Promise<TransactionSignature> {
|
||
|
const marketIndex = mangoGroup.getMarketIndex(spotMarket)
|
||
|
const dexSigner = await PublicKey.createProgramAddress(
|
||
|
[
|
||
|
spotMarket.publicKey.toBuffer(),
|
||
|
spotMarket['_decoded'].vaultSignerNonce.toArrayLike(Buffer, 'le', 8),
|
||
|
],
|
||
|
spotMarket.programId
|
||
|
)
|
||
|
|
||
|
const keys = [
|
||
|
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey },
|
||
|
{ isSigner: true, isWritable: false, pubkey: wallet.publicKey },
|
||
|
{ isSigner: false, isWritable: true, pubkey: marginAccount.publicKey },
|
||
|
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY },
|
||
|
{ isSigner: false, isWritable: false, pubkey: spotMarket.programId },
|
||
|
{ isSigner: false, isWritable: true, pubkey: spotMarket.publicKey },
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: marginAccount.openOrders[marketIndex],
|
||
|
},
|
||
|
{ isSigner: false, isWritable: false, pubkey: mangoGroup.signerKey },
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: spotMarket['_decoded'].baseVault,
|
||
|
},
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: spotMarket['_decoded'].quoteVault,
|
||
|
},
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: mangoGroup.vaults[marketIndex],
|
||
|
},
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: mangoGroup.vaults[mangoGroup.vaults.length - 1],
|
||
|
},
|
||
|
{ isSigner: false, isWritable: false, pubkey: dexSigner },
|
||
|
{ isSigner: false, isWritable: false, pubkey: TOKEN_PROGRAM_ID },
|
||
|
]
|
||
|
const data = encodeMangoInstruction({ SettleFunds: {} })
|
||
|
|
||
|
const instruction = new TransactionInstruction({ keys, data, programId })
|
||
|
|
||
|
// Add all instructions to one atomic transaction
|
||
|
const transaction = new Transaction()
|
||
|
transaction.add(instruction)
|
||
|
|
||
|
const signers = []
|
||
|
const functionName = 'SettleFunds'
|
||
|
const sendingMessage = `sending ${functionName} instruction...`
|
||
|
const sentMessage = `${functionName} instruction sent`
|
||
|
const successMessage = `${functionName} instruction success`
|
||
|
return await sendTransaction({
|
||
|
transaction,
|
||
|
wallet,
|
||
|
signers,
|
||
|
connection,
|
||
|
sendingMessage,
|
||
|
sentMessage,
|
||
|
successMessage,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function cancelOrderAndSettle(
|
||
|
connection: Connection,
|
||
|
programId: PublicKey,
|
||
|
mangoGroup: MangoGroup,
|
||
|
marginAccount: MarginAccount,
|
||
|
wallet: Wallet,
|
||
|
spotMarket: Market,
|
||
|
order: Order
|
||
|
): Promise<TransactionSignature> {
|
||
|
const keys = [
|
||
|
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey },
|
||
|
{ isSigner: true, isWritable: false, pubkey: wallet.publicKey },
|
||
|
{ isSigner: false, isWritable: true, pubkey: marginAccount.publicKey },
|
||
|
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY },
|
||
|
{ isSigner: false, isWritable: false, pubkey: mangoGroup.dexProgramId },
|
||
|
{ isSigner: false, isWritable: true, pubkey: spotMarket.publicKey },
|
||
|
{ isSigner: false, isWritable: true, pubkey: spotMarket['_decoded'].bids },
|
||
|
{ isSigner: false, isWritable: true, pubkey: spotMarket['_decoded'].asks },
|
||
|
{ isSigner: false, isWritable: true, pubkey: order.openOrdersAddress },
|
||
|
{ isSigner: false, isWritable: false, pubkey: mangoGroup.signerKey },
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: spotMarket['_decoded'].eventQueue,
|
||
|
},
|
||
|
]
|
||
|
|
||
|
const data = encodeMangoInstruction({
|
||
|
CancelOrder: {
|
||
|
side: order.side,
|
||
|
orderId: order.orderId,
|
||
|
},
|
||
|
})
|
||
|
|
||
|
const instruction = new TransactionInstruction({ keys, data, programId })
|
||
|
|
||
|
const transaction = new Transaction()
|
||
|
transaction.add(instruction)
|
||
|
|
||
|
const marketIndex = mangoGroup.getMarketIndex(spotMarket)
|
||
|
const dexSigner = await PublicKey.createProgramAddress(
|
||
|
[
|
||
|
spotMarket.publicKey.toBuffer(),
|
||
|
spotMarket['_decoded'].vaultSignerNonce.toArrayLike(Buffer, 'le', 8),
|
||
|
],
|
||
|
spotMarket.programId
|
||
|
)
|
||
|
const settleFundsIns = await makeSettleFundsInstruction(
|
||
|
programId,
|
||
|
mangoGroup.publicKey,
|
||
|
wallet.publicKey,
|
||
|
marginAccount.publicKey,
|
||
|
spotMarket.programId,
|
||
|
spotMarket.publicKey,
|
||
|
marginAccount.openOrders[marketIndex],
|
||
|
mangoGroup.signerKey,
|
||
|
spotMarket['_decoded'].baseVault,
|
||
|
spotMarket['_decoded'].quoteVault,
|
||
|
mangoGroup.vaults[marketIndex],
|
||
|
mangoGroup.vaults[mangoGroup.vaults.length - 1],
|
||
|
dexSigner
|
||
|
)
|
||
|
transaction.add(settleFundsIns)
|
||
|
|
||
|
const baseTokenIndex = marketIndex
|
||
|
const quoteTokenIndex = NUM_TOKENS - 1
|
||
|
|
||
|
const baseTokenQuantity = marginAccount.getUiBorrow(
|
||
|
mangoGroup,
|
||
|
baseTokenIndex
|
||
|
)
|
||
|
const baseTokenNativeQuantity = uiToNative(
|
||
|
baseTokenQuantity,
|
||
|
mangoGroup.mintDecimals[baseTokenIndex]
|
||
|
)
|
||
|
|
||
|
const quoteTokenQuantity = marginAccount.getUiBorrow(
|
||
|
mangoGroup,
|
||
|
quoteTokenIndex
|
||
|
)
|
||
|
const quoteTokenNativeQuantity = uiToNative(
|
||
|
quoteTokenQuantity,
|
||
|
mangoGroup.mintDecimals[quoteTokenIndex]
|
||
|
)
|
||
|
|
||
|
const settleBorrowBaseToken = await makeSettleBorrowInstruction(
|
||
|
programId,
|
||
|
mangoGroup.publicKey,
|
||
|
marginAccount.publicKey,
|
||
|
wallet.publicKey,
|
||
|
baseTokenIndex,
|
||
|
baseTokenNativeQuantity
|
||
|
)
|
||
|
|
||
|
transaction.add(settleBorrowBaseToken)
|
||
|
|
||
|
const settleBorrowQuoteToken = await makeSettleBorrowInstruction(
|
||
|
programId,
|
||
|
mangoGroup.publicKey,
|
||
|
marginAccount.publicKey,
|
||
|
wallet.publicKey,
|
||
|
quoteTokenIndex,
|
||
|
quoteTokenNativeQuantity
|
||
|
)
|
||
|
|
||
|
transaction.add(settleBorrowQuoteToken)
|
||
|
|
||
|
return await packageAndSend(
|
||
|
transaction,
|
||
|
connection,
|
||
|
wallet,
|
||
|
[],
|
||
|
'CancelOrder'
|
||
|
)
|
||
|
}
|
||
|
|
||
|
export async function settleAll(
|
||
|
connection: Connection,
|
||
|
programId: PublicKey,
|
||
|
mangoGroup: MangoGroup,
|
||
|
marginAccount: MarginAccount,
|
||
|
markets: Market[],
|
||
|
wallet: Wallet
|
||
|
): Promise<TransactionSignature> {
|
||
|
const transaction = new Transaction()
|
||
|
|
||
|
const assetGains: number[] = new Array(NUM_TOKENS).fill(0)
|
||
|
|
||
|
for (let i = 0; i < NUM_MARKETS; i++) {
|
||
|
const openOrdersAccount = marginAccount.openOrdersAccounts[i]
|
||
|
if (openOrdersAccount === undefined) {
|
||
|
continue
|
||
|
} else if (
|
||
|
openOrdersAccount.quoteTokenFree.toNumber() === 0 &&
|
||
|
openOrdersAccount.baseTokenFree.toNumber() === 0
|
||
|
) {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
assetGains[i] += openOrdersAccount.baseTokenFree.toNumber()
|
||
|
assetGains[NUM_TOKENS - 1] += openOrdersAccount.quoteTokenFree.toNumber()
|
||
|
|
||
|
const spotMarket = markets[i]
|
||
|
const dexSigner = await PublicKey.createProgramAddress(
|
||
|
[
|
||
|
spotMarket.publicKey.toBuffer(),
|
||
|
spotMarket['_decoded'].vaultSignerNonce.toArrayLike(Buffer, 'le', 8),
|
||
|
],
|
||
|
spotMarket.programId
|
||
|
)
|
||
|
|
||
|
const keys = [
|
||
|
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey },
|
||
|
{ isSigner: true, isWritable: false, pubkey: wallet.publicKey },
|
||
|
{ isSigner: false, isWritable: true, pubkey: marginAccount.publicKey },
|
||
|
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY },
|
||
|
{ isSigner: false, isWritable: false, pubkey: spotMarket.programId },
|
||
|
{ isSigner: false, isWritable: true, pubkey: spotMarket.publicKey },
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: marginAccount.openOrders[i],
|
||
|
},
|
||
|
{ isSigner: false, isWritable: false, pubkey: mangoGroup.signerKey },
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: spotMarket['_decoded'].baseVault,
|
||
|
},
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: spotMarket['_decoded'].quoteVault,
|
||
|
},
|
||
|
{ isSigner: false, isWritable: true, pubkey: mangoGroup.vaults[i] },
|
||
|
{
|
||
|
isSigner: false,
|
||
|
isWritable: true,
|
||
|
pubkey: mangoGroup.vaults[mangoGroup.vaults.length - 1],
|
||
|
},
|
||
|
{ isSigner: false, isWritable: false, pubkey: dexSigner },
|
||
|
{ isSigner: false, isWritable: false, pubkey: TOKEN_PROGRAM_ID },
|
||
|
]
|
||
|
const data = encodeMangoInstruction({ SettleFunds: {} })
|
||
|
|
||
|
const instruction = new TransactionInstruction({ keys, data, programId })
|
||
|
transaction.add(instruction)
|
||
|
}
|
||
|
|
||
|
const deposits = marginAccount.getDeposits(mangoGroup)
|
||
|
const liabs = marginAccount.getLiabs(mangoGroup)
|
||
|
|
||
|
for (let i = 0; i < NUM_TOKENS; i++) {
|
||
|
// TODO test this. maybe it hits transaction size limit
|
||
|
|
||
|
const deposit = deposits[i] + assetGains[i]
|
||
|
if (deposit === 0 || liabs[i] === 0) {
|
||
|
continue
|
||
|
}
|
||
|
const keys = [
|
||
|
{ isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey },
|
||
|
{ isSigner: false, isWritable: true, pubkey: marginAccount.publicKey },
|
||
|
{ isSigner: true, isWritable: false, pubkey: wallet.publicKey },
|
||
|
{ isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY },
|
||
|
]
|
||
|
const data = encodeMangoInstruction({
|
||
|
SettleBorrow: {
|
||
|
tokenIndex: new BN(i),
|
||
|
quantity: uiToNative(liabs[i] * 2, mangoGroup.mintDecimals[i]),
|
||
|
},
|
||
|
})
|
||
|
|
||
|
const instruction = new TransactionInstruction({ keys, data, programId })
|
||
|
transaction.add(instruction)
|
||
|
}
|
||
|
|
||
|
if (transaction.instructions.length === 0) {
|
||
|
throw new Error('No unsettled funds')
|
||
|
}
|
||
|
|
||
|
return await packageAndSend(transaction, connection, wallet, [], 'Settle All')
|
||
|
}
|
||
|
|
||
|
async function packageAndSend(
|
||
|
transaction: Transaction,
|
||
|
connection: Connection,
|
||
|
wallet: Wallet,
|
||
|
signers: Account[],
|
||
|
functionName: string
|
||
|
): Promise<TransactionSignature> {
|
||
|
const sendingMessage = `sending ${functionName} instruction...`
|
||
|
const sentMessage = `${functionName} instruction sent`
|
||
|
const successMessage = `${functionName} instruction success`
|
||
|
return await sendTransaction({
|
||
|
transaction,
|
||
|
wallet,
|
||
|
signers,
|
||
|
connection,
|
||
|
sendingMessage,
|
||
|
sentMessage,
|
||
|
successMessage,
|
||
|
})
|
||
|
}
|