Updated tests to be support more options
This commit is contained in:
parent
87d09b0fa0
commit
6952e68b82
17
src/utils.ts
17
src/utils.ts
|
@ -27,6 +27,22 @@ export async function sleep(ms) {
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getDecimalCount(value: number): number {
|
||||||
|
if (
|
||||||
|
!isNaN(value) &&
|
||||||
|
Math.floor(value) !== value &&
|
||||||
|
value.toString().includes('.')
|
||||||
|
)
|
||||||
|
return value.toString().split('.')[1].length || 0;
|
||||||
|
if (
|
||||||
|
!isNaN(value) &&
|
||||||
|
Math.floor(value) !== value &&
|
||||||
|
value.toString().includes('e')
|
||||||
|
)
|
||||||
|
return parseInt(value.toString().split('e-')[1] || '0');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
export async function simulateTransaction(
|
export async function simulateTransaction(
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
transaction: Transaction,
|
transaction: Transaction,
|
||||||
|
@ -368,4 +384,3 @@ export function decodeRecentEvents(
|
||||||
|
|
||||||
return { header, nodes };
|
return { header, nodes };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { MangoClient, MangoGroup, MarginAccount } from '../src/client';
|
||||||
import { findLargestTokenAccountForOwner } from '../src/utils';
|
import { findLargestTokenAccountForOwner } from '../src/utils';
|
||||||
import IDS from '../src/ids.json';
|
import IDS from '../src/ids.json';
|
||||||
import { Account, Connection, PublicKey } from '@solana/web3.js';
|
import { Account, Connection, PublicKey } from '@solana/web3.js';
|
||||||
import { Market } from '@project-serum/serum';
|
import { Market, OpenOrders } from '@project-serum/serum';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import { blob, struct, u8, nu64 } from 'buffer-layout';
|
import { blob, struct, u8, nu64 } from 'buffer-layout';
|
||||||
|
@ -18,9 +18,12 @@ if (!process.env.AGGREGATOR_PATH) {
|
||||||
import {
|
import {
|
||||||
_sendTransaction,
|
_sendTransaction,
|
||||||
createWalletAndRequestAirdrop,
|
createWalletAndRequestAirdrop,
|
||||||
|
getSpotMarketDetails,
|
||||||
createMangoGroupSymbolMappings,
|
createMangoGroupSymbolMappings,
|
||||||
createTokenAccountWithBalance,
|
createTokenAccountWithBalance,
|
||||||
getOwnedTokenAccounts,
|
getMinSizeAndPriceForMarket,
|
||||||
|
placeOrderUsingSerumDex,
|
||||||
|
cancelOrdersUsingSerumDex,
|
||||||
getAndDecodeBidsAndAsksForOwner,
|
getAndDecodeBidsAndAsksForOwner,
|
||||||
performSingleDepositOrWithdrawal,
|
performSingleDepositOrWithdrawal,
|
||||||
getAndDecodeBidsAndAsks,
|
getAndDecodeBidsAndAsks,
|
||||||
|
@ -55,23 +58,17 @@ function chunkOrders(orders: any[], chunkSize: number) {
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initAccountsWithBalances(neededBalances: number[]) {
|
async function initAccountsWithBalances(neededBalances: number[], wrappedSol: boolean) {
|
||||||
const owner = await createWalletAndRequestAirdrop(connection, 5);
|
const owner = await createWalletAndRequestAirdrop(connection, 5);
|
||||||
const mangoGroupTokenMappings = await createMangoGroupSymbolMappings(connection, mangoGroupIds);
|
const mangoGroupTokenMappings = await createMangoGroupSymbolMappings(connection, mangoGroupIds);
|
||||||
await Promise.all(neededBalances.map(async (x, i) => {
|
await Promise.all(neededBalances.map(async (x, i) => {
|
||||||
if (x > 0) {
|
if (x > 0) {
|
||||||
const baseSymbol = mangoGroupSymbols[i];
|
const baseSymbol = mangoGroupSymbols[i];
|
||||||
await createTokenAccountWithBalance(connection, owner, baseSymbol, mangoGroupTokenMappings, clusterIds.faucets, x);
|
await createTokenAccountWithBalance(connection, owner, baseSymbol, mangoGroupTokenMappings, clusterIds.faucets, x, wrappedSol);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
prettyPrintOwnerKeys(owner, "Account");
|
prettyPrintOwnerKeys(owner, "Account");
|
||||||
}
|
return owner;
|
||||||
|
|
||||||
async function getSpotMarketDetails(mangoGroupSpotMarket: any): Promise<any> {
|
|
||||||
const [spotMarketSymbol, spotMarketAddress] = mangoGroupSpotMarket;
|
|
||||||
const [baseSymbol, quoteSymbol] = spotMarketSymbol.split('/');
|
|
||||||
const spotMarket = await Market.load(connection, new PublicKey(spotMarketAddress), { skipPreflight: true, commitment: 'singleGossip'}, dexProgramId);
|
|
||||||
return { spotMarket, baseSymbol, quoteSymbol };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function requestPriceChange(mangoGroup: MangoGroup, requiredPrice: number, baseSymbol: string) {
|
async function requestPriceChange(mangoGroup: MangoGroup, requiredPrice: number, baseSymbol: string) {
|
||||||
|
@ -107,7 +104,7 @@ async function cleanOrderBook(mangoGroupSpotMarket: any) {
|
||||||
console.info("Cleaning order book, this will take a while...");
|
console.info("Cleaning order book, this will take a while...");
|
||||||
const mangoGroup = await client.getMangoGroup(connection, mangoGroupPk);
|
const mangoGroup = await client.getMangoGroup(connection, mangoGroupPk);
|
||||||
const mangoGroupTokenMappings = await createMangoGroupSymbolMappings(connection, mangoGroupIds);
|
const mangoGroupTokenMappings = await createMangoGroupSymbolMappings(connection, mangoGroupIds);
|
||||||
const { spotMarket, baseSymbol, quoteSymbol } = await getSpotMarketDetails(mangoGroupSpotMarket);
|
const { spotMarket, baseSymbol, quoteSymbol } = await getSpotMarketDetails(connection, mangoGroupSpotMarket, dexProgramId);
|
||||||
let bna = await getAndDecodeBidsAndAsks(connection, spotMarket);
|
let bna = await getAndDecodeBidsAndAsks(connection, spotMarket);
|
||||||
let allAsks: any[] = [...bna.askOrderBook].map(x => ({ price: x.price, size: x.size })).reverse();
|
let allAsks: any[] = [...bna.askOrderBook].map(x => ({ price: x.price, size: x.size })).reverse();
|
||||||
let allBids: any[] = [...bna.bidOrderBook].map(x => ({ price: x.price, size: x.size })).reverse();
|
let allBids: any[] = [...bna.bidOrderBook].map(x => ({ price: x.price, size: x.size })).reverse();
|
||||||
|
@ -124,7 +121,8 @@ async function cleanOrderBook(mangoGroupSpotMarket: any) {
|
||||||
marginAccount = await client.getMarginAccount(connection, marginAccountPk, dexProgramId);
|
marginAccount = await client.getMarginAccount(connection, marginAccountPk, dexProgramId);
|
||||||
const price: number = Math.max(...ask.map((x: any) => x.price));
|
const price: number = Math.max(...ask.map((x: any) => x.price));
|
||||||
const size: number = ask.reduce(( a: any, b: any ) => a + b.size, 0);
|
const size: number = ask.reduce(( a: any, b: any ) => a + b.size, 0);
|
||||||
const roundedSize = Math.round( size * 1e2 ) / 1e2;
|
const roundedSize = Math.round( size * 1e6 ) / 1e6;
|
||||||
|
console.info(`Buying ${roundedSize} for ${price}`)
|
||||||
await client.placeAndSettle(connection, mangoProgramId, mangoGroup, marginAccount, spotMarket, owner, 'buy', price, roundedSize);
|
await client.placeAndSettle(connection, mangoProgramId, mangoGroup, marginAccount, spotMarket, owner, 'buy', price, roundedSize);
|
||||||
}
|
}
|
||||||
const amountNeededToClearBids: number = Math.ceil(allBids.reduce((acc, bid) => acc + (bid.size), 0) + 10);
|
const amountNeededToClearBids: number = Math.ceil(allBids.reduce((acc, bid) => acc + (bid.size), 0) + 10);
|
||||||
|
@ -135,7 +133,8 @@ async function cleanOrderBook(mangoGroupSpotMarket: any) {
|
||||||
marginAccount = await client.getMarginAccount(connection, marginAccountPk, dexProgramId);
|
marginAccount = await client.getMarginAccount(connection, marginAccountPk, dexProgramId);
|
||||||
const price: number = Math.min(...bid.map((x: any) => x.price));
|
const price: number = Math.min(...bid.map((x: any) => x.price));
|
||||||
const size: number = bid.reduce(( a: any, b: any ) => a + b.size, 0);
|
const size: number = bid.reduce(( a: any, b: any ) => a + b.size, 0);
|
||||||
const roundedSize = Math.round( size * 1e2 ) / 1e2;
|
const roundedSize = Math.round( size * 1e6 ) / 1e6;
|
||||||
|
console.info(`Selling ${roundedSize} for ${price}`)
|
||||||
await client.placeAndSettle(connection, mangoProgramId, mangoGroup, marginAccount, spotMarket, owner, 'sell', price, roundedSize);
|
await client.placeAndSettle(connection, mangoProgramId, mangoGroup, marginAccount, spotMarket, owner, 'sell', price, roundedSize);
|
||||||
}
|
}
|
||||||
bna = await getAndDecodeBidsAndAsks(connection, spotMarket);
|
bna = await getAndDecodeBidsAndAsks(connection, spotMarket);
|
||||||
|
@ -145,6 +144,7 @@ async function cleanOrderBook(mangoGroupSpotMarket: any) {
|
||||||
expect(allBids).to.be.empty;
|
expect(allBids).to.be.empty;
|
||||||
prettyPrintOwnerKeys(owner, "Cleaner");
|
prettyPrintOwnerKeys(owner, "Cleaner");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.info(error);
|
||||||
throw new Error(`
|
throw new Error(`
|
||||||
Test Error: ${error.message},
|
Test Error: ${error.message},
|
||||||
${prettyPrintOwnerKeys(owner, "Cleaner")}
|
${prettyPrintOwnerKeys(owner, "Cleaner")}
|
||||||
|
@ -163,19 +163,14 @@ async function placeNOrdersAfterLimit(mangoGroupSpotMarket: any, marketIndex: nu
|
||||||
const buyerMarginAccountPk = await client.initMarginAccount(connection, mangoProgramId, mangoGroup, buyerOwner);
|
const buyerMarginAccountPk = await client.initMarginAccount(connection, mangoProgramId, mangoGroup, buyerOwner);
|
||||||
let buyerMarginAccount = await client.getMarginAccount(connection, buyerMarginAccountPk, dexProgramId);
|
let buyerMarginAccount = await client.getMarginAccount(connection, buyerMarginAccountPk, dexProgramId);
|
||||||
|
|
||||||
const { spotMarket, baseSymbol, quoteSymbol } = await getSpotMarketDetails(mangoGroupSpotMarket);
|
const { spotMarket, baseSymbol, quoteSymbol } = await getSpotMarketDetails(connection, mangoGroupSpotMarket, dexProgramId);
|
||||||
const baseSymbolIndex = mangoGroupSymbols.findIndex(x => x === baseSymbol);
|
|
||||||
const quoteSymbolIndex = mangoGroupSymbols.findIndex(x => x === quoteSymbol);
|
|
||||||
|
|
||||||
const [orderSize, orderPrice, _] = await getOrderSizeAndPrice(connection, spotMarket, mangoGroupTokenMappings, baseSymbol, quoteSymbol, 'buy');
|
const [orderSize, orderPrice, _] = await getOrderSizeAndPrice(connection, spotMarket, mangoGroupTokenMappings, baseSymbol, quoteSymbol, 'buy');
|
||||||
const neededQuoteAmount = orderPrice * orderSize;
|
const neededQuoteAmount = orderPrice * orderSize;
|
||||||
const neededBaseAmountForAllTrades = orderSize * orderQuantity;
|
|
||||||
const neededQuoteAmountForAllTrades = neededQuoteAmount * orderQuantity;
|
const neededQuoteAmountForAllTrades = neededQuoteAmount * orderQuantity;
|
||||||
console.info("neededQuoteAmountForAllTrades:", neededQuoteAmountForAllTrades);
|
|
||||||
|
|
||||||
await createTokenAccountWithBalance(connection, buyerOwner, baseSymbol, mangoGroupTokenMappings, clusterIds.faucets, neededQuoteAmountForAllTrades);
|
await createTokenAccountWithBalance(connection, buyerOwner, baseSymbol, mangoGroupTokenMappings, clusterIds.faucets, neededQuoteAmountForAllTrades);
|
||||||
await performSingleDepositOrWithdrawal(connection, buyerOwner, client, mangoGroup, mangoProgramId, baseSymbol, mangoGroupTokenMappings, buyerMarginAccount, 'deposit', neededQuoteAmountForAllTrades);
|
await performSingleDepositOrWithdrawal(connection, buyerOwner, client, mangoGroup, mangoProgramId, baseSymbol, mangoGroupTokenMappings, buyerMarginAccount, 'deposit', neededQuoteAmountForAllTrades);
|
||||||
|
|
||||||
await requestPriceChange(mangoGroup, orderPrice, baseSymbol);
|
await requestPriceChange(mangoGroup, orderPrice, baseSymbol);
|
||||||
|
|
||||||
for (let i = 0; i < orderQuantity; i++) {
|
for (let i = 0; i < orderQuantity; i++) {
|
||||||
|
@ -186,7 +181,6 @@ async function placeNOrdersAfterLimit(mangoGroupSpotMarket: any, marketIndex: nu
|
||||||
buyerMarginAccount = await client.getMarginAccount(connection, buyerMarginAccount.publicKey, dexProgramId);
|
buyerMarginAccount = await client.getMarginAccount(connection, buyerMarginAccount.publicKey, dexProgramId);
|
||||||
openOrdersForOwner = await getAndDecodeBidsAndAsksForOwner(connection, spotMarket, buyerMarginAccount.openOrdersAccounts[marketIndex]);
|
openOrdersForOwner = await getAndDecodeBidsAndAsksForOwner(connection, spotMarket, buyerMarginAccount.openOrdersAccounts[marketIndex]);
|
||||||
// TODO: this should be a for loop of cancellations
|
// TODO: this should be a for loop of cancellations
|
||||||
// NOTE: Maybe trying cancelling last order not first
|
|
||||||
expect(openOrdersForOwner).to.be.an('array').and.to.have.lengthOf(128);
|
expect(openOrdersForOwner).to.be.an('array').and.to.have.lengthOf(128);
|
||||||
await client.cancelOrder(connection, mangoProgramId, mangoGroup, buyerMarginAccount, buyerOwner, spotMarket, openOrdersForOwner[0]);
|
await client.cancelOrder(connection, mangoProgramId, mangoGroup, buyerMarginAccount, buyerOwner, spotMarket, openOrdersForOwner[0]);
|
||||||
buyerMarginAccount = await client.getMarginAccount(connection, buyerMarginAccount.publicKey, dexProgramId);
|
buyerMarginAccount = await client.getMarginAccount(connection, buyerMarginAccount.publicKey, dexProgramId);
|
||||||
|
@ -209,7 +203,7 @@ async function stressTestMatchOrder(mangoGroupSpotMarket: any, orderQuantity: nu
|
||||||
const sellerMarginAccountPk = await client.initMarginAccount(connection, mangoProgramId, mangoGroup, sellerOwner);
|
const sellerMarginAccountPk = await client.initMarginAccount(connection, mangoProgramId, mangoGroup, sellerOwner);
|
||||||
let sellerMarginAccount = await client.getMarginAccount(connection, sellerMarginAccountPk, dexProgramId);
|
let sellerMarginAccount = await client.getMarginAccount(connection, sellerMarginAccountPk, dexProgramId);
|
||||||
|
|
||||||
const { spotMarket, baseSymbol, quoteSymbol } = await getSpotMarketDetails(mangoGroupSpotMarket);
|
const { spotMarket, baseSymbol, quoteSymbol } = await getSpotMarketDetails(connection, mangoGroupSpotMarket, dexProgramId);
|
||||||
|
|
||||||
const [orderSize, orderPrice, _] = await getOrderSizeAndPrice(connection, spotMarket, mangoGroupTokenMappings, baseSymbol, quoteSymbol, 'sell');
|
const [orderSize, orderPrice, _] = await getOrderSizeAndPrice(connection, spotMarket, mangoGroupTokenMappings, baseSymbol, quoteSymbol, 'sell');
|
||||||
const neededQuoteAmount = orderPrice * orderSize;
|
const neededQuoteAmount = orderPrice * orderSize;
|
||||||
|
@ -248,49 +242,82 @@ async function stressTestMatchOrder(mangoGroupSpotMarket: any, orderQuantity: nu
|
||||||
expect(allBids).to.be.empty;
|
expect(allBids).to.be.empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function stressTestLiquidation(mangoGroupSpotMarket: any, orderQuantity: number, shouldPartialLiquidate: boolean = false) {
|
async function stressTestLiquidation(params: {
|
||||||
|
mangoGroupSpotMarket: any,
|
||||||
|
orderQuantity?: number,
|
||||||
|
customLiqeeOwner?: Account,
|
||||||
|
shouldPartialLiquidate?: boolean,
|
||||||
|
shouldCreateNewLiqor?: boolean,
|
||||||
|
shouldFinishLiquidationInTest?: boolean,
|
||||||
|
customOrderPrice?: number,
|
||||||
|
customOrderSize?: number,
|
||||||
|
leverageCoefficient?: number,
|
||||||
|
matchLeveragedOrder?: boolean,
|
||||||
|
side?: 'buy' | 'sell'
|
||||||
|
}) {
|
||||||
|
const {
|
||||||
|
mangoGroupSpotMarket,
|
||||||
|
orderQuantity = 1,
|
||||||
|
customLiqeeOwner = null,
|
||||||
|
shouldPartialLiquidate = false,
|
||||||
|
shouldCreateNewLiqor = true,
|
||||||
|
shouldFinishLiquidationInTest = true,
|
||||||
|
customOrderPrice = 0,
|
||||||
|
customOrderSize = 0,
|
||||||
|
leverageCoefficient = 15,
|
||||||
|
matchLeveragedOrder = false,
|
||||||
|
side = 'buy'
|
||||||
|
} = params;
|
||||||
|
console.info("orderQuantity:", orderQuantity)
|
||||||
let bna: any, allAsks: any[], allBids: any[], prices: number[];
|
let bna: any, allAsks: any[], allBids: any[], prices: number[];
|
||||||
let leverageCoefficient = 15;
|
const liqeeOwner = customLiqeeOwner || await createWalletAndRequestAirdrop(connection, 5);
|
||||||
const liqeeOwner = await createWalletAndRequestAirdrop(connection, 5);
|
|
||||||
prettyPrintOwnerKeys(liqeeOwner, "Liqee");
|
prettyPrintOwnerKeys(liqeeOwner, "Liqee");
|
||||||
const mangoGroup = await client.getMangoGroup(connection, mangoGroupPk);
|
const mangoGroup = await client.getMangoGroup(connection, mangoGroupPk);
|
||||||
const mangoGroupTokenMappings = await createMangoGroupSymbolMappings(connection, mangoGroupIds);
|
const mangoGroupTokenMappings = await createMangoGroupSymbolMappings(connection, mangoGroupIds);
|
||||||
const liqeeMarginAccountPk = await client.initMarginAccount(connection, mangoProgramId, mangoGroup, liqeeOwner);
|
const liqeeMarginAccountPk = (customLiqeeOwner) ? (await client.getMarginAccountsForOwner(connection, mangoProgramId, mangoGroup, liqeeOwner))[0].publicKey : await client.initMarginAccount(connection, mangoProgramId, mangoGroup, liqeeOwner);
|
||||||
let liqeeMarginAccount = await client.getMarginAccount(connection, liqeeMarginAccountPk, dexProgramId);
|
let liqeeMarginAccount = await client.getMarginAccount(connection, liqeeMarginAccountPk, dexProgramId);
|
||||||
|
const { spotMarket, baseSymbol, quoteSymbol } = await getSpotMarketDetails(connection, mangoGroupSpotMarket, dexProgramId);
|
||||||
const { spotMarket, baseSymbol, quoteSymbol } = await getSpotMarketDetails(mangoGroupSpotMarket);
|
|
||||||
const baseSymbolIndex = mangoGroupSymbols.findIndex(x => x === baseSymbol);
|
const baseSymbolIndex = mangoGroupSymbols.findIndex(x => x === baseSymbol);
|
||||||
const quoteSymbolIndex = mangoGroupSymbols.findIndex(x => x === quoteSymbol);
|
const quoteSymbolIndex = mangoGroupSymbols.findIndex(x => x === quoteSymbol);
|
||||||
|
|
||||||
const [orderSize, orderPrice, _] = await getOrderSizeAndPrice(connection, spotMarket, mangoGroupTokenMappings, baseSymbol, quoteSymbol, 'buy');
|
const [orderSize, orderPrice, _] = await getOrderSizeAndPrice(connection, spotMarket, mangoGroupTokenMappings, baseSymbol, quoteSymbol, side);
|
||||||
const neededQuoteAmount = orderPrice * orderSize;
|
const finalOrderPrice = customOrderPrice || orderPrice;
|
||||||
const neededBaseAmountForAllTrades = orderSize * orderQuantity;
|
const finalOrderSize = customOrderSize || orderSize;
|
||||||
|
const neededQuoteAmount = finalOrderPrice * finalOrderSize;
|
||||||
|
const neededBaseAmountForAllTrades = finalOrderSize * orderQuantity;
|
||||||
const neededQuoteAmountForAllTrades = neededQuoteAmount * orderQuantity;
|
const neededQuoteAmountForAllTrades = neededQuoteAmount * orderQuantity;
|
||||||
console.info("neededBaseAmountForAllTrades:", neededBaseAmountForAllTrades);
|
const neededCollateralTokenSymbol = (side === 'buy') ? baseSymbol : quoteSymbol;
|
||||||
|
const neededAmountForAllTrades = (side === 'buy') ? neededBaseAmountForAllTrades : neededQuoteAmountForAllTrades;
|
||||||
await createTokenAccountWithBalance(connection, liqeeOwner, baseSymbol, mangoGroupTokenMappings, clusterIds.faucets, neededBaseAmountForAllTrades);
|
await createTokenAccountWithBalance(connection, liqeeOwner, neededCollateralTokenSymbol, mangoGroupTokenMappings, clusterIds.faucets, neededAmountForAllTrades);
|
||||||
await performSingleDepositOrWithdrawal(connection, liqeeOwner, client, mangoGroup, mangoProgramId, baseSymbol, mangoGroupTokenMappings, liqeeMarginAccount, 'deposit', neededBaseAmountForAllTrades);
|
await performSingleDepositOrWithdrawal(connection, liqeeOwner, client, mangoGroup, mangoProgramId, neededCollateralTokenSymbol, mangoGroupTokenMappings, liqeeMarginAccount, 'deposit', neededAmountForAllTrades);
|
||||||
|
prices = await requestPriceChange(mangoGroup, finalOrderPrice, baseSymbol);
|
||||||
prices = await requestPriceChange(mangoGroup, orderPrice, baseSymbol);
|
|
||||||
|
|
||||||
for (let i = 0; i < orderQuantity; i++) {
|
for (let i = 0; i < orderQuantity; i++) {
|
||||||
console.info(`Placing a buy order of ${orderSize} ${baseSymbol} for ${orderPrice} ${quoteSymbol} = ~${neededQuoteAmount} ${quoteSymbol} - ${i + 1}/${orderQuantity}`);
|
console.info(`Placing a ${side} order of ${finalOrderSize} ${baseSymbol} for ${finalOrderPrice} ${quoteSymbol} = ~${neededQuoteAmount} ${quoteSymbol} - ${i + 1}/${orderQuantity}`);
|
||||||
liqeeMarginAccount = await client.getMarginAccount(connection, liqeeMarginAccountPk, dexProgramId);
|
liqeeMarginAccount = await client.getMarginAccount(connection, liqeeMarginAccountPk, dexProgramId);
|
||||||
await client.placeAndSettle(connection, mangoProgramId, mangoGroup, liqeeMarginAccount, spotMarket, liqeeOwner, 'buy', orderPrice * 2, orderSize);
|
console.info("Deposits:", liqeeMarginAccount.getAssets(mangoGroup));
|
||||||
|
console.info("Assets:", liqeeMarginAccount.getAssets(mangoGroup));
|
||||||
|
await client.placeAndSettle(connection, mangoProgramId, mangoGroup, liqeeMarginAccount, spotMarket, liqeeOwner, side, finalOrderPrice, finalOrderSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
liqeeMarginAccount = await client.getMarginAccount(connection, liqeeMarginAccountPk, dexProgramId);
|
if (matchLeveragedOrder) cleanOrderBook(mangoGroupSpotMarket);
|
||||||
|
|
||||||
|
liqeeMarginAccount = await client.getMarginAccount(connection, liqeeMarginAccountPk, dexProgramId);
|
||||||
console.info("collRatio before price change:", liqeeMarginAccount.getCollateralRatio(mangoGroup, prices));
|
console.info("collRatio before price change:", liqeeMarginAccount.getCollateralRatio(mangoGroup, prices));
|
||||||
prices = await requestPriceChange(mangoGroup, orderPrice / leverageCoefficient, baseSymbol);
|
const adjustedPrice = (side === 'buy') ? finalOrderPrice / leverageCoefficient : finalOrderPrice * leverageCoefficient;
|
||||||
|
prices = await requestPriceChange(mangoGroup, adjustedPrice, baseSymbol);
|
||||||
console.info("collRatio after price change:", liqeeMarginAccount.getCollateralRatio(mangoGroup, prices));
|
console.info("collRatio after price change:", liqeeMarginAccount.getCollateralRatio(mangoGroup, prices));
|
||||||
|
|
||||||
const liqorOwner = await createWalletAndRequestAirdrop(connection, 5);
|
let liqorOwner = new Account();
|
||||||
prettyPrintOwnerKeys(liqeeOwner, "Liqor");
|
|
||||||
|
if (shouldCreateNewLiqor) {
|
||||||
|
liqorOwner = await createWalletAndRequestAirdrop(connection, 5);
|
||||||
|
prettyPrintOwnerKeys(liqorOwner, "Liqor");
|
||||||
for (let mangoGroupSymbol of mangoGroupSymbols) {
|
for (let mangoGroupSymbol of mangoGroupSymbols) {
|
||||||
const requiredBalance = (mangoGroupSymbol === quoteSymbol) ? neededQuoteAmountForAllTrades : 0;
|
const requiredBalance = (mangoGroupSymbol === quoteSymbol) ? neededQuoteAmountForAllTrades : 0;
|
||||||
await createTokenAccountWithBalance(connection, liqorOwner, mangoGroupSymbol, mangoGroupTokenMappings, clusterIds.faucets, requiredBalance);
|
await createTokenAccountWithBalance(connection, liqorOwner, mangoGroupSymbol, mangoGroupTokenMappings, clusterIds.faucets, requiredBalance);
|
||||||
}
|
}
|
||||||
|
if (shouldFinishLiquidationInTest) {
|
||||||
const tokenWallets = (await Promise.all(
|
const tokenWallets = (await Promise.all(
|
||||||
mangoGroup.tokens.map(
|
mangoGroup.tokens.map(
|
||||||
(mint) => findLargestTokenAccountForOwner(connection, liqorOwner.publicKey, mint).then(
|
(mint) => findLargestTokenAccountForOwner(connection, liqorOwner.publicKey, mint).then(
|
||||||
|
@ -311,6 +338,10 @@ async function stressTestLiquidation(mangoGroupSpotMarket: any, orderQuantity: n
|
||||||
const liquidationConfirmedTx: any = await connection.getConfirmedTransaction(liquidationTxHash);
|
const liquidationConfirmedTx: any = await connection.getConfirmedTransaction(liquidationTxHash);
|
||||||
const liquidationTxLogInfo = extractInfoFromLogs(liquidationConfirmedTx);
|
const liquidationTxLogInfo = extractInfoFromLogs(liquidationConfirmedTx);
|
||||||
console.info("Liquidation txLogInfo:", liquidationTxLogInfo);
|
console.info("Liquidation txLogInfo:", liquidationTxLogInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {liqeeOwner, liqorOwner};
|
||||||
}
|
}
|
||||||
|
|
||||||
const mangoGroupName = 'BTC_ETH_SOL_SRM_USDC';
|
const mangoGroupName = 'BTC_ETH_SOL_SRM_USDC';
|
||||||
|
@ -319,115 +350,57 @@ const mangoGroupIds = clusterIds.mango_groups[mangoGroupName];
|
||||||
const mangoGroupSpotMarkets: [string, string][] = Object.entries(mangoGroupIds.spot_market_symbols);
|
const mangoGroupSpotMarkets: [string, string][] = Object.entries(mangoGroupIds.spot_market_symbols);
|
||||||
const mangoGroupPk = new PublicKey(mangoGroupIds.mango_group_pk);
|
const mangoGroupPk = new PublicKey(mangoGroupIds.mango_group_pk);
|
||||||
|
|
||||||
const mangoGroupSpotMarket = mangoGroupSpotMarkets[0]; //BTC/USDC
|
// const mangoGroupSpotMarket = mangoGroupSpotMarkets[0]; //BTC/USDC
|
||||||
// const mangoGroupSpotMarket = mangoGroupSpotMarkets[1]; //ETH/USDC
|
// const mangoGroupSpotMarket = mangoGroupSpotMarkets[1]; //ETH/USDC
|
||||||
// const mangoGroupSpotMarket = mangoGroupSpotMarkets[2]; //SOL/USDC
|
const mangoGroupSpotMarket = mangoGroupSpotMarkets[2]; //SOL/USDC
|
||||||
// const mangoGroupSpotMarket = mangoGroupSpotMarkets[3]; //SRM/USDC
|
// const mangoGroupSpotMarket = mangoGroupSpotMarkets[3]; //SRM/USDC
|
||||||
|
|
||||||
describe('Log stuff', async() => {
|
describe('stress test order limits', async() => {
|
||||||
// it('should log token decimals', async() => {
|
before(async () => {
|
||||||
// const MINT_LAYOUT = struct([blob(44), u8('decimals'), blob(37)]);
|
await cleanOrderBook(mangoGroupSpotMarket);
|
||||||
// const mainnetTokensToTest = [
|
});
|
||||||
// ['BTC', '9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E'],
|
it('should be able to place 129th order after cancelling one', async() => {
|
||||||
// ['ETH', '2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk'],
|
await placeNOrdersAfterLimit(mangoGroupSpotMarket, 0, 1);
|
||||||
// ['SOL', 'So11111111111111111111111111111111111111112'],
|
|
||||||
// ['SRM', 'SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt'],
|
|
||||||
// ['USDT', 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB'],
|
|
||||||
// ['USDC', 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'],
|
|
||||||
// ];
|
|
||||||
// const devnetTokensToTest = [
|
|
||||||
// ['BTC', 'bypQzRBaSDWiKhoAw3hNkf35eF3z3AZCU8Sxks6mTPP'],
|
|
||||||
// ['ETH', 'ErWGBLBQMwdyC4H3MR8ef6pFK6gyHAyBxy4o1mHoqKzm'],
|
|
||||||
// ['SOL', 'So11111111111111111111111111111111111111112'],
|
|
||||||
// ['SRM', '9FbAMDvXqNjPqZSYt4EWTguJuDrGkfvwr3gSFpiSbX9S'],
|
|
||||||
// ['USDT', '7KBVenLz5WNH4PA5MdGkJNpDDyNKnBQTwnz1UqJv9GUm'],
|
|
||||||
// ['USDC', 'H6hy7Ykzc43EuGivv7VVuUKNpKgUoFAfUY3wdPr4UyRX'],
|
|
||||||
// ];
|
|
||||||
// for (let [tokenName, tokenMint] of mainnetTokensToTest) {
|
|
||||||
// const data: any = await mainnetConnection.getAccountInfo(new PublicKey(tokenMint));
|
|
||||||
// const info = MINT_LAYOUT.decode(data.data);
|
|
||||||
// console.info(`Mainnet ${tokenName} decimals: ${info.decimals}`);
|
|
||||||
// }
|
|
||||||
// for (let [tokenName, tokenMint] of devnetTokensToTest) {
|
|
||||||
// const data: any = await connection.getAccountInfo(new PublicKey(tokenMint));
|
|
||||||
// const info = MINT_LAYOUT.decode(data.data);
|
|
||||||
// console.info(`Devnet ${tokenName} decimals: ${info.decimals}`);
|
|
||||||
// };
|
|
||||||
// });
|
|
||||||
// it('should log lotSizes', async() => {
|
|
||||||
// const mainnetSpotMarketsToTest = [
|
|
||||||
// ['BTC/USDC', 'A8YFbxQYFVqKZaoYJLLUVcQiWP7G2MeEgW5wsAQgMvFw'],
|
|
||||||
// ['ETH/USDC', '4tSvZvnbyzHXLMTiFonMyxZoHmFqau1XArcRCVHLZ5gX'],
|
|
||||||
// ['SOL/USDC', '9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT'],
|
|
||||||
// ['SRM/USDC', 'ByRys5tuUWDgL73G8JBAEfkdFf8JWBzPBDHsBVQ5vbQA'],
|
|
||||||
// ];
|
|
||||||
// for (let [spotMarketName, spotMarketAddress] of mainnetSpotMarketsToTest) {
|
|
||||||
// const spotMarket = await Market.load(mainnetConnection, new PublicKey(spotMarketAddress), { skipPreflight: true, commitment: 'singleGossip'}, mainnetDexProgramId);
|
|
||||||
// console.info(`Mainnet ${spotMarketName} base/quote lotSizes: ${spotMarket['_decoded'].baseLotSize.toString()}/${spotMarket['_decoded'].quoteLotSize.toString()}`);
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// it ('should log order', async() => {
|
|
||||||
// const { spotMarket } = await getSpotMarketDetails(mangoGroupSpotMarket);
|
|
||||||
// let bna = await getAndDecodeBidsAndAsks(connection, spotMarket);
|
|
||||||
// let allAsks: any[] = [...bna.askOrderBook].map(x => ({ price: x.price, size: x.size })).reverse();
|
|
||||||
// let allBids: any[] = [...bna.bidOrderBook].map(x => ({ price: x.price, size: x.size })).reverse();
|
|
||||||
// console.info(allAsks);
|
|
||||||
// console.info(allBids);
|
|
||||||
// })
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('create account with test money', async() => {
|
|
||||||
it('should create an account with test money', async() => {
|
|
||||||
await initAccountsWithBalances([50, 50, 50, 1000, 100000]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('stress test order limits', async() => {
|
|
||||||
// before(async () => {
|
|
||||||
// await cleanOrderBook(mangoGroupSpotMarket);
|
|
||||||
// });
|
|
||||||
// it('should be able to place 129th order after cancelling one', async() => {
|
|
||||||
// await placeNOrdersAfterLimit(mangoGroupSpotMarket, 0, 1);
|
|
||||||
// });
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('stress testing matching orders', async() => {
|
describe('stress testing matching orders', async() => {
|
||||||
// before(async () => {
|
before(async () => {
|
||||||
// await cleanOrderBook(mangoGroupSpotMarket);
|
await cleanOrderBook(mangoGroupSpotMarket);
|
||||||
// });
|
});
|
||||||
// it('should match 1 order at a single price', async() => {
|
it('should match 1 order at a single price', async() => {
|
||||||
// await stressTestMatchOrder(mangoGroupSpotMarket, 1);
|
await stressTestMatchOrder(mangoGroupSpotMarket, 1);
|
||||||
// });
|
});
|
||||||
// it('should match 10 orders at a single price', async() => {
|
it('should match 10 orders at a single price', async() => {
|
||||||
// await stressTestMatchOrder(mangoGroupSpotMarket, 10);
|
await stressTestMatchOrder(mangoGroupSpotMarket, 10);
|
||||||
// });
|
});
|
||||||
// it('should match 20 orders at a single price', async() => {
|
it('should match 20 orders at a single price', async() => {
|
||||||
// await stressTestMatchOrder(mangoGroupSpotMarket, 20);
|
await stressTestMatchOrder(mangoGroupSpotMarket, 20);
|
||||||
// });
|
});
|
||||||
// it('should match 25 orders at a single price', async() => {
|
it('should match 25 orders at a single price', async() => {
|
||||||
// await stressTestMatchOrder(mangoGroupSpotMarket, 25);
|
await stressTestMatchOrder(mangoGroupSpotMarket, 25);
|
||||||
// });
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('stress testing liquidation', async() => {
|
describe('stress testing liquidation', async() => {
|
||||||
// before(async () => {
|
before(async () => {
|
||||||
// await cleanOrderBook(mangoGroupSpotMarket);
|
await cleanOrderBook(mangoGroupSpotMarket);
|
||||||
// });
|
});
|
||||||
// it('should liquidate an account with 1 open order', async() => {
|
it('should liquidate an account with 1 open order', async() => {
|
||||||
// await stressTestLiquidation(mangoGroupSpotMarket, 1);
|
await stressTestLiquidation({mangoGroupSpotMarket, orderQuantity: 1});
|
||||||
// });
|
});
|
||||||
// it('should liquidate an account with 10 open orders', async() => {
|
it('should liquidate an account with 10 open orders', async() => {
|
||||||
// await stressTestLiquidation(mangoGroupSpotMarket, 10);
|
await stressTestLiquidation({mangoGroupSpotMarket, orderQuantity: 10});
|
||||||
// });
|
});
|
||||||
// it('should liquidate an account with 20 open orders', async() => {
|
it('should liquidate an account with 20 open orders', async() => {
|
||||||
// await stressTestLiquidation(mangoGroupSpotMarket, 20);
|
await stressTestLiquidation({mangoGroupSpotMarket, orderQuantity: 20});
|
||||||
// });
|
});
|
||||||
// it('should liquidate an account with 25 open orders', async() => {
|
it('should liquidate an account with 25 open orders', async() => {
|
||||||
// await stressTestLiquidation(mangoGroupSpotMarket, 25);
|
await stressTestLiquidation({mangoGroupSpotMarket, orderQuantity: 25});
|
||||||
// });
|
});
|
||||||
// it('should liquidate an account with 128 open orders', async() => {
|
it('should liquidate an account with 128 open orders', async() => {
|
||||||
// await stressTestLiquidation(mangoGroupSpotMarket, 128);
|
await stressTestLiquidation({mangoGroupSpotMarket, orderQuantity: 128});
|
||||||
// });
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('stress testing partial liquidation', async() => {
|
describe('stress testing partial liquidation', async() => {
|
||||||
|
@ -435,18 +408,161 @@ describe('stress testing partial liquidation', async() => {
|
||||||
// await cleanOrderBook(mangoGroupSpotMarket);
|
// await cleanOrderBook(mangoGroupSpotMarket);
|
||||||
// });
|
// });
|
||||||
it('should partially liquidate an account with 1 open order', async() => {
|
it('should partially liquidate an account with 1 open order', async() => {
|
||||||
await stressTestLiquidation(mangoGroupSpotMarket, 1, true);
|
await stressTestLiquidation({mangoGroupSpotMarket, orderQuantity: 1, shouldPartialLiquidate: true});
|
||||||
});
|
});
|
||||||
// it('should partially liquidate an account with 10 open orders', async() => {
|
it('should partially liquidate an account with 10 open orders', async() => {
|
||||||
// await stressTestLiquidation(mangoGroupSpotMarket, 10, true);
|
await stressTestLiquidation({mangoGroupSpotMarket, orderQuantity: 10, shouldPartialLiquidate: true});
|
||||||
// });
|
});
|
||||||
// it('should partially liquidate an account with 20 open orders', async() => {
|
it('should partially liquidate an account with 20 open orders', async() => {
|
||||||
// await stressTestLiquidation(mangoGroupSpotMarket, 20, true);
|
await stressTestLiquidation({mangoGroupSpotMarket, orderQuantity: 20, shouldPartialLiquidate: true});
|
||||||
// });
|
});
|
||||||
// it('should partially liquidate an account with 25 open orders', async() => {
|
it('should partially liquidate an account with 25 open orders', async() => {
|
||||||
// await stressTestLiquidation(mangoGroupSpotMarket, 25, true);
|
await stressTestLiquidation({mangoGroupSpotMarket, orderQuantity: 25, shouldPartialLiquidate: true});
|
||||||
// });
|
});
|
||||||
// it('should partially liquidate an account with 128 open orders', async() => {
|
it('should partially liquidate an account with 128 open orders', async() => {
|
||||||
// await stressTestLiquidation(mangoGroupSpotMarket, 128, true);
|
await stressTestLiquidation({mangoGroupSpotMarket, orderQuantity: 128, shouldPartialLiquidate: true});
|
||||||
// });
|
});
|
||||||
|
it ('should test socialized loss with 4 borrows', async() => {
|
||||||
|
const mangoGroupSpotMarketETH = mangoGroupSpotMarkets[1]; //ETH/USDC
|
||||||
|
const { liqeeOwner } = await stressTestLiquidation({ mangoGroupSpotMarket: mangoGroupSpotMarketETH, shouldPartialLiquidate: true, shouldFinishLiquidationInTest: false, customOrderPrice: 10, customOrderSize: 1, shouldCreateNewLiqor: false });
|
||||||
|
const mangoGroupSpotMarketSOL = mangoGroupSpotMarkets[2]; //SOL/USDC
|
||||||
|
await stressTestLiquidation({ mangoGroupSpotMarket: mangoGroupSpotMarketSOL, customLiqeeOwner: liqeeOwner, shouldPartialLiquidate: true, shouldFinishLiquidationInTest: false, customOrderPrice: 10, customOrderSize: 1, shouldCreateNewLiqor: false });
|
||||||
|
const mangoGroupSpotMarketSRM = mangoGroupSpotMarkets[3]; //SRM/USDC
|
||||||
|
await stressTestLiquidation({ mangoGroupSpotMarket: mangoGroupSpotMarketSRM, customLiqeeOwner: liqeeOwner, shouldPartialLiquidate: true, shouldFinishLiquidationInTest: false, customOrderPrice: 10, customOrderSize: 1, shouldCreateNewLiqor: false });
|
||||||
|
const mangoGroupSpotMarketBTC = mangoGroupSpotMarkets[0]; //BTC/USDC
|
||||||
|
await stressTestLiquidation({ mangoGroupSpotMarket: mangoGroupSpotMarketBTC, customLiqeeOwner: liqeeOwner, shouldPartialLiquidate: true, shouldFinishLiquidationInTest: true, customOrderPrice: 10, customOrderSize: 1, matchLeveragedOrder: true, shouldCreateNewLiqor: false });
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('create various liquidation opportunities', async() => {
|
||||||
|
it('should create a liquidation opportunity', async() => {
|
||||||
|
const {liqeeOwner, liqorOwner} = await stressTestLiquidation({ mangoGroupSpotMarket: mangoGroupSpotMarket, shouldPartialLiquidate: true, shouldFinishLiquidationInTest: false, customOrderPrice: 20, customOrderSize: 1, side: 'sell' });
|
||||||
|
// const mangoGroupTokenMappings = await createMangoGroupSymbolMappings(connection, mangoGroupIds);
|
||||||
|
// const quoteCurrencyAccountPk = await createTokenAccountWithBalance(connection, liqorOwner, 'USDC', mangoGroupTokenMappings, clusterIds.faucets, 0);
|
||||||
|
// const baseCurrencyAmounts: number[] = [1, 2, 10, 100];
|
||||||
|
// await Promise.all(mangoGroupSpotMarkets.map(async (mangoGroupSpotMarket: any, index: number) => {
|
||||||
|
// const { spotMarket, baseSymbol, minSize, minPrice } = await getSpotMarketDetails(connection, mangoGroupSpotMarket, dexProgramId);
|
||||||
|
// console.info(baseSymbol);
|
||||||
|
// const baseCurrencyAccountPk = await createTokenAccountWithBalance(connection, liqorOwner, baseSymbol, mangoGroupTokenMappings, clusterIds.faucets, baseCurrencyAmounts[index]);
|
||||||
|
// const side = 'sell';
|
||||||
|
// if (!baseCurrencyAccountPk || !quoteCurrencyAccountPk) throw Error('Missing the necessary token accounts');
|
||||||
|
// await placeOrderUsingSerumDex(connection, liqorOwner, spotMarket, baseCurrencyAccountPk, quoteCurrencyAccountPk, { side, size: minSize, price: minPrice });
|
||||||
|
// // const openOrdersAccounts = await spotMarket.findOpenOrdersAccountsForOwner(connection, owner.publicKey);
|
||||||
|
// // const ordersForOwner = await getAndDecodeBidsAndAsksForOwner(connection, spotMarket, openOrdersAccounts[0]);
|
||||||
|
// // await cancelOrdersUsingSerumDex(connection, owner, spotMarket, ordersForOwner);
|
||||||
|
// const allOpenOrdersAccounts = await OpenOrders.findForOwner(connection, liqorOwner.publicKey, dexProgramId)
|
||||||
|
// console.info("All oo accs:", allOpenOrdersAccounts.length);
|
||||||
|
// }));
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('create accounts for testing', async() => {
|
||||||
|
it('should create an account with test money', async() => {
|
||||||
|
const buyerOwner = await initAccountsWithBalances([100, 100, 500, 1000, 100000], false);
|
||||||
|
});
|
||||||
|
it('should fund mango group', async() => {
|
||||||
|
const buyerOwner = await initAccountsWithBalances([100, 100, 500, 1000, 100000], false);
|
||||||
|
const amounts = [100, 100, 500, 1000, 100000];
|
||||||
|
const symbols = ['BTC', 'ETH', 'SOL', 'SRM', 'USDC'];
|
||||||
|
const mangoGroup = await client.getMangoGroup(connection, mangoGroupPk);
|
||||||
|
const mangoGroupTokenMappings = await createMangoGroupSymbolMappings(connection, mangoGroupIds);
|
||||||
|
const buyerMarginAccountPk = await client.initMarginAccount(connection, mangoProgramId, mangoGroup, buyerOwner);
|
||||||
|
const buyerMarginAccount = await client.getMarginAccount(connection, buyerMarginAccountPk, dexProgramId);
|
||||||
|
for (let i = 0; i < symbols.length; i++) {
|
||||||
|
await createTokenAccountWithBalance(connection, buyerOwner, symbols[i], mangoGroupTokenMappings, clusterIds.faucets, amounts[i]);
|
||||||
|
await performSingleDepositOrWithdrawal(connection, buyerOwner, client, mangoGroup, mangoProgramId, symbols[i], mangoGroupTokenMappings, buyerMarginAccount, 'deposit', amounts[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('should create an account with initialised openOrders', async() => {
|
||||||
|
const owner: Account = await createWalletAndRequestAirdrop(connection, 5);
|
||||||
|
const mangoGroupTokenMappings = await createMangoGroupSymbolMappings(connection, mangoGroupIds);
|
||||||
|
const quoteCurrencyAccountPk = await createTokenAccountWithBalance(connection, owner, 'USDC', mangoGroupTokenMappings, clusterIds.faucets, 0);
|
||||||
|
await Promise.all(mangoGroupSpotMarkets.map(async (mangoGroupSpotMarket: any) => {
|
||||||
|
const { spotMarket, baseSymbol, minSize, minPrice } = await getSpotMarketDetails(connection, mangoGroupSpotMarket, dexProgramId);
|
||||||
|
const baseCurrencyAccountPk = await createTokenAccountWithBalance(connection, owner, baseSymbol, mangoGroupTokenMappings, clusterIds.faucets, minSize);
|
||||||
|
const side = 'sell';
|
||||||
|
if (!baseCurrencyAccountPk || !quoteCurrencyAccountPk) throw Error('Missing the necessary token accounts');
|
||||||
|
await placeOrderUsingSerumDex(connection, owner, spotMarket, baseCurrencyAccountPk, quoteCurrencyAccountPk, { side, size: minSize, price: minPrice });
|
||||||
|
// const openOrdersAccounts = await spotMarket.findOpenOrdersAccountsForOwner(connection, owner.publicKey);
|
||||||
|
// const ordersForOwner = await getAndDecodeBidsAndAsksForOwner(connection, spotMarket, openOrdersAccounts[0]);
|
||||||
|
// await cancelOrdersUsingSerumDex(connection, owner, spotMarket, ordersForOwner);
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('log stuff', async() => {
|
||||||
|
// NOTE: This part of tests is used to test and log random things
|
||||||
|
it('should log token decimals', async() => {
|
||||||
|
const MINT_LAYOUT = struct([blob(44), u8('decimals'), blob(37)]);
|
||||||
|
const mainnetTokensToTest = [
|
||||||
|
['BTC', '9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E'],
|
||||||
|
['ETH', '2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk'],
|
||||||
|
['SOL', 'So11111111111111111111111111111111111111112'],
|
||||||
|
['SRM', 'SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt'],
|
||||||
|
['USDT', 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB'],
|
||||||
|
['USDC', 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'],
|
||||||
|
];
|
||||||
|
const devnetTokensToTest = [
|
||||||
|
['BTC', 'bypQzRBaSDWiKhoAw3hNkf35eF3z3AZCU8Sxks6mTPP'],
|
||||||
|
['ETH', 'ErWGBLBQMwdyC4H3MR8ef6pFK6gyHAyBxy4o1mHoqKzm'],
|
||||||
|
['SOL', 'So11111111111111111111111111111111111111112'],
|
||||||
|
['SRM', '9FbAMDvXqNjPqZSYt4EWTguJuDrGkfvwr3gSFpiSbX9S'],
|
||||||
|
['USDT', '7KBVenLz5WNH4PA5MdGkJNpDDyNKnBQTwnz1UqJv9GUm'],
|
||||||
|
['USDC', 'H6hy7Ykzc43EuGivv7VVuUKNpKgUoFAfUY3wdPr4UyRX'],
|
||||||
|
];
|
||||||
|
for (let [tokenName, tokenMint] of mainnetTokensToTest) {
|
||||||
|
const data: any = await mainnetConnection.getAccountInfo(new PublicKey(tokenMint));
|
||||||
|
const info = MINT_LAYOUT.decode(data.data);
|
||||||
|
console.info(`Mainnet ${tokenName} decimals: ${info.decimals}`);
|
||||||
|
}
|
||||||
|
for (let [tokenName, tokenMint] of devnetTokensToTest) {
|
||||||
|
const data: any = await connection.getAccountInfo(new PublicKey(tokenMint));
|
||||||
|
const info = MINT_LAYOUT.decode(data.data);
|
||||||
|
console.info(`Devnet ${tokenName} decimals: ${info.decimals}`);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
it('should log lotSizes', async() => {
|
||||||
|
const mainnetSpotMarketsToTest = [
|
||||||
|
['BTC/USDC', 'A8YFbxQYFVqKZaoYJLLUVcQiWP7G2MeEgW5wsAQgMvFw'],
|
||||||
|
['BTC/USDT', 'C1EuT9VokAKLiW7i2ASnZUvxDoKuKkCpDDeNxAptuNe4'],
|
||||||
|
['ETH/USDC', '4tSvZvnbyzHXLMTiFonMyxZoHmFqau1XArcRCVHLZ5gX'],
|
||||||
|
['SOL/USDC', '9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT'],
|
||||||
|
['SRM/USDC', 'ByRys5tuUWDgL73G8JBAEfkdFf8JWBzPBDHsBVQ5vbQA'],
|
||||||
|
];
|
||||||
|
const devnetSpotMarketsToTest = [
|
||||||
|
['BTC/USDC', 'BCqDfFd119UyNEC2HavKdy3F4qhy6EMGirSurNWKgioW'],
|
||||||
|
['ETH/USDC', 'AfB75DQs1E2VoUAMRorUxAz68b18kWZ1uqQuRibGk212'],
|
||||||
|
['SOL/USDC', '6vZd6Ghwkuzpbp7qNzBuRkhcfA9H3S7BJ2LCWSYrjfzo'],
|
||||||
|
['SRM/USDC', '6rRnXBLGzcD5v1q4NfWWZQdgBfqzEuD3g4GqDWVU8yhH'],
|
||||||
|
];
|
||||||
|
for (let [spotMarketName, spotMarketAddress] of mainnetSpotMarketsToTest) {
|
||||||
|
const spotMarket = await Market.load(mainnetConnection, new PublicKey(spotMarketAddress), { skipPreflight: true, commitment: 'singleGossip'}, mainnetDexProgramId);
|
||||||
|
console.info(`Mainnet ${spotMarketName} base/quote lotSizes: ${spotMarket['_decoded'].baseLotSize.toString()}/${spotMarket['_decoded'].quoteLotSize.toString()}`);
|
||||||
|
console.info(`Mainnet ${spotMarketName} baseSizeNumberToLots: ${spotMarket.baseSizeNumberToLots(1)}`);
|
||||||
|
console.info(`Mainnet ${spotMarketName} priceNumberToLots: ${spotMarket.priceNumberToLots(1)}`);
|
||||||
|
}
|
||||||
|
for (let [spotMarketName, spotMarketAddress] of devnetSpotMarketsToTest) {
|
||||||
|
const spotMarket = await Market.load(connection, new PublicKey(spotMarketAddress), { skipPreflight: true, commitment: 'singleGossip'}, dexProgramId);
|
||||||
|
console.info(`Devnet ${spotMarketName} base/quote lotSizes: ${spotMarket['_decoded'].baseLotSize.toString()}/${spotMarket['_decoded'].quoteLotSize.toString()}`);
|
||||||
|
console.info(`Devnet ${spotMarketName} baseSizeNumberToLots: ${spotMarket.baseSizeNumberToLots(1)}`);
|
||||||
|
console.info(`Devnet ${spotMarketName} priceNumberToLots: ${spotMarket.priceNumberToLots(1)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it ('should log orderbook for spotmarket', async() => {
|
||||||
|
const { spotMarket } = await getSpotMarketDetails(connection, mangoGroupSpotMarket, dexProgramId);
|
||||||
|
let bna = await getAndDecodeBidsAndAsks(connection, spotMarket);
|
||||||
|
let allAsks: any[] = [...bna.askOrderBook].map(x => ({ price: x.price, size: x.size })).reverse();
|
||||||
|
let allBids: any[] = [...bna.bidOrderBook].map(x => ({ price: x.price, size: x.size })).reverse();
|
||||||
|
console.info("=== allAsks ===");
|
||||||
|
console.info(allAsks);
|
||||||
|
console.info("=== allBids ===");
|
||||||
|
console.info(allBids);
|
||||||
|
});
|
||||||
|
it ('should log margin account of private key', async() => {
|
||||||
|
const ownerKey = [252,0,132,118,116,9,142,85,38,150,113,82,117,172,107,37,200,103,20,206,153,172,239,151,251,175,208,119,89,164,50,4,85,244,218,137,21,123,226,241,53,80,95,8,194,128,195,133,108,79,71,175,75,177,35,99,181,251,84,107,1,154,104,105];
|
||||||
|
const owner = new Account(ownerKey);
|
||||||
|
const mangoGroup = await client.getMangoGroup(connection, mangoGroupPk);
|
||||||
|
const marginAccount = await client.getMarginAccountsForOwner(connection, mangoProgramId, mangoGroup, owner);
|
||||||
|
console.info(marginAccount[0].publicKey.toString());
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { MangoClient, MangoGroup, MarginAccount } from '../src/client';
|
import { MangoClient, MangoGroup } from '../src/client';
|
||||||
import { Account, Connection, PublicKey, SystemProgram, Transaction, TransactionInstruction, TransactionSignature, TransferParams } from '@solana/web3.js';
|
import { Account, Connection, PublicKey, SystemProgram, Transaction, TransactionInstruction, TransactionSignature } from '@solana/web3.js';
|
||||||
import { Market, TokenInstructions, OpenOrders, Orderbook } from '@project-serum/serum';
|
import { Market, TokenInstructions, OpenOrders, Orderbook } from '@project-serum/serum';
|
||||||
|
import { Order } from '@project-serum/serum/lib/market';
|
||||||
import { token } from '@project-serum/common';
|
import { token } from '@project-serum/common';
|
||||||
import { u64, NATIVE_MINT } from "@solana/spl-token";
|
import { u64, NATIVE_MINT } from "@solana/spl-token";
|
||||||
import { sleep } from '../src/utils';
|
import { sleep, getDecimalCount } from '../src/utils';
|
||||||
|
|
||||||
console.log = function () {}; // NOTE: Disable all unnecessary logging
|
console.log = function () {}; // NOTE: Disable all unnecessary logging
|
||||||
|
|
||||||
|
@ -20,6 +21,7 @@ export async function _sendTransaction (
|
||||||
transaction: Transaction,
|
transaction: Transaction,
|
||||||
signers: Account[],
|
signers: Account[],
|
||||||
): Promise<TransactionSignature> {
|
): Promise<TransactionSignature> {
|
||||||
|
await sleep(1000)
|
||||||
const signature = await connection.sendTransaction(transaction, signers);
|
const signature = await connection.sendTransaction(transaction, signers);
|
||||||
try {
|
try {
|
||||||
await connection.confirmTransaction(signature);
|
await connection.confirmTransaction(signature);
|
||||||
|
@ -115,6 +117,18 @@ export async function createWalletAndRequestAirdrop(
|
||||||
return owner;
|
return owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getSpotMarketDetails(
|
||||||
|
connection: Connection,
|
||||||
|
mangoGroupSpotMarket: any,
|
||||||
|
dexProgramId: PublicKey
|
||||||
|
): Promise<{spotMarket: Market, baseSymbol: string, quoteSymbol: string, minSize: number, minPrice: number}> {
|
||||||
|
const [spotMarketSymbol, spotMarketAddress] = mangoGroupSpotMarket;
|
||||||
|
const [baseSymbol, quoteSymbol] = spotMarketSymbol.split('/');
|
||||||
|
const spotMarket = await Market.load(connection, new PublicKey(spotMarketAddress), { skipPreflight: true, commitment: 'singleGossip'}, dexProgramId);
|
||||||
|
const { minSize, minPrice } = getMinSizeAndPriceForMarket(spotMarket);
|
||||||
|
return { spotMarket, baseSymbol, quoteSymbol, minSize: minSize as number, minPrice: minPrice as number };
|
||||||
|
}
|
||||||
|
|
||||||
export async function createMangoGroupSymbolMappings (
|
export async function createMangoGroupSymbolMappings (
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
mangoGroupIds: any,
|
mangoGroupIds: any,
|
||||||
|
@ -128,28 +142,6 @@ export async function createMangoGroupSymbolMappings (
|
||||||
return mangoGroupTokenMappings;
|
return mangoGroupTokenMappings;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getOwnedTokenAccounts(
|
|
||||||
connection: Connection,
|
|
||||||
owner: Account,
|
|
||||||
): Promise<any[]> {
|
|
||||||
const ownedTokenAccounts = await token.getOwnedTokenAccounts(connection, owner.publicKey);
|
|
||||||
return ownedTokenAccounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateMarginTokenAccountsAndDeposits(
|
|
||||||
connection: Connection,
|
|
||||||
owner: Account,
|
|
||||||
client: MangoClient,
|
|
||||||
mangoGroup: MangoGroup,
|
|
||||||
marginAccountPk: PublicKey | null,
|
|
||||||
state: any,
|
|
||||||
dexProgramId: PublicKey,
|
|
||||||
): Promise<void>{
|
|
||||||
state.ownedTokenAccounts = await token.getOwnedTokenAccounts(connection, owner.publicKey);
|
|
||||||
state.marginAccount = (marginAccountPk) ? await client.getMarginAccount(connection, marginAccountPk, dexProgramId) : null;
|
|
||||||
state.deposits = (state.marginAccount) ? state.marginAccount.getDeposits(mangoGroup) : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function buildAirdropTokensIx(
|
export async function buildAirdropTokensIx(
|
||||||
amount: u64,
|
amount: u64,
|
||||||
tokenMintPublicKey: PublicKey,
|
tokenMintPublicKey: PublicKey,
|
||||||
|
@ -188,24 +180,6 @@ export async function airdropTokens(
|
||||||
return tokenDestinationPublicKey.toBase58();
|
return tokenDestinationPublicKey.toBase58();
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function airdropToken(
|
|
||||||
connection: Connection,
|
|
||||||
owner: Account,
|
|
||||||
tokenName: string,
|
|
||||||
mangoGroupTokenMappings: any,
|
|
||||||
faucetIds: any,
|
|
||||||
amount: number
|
|
||||||
): Promise<void> {
|
|
||||||
if (tokenName !== 'SOL') throw new Error('This airdrop is function is not meant for SOL');
|
|
||||||
const ownedTokenAccounts = await token.getOwnedTokenAccounts(connection, owner.publicKey);
|
|
||||||
const tokenMapping: any = Object.values(mangoGroupTokenMappings).find((x: any) => x.tokenName === tokenName);
|
|
||||||
const { tokenMint, decimals } = tokenMapping;
|
|
||||||
const ownedTokenAccount = ownedTokenAccounts.find((x: any) => x.account.mint.equals(tokenMint));
|
|
||||||
if (!ownedTokenAccount) throw new Error(`Token account doesn't exist for ${tokenName}`);
|
|
||||||
const multiplier = Math.pow(10, decimals);
|
|
||||||
await airdropTokens(connection, owner, faucetIds[tokenName], ownedTokenAccount.publicKey, tokenMint, new u64(amount * multiplier));
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function airdropSol(
|
export async function airdropSol(
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
owner: Account,
|
owner: Account,
|
||||||
|
@ -223,26 +197,56 @@ export async function airdropSol(
|
||||||
await _sendTransaction(connection, tx, signers);
|
await _sendTransaction(connection, tx, signers);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function airdropMangoGroupTokens(
|
export function getMinSizeAndPriceForMarket(
|
||||||
|
spotMarket: Market
|
||||||
|
): { minSize : string | number, minPrice: string | number} {
|
||||||
|
const minSize = spotMarket?.minOrderSize?.toFixed(getDecimalCount(spotMarket.minOrderSize)) || spotMarket?.minOrderSize;
|
||||||
|
const minPrice = spotMarket?.tickSize?.toFixed(getDecimalCount(spotMarket.tickSize)) || spotMarket?.tickSize;
|
||||||
|
return { minSize, minPrice };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function placeOrderUsingSerumDex(
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
owner: Account,
|
owner: Account,
|
||||||
mangoGroup: MangoGroup,
|
spotMarket: Market,
|
||||||
mangoGroupTokenMappings: any,
|
baseCurrencyAccount: PublicKey,
|
||||||
ownedTokenAccounts: any,
|
quoteCurrencyAccount: PublicKey,
|
||||||
faucetIds: any
|
orderParams: any
|
||||||
): Promise<void> {
|
): Promise<string> {
|
||||||
(await Promise.all(
|
const { side, price, size, orderType = 'limit', feeDiscountPubkey = undefined } = orderParams;
|
||||||
mangoGroup.tokens.map(async (mint: PublicKey) => {
|
const { minSize, minPrice } = getMinSizeAndPriceForMarket(spotMarket);
|
||||||
const {tokenName, decimals} = mangoGroupTokenMappings[mint.toString()];
|
const isIncrement = (num: number, step: number) => Math.abs((num / step) % 1) < 1e-5 || Math.abs(((num / step) % 1) - 1) < 1e-5;
|
||||||
if (tokenName) {
|
if (isNaN(price)) throw Error('Invalid price');
|
||||||
const ownedTokenAccount = ownedTokenAccounts.find((x: any) => x.account.mint.equals(mint));
|
if (isNaN(size)) throw Error('Invalid size');
|
||||||
if (tokenName !== 'SOL') {
|
if (!spotMarket) throw Error('Invalid market');
|
||||||
const multiplier = Math.pow(10, decimals);
|
if (!isIncrement(size, spotMarket.minOrderSize)) throw Error(`Size must be an increment of ${minSize}`);
|
||||||
await airdropTokens(connection, owner, faucetIds[tokenName], ownedTokenAccount.publicKey, mint, new u64(100 * multiplier));
|
if (size < spotMarket.minOrderSize) throw Error('Size too small');
|
||||||
}
|
if (!isIncrement(price, spotMarket.tickSize)) throw Error(`Price must be an increment of ${minPrice}`);
|
||||||
}
|
if (price < spotMarket.tickSize) throw Error('Price too small');
|
||||||
})
|
const transaction = new Transaction();
|
||||||
));
|
const signers: Account[] = [owner];
|
||||||
|
const payer = side === 'sell' ? baseCurrencyAccount : quoteCurrencyAccount;
|
||||||
|
if (!payer) throw Error('Associated account for spend currency is missing');
|
||||||
|
const params = { owner: owner.publicKey, payer, side, price, size, orderType, feeDiscountPubkey: feeDiscountPubkey || null};
|
||||||
|
let { transaction: placeOrderTx, signers: placeOrderSigners } = await spotMarket.makePlaceOrderTransaction( connection, params, 120_000, 120_000 );
|
||||||
|
transaction.add(placeOrderTx);
|
||||||
|
signers.push(...placeOrderSigners);
|
||||||
|
return await _sendTransaction(connection, transaction, signers);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function cancelOrdersUsingSerumDex(
|
||||||
|
connection: Connection,
|
||||||
|
owner: Account,
|
||||||
|
spotMarket: Market,
|
||||||
|
orders: Order[]
|
||||||
|
): Promise<string>{
|
||||||
|
const transaction = new Transaction();
|
||||||
|
orders.forEach((order) => {
|
||||||
|
transaction.add(
|
||||||
|
spotMarket.makeCancelOrderInstruction(connection, owner.publicKey, order),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return await _sendTransaction(connection, transaction, [owner]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createTokenAccountWithBalance(
|
export async function createTokenAccountWithBalance(
|
||||||
|
@ -251,44 +255,26 @@ export async function createTokenAccountWithBalance(
|
||||||
tokenName: string,
|
tokenName: string,
|
||||||
mangoGroupTokenMappings: any,
|
mangoGroupTokenMappings: any,
|
||||||
faucetIds: any,
|
faucetIds: any,
|
||||||
amount: number
|
amount: number,
|
||||||
|
wrappedSol: boolean = true,
|
||||||
) {
|
) {
|
||||||
const tokenMapping: any = Object.values(mangoGroupTokenMappings).find((x: any) => x.tokenName === tokenName);
|
const tokenMapping: any = Object.values(mangoGroupTokenMappings).find((x: any) => x.tokenName === tokenName);
|
||||||
const { tokenMint, decimals } = tokenMapping;
|
const { tokenMint, decimals } = tokenMapping;
|
||||||
const multiplier = Math.pow(10, decimals);
|
const multiplier = Math.pow(10, decimals);
|
||||||
const processedAmount = amount * multiplier;
|
const processedAmount = amount * multiplier;
|
||||||
|
let ownedTokenAccountPk: PublicKey | null = null;
|
||||||
if (tokenName === 'SOL') {
|
if (tokenName === 'SOL') {
|
||||||
await airdropSol(connection, owner, amount);
|
await airdropSol(connection, owner, amount);
|
||||||
await createWrappedNativeAccount(connection, owner, processedAmount);
|
if (wrappedSol) {
|
||||||
|
ownedTokenAccountPk = await createWrappedNativeAccount(connection, owner, processedAmount);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
await createTokenAccount(connection, tokenMint, owner);
|
ownedTokenAccountPk = await createTokenAccount(connection, tokenMint, owner);
|
||||||
if (amount > 0) {
|
if (amount > 0) {
|
||||||
const ownedTokenAccounts = await token.getOwnedTokenAccounts(connection, owner.publicKey);
|
await airdropTokens(connection, owner, faucetIds[tokenName], ownedTokenAccountPk, tokenMint, new u64(processedAmount));
|
||||||
const ownedTokenAccount = ownedTokenAccounts.find((x: any) => x.account.mint.equals(tokenMint));
|
|
||||||
if (!ownedTokenAccount) throw new Error(`Token account doesn't exist for ${tokenName}`);
|
|
||||||
await airdropTokens(connection, owner, faucetIds[tokenName], ownedTokenAccount.publicKey, tokenMint, new u64(processedAmount));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return ownedTokenAccountPk;
|
||||||
|
|
||||||
export async function createTokenAccountsForMangoGroupTokens (
|
|
||||||
connection: Connection,
|
|
||||||
owner: Account,
|
|
||||||
mangoGroup: MangoGroup,
|
|
||||||
mangoGroupTokenMappings: any,
|
|
||||||
) {
|
|
||||||
(await Promise.all(
|
|
||||||
mangoGroup.tokens.map(async (mint: PublicKey) => {
|
|
||||||
const {tokenName} = mangoGroupTokenMappings[mint.toString()];
|
|
||||||
if (tokenName) {
|
|
||||||
if (tokenName === 'SOL') {
|
|
||||||
await createWrappedNativeAccount(connection, owner, 100 * 1e9);
|
|
||||||
} else {
|
|
||||||
await createTokenAccount(connection, mint, owner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function performSingleDepositOrWithdrawal (
|
export async function performSingleDepositOrWithdrawal (
|
||||||
|
@ -309,34 +295,13 @@ export async function performSingleDepositOrWithdrawal (
|
||||||
const ownedTokenAccount = ownedTokenAccounts.find((x: any) => x.account.mint.equals(tokenMint));
|
const ownedTokenAccount = ownedTokenAccounts.find((x: any) => x.account.mint.equals(tokenMint));
|
||||||
if (!ownedTokenAccount) throw new Error(`Token account doesn't exist for ${tokenName}`);
|
if (!ownedTokenAccount) throw new Error(`Token account doesn't exist for ${tokenName}`);
|
||||||
if (type === 'deposit') {
|
if (type === 'deposit') {
|
||||||
|
// TODO: Add wrapped SOL functionality here instead of creating an account for Wrapped SOL in default
|
||||||
await client.deposit(connection, mangoProgramId, mangoGroup, marginAccount, owner, tokenMint, ownedTokenAccount.publicKey, Number(amount));
|
await client.deposit(connection, mangoProgramId, mangoGroup, marginAccount, owner, tokenMint, ownedTokenAccount.publicKey, Number(amount));
|
||||||
} else if (type === 'withdraw') {
|
} else if (type === 'withdraw') {
|
||||||
await client.withdraw(connection, mangoProgramId, mangoGroup, marginAccount, owner, tokenMint, ownedTokenAccount.publicKey, Number(amount));
|
await client.withdraw(connection, mangoProgramId, mangoGroup, marginAccount, owner, tokenMint, ownedTokenAccount.publicKey, Number(amount));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function performDepositOrWithdrawal (
|
|
||||||
connection: Connection,
|
|
||||||
owner: Account,
|
|
||||||
client: MangoClient,
|
|
||||||
mangoGroup: MangoGroup,
|
|
||||||
mangoProgramId: PublicKey,
|
|
||||||
state: any,
|
|
||||||
type: string,
|
|
||||||
amount: number
|
|
||||||
) {
|
|
||||||
(await Promise.all(
|
|
||||||
mangoGroup.tokens.map(async (mint: PublicKey) => {
|
|
||||||
const ownedTokenAccount = state.ownedTokenAccounts.find((x: any) => x.account.mint.equals(mint));
|
|
||||||
if (type === 'deposit') {
|
|
||||||
await client.deposit(connection, mangoProgramId, mangoGroup, state.marginAccount, owner, mint, ownedTokenAccount.publicKey, Number(amount));
|
|
||||||
} else if (type === 'withdraw') {
|
|
||||||
await client.withdraw(connection, mangoProgramId, mangoGroup, state.marginAccount, owner, mint, ownedTokenAccount.publicKey, Number(amount));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAndDecodeBidsAndAsks (
|
export async function getAndDecodeBidsAndAsks (
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
spotMarket: Market
|
spotMarket: Market
|
||||||
|
@ -367,6 +332,7 @@ export async function getBidOrAskPriceEdge(
|
||||||
bidOrAsk: string,
|
bidOrAsk: string,
|
||||||
maxOrMin: string
|
maxOrMin: string
|
||||||
): Promise<number>{
|
): Promise<number>{
|
||||||
|
// TODO: Refactor this function to use minSize and minPrice prices
|
||||||
const { bidOrderBook, askOrderBook } = await getAndDecodeBidsAndAsks(connection, spotMarket);
|
const { bidOrderBook, askOrderBook } = await getAndDecodeBidsAndAsks(connection, spotMarket);
|
||||||
const [orderBookSide, orderBookOtherSide] = (bidOrAsk === 'bid' ? [bidOrderBook, askOrderBook] : [askOrderBook, bidOrderBook]);
|
const [orderBookSide, orderBookOtherSide] = (bidOrAsk === 'bid' ? [bidOrderBook, askOrderBook] : [askOrderBook, bidOrderBook]);
|
||||||
const orderBookSidePrices: number[] = [...orderBookSide].map(x => x.price);
|
const orderBookSidePrices: number[] = [...orderBookSide].map(x => x.price);
|
||||||
|
@ -397,7 +363,7 @@ export async function getOrderSizeAndPrice(
|
||||||
// NOTE: Always use minOrderSize
|
// NOTE: Always use minOrderSize
|
||||||
const tokenMapping: any = Object.values(mangoGroupTokenMappings).find((x: any) => x.tokenName === baseSymbol);
|
const tokenMapping: any = Object.values(mangoGroupTokenMappings).find((x: any) => x.tokenName === baseSymbol);
|
||||||
const { decimals } = tokenMapping;
|
const { decimals } = tokenMapping;
|
||||||
const [stepSize, orderSize] = (decimals === 6) ? [0.01, 1] : [10, 0.01];
|
const [stepSize, orderSize] = (decimals === 6) ? [0.1, 1] : [1, 0.1];
|
||||||
const edge = (side === 'buy') ? ['bid', 'max'] : ['ask', 'min'];
|
const edge = (side === 'buy') ? ['bid', 'max'] : ['ask', 'min'];
|
||||||
const orderPrice: number = Math.max(await getBidOrAskPriceEdge(connection, spotMarket, edge[0], edge[1]), stepSize);
|
const orderPrice: number = Math.max(await getBidOrAskPriceEdge(connection, spotMarket, edge[0], edge[1]), stepSize);
|
||||||
return [orderSize, orderPrice, stepSize];
|
return [orderSize, orderPrice, stepSize];
|
||||||
|
|
Loading…
Reference in New Issue