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", "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" "type": "i128"
}, },
{ {
"name": "loan", "name": "loan",
"docs": [
"The amount that was a loan (<= approved_amount, depends on user's deposits)"
],
"type": "i128" "type": "i128"
}, },
{ {
"name": "loanOriginationFee", "name": "loanOriginationFee",
"docs": [
"The fee paid on the loan, not included in `loan` or `change_amount`"
],
"type": "i128" "type": "i128"
}, },
{ {
@ -8286,7 +8300,19 @@
}, },
{ {
"name": "depositFee", "name": "depositFee",
"docs": [
"Deposit fee paid for positive change_amount.",
"",
"Not factored into change_amount."
],
"type": "i128" "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", "name": "@blockworks-foundation/mango-v4",
"version": "0.18.14", "version": "0.19.14",
"description": "Typescript Client for mango-v4 program.", "description": "Typescript Client for mango-v4 program.",
"repository": "https://github.com/blockworks-foundation/mango-v4", "repository": "https://github.com/blockworks-foundation/mango-v4",
"author": { "author": {

View File

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

View File

@ -662,7 +662,7 @@ async function createAndPopulateAlt() {
); );
console.log(`ALT: set at index 0 for group...`); 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}`); console.log(`...https://explorer.solana.com/tx/${sig}`);
} catch (error) { } catch (error) {
console.log(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.reloadMintInfos(client, ids),
this.reloadSerum3Markets(client, ids).then(() => this.reloadSerum3Markets(client, ids).then(() =>
this.reloadSerum3ExternalMarkets(client), this.reloadSerum3ExternalMarkets(client, ids),
), ),
]); ]);
// console.timeEnd('group.reload'); // console.timeEnd('group.reload');
@ -274,23 +274,63 @@ export class Group {
); );
} }
public async reloadSerum3ExternalMarkets(client: MangoClient): Promise<void> { public async reloadSerum3ExternalMarkets(
const externalMarkets = await Promise.all( client: MangoClient,
Array.from(this.serum3MarketsMapByExternal.values()).map((serum3Market) => ids?: Id,
Market.load( ): Promise<void> {
client.program.provider.connection, let markets: Market[] = [];
serum3Market.serumMarketExternal, const externalMarketIds = ids?.getSerum3ExternalMarkets();
{ commitment: client.program.provider.connection.commitment },
OPENBOOK_PROGRAM_ID[client.cluster], 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( this.serum3ExternalMarketsMap = new Map(
Array.from(this.serum3MarketsMapByExternal.values()).map( Array.from(this.serum3MarketsMapByExternal.values()).map(
(serum3Market, index) => [ (serum3Market, index) => [
serum3Market.serumMarketExternal.toBase58(), serum3Market.serumMarketExternal.toBase58(),
externalMarkets[index], markets[index],
], ],
), ),
); );
@ -463,7 +503,6 @@ export class Group {
await client.program.provider.connection.getMultipleAccountsInfo( await client.program.provider.connection.getMultipleAccountsInfo(
vaultPks, vaultPks,
); );
const coder = new BorshAccountsCoder(client.program.idl);
this.vaultAmountsMap = new Map( this.vaultAmountsMap = new Map(
vaultAccounts.map((vaultAi, i) => { vaultAccounts.map((vaultAi, i) => {
if (!vaultAi) { if (!vaultAi) {

View File

@ -1,7 +1,7 @@
import { AnchorProvider, BN } from '@coral-xyz/anchor'; import { AnchorProvider, BN } from '@coral-xyz/anchor';
import { utf8 } from '@coral-xyz/anchor/dist/cjs/utils/bytes'; import { utf8 } from '@coral-xyz/anchor/dist/cjs/utils/bytes';
import { OpenOrders, Order, Orderbook } from '@project-serum/serum/lib/market'; 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 { MangoClient } from '../client';
import { OPENBOOK_PROGRAM_ID, RUST_I64_MAX, RUST_I64_MIN } from '../constants'; import { OPENBOOK_PROGRAM_ID, RUST_I64_MAX, RUST_I64_MIN } from '../constants';
import { I80F48, I80F48Dto, ONE_I80F48, ZERO_I80F48 } from '../numbers/I80F48'; import { I80F48, I80F48Dto, ONE_I80F48, ZERO_I80F48 } from '../numbers/I80F48';
@ -13,6 +13,7 @@ import {
toUiDecimalsForQuote, toUiDecimalsForQuote,
toUiSellPerBuyTokenPrice, toUiSellPerBuyTokenPrice,
} from '../utils'; } from '../utils';
import { MangoSignatureStatus } from '../utils/rpc';
import { Bank, TokenIndex } from './bank'; import { Bank, TokenIndex } from './bank';
import { Group } from './group'; import { Group } from './group';
import { HealthCache } from './healthCache'; import { HealthCache } from './healthCache';
@ -888,7 +889,7 @@ export class MangoAccount {
public async serum3SettleFundsForAllMarkets( public async serum3SettleFundsForAllMarkets(
client: MangoClient, client: MangoClient,
group: Group, group: Group,
): Promise<TransactionSignature[]> { ): Promise<MangoSignatureStatus[]> {
// Future: collect ixs, batch them, and send them in fewer txs // Future: collect ixs, batch them, and send them in fewer txs
return await Promise.all( return await Promise.all(
this.serum3Active().map((s) => { this.serum3Active().map((s) => {
@ -906,7 +907,7 @@ export class MangoAccount {
public async serum3CancelAllOrdersForAllMarkets( public async serum3CancelAllOrdersForAllMarkets(
client: MangoClient, client: MangoClient,
group: Group, group: Group,
): Promise<TransactionSignature[]> { ): Promise<MangoSignatureStatus[]> {
// Future: collect ixs, batch them, and send them in in fewer txs // Future: collect ixs, batch them, and send them in in fewer txs
return await Promise.all( return await Promise.all(
this.serum3Active().map((s) => { this.serum3Active().map((s) => {
@ -1874,6 +1875,26 @@ export class TokenConditionalSwap {
return this.expiryTimestamp.toNumber(); 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( private priceLimitToUi(
group: Group, group: Group,
sellTokenPerBuyTokenNative: number, sellTokenPerBuyTokenNative: number,
@ -1891,7 +1912,7 @@ export class TokenConditionalSwap {
// buytoken/selltoken or selltoken/buytoken // buytoken/selltoken or selltoken/buytoken
// Buy limit / close short // Buy limit / close short
if (this.maxSell.eq(U64_MAX_BN)) { if (this.getTokenConditionalSwapDisplayPriceStyle(group)) {
return roundTo5(sellTokenPerBuyTokenUi); return roundTo5(sellTokenPerBuyTokenUi);
} }
@ -1909,20 +1930,44 @@ export class TokenConditionalSwap {
} }
getThresholdPriceUi(group: Group): number { getThresholdPriceUi(group: Group): number {
const a = I80F48.fromNumber(this.priceLowerLimit);
const b = I80F48.fromNumber(this.priceUpperLimit);
const buyBank = this.getBuyToken(group); const buyBank = this.getBuyToken(group);
const sellBank = this.getSellToken(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 // 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.getPriceLowerLimitUi(group);
} }
return this.getPriceUpperLimitUi(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 // in percent
getPricePremium(): number { getPricePremium(): number {
return this.pricePremiumRate * 100; return this.pricePremiumRate * 100;
@ -1945,16 +1990,20 @@ export class TokenConditionalSwap {
} }
toString(group: Group): string { 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, group,
)}, getMaxSell ${this.getMaxSellUi(group)}, bought ${this.getBoughtUi( )}, bought ${this.getBoughtUi(group)}, sold ${this.getSoldUi(
group,
)}, sold ${this.getSoldUi(
group, group,
)}, getPriceLowerLimitUi ${this.getPriceLowerLimitUi( )}, getPriceLowerLimitUi ${this.getPriceLowerLimitUi(
group, group,
)}, getPriceUpperLimitUi ${this.getPriceUpperLimitUi( )}, getPriceUpperLimitUi ${this.getPriceUpperLimitUi(
group, group,
)}, getCurrentPairPriceUi ${this.getCurrentPairPriceUi(
group,
)}, getThresholdPriceUi ${this.getThresholdPriceUi( )}, getThresholdPriceUi ${this.getThresholdPriceUi(
group, group,
)}, getPricePremium ${this.getPricePremium()}, expiry ${this.expiryTimestamp.toString()}`; )}, getPricePremium ${this.getPricePremium()}, expiry ${this.expiryTimestamp.toString()}`;

View File

@ -408,6 +408,13 @@ export class PerpMarket {
return funding; 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 * Returns instantaneous funding rate for the day. How is it actually applied - funding is

View File

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

View File

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

View File

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

View File

@ -8262,14 +8262,28 @@ export type MangoV4 = {
}, },
{ {
"name": "changeAmount", "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" "type": "i128"
}, },
{ {
"name": "loan", "name": "loan",
"docs": [
"The amount that was a loan (<= approved_amount, depends on user's deposits)"
],
"type": "i128" "type": "i128"
}, },
{ {
"name": "loanOriginationFee", "name": "loanOriginationFee",
"docs": [
"The fee paid on the loan, not included in `loan` or `change_amount`"
],
"type": "i128" "type": "i128"
}, },
{ {
@ -8286,7 +8300,19 @@ export type MangoV4 = {
}, },
{ {
"name": "depositFee", "name": "depositFee",
"docs": [
"Deposit fee paid for positive change_amount.",
"",
"Not factored into change_amount."
],
"type": "i128" "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", "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" "type": "i128"
}, },
{ {
"name": "loan", "name": "loan",
"docs": [
"The amount that was a loan (<= approved_amount, depends on user's deposits)"
],
"type": "i128" "type": "i128"
}, },
{ {
"name": "loanOriginationFee", "name": "loanOriginationFee",
"docs": [
"The fee paid on the loan, not included in `loan` or `change_amount`"
],
"type": "i128" "type": "i128"
}, },
{ {
@ -20855,7 +20895,19 @@ export const IDL: MangoV4 = {
}, },
{ {
"name": "depositFee", "name": "depositFee",
"docs": [
"Deposit fee paid for positive change_amount.",
"",
"Not factored into change_amount."
],
"type": "i128" "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, (pi) => pi.symbol == tokenName && pi.target_amount == closestTo,
); );
if (filteredPis.length > 0) { if (filteredPis.length > 0) {
return (filteredPis[0].max_price_impact_percent * 10000) / 100; return (filteredPis[0].avg_price_impact_percent * 10000) / 100;
} else { } else {
return -1; return -1;
} }

View File

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