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:
parent
d03fcdf0fe
commit
0a528077e7
|
@ -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",
|
||||
|
|
79
src/index.ts
79
src/index.ts
|
@ -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()
|
|
@ -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()
|
74
src/utils.ts
74
src/utils.ts
|
@ -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);
|
||||
}
|
||||
|
@ -303,3 +312,60 @@ export async function findLargestTokenAccountForOwner(
|
|||
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 };
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue