ts client support for perps
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
parent
b7eda83e1b
commit
81f0f38188
|
@ -154,6 +154,9 @@ fn main() -> Result<(), anyhow::Error> {
|
||||||
|
|
||||||
let mango_client = Arc::new(MangoClient::new(cluster, commitment, payer, admin));
|
let mango_client = Arc::new(MangoClient::new(cluster, commitment, payer, admin));
|
||||||
|
|
||||||
|
log::info!("Program Id {}", &mango_client.program().id());
|
||||||
|
log::info!("Admin {}", &mango_client.admin.to_base58_string());
|
||||||
|
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
.enable_all()
|
.enable_all()
|
||||||
.build()
|
.build()
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
set -e pipefail
|
set -e pipefail
|
||||||
|
|
||||||
rg m43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD -l | xargs -I % sed -i '' 's/m43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD/5V2zCYCQkm4sZc3WctiwQEAzvfAiFxyjbwCvzQnmtmkM/g' %;
|
# rg m43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD -l | xargs -I % sed -i '' 's/m43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD/5V2zCYCQkm4sZc3WctiwQEAzvfAiFxyjbwCvzQnmtmkM/g' %;
|
||||||
|
|
||||||
WALLET_WITH_FUNDS=~/.config/solana/mango-devnet.json
|
WALLET_WITH_FUNDS=~/.config/solana/mango-devnet.json
|
||||||
PROGRAM_ID=5V2zCYCQkm4sZc3WctiwQEAzvfAiFxyjbwCvzQnmtmkM
|
PROGRAM_ID=5V2zCYCQkm4sZc3WctiwQEAzvfAiFxyjbwCvzQnmtmkM
|
||||||
|
|
|
@ -20,6 +20,7 @@ pub struct MarginTrade<'info> {
|
||||||
pub owner: Signer<'info>,
|
pub owner: Signer<'info>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: add loan fees
|
||||||
pub fn margin_trade<'key, 'accounts, 'remaining, 'info>(
|
pub fn margin_trade<'key, 'accounts, 'remaining, 'info>(
|
||||||
ctx: Context<'key, 'accounts, 'remaining, 'info, MarginTrade<'info>>,
|
ctx: Context<'key, 'accounts, 'remaining, 'info, MarginTrade<'info>>,
|
||||||
banks_len: usize,
|
banks_len: usize,
|
||||||
|
|
|
@ -2,6 +2,7 @@ use anchor_lang::prelude::*;
|
||||||
use bytemuck::{cast, cast_mut, cast_ref};
|
use bytemuck::{cast, cast_mut, cast_ref};
|
||||||
|
|
||||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
|
use static_assertions::const_assert_eq;
|
||||||
|
|
||||||
use crate::state::orderbook::bookside_iterator::BookSideIter;
|
use crate::state::orderbook::bookside_iterator::BookSideIter;
|
||||||
|
|
||||||
|
@ -44,6 +45,11 @@ pub struct BookSide {
|
||||||
pub leaf_count: usize,
|
pub leaf_count: usize,
|
||||||
pub nodes: [AnyNode; MAX_BOOK_NODES],
|
pub nodes: [AnyNode; MAX_BOOK_NODES],
|
||||||
}
|
}
|
||||||
|
const_assert_eq!(
|
||||||
|
std::mem::size_of::<BookSide>(),
|
||||||
|
8 + 8 * 2 + 4 + 4 + 8 + 88 * 1024
|
||||||
|
);
|
||||||
|
const_assert_eq!(std::mem::size_of::<BookSide>() % 8, 0);
|
||||||
|
|
||||||
impl BookSide {
|
impl BookSide {
|
||||||
/// Iterate over all entries in the book filtering out invalid orders
|
/// Iterate over all entries in the book filtering out invalid orders
|
||||||
|
|
|
@ -153,6 +153,8 @@ impl QueueHeader for EventQueueHeader {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type EventQueue = Queue<EventQueueHeader>;
|
pub type EventQueue = Queue<EventQueueHeader>;
|
||||||
|
const_assert_eq!(std::mem::size_of::<EventQueue>(), 8 * 3 + 512 * 200);
|
||||||
|
const_assert_eq!(std::mem::size_of::<EventQueue>() % 8, 0);
|
||||||
|
|
||||||
const EVENT_SIZE: usize = 200;
|
const EVENT_SIZE: usize = 200;
|
||||||
#[derive(Copy, Clone, Debug, Pod)]
|
#[derive(Copy, Clone, Debug, Pod)]
|
||||||
|
|
|
@ -92,7 +92,6 @@ impl<'info> LoadZeroCopy for AccountInfo<'info> {
|
||||||
|
|
||||||
pub fn fill16_from_str(name: String) -> Result<[u8; 16]> {
|
pub fn fill16_from_str(name: String) -> Result<[u8; 16]> {
|
||||||
let name_bytes = name.as_bytes();
|
let name_bytes = name.as_bytes();
|
||||||
msg!("{}", name);
|
|
||||||
require!(name_bytes.len() < 16, MangoError::SomeError);
|
require!(name_bytes.len() < 16, MangoError::SomeError);
|
||||||
let mut name_ = [0u8; 16];
|
let mut name_ = [0u8; 16];
|
||||||
name_[..name_bytes.len()].copy_from_slice(name_bytes);
|
name_[..name_bytes.len()].copy_from_slice(name_bytes);
|
||||||
|
@ -101,7 +100,6 @@ pub fn fill16_from_str(name: String) -> Result<[u8; 16]> {
|
||||||
|
|
||||||
pub fn fill32_from_str(name: String) -> Result<[u8; 32]> {
|
pub fn fill32_from_str(name: String) -> Result<[u8; 32]> {
|
||||||
let name_bytes = name.as_bytes();
|
let name_bytes = name.as_bytes();
|
||||||
msg!("{}", name);
|
|
||||||
require!(name_bytes.len() < 32, MangoError::SomeError);
|
require!(name_bytes.len() < 32, MangoError::SomeError);
|
||||||
let mut name_ = [0u8; 32];
|
let mut name_ = [0u8; 32];
|
||||||
name_[..name_bytes.len()].copy_from_slice(name_bytes);
|
name_[..name_bytes.len()].copy_from_slice(name_bytes);
|
||||||
|
|
|
@ -151,10 +151,10 @@ async fn test_perp() -> Result<(), BanksClientError> {
|
||||||
quote_token_index: tokens[1].index,
|
quote_token_index: tokens[1].index,
|
||||||
quote_lot_size: 10,
|
quote_lot_size: 10,
|
||||||
base_lot_size: 100,
|
base_lot_size: 100,
|
||||||
init_asset_weight: 0.95,
|
|
||||||
maint_asset_weight: 0.975,
|
maint_asset_weight: 0.975,
|
||||||
init_liab_weight: 1.05,
|
init_asset_weight: 0.95,
|
||||||
maint_liab_weight: 1.025,
|
maint_liab_weight: 1.025,
|
||||||
|
init_liab_weight: 1.05,
|
||||||
liquidation_fee: 0.012,
|
liquidation_fee: 0.012,
|
||||||
maker_fee: 0.0002,
|
maker_fee: 0.0002,
|
||||||
taker_fee: 0.000,
|
taker_fee: 0.000,
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { PublicKey } from '@solana/web3.js';
|
import { PublicKey } from '@solana/web3.js';
|
||||||
import { MangoClient } from '../client';
|
import { MangoClient } from '../client';
|
||||||
import { Bank } from './bank';
|
import { Bank } from './bank';
|
||||||
|
import { PerpMarket } from './perp';
|
||||||
import { Serum3Market } from './serum3';
|
import { Serum3Market } from './serum3';
|
||||||
|
|
||||||
export class Group {
|
export class Group {
|
||||||
static from(publicKey: PublicKey, obj: { admin: PublicKey }): Group {
|
static from(publicKey: PublicKey, obj: { admin: PublicKey }): Group {
|
||||||
return new Group(publicKey, obj.admin, new Map(), new Map());
|
return new Group(publicKey, obj.admin, new Map(), new Map(), new Map());
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -13,6 +14,7 @@ export class Group {
|
||||||
public admin: PublicKey,
|
public admin: PublicKey,
|
||||||
public banksMap: Map<string, Bank>,
|
public banksMap: Map<string, Bank>,
|
||||||
public serum3MarketsMap: Map<string, Serum3Market>,
|
public serum3MarketsMap: Map<string, Serum3Market>,
|
||||||
|
public perpMarketsMap: Map<string, PerpMarket>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public findBank(tokenIndex: number): Bank | undefined {
|
public findBank(tokenIndex: number): Bank | undefined {
|
||||||
|
@ -24,6 +26,7 @@ export class Group {
|
||||||
public async reload(client: MangoClient) {
|
public async reload(client: MangoClient) {
|
||||||
await this.reloadBanks(client);
|
await this.reloadBanks(client);
|
||||||
await this.reloadSerum3Markets(client);
|
await this.reloadSerum3Markets(client);
|
||||||
|
await this.reloadPerpMarkets(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async reloadBanks(client: MangoClient) {
|
public async reloadBanks(client: MangoClient) {
|
||||||
|
@ -37,4 +40,11 @@ export class Group {
|
||||||
serum3Markets.map((serum3Market) => [serum3Market.name, serum3Market]),
|
serum3Markets.map((serum3Market) => [serum3Market.name, serum3Market]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async reloadPerpMarkets(client: MangoClient) {
|
||||||
|
const perpMarkets = await client.perpGetMarket(this);
|
||||||
|
this.perpMarketsMap = new Map(
|
||||||
|
perpMarkets.map((perpMarket) => [perpMarket.name, perpMarket]),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { BN } from '@project-serum/anchor';
|
||||||
import { utf8 } from '@project-serum/anchor/dist/cjs/utils/bytes';
|
import { utf8 } from '@project-serum/anchor/dist/cjs/utils/bytes';
|
||||||
import { PublicKey } from '@solana/web3.js';
|
import { PublicKey } from '@solana/web3.js';
|
||||||
import { MangoClient } from '../client';
|
import { MangoClient } from '../client';
|
||||||
|
@ -6,6 +7,7 @@ import { I80F48, I80F48Dto } from './I80F48';
|
||||||
export class MangoAccount {
|
export class MangoAccount {
|
||||||
public tokens: TokenAccount[];
|
public tokens: TokenAccount[];
|
||||||
public serum3: Serum3Account[];
|
public serum3: Serum3Account[];
|
||||||
|
public perps: PerpAccount[];
|
||||||
public name: string;
|
public name: string;
|
||||||
|
|
||||||
static from(
|
static from(
|
||||||
|
@ -33,7 +35,7 @@ export class MangoAccount {
|
||||||
obj.delegate,
|
obj.delegate,
|
||||||
obj.tokens as { values: TokenAccountDto[] },
|
obj.tokens as { values: TokenAccountDto[] },
|
||||||
obj.serum3 as { values: Serum3AccountDto[] },
|
obj.serum3 as { values: Serum3AccountDto[] },
|
||||||
obj.perps,
|
obj.perps as { accounts: PerpAccountDto[] },
|
||||||
obj.beingLiquidated,
|
obj.beingLiquidated,
|
||||||
obj.isBankrupt,
|
obj.isBankrupt,
|
||||||
obj.accountNum,
|
obj.accountNum,
|
||||||
|
@ -50,7 +52,7 @@ export class MangoAccount {
|
||||||
public delegate: PublicKey,
|
public delegate: PublicKey,
|
||||||
tokens: { values: TokenAccountDto[] },
|
tokens: { values: TokenAccountDto[] },
|
||||||
serum3: { values: Serum3AccountDto[] },
|
serum3: { values: Serum3AccountDto[] },
|
||||||
perps: unknown,
|
perps: { accounts: PerpAccountDto[] },
|
||||||
beingLiquidated: number,
|
beingLiquidated: number,
|
||||||
isBankrupt: number,
|
isBankrupt: number,
|
||||||
accountNum: number,
|
accountNum: number,
|
||||||
|
@ -60,6 +62,7 @@ export class MangoAccount {
|
||||||
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];
|
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];
|
||||||
this.tokens = tokens.values.map((dto) => TokenAccount.from(dto));
|
this.tokens = tokens.values.map((dto) => TokenAccount.from(dto));
|
||||||
this.serum3 = serum3.values.map((dto) => Serum3Account.from(dto));
|
this.serum3 = serum3.values.map((dto) => Serum3Account.from(dto));
|
||||||
|
this.perps = perps.accounts.map((dto) => PerpAccount.from(dto));
|
||||||
}
|
}
|
||||||
|
|
||||||
async reload(client: MangoClient) {
|
async reload(client: MangoClient) {
|
||||||
|
@ -83,9 +86,40 @@ export class MangoAccount {
|
||||||
const ta = this.findToken(bank.tokenIndex);
|
const ta = this.findToken(bank.tokenIndex);
|
||||||
return bank.borrowIndex.mul(ta?.indexedValue!);
|
return bank.borrowIndex.mul(ta?.indexedValue!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return (
|
||||||
|
'tokens:' +
|
||||||
|
JSON.stringify(
|
||||||
|
this.tokens.filter(
|
||||||
|
(token) => token.tokenIndex != TokenAccount.TokenIndexUnset,
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
4,
|
||||||
|
) +
|
||||||
|
'\nserum:' +
|
||||||
|
JSON.stringify(
|
||||||
|
this.serum3.filter(
|
||||||
|
(serum3) =>
|
||||||
|
serum3.marketIndex != Serum3Account.Serum3MarketIndexUnset,
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
4,
|
||||||
|
) +
|
||||||
|
'\nperps:' +
|
||||||
|
JSON.stringify(
|
||||||
|
this.perps.filter(
|
||||||
|
(perp) => perp.marketIndex != PerpAccount.PerpMarketIndexUnset,
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
4,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TokenAccount {
|
export class TokenAccount {
|
||||||
|
static TokenIndexUnset: number = 65535;
|
||||||
static from(dto: TokenAccountDto) {
|
static from(dto: TokenAccountDto) {
|
||||||
return new TokenAccount(
|
return new TokenAccount(
|
||||||
I80F48.from(dto.indexedValue),
|
I80F48.from(dto.indexedValue),
|
||||||
|
@ -138,3 +172,41 @@ export class Serum3AccountDto {
|
||||||
public reserved: number[],
|
public reserved: number[],
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class PerpAccount {
|
||||||
|
static PerpMarketIndexUnset = 65535;
|
||||||
|
static from(dto: PerpAccountDto) {
|
||||||
|
return new PerpAccount(
|
||||||
|
dto.marketIndex,
|
||||||
|
dto.basePositionLots.toNumber(),
|
||||||
|
dto.quotePositionNative.val.toNumber(),
|
||||||
|
dto.bidsBaseLots.toNumber(),
|
||||||
|
dto.asksBaseLots.toNumber(),
|
||||||
|
dto.takerBaseLots.toNumber(),
|
||||||
|
dto.takerQuoteLots.toNumber(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public marketIndex: number,
|
||||||
|
public basePositionLots: number,
|
||||||
|
public quotePositionNative: number,
|
||||||
|
public bidsBaseLots: number,
|
||||||
|
public asksBaseLots: number,
|
||||||
|
public takerBaseLots: number,
|
||||||
|
public takerQuoteLots: number,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PerpAccountDto {
|
||||||
|
constructor(
|
||||||
|
public marketIndex: number,
|
||||||
|
public reserved: [],
|
||||||
|
public basePositionLots: BN,
|
||||||
|
public quotePositionNative: { val: BN },
|
||||||
|
public bidsBaseLots: BN,
|
||||||
|
public asksBaseLots: BN,
|
||||||
|
public takerBaseLots: BN,
|
||||||
|
public takerQuoteLots: BN,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
import { BN } from '@project-serum/anchor';
|
||||||
|
import { utf8 } from '@project-serum/anchor/dist/cjs/utils/bytes';
|
||||||
|
import { PublicKey } from '@solana/web3.js';
|
||||||
|
import { I80F48, I80F48Dto } from './I80F48';
|
||||||
|
|
||||||
|
export class PerpMarket {
|
||||||
|
public name: string;
|
||||||
|
public quoteLotSize: number;
|
||||||
|
public baseLotSize: number;
|
||||||
|
public maintAssetWeight: I80F48;
|
||||||
|
public initAssetWeight: I80F48;
|
||||||
|
public maintLiabWeight: I80F48;
|
||||||
|
public initLiabWeight: I80F48;
|
||||||
|
public liquidationFee: I80F48;
|
||||||
|
public makerFee: I80F48;
|
||||||
|
public takerFee: I80F48;
|
||||||
|
public openInterest: number;
|
||||||
|
public seqNum: number;
|
||||||
|
public feesAccrued: I80F48;
|
||||||
|
|
||||||
|
static from(
|
||||||
|
publicKey: PublicKey,
|
||||||
|
obj: {
|
||||||
|
name: number[];
|
||||||
|
group: PublicKey;
|
||||||
|
oracle: PublicKey;
|
||||||
|
bids: PublicKey;
|
||||||
|
asks: PublicKey;
|
||||||
|
eventQueue: PublicKey;
|
||||||
|
quoteLotSize: BN;
|
||||||
|
baseLotSize: BN;
|
||||||
|
maintAssetWeight: I80F48Dto;
|
||||||
|
initAssetWeight: I80F48Dto;
|
||||||
|
maintLiabWeight: I80F48Dto;
|
||||||
|
initLiabWeight: I80F48Dto;
|
||||||
|
liquidationFee: I80F48Dto;
|
||||||
|
makerFee: I80F48Dto;
|
||||||
|
takerFee: I80F48Dto;
|
||||||
|
openInterest: BN;
|
||||||
|
seqNum: any; // TODO: ts complains that this is unknown for whatever reason
|
||||||
|
feesAccrued: I80F48Dto;
|
||||||
|
bump: number;
|
||||||
|
reserved: number[];
|
||||||
|
perpMarketIndex: number;
|
||||||
|
baseTokenIndex: number;
|
||||||
|
quoteTokenIndex: number;
|
||||||
|
},
|
||||||
|
): PerpMarket {
|
||||||
|
return new PerpMarket(
|
||||||
|
publicKey,
|
||||||
|
obj.name,
|
||||||
|
obj.group,
|
||||||
|
obj.oracle,
|
||||||
|
obj.bids,
|
||||||
|
obj.asks,
|
||||||
|
obj.eventQueue,
|
||||||
|
obj.quoteLotSize,
|
||||||
|
obj.baseLotSize,
|
||||||
|
obj.maintAssetWeight,
|
||||||
|
obj.initAssetWeight,
|
||||||
|
obj.maintLiabWeight,
|
||||||
|
obj.initLiabWeight,
|
||||||
|
obj.liquidationFee,
|
||||||
|
obj.makerFee,
|
||||||
|
obj.takerFee,
|
||||||
|
obj.openInterest,
|
||||||
|
obj.seqNum,
|
||||||
|
obj.feesAccrued,
|
||||||
|
obj.bump,
|
||||||
|
obj.reserved,
|
||||||
|
obj.perpMarketIndex,
|
||||||
|
obj.baseTokenIndex,
|
||||||
|
obj.quoteTokenIndex,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public publicKey: PublicKey,
|
||||||
|
name: number[],
|
||||||
|
public group: PublicKey,
|
||||||
|
public oracle: PublicKey,
|
||||||
|
public bids: PublicKey,
|
||||||
|
public asks: PublicKey,
|
||||||
|
public eventQueue: PublicKey,
|
||||||
|
quoteLotSize: BN,
|
||||||
|
baseLotSize: BN,
|
||||||
|
maintAssetWeight: I80F48Dto,
|
||||||
|
initAssetWeight: I80F48Dto,
|
||||||
|
maintLiabWeight: I80F48Dto,
|
||||||
|
initLiabWeight: I80F48Dto,
|
||||||
|
liquidationFee: I80F48Dto,
|
||||||
|
makerFee: I80F48Dto,
|
||||||
|
takerFee: I80F48Dto,
|
||||||
|
openInterest: BN,
|
||||||
|
seqNum: BN,
|
||||||
|
feesAccrued: I80F48Dto,
|
||||||
|
bump: number,
|
||||||
|
reserved: number[],
|
||||||
|
public perpMarketIndex: number,
|
||||||
|
public baseTokenIndex: number,
|
||||||
|
public quoteTokenIndex: number,
|
||||||
|
) {
|
||||||
|
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];
|
||||||
|
this.quoteLotSize = quoteLotSize.toNumber();
|
||||||
|
this.baseLotSize = baseLotSize.toNumber();
|
||||||
|
this.maintAssetWeight = I80F48.from(maintAssetWeight);
|
||||||
|
this.initAssetWeight = I80F48.from(initAssetWeight);
|
||||||
|
this.maintLiabWeight = I80F48.from(maintLiabWeight);
|
||||||
|
this.initLiabWeight = I80F48.from(initLiabWeight);
|
||||||
|
this.liquidationFee = I80F48.from(liquidationFee);
|
||||||
|
this.makerFee = I80F48.from(makerFee);
|
||||||
|
this.takerFee = I80F48.from(takerFee);
|
||||||
|
this.openInterest = openInterest.toNumber();
|
||||||
|
this.seqNum = seqNum.toNumber();
|
||||||
|
this.feesAccrued = I80F48.from(feesAccrued);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Side {
|
||||||
|
static bid = { bid: {} };
|
||||||
|
static ask = { ask: {} };
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OrderType {
|
||||||
|
static limit = { limit: {} };
|
||||||
|
static immediateOrCancel = { immediateorcancel: {} };
|
||||||
|
static postOnly = { postonly: {} };
|
||||||
|
static market = { market: {} };
|
||||||
|
static postOnlySlide = { postonlyslide: {} };
|
||||||
|
}
|
|
@ -4,8 +4,10 @@ import { Order } from '@project-serum/serum/lib/market';
|
||||||
import * as spl from '@solana/spl-token';
|
import * as spl from '@solana/spl-token';
|
||||||
import {
|
import {
|
||||||
AccountMeta,
|
AccountMeta,
|
||||||
|
Keypair,
|
||||||
MemcmpFilter,
|
MemcmpFilter,
|
||||||
PublicKey,
|
PublicKey,
|
||||||
|
SystemProgram,
|
||||||
SYSVAR_RENT_PUBKEY,
|
SYSVAR_RENT_PUBKEY,
|
||||||
TransactionSignature,
|
TransactionSignature,
|
||||||
} from '@solana/web3.js';
|
} from '@solana/web3.js';
|
||||||
|
@ -15,6 +17,7 @@ import { Group } from './accounts/group';
|
||||||
import { I80F48 } from './accounts/I80F48';
|
import { I80F48 } from './accounts/I80F48';
|
||||||
import { MangoAccount } from './accounts/mangoAccount';
|
import { MangoAccount } from './accounts/mangoAccount';
|
||||||
import { StubOracle } from './accounts/oracle';
|
import { StubOracle } from './accounts/oracle';
|
||||||
|
import { OrderType, PerpMarket, Side } from './accounts/perp';
|
||||||
import {
|
import {
|
||||||
Serum3Market,
|
Serum3Market,
|
||||||
Serum3OrderType,
|
Serum3OrderType,
|
||||||
|
@ -621,6 +624,175 @@ export class MangoClient {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// perps
|
||||||
|
|
||||||
|
async perpCreateMarket(
|
||||||
|
group: Group,
|
||||||
|
oraclePk: PublicKey,
|
||||||
|
perpMarketIndex: number,
|
||||||
|
name: string,
|
||||||
|
baseTokenIndex: number,
|
||||||
|
quoteTokenIndex: number,
|
||||||
|
quoteLotSize: number,
|
||||||
|
baseLotSize: number,
|
||||||
|
maintAssetWeight: number,
|
||||||
|
initAssetWeight: number,
|
||||||
|
maintLiabWeight: number,
|
||||||
|
initLiabWeight: number,
|
||||||
|
liquidationFee: number,
|
||||||
|
makerFee: number,
|
||||||
|
takerFee: number,
|
||||||
|
): Promise<TransactionSignature> {
|
||||||
|
const bids = new Keypair();
|
||||||
|
const asks = new Keypair();
|
||||||
|
const eventQueue = new Keypair();
|
||||||
|
|
||||||
|
console.log(this.program.provider.wallet.publicKey.toBase58());
|
||||||
|
|
||||||
|
return await this.program.methods
|
||||||
|
.perpCreateMarket(
|
||||||
|
perpMarketIndex,
|
||||||
|
name,
|
||||||
|
baseTokenIndex,
|
||||||
|
quoteTokenIndex,
|
||||||
|
new BN(quoteLotSize),
|
||||||
|
new BN(baseLotSize),
|
||||||
|
maintAssetWeight,
|
||||||
|
initAssetWeight,
|
||||||
|
maintLiabWeight,
|
||||||
|
initLiabWeight,
|
||||||
|
liquidationFee,
|
||||||
|
makerFee,
|
||||||
|
takerFee,
|
||||||
|
)
|
||||||
|
.accounts({
|
||||||
|
group: group.publicKey,
|
||||||
|
admin: this.program.provider.wallet.publicKey,
|
||||||
|
oracle: oraclePk,
|
||||||
|
bids: bids.publicKey,
|
||||||
|
asks: asks.publicKey,
|
||||||
|
eventQueue: eventQueue.publicKey,
|
||||||
|
payer: this.program.provider.wallet.publicKey,
|
||||||
|
})
|
||||||
|
.preInstructions([
|
||||||
|
SystemProgram.createAccount({
|
||||||
|
programId: this.program.programId,
|
||||||
|
space: 8 + 90152,
|
||||||
|
lamports:
|
||||||
|
await this.program.provider.connection.getMinimumBalanceForRentExemption(
|
||||||
|
90160,
|
||||||
|
),
|
||||||
|
fromPubkey: this.program.provider.wallet.publicKey,
|
||||||
|
newAccountPubkey: bids.publicKey,
|
||||||
|
}),
|
||||||
|
SystemProgram.createAccount({
|
||||||
|
programId: this.program.programId,
|
||||||
|
space: 8 + 90152,
|
||||||
|
lamports:
|
||||||
|
await this.program.provider.connection.getMinimumBalanceForRentExemption(
|
||||||
|
90160,
|
||||||
|
),
|
||||||
|
fromPubkey: this.program.provider.wallet.publicKey,
|
||||||
|
newAccountPubkey: asks.publicKey,
|
||||||
|
}),
|
||||||
|
SystemProgram.createAccount({
|
||||||
|
programId: this.program.programId,
|
||||||
|
space: 8 + 102424,
|
||||||
|
lamports:
|
||||||
|
await this.program.provider.connection.getMinimumBalanceForRentExemption(
|
||||||
|
102432,
|
||||||
|
),
|
||||||
|
fromPubkey: this.program.provider.wallet.publicKey,
|
||||||
|
newAccountPubkey: eventQueue.publicKey,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
.signers([bids, asks, eventQueue])
|
||||||
|
.rpc();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async perpGetMarket(
|
||||||
|
group: Group,
|
||||||
|
baseTokenIndex?: number,
|
||||||
|
quoteTokenIndex?: number,
|
||||||
|
): Promise<PerpMarket[]> {
|
||||||
|
const bumpfbuf = Buffer.alloc(1);
|
||||||
|
bumpfbuf.writeUInt8(255);
|
||||||
|
|
||||||
|
const filters: MemcmpFilter[] = [
|
||||||
|
{
|
||||||
|
memcmp: {
|
||||||
|
bytes: group.publicKey.toBase58(),
|
||||||
|
offset: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (baseTokenIndex) {
|
||||||
|
const bbuf = Buffer.alloc(2);
|
||||||
|
bbuf.writeUInt16LE(baseTokenIndex);
|
||||||
|
filters.push({
|
||||||
|
memcmp: {
|
||||||
|
bytes: bs58.encode(bbuf),
|
||||||
|
offset: 348,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quoteTokenIndex) {
|
||||||
|
const qbuf = Buffer.alloc(2);
|
||||||
|
qbuf.writeUInt16LE(quoteTokenIndex);
|
||||||
|
filters.push({
|
||||||
|
memcmp: {
|
||||||
|
bytes: bs58.encode(qbuf),
|
||||||
|
offset: 350,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await this.program.account.perpMarket.all(filters)).map((tuple) =>
|
||||||
|
PerpMarket.from(tuple.publicKey, tuple.account),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async perpPlaceOrder(
|
||||||
|
group: Group,
|
||||||
|
mangoAccount: MangoAccount,
|
||||||
|
perpMarketName: string,
|
||||||
|
side: Side,
|
||||||
|
priceLots: number,
|
||||||
|
maxBaseLots: number,
|
||||||
|
maxQuoteLots: number,
|
||||||
|
clientOrderId: number,
|
||||||
|
orderType: OrderType,
|
||||||
|
expiryTimestamp: number,
|
||||||
|
limit: number,
|
||||||
|
) {
|
||||||
|
const perpMarket = group.perpMarketsMap.get(perpMarketName)!;
|
||||||
|
|
||||||
|
await this.program.methods
|
||||||
|
.perpPlaceOrder(
|
||||||
|
side,
|
||||||
|
new BN((priceLots * perpMarket.baseLotSize) / perpMarket.quoteLotSize),
|
||||||
|
new BN(maxBaseLots),
|
||||||
|
new BN(maxQuoteLots),
|
||||||
|
new BN(clientOrderId),
|
||||||
|
orderType,
|
||||||
|
new BN(expiryTimestamp),
|
||||||
|
limit,
|
||||||
|
)
|
||||||
|
.accounts({
|
||||||
|
group: group.publicKey,
|
||||||
|
account: mangoAccount.publicKey,
|
||||||
|
perpMarket: perpMarket.publicKey,
|
||||||
|
asks: perpMarket.asks,
|
||||||
|
bids: perpMarket.bids,
|
||||||
|
eventQueue: perpMarket.eventQueue,
|
||||||
|
oracle: perpMarket.oracle,
|
||||||
|
owner: this.program.provider.wallet.publicKey,
|
||||||
|
})
|
||||||
|
.rpc();
|
||||||
|
}
|
||||||
|
|
||||||
/// static
|
/// static
|
||||||
|
|
||||||
static async connect(
|
static async connect(
|
||||||
|
@ -703,6 +875,16 @@ export class MangoClient {
|
||||||
.filter((serum3Account) => serum3Account.marketIndex !== 65535)
|
.filter((serum3Account) => serum3Account.marketIndex !== 65535)
|
||||||
.map((serum3Account) => serum3Account.openOrders),
|
.map((serum3Account) => serum3Account.openOrders),
|
||||||
);
|
);
|
||||||
|
healthRemainingAccounts.push(
|
||||||
|
...mangoAccount.perps
|
||||||
|
.filter((perp) => perp.marketIndex !== 65535)
|
||||||
|
.map(
|
||||||
|
(perp) =>
|
||||||
|
Array.from(group.perpMarketsMap.values()).filter(
|
||||||
|
(perpMarket) => perpMarket.perpMarketIndex === perp.marketIndex,
|
||||||
|
)[0].publicKey,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return healthRemainingAccounts;
|
return healthRemainingAccounts;
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -136,6 +136,36 @@ async function main() {
|
||||||
);
|
);
|
||||||
console.log(`...registerd serum3 market ${markets[0].publicKey}`);
|
console.log(`...registerd serum3 market ${markets[0].publicKey}`);
|
||||||
|
|
||||||
|
// register perp market
|
||||||
|
console.log(`Registering perp market...`);
|
||||||
|
try {
|
||||||
|
await client.perpCreateMarket(
|
||||||
|
group,
|
||||||
|
btcDevnetOracle,
|
||||||
|
0,
|
||||||
|
'BTC/USDC',
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
10,
|
||||||
|
100,
|
||||||
|
0.975,
|
||||||
|
0.95,
|
||||||
|
1.025,
|
||||||
|
1.05,
|
||||||
|
0.012,
|
||||||
|
0.0002,
|
||||||
|
0.0,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
const perpMarkets = await client.perpGetMarket(
|
||||||
|
group,
|
||||||
|
group.banksMap.get('BTC')?.tokenIndex,
|
||||||
|
group.banksMap.get('USDC')?.tokenIndex,
|
||||||
|
);
|
||||||
|
console.log(`...created perp market ${perpMarkets[0].publicKey}`);
|
||||||
|
|
||||||
process.exit();
|
process.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Provider, Wallet } from '@project-serum/anchor';
|
import { Provider, Wallet } from '@project-serum/anchor';
|
||||||
import { Connection, Keypair } from '@solana/web3.js';
|
import { Connection, Keypair } from '@solana/web3.js';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import { OrderType, Side } from '../accounts/perp';
|
||||||
import {
|
import {
|
||||||
Serum3OrderType,
|
Serum3OrderType,
|
||||||
Serum3SelfTradeBehavior,
|
Serum3SelfTradeBehavior,
|
||||||
|
@ -156,6 +157,46 @@ async function main() {
|
||||||
'BTC/USDC',
|
'BTC/USDC',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// perps
|
||||||
|
console.log(`Placing perp bid...`);
|
||||||
|
await client.perpPlaceOrder(
|
||||||
|
group,
|
||||||
|
mangoAccount,
|
||||||
|
'BTC/USDC',
|
||||||
|
Side.bid,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
65535,
|
||||||
|
65535,
|
||||||
|
OrderType.limit,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`Placing perp ask...`);
|
||||||
|
await client.perpPlaceOrder(
|
||||||
|
group,
|
||||||
|
mangoAccount,
|
||||||
|
'BTC/USDC',
|
||||||
|
Side.ask,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
65535,
|
||||||
|
65535,
|
||||||
|
OrderType.limit,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
// TODO: quotePositionNative might be buggy on program side, investigate...
|
||||||
|
console.log(
|
||||||
|
`Waiting for self trade to consume (note: make sure keeper crank is running)...`,
|
||||||
|
);
|
||||||
|
await mangoAccount.reload(client);
|
||||||
|
console.log(mangoAccount.toString());
|
||||||
|
}
|
||||||
|
|
||||||
process.exit();
|
process.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"declarationDir": "dist",
|
"declarationDir": "dist",
|
||||||
"declarationMap": true,
|
"declarationMap": true,
|
||||||
|
"noErrorTruncation": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"lib": [
|
"lib": [
|
||||||
"es2019"
|
"es2019"
|
||||||
|
|
Loading…
Reference in New Issue