ts client changes (#320)

* cleanup + small sync with program
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* Fixes from review

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* Update lock file

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* Fix tsc errors

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
microwavedcola1 2022-12-08 10:16:06 +01:00 committed by GitHub
parent 2a13cf9ac2
commit 7d9c3616af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 230 additions and 827 deletions

View File

@ -65,7 +65,7 @@ pub fn perp_settle_fees(ctx: Context<PerpSettleFees>, max_settle_amount: u64) ->
// Settle funding before settling any PnL
perp_position.settle_funding(&perp_market);
// Calculate PnL for each account
// Calculate PnL
let pnl = perp_position.pnl_for_price(&perp_market, oracle_price)?;
// Account perp position must have a loss to be able to settle against the fee account

View File

@ -368,6 +368,8 @@ pub fn serum3_place_order(
account.check_health_post(&health_cache, pre_health)?;
}
// TODO: enforce min_vault_to_deposits_ratio
Ok(())
}

View File

@ -376,6 +376,8 @@ impl HealthCache {
let entry_index = self.token_info_index(bank.token_index)?;
let mut entry = &mut self.token_infos[entry_index];
// Note: resetting the weights here assumes that the change has been applied to
// the passed in bank already
entry.init_asset_weight =
bank.scaled_init_asset_weight(entry.prices.asset(HealthType::Init));
entry.init_liab_weight = bank.scaled_init_liab_weight(entry.prices.liab(HealthType::Init));
@ -388,16 +390,6 @@ impl HealthCache {
Ok(())
}
/// Recomputes the dynamic init weights for the bank's current deposits/borrows.
pub fn recompute_token_weights(&mut self, bank: &Bank) -> Result<()> {
let entry_index = self.token_info_index(bank.token_index)?;
let mut entry = &mut self.token_infos[entry_index];
entry.init_asset_weight =
bank.scaled_init_asset_weight(entry.prices.asset(HealthType::Init));
entry.init_liab_weight = bank.scaled_init_liab_weight(entry.prices.liab(HealthType::Init));
Ok(())
}
/// Changes the cached user account token and serum balances.
///
/// WARNING: You must also call recompute_token_weights() after all bank

View File

@ -8,10 +8,14 @@ export const QUOTE_DECIMALS = 6;
export type TokenIndex = number & As<'token-index'>;
export type OracleConfig = {
export type OracleConfigDto = {
confFilter: I80F48Dto;
maxStalenessSlots: BN;
reserved: number[];
};
export type OracleConfig = {
confFilter: I80F48;
maxStalenessSlots: BN;
};
export type StablePriceModel = {
@ -34,10 +38,14 @@ export interface BankForHealth {
initLiabWeight: I80F48;
price: I80F48;
stablePriceModel: StablePriceModel;
scaledInitAssetWeight(): I80F48;
scaledInitLiabWeight(): I80F48;
}
export class Bank implements BankForHealth {
public name: string;
public oracleConfig: OracleConfig;
public depositIndex: I80F48;
public borrowIndex: I80F48;
public indexedDeposits: I80F48;
@ -70,7 +78,7 @@ export class Bank implements BankForHealth {
mint: PublicKey;
vault: PublicKey;
oracle: PublicKey;
oracleConfig: OracleConfig;
oracleConfig: OracleConfigDto;
stablePriceModel: StablePriceModel;
depositIndex: I80F48Dto;
borrowIndex: I80F48Dto;
@ -161,7 +169,7 @@ export class Bank implements BankForHealth {
public mint: PublicKey,
public vault: PublicKey,
public oracle: PublicKey,
oracleConfig: OracleConfig,
oracleConfig: OracleConfigDto,
public stablePriceModel: StablePriceModel,
depositIndex: I80F48Dto,
borrowIndex: I80F48Dto,
@ -195,10 +203,14 @@ export class Bank implements BankForHealth {
lastNetBorrowsWindowStartTs: BN,
netBorrowLimitPerWindowQuote: BN,
netBorrowsInWindow: BN,
borrowWeightScaleStartQuote: number,
depositWeightScaleStartQuote: number,
public borrowWeightScaleStartQuote: number,
public depositWeightScaleStartQuote: number,
) {
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];
this.oracleConfig = {
confFilter: I80F48.from(oracleConfig.confFilter),
maxStalenessSlots: oracleConfig.maxStalenessSlots,
} as OracleConfig;
this.depositIndex = I80F48.from(depositIndex);
this.borrowIndex = I80F48.from(borrowIndex);
this.indexedDeposits = I80F48.from(indexedDeposits);
@ -293,6 +305,28 @@ export class Bank implements BankForHealth {
);
}
scaledInitAssetWeight(): I80F48 {
const depositsQuote = this.nativeDeposits().mul(this.price);
if (
depositsQuote.lte(I80F48.fromNumber(this.depositWeightScaleStartQuote))
) {
return this.initAssetWeight;
}
return this.initAssetWeight.mul(
I80F48.fromNumber(this.depositWeightScaleStartQuote).div(depositsQuote),
);
}
scaledInitLiabWeight(): I80F48 {
const borrowsQuote = this.nativeBorrows().mul(this.price);
if (borrowsQuote.lte(I80F48.fromNumber(this.borrowWeightScaleStartQuote))) {
return this.initLiabWeight;
}
return this.initLiabWeight.mul(
borrowsQuote.div(I80F48.fromNumber(this.borrowWeightScaleStartQuote)),
);
}
get price(): I80F48 {
if (!this._price) {
throw new Error(

View File

@ -14,7 +14,7 @@ import {
} from '@solana/web3.js';
import BN from 'bn.js';
import { MangoClient } from '../client';
import { SERUM3_PROGRAM_ID } from '../constants';
import { OPENBOOK_PROGRAM_ID } from '../constants';
import { Id } from '../ids';
import { I80F48, ONE_I80F48 } from '../numbers/I80F48';
import { toNative, toNativeI80F48, toUiDecimals } from '../utils';
@ -225,7 +225,7 @@ export class Group {
client.program.provider.connection,
serum3Market.serumMarketExternal,
{ commitment: client.program.provider.connection.commitment },
SERUM3_PROGRAM_ID[client.cluster],
OPENBOOK_PROGRAM_ID[client.cluster],
),
),
);

View File

@ -23,6 +23,8 @@ function mockBankAndOracle(
initLiabWeight: I80F48.fromNumber(1 + initWeight),
price: I80F48.fromNumber(price),
stablePriceModel: { stablePrice: price } as StablePriceModel,
scaledInitAssetWeight: () => I80F48.fromNumber(1 - initWeight),
scaledInitLiabWeight: () => I80F48.fromNumber(1 + initWeight),
};
}

View File

@ -666,6 +666,8 @@ export class HealthCache {
function cacheAfterSwap(amount: I80F48): HealthCache {
const adjustedCache: HealthCache = _.cloneDeep(healthCacheClone);
// adjustedCache.logHealthCache('beforeSwap', adjustedCache);
// TODO: make a copy of the bank, apply amount, recompute weights,
// and set the new weights on the tokenInfos
adjustedCache.tokenInfos[sourceIndex].balanceNative.isub(amount);
adjustedCache.tokenInfos[targetIndex].balanceNative.iadd(
amount.mul(price),
@ -1062,9 +1064,9 @@ export class TokenInfo {
return new TokenInfo(
bank.tokenIndex,
bank.maintAssetWeight,
bank.initAssetWeight,
bank.scaledInitAssetWeight(),
bank.maintLiabWeight,
bank.initLiabWeight,
bank.scaledInitLiabWeight(),
new Prices(
bank.price,
I80F48.fromNumber(bank.stablePriceModel.stablePrice),

View File

@ -3,7 +3,7 @@ import { utf8 } from '@project-serum/anchor/dist/cjs/utils/bytes';
import { OpenOrders, Order, Orderbook } from '@project-serum/serum/lib/market';
import { AccountInfo, PublicKey, TransactionSignature } from '@solana/web3.js';
import { MangoClient } from '../client';
import { SERUM3_PROGRAM_ID } from '../constants';
import { OPENBOOK_PROGRAM_ID } from '../constants';
import { I80F48, I80F48Dto, ONE_I80F48, ZERO_I80F48 } from '../numbers/I80F48';
import { toNativeI80F48, toUiDecimals, toUiDecimalsForQuote } from '../utils';
import { Bank, TokenIndex } from './bank';
@ -118,7 +118,7 @@ export class MangoAccount {
const oo = OpenOrders.fromAccountInfo(
serum3Active[i].openOrders,
ai,
SERUM3_PROGRAM_ID[client.cluster],
OPENBOOK_PROGRAM_ID[client.cluster],
);
return [serum3Active[i].marketIndex, oo];
}),
@ -421,6 +421,8 @@ export class MangoAccount {
/**
* The amount of given native token you can withdraw including borrows, considering all existing assets as collateral.
* @returns amount of given native token you can borrow, considering all existing assets as collateral, in native token
*
* TODO: take into account net_borrow_limit and min_vault_to_deposits_ratio
*/
public getMaxWithdrawWithBorrowForToken(
group: Group,
@ -563,7 +565,7 @@ export class MangoAccount {
return OpenOrders.fromAccountInfo(
this.serum3[index].openOrders,
acc,
SERUM3_PROGRAM_ID[client.cluster],
OPENBOOK_PROGRAM_ID[client.cluster],
);
});
}
@ -1247,24 +1249,24 @@ export class PerpPosition {
);
}
// TODO FUTURE: double check with program side code that this is in sycn with latest changes in program
public getEntryPrice(perpMarket: PerpMarket): BN {
if (this.basePositionLots.eq(new BN(0))) {
return new BN(0);
}
return this.quoteEntryNative
.div(this.basePositionLots.mul(perpMarket.baseLotSize))
.abs();
public getAverageEntryPriceUi(perpMarket: PerpMarket): number {
return perpMarket.priceLotsToUi(
new BN(this.avgEntryPricePerBaseLot / perpMarket.baseLotSize.toNumber()),
);
}
// TODO FUTURE: double check with program side code that this is in sycn with latest changes in program
public getBreakEvenPrice(perpMarket: PerpMarket): BN {
public getBreakEvenPriceUi(perpMarket: PerpMarket): number {
if (this.basePositionLots.eq(new BN(0))) {
return new BN(0);
return 0;
}
return this.quoteRunningNative
.div(this.basePositionLots.mul(perpMarket.baseLotSize))
.abs();
return perpMarket.priceLotsToUi(
new BN(
this.quoteRunningNative
.neg()
.div(this.basePositionLots.mul(perpMarket.baseLotSize))
.toNumber(),
),
);
}
public getPnl(perpMarket: PerpMarket): I80F48 {

View File

@ -7,6 +7,7 @@ import { I80F48, I80F48Dto, ZERO_I80F48 } from '../numbers/I80F48';
import { As, toNative, U64_MAX_BN } from '../utils';
import {
OracleConfig,
OracleConfigDto,
QUOTE_DECIMALS,
StablePriceModel,
TokenIndex,
@ -18,6 +19,7 @@ export type PerpMarketIndex = number & As<'perp-market-index'>;
export class PerpMarket {
public name: string;
public oracleConfig: OracleConfig;
public maintAssetWeight: I80F48;
public initAssetWeight: I80F48;
public maintLiabWeight: I80F48;
@ -55,7 +57,7 @@ export class PerpMarket {
asks: PublicKey;
eventQueue: PublicKey;
oracle: PublicKey;
oracleConfig: OracleConfig;
oracleConfig: OracleConfigDto;
stablePriceModel: StablePriceModel;
quoteLotSize: BN;
baseLotSize: BN;
@ -142,7 +144,7 @@ export class PerpMarket {
public asks: PublicKey,
public eventQueue: PublicKey,
public oracle: PublicKey,
oracleConfig: OracleConfig,
oracleConfig: OracleConfigDto,
public stablePriceModel: StablePriceModel,
public quoteLotSize: BN,
public baseLotSize: BN,
@ -172,6 +174,10 @@ export class PerpMarket {
settlePnlLimitWindowSizeTs: BN,
) {
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];
this.oracleConfig = {
confFilter: I80F48.from(oracleConfig.confFilter),
maxStalenessSlots: oracleConfig.maxStalenessSlots,
} as OracleConfig;
this.maintAssetWeight = I80F48.from(maintAssetWeight);
this.initAssetWeight = I80F48.from(initAssetWeight);
this.maintLiabWeight = I80F48.from(maintLiabWeight);
@ -703,13 +709,13 @@ export class BookSide {
static toInnerNode(client: MangoClient, data: [number]): InnerNode {
return (client.program as any)._coder.types.typeLayouts
.get('InnerNode')
.decode(Buffer.from([BookSide.INNER_NODE_TAG, 0, 0, 0].concat(data)));
.decode(Buffer.from([BookSide.INNER_NODE_TAG].concat(data)));
}
static toLeafNode(client: MangoClient, data: [number]): LeafNode {
return LeafNode.from(
(client.program as any)._coder.types.typeLayouts
.get('LeafNode')
.decode(Buffer.from([BookSide.LEAF_NODE_TAG, 0, 0, 0].concat(data))),
.decode(Buffer.from([BookSide.LEAF_NODE_TAG].concat(data))),
);
}
}

View File

@ -3,7 +3,7 @@ import { Market, Orderbook } from '@project-serum/serum/lib/market';
import { Cluster, PublicKey } from '@solana/web3.js';
import BN from 'bn.js';
import { MangoClient } from '../client';
import { SERUM3_PROGRAM_ID } from '../constants';
import { OPENBOOK_PROGRAM_ID } from '../constants';
import { MAX_I80F48, ONE_I80F48, ZERO_I80F48 } from '../numbers/I80F48';
import { As } from '../utils';
import { TokenIndex } from './bank';
@ -163,6 +163,6 @@ export async function generateSerum3MarketExternalVaultSignerAddress(
8,
),
],
SERUM3_PROGRAM_ID[cluster],
OPENBOOK_PROGRAM_ID[cluster],
);
}

View File

@ -48,7 +48,7 @@ import {
Serum3SelfTradeBehavior,
Serum3Side,
} from './accounts/serum3';
import { SERUM3_PROGRAM_ID } from './constants';
import { OPENBOOK_PROGRAM_ID } from './constants';
import { Id } from './ids';
import { IDL, MangoV4 } from './mango_v4';
import { I80F48 } from './numbers/I80F48';
@ -991,7 +991,7 @@ export class MangoClient {
.accounts({
group: group.publicKey,
admin: (this.program.provider as AnchorProvider).wallet.publicKey,
serumProgram: SERUM3_PROGRAM_ID[this.cluster],
serumProgram: OPENBOOK_PROGRAM_ID[this.cluster],
serumMarketExternal: serum3MarketExternalPk,
baseBank: baseBank.publicKey,
quoteBank: quoteBank.publicKey,
@ -1203,7 +1203,7 @@ export class MangoClient {
openOrders: mangoAccount.getSerum3Account(serum3Market.marketIndex)
?.openOrders,
serumMarket: serum3Market.publicKey,
serumProgram: SERUM3_PROGRAM_ID[this.cluster],
serumProgram: OPENBOOK_PROGRAM_ID[this.cluster],
serumMarketExternal: serum3Market.serumMarketExternal,
marketBids: serum3MarketExternal.bidsAddress,
marketAsks: serum3MarketExternal.asksAddress,
@ -1256,7 +1256,7 @@ export class MangoClient {
openOrders: mangoAccount.getSerum3Account(serum3Market.marketIndex)
?.openOrders,
serumMarket: serum3Market.publicKey,
serumProgram: SERUM3_PROGRAM_ID[this.cluster],
serumProgram: OPENBOOK_PROGRAM_ID[this.cluster],
serumMarketExternal: serum3Market.serumMarketExternal,
marketBids: serum3MarketExternal.bidsAddress,
marketAsks: serum3MarketExternal.asksAddress,
@ -1301,7 +1301,7 @@ export class MangoClient {
openOrders: mangoAccount.getSerum3Account(serum3Market.marketIndex)
?.openOrders,
serumMarket: serum3Market.publicKey,
serumProgram: SERUM3_PROGRAM_ID[this.cluster],
serumProgram: OPENBOOK_PROGRAM_ID[this.cluster],
serumMarketExternal: serum3Market.serumMarketExternal,
marketBaseVault: serum3MarketExternal.decoded.baseVault,
marketQuoteVault: serum3MarketExternal.decoded.quoteVault,
@ -1350,7 +1350,7 @@ export class MangoClient {
openOrders: mangoAccount.getSerum3Account(serum3Market.marketIndex)
?.openOrders,
serumMarket: serum3Market.publicKey,
serumProgram: SERUM3_PROGRAM_ID[this.cluster],
serumProgram: OPENBOOK_PROGRAM_ID[this.cluster],
serumMarketExternal: serum3Market.serumMarketExternal,
marketBids: serum3MarketExternal.bidsAddress,
marketAsks: serum3MarketExternal.asksAddress,

View File

@ -1,9 +1,8 @@
import { PublicKey } from '@solana/web3.js';
export const SERUM3_PROGRAM_ID = {
testnet: new PublicKey('DESVgJVGajEgKGXhb6XmqDHGz3VjdgP7rEVESBgxmroY'),
devnet: new PublicKey('DESVgJVGajEgKGXhb6XmqDHGz3VjdgP7rEVESBgxmroY'),
'mainnet-beta': new PublicKey('9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin'),
export const OPENBOOK_PROGRAM_ID = {
devnet: new PublicKey('EoTcMgcDRTJVZDMZWBoU6rhYHZfkNTVEAfz3uUJRcYGj'),
'mainnet-beta': new PublicKey('srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX'),
};
export const MANGO_V4_ID = {

View File

@ -1,295 +0,0 @@
import { AnchorProvider, Wallet } from '@project-serum/anchor';
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import fs from 'fs';
import { MangoClient } from '../client';
import { MANGO_V4_ID } from '../constants';
const GROUP_NUM = Number(process.env.GROUP_NUM || 0);
// Reference
// https://explorer.solana.com/
// https://github.com/blockworks-foundation/mango-client-v3/blob/main/src/ids.json
const MAINNET_MINTS = new Map([
['USDC', 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'],
['USDT', 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB'],
['BTC', '9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E'], // Wrapped Bitcoin (Sollet)
['ETH', '7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs'], // Ether (Portal), will be treat as ETH due to higher liquidity
['soETH', '2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk'], // Wrapped Ethereum (Sollet), will be treated as soETH
['SOL', 'So11111111111111111111111111111111111111112'],
['mSOL', 'mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So'],
['MNGO', 'MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac'],
]);
// Reference
// https://pyth.network/price-feeds/
// https://switchboard.xyz/explorer
const MAINNET_ORACLES = new Map([
['USDT', '3vxLXJqLqF3JG5TCbYycbKWRBbCJQLxQmBGCkyqEEefL'],
['BTC', 'GVXRSBjFk6e6J3NbVPXohDJetcTjaeeuykUpbQF8UoMU'],
['ETH', 'JBu1AL4obBcCMqKBBxhpWCNUt136ijcuMZLFvTP7iWdB'],
['soETH', 'JBu1AL4obBcCMqKBBxhpWCNUt136ijcuMZLFvTP7iWdB'],
['SOL', 'H6ARHf6YXhGYeQfUzQNGk6rDNnLBQKrenN712K4AQJEG'],
['mSOL', 'E4v1BBgoso9s64TQvmyownAVJbhbEPGyzA3qn4n46qj9'],
['MNGO', '79wm3jjcPr6RaNQ4DGvP5KxG1mNd3gEBsg6FsNVFezK4'],
]);
const MIN_VAULT_TO_DEPOSITS_RATIO = 0.2;
const NET_BORROWS_WINDOW_SIZE_TS = 24 * 60 * 60;
const NET_BORROWS_LIMIT_NATIVE = 1 * Math.pow(10, 7) * Math.pow(10, 6);
async function createGroup() {
const admin = Keypair.fromSecretKey(
Buffer.from(
JSON.parse(fs.readFileSync(process.env.MB_PAYER_KEYPAIR!, 'utf-8')),
),
);
const options = AnchorProvider.defaultOptions();
const connection = new Connection(process.env.MB_CLUSTER_URL!, options);
const adminWallet = new Wallet(admin);
console.log(`Admin ${adminWallet.publicKey.toBase58()}`);
const adminProvider = new AnchorProvider(connection, adminWallet, options);
const client = await MangoClient.connect(
adminProvider,
'mainnet-beta',
MANGO_V4_ID['mainnet-beta'],
{
idsSource: 'get-program-accounts',
},
);
console.log(`Creating Group...`);
const insuranceMint = new PublicKey(MAINNET_MINTS.get('USDC')!);
await client.groupCreate(
GROUP_NUM,
true /* with intention */,
0 /* since spot and perp features are not finished */,
insuranceMint,
);
const group = await client.getGroupForCreator(admin.publicKey, GROUP_NUM);
console.log(`...registered group ${group.publicKey}`);
}
async function registerTokens() {
const admin = Keypair.fromSecretKey(
Buffer.from(
JSON.parse(fs.readFileSync(process.env.MB_PAYER_KEYPAIR!, 'utf-8')),
),
);
const options = AnchorProvider.defaultOptions();
const connection = new Connection(process.env.MB_CLUSTER_URL!, options);
const adminWallet = new Wallet(admin);
console.log(`Admin ${adminWallet.publicKey.toBase58()}`);
const adminProvider = new AnchorProvider(connection, adminWallet, options);
const client = await MangoClient.connect(
adminProvider,
'mainnet-beta',
MANGO_V4_ID['mainnet-beta'],
{
idsSource: 'get-program-accounts',
} /* idsjson service doesn't know about this group yet */,
);
const group = await client.getGroupForCreator(admin.publicKey, GROUP_NUM);
const defaultOracleConfig = {
confFilter: 0.1,
maxStalenessSlots: null,
};
// hoping that dynamic rate parameter adjustment would be enough to tune their rates to the markets needs
const defaultInterestRate = {
adjustmentFactor: 0.004, // rate parameters are chosen to be the same for all high asset weight tokens,
util0: 0.7,
rate0: 0.1,
util1: 0.85,
rate1: 0.2,
maxRate: 2.0,
};
console.log(`Creating USDC stub oracle...`);
const usdcMainnetMint = new PublicKey(MAINNET_MINTS.get('USDC')!);
await client.stubOracleCreate(group, usdcMainnetMint, 1.0);
const usdcMainnetOracle = (
await client.getStubOracle(group, usdcMainnetMint)
)[0];
console.log(`...created stub oracle ${usdcMainnetOracle.publicKey}`);
console.log(`Registering USDC...`);
await client.tokenRegister(
group,
usdcMainnetMint,
usdcMainnetOracle.publicKey,
defaultOracleConfig,
0, // insurance vault token should be the first to be registered
'USDC',
defaultInterestRate,
0.005, // 50 bps
0.0005, // 5 bps
1,
1,
1,
1,
0,
MIN_VAULT_TO_DEPOSITS_RATIO,
NET_BORROWS_WINDOW_SIZE_TS,
NET_BORROWS_LIMIT_NATIVE,
);
console.log(`Registering USDT...`);
const usdtMainnetMint = new PublicKey(MAINNET_MINTS.get('USDT')!);
const usdtMainnetOracle = new PublicKey(MAINNET_ORACLES.get('USDT')!);
await client.tokenRegister(
group,
usdtMainnetMint,
usdtMainnetOracle,
defaultOracleConfig,
1,
'USDT',
defaultInterestRate,
0.005,
0.0005,
0.95,
0.9,
1.05,
1.1,
0.025, // rule of thumb used - half of maintLiabWeight
MIN_VAULT_TO_DEPOSITS_RATIO,
NET_BORROWS_WINDOW_SIZE_TS,
NET_BORROWS_LIMIT_NATIVE,
);
console.log(`Registering BTC...`);
const btcMainnetMint = new PublicKey(MAINNET_MINTS.get('BTC')!);
const btcMainnetOracle = new PublicKey(MAINNET_ORACLES.get('BTC')!);
await client.tokenRegister(
group,
btcMainnetMint,
btcMainnetOracle,
defaultOracleConfig,
2,
'BTC',
defaultInterestRate,
0.005,
0.0005,
0.9,
0.8,
1.1,
1.2,
0.05,
MIN_VAULT_TO_DEPOSITS_RATIO,
NET_BORROWS_WINDOW_SIZE_TS,
NET_BORROWS_LIMIT_NATIVE,
);
console.log(`Registering ETH...`);
const ethMainnetMint = new PublicKey(MAINNET_MINTS.get('ETH')!);
const ethMainnetOracle = new PublicKey(MAINNET_ORACLES.get('ETH')!);
await client.tokenRegister(
group,
ethMainnetMint,
ethMainnetOracle,
defaultOracleConfig,
3,
'ETH',
defaultInterestRate,
0.005,
0.0005,
0.9,
0.8,
1.1,
1.2,
0.05,
MIN_VAULT_TO_DEPOSITS_RATIO,
NET_BORROWS_WINDOW_SIZE_TS,
NET_BORROWS_LIMIT_NATIVE,
);
console.log(`Registering soETH...`);
const soEthMainnetMint = new PublicKey(MAINNET_MINTS.get('soETH')!);
const soEthMainnetOracle = new PublicKey(MAINNET_ORACLES.get('soETH')!);
await client.tokenRegister(
group,
soEthMainnetMint,
soEthMainnetOracle,
defaultOracleConfig,
4,
'soETH',
defaultInterestRate,
0.005,
0.0005,
0.9,
0.8,
1.1,
1.2,
0.05,
MIN_VAULT_TO_DEPOSITS_RATIO,
NET_BORROWS_WINDOW_SIZE_TS,
NET_BORROWS_LIMIT_NATIVE,
);
console.log(`Registering SOL...`);
const solMainnetMint = new PublicKey(MAINNET_MINTS.get('SOL')!);
const solMainnetOracle = new PublicKey(MAINNET_ORACLES.get('SOL')!);
await client.tokenRegister(
group,
solMainnetMint,
solMainnetOracle,
defaultOracleConfig,
5,
'SOL',
defaultInterestRate,
0.005,
0.0005,
0.9,
0.8,
1.1,
1.2,
0.05,
MIN_VAULT_TO_DEPOSITS_RATIO,
NET_BORROWS_WINDOW_SIZE_TS,
NET_BORROWS_LIMIT_NATIVE,
);
console.log(`Registering mSOL...`);
const msolMainnetMint = new PublicKey(MAINNET_MINTS.get('mSOL')!);
const msolMainnetOracle = new PublicKey(MAINNET_ORACLES.get('mSOL')!);
await client.tokenRegister(
group,
msolMainnetMint,
msolMainnetOracle,
defaultOracleConfig,
6,
'mSOL',
defaultInterestRate,
0.005,
0.0005,
0.9,
0.8,
1.1,
1.2,
0.05,
MIN_VAULT_TO_DEPOSITS_RATIO,
NET_BORROWS_WINDOW_SIZE_TS,
NET_BORROWS_LIMIT_NATIVE,
);
// log tokens/banks
await group.reloadAll(client);
for (const [bank] of await group.banksMapByMint.values()) {
console.log(`${bank.toString()}`);
}
}
async function main() {
createGroup();
registerTokens();
}
try {
main();
} catch (error) {
console.log(error);
}

View File

@ -6,6 +6,7 @@ import {
PublicKey,
} from '@solana/web3.js';
import fs from 'fs';
import { PerpMarketIndex } from '../accounts/perp';
import { MangoClient } from '../client';
import { MANGO_V4_ID } from '../constants';
import { buildVersionedTx } from '../utils';
@ -20,29 +21,20 @@ import { buildVersionedTx } from '../utils';
// * solana airdrop 1 -k ~/.config/solana/admin.json
//
// TODO: switch out with devnet openbook markets
// https://github.com/blockworks-foundation/mango-client-v3/blob/main/src/serum.json#L70
const DEVNET_SERUM3_MARKETS = new Map([
['BTC/USDC', 'DW83EpHFywBxCHmyARxwj3nzxJd7MUdSeznmrdzZKNZB'],
['SOL/USDC', '5xWpt56U1NCuHoAEtpLeUrQcxDkEpNfScjfLFaRzLPgR'],
['ETH/USDC', 'BkAraCyL9TTLbeMY3L1VWrPcv32DvSi5QDDQjik1J6Ac'],
['SRM/USDC', '249LDNPLLL29nRq8kjBTg9hKdXMcZf4vK2UvxszZYcuZ'],
['SOL/USDC', '82iPEvGiTceyxYpeLK3DhSwga3R5m4Yfyoydd13CukQ9'],
]);
const DEVNET_MINTS = new Map([
['USDC', '8FRFC6MoGGkMFQwngccyu69VnYbzykGeez7ignHVAFSN'], // use devnet usdc
['BTC', '3UNBZ6o52WTWwjac2kPUb4FyodhU1vFkRJheu1Sh2TvU'],
['SOL', 'So11111111111111111111111111111111111111112'],
['ORCA', 'orcarKHSqC5CDDsGbho8GKvwExejWHxTqGzXgcewB9L'],
['MNGO', 'Bb9bsTQa1bGEtQ5KagGkvSHyuLqDWumFUcRqFusFNJWC'],
['ETH', 'Cu84KB3tDL6SbFgToHMLYVDJJXdJjenNzSKikeAvzmkA'],
['SRM', 'AvtB6w9xboLwA145E221vhof5TddhqsChYcx7Fy3xVMH'],
]);
const DEVNET_ORACLES = new Map([
['BTC', 'HovQMDrbAgAYPCmHVSrezcSmkMtXSSUsLDFANExrZh2J'],
['SOL', 'J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix'],
['ORCA', 'A1WttWF7X3Rg6ZRpB2YQUFHCRh1kiXV8sKKLV3S9neJV'],
['MNGO', '8k7F9Xb36oFJsjpCKpsXvg4cgBRoZtwNTc3EzG5Ttd2o'],
['BTC', 'HovQMDrbAgAYPCmHVSrezcSmkMtXSSUsLDFANExrZh2J'],
['ETH', 'EdVCmQ9FSPcVe5YySXDPCRmc8aDQLKJ9xvYBMZPie1Vw'],
['SRM', '992moaMQKs32GKZ9dxi8keyM2bUmbrwBZpK4p2K6X5Vs'],
]);
// TODO: should these constants be baked right into client.ts or even program?
@ -53,6 +45,8 @@ const NET_BORROWS_LIMIT_NATIVE = 1 * Math.pow(10, 7) * Math.pow(10, 6);
const GROUP_NUM = Number(process.env.GROUP_NUM || 0);
async function main() {
let sig;
const options = AnchorProvider.defaultOptions();
const connection = new Connection(
'https://mango.devnet.rpcpool.com',
@ -100,20 +94,19 @@ async function main() {
maxRate: 2.0,
};
// stub oracle + register token 0
// stub usdc oracle + register token 0
console.log(`Registering USDC...`);
const usdcDevnetMint = new PublicKey(DEVNET_MINTS.get('USDC')!);
try {
await client.stubOracleCreate(group, usdcDevnetMint, 1.0);
} catch (error) {
console.log(error);
}
const usdcDevnetOracle = (
await client.getStubOracle(group, usdcDevnetMint)
)[0];
console.log(`...created stub oracle ${usdcDevnetOracle.publicKey}`);
try {
await client.tokenRegister(
sig = await client.stubOracleCreate(group, usdcDevnetMint, 1.0);
const usdcDevnetOracle = (
await client.getStubOracle(group, usdcDevnetMint)
)[0];
console.log(
`...registered stub oracle ${usdcDevnetOracle}, https://explorer.solana.com/tx/${sig}?cluster=devnet`,
);
sig = await client.tokenRegister(
group,
usdcDevnetMint,
usdcDevnetOracle.publicKey,
@ -133,48 +126,24 @@ async function main() {
NET_BORROWS_LIMIT_NATIVE,
);
await group.reloadAll(client);
const bank = group.getFirstBankByMint(usdcDevnetMint);
console.log(
`...registered token bank ${bank.publicKey}, https://explorer.solana.com/tx/${sig}?cluster=devnet`,
);
await group.reloadAll(client);
} catch (error) {}
// register token 1
console.log(`Registering BTC...`);
const btcDevnetMint = new PublicKey(DEVNET_MINTS.get('BTC')!);
const btcDevnetOracle = new PublicKey(DEVNET_ORACLES.get('BTC')!);
try {
await client.tokenRegister(
group,
btcDevnetMint,
btcDevnetOracle,
defaultOracleConfig,
1, // tokenIndex
'BTC',
defaultInterestRate,
0.005,
0.0005,
0.9,
0.8,
1.1,
1.2,
0.05,
MIN_VAULT_TO_DEPOSITS_RATIO,
NET_BORROWS_WINDOW_SIZE_TS,
NET_BORROWS_LIMIT_NATIVE,
);
await group.reloadAll(client);
} catch (error) {
console.log(error);
}
// register token 2
console.log(`Registering SOL...`);
const solDevnetMint = new PublicKey(DEVNET_MINTS.get('SOL')!);
const solDevnetOracle = new PublicKey(DEVNET_ORACLES.get('SOL')!);
try {
await client.tokenRegister(
sig = await client.tokenRegister(
group,
solDevnetMint,
solDevnetOracle,
defaultOracleConfig,
2, // tokenIndex
1, // tokenIndex
'SOL',
defaultInterestRate,
0.005,
@ -189,100 +158,10 @@ async function main() {
NET_BORROWS_LIMIT_NATIVE,
);
await group.reloadAll(client);
} catch (error) {
console.log(error);
}
// register token 3
console.log(`Registering ORCA...`);
const orcaDevnetMint = new PublicKey(DEVNET_MINTS.get('ORCA')!);
const orcaDevnetOracle = new PublicKey(DEVNET_ORACLES.get('ORCA')!);
try {
await client.tokenRegister(
group,
orcaDevnetMint,
orcaDevnetOracle,
defaultOracleConfig,
3, // tokenIndex
'ORCA',
{
adjustmentFactor: 0.01,
util0: 0.4,
rate0: 0.07,
util1: 0.8,
rate1: 0.9,
maxRate: 0.63, // weird?
},
0.0005,
0.0005,
0.8,
0.6,
1.2,
1.4,
0.02,
MIN_VAULT_TO_DEPOSITS_RATIO,
NET_BORROWS_WINDOW_SIZE_TS,
NET_BORROWS_LIMIT_NATIVE,
const bank = group.getFirstBankByMint(solDevnetMint);
console.log(
`...registered token bank ${bank.publicKey}, https://explorer.solana.com/tx/${sig}?cluster=devnet`,
);
await group.reloadAll(client);
} catch (error) {
console.log(error);
}
// register token 7
console.log(`Registering ETH...`);
const ethDevnetMint = new PublicKey(DEVNET_MINTS.get('ETH')!);
const ethDevnetOracle = new PublicKey(DEVNET_ORACLES.get('ETH')!);
try {
await client.tokenRegister(
group,
ethDevnetMint,
ethDevnetOracle,
defaultOracleConfig,
7, // tokenIndex
'ETH',
defaultInterestRate,
0.005,
0.0005,
0.9,
0.8,
1.1,
1.2,
0.05,
MIN_VAULT_TO_DEPOSITS_RATIO,
NET_BORROWS_WINDOW_SIZE_TS,
NET_BORROWS_LIMIT_NATIVE,
);
await group.reloadAll(client);
} catch (error) {
console.log(error);
}
// register token 5
console.log(`Registering SRM...`);
const srmDevnetMint = new PublicKey(DEVNET_MINTS.get('SRM')!);
const srmDevnetOracle = new PublicKey(DEVNET_ORACLES.get('SRM')!);
try {
await client.tokenRegister(
group,
srmDevnetMint,
srmDevnetOracle,
defaultOracleConfig,
5, // tokenIndex
'SRM',
defaultInterestRate,
0.005,
0.0005,
0.9,
0.8,
1.1,
1.2,
0.05,
MIN_VAULT_TO_DEPOSITS_RATIO,
NET_BORROWS_WINDOW_SIZE_TS,
NET_BORROWS_LIMIT_NATIVE,
);
await group.reloadAll(client);
} catch (error) {
console.log(error);
}
@ -290,91 +169,70 @@ async function main() {
console.log(
`Editing group, setting existing admin as fastListingAdmin to be able to add MNGO truslessly...`,
);
let sig = await client.groupEdit(
sig = await client.groupEdit(
group,
group.admin,
group.admin,
undefined,
undefined,
);
console.log(`sig https://explorer.solana.com/tx/${sig}?cluster=devnet`);
console.log(
`...edited group, https://explorer.solana.com/tx/${sig}?cluster=devnet`,
);
console.log(`Registering MNGO...`);
const mngoDevnetMint = new PublicKey(DEVNET_MINTS.get('MNGO')!);
const mngoDevnetOracle = new PublicKey(DEVNET_ORACLES.get('MNGO')!);
try {
await client.tokenRegisterTrustless(
sig = await client.tokenRegisterTrustless(
group,
mngoDevnetMint,
mngoDevnetOracle,
4,
2,
'MNGO',
);
await group.reloadAll(client);
const bank = group.getFirstBankByMint(mngoDevnetMint);
console.log(
`...registered token bank ${bank.publicKey}, https://explorer.solana.com/tx/${sig}?cluster=devnet`,
);
} catch (error) {
console.log(error);
}
// DEBUGGING
// log tokens/banks
group.consoleLogBanks();
// group.consoleLogBanks();
// register serum market
console.log(`Registering serum3 market...`);
let serumMarketExternalPk = new PublicKey(
DEVNET_SERUM3_MARKETS.get('BTC/USDC')!,
);
try {
await client.serum3RegisterMarket(
group,
serumMarketExternalPk,
group.getFirstBankByMint(btcDevnetMint),
group.getFirstBankByMint(usdcDevnetMint),
0,
'BTC/USDC',
);
} catch (error) {
console.log(error);
}
const markets = await client.serum3GetMarkets(
group,
group.getFirstBankByMint(btcDevnetMint).tokenIndex,
group.getFirstBankByMint(usdcDevnetMint).tokenIndex,
);
console.log(`...registered serum3 market ${markets[0].publicKey}`);
serumMarketExternalPk = new PublicKey(DEVNET_SERUM3_MARKETS.get('ETH/USDC')!);
try {
await client.serum3RegisterMarket(
group,
serumMarketExternalPk,
group.getFirstBankByMint(ethDevnetMint),
group.getFirstBankByMint(usdcDevnetMint),
1,
'ETH/USDC',
);
} catch (error) {
console.log(error);
}
serumMarketExternalPk = new PublicKey(DEVNET_SERUM3_MARKETS.get('SRM/USDC')!);
try {
await client.serum3RegisterMarket(
group,
serumMarketExternalPk,
group.getFirstBankByMint(srmDevnetMint),
group.getFirstBankByMint(usdcDevnetMint),
2,
'SRM/USDC',
);
} catch (error) {
console.log(error);
}
// // register serum market
// const serumMarketExternalPk = new PublicKey(
// DEVNET_SERUM3_MARKETS.get('SOL/USDC')!,
// );
// try {
// sig = await client.serum3RegisterMarket(
// group,
// serumMarketExternalPk,
// group.getFirstBankByMint(solDevnetMint),
// group.getFirstBankByMint(usdcDevnetMint),
// 0,
// 'SOL/USDC',
// );
// await group.reloadAll(client);
// const serum3Market = group.getSerum3MarketByExternalMarket(
// serumMarketExternalPk,
// );
// console.log(
// `...registered serum market ${serum3Market.publicKey}, https://explorer.solana.com/tx/${sig}?cluster=devnet`,
// );
// } catch (error) {
// console.log(error);
// }
// register perp market
console.log(`Registering perp market...`);
try {
await client.perpCreateMarket(
sig = await client.perpCreateMarket(
group,
btcDevnetOracle,
new PublicKey(DEVNET_ORACLES.get('BTC')!),
0,
'BTC-PERP',
defaultOracleConfig,
@ -401,164 +259,20 @@ async function main() {
1.0,
2 * 60 * 60,
);
console.log('done');
await group.reloadAll(client);
const perpMarket = group.getPerpMarketByMarketIndex(0 as PerpMarketIndex);
console.log(
`...registered perp market ${perpMarket.publicKey}, https://explorer.solana.com/tx/${sig}?cluster=devnet`,
);
} catch (error) {
console.log(error);
}
const perpMarkets = await client.perpGetMarkets(group);
console.log(`...created perp market ${perpMarkets[0].publicKey}`);
//
// edit
//
if (true) {
console.log(`Editing USDC...`);
if (group.addressLookupTables[0].equals(PublicKey.default)) {
try {
let sig = await client.tokenEdit(
group,
usdcDevnetMint,
usdcDevnetOracle.publicKey,
defaultOracleConfig,
null,
defaultInterestRate,
0.005,
0.0005,
1,
1,
1,
1,
0,
null,
null,
null,
MIN_VAULT_TO_DEPOSITS_RATIO,
NET_BORROWS_WINDOW_SIZE_TS,
NET_BORROWS_LIMIT_NATIVE,
null,
null,
false,
false,
);
console.log(`https://explorer.solana.com/tx/${sig}?cluster=devnet`);
await group.reloadAll(client);
console.log(group.getFirstBankByMint(btcDevnetMint).toString());
} catch (error) {
throw error;
}
console.log(`Editing BTC...`);
try {
let sig = await client.tokenEdit(
group,
usdcDevnetMint,
usdcDevnetOracle.publicKey,
defaultOracleConfig,
null,
defaultInterestRate,
0.005,
0.0005,
0.9,
0.8,
1.1,
1.2,
0.05,
null,
null,
null,
MIN_VAULT_TO_DEPOSITS_RATIO,
NET_BORROWS_WINDOW_SIZE_TS,
NET_BORROWS_LIMIT_NATIVE,
null,
null,
false,
false,
);
console.log(`https://explorer.solana.com/tx/${sig}?cluster=devnet`);
await group.reloadAll(client);
console.log(group.getFirstBankByMint(btcDevnetMint).toString());
} catch (error) {
throw error;
}
console.log(`Editing SOL...`);
try {
let sig = await client.tokenEdit(
group,
usdcDevnetMint,
usdcDevnetOracle.publicKey,
defaultOracleConfig,
null,
defaultInterestRate,
0.005,
0.0005,
0.9,
0.8,
1.1,
1.2,
0.05,
null,
null,
null,
MIN_VAULT_TO_DEPOSITS_RATIO,
NET_BORROWS_WINDOW_SIZE_TS,
NET_BORROWS_LIMIT_NATIVE,
null,
null,
false,
false,
);
console.log(`https://explorer.solana.com/tx/${sig}?cluster=devnet`);
await group.reloadAll(client);
console.log(group.getFirstBankByMint(btcDevnetMint).toString());
} catch (error) {
throw error;
}
console.log(`Editing BTC-PERP...`);
try {
let sig = await client.perpEditMarket(
group,
group.getPerpMarketByName('BTC-PERP').perpMarketIndex,
btcDevnetOracle,
defaultOracleConfig,
6,
0.975,
0.95,
1.025,
1.05,
0.012,
0.0002,
0.0,
0,
0.05,
0.05,
100,
true,
true,
1000,
1000000,
0.05,
MIN_VAULT_TO_DEPOSITS_RATIO,
NET_BORROWS_WINDOW_SIZE_TS,
NET_BORROWS_LIMIT_NATIVE,
1.0,
2 * 60 * 60,
);
console.log(`https://explorer.solana.com/tx/${sig}?cluster=devnet`);
await group.reloadAll(client);
console.log(group.getFirstBankByMint(btcDevnetMint).toString());
} catch (error) {
throw error;
}
}
if (
// true
group.addressLookupTables[0].equals(PublicKey.default)
) {
try {
console.log(`ALT: Creating`);
console.log(`ALT...`);
const createIx = AddressLookupTableProgram.createLookupTable({
authority: admin.publicKey,
payer: admin.publicKey,
@ -568,37 +282,20 @@ async function main() {
client.program.provider as AnchorProvider,
[createIx[0]],
);
let sig = await connection.sendTransaction(createTx);
sig = await connection.sendTransaction(createTx);
console.log(
`...created ALT ${createIx[1]} https://explorer.solana.com/tx/${sig}?cluster=devnet`,
);
console.log(`ALT: set at index 0 for group...`);
sig = await client.altSet(
group,
new PublicKey('EmN5RjHUFsoag7tZ2AyBL2N8JrhV7nLMKgNbpCfzC81D'),
0,
);
console.log(`...https://explorer.solana.com/tx/${sig}?cluster=devnet`);
// Extend using a mango v4 program ix
// Throws > Instruction references an unknown account 11111111111111111111111111111111 atm
//
console.log(
`ALT: extending using mango v4 program with bank publick keys and oracles`,
`...set at index 0 for group https://explorer.solana.com/tx/${sig}?cluster=devnet`,
);
// let sig = await client.altExtend(
// group,
// new PublicKey('EmN5RjHUFsoag7tZ2AyBL2N8JrhV7nLMKgNbpCfzC81D'),
// 0,
// Array.from(group.banksMapByMint.values())
// .flat()
// .map((bank) => [bank.publicKey, bank.oracle])
// .flat(),
// );
// console.log(`https://explorer.solana.com/tx/${sig}?cluster=devnet`);
console.log(`ALT: extending manually with bank publick keys and oracles`);
const extendIx = AddressLookupTableProgram.extendLookupTable({
lookupTable: createIx[1],
payer: admin.publicKey,
@ -613,7 +310,9 @@ async function main() {
[extendIx],
);
sig = await client.program.provider.connection.sendTransaction(extendTx);
console.log(`https://explorer.solana.com/tx/${sig}?cluster=devnet`);
console.log(
`...extended ALT with pks, https://explorer.solana.com/tx/${sig}?cluster=devnet`,
);
} catch (error) {
console.log(error);
}

View File

@ -1,10 +1,11 @@
import * as dotenv from 'dotenv';
dotenv.config();
import { AnchorProvider, Wallet } from '@project-serum/anchor';
import { Connection, Keypair } from '@solana/web3.js';
import * as dotenv from 'dotenv';
import fs from 'fs';
import { PerpMarket } from '../accounts/perp';
import { MangoClient } from '../client';
import { MANGO_V4_ID } from '../constants';
dotenv.config();
//
// (untested?) script which closes a mango account cleanly, first closes all positions, withdraws all tokens and then closes it
@ -32,13 +33,16 @@ async function editPerpMarket(perpMarketName: string) {
const group = await client.getGroupForCreator(admin.publicKey, 2);
console.log(`Found group ${group.publicKey.toBase58()}`);
const pm = group.getPerpMarketByName(perpMarketName);
const pm: PerpMarket = group.getPerpMarketByName(perpMarketName);
const signature = await client.perpEditMarket(
group,
pm.perpMarketIndex,
pm.oracle,
pm.confFilter.toNumber(),
{
confFilter: pm.oracleConfig.confFilter.toNumber(),
maxStalenessSlots: null,
},
pm.baseDecimals,
pm.maintAssetWeight.toNumber(),
pm.initAssetWeight.toNumber(),
@ -57,6 +61,11 @@ async function editPerpMarket(perpMarketName: string) {
pm.settleFeeFlat,
pm.settleFeeAmountThreshold,
pm.settleFeeFractionLowHealth,
null,
null,
null,
null,
null,
);
console.log('Tx Successful:', signature);

View File

@ -20,9 +20,7 @@ import { buildVersionedTx } from '../utils';
const MAINNET_MINTS = new Map([
['USDC', 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'],
['USDT', 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB'],
['BTC', '9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E'], // Wrapped Bitcoin (Sollet)
['ETH', '7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs'], // Ether (Portal)
['soETH', '2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk'], // Wrapped Ethereum (Sollet)
['SOL', 'So11111111111111111111111111111111111111112'], // Wrapped SOL
['MSOL', 'mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So'],
['MNGO', 'MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac'],
@ -33,7 +31,6 @@ const MAINNET_ORACLES = new Map([
['USDT', '3vxLXJqLqF3JG5TCbYycbKWRBbCJQLxQmBGCkyqEEefL'],
['BTC', 'GVXRSBjFk6e6J3NbVPXohDJetcTjaeeuykUpbQF8UoMU'],
['ETH', 'JBu1AL4obBcCMqKBBxhpWCNUt136ijcuMZLFvTP7iWdB'],
['soETH', 'JBu1AL4obBcCMqKBBxhpWCNUt136ijcuMZLFvTP7iWdB'],
['SOL', 'H6ARHf6YXhGYeQfUzQNGk6rDNnLBQKrenN712K4AQJEG'],
['MSOL', 'E4v1BBgoso9s64TQvmyownAVJbhbEPGyzA3qn4n46qj9'],
['MNGO', '79wm3jjcPr6RaNQ4DGvP5KxG1mNd3gEBsg6FsNVFezK4'],
@ -41,14 +38,9 @@ const MAINNET_ORACLES = new Map([
['DUST', 'C5tuUPi7xJHBHZGZX6wWYf1Svm6jtTVwYrYrBCiEVejK'],
]);
// External markets are matched with those in https://github.com/blockworks-foundation/mango-client-v3/blob/main/src/ids.json
// and verified to have best liquidity for pair on https://openserum.io/
// TODO: replace with markets from https://github.com/openbook-dex/resources/blob/main/markets.json
// External markets are matched with those in https://github.com/openbook-dex/openbook-ts/blob/master/packages/serum/src/markets.json
const MAINNET_SERUM3_MARKETS = new Map([
['BTC/USDC', 'A8YFbxQYFVqKZaoYJLLUVcQiWP7G2MeEgW5wsAQgMvFw'],
['SOL/USDC', '9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT'],
['RAY/SOL', 'C6tp2RVZnxBPFbnAsfTjis8BN9tycESAT4SgDQgbbrsA'],
['DUST/SOL', '8WCzJpSNcLUYXPYeUDAXpH4hgqxFJpkYkVT6GJDSpcGx'],
]);
const MIN_VAULT_TO_DEPOSITS_RATIO = 0.2;
@ -193,29 +185,6 @@ async function registerTokens() {
NET_BORROWS_LIMIT_NATIVE,
);
console.log(`Registering BTC...`);
const btcMainnetMint = new PublicKey(MAINNET_MINTS.get('BTC')!);
const btcMainnetOracle = new PublicKey(MAINNET_ORACLES.get('BTC')!);
await client.tokenRegister(
group,
btcMainnetMint,
btcMainnetOracle,
defaultOracleConfig,
2,
'BTC',
defaultInterestRate,
0.005,
0.0005,
0.9,
0.8,
1.1,
1.2,
0.05,
MIN_VAULT_TO_DEPOSITS_RATIO,
NET_BORROWS_WINDOW_SIZE_TS,
NET_BORROWS_LIMIT_NATIVE,
);
console.log(`Registering ETH...`);
const ethMainnetMint = new PublicKey(MAINNET_MINTS.get('ETH')!);
const ethMainnetOracle = new PublicKey(MAINNET_ORACLES.get('ETH')!);
@ -239,29 +208,6 @@ async function registerTokens() {
NET_BORROWS_LIMIT_NATIVE,
);
console.log(`Registering soETH...`);
const soEthMainnetMint = new PublicKey(MAINNET_MINTS.get('soETH')!);
const soEthMainnetOracle = new PublicKey(MAINNET_ORACLES.get('soETH')!);
await client.tokenRegister(
group,
soEthMainnetMint,
soEthMainnetOracle,
defaultOracleConfig,
4,
'soETH',
defaultInterestRate,
0.005,
0.0005,
0.9,
0.8,
1.1,
1.2,
0.05,
MIN_VAULT_TO_DEPOSITS_RATIO,
NET_BORROWS_WINDOW_SIZE_TS,
NET_BORROWS_LIMIT_NATIVE,
);
console.log(`Registering SOL...`);
const solMainnetMint = new PublicKey(MAINNET_MINTS.get('SOL')!);
const solMainnetOracle = new PublicKey(MAINNET_ORACLES.get('SOL')!);
@ -404,15 +350,8 @@ async function registerSerum3Markets() {
1,
);
// Register BTC and SOL markets
await client.serum3RegisterMarket(
group,
new PublicKey(MAINNET_SERUM3_MARKETS.get('BTC/USDC')!),
group.getFirstBankByMint(new PublicKey(MAINNET_MINTS.get('BTC')!)),
group.getFirstBankByMint(new PublicKey(MAINNET_MINTS.get('USDC')!)),
0,
'BTC/USDC',
);
// Register SOL serum market
await client.serum3RegisterMarket(
group,
new PublicKey(MAINNET_SERUM3_MARKETS.get('SOL/USDC')!),
@ -421,24 +360,6 @@ async function registerSerum3Markets() {
1,
'SOL/USDC',
);
// Register RAY and DUST markets
await client.serum3RegisterMarket(
group,
new PublicKey(MAINNET_SERUM3_MARKETS.get('RAY/SOL')!),
group.getFirstBankByMint(new PublicKey(MAINNET_MINTS.get('RAY')!)),
group.getFirstBankByMint(new PublicKey(MAINNET_MINTS.get('SOL')!)),
2,
'RAY/SOL',
);
await client.serum3RegisterMarket(
group,
new PublicKey(MAINNET_SERUM3_MARKETS.get('DUST/SOL')!),
group.getFirstBankByMint(new PublicKey(MAINNET_MINTS.get('DUST')!)),
group.getFirstBankByMint(new PublicKey(MAINNET_MINTS.get('SOL')!)),
3,
'DUST/SOL',
);
}
async function unregisterSerum3Markets() {

View File

@ -114,8 +114,8 @@ async function refreshState(
mc.perpMarket = group.getPerpMarketByMarketIndex(
perpMarket.perpMarketIndex,
);
mc.bids = await perpMarket.loadBids(client);
mc.asks = await perpMarket.loadAsks(client);
mc.bids = await perpMarket.loadBids(client, true);
mc.asks = await perpMarket.loadAsks(client, true);
mc.lastBookUpdate = ts;
mc.binanceAsk = parseFloat((result[i + 2] as any).asks[0].price);
@ -448,6 +448,7 @@ async function makeMarketUpdateInstructions(
const expiryTimestamp =
params.tif !== undefined ? Date.now() / 1000 + params.tif : 0;
// TODO: oracle pegged runs out of free perp open order slots on mango account
if (params.oraclePegged) {
const uiOPegBidOffset = fairValue * (-charge + lean + bias + pfQuoteLean);
const uiOPegAskOffset = fairValue * (charge + lean + bias + pfQuoteLean);

View File

@ -1,6 +1,6 @@
{
"interval": 1000,
"oraclePegged": true,
"oraclePegged": false,
"batch": 1,
"assets": {
"BTC": {

View File

@ -7,7 +7,7 @@ import { PerpMarket, PerpOrderSide, PerpOrderType } from '../../accounts/perp';
import { MangoClient } from '../../client';
import { MANGO_V4_ID } from '../../constants';
import { ZERO_I80F48 } from '../../numbers/I80F48';
import { toUiDecimalsForQuote } from '../../utils';
import { toNativeI80F48, toUiDecimalsForQuote } from '../../utils';
// For easy switching between mainnet and devnet, default is mainnet
const CLUSTER: Cluster =
@ -33,13 +33,35 @@ async function settlePnl(
.find((pp) => pp.marketIndex === perpMarket.perpMarketIndex)!;
const pnl = pp.getPnl(perpMarket);
console.log(
`Avg entry price - ${pp.getAverageEntryPriceUi(
perpMarket,
)}, Breakeven price - ${pp.getBreakEvenPriceUi(perpMarket)}`,
);
let profitableAccount, unprofitableAccount;
if (pnl.abs().gt(toNativeI80F48(1, 6))) {
console.log(`- Settling pnl ${toUiDecimalsForQuote(pnl)} ...`);
} else {
console.log(
`- Skipping Settling pnl ${toUiDecimalsForQuote(pnl)}, too small`,
);
return;
}
if (pnl.gt(ZERO_I80F48())) {
console.log(`- Settling profit pnl...`);
profitableAccount = mangoAccount;
unprofitableAccount = (
await perpMarket.getSettlePnlCandidates(client, group, 'negative')
)[0].account;
const candidates = await perpMarket.getSettlePnlCandidates(
client,
group,
'negative',
);
if (candidates.length === 0) {
return;
}
unprofitableAccount = candidates[0].account;
const sig = await client.perpSettlePnl(
group,
profitableAccount,
@ -48,16 +70,23 @@ async function settlePnl(
perpMarket.perpMarketIndex,
);
console.log(
`Settled pnl, sig https://explorer.solana.com/tx/${sig}?cluster=${
`- Settled pnl, sig https://explorer.solana.com/tx/${sig}?cluster=${
CLUSTER == 'devnet' ? 'devnet' : ''
}`,
);
} else if (pnl.lt(ZERO_I80F48())) {
unprofitableAccount = mangoAccount;
profitableAccount = (
await perpMarket.getSettlePnlCandidates(client, group, 'positive')
)[0].account;
const sig = await client.perpSettlePnl(
const candidates = await perpMarket.getSettlePnlCandidates(
client,
group,
'positive',
);
if (candidates.length === 0) {
return;
}
profitableAccount = candidates[0].account;
console.log(`- Settling loss pnl...`);
let sig = await client.perpSettlePnl(
group,
profitableAccount,
unprofitableAccount,
@ -65,7 +94,7 @@ async function settlePnl(
perpMarket.perpMarketIndex,
);
console.log(
`Settled pnl, sig https://explorer.solana.com/tx/${sig}?cluster=${
`- Settled pnl, sig https://explorer.solana.com/tx/${sig}?cluster=${
CLUSTER == 'devnet' ? 'devnet' : ''
}`,
);
@ -87,7 +116,7 @@ async function takeOrder(
? perpMarket.uiPrice * 1.01
: perpMarket.uiPrice * 0.99;
console.log(
`${perpMarket.name} taking with a ${
`- ${perpMarket.name} taking with a ${
side === PerpOrderSide.bid ? 'bid' : 'ask'
} at price ${price.toFixed(4)} and size ${size.toFixed(6)}`,
);
@ -95,7 +124,7 @@ async function takeOrder(
const oldPosition = mangoAccount.getPerpPosition(perpMarket.perpMarketIndex);
if (oldPosition) {
console.log(
`- before base: ${perpMarket.baseLotsToUi(
`-- before base: ${perpMarket.baseLotsToUi(
oldPosition.basePositionLots,
)}, quote: ${toUiDecimalsForQuote(oldPosition.quotePositionNative)}`,
);
@ -122,7 +151,7 @@ async function takeOrder(
const newPosition = mangoAccount.getPerpPosition(perpMarket.perpMarketIndex);
if (newPosition) {
console.log(
`- after base: ${perpMarket.baseLotsToUi(
`-- after base: ${perpMarket.baseLotsToUi(
newPosition.basePositionLots,
)}, quote: ${toUiDecimalsForQuote(newPosition.quotePositionNative)}`,
);