Merge remote-tracking branch 'origin/deploy' into dev

This commit is contained in:
Christian Kamm 2023-09-07 13:14:03 +02:00
commit 0aabd28cdc
15 changed files with 765 additions and 171 deletions

View File

@ -8262,14 +8262,28 @@
},
{
"name": "changeAmount",
"docs": [
"The amount by which the user's token position changed at the end",
"",
"So if the user repaid the approved_amount in full, it'd be 0.",
"",
"Does NOT include the loan_origination_fee or deposit_fee, so the true",
"change is `change_amount - loan_origination_fee - deposit_fee`."
],
"type": "i128"
},
{
"name": "loan",
"docs": [
"The amount that was a loan (<= approved_amount, depends on user's deposits)"
],
"type": "i128"
},
{
"name": "loanOriginationFee",
"docs": [
"The fee paid on the loan, not included in `loan` or `change_amount`"
],
"type": "i128"
},
{
@ -8286,7 +8300,19 @@
},
{
"name": "depositFee",
"docs": [
"Deposit fee paid for positive change_amount.",
"",
"Not factored into change_amount."
],
"type": "i128"
},
{
"name": "approvedAmount",
"docs": [
"The amount that was transfered out to the user"
],
"type": "u64"
}
]
}

View File

@ -1,6 +1,6 @@
{
"name": "@blockworks-foundation/mango-v4",
"version": "0.18.14",
"version": "0.19.14",
"description": "Typescript Client for mango-v4 program.",
"repository": "https://github.com/blockworks-foundation/mango-v4",
"author": {

View File

@ -13,7 +13,8 @@
"mint": "So11111111111111111111111111111111111111112",
"tokenIndex": 5,
"bankNum": 0,
"active": true
"active": true,
"decimals": 99999
},
{
"name": "USDT",
@ -21,7 +22,8 @@
"mint": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
"tokenIndex": 1,
"bankNum": 0,
"active": true
"active": true,
"decimals": 99999
},
{
"name": "USDC",
@ -29,7 +31,8 @@
"mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"tokenIndex": 0,
"bankNum": 0,
"active": true
"active": true,
"decimals": 99999
},
{
"name": "BTC",
@ -37,7 +40,8 @@
"mint": "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E",
"tokenIndex": 2,
"bankNum": 0,
"active": true
"active": true,
"decimals": 99999
},
{
"name": "soETH",
@ -45,7 +49,8 @@
"mint": "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk",
"tokenIndex": 4,
"bankNum": 0,
"active": true
"active": true,
"decimals": 99999
},
{
"name": "ETH",
@ -53,7 +58,8 @@
"mint": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs",
"tokenIndex": 3,
"bankNum": 0,
"active": true
"active": true,
"decimals": 99999
},
{
"name": "MSOL",
@ -61,7 +67,8 @@
"mint": "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So",
"tokenIndex": 6,
"bankNum": 0,
"active": true
"active": true,
"decimals": 99999
}
],
"stubOracles": [

View File

@ -662,7 +662,7 @@ async function createAndPopulateAlt() {
);
console.log(`ALT: set at index 0 for group...`);
sig = await client.altSet(group, createIx[1], 0);
sig = (await client.altSet(group, createIx[1], 0)).signature;
console.log(`...https://explorer.solana.com/tx/${sig}`);
} catch (error) {
console.log(error);

View File

@ -0,0 +1,245 @@
import { AnchorProvider, Wallet } from '@coral-xyz/anchor';
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import fs from 'fs';
import { TokenIndex } from '../src/accounts/bank';
import { Group } from '../src/accounts/group';
import { MangoClient } from '../src/client';
import { DefaultTokenRegisterParams } from '../src/clientIxParamBuilder';
import { MANGO_V4_ID } from '../src/constants';
import { toNative } from '../src/utils';
const MAINNET_MINTS = new Map([
['USDC', 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'], // 0
['SOL', 'So11111111111111111111111111111111111111112'], // 1
]);
const MAINNET_ORACLES = new Map([
['USDC', 'Gnt27xtC473ZT2Mw5u8wZ68Z3gULkSTb5DuxJy7eJotD'],
['SOL', 'H6ARHf6YXhGYeQfUzQNGk6rDNnLBQKrenN712K4AQJEG'],
]);
const MAINNET_SERUM3_MARKETS = new Map([
['SOL/USDC', '8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6'],
]);
const {
MB_CLUSTER_URL,
MB_PAYER_KEYPAIR,
GROUP_NUM,
}: {
MB_CLUSTER_URL: string;
MB_PAYER_KEYPAIR: string;
GROUP_NUM: number;
} = process.env as any;
const defaultOracleConfig = {
confFilter: 0.1,
maxStalenessSlots: null,
};
const defaultInterestRate = {
adjustmentFactor: 0.0,
util0: 0.0,
rate0: 0.0,
util1: 0.0,
rate1: 0.0,
maxRate: 0.51,
};
const defaultTokenParams = {
...DefaultTokenRegisterParams,
oracleConfig: defaultOracleConfig,
interestRateParams: defaultInterestRate,
loanOriginationFeeRate: 0.0,
loanFeeRate: 0.0,
initAssetWeight: 0,
maintAssetWeight: 0,
initLiabWeight: 1,
maintLiabWeight: 1,
liquidationFee: 0,
minVaultToDepositsRatio: 1,
netBorrowLimitPerWindowQuote: 0,
};
async function buildAdminClient(): Promise<[MangoClient, Keypair]> {
const admin = Keypair.fromSecretKey(
Buffer.from(JSON.parse(fs.readFileSync(MB_PAYER_KEYPAIR!, 'utf-8'))),
);
const options = AnchorProvider.defaultOptions();
const connection = new Connection(MB_CLUSTER_URL!, options);
const adminWallet = new Wallet(admin);
const adminProvider = new AnchorProvider(connection, adminWallet, options);
const client = await MangoClient.connect(
adminProvider,
'mainnet-beta',
MANGO_V4_ID['mainnet-beta'],
{
idsSource: 'get-program-accounts',
},
);
return [client, admin];
}
async function buildUserClient(): Promise<[MangoClient, Group, Keypair]> {
const options = AnchorProvider.defaultOptions();
const connection = new Connection(MB_CLUSTER_URL!, options);
const user = Keypair.fromSecretKey(
Buffer.from(JSON.parse(fs.readFileSync(MB_PAYER_KEYPAIR, 'utf-8'))),
);
const userWallet = new Wallet(user);
const userProvider = new AnchorProvider(connection, userWallet, options);
const client = await MangoClient.connect(
userProvider,
'mainnet-beta',
MANGO_V4_ID['mainnet-beta'],
);
const group = await client.getGroupForCreator(user.publicKey, GROUP_NUM);
return [client, group, user];
}
async function createGroup(): Promise<void> {
const result = await buildAdminClient();
const client = result[0];
const admin = result[1];
const insuranceMint = new PublicKey(MAINNET_MINTS.get('USDC')!);
await client.groupCreate(GROUP_NUM, false, 2, insuranceMint);
const group = await client.getGroupForCreator(admin.publicKey, GROUP_NUM);
console.log(`...registered group ${group.publicKey}`);
}
async function registerTokens(): Promise<void> {
const result = await buildAdminClient();
const client = result[0];
const admin = result[1];
const group = await client.getGroupForCreator(admin.publicKey, GROUP_NUM);
const usdcMainnetMint = new PublicKey(MAINNET_MINTS.get('USDC')!);
const usdcMainnetOracle = new PublicKey(MAINNET_ORACLES.get('USDC')!);
let sig = await client.tokenRegister(
group,
usdcMainnetMint,
usdcMainnetOracle,
0,
'USDC',
defaultTokenParams,
);
console.log(`registered usdc ${sig}`);
const solMainnetMint = new PublicKey(MAINNET_MINTS.get('SOL')!);
const solMainnetOracle = new PublicKey(MAINNET_ORACLES.get('SOL')!);
sig = await client.tokenRegister(
group,
solMainnetMint,
solMainnetOracle,
1,
'SOL',
defaultTokenParams,
);
console.log(`registered sol ${sig}`);
}
async function registerSerum3Market(): Promise<void> {
const result = await buildAdminClient();
const client = result[0];
const admin = result[1];
const group = await client.getGroupForCreator(admin.publicKey, GROUP_NUM);
await client.serum3RegisterMarket(
group,
new PublicKey(MAINNET_SERUM3_MARKETS.get('SOL/USDC')!),
group.getFirstBankByTokenIndex(1 as TokenIndex),
group.getFirstBankByTokenIndex(0 as TokenIndex),
0,
'SOL/USDC',
);
}
async function doUserAction(): Promise<void> {
const result = await buildUserClient();
const client = result[0];
const group = result[1];
const user = result[2];
let mangoAccount = await client.getMangoAccountForOwner(
group,
user.publicKey,
0,
);
if (!mangoAccount) {
await client.createMangoAccount(group, 0);
mangoAccount = await client.getMangoAccountForOwner(
group,
user.publicKey,
0,
);
}
// await client.tokenDeposit(
// group,
// mangoAccount!,
// new PublicKey(MAINNET_MINTS.get('SOL')!),
// 0.01,
// );
// await client.tcsStopLossOnDeposit(
// group,
// mangoAccount!,
// group.getFirstBankByTokenIndex(1 as TokenIndex),
// group.getFirstBankByTokenIndex(0 as TokenIndex),
// group.getFirstBankByTokenIndex(1 as TokenIndex).uiPrice * 1.1,
// false,
// null,
// null,
// null,
// );
await mangoAccount?.reload(client);
mangoAccount
?.tokenConditionalSwapsActive()
.map((tcs) => console.log(tcs.toString(group)));
}
async function doUserAction2(): Promise<void> {
const result = await buildUserClient();
const client = result[0];
const group = result[1];
const user = result[2];
let mangoAccount = await client.getMangoAccountForOwner(
group,
user.publicKey,
1,
);
if (!mangoAccount) {
await client.createMangoAccount(group, 1);
mangoAccount = await client.getMangoAccountForOwner(
group,
user.publicKey,
1,
);
}
await client.tokenDeposit(
group,
mangoAccount!,
new PublicKey(MAINNET_MINTS.get('USDC')!),
5,
);
}
async function main(): Promise<void> {
try {
// await createGroup();
// await registerTokens();
// await registerSerum3Market();
await doUserAction();
// await doUserAction2();
} catch (error) {
console.log(error);
}
}
main();

View File

@ -0,0 +1,72 @@
import { PublicKey } from '@solana/web3.js';
import { Group } from '../src/accounts/group';
import { isSwitchboardOracle } from '../src/accounts/oracle';
import { MangoClient } from '../src/client';
import { buildFetch } from '../src/utils';
function getNameForBank(group: Group, oracle: PublicKey): string {
let match: any[] = Array.from(group.banksMapByName.values())
.flat()
.filter((b) => b.oracle.equals(oracle));
if (match.length > 0) {
return match[0].name;
}
match = Array.from(group.perpMarketsMapByName.values()).filter((p) =>
p.oracle.equals(oracle),
);
if (match.length > 0) {
return match[0].name;
}
throw new Error(`No token or perp market found for ${oracle}`);
}
async function main(): Promise<void> {
const client = await MangoClient.connectDefault(process.env.MB_CLUSTER_URL!);
const group = await client.getGroup(
new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'),
);
const oracles1 = Array.from(group.banksMapByName.values()).map(
(b) => b[0].oracle,
);
const oracles2 = Array.from(group.perpMarketsMapByName.values()).map(
(p) => p.oracle,
);
const oracles = oracles1.concat(oracles2);
const ais = await client.program.provider.connection.getMultipleAccountsInfo(
oracles,
);
const switcboardOracles: PublicKey[] = ais
.map((ai, i) => [isSwitchboardOracle(ai!), oracles[i]])
.filter((r) => r[0])
.map((r) => r[1]) as PublicKey[];
for (const o of switcboardOracles) {
const r = await (
await buildFetch()
)('https://stats.switchboard.xyz/logs', {
headers: {
accept: '*/*',
'content-type': 'application/json',
},
body: `{"cluster":"solana-mainnet","query":"${o.toString()}","number":100,"severity":"INFO"}`,
method: 'POST',
});
console.log(`${getNameForBank(group, o)} ${o}`);
(await r.json()).forEach((e: { message: string; timestamp: string }) => {
if (e.message.toLowerCase().includes('error')) {
console.log(`${e.timestamp}: ${e.message}`);
}
});
console.log(``);
}
}
main();

View File

@ -138,7 +138,7 @@ export class Group {
),
this.reloadMintInfos(client, ids),
this.reloadSerum3Markets(client, ids).then(() =>
this.reloadSerum3ExternalMarkets(client),
this.reloadSerum3ExternalMarkets(client, ids),
),
]);
// console.timeEnd('group.reload');
@ -274,23 +274,63 @@ export class Group {
);
}
public async reloadSerum3ExternalMarkets(client: MangoClient): Promise<void> {
const externalMarkets = await Promise.all(
Array.from(this.serum3MarketsMapByExternal.values()).map((serum3Market) =>
Market.load(
client.program.provider.connection,
serum3Market.serumMarketExternal,
{ commitment: client.program.provider.connection.commitment },
OPENBOOK_PROGRAM_ID[client.cluster],
public async reloadSerum3ExternalMarkets(
client: MangoClient,
ids?: Id,
): Promise<void> {
let markets: Market[] = [];
const externalMarketIds = ids?.getSerum3ExternalMarkets();
if (ids && externalMarketIds && externalMarketIds.length) {
markets = await Promise.all(
(
await client.program.provider.connection.getMultipleAccountsInfo(
externalMarketIds,
)
).map(
(account, index) =>
new Market(
Market.getLayout(OPENBOOK_PROGRAM_ID[client.cluster]).decode(
account?.data,
),
ids.banks.find(
(b) =>
b.tokenIndex ===
this.serum3MarketsMapByExternal.get(
externalMarketIds[index].toString(),
)?.baseTokenIndex,
)?.decimals || 6,
ids.banks.find(
(b) =>
b.tokenIndex ===
this.serum3MarketsMapByExternal.get(
externalMarketIds[index].toString(),
)?.quoteTokenIndex,
)?.decimals || 6,
{ commitment: client.program.provider.connection.commitment },
OPENBOOK_PROGRAM_ID[client.cluster],
),
),
),
);
);
} else {
markets = await Promise.all(
Array.from(this.serum3MarketsMapByExternal.values()).map(
(serum3Market) =>
Market.load(
client.program.provider.connection,
serum3Market.serumMarketExternal,
{ commitment: client.program.provider.connection.commitment },
OPENBOOK_PROGRAM_ID[client.cluster],
),
),
);
}
this.serum3ExternalMarketsMap = new Map(
Array.from(this.serum3MarketsMapByExternal.values()).map(
(serum3Market, index) => [
serum3Market.serumMarketExternal.toBase58(),
externalMarkets[index],
markets[index],
],
),
);
@ -463,7 +503,6 @@ export class Group {
await client.program.provider.connection.getMultipleAccountsInfo(
vaultPks,
);
const coder = new BorshAccountsCoder(client.program.idl);
this.vaultAmountsMap = new Map(
vaultAccounts.map((vaultAi, i) => {
if (!vaultAi) {

View File

@ -1,7 +1,7 @@
import { AnchorProvider, BN } from '@coral-xyz/anchor';
import { utf8 } from '@coral-xyz/anchor/dist/cjs/utils/bytes';
import { OpenOrders, Order, Orderbook } from '@project-serum/serum/lib/market';
import { AccountInfo, PublicKey, TransactionSignature } from '@solana/web3.js';
import { AccountInfo, PublicKey } from '@solana/web3.js';
import { MangoClient } from '../client';
import { OPENBOOK_PROGRAM_ID, RUST_I64_MAX, RUST_I64_MIN } from '../constants';
import { I80F48, I80F48Dto, ONE_I80F48, ZERO_I80F48 } from '../numbers/I80F48';
@ -13,6 +13,7 @@ import {
toUiDecimalsForQuote,
toUiSellPerBuyTokenPrice,
} from '../utils';
import { MangoSignatureStatus } from '../utils/rpc';
import { Bank, TokenIndex } from './bank';
import { Group } from './group';
import { HealthCache } from './healthCache';
@ -888,7 +889,7 @@ export class MangoAccount {
public async serum3SettleFundsForAllMarkets(
client: MangoClient,
group: Group,
): Promise<TransactionSignature[]> {
): Promise<MangoSignatureStatus[]> {
// Future: collect ixs, batch them, and send them in fewer txs
return await Promise.all(
this.serum3Active().map((s) => {
@ -906,7 +907,7 @@ export class MangoAccount {
public async serum3CancelAllOrdersForAllMarkets(
client: MangoClient,
group: Group,
): Promise<TransactionSignature[]> {
): Promise<MangoSignatureStatus[]> {
// Future: collect ixs, batch them, and send them in in fewer txs
return await Promise.all(
this.serum3Active().map((s) => {
@ -1874,6 +1875,26 @@ export class TokenConditionalSwap {
return this.expiryTimestamp.toNumber();
}
// TODO: will be replaced by onchain enum in next release
private getTokenConditionalSwapDisplayPriceStyle(group: Group): boolean {
const buyBank = this.getBuyToken(group);
const sellBank = this.getSellToken(group);
// If we are tp/sl'ing SOL borrow, then price is stored in sol/usdc
// then don't flip
if (sellBank.tokenIndex == 0) {
return true;
}
// E.g.
// If we are tp/sl'ing SOL deposit, then price is stored in usdc/sol
if (this.maxSell.eq(U64_MAX_BN)) {
true; // dont flip, i.e. continue using sellTokenPerBuyTokenUi price
}
// Flip the price if we know we are selling an exact amount of SOL
return false; // flip, i.e. use buyTokenPerSellTokenUi price
}
private priceLimitToUi(
group: Group,
sellTokenPerBuyTokenNative: number,
@ -1891,7 +1912,7 @@ export class TokenConditionalSwap {
// buytoken/selltoken or selltoken/buytoken
// Buy limit / close short
if (this.maxSell.eq(U64_MAX_BN)) {
if (this.getTokenConditionalSwapDisplayPriceStyle(group)) {
return roundTo5(sellTokenPerBuyTokenUi);
}
@ -1909,20 +1930,44 @@ export class TokenConditionalSwap {
}
getThresholdPriceUi(group: Group): number {
const a = I80F48.fromNumber(this.priceLowerLimit);
const b = I80F48.fromNumber(this.priceUpperLimit);
const buyBank = this.getBuyToken(group);
const sellBank = this.getSellToken(group);
const o = buyBank.price.div(sellBank.price);
const a = toUiSellPerBuyTokenPrice(this.priceLowerLimit, sellBank, buyBank);
const b = toUiSellPerBuyTokenPrice(this.priceUpperLimit, sellBank, buyBank);
const o = buyBank.uiPrice / sellBank.uiPrice;
// Choose the price closest to oracle
if (o.sub(a).abs().lt(o.sub(b).abs())) {
if (Math.abs(o - a) < Math.abs(o - b)) {
return this.getPriceLowerLimitUi(group);
}
return this.getPriceUpperLimitUi(group);
}
getCurrentPairPriceUi(group: Group): number {
const buyBank = this.getBuyToken(group);
const sellBank = this.getSellToken(group);
const sellTokenPerBuyTokenUi = toUiSellPerBuyTokenPrice(
buyBank.price.div(sellBank.price).toNumber(),
sellBank,
buyBank,
);
// Below are workarounds to know when to show an inverted price in ui
// We want to identify if the pair user is wanting to trade is
// buytoken/selltoken or selltoken/buytoken
// Buy limit / close short
if (this.getTokenConditionalSwapDisplayPriceStyle(group)) {
return roundTo5(sellTokenPerBuyTokenUi);
}
// Stop loss / take profit
const buyTokenPerSellTokenUi = 1 / sellTokenPerBuyTokenUi;
return roundTo5(buyTokenPerSellTokenUi);
}
// in percent
getPricePremium(): number {
return this.pricePremiumRate * 100;
@ -1945,16 +1990,20 @@ export class TokenConditionalSwap {
}
toString(group: Group): string {
return `getMaxBuy ${this.getMaxBuyUi(
return `${
group.getFirstBankByTokenIndex(this.buyTokenIndex).name +
'/' +
group.getFirstBankByTokenIndex(this.sellTokenIndex).name
} , getMaxBuy ${this.getMaxBuyUi(group)}, getMaxSell ${this.getMaxSellUi(
group,
)}, getMaxSell ${this.getMaxSellUi(group)}, bought ${this.getBoughtUi(
group,
)}, sold ${this.getSoldUi(
)}, bought ${this.getBoughtUi(group)}, sold ${this.getSoldUi(
group,
)}, getPriceLowerLimitUi ${this.getPriceLowerLimitUi(
group,
)}, getPriceUpperLimitUi ${this.getPriceUpperLimitUi(
group,
)}, getCurrentPairPriceUi ${this.getCurrentPairPriceUi(
group,
)}, getThresholdPriceUi ${this.getThresholdPriceUi(
group,
)}, getPricePremium ${this.getPricePremium()}, expiry ${this.expiryTimestamp.toString()}`;

View File

@ -408,6 +408,13 @@ export class PerpMarket {
return funding;
}
public getInstantaneousFundingRatePerSecond(
bids: BookSide,
asks: BookSide,
): number {
return this.getInstantaneousFundingRate(bids, asks) / (24 * 60 * 60);
}
/**
*
* Returns instantaneous funding rate for the day. How is it actually applied - funding is

View File

@ -90,7 +90,7 @@ import {
toNative,
toNativeSellPerBuyTokenPrice,
} from './utils';
import { sendTransaction } from './utils/rpc';
import { MangoSignatureStatus, sendTransaction } from './utils/rpc';
import { NATIVE_MINT, TOKEN_PROGRAM_ID } from './utils/spl';
export const DEFAULT_TOKEN_CONDITIONAL_SWAP_COUNT = 8;
@ -109,6 +109,7 @@ export type MangoClientOptions = {
estimateFee?: boolean;
txConfirmationCommitment?: Commitment;
openbookFeesToDao?: boolean;
prependedGlobalAdditionalInstructions?: TransactionInstruction[];
};
export class MangoClient {
@ -118,6 +119,7 @@ export class MangoClient {
private estimateFee: boolean;
private txConfirmationCommitment: Commitment;
private openbookFeesToDao: boolean;
private prependedGlobalAdditionalInstructions: TransactionInstruction[] = [];
constructor(
public program: Program<MangoV4>,
@ -130,6 +132,8 @@ export class MangoClient {
this.estimateFee = opts?.estimateFee || false;
this.postSendTxCallback = opts?.postSendTxCallback;
this.openbookFeesToDao = opts?.openbookFeesToDao ?? true;
this.prependedGlobalAdditionalInstructions =
opts.prependedGlobalAdditionalInstructions ?? [];
this.txConfirmationCommitment =
opts?.txConfirmationCommitment ??
(program.provider as AnchorProvider).opts.commitment ??
@ -151,7 +155,7 @@ export class MangoClient {
public async sendAndConfirmTransaction(
ixs: TransactionInstruction[],
opts: any = {},
): Promise<string> {
): Promise<MangoSignatureStatus> {
let prioritizationFee: number;
if (opts.prioritizationFee) {
prioritizationFee = opts.prioritizationFee;
@ -160,9 +164,9 @@ export class MangoClient {
} else {
prioritizationFee = this.prioritizationFee;
}
return await sendTransaction(
const status = await sendTransaction(
this.program.provider as AnchorProvider,
ixs,
[...this.prependedGlobalAdditionalInstructions, ...ixs],
opts.alts ?? [],
{
postSendTxCallback: this.postSendTxCallback,
@ -171,13 +175,14 @@ export class MangoClient {
...opts,
},
);
return status;
}
private async sendAndConfirmTransactionForGroup(
public async sendAndConfirmTransactionForGroup(
group: Group,
ixs: TransactionInstruction[],
opts: any = {},
): Promise<string> {
): Promise<MangoSignatureStatus> {
return await this.sendAndConfirmTransaction(ixs, {
alts: group.addressLookupTablesList,
...opts,
@ -188,7 +193,7 @@ export class MangoClient {
group: Group,
bank: Bank,
tokenAccountPk: PublicKey,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const admin = (this.program.provider as AnchorProvider).wallet.publicKey;
const ix = await this.program.methods
.adminTokenWithdrawFees()
@ -207,7 +212,7 @@ export class MangoClient {
group: Group,
perpMarket: PerpMarket,
tokenAccountPk: PublicKey,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const bank = group.getFirstBankByTokenIndex(perpMarket.settleTokenIndex);
const admin = (this.program.provider as AnchorProvider).wallet.publicKey;
const ix = await this.program.methods
@ -230,7 +235,7 @@ export class MangoClient {
testing: boolean,
version: number,
insuranceMintPk: PublicKey,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const adminPk = (this.program.provider as AnchorProvider).wallet.publicKey;
const ix = await this.program.methods
.groupCreate(groupNum, testing ? 1 : 0, version)
@ -256,7 +261,7 @@ export class MangoClient {
feesSwapMangoAccount?: PublicKey,
feesMngoTokenIndex?: TokenIndex,
feesExpiryInterval?: BN,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.program.methods
.groupEdit(
admin ?? null,
@ -282,7 +287,7 @@ export class MangoClient {
public async ixGateSet(
group: Group,
ixGateParams: IxGateParams,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.program.methods
.ixGateSet(buildIxGate(ixGateParams))
.accounts({
@ -293,7 +298,7 @@ export class MangoClient {
return await this.sendAndConfirmTransactionForGroup(group, [ix]);
}
public async groupClose(group: Group): Promise<TransactionSignature> {
public async groupClose(group: Group): Promise<MangoSignatureStatus> {
const adminPk = (this.program.provider as AnchorProvider).wallet.publicKey;
const ix = await this.program.methods
.groupClose()
@ -379,7 +384,7 @@ export class MangoClient {
tokenIndex: number,
name: string,
params: TokenRegisterParams,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.program.methods
.tokenRegister(
tokenIndex,
@ -424,7 +429,7 @@ export class MangoClient {
oraclePk: PublicKey,
tokenIndex: number,
name: string,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.program.methods
.tokenRegisterTrustless(tokenIndex, name)
.accounts({
@ -443,7 +448,7 @@ export class MangoClient {
group: Group,
mintPk: PublicKey,
params: TokenEditParams,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const bank = group.getFirstBankByMint(mintPk);
const mintInfo = group.mintInfosMapByTokenIndex.get(bank.tokenIndex)!;
@ -505,7 +510,7 @@ export class MangoClient {
assetTokenIndex: TokenIndex,
liabTokenIndex: TokenIndex,
maxLiabTransfer?: number,
): Promise<string> {
): Promise<MangoSignatureStatus> {
const assetBank = group.getFirstBankByTokenIndex(assetTokenIndex);
const liabBank = group.getFirstBankByTokenIndex(liabTokenIndex);
const healthRemainingAccounts: PublicKey[] =
@ -548,7 +553,7 @@ export class MangoClient {
public async tokenDeregister(
group: Group,
mintPk: PublicKey,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const bank = group.getFirstBankByMint(mintPk);
const adminPk = (this.program.provider as AnchorProvider).wallet.publicKey;
@ -652,7 +657,7 @@ export class MangoClient {
group: Group,
mintPk: PublicKey,
price: number,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.program.methods
.stubOracleCreate({ val: I80F48.fromNumber(price).getData() })
.accounts({
@ -668,7 +673,7 @@ export class MangoClient {
public async stubOracleClose(
group: Group,
oracle: PublicKey,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.program.methods
.stubOracleClose()
.accounts({
@ -685,7 +690,7 @@ export class MangoClient {
group: Group,
oraclePk: PublicKey,
price: number,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.program.methods
.stubOracleSet({ val: I80F48.fromNumber(price).getData() })
.accounts({
@ -734,7 +739,7 @@ export class MangoClient {
serum3Count?: number,
perpCount?: number,
perpOoCount?: number,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.program.methods
.accountCreate(
accountNumber ?? 0,
@ -761,7 +766,7 @@ export class MangoClient {
serum3Count: number,
perpCount: number,
perpOoCount: number,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.program.methods
.accountExpand(tokenCount, serum3Count, perpCount, perpOoCount)
.accounts({
@ -782,7 +787,7 @@ export class MangoClient {
perpCount: number,
perpOoCount: number,
tokenConditionalSwapCount: number,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.accountExpandV2Ix(
group,
account,
@ -828,7 +833,7 @@ export class MangoClient {
delegate?: PublicKey,
temporaryDelegate?: PublicKey,
delegateExpiry?: number,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.program.methods
.accountEdit(
name ?? null,
@ -849,7 +854,7 @@ export class MangoClient {
public async computeAccountData(
group: Group,
mangoAccount: MangoAccount,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const healthRemainingAccounts: PublicKey[] =
this.buildHealthRemainingAccounts(group, [mangoAccount], [], []);
@ -874,7 +879,7 @@ export class MangoClient {
group: Group,
mangoAccount: MangoAccount,
freeze: boolean,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.program.methods
.accountToggleFreeze(freeze)
.accounts({
@ -900,7 +905,7 @@ export class MangoClient {
private async getMangoAccountFromPk(
mangoAccountPk: PublicKey,
): Promise<MangoAccount> {
return await this.getMangoAccountFromAi(
return this.getMangoAccountFromAi(
mangoAccountPk,
(await this.program.provider.connection.getAccountInfo(
mangoAccountPk,
@ -908,10 +913,10 @@ export class MangoClient {
);
}
public async getMangoAccountFromAi(
public getMangoAccountFromAi(
mangoAccountPk: PublicKey,
ai: AccountInfo<Buffer>,
): Promise<MangoAccount> {
): MangoAccount {
const decodedMangoAccount = this.program.coder.accounts.decode(
'mangoAccount',
ai.data,
@ -1190,7 +1195,7 @@ export class MangoClient {
group: Group,
mangoAccount: MangoAccount,
forceClose = false,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.program.methods
.accountClose(forceClose)
.accounts({
@ -1207,7 +1212,7 @@ export class MangoClient {
public async emptyAndCloseMangoAccount(
group: Group,
mangoAccount: MangoAccount,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
// Work on a deep cloned mango account, since we would deactivating positions
// before deactivation reaches on-chain state in order to simplify building a fresh list
// of healthRemainingAccounts to each subsequent ix
@ -1292,7 +1297,7 @@ export class MangoClient {
group: Group,
mangoAccount: MangoAccount,
maxBuyback?: number,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.accountBuybackFeesWithMngoIx(
group,
mangoAccount,
@ -1307,7 +1312,7 @@ export class MangoClient {
mintPk: PublicKey,
amount: number,
reduceOnly = false,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const decimals = group.getMintDecimals(mintPk);
const nativeAmount = toNative(amount, decimals);
return await this.tokenDepositNative(
@ -1325,23 +1330,22 @@ export class MangoClient {
mintPk: PublicKey,
nativeAmount: BN,
reduceOnly = false,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const bank = group.getFirstBankByMint(mintPk);
const tokenAccountPk = await getAssociatedTokenAddress(
mintPk,
mangoAccount.owner,
true,
);
let wrappedSolAccount: Keypair | undefined;
let wrappedSolAccount: PublicKey | undefined;
let preInstructions: TransactionInstruction[] = [];
let postInstructions: TransactionInstruction[] = [];
if (mintPk.equals(NATIVE_MINT)) {
// Generate a random seed for wrappedSolAccount.
const seed = Keypair.generate().publicKey.toBase58().slice(0, 32);
// Calculate a publicKey that will be controlled by the `mangoAccount.owner`.
const wrappedSolAccount = await PublicKey.createWithSeed(
wrappedSolAccount = await PublicKey.createWithSeed(
mangoAccount.owner,
seed,
TOKEN_PROGRAM_ID,
@ -1386,7 +1390,7 @@ export class MangoClient {
bank: bank.publicKey,
vault: bank.vault,
oracle: bank.oracle,
tokenAccount: wrappedSolAccount?.publicKey ?? tokenAccountPk,
tokenAccount: wrappedSolAccount ?? tokenAccountPk,
tokenAuthority: mangoAccount.owner,
})
.remainingAccounts(
@ -1410,7 +1414,7 @@ export class MangoClient {
mintPk: PublicKey,
amount: number,
allowBorrow: boolean,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const nativeAmount = toNative(amount, group.getMintDecimals(mintPk));
const ixes = await this.tokenWithdrawNativeIx(
group,
@ -1493,7 +1497,7 @@ export class MangoClient {
mintPk: PublicKey,
nativeAmount: BN,
allowBorrow: boolean,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ixs = await this.tokenWithdrawNativeIx(
group,
mangoAccount,
@ -1513,7 +1517,7 @@ export class MangoClient {
quoteBank: Bank,
marketIndex: number,
name: string,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.program.methods
.serum3RegisterMarket(marketIndex, name)
.accounts({
@ -1535,7 +1539,7 @@ export class MangoClient {
reduceOnly: boolean | null,
forceClose: boolean | null,
name: string | null,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const serum3Market =
group.serum3MarketsMapByMarketIndex.get(serum3MarketIndex);
const ix = await this.program.methods
@ -1552,7 +1556,7 @@ export class MangoClient {
public async serum3deregisterMarket(
group: Group,
externalMarketPk: PublicKey,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const serum3Market = group.serum3MarketsMapByExternal.get(
externalMarketPk.toBase58(),
)!;
@ -1625,7 +1629,7 @@ export class MangoClient {
group: Group,
mangoAccount: MangoAccount,
externalMarketPk: PublicKey,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const serum3Market: Serum3Market = group.serum3MarketsMapByExternal.get(
externalMarketPk.toBase58(),
)!;
@ -1701,7 +1705,7 @@ export class MangoClient {
group: Group,
mangoAccount: MangoAccount,
externalMarketPk: PublicKey,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.serum3CloseOpenOrdersIx(
group,
mangoAccount,
@ -1723,7 +1727,7 @@ export class MangoClient {
mangoAccount: MangoAccount,
externalMarketPk: PublicKey,
limit?: number,
): Promise<string> {
): Promise<MangoSignatureStatus> {
const serum3Market = group.serum3MarketsMapByExternal.get(
externalMarketPk.toBase58(),
)!;
@ -1918,7 +1922,7 @@ export class MangoClient {
orderType: Serum3OrderType,
clientOrderId: number,
limit: number,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const placeOrderIxes = await this.serum3PlaceOrderIx(
group,
mangoAccount,
@ -1982,7 +1986,7 @@ export class MangoClient {
mangoAccount: MangoAccount,
externalMarketPk: PublicKey,
limit?: number,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
return await this.sendAndConfirmTransactionForGroup(group, [
await this.serum3CancelAllOrdersIx(
group,
@ -2076,7 +2080,7 @@ export class MangoClient {
group: Group,
mangoAccount: MangoAccount,
externalMarketPk: PublicKey,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.serum3SettleFundsV2Ix(
group,
mangoAccount,
@ -2126,7 +2130,7 @@ export class MangoClient {
externalMarketPk: PublicKey,
side: Serum3Side,
orderId: BN,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ixes = await Promise.all([
this.serum3CancelOrderIx(
group,
@ -2173,7 +2177,7 @@ export class MangoClient {
settlePnlLimitFactor: number,
settlePnlLimitWindowSize: number,
positivePnlLiquidationFee: number,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const bids = new Keypair();
const asks = new Keypair();
const eventQueue = new Keypair();
@ -2272,7 +2276,7 @@ export class MangoClient {
group: Group,
perpMarketIndex: PerpMarketIndex,
params: PerpEditParams,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex);
const ix = await this.program.methods
@ -2325,7 +2329,7 @@ export class MangoClient {
perpMarketIndex: PerpMarketIndex,
accountA: MangoAccount,
accountB: MangoAccount,
): Promise<string> {
): Promise<MangoSignatureStatus> {
const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex);
const ix = await this.program.methods
@ -2344,7 +2348,7 @@ export class MangoClient {
public async perpCloseMarket(
group: Group,
perpMarketIndex: PerpMarketIndex,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex);
const ix = await this.program.methods
@ -2410,7 +2414,7 @@ export class MangoClient {
group: Group,
mangoAccount: MangoAccount,
perpMarketIndex: PerpMarketIndex,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.perpDeactivatePositionIx(
group,
mangoAccount,
@ -2423,7 +2427,7 @@ export class MangoClient {
group: Group,
mangoAccount: MangoAccount,
slippage = 0.01, // 1%, 100bps
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
if (mangoAccount.perpActive().length == 0) {
throw new Error(`No perp positions found.`);
}
@ -2447,7 +2451,7 @@ export class MangoClient {
pa.marketIndex,
isLong ? PerpOrderSide.ask : PerpOrderSide.bid,
pm.uiPrice * (isLong ? 1 - slippage : 1 + slippage), // Try to cross the spread to guarantee matching
pa.getBasePositionUi(pm) * 1.01, // Send a larger size to ensure full order is closed
Math.abs(pa.getBasePositionUi(pm) * 1.01), // Send a larger size to ensure full order is closed
undefined,
Date.now(),
PerpOrderType.immediateOrCancel,
@ -2485,7 +2489,7 @@ export class MangoClient {
reduceOnly?: boolean,
expiryTimestamp?: number,
limit?: number,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.perpPlaceOrderV2Ix(
group,
mangoAccount,
@ -2633,7 +2637,7 @@ export class MangoClient {
reduceOnly?: boolean,
expiryTimestamp?: number,
limit?: number,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.perpPlaceOrderPeggedV2Ix(
group,
mangoAccount,
@ -2774,6 +2778,26 @@ export class MangoClient {
.instruction();
}
public async perpCancelOrderByClientOrderIdIx(
group: Group,
mangoAccount: MangoAccount,
perpMarketIndex: PerpMarketIndex,
clientOrderId: BN,
): Promise<TransactionInstruction> {
const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex);
return await this.program.methods
.perpCancelOrderByClientOrderId(new BN(clientOrderId))
.accounts({
group: group.publicKey,
account: mangoAccount.publicKey,
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
perpMarket: perpMarket.publicKey,
bids: perpMarket.bids,
asks: perpMarket.asks,
})
.instruction();
}
public async perpCancelOrderIx(
group: Group,
mangoAccount: MangoAccount,
@ -2799,7 +2823,7 @@ export class MangoClient {
mangoAccount: MangoAccount,
perpMarketIndex: PerpMarketIndex,
orderId: BN,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.perpCancelOrderIx(
group,
mangoAccount,
@ -2815,7 +2839,7 @@ export class MangoClient {
mangoAccount: MangoAccount,
perpMarketIndex: PerpMarketIndex,
limit: number,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.perpCancelAllOrdersIx(
group,
mangoAccount,
@ -2851,7 +2875,7 @@ export class MangoClient {
group: Group,
mangoAccount: MangoAccount,
allMangoAccounts?: MangoAccount[],
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
if (!allMangoAccounts) {
allMangoAccounts = await client.getAllMangoAccounts(group, true);
}
@ -2932,7 +2956,7 @@ export class MangoClient {
settler: MangoAccount,
perpMarketIndex: PerpMarketIndex,
maxSettleAmount?: number,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
return await this.sendAndConfirmTransactionForGroup(group, [
await this.perpSettlePnlIx(
group,
@ -2956,7 +2980,7 @@ export class MangoClient {
unprofitableAccount: MangoAccount,
settler: MangoAccount,
perpMarketIndex: PerpMarketIndex,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
return await this.sendAndConfirmTransactionForGroup(group, [
await this.perpSettlePnlIx(
group,
@ -3012,7 +3036,7 @@ export class MangoClient {
account: MangoAccount,
perpMarketIndex: PerpMarketIndex,
maxSettleAmount?: number,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
return await this.sendAndConfirmTransactionForGroup(group, [
await this.perpSettleFeesIx(
group,
@ -3064,7 +3088,7 @@ export class MangoClient {
perpMarketIndex: PerpMarketIndex,
accounts: PublicKey[],
limit: number,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
return await this.sendAndConfirmTransactionForGroup(group, [
await this.perpConsumeEventsIx(group, perpMarketIndex, accounts, limit),
]);
@ -3162,7 +3186,7 @@ export class MangoClient {
userDefinedInstructions: TransactionInstruction[];
userDefinedAlts: AddressLookupTableAccount[];
flashLoanType: FlashLoanType;
}): Promise<TransactionSignature> {
}): Promise<MangoSignatureStatus> {
const isDelegate = (
this.program.provider as AnchorProvider
).wallet.publicKey.equals(mangoAccount.delegate);
@ -3325,7 +3349,7 @@ export class MangoClient {
public async tokenUpdateIndexAndRate(
group: Group,
mintPk: PublicKey,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
return await this.sendAndConfirmTransactionForGroup(group, [
await this.tokenUpdateIndexAndRateIx(group, mintPk),
]);
@ -3365,7 +3389,7 @@ export class MangoClient {
assetMintPk: PublicKey,
liabMintPk: PublicKey,
maxLiabTransfer: number,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const assetBank: Bank = group.getFirstBankByMint(assetMintPk);
const liabBank: Bank = group.getFirstBankByMint(liabMintPk);
@ -3415,7 +3439,7 @@ export class MangoClient {
maxSellUi: number | null,
pricePremium: number | null,
expiryTimestamp: number | null,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
if (account.getTokenBalanceUi(sellBank) < 0) {
throw new Error(
`Only allowed to take profits on deposits! Current balance ${account.getTokenBalanceUi(
@ -3424,13 +3448,24 @@ export class MangoClient {
);
}
if (!thresholdPriceInSellPerBuyToken) {
thresholdPriceUi = 1 / thresholdPriceUi;
}
const thresholdPrice = toNativeSellPerBuyTokenPrice(
thresholdPriceUi,
sellBank,
buyBank,
);
const lowerLimit = 0;
const upperLimit = thresholdPrice;
return await this.tokenConditionalSwapCreate(
group,
account,
sellBank,
buyBank,
thresholdPriceUi,
thresholdPriceInSellPerBuyToken,
lowerLimit,
upperLimit,
Number.MAX_SAFE_INTEGER,
maxSellUi ?? account.getTokenBalanceUi(sellBank),
'TakeProfitOnDeposit',
@ -3438,6 +3473,7 @@ export class MangoClient {
true,
false,
expiryTimestamp,
thresholdPriceInSellPerBuyToken,
);
}
@ -3451,7 +3487,7 @@ export class MangoClient {
maxSellUi: number | null,
pricePremium: number | null,
expiryTimestamp: number | null,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
if (account.getTokenBalanceUi(sellBank) < 0) {
throw new Error(
`Only allowed to set a stop loss on deposits! Current balance ${account.getTokenBalanceUi(
@ -3460,13 +3496,24 @@ export class MangoClient {
);
}
if (!thresholdPriceInSellPerBuyToken) {
thresholdPriceUi = 1 / thresholdPriceUi;
}
const thresholdPrice = toNativeSellPerBuyTokenPrice(
thresholdPriceUi,
sellBank,
buyBank,
);
const lowerLimit = thresholdPrice;
const upperLimit = Number.MAX_SAFE_INTEGER;
return await this.tokenConditionalSwapCreate(
group,
account,
sellBank,
buyBank,
thresholdPriceUi,
thresholdPriceInSellPerBuyToken,
lowerLimit,
upperLimit,
Number.MAX_SAFE_INTEGER,
maxSellUi ?? account.getTokenBalanceUi(sellBank),
'StopLossOnDeposit',
@ -3474,6 +3521,7 @@ export class MangoClient {
true,
false,
expiryTimestamp,
thresholdPriceInSellPerBuyToken,
);
}
@ -3488,7 +3536,7 @@ export class MangoClient {
pricePremium: number | null,
allowMargin: boolean | null,
expiryTimestamp: number | null,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
if (account.getTokenBalanceUi(buyBank) > 0) {
throw new Error(
`Only allowed to take profits on borrows! Current balance ${account.getTokenBalanceUi(
@ -3497,13 +3545,24 @@ export class MangoClient {
);
}
if (!thresholdPriceInSellPerBuyToken) {
thresholdPriceUi = 1 / thresholdPriceUi;
}
const thresholdPrice = toNativeSellPerBuyTokenPrice(
thresholdPriceUi,
sellBank,
buyBank,
);
const lowerLimit = thresholdPrice;
const upperLimit = Number.MAX_SAFE_INTEGER;
return await this.tokenConditionalSwapCreate(
group,
account,
sellBank,
buyBank,
thresholdPriceUi,
thresholdPriceInSellPerBuyToken,
lowerLimit,
upperLimit,
maxBuyUi ?? -account.getTokenBalanceUi(buyBank),
Number.MAX_SAFE_INTEGER,
'TakeProfitOnBorrow',
@ -3511,6 +3570,7 @@ export class MangoClient {
false,
allowMargin ?? false,
expiryTimestamp,
thresholdPriceInSellPerBuyToken,
);
}
@ -3525,7 +3585,7 @@ export class MangoClient {
pricePremium: number | null,
allowMargin: boolean | null,
expiryTimestamp: number | null,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
if (account.getTokenBalanceUi(buyBank) > 0) {
throw new Error(
`Only allowed to set stop loss on borrows! Current balance ${account.getTokenBalanceUi(
@ -3534,13 +3594,24 @@ export class MangoClient {
);
}
if (!thresholdPriceInSellPerBuyToken) {
thresholdPriceUi = 1 / thresholdPriceUi;
}
const thresholdPrice = toNativeSellPerBuyTokenPrice(
thresholdPriceUi,
sellBank,
buyBank,
);
const lowerLimit = 0;
const upperLimit = thresholdPrice;
return await this.tokenConditionalSwapCreate(
group,
account,
sellBank,
buyBank,
thresholdPriceUi,
thresholdPriceInSellPerBuyToken,
lowerLimit,
upperLimit,
maxBuyUi ?? -account.getTokenBalanceUi(buyBank),
Number.MAX_SAFE_INTEGER,
'StopLossOnBorrow',
@ -3548,6 +3619,7 @@ export class MangoClient {
false,
allowMargin ?? false,
expiryTimestamp,
thresholdPriceInSellPerBuyToken,
);
}
@ -3556,8 +3628,8 @@ export class MangoClient {
account: MangoAccount,
sellBank: Bank,
buyBank: Bank,
thresholdPriceUi: number,
thresholdPriceInSellPerBuyToken: boolean,
lowerLimit: number,
upperLimit: number,
maxBuyUi: number,
maxSellUi: number,
tcsIntention:
@ -3570,50 +3642,43 @@ export class MangoClient {
allowCreatingDeposits: boolean,
allowCreatingBorrows: boolean,
expiryTimestamp: number | null,
): Promise<TransactionSignature> {
const maxBuy =
maxBuyUi == Number.MAX_SAFE_INTEGER
? U64_MAX_BN
: toNative(maxBuyUi, buyBank.mintDecimals);
const maxSell =
maxSellUi == Number.MAX_SAFE_INTEGER
? U64_MAX_BN
: toNative(maxSellUi, sellBank.mintDecimals);
if (!thresholdPriceInSellPerBuyToken) {
thresholdPriceUi = 1 / thresholdPriceUi;
displayPriceInSellTokenPerBuyToken: boolean,
): Promise<MangoSignatureStatus> {
let maxBuy, maxSell, buyAmountInUsd, sellAmountInUsd;
if (maxBuyUi == Number.MAX_SAFE_INTEGER) {
maxBuy = U64_MAX_BN;
} else {
buyAmountInUsd = maxBuyUi * buyBank.uiPrice;
maxBuy = toNative(maxBuyUi, buyBank.mintDecimals);
}
if (maxSellUi == Number.MAX_SAFE_INTEGER) {
maxSell = U64_MAX_BN;
} else {
sellAmountInUsd = maxSellUi * sellBank.uiPrice;
maxSell = toNative(maxSellUi, sellBank.mintDecimals);
}
let lowerLimit, upperLimit;
const thresholdPrice = toNativeSellPerBuyTokenPrice(
thresholdPriceUi,
sellBank,
buyBank,
);
const sellTokenPerBuyTokenPrice = buyBank.price
.div(sellBank.price)
.toNumber();
if (
tcsIntention == 'TakeProfitOnDeposit' ||
tcsIntention == 'StopLossOnBorrow' ||
(tcsIntention == null && thresholdPrice > sellTokenPerBuyTokenPrice)
) {
lowerLimit = thresholdPrice;
upperLimit = Number.MAX_SAFE_INTEGER;
} else {
lowerLimit = 0;
upperLimit = thresholdPrice;
// Used for computing optimal premium
let liqorTcsChunkSizeInUsd = Math.min(buyAmountInUsd, sellAmountInUsd);
if (liqorTcsChunkSizeInUsd > 5000) {
liqorTcsChunkSizeInUsd = 5000;
}
// For small TCS swaps, reduce chunk size to 1000 USD
else {
liqorTcsChunkSizeInUsd = 1000;
}
if (!pricePremium) {
if (maxBuy.eq(U64_MAX_BN)) {
maxSell.toNumber() * sellBank.uiPrice;
}
const buyTokenPriceImpact = group.getPriceImpactByTokenIndex(
buyBank.tokenIndex,
5000,
liqorTcsChunkSizeInUsd,
);
const sellTokenPriceImpact = group.getPriceImpactByTokenIndex(
sellBank.tokenIndex,
5000,
liqorTcsChunkSizeInUsd,
);
pricePremium =
((1 + buyTokenPriceImpact / 100) * (1 + sellTokenPriceImpact / 100) -
@ -3650,7 +3715,7 @@ export class MangoClient {
pricePremiumRate,
allowCreatingDeposits,
allowCreatingBorrows,
thresholdPriceInSellPerBuyToken
displayPriceInSellTokenPerBuyToken
? TokenConditionalSwapDisplayPriceStyle.sellTokenPerBuyToken
: TokenConditionalSwapDisplayPriceStyle.buyTokenPerSellToken,
intention,
@ -3672,7 +3737,7 @@ export class MangoClient {
allowCreatingBorrows: boolean,
priceDisplayStyle: TokenConditionalSwapDisplayPriceStyle,
intention: TokenConditionalSwapIntention,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const buyBank: Bank = group.getFirstBankByMint(buyMintPk);
const sellBank: Bank = group.getFirstBankByMint(sellMintPk);
const tcsIx = await this.program.methods
@ -3720,7 +3785,7 @@ export class MangoClient {
group: Group,
account: MangoAccount,
tokenConditionalSwapId: BN,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const tokenConditionalSwapIndex = account.tokenConditionalSwaps.findIndex(
(tcs) => tcs.id.eq(tokenConditionalSwapId),
);
@ -3752,7 +3817,7 @@ export class MangoClient {
public async tokenConditionalSwapCancelAll(
group: Group,
account: MangoAccount,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ixs = await Promise.all(
account.tokenConditionalSwaps
.filter((tcs) => tcs.hasData)
@ -3785,7 +3850,7 @@ export class MangoClient {
tokenConditionalSwapId: BN,
maxBuyTokenToLiqee: number,
maxSellTokenToLiqor: number,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const tokenConditionalSwapIndex = liqee.tokenConditionalSwaps.findIndex(
(tcs) => tcs.id.eq(tokenConditionalSwapId),
);
@ -3841,7 +3906,7 @@ export class MangoClient {
group: Group,
addressLookupTable: PublicKey,
index: number,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.program.methods
.altSet(index)
.accounts({
@ -3859,7 +3924,7 @@ export class MangoClient {
addressLookupTable: PublicKey,
index: number,
pks: PublicKey[],
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const ix = await this.program.methods
.altExtend(index, pks)
.accounts({
@ -4143,7 +4208,7 @@ export class MangoClient {
reduceOnly?: boolean,
expiryTimestamp?: number,
limit?: number,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const transactionInstructions: TransactionInstruction[] = [];
const [cancelOrderIx, placeOrderIx] = await Promise.all([
this.perpCancelOrderIx(group, mangoAccount, perpMarketIndex, orderId),
@ -4181,7 +4246,7 @@ export class MangoClient {
orderType: Serum3OrderType,
clientOrderId: number,
limit: number,
): Promise<TransactionSignature> {
): Promise<MangoSignatureStatus> {
const transactionInstructions: TransactionInstruction[] = [];
const [cancelOrderIx, settleIx, placeOrderIx] = await Promise.all([
this.serum3CancelOrderIx(

View File

@ -8,7 +8,14 @@ export class Id {
public publicKey: string,
public serum3ProgramId: string,
public mangoProgramId: string,
public banks: { name: string; publicKey: string; active: boolean }[],
public banks: {
name: string;
mint: string;
tokenIndex: number;
publicKey: string;
active: boolean;
decimals: number;
}[],
public stubOracles: { name: string; publicKey: string }[],
public mintInfos: { name: string; publicKey: string }[],
public serum3Markets: {
@ -23,7 +30,7 @@ export class Id {
public getBanks(): PublicKey[] {
return Array.from(
this.banks
.filter((perpMarket) => perpMarket.active)
.filter((bank) => bank.active)
.map((bank) => new PublicKey(bank.publicKey)),
);
}
@ -43,19 +50,26 @@ export class Id {
public getSerum3Markets(): PublicKey[] {
return Array.from(
this.serum3Markets
.filter((perpMarket) => perpMarket.active)
.filter((serum3Market) => serum3Market.active)
.map((serum3Market) => new PublicKey(serum3Market.publicKey)),
);
}
public getSerum3ExternalMarkets(): PublicKey[] {
return Array.from(
this.serum3Markets
.filter((serum3Market) => serum3Market.active)
.map((serum3Market) => new PublicKey(serum3Market.marketExternal)),
);
}
public getPerpMarkets(): PublicKey[] {
return Array.from(
this.perpMarkets
.filter((perpMarket) => perpMarket.active)
.map((perpMarket) => new PublicKey(perpMarket.publicKey)),
this.perpMarkets.map((perpMarket) => new PublicKey(perpMarket.publicKey)),
);
}
// DEPRECATED
static fromIdsByName(name: string): Id {
const groupConfig = ids.groups.find((id) => id['name'] === name);
if (!groupConfig) throw new Error(`No group config ${name} found in Ids!`);
@ -73,6 +87,7 @@ export class Id {
);
}
// DEPRECATED
static fromIdsByPk(groupPk: PublicKey): Id {
const groupConfig = ids.groups.find(
(id) => id['publicKey'] === groupPk.toString(),
@ -115,6 +130,8 @@ export class Id {
tokenIndex: t.tokenIndex,
bankNum: b.bankNum,
publicKey: b.publicKey,
active: t.active,
decimals: t.decimals,
})),
),
groupConfig.stubOracles.map((s) => ({
@ -126,15 +143,18 @@ export class Id {
mint: t.mint,
tokenIndex: t.tokenIndex,
publicKey: t.mintInfo,
active: t.active,
})),
groupConfig.serum3Markets.map((s) => ({
name: s.name,
publicKey: s.publicKey,
marketExternal: s.marketExternal,
marketExternal: s.serumMarketExternal,
active: s.active,
})),
groupConfig.perpMarkets.map((p) => ({
name: p.name,
publicKey: p.publicKey,
active: p.active,
})),
);
}

View File

@ -26,4 +26,5 @@ export * from './router';
export * from './stats';
export * from './types';
export * from './utils';
export * from './utils/rpc';
export { Group, MANGO_V4_ID, MangoClient, OracleProvider, StubOracle };

View File

@ -8262,14 +8262,28 @@ export type MangoV4 = {
},
{
"name": "changeAmount",
"docs": [
"The amount by which the user's token position changed at the end",
"",
"So if the user repaid the approved_amount in full, it'd be 0.",
"",
"Does NOT include the loan_origination_fee or deposit_fee, so the true",
"change is `change_amount - loan_origination_fee - deposit_fee`."
],
"type": "i128"
},
{
"name": "loan",
"docs": [
"The amount that was a loan (<= approved_amount, depends on user's deposits)"
],
"type": "i128"
},
{
"name": "loanOriginationFee",
"docs": [
"The fee paid on the loan, not included in `loan` or `change_amount`"
],
"type": "i128"
},
{
@ -8286,7 +8300,19 @@ export type MangoV4 = {
},
{
"name": "depositFee",
"docs": [
"Deposit fee paid for positive change_amount.",
"",
"Not factored into change_amount."
],
"type": "i128"
},
{
"name": "approvedAmount",
"docs": [
"The amount that was transfered out to the user"
],
"type": "u64"
}
]
}
@ -20831,14 +20857,28 @@ export const IDL: MangoV4 = {
},
{
"name": "changeAmount",
"docs": [
"The amount by which the user's token position changed at the end",
"",
"So if the user repaid the approved_amount in full, it'd be 0.",
"",
"Does NOT include the loan_origination_fee or deposit_fee, so the true",
"change is `change_amount - loan_origination_fee - deposit_fee`."
],
"type": "i128"
},
{
"name": "loan",
"docs": [
"The amount that was a loan (<= approved_amount, depends on user's deposits)"
],
"type": "i128"
},
{
"name": "loanOriginationFee",
"docs": [
"The fee paid on the loan, not included in `loan` or `change_amount`"
],
"type": "i128"
},
{
@ -20855,7 +20895,19 @@ export const IDL: MangoV4 = {
},
{
"name": "depositFee",
"docs": [
"Deposit fee paid for positive change_amount.",
"",
"Not factored into change_amount."
],
"type": "i128"
},
{
"name": "approvedAmount",
"docs": [
"The amount that was transfered out to the user"
],
"type": "u64"
}
]
}

View File

@ -73,7 +73,7 @@ export function computePriceImpactOnJup(
(pi) => pi.symbol == tokenName && pi.target_amount == closestTo,
);
if (filteredPis.length > 0) {
return (filteredPis[0].max_price_impact_percent * 10000) / 100;
return (filteredPis[0].avg_price_impact_percent * 10000) / 100;
} else {
return -1;
}

View File

@ -5,16 +5,27 @@ import {
ComputeBudgetProgram,
MessageV0,
Signer,
TransactionConfirmationStatus,
TransactionError,
TransactionInstruction,
TransactionSignature,
VersionedTransaction,
} from '@solana/web3.js';
export interface MangoSignatureStatus {
slot: number;
confirmations: number | null;
err: TransactionError | null;
confirmationStatus?: TransactionConfirmationStatus;
signature: TransactionSignature;
}
export async function sendTransaction(
provider: AnchorProvider,
ixs: TransactionInstruction[],
alts: AddressLookupTableAccount[],
opts: any = {},
): Promise<string> {
): Promise<MangoSignatureStatus> {
const connection = provider.connection;
const latestBlockhash =
opts.latestBlockhash ??
@ -102,7 +113,7 @@ export async function sendTransaction(
});
}
return signature;
return { signature, ...status };
}
export const createComputeBudgetIx = (