maintenance (#13)

* add commitment arg to getMultipleAccounts
* upgrade dependencies
* use sequence number to continuosly read out event queue ring buffer
* add test for market order
* version 0.1.19
This commit is contained in:
Maximilian Schneider 2021-05-13 14:32:38 +03:00 committed by GitHub
parent d03fcdf0fe
commit 0a528077e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 787 additions and 587 deletions

View File

@ -1,6 +1,6 @@
{
"name": "@blockworks-foundation/mango-client",
"version": "0.1.17",
"version": "0.1.19",
"description": "Library for interacting with Mango Markets' solana smart contracts.",
"repository": "blockworks-foundation/mango-client-ts",
"author": {
@ -17,7 +17,7 @@
},
"scripts": {
"build": "tsc",
"start": "ts-node src/index.ts",
"start": "ts-node src/tests.ts",
"clean": "rm -rf lib",
"prepare": "run-s clean build",
"shell": "node -e \"$(< shell)\" -i --experimental-repl-await",
@ -57,7 +57,7 @@
"dependencies": {
"@project-serum/serum": "^0.13.20",
"@project-serum/sol-wallet-adapter": "^0.1.4",
"@solana/spl-token": "0.0.13",
"@solana/spl-token": "^0.0.13",
"@solana/web3.js": "^0.90.0",
"bn.js": "^5.1.2",
"borsh": "https://github.com/defactojob/borsh-js#field-mapper",

View File

@ -1,82 +1,7 @@
import { MangoClient, MangoGroup } from './client';
import IDS from './ids.json';
export { IDS }
export { MangoClient, MangoGroup, MarginAccount, tokenToDecimals } from './client';
export { MangoIndexLayout, MarginAccountLayout, MangoGroupLayout } from './layout';
export * from './layout';
export * from './utils'
export * from './utils';
export { IDS }
// async function tests() {
// const cluster = "mainnet-beta";
// const client = new MangoClient();
// const clusterIds = IDS[cluster]
//
// const connection = new Connection(IDS.cluster_urls[cluster], 'singleGossip')
// const mangoGroupPk = new PublicKey(clusterIds.mango_groups['BTC_ETH_USDT'].mango_group_pk);
// const mangoProgramId = new PublicKey(clusterIds.mango_program_id);
//
// const keyPairPath = process.env.KEYPAIR || homedir() + '/.config/solana/id.json'
// const payer = new Account(JSON.parse(fs.readFileSync(keyPairPath, 'utf-8')))
//
// async function testSolink() {
//
// const oraclePk = new PublicKey(IDS[cluster].oracles['ETH/USDT'])
// const agg = await Aggregator.loadWithConnection(oraclePk, connection)
//
// // const agg = await Aggregator.loadWithConnection(oraclePk, connection)
// console.log(agg.answer.median.toNumber(), agg.answer.updatedAt.toNumber(), agg.round.id.toNumber())
//
// }
//
// async function testDepositSrm() {
// const srmVaultPk = new PublicKey(clusterIds['mango_groups']['BTC_ETH_USDT']['srm_vault_pk'])
// const mangoGroup = await client.getMangoGroup(connection, mangoGroupPk, srmVaultPk)
// const srmAccountPk = new PublicKey("6utvndL8EEjpwK5QVtguErncQEPVbkuyABmXu6FeygeV")
// const mangoSrmAccountPk = await client.depositSrm(connection, mangoProgramId, mangoGroup, payer, srmAccountPk, 100)
// console.log(mangoSrmAccountPk.toBase58())
// await sleep(2000)
// const mangoSrmAccount = await client.getMangoSrmAccount(connection, mangoSrmAccountPk)
// const txid = await client.withdrawSrm(connection, mangoProgramId, mangoGroup, mangoSrmAccount, payer, srmAccountPk, 50)
// console.log('success', txid)
// }
//
// async function getMarginAccountDetails() {
// const mangoGroup = await client.getMangoGroup(connection, mangoGroupPk);
// const marginAccountPk = new PublicKey("6vAry8oHVvWqPJV6SMzzJ9EcQr5kkQYHef6ui2ewaagQ")
// const marginAccount = await client.getMarginAccount(connection, marginAccountPk, mangoGroup.dexProgramId)
// const prices = await mangoGroup.getPrices(connection)
//
// console.log(marginAccount.toPrettyString(mangoGroup, prices))
// console.log(marginAccount.beingLiquidated)
// console.log(marginAccount.getUiDeposit(mangoGroup, 0), marginAccount.getUiBorrow(mangoGroup, 0))
// console.log(marginAccount.getUiDeposit(mangoGroup, 1), marginAccount.getUiBorrow(mangoGroup, 1))
// console.log(marginAccount.getUiDeposit(mangoGroup, 2), marginAccount.getUiBorrow(mangoGroup, 2))
// console.log(marginAccount.getCollateralRatio(mangoGroup, prices))
// // for (let i = 0; i < NUM_MARKETS; i++) {
// // let openOrdersAccount = marginAccount.openOrdersAccounts[i]
// // if (openOrdersAccount === undefined) {
// // continue
// // }
// //
// // for (const oid of openOrdersAccount.orders) {
// // console.log(oid.toString())
// // }
// // console.log(i,
// // nativeToUi(openOrdersAccount.quoteTokenTotal.toNumber(), mangoGroup.mintDecimals[NUM_MARKETS]),
// // nativeToUi(openOrdersAccount.quoteTokenFree.toNumber(), mangoGroup.mintDecimals[NUM_MARKETS]),
// //
// // nativeToUi(openOrdersAccount.baseTokenTotal.toNumber(), mangoGroup.mintDecimals[i]),
// // nativeToUi(openOrdersAccount.baseTokenFree.toNumber(), mangoGroup.mintDecimals[i])
// //
// // )
// // }
//
// }
// await getMarginAccountDetails()
// // await testSolink()
// // testDepositSrm()
// }
//
// tests()

176
src/tests.ts Normal file
View File

@ -0,0 +1,176 @@
import { Market, OpenOrders } from '@project-serum/serum';
import { Account, Commitment, Connection, PublicKey } from '@solana/web3.js';
import * as os from 'os';
import * as fs from 'fs';
import { MangoClient, MangoGroup } from './client';
import IDS from './ids.json';
import {
decodeRecentEvents,
findLargestTokenAccountForOwner,
getMultipleAccounts,
nativeToUi,
parseTokenAccountData,
sleep
} from './utils'
async function tests() {
const cluster = "mainnet-beta";
const client = new MangoClient();
const clusterIds = IDS[cluster]
const connection = new Connection(IDS.cluster_urls[cluster], 'processed' as Commitment)
const mangoGroupPk = new PublicKey(clusterIds.mango_groups['BTC_ETH_USDT'].mango_group_pk);
const mangoProgramId = new PublicKey(clusterIds.mango_program_id);
const keyPairPath = process.env.KEYPAIR || os.homedir() + '/.config/solana/id.json'
const payer = new Account(JSON.parse(fs.readFileSync(keyPairPath, 'utf-8')))
/*
async function testSolink() {
const oraclePk = new PublicKey(IDS[cluster].oracles['ETH/USDT'])
const agg = await Aggregator.loadWithConnection(oraclePk, connection)
// const agg = await Aggregator.loadWithConnection(oraclePk, connection)
console.log(agg.answer.median.toNumber(), agg.answer.updatedAt.toNumber(), agg.round.id.toNumber())
}
*/
/*
async function testDepositSrm() {
const srmVaultPk = new PublicKey(clusterIds['mango_groups']['BTC_ETH_USDT']['srm_vault_pk'])
const mangoGroup = await client.getMangoGroup(connection, mangoGroupPk, srmVaultPk)
const srmAccountPk = new PublicKey("6utvndL8EEjpwK5QVtguErncQEPVbkuyABmXu6FeygeV")
const mangoSrmAccountPk = await client.depositSrm(connection, mangoProgramId, mangoGroup, payer, srmAccountPk, 100)
console.log(mangoSrmAccountPk.toBase58())
await sleep(2000)
const mangoSrmAccount = await client.getMangoSrmAccount(connection, mangoSrmAccountPk)
const txid = await client.withdrawSrm(connection, mangoProgramId, mangoGroup, mangoSrmAccount, payer, srmAccountPk, 50)
console.log('success', txid)
}
*/
/*
async function getMarginAccountDetails() {
const mangoGroup = await client.getMangoGroup(connection, mangoGroupPk);
const marginAccountPk = new PublicKey("6vAry8oHVvWqPJV6SMzzJ9EcQr5kkQYHef6ui2ewaagQ")
const marginAccount = await client.getMarginAccount(connection, marginAccountPk, mangoGroup.dexProgramId)
const prices = await mangoGroup.getPrices(connection)
console.log(marginAccount.toPrettyString(mangoGroup, prices))
console.log(marginAccount.beingLiquidated)
console.log(marginAccount.getUiDeposit(mangoGroup, 0), marginAccount.getUiBorrow(mangoGroup, 0))
console.log(marginAccount.getUiDeposit(mangoGroup, 1), marginAccount.getUiBorrow(mangoGroup, 1))
console.log(marginAccount.getUiDeposit(mangoGroup, 2), marginAccount.getUiBorrow(mangoGroup, 2))
console.log(marginAccount.getCollateralRatio(mangoGroup, prices))
// for (let i = 0; i < NUM_MARKETS; i++) {
// let openOrdersAccount = marginAccount.openOrdersAccounts[i]
// if (openOrdersAccount === undefined) {
// continue
// }
//
// for (const oid of openOrdersAccount.orders) {
// console.log(oid.toString())
// }
// console.log(i,
// nativeToUi(openOrdersAccount.quoteTokenTotal.toNumber(), mangoGroup.mintDecimals[NUM_MARKETS]),
// nativeToUi(openOrdersAccount.quoteTokenFree.toNumber(), mangoGroup.mintDecimals[NUM_MARKETS]),
//
// nativeToUi(openOrdersAccount.baseTokenTotal.toNumber(), mangoGroup.mintDecimals[i]),
// nativeToUi(openOrdersAccount.baseTokenFree.toNumber(), mangoGroup.mintDecimals[i])
//
// )
// }
}
*/
async function testMarketOrderDex() {
const NUM_MARKETS = 2;
const dexProgramId = new PublicKey(clusterIds.dex_program_id);
const mangoGroup = await client.getMangoGroup(connection, mangoGroupPk)
// load largest wallet account for each token
const tokenWallets = (await Promise.all(
mangoGroup.tokens.map(
(mint) => findLargestTokenAccountForOwner(connection, payer.publicKey, mint).then(
(response) => response.publicKey
)
)
))
console.log({tokenWallets: tokenWallets.map(w => w.toString())})
// load all markets
const markets = await Promise.all(mangoGroup.spotMarkets.map(
(pk) => Market.load(connection, pk, {skipPreflight: true, commitment: 'singleGossip'}, dexProgramId)
))
console.log({markets})
// load open orders
const liqorOpenOrdersKeys: PublicKey[] = []
for (let i = 0; i < NUM_MARKETS; i++) {
let openOrdersAccounts: OpenOrders[] = await markets[i].findOpenOrdersAccountsForOwner(connection, payer.publicKey)
if(openOrdersAccounts.length) {
liqorOpenOrdersKeys.push(openOrdersAccounts[0].publicKey)
} else {
console.log(`No OpenOrders account found for market ${markets[i].publicKey.toBase58()}`)
}
}
console.log({liqorOpenOrdersKeys: liqorOpenOrdersKeys.map(k => k.toString())})
const marketIndex = 1;
const market = markets[marketIndex]; // ETH/USDT
const price = 4000;
const size = 0.001;
const txid = await market.placeOrder(
connection,
{
owner: payer,
payer: tokenWallets[marketIndex],
side: 'sell',
price,
size,
orderType: 'ioc',
openOrdersAddressKey: liqorOpenOrdersKeys[marketIndex],
feeDiscountPubkey: null // TODO find liqor's SRM fee account
}
)
console.log({txid})
var lastSeenSeqNum = undefined
for (let i = 0; i < 50; ++i) {
const status = await connection.getSignatureStatus(txid);
console.log({status: status!.value!.confirmations})
let orders = await market.loadOrdersForOwner(connection, payer.publicKey);
console.log({orders});
const info = await connection.getAccountInfo(market['_decoded'].eventQueue)
const { header, nodes } = decodeRecentEvents(info!.data, lastSeenSeqNum)
console.log({ header, nodes: nodes.map(n => [n.nativeQuantityPaid.toNumber(),
n.nativeQuantityReleased.toNumber()]) })
lastSeenSeqNum = header.seqNum
const liqorWalletAccounts = await getMultipleAccounts(connection, tokenWallets, 'processed' as Commitment)
const liqorValuesUi = liqorWalletAccounts.map(
(a, i) => nativeToUi(parseTokenAccountData(a.accountInfo.data).amount, mangoGroup.mintDecimals[i])
)
console.log({liqorValuesUi})
await sleep(500)
}
}
/*
await getMarginAccountDetails()
await testSolink()
await testDepositSrm()
*/
await testMarketOrderDex()
}
tests()

View File

@ -9,10 +9,18 @@ import {
} from '@solana/web3.js';
import BN from 'bn.js';
import { WRAPPED_SOL_MINT } from '@project-serum/serum/lib/token-instructions';
import { blob, struct, u8, nu64 } from 'buffer-layout';
import { bits, blob, struct, u8, u32, nu64 } from 'buffer-layout';
import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
import { AccountLayout } from './layout';
import {
accountFlagsLayout,
publicKeyLayout,
u128,
u64,
zeros,
} from '@project-serum/serum/lib/layout';
export const zeroKey = new PublicKey(new Uint8Array(32))
export async function sleep(ms) {
@ -253,13 +261,14 @@ export function parseTokenAccount(
export async function getMultipleAccounts(
connection: Connection,
publicKeys: PublicKey[]
publicKeys: PublicKey[],
commitment?: Commitment
): Promise<{ publicKey: PublicKey; accountInfo: AccountInfo<Buffer> }[]> {
const publickKeyStrs = publicKeys.map((pk) => (pk.toBase58()));
const args = commitment ? [publickKeyStrs, {commitment}] : [publickKeyStrs];
// @ts-ignore
const resp = await connection._rpcRequest('getMultipleAccounts', [publickKeyStrs]);
const resp = await connection._rpcRequest('getMultipleAccounts', args);
if (resp.error) {
throw new Error(resp.error.message);
}
@ -302,4 +311,61 @@ export async function findLargestTokenAccountForOwner(
} else {
throw new Error("No accounts for this token")
}
}
}
const EVENT_QUEUE_HEADER = struct([
blob(5),
accountFlagsLayout('accountFlags'),
u32('head'),
zeros(4),
u32('count'),
zeros(4),
u32('seqNum'),
zeros(4),
]);
const EVENT_FLAGS = bits(u8(), false, 'eventFlags');
EVENT_FLAGS.addBoolean('fill');
EVENT_FLAGS.addBoolean('out');
EVENT_FLAGS.addBoolean('bid');
EVENT_FLAGS.addBoolean('maker');
const EVENT = struct([
EVENT_FLAGS,
u8('openOrdersSlot'),
u8('feeTier'),
blob(5),
u64('nativeQuantityReleased'), // Amount the user received
u64('nativeQuantityPaid'), // Amount the user paid
u64('nativeFeeOrRebate'),
u128('orderId'),
publicKeyLayout('openOrders'),
u64('clientOrderId'),
]);
export function decodeRecentEvents(
buffer: Buffer,
lastSeenSeqNum?: number,
) {
const header = EVENT_QUEUE_HEADER.decode(buffer);
const nodes: any[] = [];
if (lastSeenSeqNum !== undefined) {
const allocLen = Math.floor(
(buffer.length - EVENT_QUEUE_HEADER.span) / EVENT.span,
);
const newEventsCount = header.seqNum - lastSeenSeqNum
for (let i = newEventsCount; i > 0; --i) {
const nodeIndex = (header.head + header.count + allocLen - i) % allocLen
const decodedItem = EVENT.decode(buffer, EVENT_QUEUE_HEADER.span + nodeIndex * EVENT.span)
nodes.push(decodedItem)
}
}
return { header, nodes };
}

1037
yarn.lock

File diff suppressed because it is too large Load Diff