2021-03-09 05:32:07 -08:00
|
|
|
import {
|
2021-03-09 05:44:39 -08:00
|
|
|
createAccountInstruction,
|
|
|
|
encodeMangoInstruction,
|
|
|
|
findLargestTokenAccountForOwner, getFilteredProgramAccounts,
|
2021-03-09 05:32:07 -08:00
|
|
|
IDS,
|
|
|
|
MangoClient,
|
|
|
|
MangoGroup,
|
|
|
|
MarginAccount,
|
2021-03-09 05:44:39 -08:00
|
|
|
MarginAccountLayout,
|
|
|
|
nativeToUi, NUM_MARKETS,
|
2021-03-09 05:32:07 -08:00
|
|
|
NUM_TOKENS,
|
2021-03-09 05:44:39 -08:00
|
|
|
} from '@blockworks-foundation/mango-client';
|
2021-03-09 05:32:07 -08:00
|
|
|
import {
|
|
|
|
Account,
|
2021-03-09 05:44:39 -08:00
|
|
|
Connection,
|
|
|
|
LAMPORTS_PER_SOL,
|
2021-03-09 05:32:07 -08:00
|
|
|
PublicKey,
|
|
|
|
SYSVAR_RENT_PUBKEY,
|
|
|
|
Transaction,
|
2021-03-09 05:44:39 -08:00
|
|
|
TransactionInstruction,
|
|
|
|
TransactionSignature,
|
2021-03-09 05:32:07 -08:00
|
|
|
} from '@solana/web3.js';
|
|
|
|
import fs from 'fs';
|
|
|
|
import { getUnixTs, sleep } from './utils';
|
2021-03-09 05:44:39 -08:00
|
|
|
import { AccountLayout, TOKEN_PROGRAM_ID } from '@solana/spl-token';
|
2021-03-09 05:32:07 -08:00
|
|
|
import { homedir } from 'os';
|
|
|
|
import { Market } from '@project-serum/serum';
|
|
|
|
|
|
|
|
|
|
|
|
async function genMarginAccounts() {
|
|
|
|
const client = new MangoClient()
|
|
|
|
const cluster = 'devnet'
|
|
|
|
const connection = new Connection(IDS.cluster_urls[cluster], 'singleGossip')
|
|
|
|
|
|
|
|
// The address of the Mango Program on the blockchain
|
|
|
|
const programId = new PublicKey(IDS[cluster].mango_program_id)
|
|
|
|
// The address of the serum dex program on the blockchain: https://github.com/project-serum/serum-dex
|
|
|
|
const dexProgramId = new PublicKey(IDS[cluster].dex_program_id)
|
|
|
|
|
|
|
|
// Address of the MangoGroup
|
2021-06-11 12:53:17 -07:00
|
|
|
const mangoGroupPk = new PublicKey(IDS[cluster].mango_groups['BTC_ETH_USDC'].mango_group_pk)
|
2021-03-09 05:32:07 -08:00
|
|
|
|
|
|
|
const keyPairPath = '/home/dd/.config/solana/id.json'
|
|
|
|
const payer = new Account(JSON.parse(fs.readFileSync(keyPairPath, 'utf-8')))
|
|
|
|
|
|
|
|
const mangoGroup = await client.getMangoGroup(connection, mangoGroupPk)
|
|
|
|
|
|
|
|
const n = 1800
|
|
|
|
|
|
|
|
const t0 = getUnixTs()
|
|
|
|
for (let i = 0; i < n; i++) {
|
|
|
|
// const pk = await client.initMarginAccount(connection, programId, mangoGroup, payer)
|
|
|
|
const pks = await initMultipleMarginAccounts(client, connection, programId, mangoGroup, payer, 5)
|
|
|
|
|
|
|
|
const elapsed = getUnixTs() - t0
|
|
|
|
console.log(i, elapsed / (i+1), elapsed)
|
|
|
|
|
|
|
|
for (const pk of pks) {
|
|
|
|
console.log(pk.toBase58())
|
|
|
|
}
|
|
|
|
console.log('\n')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function initMultipleMarginAccounts(
|
|
|
|
client: MangoClient,
|
|
|
|
connection: Connection,
|
|
|
|
programId: PublicKey,
|
|
|
|
mangoGroup: MangoGroup,
|
|
|
|
owner: Account, // assumed to be same as payer for now
|
|
|
|
n: number
|
|
|
|
): Promise<PublicKey[]> {
|
|
|
|
const transaction = new Transaction()
|
|
|
|
|
|
|
|
const additionalSigners: Account[] = []
|
|
|
|
const marginAccountKeys: PublicKey[] = []
|
|
|
|
for (let i = 0; i < n; i++) {
|
|
|
|
// Create a Solana account for the MarginAccount and allocate space
|
|
|
|
const accInstr = await createAccountInstruction(connection,
|
|
|
|
owner.publicKey, MarginAccountLayout.span, programId)
|
|
|
|
|
|
|
|
// Specify the accounts this instruction takes in (see program/src/instruction.rs)
|
|
|
|
const keys = [
|
|
|
|
{ isSigner: false, isWritable: false, pubkey: mangoGroup.publicKey },
|
|
|
|
{ isSigner: false, isWritable: true, pubkey: accInstr.account.publicKey },
|
|
|
|
{ isSigner: true, isWritable: false, pubkey: owner.publicKey },
|
|
|
|
{ isSigner: false, isWritable: false, pubkey: SYSVAR_RENT_PUBKEY }
|
|
|
|
]
|
|
|
|
|
|
|
|
// Encode and create instruction for actual initMarginAccount instruction
|
|
|
|
const data = encodeMangoInstruction({ InitMarginAccount: {} })
|
|
|
|
const initMarginAccountInstruction = new TransactionInstruction( { keys, data, programId })
|
|
|
|
|
|
|
|
// Add all instructions to one atomic transaction
|
|
|
|
transaction.add(accInstr.instruction)
|
|
|
|
transaction.add(initMarginAccountInstruction)
|
|
|
|
|
|
|
|
// Specify signers in addition to the wallet
|
|
|
|
additionalSigners.push(accInstr.account)
|
|
|
|
|
|
|
|
|
|
|
|
marginAccountKeys.push(accInstr.account.publicKey)
|
|
|
|
}
|
|
|
|
|
|
|
|
// sign, send and confirm transaction
|
|
|
|
await client.sendTransaction(connection, transaction, owner, additionalSigners)
|
|
|
|
|
|
|
|
return marginAccountKeys
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
async function testRent() {
|
|
|
|
const client = new MangoClient()
|
|
|
|
const cluster = 'mainnet-beta'
|
|
|
|
const connection = new Connection(IDS.cluster_urls[cluster], 'singleGossip')
|
|
|
|
const r = await connection.getMinimumBalanceForRentExemption(240, 'singleGossip')
|
|
|
|
|
|
|
|
console.log(r, LAMPORTS_PER_SOL, r / LAMPORTS_PER_SOL, 16 * r / LAMPORTS_PER_SOL)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function testTokenCall() {
|
|
|
|
|
|
|
|
const client = new MangoClient()
|
|
|
|
const cluster = 'mainnet-beta'
|
|
|
|
const clusterUrl = IDS['cluster_urls'][cluster]
|
|
|
|
const connection = new Connection(clusterUrl, 'singleGossip')
|
|
|
|
const usdtKey = new PublicKey(IDS[cluster]['symbols']['USDC'])
|
|
|
|
// const usdtKey = new PublicKey("8GxiBm7XirFqisDry3QdgiZDYMNfuZF1RKFTQbqBRVmp")
|
|
|
|
|
|
|
|
const filters = [
|
|
|
|
{
|
|
|
|
memcmp: {
|
|
|
|
offset: AccountLayout.offsetOf('mint'),
|
|
|
|
bytes: usdtKey.toBase58(),
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
dataSize: AccountLayout.span,
|
|
|
|
},
|
|
|
|
]
|
|
|
|
const t0 = getUnixTs()
|
|
|
|
const accounts = await getFilteredProgramAccounts(connection, TOKEN_PROGRAM_ID, filters)
|
|
|
|
const t1 = getUnixTs()
|
|
|
|
console.log(accounts.length, t1 - t0)
|
|
|
|
}
|
|
|
|
|
|
|
|
async function testServer() {
|
|
|
|
const cluster = 'mainnet-beta'
|
|
|
|
let clusterUrl = process.env.CLUSTER_URL
|
|
|
|
if (!clusterUrl) {
|
|
|
|
clusterUrl = IDS['cluster_urls'][cluster]
|
|
|
|
}
|
|
|
|
const connection = new Connection(clusterUrl, 'singleGossip')
|
|
|
|
const usdtKey = new PublicKey(IDS[cluster]['symbols']['USDT'])
|
|
|
|
const filters = [
|
|
|
|
{
|
|
|
|
memcmp: {
|
|
|
|
offset: AccountLayout.offsetOf('mint'),
|
|
|
|
bytes: usdtKey.toBase58(),
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
dataSize: AccountLayout.span,
|
|
|
|
},
|
|
|
|
]
|
|
|
|
const t0 = getUnixTs()
|
|
|
|
const accounts = await getFilteredProgramAccounts(connection, TOKEN_PROGRAM_ID, filters)
|
|
|
|
const t1 = getUnixTs()
|
|
|
|
console.log(accounts.length, t1 - t0, accounts.length * AccountLayout.span)
|
|
|
|
}
|
|
|
|
|
|
|
|
async function drainAccount(
|
|
|
|
client: MangoClient,
|
|
|
|
connection: Connection,
|
|
|
|
programId: PublicKey,
|
|
|
|
mangoGroup: MangoGroup,
|
|
|
|
ma: MarginAccount,
|
|
|
|
markets: Market[],
|
|
|
|
payer: Account,
|
|
|
|
prices: number[],
|
|
|
|
usdWallet: PublicKey
|
|
|
|
) {
|
|
|
|
// Cancel all open orders
|
|
|
|
const bidsPromises = markets.map((market) => market.loadBids(connection))
|
|
|
|
const asksPromises = markets.map((market) => market.loadAsks(connection))
|
|
|
|
const books = await Promise.all(bidsPromises.concat(asksPromises))
|
|
|
|
const bids = books.slice(0, books.length / 2)
|
|
|
|
const asks = books.slice(books.length / 2, books.length)
|
|
|
|
|
|
|
|
const cancelProms: Promise<TransactionSignature[]>[] = []
|
|
|
|
for (let i = 0; i < NUM_MARKETS; i++) {
|
|
|
|
cancelProms.push(ma.cancelAllOrdersByMarket(connection, client, programId, mangoGroup, markets[i], bids[i], asks[i], payer))
|
|
|
|
}
|
|
|
|
|
|
|
|
await Promise.all(cancelProms)
|
|
|
|
console.log('all orders cancelled')
|
|
|
|
|
|
|
|
console.log()
|
|
|
|
await client.settleAll(connection, programId, mangoGroup, ma, markets, payer)
|
|
|
|
console.log('settleAll complete')
|
|
|
|
ma = await client.getMarginAccount(connection, ma.publicKey, mangoGroup.dexProgramId)
|
|
|
|
|
|
|
|
// sort non-quote currency assets by value
|
|
|
|
const assets = ma.getAssets(mangoGroup)
|
|
|
|
const liabs = ma.getLiabs(mangoGroup)
|
|
|
|
|
|
|
|
const netValues: [number, number][] = []
|
|
|
|
|
|
|
|
for (let i = 0; i < NUM_TOKENS - 1; i++) {
|
|
|
|
netValues.push([i, (assets[i] - liabs[i]) * prices[i]])
|
|
|
|
}
|
|
|
|
netValues.sort((a, b) => (b[1] - a[1]))
|
|
|
|
|
|
|
|
for (let i = 0; i < NUM_TOKENS - 1; i++) {
|
|
|
|
const marketIndex = netValues[i][0]
|
|
|
|
const market = markets[marketIndex]
|
|
|
|
|
|
|
|
if (netValues[i][1] > 0) { // sell to close
|
|
|
|
const price = prices[marketIndex] * 0.95
|
|
|
|
const size = assets[marketIndex]
|
|
|
|
console.log(`Sell to close ${marketIndex} ${size}`)
|
|
|
|
await client.placeOrder(connection, programId, mangoGroup, ma, market, payer, 'sell', price, size, 'limit')
|
|
|
|
|
|
|
|
} else if (netValues[i][1] < 0) { // buy to close
|
|
|
|
const price = prices[marketIndex] * 1.05 // buy at up to 5% higher than oracle price
|
|
|
|
const size = liabs[marketIndex]
|
|
|
|
console.log(mangoGroup.getUiTotalDeposit(NUM_MARKETS), mangoGroup.getUiTotalBorrow(NUM_MARKETS))
|
|
|
|
console.log(ma.getUiDeposit(mangoGroup, NUM_MARKETS), ma.getUiBorrow(mangoGroup, NUM_MARKETS))
|
|
|
|
console.log(`Buy to close ${marketIndex} ${size}`)
|
|
|
|
await client.placeOrder(connection, programId, mangoGroup, ma, market, payer, 'buy', price, size, 'limit')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
await client.settleAll(connection, programId, mangoGroup, ma, markets, payer)
|
|
|
|
console.log('settleAll complete')
|
|
|
|
ma = await client.getMarginAccount(connection, ma.publicKey, mangoGroup.dexProgramId)
|
|
|
|
console.log('Liquidation process complete\n', ma.toPrettyString(mangoGroup, prices))
|
|
|
|
|
|
|
|
console.log('Withdrawing USD')
|
|
|
|
await client.withdraw(connection, programId, mangoGroup, ma, payer, mangoGroup.tokens[NUM_TOKENS-1], usdWallet, ma.getUiDeposit(mangoGroup, NUM_TOKENS-1) * 0.999)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function testAll() {
|
|
|
|
const client = new MangoClient()
|
2021-04-07 17:54:06 -07:00
|
|
|
const cluster = 'devnet'
|
2021-03-09 05:32:07 -08:00
|
|
|
const clusterUrl = process.env.CLUSTER_URL || IDS.cluster_urls[cluster]
|
|
|
|
const connection = new Connection(clusterUrl, 'singleGossip')
|
|
|
|
const programId = new PublicKey(IDS[cluster].mango_program_id)
|
|
|
|
const dexProgramId = new PublicKey(IDS[cluster].dex_program_id)
|
|
|
|
const mangoGroupPk = new PublicKey(IDS[cluster].mango_groups['BTC_ETH_USDT'].mango_group_pk)
|
|
|
|
|
2021-04-07 17:54:06 -07:00
|
|
|
const keyPairPath = process.env.KEYPAIR || homedir() + '/.config/solana/id2.json'
|
2021-03-09 05:32:07 -08:00
|
|
|
|
|
|
|
const payer = new Account(JSON.parse(fs.readFileSync(keyPairPath, 'utf-8')))
|
|
|
|
const mangoGroup = await client.getMangoGroup(connection, mangoGroupPk)
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Verify that balances in the vault matches total deposits + amount in all the open orders
|
|
|
|
*/
|
|
|
|
async function testVaultBalances() {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test what happens when you try to withdraw what's in your deposits, but some of your coins are still borrowed
|
|
|
|
*/
|
|
|
|
async function testWithdrawExcess() {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
async function testPlaceCancelOrder() {
|
|
|
|
|
|
|
|
const prices = await mangoGroup.getPrices(connection)
|
|
|
|
const marginAccounts = (await client.getMarginAccountsForOwner(connection, programId, mangoGroup, payer))
|
|
|
|
marginAccounts.sort(
|
|
|
|
(a, b) => (a.computeValue(mangoGroup, prices) > b.computeValue(mangoGroup, prices) ? -1 : 1)
|
|
|
|
)
|
|
|
|
let marginAccount = marginAccounts[0]
|
|
|
|
|
|
|
|
const market = await Market.load(connection, mangoGroup.spotMarkets[0], { skipPreflight: true, commitment: 'singleGossip'}, mangoGroup.dexProgramId)
|
|
|
|
console.log('placing order')
|
|
|
|
const txid = await client.placeOrder(connection, programId, mangoGroup, marginAccount, market, payer, 'buy', 48000, 0.0001)
|
|
|
|
console.log('order placed')
|
|
|
|
|
|
|
|
await sleep(5000)
|
|
|
|
marginAccount = await client.getMarginAccount(connection, marginAccount.publicKey, mangoGroup.dexProgramId)
|
|
|
|
const bids = await market.loadBids(connection)
|
|
|
|
const asks = await market.loadAsks(connection)
|
|
|
|
console.log('canceling orders')
|
|
|
|
await marginAccount.cancelAllOrdersByMarket(connection, client, programId, mangoGroup, market, bids, asks, payer)
|
|
|
|
console.log('orders canceled')
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
async function testGetOpenOrdersLatency() {
|
|
|
|
const t0 = getUnixTs()
|
|
|
|
const accounts = await client.getMarginAccountsForOwner(connection, programId, mangoGroup, payer)
|
|
|
|
const t1 = getUnixTs()
|
|
|
|
console.log(t1 - t0, accounts.length)
|
|
|
|
}
|
|
|
|
|
|
|
|
async function testDrainAccount() {
|
|
|
|
const prices = await mangoGroup.getPrices(connection)
|
|
|
|
const tokenWallets = (await Promise.all(
|
|
|
|
mangoGroup.tokens.map(
|
|
|
|
(mint) => findLargestTokenAccountForOwner(connection, payer.publicKey, mint).then(
|
|
|
|
(response) => response.publicKey
|
|
|
|
)
|
|
|
|
)
|
|
|
|
))
|
|
|
|
|
|
|
|
// load all markets
|
|
|
|
const markets = await Promise.all(mangoGroup.spotMarkets.map(
|
|
|
|
(pk) => Market.load(connection, pk, {skipPreflight: true, commitment: 'singleGossip'}, dexProgramId)
|
|
|
|
))
|
|
|
|
|
|
|
|
const marginAccountPk = new PublicKey("BrfYHWjU8UaWELfdR73qug1T5bWReg2tNJwUyHbzCgc2")
|
|
|
|
const ma = await client.getMarginAccount(connection, marginAccountPk, mangoGroup.dexProgramId)
|
|
|
|
while (true) {
|
|
|
|
try {
|
|
|
|
await drainAccount(client, connection, programId, mangoGroup, ma, markets, payer, prices, tokenWallets[NUM_TOKENS-1])
|
|
|
|
console.log('complete')
|
|
|
|
break
|
|
|
|
} catch (e) {
|
|
|
|
await sleep(1000)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
async function testBorrowLimits() {
|
|
|
|
console.log(mangoGroup.borrowLimits.map((b, i) => nativeToUi(b, mangoGroup.mintDecimals[i])))
|
|
|
|
}
|
|
|
|
|
2021-04-07 17:54:06 -07:00
|
|
|
async function placeMultipleOrders() {
|
|
|
|
const marginAccountPk = new PublicKey("85zCT5JsSmE5tgF42gPH6xxeVic5tXutAQDkSwfm9FN9")
|
|
|
|
const marginAccount = await client.getMarginAccount(connection, marginAccountPk, dexProgramId)
|
|
|
|
const market = await Market.load(connection, mangoGroup.spotMarkets[1], { skipPreflight: true, commitment: 'singleGossip'}, mangoGroup.dexProgramId)
|
|
|
|
|
2021-04-09 13:18:06 -07:00
|
|
|
for (let i = 0; i < 45; i++) {
|
2021-04-09 16:47:57 -07:00
|
|
|
const price = 990 - 10 * i
|
|
|
|
await client.placeAndSettle(connection, programId, mangoGroup, marginAccount, market, payer, "buy", price, 0.001)
|
2021-04-07 17:54:06 -07:00
|
|
|
await sleep(500)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
placeMultipleOrders()
|
|
|
|
// await testBorrowLimits()
|
2021-03-09 05:32:07 -08:00
|
|
|
// await testGetOpenOrdersLatency()
|
|
|
|
// await testPlaceCancelOrder()
|
|
|
|
// await testDrainAccount()
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
testAll()
|
2021-06-11 12:53:17 -07:00
|
|
|
// testServer()
|