From cb3a43236df47189da984949569e69c5cdf420c1 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Mon, 3 Jul 2023 19:16:17 +0200 Subject: [PATCH 01/90] Revert "Pyth oracles: Check that status is "trading" (#607)" This reverts commit bbf01863988864240f33489866ee54a33906b780. --- programs/mango-v4/src/state/oracle.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/programs/mango-v4/src/state/oracle.rs b/programs/mango-v4/src/state/oracle.rs index a44fb0bb6..dde179807 100644 --- a/programs/mango-v4/src/state/oracle.rs +++ b/programs/mango-v4/src/state/oracle.rs @@ -165,17 +165,6 @@ pub fn oracle_price_and_state( let price_data = price_account.to_price(); let price = I80F48::from_num(price_data.price); - // Don't use price_data.status, because that has its own built-in staleness detection, - // check PriceAccount::to_price() impl. - if price_account.agg.status != pyth_sdk_solana::PriceStatus::Trading { - msg!( - "Pyth price status isn't 'Trading': status: {}", - price_data.status as u64 - ); - - return Err(MangoError::OracleStale.into()); - } - // Filter out bad prices if I80F48::from_num(price_data.conf) > (config.conf_filter * price) { msg!( From a9a3c63f915ccb8abe6610b0c801c4af107f6aed Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Mon, 3 Jul 2023 19:25:42 +0200 Subject: [PATCH 02/90] Changelog for v0.17.1 --- CHANGELOG.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 804e7393e..2eefd223a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,22 @@ Update this for each program release and mainnet deployment. ## not on mainnet -### v0.17.0, 2023-6- +### v0.17.1, 2023-7- + +- Remove extra Pyth oracle status check added in v0.17.0 + + The Pyth oracle status also reverts to Unknown if not enough publishers have + reported in a 25 slot window. So checking for the "Trading" status means an + implicit staleness limit of 25 slots. + + This staleness limit is much more strict than the ones configured on the + oracles currently used by Mango and caused occasional transaction failures. + +## mainnet + +### v0.17.0, 2023-7-3 + +Deployment: Jul 3, 2023 at 09:46:14 Central European Summer Time, https://explorer.solana.com/tx/4G6b1uihopkHqp968sq3RYacYHn5ND8mMmeNd1RfswTCmiqeappTN2747JTvswVXxs7oqgfU6M3VKPGVRFPGJYuL - Configurable perp market settle token (#550) @@ -41,8 +56,6 @@ Update this for each program release and mainnet deployment. - Improve docs (#590, #594) - Use workspace dependencies (#588) -## mainnet - ### v0.16.0, 2023-5-19 Deployment: May 19, 2023 at 15:35:12 Central European Summer Time, https://explorer.solana.com/tx/22fEcghPGgAnYCZkfjTxTeKQwX5rzWSx3c5CV9TikJmaAKWCpubCZYBx5ZJJPeNG1xWUPWMw3ooDhFBRYCR3tKYU From ae66dbc462f23404ca88f5f45750b14bca2a73b7 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Mon, 3 Jul 2023 19:54:21 +0200 Subject: [PATCH 03/90] Bump version and idl to v0.17.1 --- Cargo.lock | 2 +- mango_v4.json | 2 +- programs/mango-v4/Cargo.toml | 2 +- ts/client/src/mango_v4.ts | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 53565103d..7fc8b3c38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2928,7 +2928,7 @@ dependencies = [ [[package]] name = "mango-v4" -version = "0.17.0" +version = "0.17.1" dependencies = [ "anchor-lang", "anchor-spl", diff --git a/mango_v4.json b/mango_v4.json index e01fe4eb0..24ec90cb9 100644 --- a/mango_v4.json +++ b/mango_v4.json @@ -1,5 +1,5 @@ { - "version": "0.17.0", + "version": "0.17.1", "name": "mango_v4", "instructions": [ { diff --git a/programs/mango-v4/Cargo.toml b/programs/mango-v4/Cargo.toml index f6440faf4..bd3592f7d 100644 --- a/programs/mango-v4/Cargo.toml +++ b/programs/mango-v4/Cargo.toml @@ -2,7 +2,7 @@ cargo-features = ["workspace-inheritance"] [package] name = "mango-v4" -version = "0.17.0" +version = "0.17.1" description = "Created with Anchor" edition = "2021" diff --git a/ts/client/src/mango_v4.ts b/ts/client/src/mango_v4.ts index 0cdd4d006..3529d5146 100644 --- a/ts/client/src/mango_v4.ts +++ b/ts/client/src/mango_v4.ts @@ -1,5 +1,5 @@ export type MangoV4 = { - "version": "0.17.0", + "version": "0.17.1", "name": "mango_v4", "instructions": [ { @@ -10004,7 +10004,7 @@ export type MangoV4 = { }; export const IDL: MangoV4 = { - "version": "0.17.0", + "version": "0.17.1", "name": "mango_v4", "instructions": [ { From 580a56570530a829d83b292b5863e08d05aeef7d Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Tue, 4 Jul 2023 10:55:14 +0200 Subject: [PATCH 04/90] token-delegate-example Signed-off-by: microwavedcola1 --- .../scripts/archive/token-approve-test.ts | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 ts/client/scripts/archive/token-approve-test.ts diff --git a/ts/client/scripts/archive/token-approve-test.ts b/ts/client/scripts/archive/token-approve-test.ts new file mode 100644 index 000000000..ed2c3af9f --- /dev/null +++ b/ts/client/scripts/archive/token-approve-test.ts @@ -0,0 +1,107 @@ +import { + createApproveInstruction, + createSyncNativeInstruction, + createTransferInstruction, + getAccount, + getAssociatedTokenAddress, + NATIVE_MINT, +} from '@solana/spl-token'; +import { + Connection, + Keypair, + sendAndConfirmTransaction, + SystemProgram, + Transaction, +} from '@solana/web3.js'; +import fs from 'fs'; + +async function main(): Promise { + try { + let sig; + const conn = new Connection(process.env.MB_CLUSTER_URL!); + + // load wallet 1 + const w1 = Keypair.fromSecretKey( + Buffer.from(JSON.parse(fs.readFileSync(process.env.wallet1!, 'utf-8'))), + ); + + // load wallet 2 + const w2 = Keypair.fromSecretKey( + Buffer.from(JSON.parse(fs.readFileSync(process.env.wallet2!, 'utf-8'))), + ); + + const w1WsolTA = await getAssociatedTokenAddress(NATIVE_MINT, w1.publicKey); + // const ataTransaction1 = new Transaction().add( + // createAssociatedTokenAccountInstruction( + // w1.publicKey, + // w1WsolTA, + // w1.publicKey, + // NATIVE_MINT, + // ), + // ); + // await sendAndConfirmTransaction(conn, ataTransaction1, [w1]); + + const w2WsolTA = await getAssociatedTokenAddress(NATIVE_MINT, w2.publicKey); + // const ataTransaction2 = new Transaction().add( + // createAssociatedTokenAccountInstruction( + // w2.publicKey, + // w2WsolTA, + // w2.publicKey, + // NATIVE_MINT, + // ), + // ); + // await sendAndConfirmTransaction(conn, ataTransaction2, [w2]); + + // wallet 1 wrap sol to wsol + const solTransferTransaction = new Transaction().add( + SystemProgram.transfer({ + fromPubkey: w1.publicKey, + toPubkey: w1WsolTA, + lamports: 1, + }), + createSyncNativeInstruction(w1WsolTA), + ); + sig = await sendAndConfirmTransaction(conn, solTransferTransaction, [w1]); + console.log( + `sig w1 wrapped some sol https://explorer.solana.com/tx/${sig}`, + ); + + // wallet 1 approve wallet 2 for some wsol + const tokenApproveTx = new Transaction().add( + createApproveInstruction(w1WsolTA, w2.publicKey, w1.publicKey, 1), + ); + sig = await sendAndConfirmTransaction(conn, tokenApproveTx, [w1]); + console.log( + `sig w1 token approve w2 https://explorer.solana.com/tx/${sig}`, + ); + + // log delegate amount + let w2WsolAtaInfo = await getAccount(conn, w1WsolTA); + console.log( + `- delegate ${w2WsolAtaInfo.delegate}, amount ${w2WsolAtaInfo.delegatedAmount}`, + ); + + // wallet 2 transfer wsol from wallet 1 to wallet 2 + const tokenTransferTx = new Transaction().add( + createTransferInstruction(w1WsolTA, w2WsolTA, w2.publicKey, 1), + ); + sig = await sendAndConfirmTransaction(conn, tokenTransferTx, [w2], { + skipPreflight: true, + }); + console.log( + `sig w1 transfer wsol to w2 https://explorer.solana.com/tx/${sig}`, + ); + + // log delegate amount + w2WsolAtaInfo = await getAccount(conn, w1WsolTA, 'finalized'); + console.log( + `- delegate ${w2WsolAtaInfo.delegate}, amount ${w2WsolAtaInfo.delegatedAmount}`, + ); + } catch (error) { + console.log(error); + } + + // wallet 2 unwrap all wsol +} + +main(); From baab384055262e6621c6d01d433918185d2d317f Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Tue, 4 Jul 2023 10:55:59 +0200 Subject: [PATCH 05/90] update Signed-off-by: microwavedcola1 --- ts/client/scripts/archive/token-approve-test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ts/client/scripts/archive/token-approve-test.ts b/ts/client/scripts/archive/token-approve-test.ts index ed2c3af9f..89fbe9f67 100644 --- a/ts/client/scripts/archive/token-approve-test.ts +++ b/ts/client/scripts/archive/token-approve-test.ts @@ -102,6 +102,7 @@ async function main(): Promise { } // wallet 2 unwrap all wsol + // todo } main(); From 08188f3cf2e9d24cdae5291c5fa0903210abc393 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Tue, 4 Jul 2023 11:20:21 +0200 Subject: [PATCH 06/90] updarte Signed-off-by: microwavedcola1 --- ts/client/scripts/archive/token-approve-test.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ts/client/scripts/archive/token-approve-test.ts b/ts/client/scripts/archive/token-approve-test.ts index 89fbe9f67..9854f339f 100644 --- a/ts/client/scripts/archive/token-approve-test.ts +++ b/ts/client/scripts/archive/token-approve-test.ts @@ -1,5 +1,6 @@ import { createApproveInstruction, + createCloseAccountInstruction, createSyncNativeInstruction, createTransferInstruction, getAccount, @@ -97,12 +98,18 @@ async function main(): Promise { console.log( `- delegate ${w2WsolAtaInfo.delegate}, amount ${w2WsolAtaInfo.delegatedAmount}`, ); + + // wallet 2 unwrap all wsol + const closeAtaIx = new Transaction().add( + createCloseAccountInstruction(w2WsolTA, w2.publicKey, w2.publicKey), + ); + sig = await sendAndConfirmTransaction(conn, closeAtaIx, [w2], { + skipPreflight: true, + }); + console.log(`sig w2 unwrap wsol https://explorer.solana.com/tx/${sig}`); } catch (error) { console.log(error); } - - // wallet 2 unwrap all wsol - // todo } main(); From 7ab0164d388a8da034d3cd3e7fb022391b0326c2 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Wed, 5 Jul 2023 07:32:26 +0200 Subject: [PATCH 07/90] v0.17.1 --- package.json | 2 +- ts/client/src/accounts/mangoAccount.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 3358dfc81..ce9b9b025 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.17.0", + "version": "0.17.1", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { diff --git a/ts/client/src/accounts/mangoAccount.ts b/ts/client/src/accounts/mangoAccount.ts index 8eeae3283..ee5f78e6b 100644 --- a/ts/client/src/accounts/mangoAccount.ts +++ b/ts/client/src/accounts/mangoAccount.ts @@ -411,9 +411,9 @@ export class MangoAccount { * Sum of all positive assets. * @returns assets, in native quote */ - public getAssetsValue(group: Group): I80F48 { + public getAssetsValue(group: Group, healthType?: HealthType): I80F48 { const hc = HealthCache.fromMangoAccount(group, this); - return hc.healthAssetsAndLiabs(undefined, false).assets; + return hc.healthAssetsAndLiabs(healthType, false).assets; } /** From 4b82771487398027df0c4352e8da535b42512e3a Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Thu, 6 Jul 2023 15:21:30 +0200 Subject: [PATCH 08/90] also consider shorts Signed-off-by: microwavedcola1 --- ts/client/src/stats.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ts/client/src/stats.ts b/ts/client/src/stats.ts index 97bf3ab1a..41c462738 100644 --- a/ts/client/src/stats.ts +++ b/ts/client/src/stats.ts @@ -39,8 +39,12 @@ export async function getLargestPerpPositions( allPps.sort( (a, b) => - b.getNotionalValueUi(group.getPerpMarketByMarketIndex(b.marketIndex)) - - a.getNotionalValueUi(group.getPerpMarketByMarketIndex(a.marketIndex)), + Math.abs( + b.getNotionalValueUi(group.getPerpMarketByMarketIndex(b.marketIndex)), + ) - + Math.abs( + a.getNotionalValueUi(group.getPerpMarketByMarketIndex(a.marketIndex)), + ), ); return allPps.map((pp) => ({ From ca4a8c179a919b6bcae1830bbe700483530e499e Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Thu, 6 Jul 2023 15:22:08 +0200 Subject: [PATCH 09/90] v0.17.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ce9b9b025..23f463eb6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.17.1", + "version": "0.17.2", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From 05b9f8f5da5c67735564ee3b90cff71ba08956d4 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Thu, 6 Jul 2023 18:17:24 +0200 Subject: [PATCH 10/90] ui helper Signed-off-by: microwavedcola1 --- ts/client/src/accounts/healthCache.ts | 42 +++++++++++++++++++++++++- ts/client/src/accounts/mangoAccount.ts | 8 +++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/ts/client/src/accounts/healthCache.ts b/ts/client/src/accounts/healthCache.ts index 9552dac4f..38e78f310 100644 --- a/ts/client/src/accounts/healthCache.ts +++ b/ts/client/src/accounts/healthCache.ts @@ -9,7 +9,7 @@ import { ONE_I80F48, ZERO_I80F48, } from '../numbers/I80F48'; -import { toNativeI80F48ForQuote } from '../utils'; +import { toNativeI80F48ForQuote, toUiDecimalsForQuote } from '../utils'; import { Bank, BankForHealth, TokenIndex } from './bank'; import { Group } from './group'; @@ -262,6 +262,46 @@ export class HealthCache { return health; } + healthContributionPerAssetUi( + group: Group, + healthType: HealthType, + ): { asset: string; contribution: number }[] { + const tokenBalances = this.effectiveTokenBalancesInternal( + healthType, + false, + ); + + const ret = new Array<{ asset: string; contribution: number }>(); + for (const index of this.tokenInfos.keys()) { + const tokenInfo = this.tokenInfos[index]; + const tokenBalance = tokenBalances[index]; + const contrib = tokenInfo.healthContribution( + healthType, + tokenBalance.spotAndPerp, + ); + ret.push({ + asset: group.getFirstBankByTokenIndex(tokenInfo.tokenIndex).name, + contribution: toUiDecimalsForQuote(contrib), + }); + } + const res = this.computeSerum3Reservations(healthType); + for (const [index, serum3Info] of this.serum3Infos.entries()) { + const contrib = serum3Info.healthContribution( + healthType, + this.tokenInfos, + tokenBalances, + res.tokenMaxReserved, + res.serum3Reserved[index], + ); + ret.push({ + asset: group.getSerum3MarketByMarketIndex(serum3Info.marketIndex).name, + contribution: toUiDecimalsForQuote(contrib), + }); + } + + return ret; + } + public health(healthType: HealthType): I80F48 { const tokenBalances = this.effectiveTokenBalancesInternal( healthType, diff --git a/ts/client/src/accounts/mangoAccount.ts b/ts/client/src/accounts/mangoAccount.ts index ee5f78e6b..2d669c890 100644 --- a/ts/client/src/accounts/mangoAccount.ts +++ b/ts/client/src/accounts/mangoAccount.ts @@ -333,6 +333,14 @@ export class MangoAccount { return hc.health(healthType); } + public getHealthContributionPerAssetUi( + group: Group, + healthType: HealthType, + ): { asset: string; contribution: number }[] { + const hc = HealthCache.fromMangoAccount(group, this); + return hc.healthContributionPerAssetUi(group, healthType); + } + public perpMaxSettle( group: Group, perpMarketSettleTokenIndex: TokenIndex, From ea66b34d3651b147ea98ee93e0878c182b79fa08 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Thu, 6 Jul 2023 18:18:10 +0200 Subject: [PATCH 11/90] v0.17.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 23f463eb6..9eb421d76 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.17.2", + "version": "0.17.3", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From 2060245f3170f7a3ca02ad293e4906aebac22359 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Thu, 6 Jul 2023 18:45:42 +0200 Subject: [PATCH 12/90] optimise Signed-off-by: microwavedcola1 --- ts/client/src/risk.ts | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/ts/client/src/risk.ts b/ts/client/src/risk.ts index 33ad34478..ba2f73174 100644 --- a/ts/client/src/risk.ts +++ b/ts/client/src/risk.ts @@ -374,20 +374,11 @@ export async function getPerpPositionsToBeLiquidated( export async function getEquityForMangoAccounts( client: MangoClient, group: Group, - mangoAccounts: PublicKey[], + mangoAccountPks: PublicKey[], + allMangoAccounts: MangoAccount[], ): Promise { - // Filter mango accounts which might be closed - const liqors = ( - await client.connection.getMultipleAccountsInfo(mangoAccounts) - ) - .map((ai, i) => { - return { ai: ai, pk: mangoAccounts[i] }; - }) - .filter((val) => val.ai) - .map((val) => val.pk); - - const liqorMangoAccounts = await Promise.all( - liqors.map((liqor) => client.getMangoAccount(liqor, true)), + const liqorMangoAccounts = allMangoAccounts.filter((a) => + mangoAccountPks.find((pk) => pk.equals(a.publicKey)), ); const accountsWithEquity = liqorMangoAccounts.map((a: MangoAccount) => { @@ -435,12 +426,6 @@ export async function getRiskStats( // Get all mango accounts const mangoAccounts = await client.getAllMangoAccounts(group, true); - // const mangoAccounts = [ - // await client.getMangoAccount( - // new PublicKey('5G9XriaoqQy1V4s9RmnbczWAozzbv6h2RuEeAHk4R6Lb'), // https://app.mango.markets/stats?token=SOL - // true, - // ), - // ]; // Get on chain prices const mints = [ @@ -538,8 +523,8 @@ export async function getRiskStats( getPriceImpactForLiqor(groupUsdtDepeg, mangoAccounts), getPerpPositionsToBeLiquidated(groupDrop, mangoAccounts), getPerpPositionsToBeLiquidated(groupRally, mangoAccounts), - getEquityForMangoAccounts(client, group, liqors), - getEquityForMangoAccounts(client, group, mms), + getEquityForMangoAccounts(client, group, liqors, mangoAccounts), + getEquityForMangoAccounts(client, group, mms, mangoAccounts), ]); return { From 3b7092a6a20ee04fb4a7dc86f30d4de453c5c961 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Thu, 6 Jul 2023 18:46:21 +0200 Subject: [PATCH 13/90] v0.17.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9eb421d76..ed2469c1c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.17.3", + "version": "0.17.4", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From a6fe8dfd48352cc57c5a392fb6d0329db6e06622 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Wed, 12 Jul 2023 09:38:48 +0200 Subject: [PATCH 14/90] fix max swap Signed-off-by: microwavedcola1 --- ts/client/src/accounts/mangoAccount.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ts/client/src/accounts/mangoAccount.ts b/ts/client/src/accounts/mangoAccount.ts index 2d669c890..3222be1ec 100644 --- a/ts/client/src/accounts/mangoAccount.ts +++ b/ts/client/src/accounts/mangoAccount.ts @@ -600,6 +600,10 @@ export class MangoAccount { sourceBorrow.div(ONE_I80F48().add(sourceBank.loanOriginationFeeRate)), ); } + + // Cannot swap more than total deposits in mango for the token + maxSource = maxSource.min(sourceBank.nativeDeposits()); + return toUiDecimals(maxSource, group.getMintDecimals(sourceMintPk)); } From 54f1e9e1e5dee692fafb05fd50769de6899654f3 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Wed, 12 Jul 2023 09:39:47 +0200 Subject: [PATCH 15/90] v0.17.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ed2469c1c..b69e0c4ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.17.4", + "version": "0.17.5", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From 604c38ed5b225fcdb71006a9590d3504dc67b1bf Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Wed, 12 Jul 2023 09:53:38 +0200 Subject: [PATCH 16/90] fix max swap Signed-off-by: microwavedcola1 --- ts/client/src/accounts/mangoAccount.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ts/client/src/accounts/mangoAccount.ts b/ts/client/src/accounts/mangoAccount.ts index 3222be1ec..c62379169 100644 --- a/ts/client/src/accounts/mangoAccount.ts +++ b/ts/client/src/accounts/mangoAccount.ts @@ -595,7 +595,13 @@ export class MangoAccount { ); const sourceBalance = this.getEffectiveTokenBalance(group, sourceBank); if (maxSource.gt(sourceBalance)) { - const sourceBorrow = maxSource.sub(sourceBalance); + // Cannot borrow more than the window limit + let sourceBorrow = maxSource.sub(sourceBalance); + const netBorrowLimitPerWindow = I80F48.fromI64( + sourceBank.netBorrowLimitPerWindowQuote, + ).div(sourceBank.price); + sourceBorrow = sourceBorrow.min(netBorrowLimitPerWindow); + maxSource = sourceBalance.add( sourceBorrow.div(ONE_I80F48().add(sourceBank.loanOriginationFeeRate)), ); From d5472d790e53422c7f7e7f5fd5e0b08d1c4aecd5 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Wed, 12 Jul 2023 09:54:12 +0200 Subject: [PATCH 17/90] v0.17.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b69e0c4ac..59b1ca098 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.17.5", + "version": "0.17.6", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From 2be2c291010f5b398a7fa9dc4aad60a8aa730157 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Mon, 10 Jul 2023 10:40:48 +0200 Subject: [PATCH 18/90] Client: jupiter swap and tx builder improvements - jupiter swap now supports multiple hops - tx builder can check resulting tx size (cherry picked from commit c58ee91356d6b822b8eeeb90fb6036383d819c8d) --- bin/cli/src/main.rs | 15 ++- bin/liquidator/src/liquidate.rs | 2 + bin/liquidator/src/rebalance.rs | 2 + bin/liquidator/src/util.rs | 113 +++++++++++++++++++- bin/settler/src/settle.rs | 2 +- lib/client/src/client.rs | 177 +++++++++++++++++++------------- 6 files changed, 232 insertions(+), 79 deletions(-) diff --git a/bin/cli/src/main.rs b/bin/cli/src/main.rs index 36e842ca9..546bc343d 100644 --- a/bin/cli/src/main.rs +++ b/bin/cli/src/main.rs @@ -138,7 +138,7 @@ async fn main() -> Result<(), anyhow::Error> { Command::CreateAccount(cmd) => { let client = cmd.rpc.client(Some(&cmd.owner))?; let group = pubkey_from_cli(&cmd.group); - let owner = keypair_from_cli(&cmd.owner); + let owner = Arc::new(keypair_from_cli(&cmd.owner)); let account_num = if let Some(num) = cmd.account_num { num @@ -156,9 +156,15 @@ async fn main() -> Result<(), anyhow::Error> { + 1 } }; - let (account, txsig) = - MangoClient::create_account(&client, group, &owner, &owner, account_num, &cmd.name) - .await?; + let (account, txsig) = MangoClient::create_account( + &client, + group, + owner.clone(), + owner.clone(), + account_num, + &cmd.name, + ) + .await?; println!("{}", account); println!("{}", txsig); } @@ -185,6 +191,7 @@ async fn main() -> Result<(), anyhow::Error> { cmd.amount, cmd.slippage_bps, JupiterSwapMode::ExactIn, + false, ) .await?; println!("{}", txsig); diff --git a/bin/liquidator/src/liquidate.rs b/bin/liquidator/src/liquidate.rs index fe625c659..8a97d6214 100644 --- a/bin/liquidator/src/liquidate.rs +++ b/bin/liquidator/src/liquidate.rs @@ -41,6 +41,7 @@ pub async fn jupiter_market_can_buy( quote_amount, slippage, JupiterSwapMode::ExactIn, + false, ) .await .is_ok() @@ -69,6 +70,7 @@ pub async fn jupiter_market_can_sell( quote_amount, slippage, JupiterSwapMode::ExactOut, + false, ) .await .is_ok() diff --git a/bin/liquidator/src/rebalance.rs b/bin/liquidator/src/rebalance.rs index 89b7a1149..33d434d6a 100644 --- a/bin/liquidator/src/rebalance.rs +++ b/bin/liquidator/src/rebalance.rs @@ -165,6 +165,7 @@ impl Rebalancer { input_amount.to_num::(), self.config.slippage_bps, JupiterSwapMode::ExactIn, + false, ) .await?; log::info!( @@ -197,6 +198,7 @@ impl Rebalancer { amount.to_num::(), self.config.slippage_bps, JupiterSwapMode::ExactIn, + false, ) .await?; log::info!( diff --git a/bin/liquidator/src/util.rs b/bin/liquidator/src/util.rs index d494cf5eb..c1d611298 100644 --- a/bin/liquidator/src/util.rs +++ b/bin/liquidator/src/util.rs @@ -1,10 +1,14 @@ use mango_v4::accounts_zerocopy::*; -use mango_v4::state::{Bank, MintInfo, PerpMarket}; +use mango_v4::state::{Bank, MangoAccountValue, MintInfo, PerpMarket, TokenIndex}; + +use anyhow::Context; +use fixed::types::I80F48; use solana_sdk::account::AccountSharedData; use solana_sdk::pubkey::Pubkey; pub use mango_v4_client::snapshot_source::is_mango_account; +use mango_v4_client::{chain_data, JupiterSwapMode, MangoClient}; pub fn is_mango_bank<'a>(account: &'a AccountSharedData, group_id: &Pubkey) -> Option<&'a Bank> { let bank = account.load::().ok()?; @@ -32,3 +36,110 @@ pub fn is_perp_market<'a>( } Some(perp_market) } + +/// A wrapper that can mock the response +pub async fn jupiter_route( + mango_client: &MangoClient, + input_mint: Pubkey, + output_mint: Pubkey, + amount: u64, + slippage: u64, + swap_mode: JupiterSwapMode, + only_direct_routes: bool, + mock: bool, +) -> anyhow::Result { + if !mock { + return mango_client + .jupiter_route( + input_mint, + output_mint, + amount, + slippage, + swap_mode, + only_direct_routes, + ) + .await; + } + + let input_price = mango_client + .bank_oracle_price(mango_client.context.token_by_mint(&input_mint)?.token_index) + .await?; + let output_price = mango_client + .bank_oracle_price( + mango_client + .context + .token_by_mint(&output_mint)? + .token_index, + ) + .await?; + let in_amount: u64; + let out_amount: u64; + let other_amount_threshold: u64; + let swap_mode_str; + match swap_mode { + JupiterSwapMode::ExactIn => { + in_amount = amount; + out_amount = (I80F48::from(amount) * input_price / output_price).to_num(); + other_amount_threshold = out_amount; + swap_mode_str = "ExactIn".to_string(); + } + JupiterSwapMode::ExactOut => { + in_amount = (I80F48::from(amount) * output_price / input_price).to_num(); + out_amount = amount; + other_amount_threshold = in_amount; + swap_mode_str = "ExactOut".to_string(); + } + } + + Ok(mango_v4_client::jupiter::QueryRoute { + in_amount: in_amount.to_string(), + out_amount: out_amount.to_string(), + price_impact_pct: 0.1, + market_infos: vec![], + amount: amount.to_string(), + slippage_bps: 1, + other_amount_threshold: other_amount_threshold.to_string(), + swap_mode: swap_mode_str, + fees: None, + }) +} + +/// Convenience wrapper for getting max swap amounts for a token pair +pub async fn max_swap_source( + client: &MangoClient, + account_fetcher: &chain_data::AccountFetcher, + account: &MangoAccountValue, + source: TokenIndex, + target: TokenIndex, + price: I80F48, + min_health_ratio: I80F48, +) -> anyhow::Result { + let mut account = account.clone(); + + // Ensure the tokens are activated, so they appear in the health cache and + // max_swap_source() will work. + account.ensure_token_position(source)?; + account.ensure_token_position(target)?; + + let health_cache = + mango_v4_client::health_cache::new(&client.context, account_fetcher, &account) + .await + .expect("always ok"); + + let source_bank = client.first_bank(source).await?; + let target_bank = client.first_bank(target).await?; + + let source_price = health_cache.token_info(source).unwrap().prices.oracle; + + let amount = health_cache + .max_swap_source_for_health_ratio( + &account, + &source_bank, + source_price, + &target_bank, + price, + min_health_ratio, + ) + .context("getting max_swap_source")?; + Ok(amount) +} diff --git a/bin/settler/src/settle.rs b/bin/settler/src/settle.rs index a23bd7b7d..239d8ac8e 100644 --- a/bin/settler/src/settle.rs +++ b/bin/settler/src/settle.rs @@ -250,7 +250,7 @@ struct SettleBatchProcessor<'a> { impl<'a> SettleBatchProcessor<'a> { fn transaction(&self) -> anyhow::Result { let client = &self.mango_client.client; - let fee_payer = &*client.fee_payer; + let fee_payer = client.fee_payer.clone(); TransactionBuilder { instructions: self.instructions.clone(), diff --git a/lib/client/src/client.rs b/lib/client/src/client.rs index 9d45d034d..5f39292f0 100644 --- a/lib/client/src/client.rs +++ b/lib/client/src/client.rs @@ -1,3 +1,4 @@ +use std::ops::Deref; use std::str::FromStr; use std::sync::Arc; use std::time::Duration; @@ -133,16 +134,16 @@ impl MangoClient { pub async fn find_or_create_account( client: &Client, group: Pubkey, - owner: &Keypair, - payer: &Keypair, // pays the SOL for the new account + owner: Arc, + payer: Arc, // pays the SOL for the new account mango_account_name: &str, ) -> anyhow::Result { let rpc = client.rpc_async(); let program = mango_v4::ID; + let owner_pk = owner.pubkey(); // Mango Account - let mut mango_account_tuples = - fetch_mango_accounts(&rpc, program, group, owner.pubkey()).await?; + let mut mango_account_tuples = fetch_mango_accounts(&rpc, program, group, owner_pk).await?; let mango_account_opt = mango_account_tuples .iter() .find(|(_, account)| account.fixed.name() == mango_account_name); @@ -157,12 +158,18 @@ impl MangoClient { Some(tuple) => tuple.1.fixed.account_num + 1, None => 0u32, }; - Self::create_account(client, group, owner, payer, account_num, mango_account_name) - .await - .context("Failed to create account...")?; + Self::create_account( + client, + group, + owner.clone(), + payer, + account_num, + mango_account_name, + ) + .await + .context("Failed to create account...")?; } - let mango_account_tuples = - fetch_mango_accounts(&rpc, program, group, owner.pubkey()).await?; + let mango_account_tuples = fetch_mango_accounts(&rpc, program, group, owner_pk).await?; let index = mango_account_tuples .iter() .position(|tuple| tuple.1.fixed.name() == mango_account_name) @@ -173,8 +180,8 @@ impl MangoClient { pub async fn create_account( client: &Client, group: Pubkey, - owner: &Keypair, - payer: &Keypair, // pays the SOL for the new account + owner: Arc, + payer: Arc, // pays the SOL for the new account account_num: u32, mango_account_name: &str, ) -> anyhow::Result<(Pubkey, Signature)> { @@ -1305,6 +1312,7 @@ impl MangoClient { amount: u64, slippage: u64, swap_mode: JupiterSwapMode, + only_direct_routes: bool, ) -> anyhow::Result { let quote = self .http_client @@ -1313,7 +1321,7 @@ impl MangoClient { ("inputMint", input_mint.to_string()), ("outputMint", output_mint.to_string()), ("amount", format!("{}", amount)), - ("onlyDirectRoutes", "true".into()), + ("onlyDirectRoutes", only_direct_routes.to_string()), ("enforceSingleTx", "true".into()), ("filterTopNResult", "10".into()), ("slippageBps", format!("{}", slippage)), @@ -1353,19 +1361,18 @@ impl MangoClient { Ok(route.clone()) } - pub async fn jupiter_swap( + /// Find the instructions and account lookup tables for a jupiter swap through mango + /// + /// It would be nice if we didn't have to pass input_mint/output_mint - the data is + /// definitely in QueryRoute - but it's unclear how. + pub async fn prepare_jupiter_swap_transaction( &self, input_mint: Pubkey, output_mint: Pubkey, - amount: u64, - slippage: u64, - swap_mode: JupiterSwapMode, - ) -> anyhow::Result { + route: &jupiter::QueryRoute, + ) -> anyhow::Result { let source_token = self.context.token_by_mint(&input_mint)?; let target_token = self.context.token_by_mint(&output_mint)?; - let route = self - .jupiter_route(input_mint, output_mint, amount, slippage, swap_mode) - .await?; let swap = self .http_client @@ -1400,22 +1407,25 @@ impl MangoClient { let ata_program = anchor_spl::associated_token::ID; let token_program = anchor_spl::token::ID; let compute_budget_program = solana_sdk::compute_budget::ID; - // these setup instructions are unnecessary since FlashLoan already takes care of it + // these setup instructions should be placed outside of flashloan begin-end let is_setup_ix = |k: Pubkey| -> bool { k == ata_program || k == token_program || k == compute_budget_program }; let (jup_ixs, jup_alts) = self .deserialize_instructions_and_alts(&jup_tx.message) .await?; - let jup_cu_ix = jup_ixs + let jup_action_ix_begin = jup_ixs .iter() - .filter(|ix| ix.program_id == compute_budget_program) - .cloned() - .collect::>(); - let jup_action_ix = jup_ixs - .into_iter() - .filter(|ix| !is_setup_ix(ix.program_id)) - .collect::>(); + .position(|ix| !is_setup_ix(ix.program_id)) + .ok_or_else(|| { + anyhow::anyhow!("jupiter swap response only had setup-like instructions") + })?; + let jup_action_ix_end = jup_ixs.len() + - jup_ixs + .iter() + .rev() + .position(|ix| !is_setup_ix(ix.program_id)) + .unwrap(); let bank_ams = [ source_token.mint_info.first_bank(), @@ -1445,14 +1455,14 @@ impl MangoClient { }) .collect::>(); - let loan_amounts = vec![ - match swap_mode { - JupiterSwapMode::ExactIn => amount, - // in amount + slippage - JupiterSwapMode::ExactOut => u64::from_str(&route.other_amount_threshold).unwrap(), - }, - 0u64, - ]; + let source_loan = if route.swap_mode == "ExactIn" { + u64::from_str(&route.amount).unwrap() + } else if route.swap_mode == "ExactOut" { + u64::from_str(&route.other_amount_threshold).unwrap() + } else { + anyhow::bail!("unknown swap mode: {}", route.swap_mode); + }; + let loan_amounts = vec![source_loan, 0u64]; let num_loans: u8 = loan_amounts.len().try_into().unwrap(); // This relies on the fact that health account banks will be identical to the first_bank above! @@ -1467,25 +1477,9 @@ impl MangoClient { let mut instructions = Vec::new(); - for ix in jup_cu_ix { + for ix in &jup_ixs[..jup_action_ix_begin] { instructions.push(ix.clone()); } - instructions.push( - spl_associated_token_account::instruction::create_associated_token_account_idempotent( - &self.owner.pubkey(), - &self.owner.pubkey(), - &source_token.mint_info.mint, - &Token::id(), - ), - ); - instructions.push( - spl_associated_token_account::instruction::create_associated_token_account_idempotent( - &self.owner.pubkey(), - &self.owner.pubkey(), - &target_token.mint_info.mint, - &Token::id(), - ), - ); instructions.push(Instruction { program_id: mango_v4::id(), accounts: { @@ -1508,7 +1502,7 @@ impl MangoClient { loan_amounts, }), }); - for ix in jup_action_ix { + for ix in &jup_ixs[jup_action_ix_begin..jup_action_ix_end] { instructions.push(ix.clone()); } instructions.push(Instruction { @@ -1533,20 +1527,49 @@ impl MangoClient { flash_loan_type: mango_v4::accounts_ix::FlashLoanType::Swap, }), }); + for ix in &jup_ixs[jup_action_ix_end..] { + instructions.push(ix.clone()); + } - let payer = self.owner.pubkey(); // maybe use fee_payer? but usually it's the same let mut address_lookup_tables = self.mango_address_lookup_tables().await?; address_lookup_tables.extend(jup_alts.into_iter()); - TransactionBuilder { + let payer = self.owner.pubkey(); // maybe use fee_payer? but usually it's the same + + Ok(TransactionBuilder { instructions, address_lookup_tables, payer, - signers: vec![&*self.owner], + signers: vec![self.owner.clone()], config: self.client.transaction_builder_config, - } - .send_and_confirm(&self.client) - .await + }) + } + + pub async fn jupiter_swap( + &self, + input_mint: Pubkey, + output_mint: Pubkey, + amount: u64, + slippage: u64, + swap_mode: JupiterSwapMode, + only_direct_routes: bool, + ) -> anyhow::Result { + let route = self + .jupiter_route( + input_mint, + output_mint, + amount, + slippage, + swap_mode, + only_direct_routes, + ) + .await?; + + let tx_builder = self + .prepare_jupiter_swap_transaction(input_mint, output_mint, &route) + .await?; + + tx_builder.send_and_confirm(&self.client).await } async fn fetch_address_lookup_table( @@ -1630,7 +1653,7 @@ impl MangoClient { instructions, address_lookup_tables: vec![], payer: self.client.fee_payer.pubkey(), - signers: vec![&*self.owner, &*self.client.fee_payer], + signers: vec![self.owner.clone(), self.client.fee_payer.clone()], config: self.client.transaction_builder_config, } .send_and_confirm(&self.client) @@ -1645,7 +1668,7 @@ impl MangoClient { instructions, address_lookup_tables: vec![], payer: self.client.fee_payer.pubkey(), - signers: vec![&*self.client.fee_payer], + signers: vec![self.client.fee_payer.clone()], config: self.client.transaction_builder_config, } .send_and_confirm(&self.client) @@ -1677,17 +1700,17 @@ pub struct TransactionBuilderConfig { pub prioritization_micro_lamports: Option, } -pub struct TransactionBuilder<'a> { +pub struct TransactionBuilder { pub instructions: Vec, pub address_lookup_tables: Vec, - pub signers: Vec<&'a Keypair>, + pub signers: Vec>, pub payer: Pubkey, pub config: TransactionBuilderConfig, } -impl<'a> TransactionBuilder<'a> { +impl TransactionBuilder { pub async fn transaction( - self, + &self, rpc: &RpcClientAsync, ) -> anyhow::Result { let latest_blockhash = rpc.get_latest_blockhash().await?; @@ -1695,11 +1718,12 @@ impl<'a> TransactionBuilder<'a> { } pub fn transaction_with_blockhash( - mut self, + &self, blockhash: Hash, ) -> anyhow::Result { + let mut ix = self.instructions.clone(); if let Some(prio_price) = self.config.prioritization_micro_lamports { - self.instructions.insert( + ix.insert( 0, solana_sdk::compute_budget::ComputeBudgetInstruction::set_compute_unit_price( prio_price, @@ -1708,15 +1732,16 @@ impl<'a> TransactionBuilder<'a> { } let v0_message = solana_sdk::message::v0::Message::try_compile( &self.payer, - &self.instructions, + &ix, &self.address_lookup_tables, blockhash, )?; let versioned_message = solana_sdk::message::VersionedMessage::V0(v0_message); let signers = self .signers - .into_iter() + .iter() .unique_by(|s| s.pubkey()) + .map(|v| v.deref()) .collect::>(); let tx = solana_sdk::transaction::VersionedTransaction::try_new(versioned_message, &signers)?; @@ -1725,7 +1750,7 @@ impl<'a> TransactionBuilder<'a> { // These two send() functions don't really belong into the transaction builder! - pub async fn send(self, client: &Client) -> anyhow::Result { + pub async fn send(&self, client: &Client) -> anyhow::Result { let rpc = client.rpc_async(); let tx = self.transaction(&rpc).await?; rpc.send_transaction_with_config(&tx, client.rpc_send_transaction_config) @@ -1733,7 +1758,7 @@ impl<'a> TransactionBuilder<'a> { .map_err(prettify_solana_client_error) } - pub async fn send_and_confirm(self, client: &Client) -> anyhow::Result { + pub async fn send_and_confirm(&self, client: &Client) -> anyhow::Result { let rpc = client.rpc_async(); let tx = self.transaction(&rpc).await?; // TODO: Wish we could use client.rpc_send_transaction_config here too! @@ -1741,6 +1766,12 @@ impl<'a> TransactionBuilder<'a> { .await .map_err(prettify_solana_client_error) } + + pub fn transaction_size_ok(&self) -> anyhow::Result { + let tx = self.transaction_with_blockhash(solana_sdk::hash::Hash::default())?; + let bytes = bincode::serialize(&tx)?; + Ok(bytes.len() <= solana_sdk::packet::PACKET_DATA_SIZE) + } } /// Do some manual unpacking on some ClientErrors From 77da9d49a6137a7faaf25a9601af9c0327fdf753 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Tue, 11 Jul 2023 09:08:38 +0200 Subject: [PATCH 19/90] liquidator: when rebalancing allow multi-hop jupiter routes and if that doesn't fit into a transaction, try a direct route with USDC or SOL. (cherry picked from commit db8f5ae30d031f005a601ab351bd881e26d8b271) --- bin/liquidator/src/liquidate.rs | 15 +- bin/liquidator/src/rebalance.rs | 280 ++++++++++++++++++++++++++------ lib/client/src/client.rs | 15 ++ lib/client/src/lib.rs | 2 +- 4 files changed, 254 insertions(+), 58 deletions(-) diff --git a/bin/liquidator/src/liquidate.rs b/bin/liquidator/src/liquidate.rs index 8a97d6214..46a60dcf6 100644 --- a/bin/liquidator/src/liquidate.rs +++ b/bin/liquidator/src/liquidate.rs @@ -1,6 +1,7 @@ use std::collections::HashSet; use std::time::Duration; +use itertools::Itertools; use mango_v4::accounts_zerocopy::KeyedAccountSharedData; use mango_v4::health::{HealthCache, HealthType}; use mango_v4::state::{ @@ -91,17 +92,15 @@ struct LiquidateHelper<'a> { impl<'a> LiquidateHelper<'a> { async fn serum3_close_orders(&self) -> anyhow::Result> { // look for any open serum orders or settleable balances - let serum_oos: anyhow::Result> = stream::iter(self.liqee.active_serum3_orders()) - .then(|orders| async { - let open_orders_account = self - .account_fetcher - .fetch_raw_account(&orders.open_orders) - .await?; + let serum_oos: anyhow::Result> = self + .liqee + .active_serum3_orders() + .map(|orders| { + let open_orders_account = self.account_fetcher.fetch_raw(&orders.open_orders)?; let open_orders = mango_v4::serum3_cpi::load_open_orders(&open_orders_account)?; Ok((*orders, *open_orders)) }) - .try_collect() - .await; + .try_collect(); let serum_force_cancels = serum_oos? .into_iter() .filter_map(|(orders, open_orders)| { diff --git a/bin/liquidator/src/rebalance.rs b/bin/liquidator/src/rebalance.rs index 33d434d6a..5dfe5a1e1 100644 --- a/bin/liquidator/src/rebalance.rs +++ b/bin/liquidator/src/rebalance.rs @@ -1,15 +1,17 @@ +use itertools::Itertools; use mango_v4::accounts_zerocopy::KeyedAccountSharedData; use mango_v4::state::{ Bank, BookSide, PlaceOrderType, Side, TokenIndex, TokenPosition, QUOTE_TOKEN_INDEX, }; use mango_v4_client::{ - chain_data, perp_pnl, AccountFetcher, AnyhowWrap, JupiterSwapMode, MangoClient, TokenContext, + chain_data, jupiter::QueryRoute, perp_pnl, AnyhowWrap, JupiterSwapMode, MangoClient, + TokenContext, TransactionBuilder, }; use {fixed::types::I80F48, solana_sdk::pubkey::Pubkey}; -use futures::{stream, StreamExt, TryStreamExt}; use solana_sdk::signature::Signature; +use std::str::FromStr; use std::sync::Arc; use std::{collections::HashMap, time::Duration}; @@ -32,14 +34,14 @@ struct TokenState { } impl TokenState { - async fn new_position( + fn new_position( token: &TokenContext, position: &TokenPosition, account_fetcher: &chain_data::AccountFetcher, ) -> anyhow::Result { let bank = Self::bank(token, account_fetcher)?; Ok(Self { - price: Self::fetch_price(token, &bank, account_fetcher).await?, + price: Self::fetch_price(token, &bank, account_fetcher)?, native_position: position.native(&bank), in_use: position.is_in_use(), }) @@ -52,14 +54,12 @@ impl TokenState { account_fetcher.fetch::(&token.mint_info.first_bank()) } - async fn fetch_price( + fn fetch_price( token: &TokenContext, bank: &Bank, account_fetcher: &chain_data::AccountFetcher, ) -> anyhow::Result { - let oracle = account_fetcher - .fetch_raw_account(&token.mint_info.oracle) - .await?; + let oracle = account_fetcher.fetch_raw(&token.mint_info.oracle)?; bank.oracle_price( &KeyedAccountSharedData::new(token.mint_info.oracle, oracle.into()), None, @@ -68,6 +68,13 @@ impl TokenState { } } +#[derive(Clone)] +struct WrappedJupRoute { + input_mint: Pubkey, + output_mint: Pubkey, + route: QueryRoute, +} + pub struct Rebalancer { pub mango_client: Arc, pub account_fetcher: Arc, @@ -105,6 +112,188 @@ impl Rebalancer { Ok(true) } + /// Wrapping client.jupiter_route() in a way that preserves the in/out mints + async fn jupiter_route( + &self, + input_mint: Pubkey, + output_mint: Pubkey, + amount: u64, + only_direct_routes: bool, + ) -> anyhow::Result { + let route = self + .mango_client + .jupiter_route( + input_mint, + output_mint, + amount, + self.config.slippage_bps, + JupiterSwapMode::ExactIn, + only_direct_routes, + ) + .await?; + Ok(WrappedJupRoute { + input_mint, + output_mint, + route, + }) + } + + /// Grab three possible routes: + /// 1. USDC -> output (complex routes) + /// 2. USDC -> output (direct route only) + /// 3. SOL -> output (direct route only) + /// Use 1. if it fits into a tx. Otherwise use the better of 2./3. + async fn token_swap_buy( + &self, + output_mint: Pubkey, + in_amount_quote: u64, + ) -> anyhow::Result<(Signature, WrappedJupRoute)> { + let quote_token = self.mango_client.context.token(QUOTE_TOKEN_INDEX); + let sol_token = self.mango_client.context.token( + *self + .mango_client + .context + .token_indexes_by_name + .get("SOL") // TODO: better use mint + .unwrap(), + ); + + let full_route_job = self.jupiter_route( + quote_token.mint_info.mint, + output_mint, + in_amount_quote, + false, + ); + let direct_quote_route_job = self.jupiter_route( + quote_token.mint_info.mint, + output_mint, + in_amount_quote, + true, + ); + + // For the SOL -> output route we need to adjust the in amount by the SOL price + let sol_bank = TokenState::bank(sol_token, &self.account_fetcher)?; + let sol_price = TokenState::fetch_price(sol_token, &sol_bank, &self.account_fetcher)?; + let in_amount_sol = (I80F48::from(in_amount_quote) / sol_price) + .ceil() + .to_num::(); + let direct_sol_route_job = + self.jupiter_route(sol_token.mint_info.mint, output_mint, in_amount_sol, true); + + let (full_route, direct_quote_route, direct_sol_route) = + tokio::join!(full_route_job, direct_quote_route_job, direct_sol_route_job); + let alternatives = [direct_quote_route, direct_sol_route] + .into_iter() + .filter_map(|v| v.ok()) + .collect_vec(); + + let (tx_builder, route) = self + .determine_best_jupiter_tx( + // If the best_route couldn't be fetched, something is wrong + &full_route?, + &alternatives, + ) + .await?; + let sig = tx_builder + .send_and_confirm(&self.mango_client.client) + .await?; + Ok((sig, route)) + } + + /// Grab three possible routes: + /// 1. input -> USDC (complex routes) + /// 2. input -> USDC (direct route only) + /// 3. input -> SOL (direct route only) + /// Use 1. if it fits into a tx. Otherwise use the better of 2./3. + async fn token_swap_sell( + &self, + input_mint: Pubkey, + in_amount: u64, + ) -> anyhow::Result<(Signature, WrappedJupRoute)> { + let quote_token = self.mango_client.context.token(QUOTE_TOKEN_INDEX); + let sol_token = self.mango_client.context.token( + *self + .mango_client + .context + .token_indexes_by_name + .get("SOL") // TODO: better use mint + .unwrap(), + ); + + let full_route_job = + self.jupiter_route(input_mint, quote_token.mint_info.mint, in_amount, false); + let direct_quote_route_job = + self.jupiter_route(input_mint, quote_token.mint_info.mint, in_amount, true); + let direct_sol_route_job = + self.jupiter_route(input_mint, sol_token.mint_info.mint, in_amount, true); + let (full_route, direct_quote_route, direct_sol_route) = + tokio::join!(full_route_job, direct_quote_route_job, direct_sol_route_job); + let alternatives = [direct_quote_route, direct_sol_route] + .into_iter() + .filter_map(|v| v.ok()) + .collect_vec(); + + let (tx_builder, route) = self + .determine_best_jupiter_tx( + // If the best_route couldn't be fetched, something is wrong + &full_route?, + &alternatives, + ) + .await?; + + let sig = tx_builder + .send_and_confirm(&self.mango_client.client) + .await?; + Ok((sig, route)) + } + + async fn determine_best_jupiter_tx( + &self, + full: &WrappedJupRoute, + alternatives: &[WrappedJupRoute], + ) -> anyhow::Result<(TransactionBuilder, WrappedJupRoute)> { + let builder = self + .mango_client + .prepare_jupiter_swap_transaction(full.input_mint, full.output_mint, &full.route) + .await?; + if builder.transaction_size_ok()? { + return Ok((builder, full.clone())); + } + log::trace!( + "full route from {} to {} does not fit in a tx, market_info.label {}", + full.input_mint, + full.output_mint, + full.route + .market_infos + .first() + .map(|v| v.label.clone()) + .unwrap_or_else(|| "no market_info".into()) + ); + + if alternatives.is_empty() { + anyhow::bail!( + "no alternative routes from {} to {}", + full.input_mint, + full.output_mint + ); + } + + let best = alternatives + .iter() + .min_by(|a, b| { + a.route + .price_impact_pct + .partial_cmp(&b.route.price_impact_pct) + .unwrap() + }) + .unwrap(); + let builder = self + .mango_client + .prepare_jupiter_swap_transaction(best.input_mint, best.output_mint, &best.route) + .await?; + Ok((builder, best.clone())) + } + async fn rebalance_tokens(&self) -> anyhow::Result<()> { let account = self .account_fetcher @@ -113,18 +302,16 @@ impl Rebalancer { // TODO: configurable? let quote_token = self.mango_client.context.token(QUOTE_TOKEN_INDEX); - let tokens: anyhow::Result> = - stream::iter(account.active_token_positions()) - .then(|token_position| async { - let token = self.mango_client.context.token(token_position.token_index); - Ok(( - token.token_index, - TokenState::new_position(token, token_position, &self.account_fetcher) - .await?, - )) - }) - .try_collect() - .await; + let tokens: anyhow::Result> = account + .active_token_positions() + .map(|token_position| { + let token = self.mango_client.context.token(token_position.token_index); + Ok(( + token.token_index, + TokenState::new_position(token, token_position, &self.account_fetcher)?, + )) + }) + .try_collect(); let tokens = tokens?; log::trace!("account tokens: {:?}", tokens); @@ -134,7 +321,6 @@ impl Rebalancer { continue; } let token_mint = token.mint_info.mint; - let quote_mint = quote_token.mint_info.mint; // It's not always possible to bring the native balance to 0 through swaps: // Consider a price <1. You need to sell a bunch of tokens to get 1 USDC native and @@ -157,23 +343,21 @@ impl Rebalancer { let input_amount = buy_amount * token_state.price * I80F48::from_num(self.config.borrow_settle_excess); - let txsig = self - .mango_client - .jupiter_swap( - quote_mint, - token_mint, - input_amount.to_num::(), - self.config.slippage_bps, - JupiterSwapMode::ExactIn, - false, - ) + let (txsig, route) = self + .token_swap_buy(token_mint, input_amount.to_num()) .await?; + let in_token = self + .mango_client + .context + .token_by_mint(&route.input_mint) + .unwrap(); log::info!( - "bought {} {} for {} in tx {}", - token.native_to_ui(buy_amount), + "bought {} {} for {} {} in tx {}", + token.native_to_ui(I80F48::from_str(&route.route.out_amount).unwrap()), token.name, - quote_token.name, - txsig + in_token.native_to_ui(I80F48::from_str(&route.route.in_amount).unwrap()), + in_token.name, + txsig, ); if !self.refresh_mango_account_after_tx(txsig).await? { return Ok(()); @@ -190,23 +374,21 @@ impl Rebalancer { if amount > dust_threshold { // Sell - let txsig = self - .mango_client - .jupiter_swap( - token_mint, - quote_mint, - amount.to_num::(), - self.config.slippage_bps, - JupiterSwapMode::ExactIn, - false, - ) + let (txsig, route) = self + .token_swap_sell(token_mint, amount.to_num::()) .await?; + let out_token = self + .mango_client + .context + .token_by_mint(&route.output_mint) + .unwrap(); log::info!( - "sold {} {} for {} in tx {}", - token.native_to_ui(amount), + "sold {} {} for {} {} in tx {}", + token.native_to_ui(I80F48::from_str(&route.route.in_amount).unwrap()), token.name, - quote_token.name, - txsig + out_token.native_to_ui(I80F48::from_str(&route.route.out_amount).unwrap()), + out_token.name, + txsig, ); if !self.refresh_mango_account_after_tx(txsig).await? { return Ok(()); diff --git a/lib/client/src/client.rs b/lib/client/src/client.rs index 5f39292f0..e4fd03721 100644 --- a/lib/client/src/client.rs +++ b/lib/client/src/client.rs @@ -17,6 +17,7 @@ use futures::{stream, StreamExt, TryStreamExt}; use itertools::Itertools; use mango_v4::accounts_ix::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side}; +use mango_v4::accounts_zerocopy::KeyedAccountSharedData; use mango_v4::state::{ Bank, Group, MangoAccountValue, PerpMarketIndex, PlaceOrderType, SelfTradeBehavior, Serum3MarketIndex, Side, TokenIndex, INSURANCE_TOKEN_INDEX, @@ -419,6 +420,20 @@ impl MangoClient { self.send_and_confirm_owner_tx(ixs).await } + pub async fn bank_oracle_price(&self, token_index: TokenIndex) -> anyhow::Result { + let bank = self.first_bank(token_index).await?; + let mint_info = self.context.mint_info(token_index); + let oracle = self + .account_fetcher + .fetch_raw_account(&mint_info.oracle) + .await?; + let price = bank.oracle_price( + &KeyedAccountSharedData::new(mint_info.oracle, oracle.into()), + None, + )?; + Ok(price) + } + pub async fn get_oracle_price( &self, token_name: &str, diff --git a/lib/client/src/lib.rs b/lib/client/src/lib.rs index c7fbeca8b..cf9a91256 100644 --- a/lib/client/src/lib.rs +++ b/lib/client/src/lib.rs @@ -11,7 +11,7 @@ mod client; mod context; mod gpa; pub mod health_cache; -mod jupiter; +pub mod jupiter; pub mod perp_pnl; pub mod snapshot_source; mod util; From 5ca349f94bd431e37ee2d070d4bdc2db6ec14f3a Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Wed, 12 Jul 2023 14:30:47 +0200 Subject: [PATCH 20/90] use mngo cloud for risl Signed-off-by: microwavedcola1 --- ts/client/src/risk.ts | 101 +++++++++++++++++++++++++----------------- 1 file changed, 61 insertions(+), 40 deletions(-) diff --git a/ts/client/src/risk.ts b/ts/client/src/risk.ts index ba2f73174..e4a11203c 100644 --- a/ts/client/src/risk.ts +++ b/ts/client/src/risk.ts @@ -56,33 +56,39 @@ export interface Risk { liqorEquity: { title: string; data: AccountEquity[] }; } -export async function computePriceImpactOnJup( - amount: string, - inputMint: string, - outputMint: string, -): Promise<{ outAmount: number; priceImpactPct: number }> { - const url = `https://quote-api.jup.ag/v4/quote?inputMint=${inputMint}&outputMint=${outputMint}&amount=${amount}&swapMode=ExactIn&slippageBps=10000&onlyDirectRoutes=false&asLegacyTransaction=false`; - const response = await (await buildFetch())(url, { mode: 'no-cors' }); +type PriceImpact = { + symbol: string; + side: 'bid' | 'ask'; + target_amount: number; + avg_price_impact_percent: number; + min_price_impact_percent: number; + max_price_impact_percent: number; +}; +export async function computePriceImpactOnJup( + pis: PriceImpact[], + usdcAmount: number, + tokenName: string, +): Promise { + console.log(pis); try { - const res = await response.json(); - if (res['data'] && res.data.length > 0 && res.data[0].outAmount) { - return { - outAmount: parseFloat(res.data[0].outAmount), - priceImpactPct: parseFloat(res.data[0].priceImpactPct), - }; + const closestTo = [1000, 5000, 20000, 100000].reduce((prev, curr) => + Math.abs(curr - usdcAmount) < Math.abs(prev - usdcAmount) ? curr : prev, + ); + // Workaround api + if (tokenName == 'ETH (Portal)') { + tokenName = 'ETH'; + } + const filteredPis: PriceImpact[] = pis.filter( + (pi) => pi.symbol == tokenName && pi.target_amount == closestTo, + ); + if (filteredPis.length > 0) { + return (filteredPis[0].max_price_impact_percent * 10000) / 100; } else { - return { - outAmount: -1 / 10000, - priceImpactPct: -1 / 10000, - }; + return -1; } } catch (e) { - console.log(e); - return { - outAmount: -1 / 10000, - priceImpactPct: -1 / 10000, - }; + return -1; } } @@ -107,6 +113,7 @@ export async function getOnChainPriceForMints( export async function getPriceImpactForLiqor( group: Group, + pis: PriceImpact[], mangoAccounts: MangoAccount[], ): Promise { const mangoAccountsWithHealth = mangoAccounts.map((a: MangoAccount) => { @@ -236,20 +243,20 @@ export async function getPriceImpactForLiqor( !liabsInUsdc.eq(ZERO_I80F48()) && usdcMint.toBase58() !== bank.mint.toBase58() ? computePriceImpactOnJup( - liabsInUsdc.toString(), - usdcMint.toBase58(), - bank.mint.toBase58(), + pis, + toUiDecimalsForQuote(liabsInUsdc), + bank.name, ) - : Promise.resolve({ priceImpactPct: 0, outAmount: 0 }), + : Promise.resolve(0), !assets.eq(ZERO_I80F48()) && usdcMint.toBase58() !== bank.mint.toBase58() ? computePriceImpactOnJup( - assets.floor().toString(), - bank.mint.toBase58(), - usdcMint.toBase58(), + pis, + toUiDecimals(assets.mul(bank.price), bank.mintDecimals), + bank.name, ) - : Promise.resolve({ priceImpactPct: 0, outAmount: 0 }), + : Promise.resolve(0), ]); return { @@ -277,9 +284,9 @@ export async function getPriceImpactForLiqor( highlight: Math.round(toUiDecimalsForQuote(liabsInUsdc)) > 5000, }, 'Liabs Slippage': { - val: Math.round(pi1.priceImpactPct * 10000), + val: Math.round(pi1), highlight: - Math.round(pi1.priceImpactPct * 10000) > + Math.round(pi1) > Math.round(bank.liquidationFee.toNumber() * 10000), }, Assets: { @@ -292,9 +299,9 @@ export async function getPriceImpactForLiqor( ) > 5000, }, 'Assets Slippage': { - val: Math.round(pi2.priceImpactPct * 10000), + val: Math.round(pi2), highlight: - Math.round(pi2.priceImpactPct * 10000) > + Math.round(pi2) > Math.round(bank.liquidationFee.toNumber() * 10000), }, }; @@ -377,11 +384,11 @@ export async function getEquityForMangoAccounts( mangoAccountPks: PublicKey[], allMangoAccounts: MangoAccount[], ): Promise { - const liqorMangoAccounts = allMangoAccounts.filter((a) => + const mangoAccounts = allMangoAccounts.filter((a) => mangoAccountPks.find((pk) => pk.equals(a.publicKey)), ); - const accountsWithEquity = liqorMangoAccounts.map((a: MangoAccount) => { + const accountsWithEquity = mangoAccounts.map((a: MangoAccount) => { return { Account: { val: a.publicKey, highlight: false }, Equity: { @@ -399,6 +406,20 @@ export async function getRiskStats( group: Group, change = 0.4, // simulates 40% price rally and price drop on tokens and markets ): Promise { + let pis; + try { + pis = await ( + await ( + await buildFetch() + )( + `https://api.mngo.cloud/data/v4/risk/listed-tokens-one-week-price-impacts`, + { mode: 'no-cors' }, + ) + ).json(); + } catch (error) { + pis = []; + } + // Get known liqors let liqors: PublicKey[]; try { @@ -517,10 +538,10 @@ export async function getRiskStats( liqorEquity, marketMakerEquity, ] = await Promise.all([ - getPriceImpactForLiqor(groupDrop, mangoAccounts), - getPriceImpactForLiqor(groupRally, mangoAccounts), - getPriceImpactForLiqor(groupUsdcDepeg, mangoAccounts), - getPriceImpactForLiqor(groupUsdtDepeg, mangoAccounts), + getPriceImpactForLiqor(groupDrop, pis, mangoAccounts), + getPriceImpactForLiqor(groupRally, pis, mangoAccounts), + getPriceImpactForLiqor(groupUsdcDepeg, pis, mangoAccounts), + getPriceImpactForLiqor(groupUsdtDepeg, pis, mangoAccounts), getPerpPositionsToBeLiquidated(groupDrop, mangoAccounts), getPerpPositionsToBeLiquidated(groupRally, mangoAccounts), getEquityForMangoAccounts(client, group, liqors, mangoAccounts), From 7d41927691a7f9a09254f7e91f8ef284b0ee0013 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Wed, 12 Jul 2023 14:31:29 +0200 Subject: [PATCH 21/90] v0.17.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 59b1ca098..a476d9453 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.17.6", + "version": "0.17.8", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From d67970ebc1e66bed5af20ff5f8eb7e96645e10bd Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Wed, 12 Jul 2023 15:28:20 +0200 Subject: [PATCH 22/90] fix header Signed-off-by: microwavedcola1 --- ts/client/src/risk.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ts/client/src/risk.ts b/ts/client/src/risk.ts index e4a11203c..80ebfb779 100644 --- a/ts/client/src/risk.ts +++ b/ts/client/src/risk.ts @@ -413,7 +413,12 @@ export async function getRiskStats( await buildFetch() )( `https://api.mngo.cloud/data/v4/risk/listed-tokens-one-week-price-impacts`, - { mode: 'no-cors' }, + { + mode: 'no-cors', + headers: { + 'Content-Type': 'application/json', + }, + }, ) ).json(); } catch (error) { @@ -429,7 +434,12 @@ export async function getRiskStats( await buildFetch() )( `https://api.mngo.cloud/data/v4/stats/liqors-over_period?over_period=1MONTH`, - { mode: 'no-cors' }, + { + mode: 'no-cors', + headers: { + 'Content-Type': 'application/json', + }, + }, ) ).json() ).map((data) => new PublicKey(data['liqor'])); From 344d77d6de99e00e6069cfa44f96a57a33e545af Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Wed, 12 Jul 2023 15:29:04 +0200 Subject: [PATCH 23/90] v0.17.9 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a476d9453..1e07ed486 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.17.8", + "version": "0.17.9", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From b3b932f148042554800566341c9c8da6b99a44ea Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Wed, 12 Jul 2023 15:45:42 +0200 Subject: [PATCH 24/90] Fix headers Signed-off-by: microwavedcola1 --- ts/client/src/risk.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ts/client/src/risk.ts b/ts/client/src/risk.ts index 80ebfb779..8ec4ae380 100644 --- a/ts/client/src/risk.ts +++ b/ts/client/src/risk.ts @@ -414,9 +414,10 @@ export async function getRiskStats( )( `https://api.mngo.cloud/data/v4/risk/listed-tokens-one-week-price-impacts`, { - mode: 'no-cors', + mode: 'cors', headers: { 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', }, }, ) @@ -435,9 +436,10 @@ export async function getRiskStats( )( `https://api.mngo.cloud/data/v4/stats/liqors-over_period?over_period=1MONTH`, { - mode: 'no-cors', + mode: 'cors', headers: { 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', }, }, ) From 20a4fdebf9612265b911d2390d8d758bae0d1781 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Wed, 12 Jul 2023 15:46:24 +0200 Subject: [PATCH 25/90] v0.17.19 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1e07ed486..516b60255 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.17.9", + "version": "0.17.19", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From 2ebffcdb05de45e40988fb4f7fbeb62a2f8a1f4f Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Wed, 12 Jul 2023 17:25:16 +0200 Subject: [PATCH 26/90] update Signed-off-by: microwavedcola1 --- ts/client/src/accounts/healthCache.ts | 89 ++++++++++++++++++++++++--- 1 file changed, 80 insertions(+), 9 deletions(-) diff --git a/ts/client/src/accounts/healthCache.ts b/ts/client/src/accounts/healthCache.ts index 38e78f310..66c66e22a 100644 --- a/ts/client/src/accounts/healthCache.ts +++ b/ts/client/src/accounts/healthCache.ts @@ -14,7 +14,7 @@ import { Bank, BankForHealth, TokenIndex } from './bank'; import { Group } from './group'; import { HealthType, MangoAccount, PerpPosition } from './mangoAccount'; -import { PerpMarket, PerpOrderSide } from './perp'; +import { PerpMarket, PerpMarketIndex, PerpOrderSide } from './perp'; import { MarketIndex, Serum3Market, Serum3Side } from './serum3'; // â–‘â–‘â–‘â–‘ @@ -235,6 +235,42 @@ export class HealthCache { return tokenBalances; } + effectiveTokenBalancesInternalDisplay( + group: Group, + healthType: HealthType | undefined, + ignoreNegativePerp: boolean, + ): TokenBalanceDisplay[] { + const tokenBalances = new Array(this.tokenInfos.length) + .fill(null) + .map((ignored) => new TokenBalanceDisplay(ZERO_I80F48(), 0, [])); + + for (const perpInfo of this.perpInfos) { + const settleTokenIndex = this.findTokenInfoIndex( + perpInfo.settleTokenIndex, + ); + const perpSettleToken = tokenBalances[settleTokenIndex]; + const healthUnsettled = perpInfo.healthUnsettledPnl(healthType); + perpSettleToken.perpMarketContributions.push({ + market: group.getPerpMarketByMarketIndex( + perpInfo.perpMarketIndex as PerpMarketIndex, + ).name, + contributionUi: toUiDecimalsForQuote(healthUnsettled), + }); + if (!ignoreNegativePerp || healthUnsettled.gt(ZERO_I80F48())) { + perpSettleToken.spotAndPerp.iadd(healthUnsettled); + } + } + + for (const index of this.tokenInfos.keys()) { + const tokenInfo = this.tokenInfos[index]; + const tokenBalance = tokenBalances[index]; + tokenBalance.spotAndPerp.iadd(tokenInfo.balanceSpot); + tokenBalance.spotUi += toUiDecimalsForQuote(tokenInfo.balanceSpot); + } + + return tokenBalances; + } + healthSum(healthType: HealthType, tokenBalances: TokenBalance[]): I80F48 { const health = ZERO_I80F48(); for (const index of this.tokenInfos.keys()) { @@ -265,16 +301,35 @@ export class HealthCache { healthContributionPerAssetUi( group: Group, healthType: HealthType, - ): { asset: string; contribution: number }[] { - const tokenBalances = this.effectiveTokenBalancesInternal( - healthType, - false, - ); + ): { + asset: string; + contribution: number; + contributionDetails: + | { + spotUi: number; + perpMarketContributions: { market: string; contributionUi: number }[]; + } + | undefined; + }[] { + const tokenBalancesDisplay: TokenBalanceDisplay[] = + this.effectiveTokenBalancesInternalDisplay(group, healthType, false); - const ret = new Array<{ asset: string; contribution: number }>(); + const ret = new Array<{ + asset: string; + contribution: number; + contributionDetails: + | { + spotUi: number; + perpMarketContributions: { + market: string; + contributionUi: number; + }[]; + } + | undefined; + }>(); for (const index of this.tokenInfos.keys()) { const tokenInfo = this.tokenInfos[index]; - const tokenBalance = tokenBalances[index]; + const tokenBalance = tokenBalancesDisplay[index]; const contrib = tokenInfo.healthContribution( healthType, tokenBalance.spotAndPerp, @@ -282,6 +337,10 @@ export class HealthCache { ret.push({ asset: group.getFirstBankByTokenIndex(tokenInfo.tokenIndex).name, contribution: toUiDecimalsForQuote(contrib), + contributionDetails: { + spotUi: tokenBalance.spotUi, + perpMarketContributions: tokenBalance.perpMarketContributions, + }, }); } const res = this.computeSerum3Reservations(healthType); @@ -289,13 +348,14 @@ export class HealthCache { const contrib = serum3Info.healthContribution( healthType, this.tokenInfos, - tokenBalances, + tokenBalancesDisplay, res.tokenMaxReserved, res.serum3Reserved[index], ); ret.push({ asset: group.getSerum3MarketByMarketIndex(serum3Info.marketIndex).name, contribution: toUiDecimalsForQuote(contrib), + contributionDetails: undefined, }); } @@ -1429,6 +1489,17 @@ class TokenBalance { constructor(public spotAndPerp: I80F48) {} } +class TokenBalanceDisplay { + constructor( + public spotAndPerp: I80F48, + public spotUi: number, + public perpMarketContributions: { + market: string; + contributionUi: number; + }[], + ) {} +} + class TokenMaxReserved { constructor(public maxSerumReserved: I80F48) {} } From 524dec57c1906f37076375ef41fb677292c9ba14 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Wed, 12 Jul 2023 17:25:52 +0200 Subject: [PATCH 27/90] v0.17.20 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 516b60255..d8a558b08 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.17.19", + "version": "0.17.20", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From c2a939bcfdabf4ff7684d4a4b9097cd4697291ce Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Thu, 13 Jul 2023 12:15:28 +0200 Subject: [PATCH 28/90] Fix decimals Signed-off-by: microwavedcola1 --- ts/client/src/accounts/healthCache.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/ts/client/src/accounts/healthCache.ts b/ts/client/src/accounts/healthCache.ts index 66c66e22a..eb56795bb 100644 --- a/ts/client/src/accounts/healthCache.ts +++ b/ts/client/src/accounts/healthCache.ts @@ -9,7 +9,11 @@ import { ONE_I80F48, ZERO_I80F48, } from '../numbers/I80F48'; -import { toNativeI80F48ForQuote, toUiDecimalsForQuote } from '../utils'; +import { + toNativeI80F48ForQuote, + toUiDecimals, + toUiDecimalsForQuote, +} from '../utils'; import { Bank, BankForHealth, TokenIndex } from './bank'; import { Group } from './group'; @@ -254,7 +258,10 @@ export class HealthCache { market: group.getPerpMarketByMarketIndex( perpInfo.perpMarketIndex as PerpMarketIndex, ).name, - contributionUi: toUiDecimalsForQuote(healthUnsettled), + contributionUi: toUiDecimals( + healthUnsettled, + group.getMintDecimalsByTokenIndex(perpInfo.settleTokenIndex), + ), }); if (!ignoreNegativePerp || healthUnsettled.gt(ZERO_I80F48())) { perpSettleToken.spotAndPerp.iadd(healthUnsettled); @@ -265,7 +272,10 @@ export class HealthCache { const tokenInfo = this.tokenInfos[index]; const tokenBalance = tokenBalances[index]; tokenBalance.spotAndPerp.iadd(tokenInfo.balanceSpot); - tokenBalance.spotUi += toUiDecimalsForQuote(tokenInfo.balanceSpot); + tokenBalance.spotUi += toUiDecimals( + tokenInfo.balanceSpot, + group.getMintDecimalsByTokenIndex(tokenInfo.tokenIndex), + ); } return tokenBalances; From 23b72b54a3ca20bf7e8db89c02882af1114f9573 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Thu, 13 Jul 2023 12:15:55 +0200 Subject: [PATCH 29/90] v0.17.21 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d8a558b08..e1ad1da34 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.17.20", + "version": "0.17.21", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From e623b8c276d6f2ec3a48e2b9999012e35ffc44f1 Mon Sep 17 00:00:00 2001 From: microwavedcola1 <89031858+microwavedcola1@users.noreply.github.com> Date: Thu, 13 Jul 2023 16:29:13 +0200 Subject: [PATCH 30/90] Enforce safety limits while borrowing, while computing max swap source, and max spot order base/quote (#642) * --wip-- [skip ci] * enforce safety limits while borrowing Signed-off-by: microwavedcola1 * --wip-- [skip ci] * --wip-- [skip ci] * --wip-- [skip ci] * Fixes from review Signed-off-by: microwavedcola1 * Fixes from review Signed-off-by: microwavedcola1 --------- Signed-off-by: microwavedcola1 --- ts/client/src/accounts/bank.ts | 54 +++++++++++++++++++++----- ts/client/src/accounts/group.ts | 21 ++++++---- ts/client/src/accounts/mangoAccount.ts | 43 +++++++------------- 3 files changed, 74 insertions(+), 44 deletions(-) diff --git a/ts/client/src/accounts/bank.ts b/ts/client/src/accounts/bank.ts index dcb1fb910..e24ae651d 100644 --- a/ts/client/src/accounts/bank.ts +++ b/ts/client/src/accounts/bank.ts @@ -1,7 +1,7 @@ import { BN } from '@coral-xyz/anchor'; import { utf8 } from '@coral-xyz/anchor/dist/cjs/utils/bytes'; import { PublicKey } from '@solana/web3.js'; -import { I80F48, I80F48Dto, ZERO_I80F48 } from '../numbers/I80F48'; +import { I80F48, I80F48Dto, ONE_I80F48, ZERO_I80F48 } from '../numbers/I80F48'; import { As, toUiDecimals } from '../utils'; import { OracleProvider } from './oracle'; @@ -397,17 +397,11 @@ export class Bank implements BankForHealth { } uiDeposits(): number { - return toUiDecimals( - this.indexedDeposits.mul(this.depositIndex), - this.mintDecimals, - ); + return toUiDecimals(this.nativeDeposits(), this.mintDecimals); } uiBorrows(): number { - return toUiDecimals( - this.indexedBorrows.mul(this.borrowIndex), - this.mintDecimals, - ); + return toUiDecimals(this.nativeBorrows(), this.mintDecimals); } /** @@ -481,6 +475,48 @@ export class Bank implements BankForHealth { getDepositRateUi(): number { return this.getDepositRate().toNumber() * 100; } + + getNetBorrowLimitPerWindow(): I80F48 { + return I80F48.fromI64(this.netBorrowLimitPerWindowQuote).div(this.price); + } + + getBorrowLimitLeftInWindow(): I80F48 { + return this.getNetBorrowLimitPerWindow() + .sub(I80F48.fromI64(this.netBorrowsInWindow)) + .max(ZERO_I80F48()); + } + + getNetBorrowLimitPerWindowUi(): number { + return toUiDecimals(this.getNetBorrowLimitPerWindow(), this.mintDecimals); + } + + getMaxWithdraw(vaultBalance: BN, userDeposits = ZERO_I80F48()): I80F48 { + userDeposits = userDeposits.max(ZERO_I80F48()); + + // any borrow must respect the minVaultToDepositsRatio + const minVaultBalanceRequired = this.nativeDeposits().mul( + I80F48.fromNumber(this.minVaultToDepositsRatio), + ); + const maxBorrowFromVault = I80F48.fromI64(vaultBalance) + .sub(minVaultBalanceRequired) + .max(ZERO_I80F48()); + // User deposits can exceed maxWithdrawFromVault + let maxBorrow = maxBorrowFromVault.sub(userDeposits).max(ZERO_I80F48()); + // any borrow must respect the limit left in window + maxBorrow = maxBorrow.min(this.getBorrowLimitLeftInWindow()); + // borrows would be applied a fee + maxBorrow = maxBorrow.div(ONE_I80F48().add(this.loanOriginationFeeRate)); + + // user deposits can always be withdrawn + // even if vaults can be depleted + return maxBorrow.add(userDeposits).min(I80F48.fromI64(vaultBalance)); + } + + getTimeToNextBorrowLimitWindowStartsTs(): number { + return this.netBorrowLimitWindowSizeTs + .sub(new BN(Date.now() / 1000).sub(this.lastNetBorrowsWindowStartTs)) + .toNumber(); + } } export class MintInfo { diff --git a/ts/client/src/accounts/group.ts b/ts/client/src/accounts/group.ts index f1cab82c3..df3bc7b0b 100644 --- a/ts/client/src/accounts/group.ts +++ b/ts/client/src/accounts/group.ts @@ -488,12 +488,7 @@ export class Group { return this.getFirstBankByTokenIndex(0 as TokenIndex); } - /** - * - * @param mintPk - * @returns sum of ui balances of vaults for all banks for a token - */ - public getTokenVaultBalanceByMintUi(mintPk: PublicKey): number { + public getTokenVaultBalanceByMint(mintPk: PublicKey): BN { const banks = this.banksMapByMint.get(mintPk.toBase58()); if (!banks) { throw new Error(`No bank found for mint ${mintPk}!`); @@ -509,7 +504,19 @@ export class Group { totalAmount.iadd(amount); } - return toUiDecimals(totalAmount, this.getMintDecimals(mintPk)); + return totalAmount; + } + + /** + * + * @param mintPk + * @returns sum of ui balances of vaults for all banks for a token + */ + public getTokenVaultBalanceByMintUi(mintPk: PublicKey): number { + return toUiDecimals( + this.getTokenVaultBalanceByMint(mintPk), + this.getMintDecimals(mintPk), + ); } public getSerum3MarketByMarketIndex(marketIndex: MarketIndex): Serum3Market { diff --git a/ts/client/src/accounts/mangoAccount.ts b/ts/client/src/accounts/mangoAccount.ts index c62379169..554277c01 100644 --- a/ts/client/src/accounts/mangoAccount.ts +++ b/ts/client/src/accounts/mangoAccount.ts @@ -594,22 +594,11 @@ export class MangoAccount { ), ); const sourceBalance = this.getEffectiveTokenBalance(group, sourceBank); - if (maxSource.gt(sourceBalance)) { - // Cannot borrow more than the window limit - let sourceBorrow = maxSource.sub(sourceBalance); - const netBorrowLimitPerWindow = I80F48.fromI64( - sourceBank.netBorrowLimitPerWindowQuote, - ).div(sourceBank.price); - sourceBorrow = sourceBorrow.min(netBorrowLimitPerWindow); - - maxSource = sourceBalance.add( - sourceBorrow.div(ONE_I80F48().add(sourceBank.loanOriginationFeeRate)), - ); - } - - // Cannot swap more than total deposits in mango for the token - maxSource = maxSource.min(sourceBank.nativeDeposits()); - + const maxWithdrawNative = sourceBank.getMaxWithdraw( + group.getTokenVaultBalanceByMint(sourceBank.mint), + sourceBalance, + ); + maxSource = maxSource.min(maxWithdrawNative); return toUiDecimals(maxSource, group.getMintDecimals(sourceMintPk)); } @@ -740,12 +729,11 @@ export class MangoAccount { // If its a bid then the reserved fund and potential loan is in base // also keep some buffer for fees, use taker fees for worst case simulation. const quoteBalance = this.getEffectiveTokenBalance(group, quoteBank); - if (quoteAmount.gt(quoteBalance)) { - const quoteBorrow = quoteAmount.sub(quoteBalance); - quoteAmount = quoteBalance.add( - quoteBorrow.div(ONE_I80F48().add(quoteBank.loanOriginationFeeRate)), - ); - } + const maxWithdrawNative = quoteBank.getMaxWithdraw( + group.getTokenVaultBalanceByMint(quoteBank.mint), + quoteBalance, + ); + quoteAmount = quoteAmount.min(maxWithdrawNative); quoteAmount = quoteAmount.div( ONE_I80F48().add(I80F48.fromNumber(serum3Market.getFeeRates(true))), ); @@ -782,12 +770,11 @@ export class MangoAccount { // If its a ask then the reserved fund and potential loan is in base // also keep some buffer for fees, use taker fees for worst case simulation. const baseBalance = this.getEffectiveTokenBalance(group, baseBank); - if (baseAmount.gt(baseBalance)) { - const baseBorrow = baseAmount.sub(baseBalance); - baseAmount = baseBalance.add( - baseBorrow.div(ONE_I80F48().add(baseBank.loanOriginationFeeRate)), - ); - } + const maxWithdrawNative = baseBank.getMaxWithdraw( + group.getTokenVaultBalanceByMint(baseBank.mint), + baseBalance, + ); + baseAmount = baseAmount.min(maxWithdrawNative); baseAmount = baseAmount.div( ONE_I80F48().add(I80F48.fromNumber(serum3Market.getFeeRates(true))), ); From 9d2e4bf609d8ded7d291f8142604382095fad7fb Mon Sep 17 00:00:00 2001 From: microwavedcola1 <89031858+microwavedcola1@users.noreply.github.com> Date: Thu, 13 Jul 2023 17:10:16 +0200 Subject: [PATCH 31/90] Perp close all positions helper (#643) * close multiple perp positions in one tx Signed-off-by: microwavedcola1 * settle all Signed-off-by: microwavedcola1 * update Signed-off-by: microwavedcola1 * Fixes from review Signed-off-by: microwavedcola1 * Fixes from review Signed-off-by: microwavedcola1 --------- Signed-off-by: microwavedcola1 --- ts/client/src/client.ts | 100 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index 59682f992..bf7233906 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -2265,6 +2265,56 @@ export class MangoClient { return await this.sendAndConfirmTransactionForGroup(group, [ix]); } + public async perpCloseAll( + group: Group, + mangoAccount: MangoAccount, + slippage = 0.01, // 1%, 100bps + ): Promise { + if (mangoAccount.perpActive().length == 0) { + throw new Error(`No perp positions found.`); + } + + if (mangoAccount.perpActive().length > 8) { + // Technically we can fit in 16, 1.6M CU, 100k CU per ix, but lets be conservative + throw new Error( + `Can't close more than 8 positions in one tx, due to compute usage limit.`, + ); + } + + const hrix1 = await this.healthRegionBeginIx(group, mangoAccount); + const ixs = await Promise.all( + mangoAccount.perpActive().map(async (pa) => { + const pm = group.getPerpMarketByMarketIndex(pa.marketIndex); + const isLong = pa.basePositionLots.gt(new BN(0)); + + return await this.perpPlaceOrderV2Ix( + group, + mangoAccount, + 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 + undefined, + Date.now(), + PerpOrderType.immediateOrCancel, + PerpSelfTradeBehavior.decrementTake, + true, // Reduce only + undefined, + undefined, + ); + }), + ); + const hrix2 = await this.healthRegionEndIx(group, mangoAccount); + + return await this.sendAndConfirmTransactionForGroup( + group, + [hrix1, ...ixs, hrix2], + { + prioritizationFee: true, + }, + ); + } + // perpPlaceOrder ix returns an optional, custom order id, // but, since we use a customer tx sender, this method // doesn't return it @@ -2642,6 +2692,56 @@ export class MangoClient { .instruction(); } + async perpSettleAll( + client: MangoClient, + group: Group, + mangoAccount: MangoAccount, + allMangoAccounts?: MangoAccount[], + ): Promise { + if (!allMangoAccounts) { + allMangoAccounts = await client.getAllMangoAccounts(group, true); + } + + const ixs = new Array(); + // This is optimistic, since we might find the same opponent candidate for all markets, + // and they have might not be able to settle at some point due to safety limits + // Future: correct way to do is, to apply the settlement on a copy and then move to next position + for (const pa of mangoAccount.perpActive()) { + const pm = group.getPerpMarketByMarketIndex(pa.marketIndex); + const candidates = await pm.getSettlePnlCandidates( + client, + group, + allMangoAccounts, + pa.getUnsettledPnlUi(pm) > 0 ? 'negative' : 'positive', + 2, + ); + if (candidates.length == 0) { + continue; + } + ixs.push( + await this.perpSettlePnlIx( + group, + pa.getUnsettledPnlUi(pm) > 0 ? mangoAccount : candidates[0].account, + pa.getUnsettledPnlUi(pm) < 0 ? candidates[0].account : mangoAccount, + mangoAccount, + pm.perpMarketIndex, + ), + ); + ixs.push( + await this.perpSettleFeesIx( + group, + mangoAccount, + pm.perpMarketIndex, + undefined, + ), + ); + } + + return await this.sendAndConfirmTransactionForGroup(group, ixs, { + prioritizationFee: true, + }); + } + async perpSettlePnlAndFees( group: Group, profitableAccount: MangoAccount, From 1e61a6429e8ebcd81aff3bdfa2a15088a5759ce4 Mon Sep 17 00:00:00 2001 From: microwavedcola1 <89031858+microwavedcola1@users.noreply.github.com> Date: Thu, 13 Jul 2023 18:22:35 +0200 Subject: [PATCH 32/90] Fees to dao (#644) * withdraw fees to dao Signed-off-by: microwavedcola1 * Fixes from review Signed-off-by: microwavedcola1 * Fixes from review Signed-off-by: microwavedcola1 * Fixes from review Signed-off-by: microwavedcola1 * Fixes from review Signed-off-by: microwavedcola1 * Fixes from review Signed-off-by: microwavedcola1 * Fixes from review Signed-off-by: microwavedcola1 * rename Signed-off-by: microwavedcola1 * Fixes from review Signed-off-by: microwavedcola1 --------- Signed-off-by: microwavedcola1 --- .../accounts_ix/admin_perp_withdraw_fees.rs | 52 +++++++++++++++++++ .../accounts_ix/admin_token_withdraw_fees.rs | 45 ++++++++++++++++ programs/mango-v4/src/accounts_ix/mod.rs | 4 ++ .../instructions/admin_perp_withdraw_fees.rs | 21 ++++++++ .../instructions/admin_withdraw_token_fees.rs | 21 ++++++++ .../mango-v4/src/instructions/ix_gate_set.rs | 12 +++++ programs/mango-v4/src/instructions/mod.rs | 4 ++ .../instructions/token_register_trustless.rs | 4 +- programs/mango-v4/src/lib.rs | 12 +++++ programs/mango-v4/src/state/bank.rs | 16 ++++-- programs/mango-v4/src/state/group.rs | 12 +++++ programs/mango-v4/src/state/perp_market.rs | 14 +++-- ts/client/src/clientIxParamBuilder.ts | 36 +++++++++++++ 13 files changed, 246 insertions(+), 7 deletions(-) create mode 100644 programs/mango-v4/src/accounts_ix/admin_perp_withdraw_fees.rs create mode 100644 programs/mango-v4/src/accounts_ix/admin_token_withdraw_fees.rs create mode 100644 programs/mango-v4/src/instructions/admin_perp_withdraw_fees.rs create mode 100644 programs/mango-v4/src/instructions/admin_withdraw_token_fees.rs diff --git a/programs/mango-v4/src/accounts_ix/admin_perp_withdraw_fees.rs b/programs/mango-v4/src/accounts_ix/admin_perp_withdraw_fees.rs new file mode 100644 index 000000000..3b3db877d --- /dev/null +++ b/programs/mango-v4/src/accounts_ix/admin_perp_withdraw_fees.rs @@ -0,0 +1,52 @@ +use anchor_lang::prelude::*; +use anchor_spl::token; +use anchor_spl::token::Token; +use anchor_spl::token::TokenAccount; + +use crate::error::*; +use crate::state::*; + +#[derive(Accounts)] +pub struct AdminPerpWithdrawFees<'info> { + #[account( + constraint = group.load()?.is_ix_enabled(IxGate::AdminPerpWithdrawFees) @ MangoError::IxIsDisabled, + has_one = admin, + )] + pub group: AccountLoader<'info, Group>, + + #[account( + mut, + has_one = group, + )] + pub perp_market: AccountLoader<'info, PerpMarket>, + + #[account( + mut, + has_one = group, + has_one = vault, + constraint = bank.load()?.token_index == perp_market.load()?.settle_token_index + )] + pub bank: AccountLoader<'info, Bank>, + + #[account(mut)] + pub vault: Account<'info, TokenAccount>, + + #[account(mut)] + pub token_account: Box>, + + pub token_program: Program<'info, Token>, + + pub admin: Signer<'info>, +} + +impl<'info> AdminPerpWithdrawFees<'info> { + pub fn transfer_ctx(&self) -> CpiContext<'_, '_, '_, 'info, token::Transfer<'info>> { + let program = self.token_program.to_account_info(); + let accounts = token::Transfer { + from: self.vault.to_account_info(), + to: self.token_account.to_account_info(), + authority: self.group.to_account_info(), + }; + CpiContext::new(program, accounts) + } +} diff --git a/programs/mango-v4/src/accounts_ix/admin_token_withdraw_fees.rs b/programs/mango-v4/src/accounts_ix/admin_token_withdraw_fees.rs new file mode 100644 index 000000000..50ea5b76c --- /dev/null +++ b/programs/mango-v4/src/accounts_ix/admin_token_withdraw_fees.rs @@ -0,0 +1,45 @@ +use anchor_lang::prelude::*; +use anchor_spl::token; +use anchor_spl::token::Token; +use anchor_spl::token::TokenAccount; + +use crate::error::*; +use crate::state::*; + +#[derive(Accounts)] +pub struct AdminTokenWithdrawFees<'info> { + #[account( + constraint = group.load()?.is_ix_enabled(IxGate::AdminTokenWithdrawFees) @ MangoError::IxIsDisabled, + has_one = admin, + )] + pub group: AccountLoader<'info, Group>, + + #[account( + mut, + has_one = group, + has_one = vault, + )] + pub bank: AccountLoader<'info, Bank>, + + #[account(mut)] + pub vault: Account<'info, TokenAccount>, + + #[account(mut)] + pub token_account: Box>, + + pub token_program: Program<'info, Token>, + + pub admin: Signer<'info>, +} + +impl<'info> AdminTokenWithdrawFees<'info> { + pub fn transfer_ctx(&self) -> CpiContext<'_, '_, '_, 'info, token::Transfer<'info>> { + let program = self.token_program.to_account_info(); + let accounts = token::Transfer { + from: self.vault.to_account_info(), + to: self.token_account.to_account_info(), + authority: self.group.to_account_info(), + }; + CpiContext::new(program, accounts) + } +} diff --git a/programs/mango-v4/src/accounts_ix/mod.rs b/programs/mango-v4/src/accounts_ix/mod.rs index dde1ef212..82f503e3f 100644 --- a/programs/mango-v4/src/accounts_ix/mod.rs +++ b/programs/mango-v4/src/accounts_ix/mod.rs @@ -5,6 +5,8 @@ pub use account_create::*; pub use account_edit::*; pub use account_expand::*; pub use account_toggle_freeze::*; +pub use admin_perp_withdraw_fees::*; +pub use admin_token_withdraw_fees::*; pub use alt_extend::*; pub use alt_set::*; pub use benchmark::*; @@ -67,6 +69,8 @@ mod account_create; mod account_edit; mod account_expand; mod account_toggle_freeze; +mod admin_perp_withdraw_fees; +mod admin_token_withdraw_fees; mod alt_extend; mod alt_set; mod benchmark; diff --git a/programs/mango-v4/src/instructions/admin_perp_withdraw_fees.rs b/programs/mango-v4/src/instructions/admin_perp_withdraw_fees.rs new file mode 100644 index 000000000..0c43929a9 --- /dev/null +++ b/programs/mango-v4/src/instructions/admin_perp_withdraw_fees.rs @@ -0,0 +1,21 @@ +use anchor_lang::prelude::*; +use anchor_spl::token; + +use crate::{accounts_ix::*, group_seeds}; + +pub fn admin_perp_withdraw_fees(ctx: Context) -> Result<()> { + let group = ctx.accounts.group.load()?; + let mut perp_market = ctx.accounts.perp_market.load_mut()?; + + let group_seeds = group_seeds!(group); + let fees = perp_market.fees_settled.floor().to_num::() - perp_market.fees_withdrawn; + let amount = fees.min(ctx.accounts.vault.amount); + token::transfer( + ctx.accounts.transfer_ctx().with_signer(&[group_seeds]), + amount, + )?; + + perp_market.fees_withdrawn += amount; + + Ok(()) +} diff --git a/programs/mango-v4/src/instructions/admin_withdraw_token_fees.rs b/programs/mango-v4/src/instructions/admin_withdraw_token_fees.rs new file mode 100644 index 000000000..fb8dbc168 --- /dev/null +++ b/programs/mango-v4/src/instructions/admin_withdraw_token_fees.rs @@ -0,0 +1,21 @@ +use anchor_lang::prelude::*; +use anchor_spl::token; + +use crate::{accounts_ix::*, group_seeds}; + +pub fn admin_withdraw_token_fees(ctx: Context) -> Result<()> { + let group = ctx.accounts.group.load()?; + let mut bank = ctx.accounts.bank.load_mut()?; + + let group_seeds = group_seeds!(group); + let fees = bank.collected_fees_native.floor().to_num::() - bank.fees_withdrawn; + let amount = fees.min(ctx.accounts.vault.amount); + token::transfer( + ctx.accounts.transfer_ctx().with_signer(&[group_seeds]), + amount, + )?; + + bank.fees_withdrawn += amount; + + Ok(()) +} diff --git a/programs/mango-v4/src/instructions/ix_gate_set.rs b/programs/mango-v4/src/instructions/ix_gate_set.rs index e91a56374..7a9ea0bae 100644 --- a/programs/mango-v4/src/instructions/ix_gate_set.rs +++ b/programs/mango-v4/src/instructions/ix_gate_set.rs @@ -70,6 +70,18 @@ pub fn ix_gate_set(ctx: Context, ix_gate: u128) -> Result<()> { log_if_changed(&group, ix_gate, IxGate::TokenConditionalSwapCreate); log_if_changed(&group, ix_gate, IxGate::TokenConditionalSwapTrigger); log_if_changed(&group, ix_gate, IxGate::TokenConditionalSwapCancel); + log_if_changed(&group, ix_gate, IxGate::OpenbookV2CancelOrder); + log_if_changed(&group, ix_gate, IxGate::OpenbookV2CloseOpenOrders); + log_if_changed(&group, ix_gate, IxGate::OpenbookV2CreateOpenOrders); + log_if_changed(&group, ix_gate, IxGate::OpenbookV2DeregisterMarket); + log_if_changed(&group, ix_gate, IxGate::OpenbookV2EditMarket); + log_if_changed(&group, ix_gate, IxGate::OpenbookV2LiqForceCancelOrders); + log_if_changed(&group, ix_gate, IxGate::OpenbookV2PlaceOrder); + log_if_changed(&group, ix_gate, IxGate::OpenbookV2PlaceTakeOrder); + log_if_changed(&group, ix_gate, IxGate::OpenbookV2RegisterMarket); + log_if_changed(&group, ix_gate, IxGate::OpenbookV2SettleFunds); + log_if_changed(&group, ix_gate, IxGate::AdminTokenWithdrawFees); + log_if_changed(&group, ix_gate, IxGate::AdminPerpWithdrawFees); group.ix_gate = ix_gate; diff --git a/programs/mango-v4/src/instructions/mod.rs b/programs/mango-v4/src/instructions/mod.rs index 485dbd9ea..7929c2d4a 100644 --- a/programs/mango-v4/src/instructions/mod.rs +++ b/programs/mango-v4/src/instructions/mod.rs @@ -4,6 +4,8 @@ pub use account_create::*; pub use account_edit::*; pub use account_expand::*; pub use account_toggle_freeze::*; +pub use admin_perp_withdraw_fees::*; +pub use admin_withdraw_token_fees::*; pub use alt_extend::*; pub use alt_set::*; pub use benchmark::*; @@ -66,6 +68,8 @@ mod account_create; mod account_edit; mod account_expand; mod account_toggle_freeze; +mod admin_perp_withdraw_fees; +mod admin_withdraw_token_fees; mod alt_extend; mod alt_set; mod benchmark; diff --git a/programs/mango-v4/src/instructions/token_register_trustless.rs b/programs/mango-v4/src/instructions/token_register_trustless.rs index 977cd1e0f..b25b6a85f 100644 --- a/programs/mango-v4/src/instructions/token_register_trustless.rs +++ b/programs/mango-v4/src/instructions/token_register_trustless.rs @@ -75,7 +75,9 @@ pub fn token_register_trustless( deposit_weight_scale_start_quote: 5_000_000_000.0, // $5k reduce_only: 2, // deposit-only force_close: 0, - reserved: [0; 2118], + padding: [0; 6], + fees_withdrawn: 0, + reserved: [0; 2104], }; require_gt!(bank.max_rate, MINIMUM_MAX_RATE); diff --git a/programs/mango-v4/src/lib.rs b/programs/mango-v4/src/lib.rs index 503bff185..7f7e342c7 100644 --- a/programs/mango-v4/src/lib.rs +++ b/programs/mango-v4/src/lib.rs @@ -43,6 +43,18 @@ pub mod mango_v4 { use super::*; use error::*; + pub fn admin_withdraw_token_fees(ctx: Context) -> Result<()> { + #[cfg(feature = "enable-gpl")] + instructions::admin_withdraw_token_fees(ctx)?; + Ok(()) + } + + pub fn admin_perp_withdraw_fees(ctx: Context) -> Result<()> { + #[cfg(feature = "enable-gpl")] + instructions::admin_perp_withdraw_fees(ctx)?; + Ok(()) + } + pub fn group_create( ctx: Context, group_num: u32, diff --git a/programs/mango-v4/src/state/bank.rs b/programs/mango-v4/src/state/bank.rs index a70f30fd0..f090a8ae2 100644 --- a/programs/mango-v4/src/state/bank.rs +++ b/programs/mango-v4/src/state/bank.rs @@ -136,8 +136,14 @@ pub struct Bank { pub reduce_only: u8, pub force_close: u8, + pub padding: [u8; 6], + + // Do separate bookkeping for how many tokens were withdrawn + // This ensures that collected_fees_native is strictly increasing for stats gathering purposes + pub fees_withdrawn: u64, + #[derivative(Debug = "ignore")] - pub reserved: [u8; 2118], + pub reserved: [u8; 2104], } const_assert_eq!( size_of::(), @@ -166,7 +172,9 @@ const_assert_eq!( + 8 + 1 + 1 - + 2118 + + 6 + + 8 + + 2104 ); const_assert_eq!(size_of::(), 3064); const_assert_eq!(size_of::() % 8, 0); @@ -240,7 +248,9 @@ impl Bank { deposit_weight_scale_start_quote: f64::MAX, reduce_only: 0, force_close: 0, - reserved: [0; 2118], + padding: [0; 6], + fees_withdrawn: 0, + reserved: [0; 2104], } } diff --git a/programs/mango-v4/src/state/group.rs b/programs/mango-v4/src/state/group.rs index 1cfb8e3e1..ac0f45c70 100644 --- a/programs/mango-v4/src/state/group.rs +++ b/programs/mango-v4/src/state/group.rs @@ -197,6 +197,18 @@ pub enum IxGate { TokenConditionalSwapCreate = 52, TokenConditionalSwapTrigger = 53, TokenConditionalSwapCancel = 54, + OpenbookV2CancelOrder = 55, + OpenbookV2CloseOpenOrders = 56, + OpenbookV2CreateOpenOrders = 57, + OpenbookV2DeregisterMarket = 58, + OpenbookV2EditMarket = 59, + OpenbookV2LiqForceCancelOrders = 60, + OpenbookV2PlaceOrder = 61, + OpenbookV2PlaceTakeOrder = 62, + OpenbookV2RegisterMarket = 63, + OpenbookV2SettleFunds = 64, + AdminTokenWithdrawFees = 65, + AdminPerpWithdrawFees = 66, // NOTE: Adding new variants requires matching changes in ts and the ix_gate_set instruction. } diff --git a/programs/mango-v4/src/state/perp_market.rs b/programs/mango-v4/src/state/perp_market.rs index 0dc72c86a..80b979170 100644 --- a/programs/mango-v4/src/state/perp_market.rs +++ b/programs/mango-v4/src/state/perp_market.rs @@ -124,8 +124,10 @@ pub struct PerpMarket { pub taker_fee: I80F48, /// Fees accrued in native quote currency + /// these are increased when new fees are paid and decreased when perp_settle_fees is called pub fees_accrued: I80F48, /// Fees settled in native quote currency + /// these are increased when perp_settle_fees is called, and never decreased pub fees_settled: I80F48, /// Fee (in quote native) to charge for ioc orders @@ -170,7 +172,11 @@ pub struct PerpMarket { pub positive_pnl_liquidation_fee: I80F48, - pub reserved: [u8; 1888], + // Do separate bookkeping for how many tokens were withdrawn + // This ensures that fees_settled is strictly increasing for stats gathering purposes + pub fees_withdrawn: u64, + + pub reserved: [u8; 1880], } const_assert_eq!( @@ -206,7 +212,8 @@ const_assert_eq!( + 1 + 7 + 3 * 16 - + 1888 + + 8 + + 1880 ); const_assert_eq!(size_of::(), 2808); const_assert_eq!(size_of::() % 8, 0); @@ -495,7 +502,8 @@ impl PerpMarket { maint_overall_asset_weight: I80F48::ONE, init_overall_asset_weight: I80F48::ONE, positive_pnl_liquidation_fee: I80F48::ZERO, - reserved: [0; 1888], + fees_withdrawn: 0, + reserved: [0; 1880], } } } diff --git a/ts/client/src/clientIxParamBuilder.ts b/ts/client/src/clientIxParamBuilder.ts index 35afe5dcb..f74a8f379 100644 --- a/ts/client/src/clientIxParamBuilder.ts +++ b/ts/client/src/clientIxParamBuilder.ts @@ -182,6 +182,18 @@ export interface IxGateParams { TokenConditionalSwapCreate: boolean; TokenConditionalSwapTrigger: boolean; TokenConditionalSwapCancel: boolean; + OpenbookV2CancelOrder: boolean; + OpenbookV2CloseOpenOrders: boolean; + OpenbookV2CreateOpenOrders: boolean; + OpenbookV2DeregisterMarket: boolean; + OpenbookV2EditMarket: boolean; + OpenbookV2LiqForceCancelOrders: boolean; + OpenbookV2PlaceOrder: boolean; + OpenbookV2PlaceTakeOrder: boolean; + OpenbookV2RegisterMarket: boolean; + OpenbookV2SettleFunds: boolean; + AdminTokenWithdrawFees: boolean; + AdminPerpWithdrawFees: boolean; } // Default with all ixs enabled, use with buildIxGate @@ -244,6 +256,18 @@ export const TrueIxGateParams: IxGateParams = { TokenConditionalSwapCreate: true, TokenConditionalSwapTrigger: true, TokenConditionalSwapCancel: true, + OpenbookV2CancelOrder: true, + OpenbookV2CloseOpenOrders: true, + OpenbookV2CreateOpenOrders: true, + OpenbookV2DeregisterMarket: true, + OpenbookV2EditMarket: true, + OpenbookV2LiqForceCancelOrders: true, + OpenbookV2PlaceOrder: true, + OpenbookV2PlaceTakeOrder: true, + OpenbookV2RegisterMarket: true, + OpenbookV2SettleFunds: true, + AdminTokenWithdrawFees: true, + AdminPerpWithdrawFees: true, }; // build ix gate e.g. buildIxGate(Builder(TrueIxGateParams).TokenDeposit(false).build()).toNumber(), @@ -316,6 +340,18 @@ export function buildIxGate(p: IxGateParams): BN { toggleIx(ixGate, p, 'TokenConditionalSwapCreate', 52); toggleIx(ixGate, p, 'TokenConditionalSwapTrigger', 53); toggleIx(ixGate, p, 'TokenConditionalSwapCancel', 54); + toggleIx(ixGate, p, 'OpenbookV2CancelOrder', 55); + toggleIx(ixGate, p, 'OpenbookV2CloseOpenOrders', 56); + toggleIx(ixGate, p, 'OpenbookV2CreateOpenOrders', 57); + toggleIx(ixGate, p, 'OpenbookV2DeregisterMarket', 58); + toggleIx(ixGate, p, 'OpenbookV2EditMarket', 59); + toggleIx(ixGate, p, 'OpenbookV2LiqForceCancelOrders', 60); + toggleIx(ixGate, p, 'OpenbookV2PlaceOrder', 61); + toggleIx(ixGate, p, 'OpenbookV2PlaceTakeOrder', 62); + toggleIx(ixGate, p, 'OpenbookV2RegisterMarket', 63); + toggleIx(ixGate, p, 'OpenbookV2SettleFunds', 63); + toggleIx(ixGate, p, 'AdminTokenWithdrawFees', 65); + toggleIx(ixGate, p, 'AdminPerpWithdrawFees', 66); return ixGate; } From fd03bfc68b894d09a8266eb2a56d216fc2c2a7bb Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Thu, 13 Jul 2023 18:41:01 +0200 Subject: [PATCH 33/90] Fix rename Signed-off-by: microwavedcola1 --- ...in_withdraw_token_fees.rs => admin_token_withdraw_fees.rs} | 2 +- programs/mango-v4/src/instructions/mod.rs | 4 ++-- programs/mango-v4/src/lib.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename programs/mango-v4/src/instructions/{admin_withdraw_token_fees.rs => admin_token_withdraw_fees.rs} (89%) diff --git a/programs/mango-v4/src/instructions/admin_withdraw_token_fees.rs b/programs/mango-v4/src/instructions/admin_token_withdraw_fees.rs similarity index 89% rename from programs/mango-v4/src/instructions/admin_withdraw_token_fees.rs rename to programs/mango-v4/src/instructions/admin_token_withdraw_fees.rs index fb8dbc168..fc8f349a5 100644 --- a/programs/mango-v4/src/instructions/admin_withdraw_token_fees.rs +++ b/programs/mango-v4/src/instructions/admin_token_withdraw_fees.rs @@ -3,7 +3,7 @@ use anchor_spl::token; use crate::{accounts_ix::*, group_seeds}; -pub fn admin_withdraw_token_fees(ctx: Context) -> Result<()> { +pub fn admin_token_withdraw_fees(ctx: Context) -> Result<()> { let group = ctx.accounts.group.load()?; let mut bank = ctx.accounts.bank.load_mut()?; diff --git a/programs/mango-v4/src/instructions/mod.rs b/programs/mango-v4/src/instructions/mod.rs index 7929c2d4a..82cdabe79 100644 --- a/programs/mango-v4/src/instructions/mod.rs +++ b/programs/mango-v4/src/instructions/mod.rs @@ -5,7 +5,7 @@ pub use account_edit::*; pub use account_expand::*; pub use account_toggle_freeze::*; pub use admin_perp_withdraw_fees::*; -pub use admin_withdraw_token_fees::*; +pub use admin_token_withdraw_fees::*; pub use alt_extend::*; pub use alt_set::*; pub use benchmark::*; @@ -69,7 +69,7 @@ mod account_edit; mod account_expand; mod account_toggle_freeze; mod admin_perp_withdraw_fees; -mod admin_withdraw_token_fees; +mod admin_token_withdraw_fees; mod alt_extend; mod alt_set; mod benchmark; diff --git a/programs/mango-v4/src/lib.rs b/programs/mango-v4/src/lib.rs index 7f7e342c7..32805dad6 100644 --- a/programs/mango-v4/src/lib.rs +++ b/programs/mango-v4/src/lib.rs @@ -43,9 +43,9 @@ pub mod mango_v4 { use super::*; use error::*; - pub fn admin_withdraw_token_fees(ctx: Context) -> Result<()> { + pub fn adming_token_withdraw_fees(ctx: Context) -> Result<()> { #[cfg(feature = "enable-gpl")] - instructions::admin_withdraw_token_fees(ctx)?; + instructions::admin_token_withdraw_fees(ctx)?; Ok(()) } From fba6ad6d87448fde36bef14891a2023c82305a57 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 14 Jul 2023 10:57:35 +0200 Subject: [PATCH 34/90] bump Signed-off-by: microwavedcola1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e1ad1da34..fc4c36356 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.17.21", + "version": "0.17.22", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From b1b0729eeec26e42122b632c8b8beafb8733f3d5 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 14 Jul 2023 13:19:17 +0200 Subject: [PATCH 35/90] settle all even spot Signed-off-by: microwavedcola1 --- ts/client/src/client.ts | 42 ++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index bf7233906..d4cd93cb6 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -2692,7 +2692,7 @@ export class MangoClient { .instruction(); } - async perpSettleAll( + async settleAll( client: MangoClient, group: Group, mangoAccount: MangoAccount, @@ -2702,7 +2702,7 @@ export class MangoClient { allMangoAccounts = await client.getAllMangoAccounts(group, true); } - const ixs = new Array(); + const ixs1 = new Array(); // This is optimistic, since we might find the same opponent candidate for all markets, // and they have might not be able to settle at some point due to safety limits // Future: correct way to do is, to apply the settlement on a copy and then move to next position @@ -2718,7 +2718,8 @@ export class MangoClient { if (candidates.length == 0) { continue; } - ixs.push( + ixs1.push( + // Takes ~130k CU await this.perpSettlePnlIx( group, pa.getUnsettledPnlUi(pm) > 0 ? mangoAccount : candidates[0].account, @@ -2727,7 +2728,8 @@ export class MangoClient { pm.perpMarketIndex, ), ); - ixs.push( + ixs1.push( + // Takes ~20k CU await this.perpSettleFeesIx( group, mangoAccount, @@ -2737,9 +2739,35 @@ export class MangoClient { ); } - return await this.sendAndConfirmTransactionForGroup(group, ixs, { - prioritizationFee: true, - }); + const ixs2 = await Promise.all( + mangoAccount.serum3Active().map((s) => { + const serum3Market = group.getSerum3MarketByMarketIndex(s.marketIndex); + // Takes ~65k CU + return this.serum3SettleFundsV2Ix( + group, + mangoAccount, + serum3Market.serumMarketExternal, + ); + }), + ); + + if ( + mangoAccount.perpActive().length * 150 + + mangoAccount.serum3Active().length * 65 > + 1600 + ) { + throw new Error( + `Too many perp positions and serum open orders to settle in one tx! Please try settling individually!`, + ); + } + + return await this.sendAndConfirmTransactionForGroup( + group, + [...ixs1, ...ixs2], + { + prioritizationFee: true, + }, + ); } async perpSettlePnlAndFees( From 1772754fd20a7460bbf322158d2a31d799d6f142 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 14 Jul 2023 13:19:57 +0200 Subject: [PATCH 36/90] v0.17.23 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fc4c36356..2a0acace6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.17.22", + "version": "0.17.23", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From 7e917bb85de63ccead13a41aff8e985f0b313951 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 14 Jul 2023 14:02:06 +0200 Subject: [PATCH 37/90] Fix type Signed-off-by: microwavedcola1 --- ts/client/src/accounts/mangoAccount.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ts/client/src/accounts/mangoAccount.ts b/ts/client/src/accounts/mangoAccount.ts index 554277c01..dd0ccd5fe 100644 --- a/ts/client/src/accounts/mangoAccount.ts +++ b/ts/client/src/accounts/mangoAccount.ts @@ -336,7 +336,16 @@ export class MangoAccount { public getHealthContributionPerAssetUi( group: Group, healthType: HealthType, - ): { asset: string; contribution: number }[] { + ): { + asset: string; + contribution: number; + contributionDetails: + | { + spotUi: number; + perpMarketContributions: { market: string; contributionUi: number }[]; + } + | undefined; + }[] { const hc = HealthCache.fromMangoAccount(group, this); return hc.healthContributionPerAssetUi(group, healthType); } From eea5e4c03e2b187eaf1f195b295a29a75ce31c8e Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 14 Jul 2023 14:02:37 +0200 Subject: [PATCH 38/90] v0.17.24 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2a0acace6..470f600c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.17.23", + "version": "0.17.24", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From d243035da0c60acf8b1dcd57a3fbb710bdda02db Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Fri, 14 Jul 2023 16:27:52 +0200 Subject: [PATCH 39/90] Compile and test fix (#645) (cherry picked from commit 1ba6513b5ea2b0e557808e712fcf0a811968b45b) --- programs/mango-v4/src/instructions/perp_create_market.rs | 3 ++- programs/mango-v4/src/instructions/token_register.rs | 4 +++- .../mango-v4/src/instructions/token_register_trustless.rs | 2 +- programs/mango-v4/tests/cases/test_ix_gate_set.rs | 4 +++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/programs/mango-v4/src/instructions/perp_create_market.rs b/programs/mango-v4/src/instructions/perp_create_market.rs index 67650325b..cf1133b01 100644 --- a/programs/mango-v4/src/instructions/perp_create_market.rs +++ b/programs/mango-v4/src/instructions/perp_create_market.rs @@ -91,7 +91,8 @@ pub fn perp_create_market( maint_overall_asset_weight: I80F48::from_num(maint_overall_asset_weight), init_overall_asset_weight: I80F48::from_num(init_overall_asset_weight), positive_pnl_liquidation_fee: I80F48::from_num(positive_pnl_liquidation_fee), - reserved: [0; 1888], + fees_withdrawn: 0, + reserved: [0; 1880], }; if let Ok(oracle_price) = diff --git a/programs/mango-v4/src/instructions/token_register.rs b/programs/mango-v4/src/instructions/token_register.rs index ad25f199b..4709ec34f 100644 --- a/programs/mango-v4/src/instructions/token_register.rs +++ b/programs/mango-v4/src/instructions/token_register.rs @@ -89,7 +89,9 @@ pub fn token_register( deposit_weight_scale_start_quote: f64::MAX, reduce_only: 0, force_close: 0, - reserved: [0; 2118], + padding: Default::default(), + fees_withdrawn: 0, + reserved: [0; 2104], }; require_gt!(bank.max_rate, MINIMUM_MAX_RATE); diff --git a/programs/mango-v4/src/instructions/token_register_trustless.rs b/programs/mango-v4/src/instructions/token_register_trustless.rs index b25b6a85f..824850840 100644 --- a/programs/mango-v4/src/instructions/token_register_trustless.rs +++ b/programs/mango-v4/src/instructions/token_register_trustless.rs @@ -75,7 +75,7 @@ pub fn token_register_trustless( deposit_weight_scale_start_quote: 5_000_000_000.0, // $5k reduce_only: 2, // deposit-only force_close: 0, - padding: [0; 6], + padding: Default::default(), fees_withdrawn: 0, reserved: [0; 2104], }; diff --git a/programs/mango-v4/tests/cases/test_ix_gate_set.rs b/programs/mango-v4/tests/cases/test_ix_gate_set.rs index 837ebb5de..6727b85f7 100644 --- a/programs/mango-v4/tests/cases/test_ix_gate_set.rs +++ b/programs/mango-v4/tests/cases/test_ix_gate_set.rs @@ -2,7 +2,9 @@ use super::*; #[tokio::test] async fn test_ix_gate_set() -> Result<(), TransportError> { - let context = TestContext::new().await; + let mut test_builder = TestContextBuilder::new(); + test_builder.test().set_compute_max_units(200_000); // lots of logging + let context = test_builder.start_default().await; let solana = &context.solana.clone(); let admin = TestKeypair::new(); From 81525ed1394f388849541ae27d8b626f51848d08 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Wed, 12 Jul 2023 08:38:38 +0200 Subject: [PATCH 40/90] logging improvements (#641) - switch logging to tracing crate - update liquidator logging to use tracing features (cherry picked from commit b8376b4b892bcd41bc3bcd4ae74f83a417da0d97) --- Cargo.lock | 83 ++++-- bin/cli/Cargo.toml | 3 +- bin/cli/src/main.rs | 4 +- bin/cli/src/test_oracles.rs | 11 +- bin/keeper/Cargo.toml | 3 +- bin/keeper/src/crank.rs | 34 ++- bin/keeper/src/main.rs | 7 +- bin/keeper/src/taker.rs | 19 +- bin/liquidator/Cargo.toml | 2 +- bin/liquidator/src/liquidate.rs | 102 ++++--- bin/liquidator/src/main.rs | 82 +++--- bin/liquidator/src/metrics.rs | 13 +- bin/liquidator/src/rebalance.rs | 352 ++++++++++++------------ bin/liquidator/src/token_swap_info.rs | 4 +- bin/liquidator/src/trigger_tcs.rs | 19 +- bin/settler/Cargo.toml | 2 +- bin/settler/src/main.rs | 14 +- bin/settler/src/metrics.rs | 13 +- bin/settler/src/settle.rs | 7 +- lib/client/Cargo.toml | 4 +- lib/client/src/account_update_stream.rs | 2 +- lib/client/src/snapshot_source.rs | 6 +- lib/client/src/util.rs | 26 +- lib/client/src/websocket_source.rs | 2 +- 24 files changed, 426 insertions(+), 388 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e70e9a98c..d237970fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1698,19 +1698,6 @@ dependencies = [ "syn 1.0.105", ] -[[package]] -name = "env_logger" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" -dependencies = [ - "atty", - "humantime", - "log 0.4.17", - "regex", - "termcolor", -] - [[package]] name = "env_logger" version = "0.9.3" @@ -2949,7 +2936,7 @@ dependencies = [ "bytemuck", "default-env", "derivative", - "env_logger 0.9.3", + "env_logger", "fixed", "itertools", "lazy_static", @@ -2983,11 +2970,9 @@ dependencies = [ "anyhow", "clap 3.2.23", "dotenv", - "env_logger 0.8.4", "fixed", "futures 0.3.25", "itertools", - "log 0.4.17", "mango-v4", "mango-v4-client", "pyth-sdk-solana", @@ -2995,6 +2980,7 @@ dependencies = [ "solana-client", "solana-sdk", "tokio", + "tracing", ] [[package]] @@ -3008,6 +2994,7 @@ dependencies = [ "async-channel", "async-once-cell", "async-trait", + "atty", "base64 0.13.1", "bincode", "fixed", @@ -3015,7 +3002,6 @@ dependencies = [ "itertools", "jsonrpc-core 18.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core-client", - "log 0.4.17", "mango-feeds-connector", "mango-v4", "pyth-sdk-solana", @@ -3033,6 +3019,8 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", + "tracing", + "tracing-subscriber", ] [[package]] @@ -3045,12 +3033,10 @@ dependencies = [ "anyhow", "clap 3.2.23", "dotenv", - "env_logger 0.8.4", "fixed", "futures 0.3.25", "itertools", "lazy_static", - "log 0.4.17", "mango-v4", "mango-v4-client", "prometheus", @@ -3059,6 +3045,7 @@ dependencies = [ "solana-client", "solana-sdk", "tokio", + "tracing", "warp", ] @@ -3086,7 +3073,6 @@ dependencies = [ "jemallocator", "jsonrpc-core 18.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core-client", - "log 0.4.17", "mango-v4", "mango-v4-client", "once_cell", @@ -3105,6 +3091,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-tungstenite 0.16.1", + "tracing", ] [[package]] @@ -3132,7 +3119,6 @@ dependencies = [ "jemallocator", "jsonrpc-core 18.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core-client", - "log 0.4.17", "mango-v4", "mango-v4-client", "once_cell", @@ -3152,6 +3138,16 @@ dependencies = [ "tokio", "tokio-stream", "tokio-tungstenite 0.16.1", + "tracing", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", ] [[package]] @@ -3425,6 +3421,16 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi 0.3.9", +] + [[package]] name = "num" version = "0.2.1" @@ -3726,6 +3732,12 @@ dependencies = [ "syn 1.0.105", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking_lot" version = "0.9.0" @@ -4520,6 +4532,15 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + [[package]] name = "regex-syntax" version = "0.6.28" @@ -5681,7 +5702,7 @@ version = "1.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b502866be84a799633c0744e1d72b819a256337149e9fb6c7eee4db84ec63f5" dependencies = [ - "env_logger 0.9.3", + "env_logger", "lazy_static", "log 0.4.17", ] @@ -7238,6 +7259,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log 0.4.17", + "tracing-core", +] + [[package]] name = "tracing-opentelemetry" version = "0.17.4" @@ -7257,9 +7289,16 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", "sharded-slab", + "smallvec 1.10.0", "thread_local", + "tracing", "tracing-core", + "tracing-log", ] [[package]] diff --git a/bin/cli/Cargo.toml b/bin/cli/Cargo.toml index 8842f19a0..4e5153c7a 100644 --- a/bin/cli/Cargo.toml +++ b/bin/cli/Cargo.toml @@ -16,10 +16,8 @@ anchor-spl = { workspace = true } anyhow = "1.0" clap = { version = "3.1.8", features = ["derive", "env"] } dotenv = "0.15.0" -env_logger = "0.8.4" fixed = { workspace = true, features = ["serde", "borsh"] } futures = "0.3.21" -log = "0.4.0" mango-v4 = { path = "../../programs/mango-v4", features = ["client"] } mango-v4-client = { path = "../../lib/client" } pyth-sdk-solana = { workspace = true } @@ -28,3 +26,4 @@ solana-client = { workspace = true } solana-sdk = { workspace = true } tokio = { version = "1.14.1", features = ["rt-multi-thread", "time", "macros", "sync"] } itertools = "0.10.3" +tracing = "0.1" diff --git a/bin/cli/src/main.rs b/bin/cli/src/main.rs index 9921c147e..819ec4a2f 100644 --- a/bin/cli/src/main.rs +++ b/bin/cli/src/main.rs @@ -137,9 +137,7 @@ impl Rpc { #[tokio::main] async fn main() -> Result<(), anyhow::Error> { - env_logger::init_from_env( - env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), - ); + mango_v4_client::tracing_subscriber_init(); dotenv::dotenv().ok(); let cli = Cli::parse(); diff --git a/bin/cli/src/test_oracles.rs b/bin/cli/src/test_oracles.rs index 2d85a14af..7f8be54c0 100644 --- a/bin/cli/src/test_oracles.rs +++ b/bin/cli/src/test_oracles.rs @@ -3,6 +3,7 @@ use mango_v4::accounts_zerocopy::KeyedAccount; use mango_v4_client::{Client, MangoGroupContext}; use solana_sdk::commitment_config::CommitmentConfig; use solana_sdk::pubkey::Pubkey; +use tracing::*; pub async fn run(client: &Client, group: Pubkey) -> anyhow::Result<()> { let rpc_async = client.rpc_async(); @@ -23,7 +24,7 @@ pub async fn run(client: &Client, group: Pubkey) -> anyhow::Result<()> { .get_multiple_accounts_with_commitment(&oracles, CommitmentConfig::processed()) .await; if response.is_err() { - log::warn!("could not fetch oracles"); + warn!("could not fetch oracles"); continue; } let response = response.unwrap(); @@ -32,7 +33,7 @@ pub async fn run(client: &Client, group: Pubkey) -> anyhow::Result<()> { for (pubkey, account_opt) in oracles.iter().zip(accounts.into_iter()) { if account_opt.is_none() { - log::warn!("no oracle data for {pubkey}"); + warn!("no oracle data for {pubkey}"); continue; } let keyed_account = KeyedAccount { @@ -53,7 +54,7 @@ pub async fn run(client: &Client, group: Pubkey) -> anyhow::Result<()> { match tc.bank.oracle_price(&keyed_account, Some(slot)) { Ok(p) => price = Some(p), Err(e) => { - log::error!("could not read bank oracle {}: {e:?}", keyed_account.key); + error!("could not read bank oracle {}: {e:?}", keyed_account.key); } } } @@ -61,12 +62,12 @@ pub async fn run(client: &Client, group: Pubkey) -> anyhow::Result<()> { match pc.market.oracle_price(&keyed_account, Some(slot)) { Ok(p) => price = Some(p), Err(e) => { - log::error!("could not read perp oracle {}: {e:?}", keyed_account.key); + error!("could not read perp oracle {}: {e:?}", keyed_account.key); } } } if let Some(p) = price { - log::info!("{pubkey},{p}"); + info!("{pubkey},{p}"); } } } diff --git a/bin/keeper/Cargo.toml b/bin/keeper/Cargo.toml index f7ebae745..0ce59102b 100644 --- a/bin/keeper/Cargo.toml +++ b/bin/keeper/Cargo.toml @@ -16,11 +16,9 @@ anchor-spl = { workspace = true } anyhow = "1.0" clap = { version = "3.1.8", features = ["derive", "env"] } dotenv = "0.15.0" -env_logger = "0.8.4" fixed = { workspace = true, features = ["serde", "borsh"] } futures = "0.3.21" itertools = "0.10.3" -log = "0.4.0" mango-v4 = { path = "../../programs/mango-v4", features = ["client"] } mango-v4-client = { path = "../../lib/client" } pyth-sdk-solana = { workspace = true } @@ -31,3 +29,4 @@ tokio = { version = "1.14.1", features = ["rt-multi-thread", "time", "macros", " prometheus = "0.13.3" warp = "0.3.3" lazy_static = "1.4.0" +tracing = "0.1" diff --git a/bin/keeper/src/crank.rs b/bin/keeper/src/crank.rs index 572a03721..1e82eba4f 100644 --- a/bin/keeper/src/crank.rs +++ b/bin/keeper/src/crank.rs @@ -12,6 +12,7 @@ use solana_sdk::{ pubkey::Pubkey, }; use tokio::time; +use tracing::*; use warp::Filter; lazy_static::lazy_static! { @@ -229,21 +230,18 @@ pub async fn loop_update_index_and_rate( if let Err(e) = sig_result { METRIC_UPDATE_TOKENS_FAILURE.inc(); - log::info!( + info!( "metricName=UpdateTokensV4Failure tokens={} durationMs={} error={}", - token_names, - confirmation_time, - e + token_names, confirmation_time, e ); - log::error!("{:?}", e) + error!("{:?}", e) } else { METRIC_UPDATE_TOKENS_SUCCESS.inc(); - log::info!( + info!( "metricName=UpdateTokensV4Success tokens={} durationMs={}", - token_names, - confirmation_time, + token_names, confirmation_time, ); - log::info!("{:?}", sig_result); + info!("{:?}", sig_result); } } } @@ -304,7 +302,7 @@ pub async fn loop_consume_events( Ok(Some(x)) => x, Ok(None) => continue, Err(err) => { - log::error!("preparing consume_events ams: {err:?}"); + error!("preparing consume_events ams: {err:?}"); continue; } }; @@ -347,23 +345,23 @@ pub async fn loop_consume_events( if let Err(e) = sig_result { METRIC_CONSUME_EVENTS_FAILURE.inc(); - log::info!( + info!( "metricName=ConsumeEventsV4Failure market={} durationMs={} consumed={} error={}", perp_market.name(), confirmation_time, num_of_events, e.to_string() ); - log::error!("{:?}", e) + error!("{:?}", e) } else { METRIC_CONSUME_EVENTS_SUCCESS.inc(); - log::info!( + info!( "metricName=ConsumeEventsV4Success market={} durationMs={} consumed={}", perp_market.name(), confirmation_time, num_of_events, ); - log::info!("{:?}", sig_result); + info!("{:?}", sig_result); } } } @@ -402,21 +400,21 @@ pub async fn loop_update_funding( if let Err(e) = sig_result { METRIC_UPDATE_FUNDING_FAILURE.inc(); - log::error!( + error!( "metricName=UpdateFundingV4Error market={} durationMs={} error={}", perp_market.name(), confirmation_time, e.to_string() ); - log::error!("{:?}", e) + error!("{:?}", e) } else { METRIC_UPDATE_FUNDING_SUCCESS.inc(); - log::info!( + info!( "metricName=UpdateFundingV4Success market={} durationMs={}", perp_market.name(), confirmation_time, ); - log::info!("{:?}", sig_result); + info!("{:?}", sig_result); } } } diff --git a/bin/keeper/src/main.rs b/bin/keeper/src/main.rs index 47ce8f294..f5b88a730 100644 --- a/bin/keeper/src/main.rs +++ b/bin/keeper/src/main.rs @@ -11,6 +11,7 @@ use mango_v4_client::{keypair_from_cli, Client, MangoClient, TransactionBuilderC use solana_sdk::commitment_config::CommitmentConfig; use solana_sdk::pubkey::Pubkey; use tokio::time; +use tracing::*; // TODO // - may be nice to have one-shot cranking as well as the interval cranking @@ -73,9 +74,7 @@ enum Command { #[tokio::main] async fn main() -> Result<(), anyhow::Error> { - env_logger::init_from_env( - env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), - ); + mango_v4_client::tracing_subscriber_init(); let args = if let Ok(cli_dotenv) = CliDotenv::try_parse() { dotenv::from_path(cli_dotenv.dotenv)?; @@ -121,7 +120,7 @@ async fn main() -> Result<(), anyhow::Error> { interval.tick().await; let client = mango_client.clone(); tokio::task::spawn_blocking(move || { - log::info!( + info!( "Arc::strong_count() {}", Arc::::strong_count(&client) ) diff --git a/bin/keeper/src/taker.rs b/bin/keeper/src/taker.rs index d24f0a965..17cd9ebc2 100644 --- a/bin/keeper/src/taker.rs +++ b/bin/keeper/src/taker.rs @@ -10,6 +10,7 @@ use mango_v4::{ accounts_ix::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side}, state::TokenIndex, }; +use tracing::*; use tokio::time; @@ -90,7 +91,7 @@ async fn ensure_deposit(mango_client: &Arc) -> Result<(), anyhow::E Some(token_account) => { let native = token_account.native(&bank); let ui = token_account.ui(&bank); - log::info!("Current balance {} {}", ui, bank.name()); + info!("Current balance {} {}", ui, bank.name()); if native < I80F48::ZERO { desired_balance - native @@ -105,7 +106,7 @@ async fn ensure_deposit(mango_client: &Arc) -> Result<(), anyhow::E continue; } - log::info!("Depositing {} {}", deposit_native, bank.name()); + info!("Depositing {} {}", deposit_native, bank.name()); mango_client .token_deposit(bank.mint, desired_balance.to_num(), false) .await?; @@ -125,7 +126,7 @@ pub async fn loop_blocking_price_update( interval.tick().await; let fresh_price = mango_client.bank_oracle_price(token_index).await.unwrap(); - log::info!("{} Updated price is {:?}", token_name, fresh_price); + info!("{} Updated price is {:?}", token_name, fresh_price); if let Ok(mut price) = price.write() { *price = fresh_price; } @@ -144,7 +145,7 @@ pub async fn loop_blocking_orders( .serum3_cancel_all_orders(&market_name) .await .unwrap(); - log::info!("Cancelled orders - {:?} for {}", orders, market_name); + info!("Cancelled orders - {:?} for {}", orders, market_name); let market_index = mango_client.context.serum3_market_index(&market_name); let s3 = mango_client.context.serum3(market_index); @@ -178,9 +179,9 @@ pub async fn loop_blocking_orders( ) .await; if let Err(e) = res { - log::error!("Error while placing taker bid {:#?}", e) + error!("Error while placing taker bid {:#?}", e) } else { - log::info!("Placed bid at {} for {}", bid_price, market_name) + info!("Placed bid at {} for {}", bid_price, market_name) } let ask_price = fresh_price * 0.9; @@ -200,9 +201,9 @@ pub async fn loop_blocking_orders( ) .await; if let Err(e) = res { - log::error!("Error while placing taker ask {:#?}", e) + error!("Error while placing taker ask {:#?}", e) } else { - log::info!("Placed ask at {} for {}", ask_price, market_name) + info!("Placed ask at {} for {}", ask_price, market_name) } Ok(()) @@ -210,7 +211,7 @@ pub async fn loop_blocking_orders( .await; if let Err(err) = res { - log::error!("{:?}", err); + error!("{:?}", err); } } } diff --git a/bin/liquidator/Cargo.toml b/bin/liquidator/Cargo.toml index 99737f13e..a099d61a2 100644 --- a/bin/liquidator/Cargo.toml +++ b/bin/liquidator/Cargo.toml @@ -30,7 +30,6 @@ itertools = "0.10.3" jemallocator = "0.3.2" jsonrpc-core = "18.0.0" jsonrpc-core-client = { version = "18.0.0", features = ["ws", "http", "tls"] } -log = "0.4" mango-v4 = { path = "../../programs/mango-v4", features = ["client"] } mango-v4-client = { path = "../../lib/client" } once_cell = "1.12.0" @@ -49,3 +48,4 @@ solana-sdk = { workspace = true } tokio = { version = "1", features = ["full"] } tokio-stream = { version = "0.1.9"} tokio-tungstenite = "0.16.1" +tracing = "0.1" diff --git a/bin/liquidator/src/liquidate.rs b/bin/liquidator/src/liquidate.rs index 9a0b09f80..8387789e4 100644 --- a/bin/liquidator/src/liquidate.rs +++ b/bin/liquidator/src/liquidate.rs @@ -8,6 +8,7 @@ use solana_sdk::signature::Signature; use futures::{stream, StreamExt, TryStreamExt}; use rand::seq::SliceRandom; +use tracing::*; use {anyhow::Context, fixed::types::I80F48, solana_sdk::pubkey::Pubkey}; use crate::util; @@ -118,7 +119,7 @@ impl<'a> LiquidateHelper<'a> { } // Cancel all orders on a random serum market let serum_orders = serum_force_cancels.choose(&mut rand::thread_rng()).unwrap(); - let sig = self + let txsig = self .client .serum3_liq_force_cancel_orders( (self.pubkey, &self.liqee), @@ -126,14 +127,12 @@ impl<'a> LiquidateHelper<'a> { &serum_orders.open_orders, ) .await?; - log::info!( - "Force cancelled serum orders on account {}, market index {}, maint_health was {}, tx sig {:?}", - self.pubkey, - serum_orders.market_index, - self.maint_health, - sig + info!( + market_index = serum_orders.market_index, + %txsig, + "Force cancelled serum orders", ); - Ok(Some(sig)) + Ok(Some(txsig)) } async fn perp_close_orders(&self) -> anyhow::Result> { @@ -148,18 +147,16 @@ impl<'a> LiquidateHelper<'a> { // Cancel all orders on a random perp market let perp_market_index = *perp_force_cancels.choose(&mut rand::thread_rng()).unwrap(); - let sig = self + let txsig = self .client .perp_liq_force_cancel_orders((self.pubkey, &self.liqee), perp_market_index) .await?; - log::info!( - "Force cancelled perp orders on account {}, market index {}, maint_health was {}, tx sig {:?}", - self.pubkey, + info!( perp_market_index, - self.maint_health, - sig + %txsig, + "Force cancelled perp orders", ); - Ok(Some(sig)) + Ok(Some(txsig)) } async fn perp_liq_base_or_positive_pnl(&self) -> anyhow::Result> { @@ -254,9 +251,13 @@ impl<'a> LiquidateHelper<'a> { (max_base_transfer, max_pnl_transfer.floor().to_num::()) }; - log::info!("computed max_base_transfer: {max_base_transfer_abs}, max_pnl_transfer: {max_pnl_transfer}"); + trace!( + max_base_transfer_abs, + max_pnl_transfer, + "computed transfer maximums" + ); - let sig = self + let txsig = self .client .perp_liq_base_or_positive_pnl( (self.pubkey, &self.liqee), @@ -265,14 +266,12 @@ impl<'a> LiquidateHelper<'a> { max_pnl_transfer, ) .await?; - log::info!( - "Liquidated base position for perp market on account {}, market index {}, maint_health was {}, tx sig {:?}", - self.pubkey, + info!( perp_market_index, - self.maint_health, - sig + %txsig, + "Liquidated base position for perp market", ); - Ok(Some(sig)) + Ok(Some(txsig)) } async fn perp_liq_negative_pnl_or_bankruptcy(&self) -> anyhow::Result> { @@ -297,7 +296,7 @@ impl<'a> LiquidateHelper<'a> { } let (perp_market_index, _) = perp_negative_pnl.first().unwrap(); - let sig = self + let txsig = self .client .perp_liq_negative_pnl_or_bankruptcy( (self.pubkey, &self.liqee), @@ -306,14 +305,12 @@ impl<'a> LiquidateHelper<'a> { u64::MAX, ) .await?; - log::info!( - "Liquidated negative perp pnl on account {}, market index {}, maint_health was {}, tx sig {:?}", - self.pubkey, + info!( perp_market_index, - self.maint_health, - sig + %txsig, + "Liquidated negative perp pnl", ); - Ok(Some(sig)) + Ok(Some(txsig)) } async fn tokens(&self) -> anyhow::Result> { @@ -415,7 +412,7 @@ impl<'a> LiquidateHelper<'a> { // TODO: log liqor's assets in UI form // TODO: log liquee's liab_needed, need to refactor program code to be able to be accessed from client side // - let sig = self + let txsig = self .client .token_liq_with_token( (self.pubkey, &self.liqee), @@ -425,13 +422,13 @@ impl<'a> LiquidateHelper<'a> { ) .await .context("sending liq_token_with_token")?; - log::info!( - "Liquidated token with token for {}, maint_health was {}, tx sig {:?}", - self.pubkey, - self.maint_health, - sig + info!( + asset_token_index, + liab_token_index, + %txsig, + "Liquidated token with token", ); - Ok(Some(sig)) + Ok(Some(txsig)) } async fn token_liq_bankruptcy(&self) -> anyhow::Result> { @@ -469,7 +466,7 @@ impl<'a> LiquidateHelper<'a> { .max_token_liab_transfer(liab_token_index, quote_token_index) .await?; - let sig = self + let txsig = self .client .token_liq_bankruptcy( (self.pubkey, &self.liqee), @@ -478,15 +475,15 @@ impl<'a> LiquidateHelper<'a> { ) .await .context("sending liq_token_bankruptcy")?; - log::info!( - "Liquidated bankruptcy for {}, maint_health was {}, tx sig {:?}", - self.pubkey, - self.maint_health, - sig + info!( + liab_token_index, + %txsig, + "Liquidated token bankruptcy", ); - Ok(Some(sig)) + Ok(Some(txsig)) } + #[instrument(skip(self), fields(pubkey = %*self.pubkey, maint = %self.maint_health))] async fn send_liq_tx(&self) -> anyhow::Result> { // TODO: Should we make an attempt to settle positive PNL first? // The problem with it is that small market movements can continuously create @@ -529,11 +526,7 @@ impl<'a> LiquidateHelper<'a> { } if self.health_cache.has_perp_open_fills() { - log::info!( - "Account {} has open perp fills, maint_health {}, waiting...", - self.pubkey, - self.maint_health - ); + info!("there are open perp fills, waiting...",); return Ok(None); } @@ -587,11 +580,10 @@ pub async fn maybe_liquidate_account( return Ok(false); } - log::trace!( - "possible candidate: {}, with owner: {}, maint health: {}", - pubkey, - account.fixed.owner, - maint_health, + trace!( + %pubkey, + %maint_health, + "possible candidate", ); // Fetch a fresh account and re-compute @@ -640,7 +632,7 @@ pub async fn maybe_liquidate_account( ) .await { - log::info!("could not refresh after liquidation: {}", e); + info!("could not refresh after liquidation: {}", e); } } diff --git a/bin/liquidator/src/main.rs b/bin/liquidator/src/main.rs index 0463ff9ec..064e49674 100644 --- a/bin/liquidator/src/main.rs +++ b/bin/liquidator/src/main.rs @@ -1,10 +1,10 @@ use std::collections::HashMap; +use std::collections::HashSet; use std::sync::{Arc, RwLock}; use std::time::{Duration, Instant}; use anchor_client::Cluster; use clap::Parser; -use log::*; use mango_v4::state::{PerpMarketIndex, TokenIndex}; use mango_v4_client::{ account_update_stream, chain_data, keypair_from_cli, snapshot_source, websocket_source, @@ -15,7 +15,7 @@ use mango_v4_client::{ use itertools::Itertools; use solana_sdk::commitment_config::CommitmentConfig; use solana_sdk::pubkey::Pubkey; -use std::collections::HashSet; +use tracing::*; pub mod liquidate; pub mod metrics; @@ -41,6 +41,14 @@ struct CliDotenv { remaining_args: Vec, } +// Prefer "--rebalance false" over "--no-rebalance" because it works +// better with REBALANCE=false env values. +#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq)] +enum BoolArg { + True, + False, +} + #[derive(Parser)] #[clap()] struct Cli { @@ -71,8 +79,8 @@ struct Cli { /// if rebalancing is enabled /// /// typically only disabled for tests where swaps are unavailable - #[clap(long, env, default_value = "true")] - rebalance: bool, + #[clap(long, env, value_enum, default_value = "true")] + rebalance: BoolArg, /// max slippage to request on swaps to rebalance spot tokens #[clap(long, env, default_value = "100")] @@ -85,8 +93,8 @@ struct Cli { /// use a jupiter mock instead of actual queries /// /// This is required for devnet testing. - #[clap(long, env, default_value = "false")] - mock_jupiter: bool, + #[clap(long, env, value_enum, default_value = "false")] + mock_jupiter: BoolArg, } pub fn encode_address(addr: &Pubkey) -> String { @@ -95,6 +103,8 @@ pub fn encode_address(addr: &Pubkey) -> String { #[tokio::main] async fn main() -> anyhow::Result<()> { + mango_v4_client::tracing_subscriber_init(); + let args = if let Ok(cli_dotenv) = CliDotenv::try_parse() { dotenv::from_path(cli_dotenv.dotenv)?; cli_dotenv.remaining_args @@ -161,7 +171,6 @@ async fn main() -> anyhow::Result<()> { // FUTURE: decouple feed setup and liquidator business logic // feed should send updates to a channel which liquidator can consume - solana_logger::setup_with_default("info"); info!("startup"); let metrics = metrics::start(); @@ -222,7 +231,7 @@ async fn main() -> anyhow::Result<()> { let token_swap_info_config = token_swap_info::Config { quote_index: 0, // USDC quote_amount: 1_000_000_000, // TODO: config, $1000, should be >= tcs_config.max_trigger_quote_amount - mock_jupiter: cli.mock_jupiter, + mock_jupiter: cli.mock_jupiter == BoolArg::True, }; let token_swap_info_updater = Arc::new(token_swap_info::TokenSwapInfoUpdater::new( @@ -232,7 +241,7 @@ async fn main() -> anyhow::Result<()> { let liq_config = liquidate::Config { min_health_ratio: cli.min_health_ratio, - mock_jupiter: cli.mock_jupiter, + mock_jupiter: cli.mock_jupiter == BoolArg::True, // TODO: config refresh_timeout: Duration::from_secs(30), }; @@ -240,14 +249,14 @@ async fn main() -> anyhow::Result<()> { let tcs_config = trigger_tcs::Config { min_health_ratio: cli.min_health_ratio, max_trigger_quote_amount: 1_000_000_000, // TODO: config, $1000 - mock_jupiter: cli.mock_jupiter, + mock_jupiter: cli.mock_jupiter == BoolArg::True, // TODO: config refresh_timeout: Duration::from_secs(30), }; let mut rebalance_interval = tokio::time::interval(Duration::from_secs(5)); let rebalance_config = rebalance::Config { - enabled: cli.rebalance, + enabled: cli.rebalance == BoolArg::True, slippage_bps: cli.rebalance_slippage_bps, // TODO: config borrow_settle_excess: 1.05, @@ -261,7 +270,7 @@ async fn main() -> anyhow::Result<()> { config: rebalance_config, }); - let mut liquidation = LiquidationState { + let mut liquidation = Box::new(LiquidationState { mango_client, account_fetcher, liquidation_config: liq_config, @@ -280,7 +289,7 @@ async fn main() -> anyhow::Result<()> { reset_duration: std::time::Duration::from_secs(360), ..ErrorTracking::default() }, - }; + }); let (liquidation_trigger_sender, liquidation_trigger_receiver) = async_channel::bounded::<()>(1); @@ -316,7 +325,7 @@ async fn main() -> anyhow::Result<()> { let mut state = shared_state.write().unwrap(); if is_mango_account(&account_write.account, &mango_group).is_some() { // e.g. to render debug logs RUST_LOG="liquidator=debug" - log::debug!( + debug!( "change to mango account {}...", &account_write.pubkey.to_string()[0..3] ); @@ -332,15 +341,15 @@ async fn main() -> anyhow::Result<()> { } else { let mut must_check_all = false; if is_mango_bank(&account_write.account, &mango_group).is_some() { - log::debug!("change to bank {}", &account_write.pubkey); + debug!("change to bank {}", &account_write.pubkey); must_check_all = true; } if is_perp_market(&account_write.account, &mango_group).is_some() { - log::debug!("change to perp market {}", &account_write.pubkey); + debug!("change to perp market {}", &account_write.pubkey); must_check_all = true; } if oracles.contains(&account_write.pubkey) { - log::debug!("change to oracle {}", &account_write.pubkey); + debug!("change to oracle {}", &account_write.pubkey); must_check_all = true; } if must_check_all { @@ -391,7 +400,7 @@ async fn main() -> anyhow::Result<()> { continue; } if let Err(err) = rebalancer.zero_all_non_quote().await { - log::error!("failed to rebalance liqor: {:?}", err); + error!("failed to rebalance liqor: {:?}", err); // Workaround: We really need a sequence enforcer in the liquidator since we don't want to // accidentally send a similar tx again when we incorrectly believe an earlier one got forked @@ -459,7 +468,7 @@ async fn main() -> anyhow::Result<()> { match token_swap_info_updater.update_one(token_index).await { Ok(()) => {} Err(err) => { - log::warn!( + warn!( "failed to update token swap info for token {token_index}: {:?}", err ); @@ -485,7 +494,7 @@ async fn main() -> anyhow::Result<()> { .collect(); jobs.next().await; - log::error!("a critical job aborted, exiting"); + error!("a critical job aborted, exiting"); Ok(()) } @@ -591,7 +600,7 @@ impl LiquidationState { } if let Err(err) = self.rebalancer.zero_all_non_quote().await { - log::error!("failed to rebalance liqor: {:?}", err); + error!("failed to rebalance liqor: {:?}", err); } Ok(true) } @@ -602,7 +611,7 @@ impl LiquidationState { // Skip a pubkey if there've been too many errors recently if let Some(error_entry) = error_tracking.had_too_many_errors(pubkey, now) { - log::trace!( + trace!( "skip checking account {pubkey}, had {} errors recently", error_entry.count ); @@ -622,7 +631,7 @@ impl LiquidationState { error_tracking.record_error(pubkey, now); // Not all errors need to be raised to the user's attention. - let mut log_level = log::Level::Error; + let mut is_error = true; // Simulation errors due to liqee precondition failures on the liquidation instructions // will commonly happen if our liquidator is late or if there are chain forks. @@ -631,12 +640,16 @@ impl LiquidationState { if logs.iter().any(|line| { line.contains("HealthMustBeNegative") || line.contains("IsNotBankrupt") }) { - log_level = log::Level::Trace; + is_error = false; } } _ => {} }; - log::log!(log_level, "liquidating account {}: {:?}", pubkey, err); + if is_error { + error!("liquidating account {}: {:?}", pubkey, err); + } else { + trace!("liquidating account {}: {:?}", pubkey, err); + } } else { error_tracking.clear_errors(pubkey); } @@ -672,7 +685,7 @@ impl LiquidationState { } if let Err(err) = self.rebalancer.zero_all_non_quote().await { - log::error!("failed to rebalance liqor: {:?}", err); + error!("failed to rebalance liqor: {:?}", err); } Ok(()) } @@ -686,7 +699,7 @@ impl LiquidationState { // Skip a pubkey if there've been too many errors recently if let Some(error_entry) = error_tracking.had_too_many_errors(pubkey, now) { - log::trace!( + trace!( "skip checking for tcs on account {pubkey}, had {} errors recently", error_entry.count ); @@ -707,7 +720,7 @@ impl LiquidationState { error_tracking.record_error(pubkey, now); // Not all errors need to be raised to the user's attention. - let mut log_level = log::Level::Error; + let mut is_error = true; // Simulation errors due to liqee precondition failures // will commonly happen if our liquidator is late or if there are chain forks. @@ -717,17 +730,16 @@ impl LiquidationState { .iter() .any(|line| line.contains("TokenConditionalSwapPriceNotInRange")) { - log_level = log::Level::Trace; + is_error = false; } } _ => {} }; - log::log!( - log_level, - "token conditional swap on account {}: {:?}", - pubkey, - err - ); + if is_error { + error!("token conditional swap on account {}: {:?}", pubkey, err); + } else { + trace!("token conditional swap on account {}: {:?}", pubkey, err); + } } else { error_tracking.clear_errors(pubkey); } diff --git a/bin/liquidator/src/metrics.rs b/bin/liquidator/src/metrics.rs index 434d55d17..5f703a08f 100644 --- a/bin/liquidator/src/metrics.rs +++ b/bin/liquidator/src/metrics.rs @@ -2,6 +2,7 @@ use { std::collections::HashMap, std::sync::{atomic, Arc, Mutex, RwLock}, tokio::time, + tracing::*, }; #[derive(Debug)] @@ -151,7 +152,7 @@ pub fn start() -> Metrics { 0 }; let diff = new_value.wrapping_sub(previous_value) as i64; - log::info!("metric: {}: {} ({:+})", name, new_value, diff); + info!("metric: {}: {} ({:+})", name, new_value, diff); } Value::I64(v) => { let new_value = v.load(atomic::Ordering::Acquire); @@ -164,7 +165,7 @@ pub fn start() -> Metrics { 0 }; let diff = new_value - previous_value; - log::info!("metric: {}: {} ({:+})", name, new_value, diff); + info!("metric: {}: {} ({:+})", name, new_value, diff); } Value::String(v) => { let new_value = v.lock().unwrap(); @@ -178,13 +179,11 @@ pub fn start() -> Metrics { "".into() }; if *new_value == previous_value { - log::info!("metric: {}: {} (unchanged)", name, &*new_value); + info!("metric: {}: {} (unchanged)", name, &*new_value); } else { - log::info!( + info!( "metric: {}: {} (before: {})", - name, - &*new_value, - previous_value + name, &*new_value, previous_value ); } } diff --git a/bin/liquidator/src/rebalance.rs b/bin/liquidator/src/rebalance.rs index 11079c2bc..6bd25c1f4 100644 --- a/bin/liquidator/src/rebalance.rs +++ b/bin/liquidator/src/rebalance.rs @@ -1,9 +1,11 @@ use mango_v4::accounts_zerocopy::KeyedAccountSharedData; use mango_v4::state::{ - Bank, BookSide, PlaceOrderType, Side, TokenIndex, TokenPosition, QUOTE_TOKEN_INDEX, + Bank, BookSide, MangoAccountValue, PerpPosition, PlaceOrderType, Side, TokenIndex, + TokenPosition, QUOTE_TOKEN_INDEX, }; use mango_v4_client::{ - chain_data, perp_pnl, AccountFetcher, AnyhowWrap, JupiterSwapMode, MangoClient, TokenContext, + chain_data, perp_pnl, AccountFetcher, AnyhowWrap, JupiterSwapMode, MangoClient, + PerpMarketContext, TokenContext, }; use {fixed::types::I80F48, solana_sdk::pubkey::Pubkey}; @@ -12,6 +14,7 @@ use futures::{stream, StreamExt, TryStreamExt}; use solana_sdk::signature::Signature; use std::sync::Arc; use std::{collections::HashMap, time::Duration}; +use tracing::*; #[derive(Clone)] pub struct Config { @@ -82,7 +85,10 @@ impl Rebalancer { return Ok(()); } - log::trace!("checking for rebalance: {}", self.mango_account_address); + trace!( + pubkey = %self.mango_account_address, + "checking for rebalance" + ); self.rebalance_perps().await?; self.rebalance_tokens().await?; @@ -104,7 +110,7 @@ impl Rebalancer { { // If we don't get fresh data, maybe the tx landed on a fork? // Rebalance is technically still ok. - log::info!("could not refresh account data: {}", e); + info!("could not refresh account data: {}", e); return Ok(false); } Ok(true) @@ -131,7 +137,7 @@ impl Rebalancer { .try_collect() .await; let tokens = tokens?; - log::trace!("account tokens: {:?}", tokens); + trace!(?tokens, "account tokens"); for (token_index, token_state) in tokens { let token = self.mango_client.context.token(token_index); @@ -172,12 +178,12 @@ impl Rebalancer { JupiterSwapMode::ExactIn, ) .await?; - log::info!( - "bought {} {} for {} in tx {}", + info!( + %txsig, + "bought {} {} for {}", token.native_to_ui(buy_amount), token.name, quote_token.name, - txsig ); if !self.refresh_mango_account_after_tx(txsig).await? { return Ok(()); @@ -204,12 +210,12 @@ impl Rebalancer { JupiterSwapMode::ExactIn, ) .await?; - log::info!( - "sold {} {} for {} in tx {}", + info!( + %txsig, + "sold {} {} for {}", token.native_to_ui(amount), token.name, quote_token.name, - txsig ); if !self.refresh_mango_account_after_tx(txsig).await? { return Ok(()); @@ -232,11 +238,11 @@ impl Rebalancer { .mango_client .token_withdraw(token_mint, u64::MAX, allow_borrow) .await?; - log::info!( - "withdrew {} {} to liqor wallet in {}", + info!( + %txsig, + "withdrew {} {} to liqor wallet", token.native_to_ui(amount), token.name, - txsig ); if !self.refresh_mango_account_after_tx(txsig).await? { return Ok(()); @@ -253,174 +259,170 @@ impl Rebalancer { Ok(()) } - async fn rebalance_perps(&self) -> anyhow::Result<()> { + #[instrument( + skip_all, + fields( + perp_market_name = perp.market.name(), + base_lots = perp_position.base_position_lots(), + effective_lots = perp_position.effective_base_position_lots(), + quote_native = %perp_position.quote_position_native() + ) + )] + async fn rebalance_perp( + &self, + account: &MangoAccountValue, + perp: &PerpMarketContext, + perp_position: &PerpPosition, + ) -> anyhow::Result { let now_ts: u64 = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH)? .as_secs() .try_into()?; - let account = self - .account_fetcher - .fetch_mango_account(&self.mango_account_address)?; + + let base_lots = perp_position.base_position_lots(); + let effective_lots = perp_position.effective_base_position_lots(); + let quote_native = perp_position.quote_position_native(); + + if effective_lots != 0 { + // send an ioc order to reduce the base position + let oracle_account_data = self.account_fetcher.fetch_raw(&perp.market.oracle)?; + let oracle_account = + KeyedAccountSharedData::new(perp.market.oracle, oracle_account_data); + let oracle_price = perp.market.oracle_price(&oracle_account, None)?; + let oracle_price_lots = perp.market.native_price_to_lot(oracle_price); + let (side, order_price, oo_lots) = if effective_lots > 0 { + ( + Side::Ask, + oracle_price * (I80F48::ONE - perp.market.base_liquidation_fee), + perp_position.asks_base_lots, + ) + } else { + ( + Side::Bid, + oracle_price * (I80F48::ONE + perp.market.base_liquidation_fee), + perp_position.bids_base_lots, + ) + }; + let price_lots = perp.market.native_price_to_lot(order_price); + let max_base_lots = effective_lots.abs() - oo_lots; + if max_base_lots <= 0 { + warn!(?side, oo_lots, "cannot place reduce-only order",); + return Ok(true); + } + + // Check the orderbook before sending the ioc order to see if we could + // even match anything. That way we don't need to pay the tx fee and + // ioc penalty fee unnecessarily. + let opposite_side_key = match side.invert_side() { + Side::Bid => perp.market.bids, + Side::Ask => perp.market.asks, + }; + let bookside = Box::new(self.account_fetcher.fetch::(&opposite_side_key)?); + if bookside.quantity_at_price(price_lots, now_ts, oracle_price_lots) <= 0 { + warn!( + other_side = ?side.invert_side(), + %order_price, + %oracle_price, + "no liquidity", + ); + return Ok(true); + } + + let txsig = self + .mango_client + .perp_place_order( + perp_position.market_index, + side, + price_lots, + max_base_lots, + i64::MAX, + 0, + PlaceOrderType::ImmediateOrCancel, + true, // reduce only + 0, + 10, + mango_v4::state::SelfTradeBehavior::DecrementTake, + ) + .await?; + info!( + %txsig, + %order_price, + "attempt to ioc reduce perp base position" + ); + if !self.refresh_mango_account_after_tx(txsig).await? { + return Ok(false); + } + } else if base_lots == 0 && quote_native != 0 { + // settle pnl + let direction = if quote_native > 0 { + perp_pnl::Direction::MaxNegative + } else { + perp_pnl::Direction::MaxPositive + }; + let counters = perp_pnl::fetch_top( + &self.mango_client.context, + self.account_fetcher.as_ref(), + perp_position.market_index, + direction, + 2, + ) + .await?; + if counters.is_empty() { + // If we can't settle some positive PNL because we're lacking a suitable counterparty, + // then liquidation should continue, even though this step produced no transaction + info!("could not settle perp pnl on perp market: no counterparty",); + return Ok(true); + } + let (counter_key, counter_acc, _counter_pnl) = counters.first().unwrap(); + + let (account_a, account_b) = if quote_native > 0 { + ( + (&self.mango_account_address, account), + (counter_key, counter_acc), + ) + } else { + ( + (counter_key, counter_acc), + (&self.mango_account_address, account), + ) + }; + let txsig = self + .mango_client + .perp_settle_pnl(perp_position.market_index, account_a, account_b) + .await?; + info!(%txsig, "settled perp pnl"); + if !self.refresh_mango_account_after_tx(txsig).await? { + return Ok(false); + } + } else if base_lots == 0 && quote_native == 0 { + // close perp position + let txsig = self + .mango_client + .perp_deactivate_position(perp_position.market_index) + .await?; + info!( + %txsig, "closed perp position" + ); + if !self.refresh_mango_account_after_tx(txsig).await? { + return Ok(false); + } + } else { + // maybe we're still waiting for consume_events + info!("cannot deactivate perp position, waiting for consume events?"); + } + Ok(true) + } + + async fn rebalance_perps(&self) -> anyhow::Result<()> { + let account = Box::new( + self.account_fetcher + .fetch_mango_account(&self.mango_account_address)?, + ); for perp_position in account.active_perp_positions() { let perp = self.mango_client.context.perp(perp_position.market_index); - let base_lots = perp_position.base_position_lots(); - let effective_lots = perp_position.effective_base_position_lots(); - let quote_native = perp_position.quote_position_native(); - log::info!( - "active perp position on {}, base lots: {}, effective lots: {}, quote native: {}", - perp.market.name(), - base_lots, - effective_lots, - quote_native, - ); - - if effective_lots != 0 { - // send an ioc order to reduce the base position - let oracle_account_data = self.account_fetcher.fetch_raw(&perp.market.oracle)?; - let oracle_account = - KeyedAccountSharedData::new(perp.market.oracle, oracle_account_data); - let oracle_price = perp.market.oracle_price(&oracle_account, None)?; - let oracle_price_lots = perp.market.native_price_to_lot(oracle_price); - let (side, order_price, oo_lots) = if effective_lots > 0 { - ( - Side::Ask, - oracle_price * (I80F48::ONE - perp.market.base_liquidation_fee), - perp_position.asks_base_lots, - ) - } else { - ( - Side::Bid, - oracle_price * (I80F48::ONE + perp.market.base_liquidation_fee), - perp_position.bids_base_lots, - ) - }; - let price_lots = perp.market.native_price_to_lot(order_price); - let max_base_lots = effective_lots.abs() - oo_lots; - if max_base_lots <= 0 { - log::warn!( - "cannot place reduce-only order on {} {:?}, base pos: {}, in open orders: {}", - perp.market.name(), - side, - effective_lots, - oo_lots, - ); - continue; - } - - // Check the orderbook before sending the ioc order to see if we could - // even match anything. That way we don't need to pay the tx fee and - // ioc penalty fee unnecessarily. - let opposite_side_key = match side.invert_side() { - Side::Bid => perp.market.bids, - Side::Ask => perp.market.asks, - }; - let bookside = self.account_fetcher.fetch::(&opposite_side_key)?; - if bookside.quantity_at_price(price_lots, now_ts, oracle_price_lots) <= 0 { - log::warn!( - "no liquidity on {} {:?} at price {}, oracle price {}", - perp.market.name(), - side.invert_side(), - order_price, - oracle_price, - ); - continue; - } - - let txsig = self - .mango_client - .perp_place_order( - perp_position.market_index, - side, - price_lots, - max_base_lots, - i64::MAX, - 0, - PlaceOrderType::ImmediateOrCancel, - true, // reduce only - 0, - 10, - mango_v4::state::SelfTradeBehavior::DecrementTake, - ) - .await?; - log::info!( - "attempt to ioc reduce perp base position of {} {} at price {} in {}", - perp_position.base_position_native(&perp.market), - perp.market.name(), - order_price, - txsig - ); - if !self.refresh_mango_account_after_tx(txsig).await? { - return Ok(()); - } - } else if base_lots == 0 && quote_native != 0 { - // settle pnl - let direction = if quote_native > 0 { - perp_pnl::Direction::MaxNegative - } else { - perp_pnl::Direction::MaxPositive - }; - let counters = perp_pnl::fetch_top( - &self.mango_client.context, - self.account_fetcher.as_ref(), - perp_position.market_index, - direction, - 2, - ) - .await?; - if counters.is_empty() { - // If we can't settle some positive PNL because we're lacking a suitable counterparty, - // then liquidation should continue, even though this step produced no transaction - log::info!( - "could not settle perp pnl on perp market {}: no counterparty", - perp.market.name() - ); - continue; - } - let (counter_key, counter_acc, _counter_pnl) = counters.first().unwrap(); - - let (account_a, account_b) = if quote_native > 0 { - ( - (&self.mango_account_address, &account), - (counter_key, counter_acc), - ) - } else { - ( - (counter_key, counter_acc), - (&self.mango_account_address, &account), - ) - }; - let txsig = self - .mango_client - .perp_settle_pnl(perp_position.market_index, account_a, account_b) - .await?; - log::info!("settled perp {} pnl, tx sig {}", perp.market.name(), txsig); - if !self.refresh_mango_account_after_tx(txsig).await? { - return Ok(()); - } - } else if base_lots == 0 && quote_native == 0 { - // close perp position - let txsig = self - .mango_client - .perp_deactivate_position(perp_position.market_index) - .await?; - log::info!( - "closed perp position on {} in {}", - perp.market.name(), - txsig - ); - if !self.refresh_mango_account_after_tx(txsig).await? { - return Ok(()); - } - } else { - // maybe we're still waiting for consume_events - log::info!( - "cannot deactivate perp {} position, base lots {}, effective lots {}, quote {}", - perp.market.name(), - perp_position.base_position_lots(), - effective_lots, - perp_position.quote_position_native() - ); + if !self.rebalance_perp(&account, perp, perp_position).await? { + return Ok(()); } } diff --git a/bin/liquidator/src/token_swap_info.rs b/bin/liquidator/src/token_swap_info.rs index 413816e8a..5341f4034 100644 --- a/bin/liquidator/src/token_swap_info.rs +++ b/bin/liquidator/src/token_swap_info.rs @@ -2,6 +2,8 @@ use std::collections::HashMap; use std::sync::{Arc, RwLock}; use itertools::Itertools; +use tracing::*; + use mango_v4::state::TokenIndex; use mango_v4_client::jupiter::QueryRoute; use mango_v4_client::{JupiterSwapMode, MangoClient}; @@ -153,6 +155,6 @@ impl TokenSwapInfoUpdater { .unwrap_or_else(|| "no data".into()); msg.push_str(&format!("token {token}, {info}")); } - log::trace!("swap infos:{}", msg); + trace!("swap infos:{}", msg); } } diff --git a/bin/liquidator/src/trigger_tcs.rs b/bin/liquidator/src/trigger_tcs.rs index f805c0d6c..7942e7b9a 100644 --- a/bin/liquidator/src/trigger_tcs.rs +++ b/bin/liquidator/src/trigger_tcs.rs @@ -4,6 +4,7 @@ use mango_v4::state::{MangoAccountValue, TokenConditionalSwap}; use mango_v4_client::{chain_data, health_cache, JupiterSwapMode, MangoClient}; use rand::seq::SliceRandom; +use tracing::*; use {anyhow::Context, fixed::types::I80F48, solana_sdk::pubkey::Pubkey}; use crate::{token_swap_info, util}; @@ -180,7 +181,7 @@ pub async fn maybe_execute_token_conditional_swap_inner( let swap_price = sell_amount / buy_amount; if swap_price > taker_price.to_num::() { - log::trace!( + trace!( "skipping token conditional swap for: {pubkey}, id: {tcs_id}, \ max_buy: {max_buy_token_to_liqee}, max_sell: {max_sell_token_to_liqor}, \ because counter swap price: {swap_price} while taker price: {taker_price}", @@ -189,7 +190,7 @@ pub async fn maybe_execute_token_conditional_swap_inner( } } - log::trace!( + trace!( "executing token conditional swap for: {}, with owner: {}, id: {}, max_buy: {}, max_sell: {}", pubkey, liqee.fixed.owner, @@ -206,11 +207,9 @@ pub async fn maybe_execute_token_conditional_swap_inner( max_sell_token_to_liqor, ) .await?; - log::info!( + info!( "Executed swap account {}, tcs index {}, tx sig {:?}", - pubkey, - tcs_id, - txsig + pubkey, tcs_id, txsig ); let slot = account_fetcher.transaction_max_slot(&[txsig]).await?; @@ -222,7 +221,7 @@ pub async fn maybe_execute_token_conditional_swap_inner( ) .await { - log::info!("could not refresh after tcs: {}", e); + info!("could not refresh after tcs: {}", e); } Ok(true) @@ -238,11 +237,9 @@ pub async fn remove_expired_token_conditional_swap( let txsig = mango_client .token_conditional_swap_trigger((pubkey, &liqee), tcs_id, 0, 0) .await?; - log::info!( + info!( "Removed expired token conditional swap account {}, tcs index {}, tx sig {:?}", - pubkey, - tcs_id, - txsig + pubkey, tcs_id, txsig ); Ok(true) diff --git a/bin/settler/Cargo.toml b/bin/settler/Cargo.toml index 74f5812f1..4af48adf8 100644 --- a/bin/settler/Cargo.toml +++ b/bin/settler/Cargo.toml @@ -31,7 +31,6 @@ itertools = "0.10.3" jemallocator = "0.3.2" jsonrpc-core = "18.0.0" jsonrpc-core-client = { version = "18.0.0", features = ["ws", "http", "tls"] } -log = "0.4" mango-v4 = { path = "../../programs/mango-v4", features = ["client"] } mango-v4-client = { path = "../../lib/client" } once_cell = "1.12.0" @@ -51,3 +50,4 @@ solana-sdk = { workspace = true } tokio = { version = "1", features = ["full"] } tokio-stream = { version = "0.1.9"} tokio-tungstenite = "0.16.1" +tracing = "0.1" diff --git a/bin/settler/src/main.rs b/bin/settler/src/main.rs index 719c57058..c41af0744 100644 --- a/bin/settler/src/main.rs +++ b/bin/settler/src/main.rs @@ -4,12 +4,12 @@ use std::time::Duration; use anchor_client::Cluster; use clap::Parser; -use log::*; use mango_v4::state::{PerpMarketIndex, TokenIndex}; use mango_v4_client::{ account_update_stream, chain_data, keypair_from_cli, snapshot_source, websocket_source, AsyncChannelSendUnlessFull, Client, MangoClient, MangoGroupContext, TransactionBuilderConfig, }; +use tracing::*; use itertools::Itertools; use solana_sdk::commitment_config::CommitmentConfig; @@ -75,6 +75,8 @@ pub fn encode_address(addr: &Pubkey) -> String { #[tokio::main] async fn main() -> anyhow::Result<()> { + mango_v4_client::tracing_subscriber_init(); + let args = if let Ok(cli_dotenv) = CliDotenv::try_parse() { dotenv::from_path(cli_dotenv.dotenv)?; cli_dotenv.remaining_args @@ -234,7 +236,7 @@ async fn main() -> anyhow::Result<()> { let mut state = shared_state.write().unwrap(); if is_mango_account(&account_write.account, &mango_group).is_some() { // e.g. to render debug logs RUST_LOG="liquidator=debug" - log::debug!( + debug!( "change to mango account {}...", &account_write.pubkey.to_string()[0..3] ); @@ -250,15 +252,15 @@ async fn main() -> anyhow::Result<()> { } else { let mut must_check_all = false; if is_mango_bank(&account_write.account, &mango_group).is_some() { - log::debug!("change to bank {}", &account_write.pubkey); + debug!("change to bank {}", &account_write.pubkey); must_check_all = true; } if is_perp_market(&account_write.account, &mango_group).is_some() { - log::debug!("change to perp market {}", &account_write.pubkey); + debug!("change to perp market {}", &account_write.pubkey); must_check_all = true; } if oracles.contains(&account_write.pubkey) { - log::debug!("change to oracle {}", &account_write.pubkey); + debug!("change to oracle {}", &account_write.pubkey); must_check_all = true; } if must_check_all { @@ -324,7 +326,7 @@ async fn main() -> anyhow::Result<()> { vec![data_job, settle_job].into_iter().collect(); jobs.next().await; - log::error!("a critical job aborted, exiting"); + error!("a critical job aborted, exiting"); Ok(()) } diff --git a/bin/settler/src/metrics.rs b/bin/settler/src/metrics.rs index 434d55d17..5f703a08f 100644 --- a/bin/settler/src/metrics.rs +++ b/bin/settler/src/metrics.rs @@ -2,6 +2,7 @@ use { std::collections::HashMap, std::sync::{atomic, Arc, Mutex, RwLock}, tokio::time, + tracing::*, }; #[derive(Debug)] @@ -151,7 +152,7 @@ pub fn start() -> Metrics { 0 }; let diff = new_value.wrapping_sub(previous_value) as i64; - log::info!("metric: {}: {} ({:+})", name, new_value, diff); + info!("metric: {}: {} ({:+})", name, new_value, diff); } Value::I64(v) => { let new_value = v.load(atomic::Ordering::Acquire); @@ -164,7 +165,7 @@ pub fn start() -> Metrics { 0 }; let diff = new_value - previous_value; - log::info!("metric: {}: {} ({:+})", name, new_value, diff); + info!("metric: {}: {} ({:+})", name, new_value, diff); } Value::String(v) => { let new_value = v.lock().unwrap(); @@ -178,13 +179,11 @@ pub fn start() -> Metrics { "".into() }; if *new_value == previous_value { - log::info!("metric: {}: {} (unchanged)", name, &*new_value); + info!("metric: {}: {} (unchanged)", name, &*new_value); } else { - log::info!( + info!( "metric: {}: {} (before: {})", - name, - &*new_value, - previous_value + name, &*new_value, previous_value ); } } diff --git a/bin/settler/src/settle.rs b/bin/settler/src/settle.rs index a23bd7b7d..cced540d1 100644 --- a/bin/settler/src/settle.rs +++ b/bin/settler/src/settle.rs @@ -15,6 +15,7 @@ use solana_sdk::signature::Signature; use solana_sdk::signer::Signer; use solana_sdk::transaction::VersionedTransaction; +use tracing::*; use {anyhow::Context, fixed::types::I80F48, solana_sdk::pubkey::Pubkey}; pub struct Config { @@ -44,7 +45,7 @@ fn perp_markets_and_prices( |v: anyhow::Result<(PerpMarketIndex, (PerpMarket, I80F48))>| match v { Ok(v) => Some(v), Err(err) => { - log::error!("error while retriving perp market and price: {:?}", err); + error!("error while retriving perp market and price: {:?}", err); None } }, @@ -279,12 +280,12 @@ impl<'a> SettleBatchProcessor<'a> { .map_err(|e| prettify_solana_client_error(e)); if let Err(err) = send_result { - log::info!("error while sending settle batch: {}", err); + info!("error while sending settle batch: {}", err); return Ok(None); } let txsig = send_result.unwrap(); - log::info!("sent settle tx: {txsig}"); + info!("sent settle tx: {txsig}"); Ok(Some(txsig)) } diff --git a/lib/client/Cargo.toml b/lib/client/Cargo.toml index 6c896e13e..53345683b 100644 --- a/lib/client/Cargo.toml +++ b/lib/client/Cargo.toml @@ -16,6 +16,7 @@ anyhow = "1.0" async-channel = "1.6" async-once-cell = { version = "0.4.2", features = ["unpin"] } async-trait = "0.1.52" +atty = "0.2" fixed = { workspace = true, features = ["serde", "borsh"] } futures = "0.3.25" itertools = "0.10.3" @@ -33,7 +34,6 @@ solana-address-lookup-table-program = { workspace = true } mango-feeds-connector = "0.1.1" spl-associated-token-account = "1.0.3" thiserror = "1.0.31" -log = "0.4" reqwest = "0.11.11" tokio = { version = "1", features = ["full"] } tokio-stream = { version = "0.1.9"} @@ -41,3 +41,5 @@ serde = "1.0.141" serde_json = "1.0.82" base64 = "0.13.0" bincode = "1.3.3" +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", features = ["env-filter"] } \ No newline at end of file diff --git a/lib/client/src/account_update_stream.rs b/lib/client/src/account_update_stream.rs index 59487d629..d73987e2f 100644 --- a/lib/client/src/account_update_stream.rs +++ b/lib/client/src/account_update_stream.rs @@ -1,8 +1,8 @@ use solana_client::rpc_response::{Response, RpcKeyedAccount}; use solana_sdk::{account::AccountSharedData, pubkey::Pubkey}; -use log::*; use std::{str::FromStr, sync::Arc}; +use tracing::*; use crate::chain_data; diff --git a/lib/client/src/snapshot_source.rs b/lib/client/src/snapshot_source.rs index d667c68c4..8acb629c9 100644 --- a/lib/client/src/snapshot_source.rs +++ b/lib/client/src/snapshot_source.rs @@ -12,10 +12,10 @@ use solana_sdk::{account::AccountSharedData, commitment_config::CommitmentConfig use anyhow::Context; use futures::{stream, StreamExt}; -use log::*; use std::str::FromStr; use std::time::Duration; use tokio::time; +use tracing::*; use crate::account_update_stream::{AccountUpdate, Message}; use crate::AnyhowWrap; @@ -230,10 +230,10 @@ pub fn start(config: Config, mango_oracles: Vec, sender: async_channel:: })) .await .expect("always Ok"); - log::debug!("latest slot for snapshot {}", epoch_info.absolute_slot); + debug!("latest slot for snapshot {}", epoch_info.absolute_slot); if epoch_info.absolute_slot > config.min_slot { - log::debug!("continuing to fetch snapshot now, min_slot {} is older than latest epoch slot {}", config.min_slot, epoch_info.absolute_slot); + debug!("continuing to fetch snapshot now, min_slot {} is older than latest epoch slot {}", config.min_slot, epoch_info.absolute_slot); break; } } diff --git a/lib/client/src/util.rs b/lib/client/src/util.rs index 59da7433d..653ac2f72 100644 --- a/lib/client/src/util.rs +++ b/lib/client/src/util.rs @@ -9,21 +9,6 @@ use solana_sdk::{ use std::{thread, time}; -// #[allow(dead_code)] -// pub fn retry(request: impl Fn() -> Result) -> anyhow::Result { -// for _i in 0..5 { -// match request() { -// Ok(res) => return Ok(res), -// Err(err) => { -// // TODO: only retry for recoverable errors -// log::error!("{:#?}", err); -// continue; -// } -// } -// } -// Err(anyhow!("Retry failed")) -// } - /// Some Result<> types don't convert to anyhow::Result nicely. Force them through stringification. pub trait AnyhowWrap { type Value; @@ -114,3 +99,14 @@ pub fn send_and_confirm_transaction( ) .into()) } + +/// Convenience function used in binaries to set up the fmt tracing_subscriber, +/// with cololring enabled only if logging to a terminal and with EnvFilter. +pub fn tracing_subscriber_init() { + let format = tracing_subscriber::fmt::format().with_ansi(atty::is(atty::Stream::Stdout)); + + tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .event_format(format) + .init(); +} diff --git a/lib/client/src/websocket_source.rs b/lib/client/src/websocket_source.rs index d431bcc2f..61c34ad60 100644 --- a/lib/client/src/websocket_source.rs +++ b/lib/client/src/websocket_source.rs @@ -11,9 +11,9 @@ use solana_rpc::rpc_pubsub::RpcSolPubSubClient; use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey}; use anyhow::Context; -use log::*; use std::time::Duration; use tokio_stream::StreamMap; +use tracing::*; use crate::account_update_stream::{AccountUpdate, Message}; use crate::AnyhowWrap; From 053b3d0f2976a5bd9798e05435db3b48db2699e5 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Mon, 10 Jul 2023 10:40:48 +0200 Subject: [PATCH 41/90] Client: jupiter swap and tx builder improvements - jupiter swap now supports multiple hops - tx builder can check resulting tx size (cherry picked from commit c58ee91356d6b822b8eeeb90fb6036383d819c8d) --- bin/cli/src/main.rs | 15 ++- bin/liquidator/src/liquidate.rs | 2 + bin/liquidator/src/rebalance.rs | 2 + bin/liquidator/src/token_swap_info.rs | 2 + bin/liquidator/src/trigger_tcs.rs | 1 + bin/liquidator/src/util.rs | 10 +- bin/settler/src/settle.rs | 2 +- lib/client/src/client.rs | 177 +++++++++++++++----------- 8 files changed, 132 insertions(+), 79 deletions(-) diff --git a/bin/cli/src/main.rs b/bin/cli/src/main.rs index 819ec4a2f..14bae6ef0 100644 --- a/bin/cli/src/main.rs +++ b/bin/cli/src/main.rs @@ -146,7 +146,7 @@ async fn main() -> Result<(), anyhow::Error> { Command::CreateAccount(cmd) => { let client = cmd.rpc.client(Some(&cmd.owner))?; let group = pubkey_from_cli(&cmd.group); - let owner = keypair_from_cli(&cmd.owner); + let owner = Arc::new(keypair_from_cli(&cmd.owner)); let account_num = if let Some(num) = cmd.account_num { num @@ -164,9 +164,15 @@ async fn main() -> Result<(), anyhow::Error> { + 1 } }; - let (account, txsig) = - MangoClient::create_account(&client, group, &owner, &owner, account_num, &cmd.name) - .await?; + let (account, txsig) = MangoClient::create_account( + &client, + group, + owner.clone(), + owner.clone(), + account_num, + &cmd.name, + ) + .await?; println!("{}", account); println!("{}", txsig); } @@ -193,6 +199,7 @@ async fn main() -> Result<(), anyhow::Error> { cmd.amount, cmd.slippage_bps, JupiterSwapMode::ExactIn, + false, ) .await?; println!("{}", txsig); diff --git a/bin/liquidator/src/liquidate.rs b/bin/liquidator/src/liquidate.rs index 8387789e4..f94df100e 100644 --- a/bin/liquidator/src/liquidate.rs +++ b/bin/liquidator/src/liquidate.rs @@ -42,6 +42,7 @@ pub async fn jupiter_market_can_buy( quote_amount, slippage, JupiterSwapMode::ExactIn, + false, ) .await .is_ok() @@ -70,6 +71,7 @@ pub async fn jupiter_market_can_sell( quote_amount, slippage, JupiterSwapMode::ExactOut, + false, ) .await .is_ok() diff --git a/bin/liquidator/src/rebalance.rs b/bin/liquidator/src/rebalance.rs index 6bd25c1f4..9d012b790 100644 --- a/bin/liquidator/src/rebalance.rs +++ b/bin/liquidator/src/rebalance.rs @@ -176,6 +176,7 @@ impl Rebalancer { input_amount.to_num::(), self.config.slippage_bps, JupiterSwapMode::ExactIn, + false, ) .await?; info!( @@ -208,6 +209,7 @@ impl Rebalancer { amount.to_num::(), self.config.slippage_bps, JupiterSwapMode::ExactIn, + false, ) .await?; info!( diff --git a/bin/liquidator/src/token_swap_info.rs b/bin/liquidator/src/token_swap_info.rs index 5341f4034..ff93830df 100644 --- a/bin/liquidator/src/token_swap_info.rs +++ b/bin/liquidator/src/token_swap_info.rs @@ -104,6 +104,7 @@ impl TokenSwapInfoUpdater { token_amount, slippage, JupiterSwapMode::ExactIn, + false, self.config.mock_jupiter, ) .await?; @@ -114,6 +115,7 @@ impl TokenSwapInfoUpdater { self.config.quote_amount, slippage, JupiterSwapMode::ExactIn, + false, self.config.mock_jupiter, ) .await?; diff --git a/bin/liquidator/src/trigger_tcs.rs b/bin/liquidator/src/trigger_tcs.rs index 7942e7b9a..bb1c5de0f 100644 --- a/bin/liquidator/src/trigger_tcs.rs +++ b/bin/liquidator/src/trigger_tcs.rs @@ -172,6 +172,7 @@ pub async fn maybe_execute_token_conditional_swap_inner( input_amount, slippage, swap_mode, + false, config.mock_jupiter, ) .await?; diff --git a/bin/liquidator/src/util.rs b/bin/liquidator/src/util.rs index 8fa456d2c..c1d611298 100644 --- a/bin/liquidator/src/util.rs +++ b/bin/liquidator/src/util.rs @@ -45,11 +45,19 @@ pub async fn jupiter_route( amount: u64, slippage: u64, swap_mode: JupiterSwapMode, + only_direct_routes: bool, mock: bool, ) -> anyhow::Result { if !mock { return mango_client - .jupiter_route(input_mint, output_mint, amount, slippage, swap_mode) + .jupiter_route( + input_mint, + output_mint, + amount, + slippage, + swap_mode, + only_direct_routes, + ) .await; } diff --git a/bin/settler/src/settle.rs b/bin/settler/src/settle.rs index cced540d1..a46ccd5c4 100644 --- a/bin/settler/src/settle.rs +++ b/bin/settler/src/settle.rs @@ -251,7 +251,7 @@ struct SettleBatchProcessor<'a> { impl<'a> SettleBatchProcessor<'a> { fn transaction(&self) -> anyhow::Result { let client = &self.mango_client.client; - let fee_payer = &*client.fee_payer; + let fee_payer = client.fee_payer.clone(); TransactionBuilder { instructions: self.instructions.clone(), diff --git a/lib/client/src/client.rs b/lib/client/src/client.rs index bc5a5ad41..bf00463bc 100644 --- a/lib/client/src/client.rs +++ b/lib/client/src/client.rs @@ -1,3 +1,4 @@ +use std::ops::Deref; use std::str::FromStr; use std::sync::Arc; use std::time::Duration; @@ -134,16 +135,16 @@ impl MangoClient { pub async fn find_or_create_account( client: &Client, group: Pubkey, - owner: &Keypair, - payer: &Keypair, // pays the SOL for the new account + owner: Arc, + payer: Arc, // pays the SOL for the new account mango_account_name: &str, ) -> anyhow::Result { let rpc = client.rpc_async(); let program = mango_v4::ID; + let owner_pk = owner.pubkey(); // Mango Account - let mut mango_account_tuples = - fetch_mango_accounts(&rpc, program, group, owner.pubkey()).await?; + let mut mango_account_tuples = fetch_mango_accounts(&rpc, program, group, owner_pk).await?; let mango_account_opt = mango_account_tuples .iter() .find(|(_, account)| account.fixed.name() == mango_account_name); @@ -158,12 +159,18 @@ impl MangoClient { Some(tuple) => tuple.1.fixed.account_num + 1, None => 0u32, }; - Self::create_account(client, group, owner, payer, account_num, mango_account_name) - .await - .context("Failed to create account...")?; + Self::create_account( + client, + group, + owner.clone(), + payer, + account_num, + mango_account_name, + ) + .await + .context("Failed to create account...")?; } - let mango_account_tuples = - fetch_mango_accounts(&rpc, program, group, owner.pubkey()).await?; + let mango_account_tuples = fetch_mango_accounts(&rpc, program, group, owner_pk).await?; let index = mango_account_tuples .iter() .position(|tuple| tuple.1.fixed.name() == mango_account_name) @@ -174,8 +181,8 @@ impl MangoClient { pub async fn create_account( client: &Client, group: Pubkey, - owner: &Keypair, - payer: &Keypair, // pays the SOL for the new account + owner: Arc, + payer: Arc, // pays the SOL for the new account account_num: u32, mango_account_name: &str, ) -> anyhow::Result<(Pubkey, Signature)> { @@ -1390,6 +1397,7 @@ impl MangoClient { amount: u64, slippage: u64, swap_mode: JupiterSwapMode, + only_direct_routes: bool, ) -> anyhow::Result { let response = self .http_client @@ -1398,7 +1406,7 @@ impl MangoClient { ("inputMint", input_mint.to_string()), ("outputMint", output_mint.to_string()), ("amount", format!("{}", amount)), - ("onlyDirectRoutes", "true".into()), + ("onlyDirectRoutes", only_direct_routes.to_string()), ("enforceSingleTx", "true".into()), ("filterTopNResult", "10".into()), ("slippageBps", format!("{}", slippage)), @@ -1429,19 +1437,18 @@ impl MangoClient { Ok(route.clone()) } - pub async fn jupiter_swap( + /// Find the instructions and account lookup tables for a jupiter swap through mango + /// + /// It would be nice if we didn't have to pass input_mint/output_mint - the data is + /// definitely in QueryRoute - but it's unclear how. + pub async fn prepare_jupiter_swap_transaction( &self, input_mint: Pubkey, output_mint: Pubkey, - amount: u64, - slippage: u64, - swap_mode: JupiterSwapMode, - ) -> anyhow::Result { + route: &jupiter::QueryRoute, + ) -> anyhow::Result { let source_token = self.context.token_by_mint(&input_mint)?; let target_token = self.context.token_by_mint(&output_mint)?; - let route = self - .jupiter_route(input_mint, output_mint, amount, slippage, swap_mode) - .await?; let swap_response = self .http_client @@ -1477,22 +1484,25 @@ impl MangoClient { let ata_program = anchor_spl::associated_token::ID; let token_program = anchor_spl::token::ID; let compute_budget_program = solana_sdk::compute_budget::ID; - // these setup instructions are unnecessary since FlashLoan already takes care of it + // these setup instructions should be placed outside of flashloan begin-end let is_setup_ix = |k: Pubkey| -> bool { k == ata_program || k == token_program || k == compute_budget_program }; let (jup_ixs, jup_alts) = self .deserialize_instructions_and_alts(&jup_tx.message) .await?; - let jup_cu_ix = jup_ixs + let jup_action_ix_begin = jup_ixs .iter() - .filter(|ix| ix.program_id == compute_budget_program) - .cloned() - .collect::>(); - let jup_action_ix = jup_ixs - .into_iter() - .filter(|ix| !is_setup_ix(ix.program_id)) - .collect::>(); + .position(|ix| !is_setup_ix(ix.program_id)) + .ok_or_else(|| { + anyhow::anyhow!("jupiter swap response only had setup-like instructions") + })?; + let jup_action_ix_end = jup_ixs.len() + - jup_ixs + .iter() + .rev() + .position(|ix| !is_setup_ix(ix.program_id)) + .unwrap(); let bank_ams = [ source_token.mint_info.first_bank(), @@ -1522,14 +1532,14 @@ impl MangoClient { }) .collect::>(); - let loan_amounts = vec![ - match swap_mode { - JupiterSwapMode::ExactIn => amount, - // in amount + slippage - JupiterSwapMode::ExactOut => u64::from_str(&route.other_amount_threshold).unwrap(), - }, - 0u64, - ]; + let source_loan = if route.swap_mode == "ExactIn" { + u64::from_str(&route.amount).unwrap() + } else if route.swap_mode == "ExactOut" { + u64::from_str(&route.other_amount_threshold).unwrap() + } else { + anyhow::bail!("unknown swap mode: {}", route.swap_mode); + }; + let loan_amounts = vec![source_loan, 0u64]; let num_loans: u8 = loan_amounts.len().try_into().unwrap(); // This relies on the fact that health account banks will be identical to the first_bank above! @@ -1544,25 +1554,9 @@ impl MangoClient { let mut instructions = Vec::new(); - for ix in jup_cu_ix { + for ix in &jup_ixs[..jup_action_ix_begin] { instructions.push(ix.clone()); } - instructions.push( - spl_associated_token_account::instruction::create_associated_token_account_idempotent( - &self.owner.pubkey(), - &self.owner.pubkey(), - &source_token.mint_info.mint, - &Token::id(), - ), - ); - instructions.push( - spl_associated_token_account::instruction::create_associated_token_account_idempotent( - &self.owner.pubkey(), - &self.owner.pubkey(), - &target_token.mint_info.mint, - &Token::id(), - ), - ); instructions.push(Instruction { program_id: mango_v4::id(), accounts: { @@ -1585,7 +1579,7 @@ impl MangoClient { loan_amounts, }), }); - for ix in jup_action_ix { + for ix in &jup_ixs[jup_action_ix_begin..jup_action_ix_end] { instructions.push(ix.clone()); } instructions.push(Instruction { @@ -1610,20 +1604,49 @@ impl MangoClient { flash_loan_type: mango_v4::accounts_ix::FlashLoanType::Swap, }), }); + for ix in &jup_ixs[jup_action_ix_end..] { + instructions.push(ix.clone()); + } - let payer = self.owner.pubkey(); // maybe use fee_payer? but usually it's the same let mut address_lookup_tables = self.mango_address_lookup_tables().await?; address_lookup_tables.extend(jup_alts.into_iter()); - TransactionBuilder { + let payer = self.owner.pubkey(); // maybe use fee_payer? but usually it's the same + + Ok(TransactionBuilder { instructions, address_lookup_tables, payer, - signers: vec![&*self.owner], + signers: vec![self.owner.clone()], config: self.client.transaction_builder_config, - } - .send_and_confirm(&self.client) - .await + }) + } + + pub async fn jupiter_swap( + &self, + input_mint: Pubkey, + output_mint: Pubkey, + amount: u64, + slippage: u64, + swap_mode: JupiterSwapMode, + only_direct_routes: bool, + ) -> anyhow::Result { + let route = self + .jupiter_route( + input_mint, + output_mint, + amount, + slippage, + swap_mode, + only_direct_routes, + ) + .await?; + + let tx_builder = self + .prepare_jupiter_swap_transaction(input_mint, output_mint, &route) + .await?; + + tx_builder.send_and_confirm(&self.client).await } async fn fetch_address_lookup_table( @@ -1707,7 +1730,7 @@ impl MangoClient { instructions, address_lookup_tables: vec![], payer: self.client.fee_payer.pubkey(), - signers: vec![&*self.owner, &*self.client.fee_payer], + signers: vec![self.owner.clone(), self.client.fee_payer.clone()], config: self.client.transaction_builder_config, } .send_and_confirm(&self.client) @@ -1722,7 +1745,7 @@ impl MangoClient { instructions, address_lookup_tables: vec![], payer: self.client.fee_payer.pubkey(), - signers: vec![&*self.client.fee_payer], + signers: vec![self.client.fee_payer.clone()], config: self.client.transaction_builder_config, } .send_and_confirm(&self.client) @@ -1747,17 +1770,17 @@ pub struct TransactionBuilderConfig { pub prioritization_micro_lamports: Option, } -pub struct TransactionBuilder<'a> { +pub struct TransactionBuilder { pub instructions: Vec, pub address_lookup_tables: Vec, - pub signers: Vec<&'a Keypair>, + pub signers: Vec>, pub payer: Pubkey, pub config: TransactionBuilderConfig, } -impl<'a> TransactionBuilder<'a> { +impl TransactionBuilder { pub async fn transaction( - self, + &self, rpc: &RpcClientAsync, ) -> anyhow::Result { let latest_blockhash = rpc.get_latest_blockhash().await?; @@ -1765,11 +1788,12 @@ impl<'a> TransactionBuilder<'a> { } pub fn transaction_with_blockhash( - mut self, + &self, blockhash: Hash, ) -> anyhow::Result { + let mut ix = self.instructions.clone(); if let Some(prio_price) = self.config.prioritization_micro_lamports { - self.instructions.insert( + ix.insert( 0, solana_sdk::compute_budget::ComputeBudgetInstruction::set_compute_unit_price( prio_price, @@ -1778,15 +1802,16 @@ impl<'a> TransactionBuilder<'a> { } let v0_message = solana_sdk::message::v0::Message::try_compile( &self.payer, - &self.instructions, + &ix, &self.address_lookup_tables, blockhash, )?; let versioned_message = solana_sdk::message::VersionedMessage::V0(v0_message); let signers = self .signers - .into_iter() + .iter() .unique_by(|s| s.pubkey()) + .map(|v| v.deref()) .collect::>(); let tx = solana_sdk::transaction::VersionedTransaction::try_new(versioned_message, &signers)?; @@ -1795,7 +1820,7 @@ impl<'a> TransactionBuilder<'a> { // These two send() functions don't really belong into the transaction builder! - pub async fn send(self, client: &Client) -> anyhow::Result { + pub async fn send(&self, client: &Client) -> anyhow::Result { let rpc = client.rpc_async(); let tx = self.transaction(&rpc).await?; rpc.send_transaction_with_config(&tx, client.rpc_send_transaction_config) @@ -1803,7 +1828,7 @@ impl<'a> TransactionBuilder<'a> { .map_err(prettify_solana_client_error) } - pub async fn send_and_confirm(self, client: &Client) -> anyhow::Result { + pub async fn send_and_confirm(&self, client: &Client) -> anyhow::Result { let rpc = client.rpc_async(); let tx = self.transaction(&rpc).await?; // TODO: Wish we could use client.rpc_send_transaction_config here too! @@ -1811,6 +1836,12 @@ impl<'a> TransactionBuilder<'a> { .await .map_err(prettify_solana_client_error) } + + pub fn transaction_size_ok(&self) -> anyhow::Result { + let tx = self.transaction_with_blockhash(solana_sdk::hash::Hash::default())?; + let bytes = bincode::serialize(&tx)?; + Ok(bytes.len() <= solana_sdk::packet::PACKET_DATA_SIZE) + } } /// Do some manual unpacking on some ClientErrors From f908dd5a2ee69d279a14937985bda7028b4d3d2c Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Tue, 11 Jul 2023 09:08:38 +0200 Subject: [PATCH 42/90] liquidator: when rebalancing allow multi-hop jupiter routes and if that doesn't fit into a transaction, try a direct route with USDC or SOL. (cherry picked from commit db8f5ae30d031f005a601ab351bd881e26d8b271) --- bin/liquidator/src/liquidate.rs | 17 +- bin/liquidator/src/rebalance.rs | 277 ++++++++++++++++++++++++++------ 2 files changed, 237 insertions(+), 57 deletions(-) diff --git a/bin/liquidator/src/liquidate.rs b/bin/liquidator/src/liquidate.rs index f94df100e..50f24e7ad 100644 --- a/bin/liquidator/src/liquidate.rs +++ b/bin/liquidator/src/liquidate.rs @@ -1,9 +1,10 @@ use std::collections::HashSet; use std::time::Duration; +use itertools::Itertools; use mango_v4::health::{HealthCache, HealthType}; use mango_v4::state::{MangoAccountValue, PerpMarketIndex, Side, TokenIndex, QUOTE_TOKEN_INDEX}; -use mango_v4_client::{chain_data, health_cache, AccountFetcher, JupiterSwapMode, MangoClient}; +use mango_v4_client::{chain_data, health_cache, JupiterSwapMode, MangoClient}; use solana_sdk::signature::Signature; use futures::{stream, StreamExt, TryStreamExt}; @@ -92,17 +93,15 @@ struct LiquidateHelper<'a> { impl<'a> LiquidateHelper<'a> { async fn serum3_close_orders(&self) -> anyhow::Result> { // look for any open serum orders or settleable balances - let serum_oos: anyhow::Result> = stream::iter(self.liqee.active_serum3_orders()) - .then(|orders| async { - let open_orders_account = self - .account_fetcher - .fetch_raw_account(&orders.open_orders) - .await?; + let serum_oos: anyhow::Result> = self + .liqee + .active_serum3_orders() + .map(|orders| { + let open_orders_account = self.account_fetcher.fetch_raw(&orders.open_orders)?; let open_orders = mango_v4::serum3_cpi::load_open_orders(&open_orders_account)?; Ok((*orders, *open_orders)) }) - .try_collect() - .await; + .try_collect(); let serum_force_cancels = serum_oos? .into_iter() .filter_map(|(orders, open_orders)| { diff --git a/bin/liquidator/src/rebalance.rs b/bin/liquidator/src/rebalance.rs index 9d012b790..34f0fc11d 100644 --- a/bin/liquidator/src/rebalance.rs +++ b/bin/liquidator/src/rebalance.rs @@ -1,17 +1,18 @@ +use itertools::Itertools; use mango_v4::accounts_zerocopy::KeyedAccountSharedData; use mango_v4::state::{ Bank, BookSide, MangoAccountValue, PerpPosition, PlaceOrderType, Side, TokenIndex, TokenPosition, QUOTE_TOKEN_INDEX, }; use mango_v4_client::{ - chain_data, perp_pnl, AccountFetcher, AnyhowWrap, JupiterSwapMode, MangoClient, - PerpMarketContext, TokenContext, + chain_data, jupiter::QueryRoute, perp_pnl, AnyhowWrap, JupiterSwapMode, MangoClient, + PerpMarketContext, TokenContext, TransactionBuilder, }; use {fixed::types::I80F48, solana_sdk::pubkey::Pubkey}; -use futures::{stream, StreamExt, TryStreamExt}; use solana_sdk::signature::Signature; +use std::str::FromStr; use std::sync::Arc; use std::{collections::HashMap, time::Duration}; use tracing::*; @@ -36,14 +37,14 @@ struct TokenState { } impl TokenState { - async fn new_position( + fn new_position( token: &TokenContext, position: &TokenPosition, account_fetcher: &chain_data::AccountFetcher, ) -> anyhow::Result { let bank = Self::bank(token, account_fetcher)?; Ok(Self { - price: Self::fetch_price(token, &bank, account_fetcher).await?, + price: Self::fetch_price(token, &bank, account_fetcher)?, native_position: position.native(&bank), in_use: position.is_in_use(), }) @@ -56,14 +57,12 @@ impl TokenState { account_fetcher.fetch::(&token.mint_info.first_bank()) } - async fn fetch_price( + fn fetch_price( token: &TokenContext, bank: &Bank, account_fetcher: &chain_data::AccountFetcher, ) -> anyhow::Result { - let oracle = account_fetcher - .fetch_raw_account(&token.mint_info.oracle) - .await?; + let oracle = account_fetcher.fetch_raw(&token.mint_info.oracle)?; bank.oracle_price( &KeyedAccountSharedData::new(token.mint_info.oracle, oracle.into()), None, @@ -72,6 +71,13 @@ impl TokenState { } } +#[derive(Clone)] +struct WrappedJupRoute { + input_mint: Pubkey, + output_mint: Pubkey, + route: QueryRoute, +} + pub struct Rebalancer { pub mango_client: Arc, pub account_fetcher: Arc, @@ -116,6 +122,188 @@ impl Rebalancer { Ok(true) } + /// Wrapping client.jupiter_route() in a way that preserves the in/out mints + async fn jupiter_route( + &self, + input_mint: Pubkey, + output_mint: Pubkey, + amount: u64, + only_direct_routes: bool, + ) -> anyhow::Result { + let route = self + .mango_client + .jupiter_route( + input_mint, + output_mint, + amount, + self.config.slippage_bps, + JupiterSwapMode::ExactIn, + only_direct_routes, + ) + .await?; + Ok(WrappedJupRoute { + input_mint, + output_mint, + route, + }) + } + + /// Grab three possible routes: + /// 1. USDC -> output (complex routes) + /// 2. USDC -> output (direct route only) + /// 3. SOL -> output (direct route only) + /// Use 1. if it fits into a tx. Otherwise use the better of 2./3. + async fn token_swap_buy( + &self, + output_mint: Pubkey, + in_amount_quote: u64, + ) -> anyhow::Result<(Signature, WrappedJupRoute)> { + let quote_token = self.mango_client.context.token(QUOTE_TOKEN_INDEX); + let sol_token = self.mango_client.context.token( + *self + .mango_client + .context + .token_indexes_by_name + .get("SOL") // TODO: better use mint + .unwrap(), + ); + + let full_route_job = self.jupiter_route( + quote_token.mint_info.mint, + output_mint, + in_amount_quote, + false, + ); + let direct_quote_route_job = self.jupiter_route( + quote_token.mint_info.mint, + output_mint, + in_amount_quote, + true, + ); + + // For the SOL -> output route we need to adjust the in amount by the SOL price + let sol_bank = TokenState::bank(sol_token, &self.account_fetcher)?; + let sol_price = TokenState::fetch_price(sol_token, &sol_bank, &self.account_fetcher)?; + let in_amount_sol = (I80F48::from(in_amount_quote) / sol_price) + .ceil() + .to_num::(); + let direct_sol_route_job = + self.jupiter_route(sol_token.mint_info.mint, output_mint, in_amount_sol, true); + + let (full_route, direct_quote_route, direct_sol_route) = + tokio::join!(full_route_job, direct_quote_route_job, direct_sol_route_job); + let alternatives = [direct_quote_route, direct_sol_route] + .into_iter() + .filter_map(|v| v.ok()) + .collect_vec(); + + let (tx_builder, route) = self + .determine_best_jupiter_tx( + // If the best_route couldn't be fetched, something is wrong + &full_route?, + &alternatives, + ) + .await?; + let sig = tx_builder + .send_and_confirm(&self.mango_client.client) + .await?; + Ok((sig, route)) + } + + /// Grab three possible routes: + /// 1. input -> USDC (complex routes) + /// 2. input -> USDC (direct route only) + /// 3. input -> SOL (direct route only) + /// Use 1. if it fits into a tx. Otherwise use the better of 2./3. + async fn token_swap_sell( + &self, + input_mint: Pubkey, + in_amount: u64, + ) -> anyhow::Result<(Signature, WrappedJupRoute)> { + let quote_token = self.mango_client.context.token(QUOTE_TOKEN_INDEX); + let sol_token = self.mango_client.context.token( + *self + .mango_client + .context + .token_indexes_by_name + .get("SOL") // TODO: better use mint + .unwrap(), + ); + + let full_route_job = + self.jupiter_route(input_mint, quote_token.mint_info.mint, in_amount, false); + let direct_quote_route_job = + self.jupiter_route(input_mint, quote_token.mint_info.mint, in_amount, true); + let direct_sol_route_job = + self.jupiter_route(input_mint, sol_token.mint_info.mint, in_amount, true); + let (full_route, direct_quote_route, direct_sol_route) = + tokio::join!(full_route_job, direct_quote_route_job, direct_sol_route_job); + let alternatives = [direct_quote_route, direct_sol_route] + .into_iter() + .filter_map(|v| v.ok()) + .collect_vec(); + + let (tx_builder, route) = self + .determine_best_jupiter_tx( + // If the best_route couldn't be fetched, something is wrong + &full_route?, + &alternatives, + ) + .await?; + + let sig = tx_builder + .send_and_confirm(&self.mango_client.client) + .await?; + Ok((sig, route)) + } + + async fn determine_best_jupiter_tx( + &self, + full: &WrappedJupRoute, + alternatives: &[WrappedJupRoute], + ) -> anyhow::Result<(TransactionBuilder, WrappedJupRoute)> { + let builder = self + .mango_client + .prepare_jupiter_swap_transaction(full.input_mint, full.output_mint, &full.route) + .await?; + if builder.transaction_size_ok()? { + return Ok((builder, full.clone())); + } + log::trace!( + "full route from {} to {} does not fit in a tx, market_info.label {}", + full.input_mint, + full.output_mint, + full.route + .market_infos + .first() + .map(|v| v.label.clone()) + .unwrap_or_else(|| "no market_info".into()) + ); + + if alternatives.is_empty() { + anyhow::bail!( + "no alternative routes from {} to {}", + full.input_mint, + full.output_mint + ); + } + + let best = alternatives + .iter() + .min_by(|a, b| { + a.route + .price_impact_pct + .partial_cmp(&b.route.price_impact_pct) + .unwrap() + }) + .unwrap(); + let builder = self + .mango_client + .prepare_jupiter_swap_transaction(best.input_mint, best.output_mint, &best.route) + .await?; + Ok((builder, best.clone())) + } + async fn rebalance_tokens(&self) -> anyhow::Result<()> { let account = self .account_fetcher @@ -124,18 +312,16 @@ impl Rebalancer { // TODO: configurable? let quote_token = self.mango_client.context.token(QUOTE_TOKEN_INDEX); - let tokens: anyhow::Result> = - stream::iter(account.active_token_positions()) - .then(|token_position| async { - let token = self.mango_client.context.token(token_position.token_index); - Ok(( - token.token_index, - TokenState::new_position(token, token_position, &self.account_fetcher) - .await?, - )) - }) - .try_collect() - .await; + let tokens: anyhow::Result> = account + .active_token_positions() + .map(|token_position| { + let token = self.mango_client.context.token(token_position.token_index); + Ok(( + token.token_index, + TokenState::new_position(token, token_position, &self.account_fetcher)?, + )) + }) + .try_collect(); let tokens = tokens?; trace!(?tokens, "account tokens"); @@ -145,7 +331,6 @@ impl Rebalancer { continue; } let token_mint = token.mint_info.mint; - let quote_mint = quote_token.mint_info.mint; // It's not always possible to bring the native balance to 0 through swaps: // Consider a price <1. You need to sell a bunch of tokens to get 1 USDC native and @@ -168,23 +353,21 @@ impl Rebalancer { let input_amount = buy_amount * token_state.price * I80F48::from_num(self.config.borrow_settle_excess); - let txsig = self - .mango_client - .jupiter_swap( - quote_mint, - token_mint, - input_amount.to_num::(), - self.config.slippage_bps, - JupiterSwapMode::ExactIn, - false, - ) + let (txsig, route) = self + .token_swap_buy(token_mint, input_amount.to_num()) .await?; + let in_token = self + .mango_client + .context + .token_by_mint(&route.input_mint) + .unwrap(); info!( %txsig, - "bought {} {} for {}", - token.native_to_ui(buy_amount), + "bought {} {} for {} {}", + token.native_to_ui(I80F48::from_str(&route.route.out_amount).unwrap()), token.name, - quote_token.name, + in_token.native_to_ui(I80F48::from_str(&route.route.in_amount).unwrap()), + in_token.name, ); if !self.refresh_mango_account_after_tx(txsig).await? { return Ok(()); @@ -201,23 +384,21 @@ impl Rebalancer { if amount > dust_threshold { // Sell - let txsig = self - .mango_client - .jupiter_swap( - token_mint, - quote_mint, - amount.to_num::(), - self.config.slippage_bps, - JupiterSwapMode::ExactIn, - false, - ) + let (txsig, route) = self + .token_swap_sell(token_mint, amount.to_num::()) .await?; + let out_token = self + .mango_client + .context + .token_by_mint(&route.output_mint) + .unwrap(); info!( %txsig, - "sold {} {} for {}", - token.native_to_ui(amount), + "sold {} {} for {} {}", + token.native_to_ui(I80F48::from_str(&route.route.in_amount).unwrap()), token.name, - quote_token.name, + out_token.native_to_ui(I80F48::from_str(&route.route.out_amount).unwrap()), + out_token.name, ); if !self.refresh_mango_account_after_tx(txsig).await? { return Ok(()); From 348fef80f3c8b7436accfb02612be32208d43a5d Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Mon, 17 Jul 2023 13:14:53 +0200 Subject: [PATCH 43/90] Tcs: reserve token positions while tcs is open (#647) - Update in_use_count to u16 - Pass banks to TCS cancel instruction - Increase the in_use_count when a tcs is created and decrease on trigger/cancel (cherry picked from commit 5dd5c507f27cb4b51228261a7ba39c80b6ca4b0c) --- mango_v4.json | 180 ++++++++- programs/mango-v4/src/accounts_ix/mod.rs | 4 +- ...ty.rs => token_conditional_swap_cancel.rs} | 18 +- .../instructions/serum3_close_open_orders.rs | 4 +- .../instructions/serum3_create_open_orders.rs | 4 +- .../token_conditional_swap_cancel.rs | 21 +- .../token_conditional_swap_create.rs | 10 + .../token_conditional_swap_trigger.rs | 38 +- programs/mango-v4/src/lib.rs | 2 +- programs/mango-v4/src/state/bank.rs | 15 +- programs/mango-v4/src/state/mango_account.rs | 42 +- .../src/state/mango_account_components.rs | 14 +- .../tests/program_test/mango_client.rs | 10 +- ts/client/src/client.ts | 47 ++- ts/client/src/mango_v4.ts | 360 +++++++++++++++++- 15 files changed, 705 insertions(+), 64 deletions(-) rename programs/mango-v4/src/accounts_ix/{account_and_authority.rs => token_conditional_swap_cancel.rs} (50%) diff --git a/mango_v4.json b/mango_v4.json index 057a5f09d..80fb3e2f5 100644 --- a/mango_v4.json +++ b/mango_v4.json @@ -2,6 +2,100 @@ "version": "0.18.0", "name": "mango_v4", "instructions": [ + { + "name": "admingTokenWithdrawFees", + "accounts": [ + { + "name": "group", + "isMut": false, + "isSigner": false, + "relations": [ + "admin" + ] + }, + { + "name": "bank", + "isMut": true, + "isSigner": false, + "relations": [ + "group", + "vault" + ] + }, + { + "name": "vault", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenAccount", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "admin", + "isMut": false, + "isSigner": true + } + ], + "args": [] + }, + { + "name": "adminPerpWithdrawFees", + "accounts": [ + { + "name": "group", + "isMut": false, + "isSigner": false, + "relations": [ + "admin" + ] + }, + { + "name": "perpMarket", + "isMut": true, + "isSigner": false, + "relations": [ + "group" + ] + }, + { + "name": "bank", + "isMut": true, + "isSigner": false, + "relations": [ + "group", + "vault" + ] + }, + { + "name": "vault", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenAccount", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "admin", + "isMut": false, + "isSigner": true + } + ], + "args": [] + }, { "name": "groupCreate", "accounts": [ @@ -4818,6 +4912,25 @@ "name": "authority", "isMut": false, "isSigner": true + }, + { + "name": "buyBank", + "isMut": true, + "isSigner": false, + "docs": [ + "The bank's token_index is checked at #1" + ], + "relations": [ + "group" + ] + }, + { + "name": "sellBank", + "isMut": true, + "isSigner": false, + "relations": [ + "group" + ] } ], "args": [ @@ -5263,12 +5376,25 @@ "name": "forceClose", "type": "u8" }, + { + "name": "padding", + "type": { + "array": [ + "u8", + 6 + ] + } + }, + { + "name": "feesWithdrawn", + "type": "u64" + }, { "name": "reserved", "type": { "array": [ "u8", - 2118 + 2104 ] } } @@ -6041,7 +6167,8 @@ { "name": "feesAccrued", "docs": [ - "Fees accrued in native quote currency" + "Fees accrued in native quote currency", + "these are increased when new fees are paid and decreased when perp_settle_fees is called" ], "type": { "defined": "I80F48" @@ -6050,7 +6177,8 @@ { "name": "feesSettled", "docs": [ - "Fees settled in native quote currency" + "Fees settled in native quote currency", + "these are increased when perp_settle_fees is called, and never decreased" ], "type": { "defined": "I80F48" @@ -6160,12 +6288,16 @@ "defined": "I80F48" } }, + { + "name": "feesWithdrawn", + "type": "u64" + }, { "name": "reserved", "type": { "array": [ "u8", - 1888 + 1880 ] } } @@ -6724,14 +6856,14 @@ "docs": [ "incremented when a market requires this position to stay alive" ], - "type": "u8" + "type": "u16" }, { "name": "padding", "type": { "array": [ "u8", - 5 + 4 ] } }, @@ -8353,6 +8485,42 @@ }, { "name": "TokenConditionalSwapCancel" + }, + { + "name": "OpenbookV2CancelOrder" + }, + { + "name": "OpenbookV2CloseOpenOrders" + }, + { + "name": "OpenbookV2CreateOpenOrders" + }, + { + "name": "OpenbookV2DeregisterMarket" + }, + { + "name": "OpenbookV2EditMarket" + }, + { + "name": "OpenbookV2LiqForceCancelOrders" + }, + { + "name": "OpenbookV2PlaceOrder" + }, + { + "name": "OpenbookV2PlaceTakeOrder" + }, + { + "name": "OpenbookV2RegisterMarket" + }, + { + "name": "OpenbookV2SettleFunds" + }, + { + "name": "AdminTokenWithdrawFees" + }, + { + "name": "AdminPerpWithdrawFees" } ] } diff --git a/programs/mango-v4/src/accounts_ix/mod.rs b/programs/mango-v4/src/accounts_ix/mod.rs index 82f503e3f..82cdabe79 100644 --- a/programs/mango-v4/src/accounts_ix/mod.rs +++ b/programs/mango-v4/src/accounts_ix/mod.rs @@ -1,4 +1,3 @@ -pub use account_and_authority::*; pub use account_buyback_fees_with_mngo::*; pub use account_close::*; pub use account_create::*; @@ -49,6 +48,7 @@ pub use stub_oracle_close::*; pub use stub_oracle_create::*; pub use stub_oracle_set::*; pub use token_add_bank::*; +pub use token_conditional_swap_cancel::*; pub use token_conditional_swap_create::*; pub use token_conditional_swap_trigger::*; pub use token_deposit::*; @@ -62,7 +62,6 @@ pub use token_register_trustless::*; pub use token_update_index_and_rate::*; pub use token_withdraw::*; -mod account_and_authority; mod account_buyback_fees_with_mngo; mod account_close; mod account_create; @@ -113,6 +112,7 @@ mod stub_oracle_close; mod stub_oracle_create; mod stub_oracle_set; mod token_add_bank; +mod token_conditional_swap_cancel; mod token_conditional_swap_create; mod token_conditional_swap_trigger; mod token_deposit; diff --git a/programs/mango-v4/src/accounts_ix/account_and_authority.rs b/programs/mango-v4/src/accounts_ix/token_conditional_swap_cancel.rs similarity index 50% rename from programs/mango-v4/src/accounts_ix/account_and_authority.rs rename to programs/mango-v4/src/accounts_ix/token_conditional_swap_cancel.rs index 0e5c00886..55a62a217 100644 --- a/programs/mango-v4/src/accounts_ix/account_and_authority.rs +++ b/programs/mango-v4/src/accounts_ix/token_conditional_swap_cancel.rs @@ -3,8 +3,10 @@ use crate::state::*; use anchor_lang::prelude::*; #[derive(Accounts)] -pub struct AccountAndAuthority<'info> { - // Instructions using this must put the ix gate into instruction code! +pub struct TokenConditionalSwapCancel<'info> { + #[account( + constraint = group.load()?.is_ix_enabled(IxGate::TokenConditionalSwapCancel) @ MangoError::IxIsDisabled, + )] pub group: AccountLoader<'info, Group>, #[account( @@ -15,4 +17,16 @@ pub struct AccountAndAuthority<'info> { )] pub account: AccountLoader<'info, MangoAccountFixed>, pub authority: Signer<'info>, + + /// The bank's token_index is checked at #1 + #[account( + mut, + has_one = group, + )] + pub buy_bank: AccountLoader<'info, Bank>, + #[account( + mut, + has_one = group, + )] + pub sell_bank: AccountLoader<'info, Bank>, } diff --git a/programs/mango-v4/src/instructions/serum3_close_open_orders.rs b/programs/mango-v4/src/instructions/serum3_close_open_orders.rs index 96679b98c..d463c5d58 100644 --- a/programs/mango-v4/src/instructions/serum3_close_open_orders.rs +++ b/programs/mango-v4/src/instructions/serum3_close_open_orders.rs @@ -34,9 +34,9 @@ pub fn serum3_close_open_orders(ctx: Context) -> Result<( // Reduce the in_use_count on the token positions - they no longer need to be forced open. // We cannot immediately dust tiny positions because we don't have the banks. let (base_position, _) = account.token_position_mut(serum_market.base_token_index)?; - base_position.in_use_count = base_position.in_use_count.saturating_sub(1); + base_position.decrement_in_use(); let (quote_position, _) = account.token_position_mut(serum_market.quote_token_index)?; - quote_position.in_use_count = quote_position.in_use_count.saturating_sub(1); + quote_position.decrement_in_use(); // Deactivate the serum open orders account itself account.deactivate_serum3_orders(serum_market.market_index)?; diff --git a/programs/mango-v4/src/instructions/serum3_create_open_orders.rs b/programs/mango-v4/src/instructions/serum3_create_open_orders.rs index aaced94f3..5791b12cb 100644 --- a/programs/mango-v4/src/instructions/serum3_create_open_orders.rs +++ b/programs/mango-v4/src/instructions/serum3_create_open_orders.rs @@ -25,9 +25,9 @@ pub fn serum3_create_open_orders(ctx: Context) -> Result // stay permanently blocked. Otherwise users may end up in situations where // they can't settle a market because they don't have free token_account_map! let (quote_position, _, _) = account.ensure_token_position(serum_market.quote_token_index)?; - quote_position.in_use_count += 1; + quote_position.increment_in_use(); let (base_position, _, _) = account.ensure_token_position(serum_market.base_token_index)?; - base_position.in_use_count += 1; + base_position.increment_in_use(); Ok(()) } diff --git a/programs/mango-v4/src/instructions/token_conditional_swap_cancel.rs b/programs/mango-v4/src/instructions/token_conditional_swap_cancel.rs index ac7050bb2..c1fd88a0d 100644 --- a/programs/mango-v4/src/instructions/token_conditional_swap_cancel.rs +++ b/programs/mango-v4/src/instructions/token_conditional_swap_cancel.rs @@ -1,26 +1,22 @@ use anchor_lang::prelude::*; use crate::accounts_ix::*; -use crate::error::*; use crate::logs::TokenConditionalSwapCancelLog; use crate::state::*; #[allow(clippy::too_many_arguments)] pub fn token_conditional_swap_cancel( - ctx: Context, + ctx: Context, token_conditional_swap_index: usize, token_conditional_swap_id: u64, ) -> Result<()> { - require!( - ctx.accounts - .group - .load()? - .is_ix_enabled(IxGate::TokenConditionalSwapCancel), - MangoError::IxIsDisabled - ); + let mut buy_bank = ctx.accounts.buy_bank.load_mut()?; + let mut sell_bank = ctx.accounts.sell_bank.load_mut()?; let mut account = ctx.accounts.account.load_full_mut()?; let tcs = account.token_conditional_swap_mut_by_index(token_conditional_swap_index)?; + require_eq!(tcs.buy_token_index, buy_bank.token_index); + require_eq!(tcs.sell_token_index, sell_bank.token_index); // If the tcs is already inactive, this just is a noop if !tcs.has_data() { @@ -29,6 +25,7 @@ pub fn token_conditional_swap_cancel( require_eq!(tcs.id, token_conditional_swap_id); *tcs = TokenConditionalSwap::default(); + drop(tcs); emit!(TokenConditionalSwapCancelLog { mango_group: ctx.accounts.group.key(), @@ -36,5 +33,11 @@ pub fn token_conditional_swap_cancel( id: token_conditional_swap_id, }); + let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap(); + + // Free up any locks on token positions, possibly dust and deactivate them. + account.token_decrement_dust_deactivate(&mut buy_bank, now_ts, ctx.accounts.account.key())?; + account.token_decrement_dust_deactivate(&mut sell_bank, now_ts, ctx.accounts.account.key())?; + Ok(()) } diff --git a/programs/mango-v4/src/instructions/token_conditional_swap_create.rs b/programs/mango-v4/src/instructions/token_conditional_swap_create.rs index af918ec04..ced6fcb7a 100644 --- a/programs/mango-v4/src/instructions/token_conditional_swap_create.rs +++ b/programs/mango-v4/src/instructions/token_conditional_swap_create.rs @@ -18,6 +18,16 @@ pub fn token_conditional_swap_create( } let mut account = ctx.accounts.account.load_full_mut()?; + { + let buy_pos = account + .ensure_token_position(token_conditional_swap.buy_token_index)? + .0; + buy_pos.increment_in_use(); + let sell_pos = account + .ensure_token_position(token_conditional_swap.sell_token_index)? + .0; + sell_pos.increment_in_use(); + } let id = account.fixed.next_token_conditional_swap_id; account.fixed.next_token_conditional_swap_id = diff --git a/programs/mango-v4/src/instructions/token_conditional_swap_trigger.rs b/programs/mango-v4/src/instructions/token_conditional_swap_trigger.rs index 4ccfaa99e..10b2d671e 100644 --- a/programs/mango-v4/src/instructions/token_conditional_swap_trigger.rs +++ b/programs/mango-v4/src/instructions/token_conditional_swap_trigger.rs @@ -41,13 +41,30 @@ pub fn token_conditional_swap_trigger( require_eq!(tcs.id, token_conditional_swap_id); let buy_token_index = tcs.buy_token_index; let sell_token_index = tcs.sell_token_index; + let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap(); + let tcs_is_expired = tcs.is_expired(now_ts); + + // As a precaution, ensure that the liqee (and its health cache) will have an entry for both tokens: + // we will want to adjust their values later. This is already guaranteed by the in_use_count + // changes when the tcs was created. + liqee.ensure_token_position(buy_token_index)?; + liqee.ensure_token_position(sell_token_index)?; + + let mut liqee_health_cache = new_health_cache(&liqee.borrow(), &account_retriever) + .context("create liqee health cache")?; + let (buy_bank, buy_token_price, sell_bank_and_oracle_opt) = + account_retriever.banks_mut_and_oracles(buy_token_index, sell_token_index)?; + let (sell_bank, sell_token_price) = sell_bank_and_oracle_opt.unwrap(); // Possibly wipe the tcs and exit, if it's already expired - let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap(); - if tcs.is_expired(now_ts) { + if tcs_is_expired { let tcs = liqee.token_conditional_swap_mut_by_index(token_conditional_swap_index)?; *tcs = TokenConditionalSwap::default(); + // Release the hold on token positions and potentially close them + liqee.token_decrement_dust_deactivate(buy_bank, now_ts, liqee_key)?; + liqee.token_decrement_dust_deactivate(sell_bank, now_ts, liqee_key)?; + msg!("TokenConditionalSwap is expired, removing"); emit!(TokenConditionalSwapCancelLog { mango_group: ctx.accounts.group.key(), @@ -58,19 +75,8 @@ pub fn token_conditional_swap_trigger( return Ok(()); } - // Guarantee that the liqee health cache will have an entry for both tokens: - // we will want to adjust their values later. - liqee.ensure_token_position(buy_token_index)?; - liqee.ensure_token_position(sell_token_index)?; - - let mut liqee_health_cache = new_health_cache(&liqee.borrow(), &account_retriever) - .context("create liqee health cache")?; let liqee_pre_init_health = liqee.check_health_pre(&liqee_health_cache)?; - let (buy_bank, buy_token_price, sell_bank_and_oracle_opt) = - account_retriever.banks_mut_and_oracles(buy_token_index, sell_token_index)?; - let (sell_bank, sell_token_price) = sell_bank_and_oracle_opt.unwrap(); - let (liqee_buy_change, liqee_sell_change) = action( &mut liqor.borrow_mut(), liqor_key, @@ -421,6 +427,12 @@ fn action( } }; + if closed { + // Free up token position locks, maybe dusting and deactivating them + liqee.token_decrement_dust_deactivate(buy_bank, now_ts, liqee_key)?; + liqee.token_decrement_dust_deactivate(sell_bank, now_ts, liqee_key)?; + } + emit!(TokenConditionalSwapTriggerLog { mango_group: liqee.fixed.group, liqee: liqee_key, diff --git a/programs/mango-v4/src/lib.rs b/programs/mango-v4/src/lib.rs index 32805dad6..ceb2aebb8 100644 --- a/programs/mango-v4/src/lib.rs +++ b/programs/mango-v4/src/lib.rs @@ -1187,7 +1187,7 @@ pub mod mango_v4 { } pub fn token_conditional_swap_cancel( - ctx: Context, + ctx: Context, token_conditional_swap_index: u8, token_conditional_swap_id: u64, ) -> Result<()> { diff --git a/programs/mango-v4/src/state/bank.rs b/programs/mango-v4/src/state/bank.rs index f090a8ae2..aa799dd15 100644 --- a/programs/mango-v4/src/state/bank.rs +++ b/programs/mango-v4/src/state/bank.rs @@ -587,6 +587,19 @@ impl Bank { }) } + /// Returns true if the position remains active + pub fn dust_if_possible(&mut self, position: &mut TokenPosition, now_ts: u64) -> Result { + if position.is_in_use() { + return Ok(true); + } + let native = position.native(self); + if native >= 0 && native < 1 { + // Withdrawing 0 triggers the dusting check + return self.withdraw_without_fee(position, I80F48::ZERO, now_ts); + } + Ok(true) + } + /// Change a position without applying the loan origination fee pub fn change_without_fee( &mut self, @@ -971,7 +984,7 @@ mod tests { let mut account = TokenPosition { indexed_position: I80F48::ZERO, token_index: 0, - in_use_count: u8::from(is_in_use), + in_use_count: u16::from(is_in_use), cumulative_deposit_interest: 0.0, cumulative_borrow_interest: 0.0, previous_index: I80F48::ZERO, diff --git a/programs/mango-v4/src/state/mango_account.rs b/programs/mango-v4/src/state/mango_account.rs index d8f93fb29..3c9d4cedd 100644 --- a/programs/mango-v4/src/state/mango_account.rs +++ b/programs/mango-v4/src/state/mango_account.rs @@ -863,7 +863,7 @@ impl< raw_index: usize, mango_account_pubkey: Pubkey, ) { - let mango_group = self.fixed.deref_or_borrow().group; + let mango_group = self.fixed().group; let token_position = self.token_position_mut_by_raw_index(raw_index); assert!(token_position.in_use_count == 0); emit!(DeactivateTokenPositionLog { @@ -876,6 +876,32 @@ impl< self.token_position_mut_by_raw_index(raw_index).token_index = TokenIndex::MAX; } + /// Decrements the in_use_count for the token position for the bank. + /// + /// If it goes to 0, the position may be dusted (if between 0 and 1 native tokens) + /// and closed. + pub fn token_decrement_dust_deactivate( + &mut self, + bank: &mut crate::state::Bank, + now_ts: u64, + mango_account_pubkey: Pubkey, + ) -> Result<()> { + let token_result = self.token_position_mut(bank.token_index); + if token_result.is_anchor_error_with_code(MangoError::TokenPositionDoesNotExist.into()) { + // Already deactivated is ok + return Ok(()); + } + let (position, raw_index) = token_result?; + + position.decrement_in_use(); + let active = bank.dust_if_possible(position, now_ts)?; + if !active { + self.deactivate_token_position_and_log(raw_index, mango_account_pubkey); + } + + Ok(()) + } + // get mut Serum3Orders at raw_index pub fn serum3_orders_mut_by_raw_index(&mut self, raw_index: usize) -> &mut Serum3Orders { let offset = self.header().serum3_offset(raw_index); @@ -961,8 +987,8 @@ impl< *perp_position = PerpPosition::default(); perp_position.market_index = perp_market_index; - let mut settle_token_position = self.ensure_token_position(settle_token_index)?.0; - settle_token_position.in_use_count += 1; + let settle_token_position = self.ensure_token_position(settle_token_index)?.0; + settle_token_position.increment_in_use(); } } if let Some(raw_index) = raw_index_opt { @@ -979,8 +1005,8 @@ impl< ) -> Result<()> { self.perp_position_mut(perp_market_index)?.market_index = PerpMarketIndex::MAX; - let mut settle_token_position = self.token_position_mut(settle_token_index)?.0; - settle_token_position.in_use_count -= 1; + let settle_token_position = self.token_position_mut(settle_token_index)?.0; + settle_token_position.decrement_in_use(); Ok(()) } @@ -991,7 +1017,7 @@ impl< settle_token_index: TokenIndex, mango_account_pubkey: Pubkey, ) -> Result<()> { - let mango_group = self.fixed.deref_or_borrow().group; + let mango_group = self.fixed().group; let perp_position = self.perp_position_mut(perp_market_index)?; emit!(DeactivatePerpPositionLog { @@ -1007,8 +1033,8 @@ impl< perp_position.market_index = PerpMarketIndex::MAX; - let mut settle_token_position = self.token_position_mut(settle_token_index)?.0; - settle_token_position.in_use_count -= 1; + let settle_token_position = self.token_position_mut(settle_token_index)?.0; + settle_token_position.decrement_in_use(); Ok(()) } diff --git a/programs/mango-v4/src/state/mango_account_components.rs b/programs/mango-v4/src/state/mango_account_components.rs index a7adb164e..778885888 100644 --- a/programs/mango-v4/src/state/mango_account_components.rs +++ b/programs/mango-v4/src/state/mango_account_components.rs @@ -27,10 +27,10 @@ pub struct TokenPosition { pub token_index: TokenIndex, /// incremented when a market requires this position to stay alive - pub in_use_count: u8, + pub in_use_count: u16, #[derivative(Debug = "ignore")] - pub padding: [u8; 5], + pub padding: [u8; 4], // bookkeeping variable for onchain interest calculation // either deposit_index or borrow_index at last indexed_position change @@ -48,7 +48,7 @@ pub struct TokenPosition { const_assert_eq!( size_of::(), - 16 + 2 + 1 + 5 + 16 + 8 + 8 + 128 + 16 + 2 + 2 + 4 + 16 + 8 + 8 + 128 ); const_assert_eq!(size_of::(), 184); const_assert_eq!(size_of::() % 8, 0); @@ -99,6 +99,14 @@ impl TokenPosition { pub fn is_in_use(&self) -> bool { self.in_use_count > 0 } + + pub fn increment_in_use(&mut self) { + self.in_use_count += 1; // panic on overflow + } + + pub fn decrement_in_use(&mut self) { + self.in_use_count = self.in_use_count.saturating_sub(1); + } } #[zero_copy] diff --git a/programs/mango-v4/tests/program_test/mango_client.rs b/programs/mango-v4/tests/program_test/mango_client.rs index 1ae75eb76..c3deb879a 100644 --- a/programs/mango-v4/tests/program_test/mango_client.rs +++ b/programs/mango-v4/tests/program_test/mango_client.rs @@ -4216,7 +4216,7 @@ pub struct TokenConditionalSwapCancelInstruction { } #[async_trait::async_trait(?Send)] impl ClientInstruction for TokenConditionalSwapCancelInstruction { - type Accounts = mango_v4::accounts::AccountAndAuthority; + type Accounts = mango_v4::accounts::TokenConditionalSwapCancel; type Instruction = mango_v4::instruction::TokenConditionalSwapCancel; async fn to_instruction( &self, @@ -4232,11 +4232,19 @@ impl ClientInstruction for TokenConditionalSwapCancelInstruction { .load_mango_account(&self.account) .await .unwrap(); + let tcs = account.token_conditional_swap_by_id(self.id).unwrap().1; + + let buy_mint_info = + get_mint_info_by_token_index(&account_loader, &account, tcs.buy_token_index).await; + let sell_mint_info = + get_mint_info_by_token_index(&account_loader, &account, tcs.sell_token_index).await; let accounts = Self::Accounts { group: account.fixed.group, account: self.account, authority: self.owner.pubkey(), + buy_bank: buy_mint_info.first_bank(), + sell_bank: sell_mint_info.first_bank(), }; let instruction = make_instruction(program_id, &accounts, &instruction); diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index dc88f019a..3164e1c1b 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -3220,8 +3220,18 @@ export class MangoClient { group: Group, account: MangoAccount, tokenConditionalSwapIndex: number, - tokenConditionalSwapId: number, + tokenConditionalSwapId: BN, ): Promise { + const tcs = account + .tokenConditionalSwapsActive() + .find((tcs) => tcs.id.eq(tokenConditionalSwapId)); + if (!tcs) { + throw new Error('tcs with id not found'); + } + + const buyBank = group.banksMapByTokenIndex.get(tcs.buyTokenIndex)![0]; + const sellBank = group.banksMapByTokenIndex.get(tcs.sellTokenIndex)![0]; + const ix = await this.program.methods .tokenConditionalSwapCancel( tokenConditionalSwapIndex, @@ -3231,6 +3241,8 @@ export class MangoClient { group: group.publicKey, account: account.publicKey, authority: (this.program.provider as AnchorProvider).wallet.publicKey, + buyBank: buyBank.publicKey, + sellBank: sellBank.publicKey, }) .instruction(); @@ -3242,10 +3254,40 @@ export class MangoClient { liqee: MangoAccount, liqor: MangoAccount, tokenConditionalSwapIndex: number, - tokenConditionalSwapId: number, + tokenConditionalSwapId: BN, maxBuyTokenToLiqee: number, maxSellTokenToLiqor: number, ): Promise { + const tcs = liqee + .tokenConditionalSwapsActive() + .find((tcs) => tcs.id.eq(tokenConditionalSwapId)); + if (!tcs) { + throw new Error('tcs with id not found'); + } + + const buyBank = group.banksMapByTokenIndex.get(tcs.buyTokenIndex)![0]; + const sellBank = group.banksMapByTokenIndex.get(tcs.sellTokenIndex)![0]; + + const healthRemainingAccounts: PublicKey[] = + this.buildHealthRemainingAccounts( + group, + [liqor, liqee], + [buyBank, sellBank], + [], + ); + + const parsedHealthAccounts = healthRemainingAccounts.map( + (pk) => + ({ + pubkey: pk, + isWritable: + pk.equals(buyBank.publicKey) || pk.equals(sellBank.publicKey) + ? true + : false, + isSigner: false, + } as AccountMeta), + ); + const ix = await this.program.methods .tokenConditionalSwapTrigger( tokenConditionalSwapIndex, @@ -3260,6 +3302,7 @@ export class MangoClient { liqorAuthority: (this.program.provider as AnchorProvider).wallet .publicKey, }) + .remainingAccounts(parsedHealthAccounts) .instruction(); return await this.sendAndConfirmTransactionForGroup(group, [ix]); diff --git a/ts/client/src/mango_v4.ts b/ts/client/src/mango_v4.ts index d875c3997..2b3bcce74 100644 --- a/ts/client/src/mango_v4.ts +++ b/ts/client/src/mango_v4.ts @@ -2,6 +2,100 @@ export type MangoV4 = { "version": "0.18.0", "name": "mango_v4", "instructions": [ + { + "name": "admingTokenWithdrawFees", + "accounts": [ + { + "name": "group", + "isMut": false, + "isSigner": false, + "relations": [ + "admin" + ] + }, + { + "name": "bank", + "isMut": true, + "isSigner": false, + "relations": [ + "group", + "vault" + ] + }, + { + "name": "vault", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenAccount", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "admin", + "isMut": false, + "isSigner": true + } + ], + "args": [] + }, + { + "name": "adminPerpWithdrawFees", + "accounts": [ + { + "name": "group", + "isMut": false, + "isSigner": false, + "relations": [ + "admin" + ] + }, + { + "name": "perpMarket", + "isMut": true, + "isSigner": false, + "relations": [ + "group" + ] + }, + { + "name": "bank", + "isMut": true, + "isSigner": false, + "relations": [ + "group", + "vault" + ] + }, + { + "name": "vault", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenAccount", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "admin", + "isMut": false, + "isSigner": true + } + ], + "args": [] + }, { "name": "groupCreate", "accounts": [ @@ -4818,6 +4912,25 @@ export type MangoV4 = { "name": "authority", "isMut": false, "isSigner": true + }, + { + "name": "buyBank", + "isMut": true, + "isSigner": false, + "docs": [ + "The bank's token_index is checked at #1" + ], + "relations": [ + "group" + ] + }, + { + "name": "sellBank", + "isMut": true, + "isSigner": false, + "relations": [ + "group" + ] } ], "args": [ @@ -5263,12 +5376,25 @@ export type MangoV4 = { "name": "forceClose", "type": "u8" }, + { + "name": "padding", + "type": { + "array": [ + "u8", + 6 + ] + } + }, + { + "name": "feesWithdrawn", + "type": "u64" + }, { "name": "reserved", "type": { "array": [ "u8", - 2118 + 2104 ] } } @@ -6041,7 +6167,8 @@ export type MangoV4 = { { "name": "feesAccrued", "docs": [ - "Fees accrued in native quote currency" + "Fees accrued in native quote currency", + "these are increased when new fees are paid and decreased when perp_settle_fees is called" ], "type": { "defined": "I80F48" @@ -6050,7 +6177,8 @@ export type MangoV4 = { { "name": "feesSettled", "docs": [ - "Fees settled in native quote currency" + "Fees settled in native quote currency", + "these are increased when perp_settle_fees is called, and never decreased" ], "type": { "defined": "I80F48" @@ -6160,12 +6288,16 @@ export type MangoV4 = { "defined": "I80F48" } }, + { + "name": "feesWithdrawn", + "type": "u64" + }, { "name": "reserved", "type": { "array": [ "u8", - 1888 + 1880 ] } } @@ -6724,14 +6856,14 @@ export type MangoV4 = { "docs": [ "incremented when a market requires this position to stay alive" ], - "type": "u8" + "type": "u16" }, { "name": "padding", "type": { "array": [ "u8", - 5 + 4 ] } }, @@ -8353,6 +8485,42 @@ export type MangoV4 = { }, { "name": "TokenConditionalSwapCancel" + }, + { + "name": "OpenbookV2CancelOrder" + }, + { + "name": "OpenbookV2CloseOpenOrders" + }, + { + "name": "OpenbookV2CreateOpenOrders" + }, + { + "name": "OpenbookV2DeregisterMarket" + }, + { + "name": "OpenbookV2EditMarket" + }, + { + "name": "OpenbookV2LiqForceCancelOrders" + }, + { + "name": "OpenbookV2PlaceOrder" + }, + { + "name": "OpenbookV2PlaceTakeOrder" + }, + { + "name": "OpenbookV2RegisterMarket" + }, + { + "name": "OpenbookV2SettleFunds" + }, + { + "name": "AdminTokenWithdrawFees" + }, + { + "name": "AdminPerpWithdrawFees" } ] } @@ -10679,6 +10847,100 @@ export const IDL: MangoV4 = { "version": "0.18.0", "name": "mango_v4", "instructions": [ + { + "name": "admingTokenWithdrawFees", + "accounts": [ + { + "name": "group", + "isMut": false, + "isSigner": false, + "relations": [ + "admin" + ] + }, + { + "name": "bank", + "isMut": true, + "isSigner": false, + "relations": [ + "group", + "vault" + ] + }, + { + "name": "vault", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenAccount", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "admin", + "isMut": false, + "isSigner": true + } + ], + "args": [] + }, + { + "name": "adminPerpWithdrawFees", + "accounts": [ + { + "name": "group", + "isMut": false, + "isSigner": false, + "relations": [ + "admin" + ] + }, + { + "name": "perpMarket", + "isMut": true, + "isSigner": false, + "relations": [ + "group" + ] + }, + { + "name": "bank", + "isMut": true, + "isSigner": false, + "relations": [ + "group", + "vault" + ] + }, + { + "name": "vault", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenAccount", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "admin", + "isMut": false, + "isSigner": true + } + ], + "args": [] + }, { "name": "groupCreate", "accounts": [ @@ -15495,6 +15757,25 @@ export const IDL: MangoV4 = { "name": "authority", "isMut": false, "isSigner": true + }, + { + "name": "buyBank", + "isMut": true, + "isSigner": false, + "docs": [ + "The bank's token_index is checked at #1" + ], + "relations": [ + "group" + ] + }, + { + "name": "sellBank", + "isMut": true, + "isSigner": false, + "relations": [ + "group" + ] } ], "args": [ @@ -15940,12 +16221,25 @@ export const IDL: MangoV4 = { "name": "forceClose", "type": "u8" }, + { + "name": "padding", + "type": { + "array": [ + "u8", + 6 + ] + } + }, + { + "name": "feesWithdrawn", + "type": "u64" + }, { "name": "reserved", "type": { "array": [ "u8", - 2118 + 2104 ] } } @@ -16718,7 +17012,8 @@ export const IDL: MangoV4 = { { "name": "feesAccrued", "docs": [ - "Fees accrued in native quote currency" + "Fees accrued in native quote currency", + "these are increased when new fees are paid and decreased when perp_settle_fees is called" ], "type": { "defined": "I80F48" @@ -16727,7 +17022,8 @@ export const IDL: MangoV4 = { { "name": "feesSettled", "docs": [ - "Fees settled in native quote currency" + "Fees settled in native quote currency", + "these are increased when perp_settle_fees is called, and never decreased" ], "type": { "defined": "I80F48" @@ -16837,12 +17133,16 @@ export const IDL: MangoV4 = { "defined": "I80F48" } }, + { + "name": "feesWithdrawn", + "type": "u64" + }, { "name": "reserved", "type": { "array": [ "u8", - 1888 + 1880 ] } } @@ -17401,14 +17701,14 @@ export const IDL: MangoV4 = { "docs": [ "incremented when a market requires this position to stay alive" ], - "type": "u8" + "type": "u16" }, { "name": "padding", "type": { "array": [ "u8", - 5 + 4 ] } }, @@ -19030,6 +19330,42 @@ export const IDL: MangoV4 = { }, { "name": "TokenConditionalSwapCancel" + }, + { + "name": "OpenbookV2CancelOrder" + }, + { + "name": "OpenbookV2CloseOpenOrders" + }, + { + "name": "OpenbookV2CreateOpenOrders" + }, + { + "name": "OpenbookV2DeregisterMarket" + }, + { + "name": "OpenbookV2EditMarket" + }, + { + "name": "OpenbookV2LiqForceCancelOrders" + }, + { + "name": "OpenbookV2PlaceOrder" + }, + { + "name": "OpenbookV2PlaceTakeOrder" + }, + { + "name": "OpenbookV2RegisterMarket" + }, + { + "name": "OpenbookV2SettleFunds" + }, + { + "name": "AdminTokenWithdrawFees" + }, + { + "name": "AdminPerpWithdrawFees" } ] } From e09bfdea9b2a616853ae2aca24174884879afac2 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Mon, 17 Jul 2023 13:16:01 +0200 Subject: [PATCH 44/90] liquidator: Improve logging for tcs execution (#648) (cherry picked from commit cce2620de81090e7f8e306010b3519d16316e679) --- bin/liquidator/src/rebalance.rs | 13 +++++----- bin/liquidator/src/trigger_tcs.rs | 43 ++++++++++++++++++++----------- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/bin/liquidator/src/rebalance.rs b/bin/liquidator/src/rebalance.rs index 34f0fc11d..01cfb8b9b 100644 --- a/bin/liquidator/src/rebalance.rs +++ b/bin/liquidator/src/rebalance.rs @@ -269,15 +269,16 @@ impl Rebalancer { if builder.transaction_size_ok()? { return Ok((builder, full.clone())); } - log::trace!( - "full route from {} to {} does not fit in a tx, market_info.label {}", - full.input_mint, - full.output_mint, - full.route + trace!( + market_info_label = full + .route .market_infos .first() .map(|v| v.label.clone()) - .unwrap_or_else(|| "no market_info".into()) + .unwrap_or_else(|| "no market_info".into()), + %full.input_mint, + %full.output_mint, + "full route does not fit in a tx", ); if alternatives.is_empty() { diff --git a/bin/liquidator/src/trigger_tcs.rs b/bin/liquidator/src/trigger_tcs.rs index bb1c5de0f..b017bb72a 100644 --- a/bin/liquidator/src/trigger_tcs.rs +++ b/bin/liquidator/src/trigger_tcs.rs @@ -69,7 +69,7 @@ async fn tcs_is_interesting( } #[allow(clippy::too_many_arguments)] -pub async fn maybe_execute_token_conditional_swap_inner( +async fn maybe_execute_token_conditional_swap_inner( mango_client: &MangoClient, account_fetcher: &chain_data::AccountFetcher, token_swap_info: &token_swap_info::TokenSwapInfoUpdater, @@ -100,6 +100,19 @@ pub async fn maybe_execute_token_conditional_swap_inner( return Ok(false); } + execute_token_conditional_swap(mango_client, account_fetcher, pubkey, config, &liqee, tcs).await +} + +#[allow(clippy::too_many_arguments)] +#[instrument(skip_all, fields(%pubkey, tcs_id = tcs.id))] +async fn execute_token_conditional_swap( + mango_client: &MangoClient, + account_fetcher: &chain_data::AccountFetcher, + pubkey: &Pubkey, + config: &Config, + liqee: &MangoAccountValue, + tcs: &TokenConditionalSwap, +) -> anyhow::Result { let liqor_min_health_ratio = I80F48::from_num(config.min_health_ratio); // Compute the max viable swap (for liqor and liqee) and min it @@ -183,21 +196,20 @@ pub async fn maybe_execute_token_conditional_swap_inner( if swap_price > taker_price.to_num::() { trace!( - "skipping token conditional swap for: {pubkey}, id: {tcs_id}, \ - max_buy: {max_buy_token_to_liqee}, max_sell: {max_sell_token_to_liqor}, \ - because counter swap price: {swap_price} while taker price: {taker_price}", + max_buy = max_buy_token_to_liqee, + max_sell = max_sell_token_to_liqor, + jupiter_swap_price = %swap_price, + tcs_taker_price = %taker_price, + "skipping token conditional swap because of prices", ); return Ok(false); } } trace!( - "executing token conditional swap for: {}, with owner: {}, id: {}, max_buy: {}, max_sell: {}", - pubkey, - liqee.fixed.owner, - tcs_id, - max_buy_token_to_liqee, - max_sell_token_to_liqor, + max_buy = max_buy_token_to_liqee, + max_sell = max_sell_token_to_liqor, + "executing token conditional swap", ); let txsig = mango_client @@ -209,8 +221,8 @@ pub async fn maybe_execute_token_conditional_swap_inner( ) .await?; info!( - "Executed swap account {}, tcs index {}, tx sig {:?}", - pubkey, tcs_id, txsig + %txsig, + "Executed token conditional swap", ); let slot = account_fetcher.transaction_max_slot(&[txsig]).await?; @@ -222,13 +234,14 @@ pub async fn maybe_execute_token_conditional_swap_inner( ) .await { - info!("could not refresh after tcs: {}", e); + info!(%txsig, "could not refresh after tcs execution: {}", e); } Ok(true) } #[allow(clippy::too_many_arguments)] +#[instrument(skip_all, fields(%pubkey, tcs_id))] pub async fn remove_expired_token_conditional_swap( mango_client: &MangoClient, pubkey: &Pubkey, @@ -239,8 +252,8 @@ pub async fn remove_expired_token_conditional_swap( .token_conditional_swap_trigger((pubkey, &liqee), tcs_id, 0, 0) .await?; info!( - "Removed expired token conditional swap account {}, tcs index {}, tx sig {:?}", - pubkey, tcs_id, txsig + %txsig, + "Removed expired token conditional swap", ); Ok(true) From f3a7c5ca14b8c84707493b95c39704521a31a3e4 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Mon, 17 Jul 2023 15:23:47 +0200 Subject: [PATCH 45/90] make consistent with other sim functions Signed-off-by: microwavedcola1 --- ts/client/src/accounts/mangoAccount.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ts/client/src/accounts/mangoAccount.ts b/ts/client/src/accounts/mangoAccount.ts index dd0ccd5fe..b0649ff65 100644 --- a/ts/client/src/accounts/mangoAccount.ts +++ b/ts/client/src/accounts/mangoAccount.ts @@ -957,6 +957,7 @@ export class MangoAccount { group: Group, perpMarketIndex: PerpMarketIndex, size: number, + healthType: HealthType = HealthType.init, ): number { const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex); const pp = this.getPerpPosition(perpMarket.perpMarketIndex); @@ -970,7 +971,7 @@ export class MangoAccount { PerpOrderSide.bid, perpMarket.uiBaseToLots(size), perpMarket.price, - HealthType.init, + healthType, ) .toNumber(); } @@ -979,6 +980,7 @@ export class MangoAccount { group: Group, perpMarketIndex: PerpMarketIndex, size: number, + healthType: HealthType = HealthType.init, ): number { const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex); const pp = this.getPerpPosition(perpMarket.perpMarketIndex); @@ -992,7 +994,7 @@ export class MangoAccount { PerpOrderSide.ask, perpMarket.uiBaseToLots(size), perpMarket.price, - HealthType.init, + healthType, ) .toNumber(); } From 87b8048bed95dcdcc7f3f2ce1365c7d171b3d461 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Mon, 17 Jul 2023 15:24:28 +0200 Subject: [PATCH 46/90] v0.17.25 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 470f600c9..2aa9111b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.17.24", + "version": "0.17.25", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From 214ffafb1f4b515d05dd45539fe0b25b83298a7d Mon Sep 17 00:00:00 2001 From: tjs Date: Wed, 19 Jul 2023 14:57:29 -0400 Subject: [PATCH 47/90] expose additional bank params --- ts/client/src/accounts/bank.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ts/client/src/accounts/bank.ts b/ts/client/src/accounts/bank.ts index e24ae651d..45f78df24 100644 --- a/ts/client/src/accounts/bank.ts +++ b/ts/client/src/accounts/bank.ts @@ -202,8 +202,8 @@ export class Bank implements BankForHealth { initLiabWeight: I80F48Dto, liquidationFee: I80F48Dto, dust: I80F48Dto, - flashLoanTokenAccountInitial: BN, - flashLoanApprovedAmount: BN, + public flashLoanTokenAccountInitial: BN, + public flashLoanApprovedAmount: BN, public tokenIndex: TokenIndex, public mintDecimals: number, public bankNum: number, From 53aa6851962beb655bb18dc398775e28f7b7f1b1 Mon Sep 17 00:00:00 2001 From: tjs Date: Thu, 20 Jul 2023 13:12:47 -0400 Subject: [PATCH 48/90] v0.17.26 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2aa9111b3..50bd9d854 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.17.25", + "version": "0.17.26", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From cad6306bf6d51f5624b2df1edbbf3c55ead16f57 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 21 Jul 2023 13:45:07 +0200 Subject: [PATCH 49/90] examples Signed-off-by: microwavedcola1 --- programs/mango-v4/src/lib.rs | 1 + ts/client/src/client.ts | 40 ++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/programs/mango-v4/src/lib.rs b/programs/mango-v4/src/lib.rs index ceb2aebb8..5307dfdb2 100644 --- a/programs/mango-v4/src/lib.rs +++ b/programs/mango-v4/src/lib.rs @@ -43,6 +43,7 @@ pub mod mango_v4 { use super::*; use error::*; + // TODO: fix typo in name pub fn adming_token_withdraw_fees(ctx: Context) -> Result<()> { #[cfg(feature = "enable-gpl")] instructions::admin_token_withdraw_fees(ctx)?; diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index 9f85c766b..b87660957 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -159,6 +159,46 @@ export class MangoClient { }); } + public async adminTokenWithdrawFees( + group: Group, + bank: Bank, + tokenAccountPk: PublicKey, + ): Promise { + const admin = (this.program.provider as AnchorProvider).wallet.publicKey; + const ix = await this.program.methods + .admingTokenWithdrawFees() + .accounts({ + group: group.publicKey, + bank: bank.publicKey, + vault: bank.vault, + tokenAccount: tokenAccountPk, + admin, + }) + .instruction(); + return await this.sendAndConfirmTransaction([ix]); + } + + public async adminPerpWithdrawFees( + group: Group, + perpMarket: PerpMarket, + tokenAccountPk: PublicKey, + ): Promise { + const bank = group.getFirstBankByTokenIndex(perpMarket.settleTokenIndex); + const admin = (this.program.provider as AnchorProvider).wallet.publicKey; + const ix = await this.program.methods + .adminPerpWithdrawFees() + .accounts({ + group: group.publicKey, + perpMarket: perpMarket.publicKey, + bank: bank.publicKey, + vault: bank.vault, + tokenAccount: tokenAccountPk, + admin, + }) + .instruction(); + return await this.sendAndConfirmTransaction([ix]); + } + // Group public async groupCreate( groupNum: number, From d162c612d07d7311559de54bffb18ace0436bc98 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Tue, 25 Jul 2023 13:35:25 +0200 Subject: [PATCH 50/90] allow pdas as owners Signed-off-by: microwavedcola1 --- ts/client/src/client.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index d4cd93cb6..96d21e579 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -1151,6 +1151,7 @@ export class MangoClient { const tokenAccountPk = await getAssociatedTokenAddress( mintPk, mangoAccount.owner, + true, ); let wrappedSolAccount: Keypair | undefined; @@ -1246,6 +1247,7 @@ export class MangoClient { const tokenAccountPk = await getAssociatedTokenAddress( bank.mint, mangoAccount.owner, + true, ); // ensure withdraws don't fail with missing ATAs @@ -3041,6 +3043,7 @@ export class MangoClient { const inputTokenAccountPk = await getAssociatedTokenAddress( inputBank.mint, swapExecutingWallet, + true, ); const inputTokenAccExists = await this.program.provider.connection.getAccountInfo( @@ -3060,6 +3063,7 @@ export class MangoClient { const outputTokenAccountPk = await getAssociatedTokenAddress( outputBank.mint, swapExecutingWallet, + true, ); const outputTokenAccExists = await this.program.provider.connection.getAccountInfo( From 900bb02e1a24b5498f5340f0ea01c18b7c828f48 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Tue, 25 Jul 2023 13:36:01 +0200 Subject: [PATCH 51/90] v0.17.27 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 50bd9d854..36268088f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.17.26", + "version": "0.17.27", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From c24e77d3a4cf96af592ee0f5b47887545a6c7f27 Mon Sep 17 00:00:00 2001 From: microwavedcola1 <89031858+microwavedcola1@users.noreply.github.com> Date: Fri, 28 Jul 2023 08:26:34 +0200 Subject: [PATCH 52/90] wip: extend tcs helpers (#649) * extend tcs helpers Signed-off-by: microwavedcola1 * comment Signed-off-by: microwavedcola1 * add slippage helper Signed-off-by: microwavedcola1 --------- Signed-off-by: microwavedcola1 --- mango_v4.json | 790 ++------- .../scripts/archive/conditional-swaps.ts | 123 +- ts/client/src/accounts/group.ts | 40 +- ts/client/src/accounts/mangoAccount.ts | 101 +- ts/client/src/client.ts | 157 +- ts/client/src/mango_v4.ts | 1580 +++-------------- ts/client/src/numbers/numbers.spec.ts | 17 +- ts/client/src/risk.ts | 35 +- ts/client/src/utils.ts | 49 + 9 files changed, 858 insertions(+), 2034 deletions(-) diff --git a/mango_v4.json b/mango_v4.json index 80fb3e2f5..719ac923f 100644 --- a/mango_v4.json +++ b/mango_v4.json @@ -8,19 +8,12 @@ { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "bank", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "vault" - ] + "isSigner": false }, { "name": "vault", @@ -51,27 +44,17 @@ { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "bank", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "vault" - ] + "isSigner": false }, { "name": "vault", @@ -194,10 +177,7 @@ { "name": "group", "isMut": true, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -292,11 +272,7 @@ { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "insurance_vault", - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -353,11 +329,7 @@ { "name": "group", "isMut": true, - "isSigner": false, - "relations": [ - "admin", - "insurance_vault" - ] + "isSigner": false }, { "name": "admin", @@ -388,10 +360,7 @@ { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -729,10 +698,7 @@ { "name": "mintInfo", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "oracle", @@ -897,10 +863,7 @@ { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -915,11 +878,7 @@ { "name": "existingBank", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "mint" - ] + "isSigner": false }, { "name": "bank", @@ -982,11 +941,7 @@ { "name": "mintInfo", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "mint" - ] + "isSigner": false }, { "name": "payer", @@ -1026,10 +981,7 @@ { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -1039,10 +991,7 @@ { "name": "mintInfo", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "dustVault", @@ -1073,11 +1022,7 @@ { "name": "mintInfo", "isMut": false, - "isSigner": false, - "relations": [ - "oracle", - "group" - ] + "isSigner": false }, { "name": "oracle", @@ -1183,11 +1128,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "owner" - ] + "isSigner": false }, { "name": "owner", @@ -1235,11 +1176,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "owner" - ] + "isSigner": false }, { "name": "owner", @@ -1291,11 +1228,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "owner" - ] + "isSigner": false }, { "name": "owner", @@ -1329,10 +1262,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "admin", @@ -1358,11 +1288,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "owner" - ] + "isSigner": false }, { "name": "owner", @@ -1398,10 +1324,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -1411,18 +1334,12 @@ { "name": "daoAccount", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "mngoBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "mngoOracle", @@ -1432,10 +1349,7 @@ { "name": "feesBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "feesOracle", @@ -1456,10 +1370,7 @@ { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "oracle", @@ -1522,10 +1433,7 @@ { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -1535,10 +1443,7 @@ { "name": "oracle", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "solDestination", @@ -1559,10 +1464,7 @@ { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -1572,10 +1474,7 @@ { "name": "oracle", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [ @@ -1598,11 +1497,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "owner" - ] + "isSigner": false }, { "name": "owner", @@ -1612,12 +1507,7 @@ { "name": "bank", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "vault", - "oracle" - ] + "isSigner": false }, { "name": "vault", @@ -1667,20 +1557,12 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "bank", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "vault", - "oracle" - ] + "isSigner": false }, { "name": "vault", @@ -1730,11 +1612,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "owner" - ] + "isSigner": false }, { "name": "owner", @@ -1744,12 +1622,7 @@ { "name": "bank", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "vault", - "oracle" - ] + "isSigner": false }, { "name": "vault", @@ -1898,10 +1771,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [] @@ -1928,10 +1798,7 @@ { "name": "group", "isMut": true, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -1999,18 +1866,12 @@ { "name": "quoteBank", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "baseBank", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "payer", @@ -2050,10 +1911,7 @@ { "name": "market", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [ @@ -2083,10 +1941,7 @@ { "name": "group", "isMut": true, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -2096,18 +1951,12 @@ { "name": "serumMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "indexReservation", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "solDestination", @@ -2133,10 +1982,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -2146,12 +1992,7 @@ { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -2216,10 +2057,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -2229,12 +2067,7 @@ { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -2270,10 +2103,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -2288,12 +2118,7 @@ { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -2349,9 +2174,6 @@ "isSigner": false, "docs": [ "The bank that pays for the order, if necessary" - ], - "relations": [ - "group" ] }, { @@ -2425,10 +2247,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -2443,12 +2262,7 @@ { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -2500,10 +2314,7 @@ { "name": "account", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -2518,12 +2329,7 @@ { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -2575,10 +2381,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -2593,12 +2396,7 @@ { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -2631,10 +2429,7 @@ { "name": "quoteBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "quoteVault", @@ -2644,10 +2439,7 @@ { "name": "baseBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "baseVault", @@ -2680,10 +2472,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -2698,12 +2487,7 @@ { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -2736,10 +2520,7 @@ { "name": "quoteBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "quoteVault", @@ -2749,10 +2530,7 @@ { "name": "baseBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "baseVault", @@ -2800,10 +2578,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "openOrders", @@ -2813,12 +2588,7 @@ { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -2863,10 +2633,7 @@ { "name": "quoteBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "quoteVault", @@ -2876,10 +2643,7 @@ { "name": "baseBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "baseVault", @@ -2910,10 +2674,7 @@ { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -2923,10 +2684,7 @@ { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [ @@ -2952,18 +2710,12 @@ { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "insurance_vault" - ] + "isSigner": false }, { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -2973,18 +2725,12 @@ { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liabMintInfo", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "quoteVault", @@ -3022,10 +2768,7 @@ { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -3035,10 +2778,7 @@ { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [ @@ -3069,10 +2809,7 @@ { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -3082,10 +2819,7 @@ { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [ @@ -3109,18 +2843,12 @@ { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "insurance_vault" - ] + "isSigner": false }, { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -3130,18 +2858,12 @@ { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liabMintInfo", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "quoteVault", @@ -3179,10 +2901,7 @@ { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -3377,10 +3096,7 @@ { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "oracle", @@ -3581,10 +3297,7 @@ { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -3594,13 +3307,7 @@ { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks", - "event_queue" - ] + "isSigner": false }, { "name": "bids", @@ -3641,10 +3348,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -3654,10 +3358,7 @@ { "name": "perpMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [] @@ -3673,10 +3374,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -3686,14 +3384,7 @@ { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks", - "event_queue", - "oracle" - ] + "isSigner": false }, { "name": "bids", @@ -3773,10 +3464,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -3786,14 +3474,7 @@ { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks", - "event_queue", - "oracle" - ] + "isSigner": false }, { "name": "bids", @@ -3879,10 +3560,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -3892,14 +3570,7 @@ { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks", - "event_queue", - "oracle" - ] + "isSigner": false }, { "name": "bids", @@ -3987,10 +3658,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -4000,14 +3668,7 @@ { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks", - "event_queue", - "oracle" - ] + "isSigner": false }, { "name": "bids", @@ -4101,10 +3762,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -4114,12 +3772,7 @@ { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks" - ] + "isSigner": false }, { "name": "bids", @@ -4150,10 +3803,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -4163,12 +3813,7 @@ { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks" - ] + "isSigner": false }, { "name": "bids", @@ -4199,10 +3844,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -4212,12 +3854,7 @@ { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks" - ] + "isSigner": false }, { "name": "bids", @@ -4248,10 +3885,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -4261,12 +3895,7 @@ { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks" - ] + "isSigner": false }, { "name": "bids", @@ -4305,11 +3934,7 @@ { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "event_queue" - ] + "isSigner": false }, { "name": "eventQueue", @@ -4335,13 +3960,7 @@ { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks", - "oracle" - ] + "isSigner": false }, { "name": "bids", @@ -4372,10 +3991,7 @@ { "name": "settler", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "settlerOwner", @@ -4385,27 +4001,17 @@ { "name": "perpMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "oracle" - ] + "isSigner": false }, { "name": "accountA", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "accountB", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "oracle", @@ -4415,10 +4021,7 @@ { "name": "settleBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "settleOracle", @@ -4439,27 +4042,17 @@ { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "oracle" - ] + "isSigner": false }, { "name": "accountA", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "accountB", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "oracle", @@ -4480,19 +4073,12 @@ { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "oracle" - ] + "isSigner": false }, { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "oracle", @@ -4502,10 +4088,7 @@ { "name": "settleBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "settleOracle", @@ -4531,11 +4114,7 @@ { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "oracle" - ] + "isSigner": false }, { "name": "oracle", @@ -4545,10 +4124,7 @@ { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -4558,18 +4134,12 @@ { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "settleBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "settleVault", @@ -4604,20 +4174,12 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks" - ] + "isSigner": false }, { "name": "bids", @@ -4643,18 +4205,12 @@ { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "insurance_vault" - ] + "isSigner": false }, { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -4664,19 +4220,12 @@ { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "oracle" - ] + "isSigner": false }, { "name": "oracle", @@ -4686,10 +4235,7 @@ { "name": "settleBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "settleVault", @@ -4725,18 +4271,12 @@ { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "insurance_vault" - ] + "isSigner": false }, { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -4746,19 +4286,12 @@ { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "oracle" - ] + "isSigner": false }, { "name": "oracle", @@ -4768,10 +4301,7 @@ { "name": "settleBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "settleVault", @@ -4791,10 +4321,7 @@ { "name": "insuranceBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "insuranceBankVault", @@ -4830,10 +4357,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "authority", @@ -4843,18 +4367,12 @@ { "name": "buyBank", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "sellBank", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [ @@ -4903,10 +4421,7 @@ { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "authority", @@ -4919,18 +4434,12 @@ "isSigner": false, "docs": [ "The bank's token_index is checked at #1" - ], - "relations": [ - "group" ] }, { "name": "sellBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [ @@ -4955,18 +4464,12 @@ { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorAuthority", @@ -4999,10 +4502,7 @@ { "name": "group", "isMut": true, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -5028,10 +4528,7 @@ { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -5073,10 +4570,7 @@ { "name": "account", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [] diff --git a/ts/client/scripts/archive/conditional-swaps.ts b/ts/client/scripts/archive/conditional-swaps.ts index 06fe87e86..435b8f21f 100644 --- a/ts/client/scripts/archive/conditional-swaps.ts +++ b/ts/client/scripts/archive/conditional-swaps.ts @@ -1,6 +1,7 @@ import { AnchorProvider, Wallet } from '@coral-xyz/anchor'; -import { Connection, Keypair, PublicKey } from '@solana/web3.js'; +import { Connection, Keypair } from '@solana/web3.js'; import fs from 'fs'; +import { MangoAccount } from '../../src/accounts/mangoAccount'; import { MangoClient } from '../../src/client'; import { MANGO_V4_ID } from '../../src/constants'; @@ -21,60 +22,57 @@ async function main(): Promise { // mainnet // - const client = await MangoClient.connect( - userProvider, - 'mainnet-beta', - MANGO_V4_ID['mainnet-beta'], - { - idsSource: 'get-program-accounts', - }, - ); - const group = await client.getGroup( - new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'), - ); - console.log( - await client.getMangoAccountForOwner( - group, - new PublicKey('v3mmtZ8JjXkaAbRRMBiNsjJF1rnN3qsMQqRLMk7Nz2C'), - 3, - ), - ); - console.log( - await client.getMangoAccountsForDelegate( - group, - new PublicKey('5P9rHX22jb3MDq46VgeaHZ2TxQDKezPxsxNX3MaXyHwT'), - ), - ); + // const client = await MangoClient.connect( + // userProvider, + // 'mainnet-beta', + // MANGO_V4_ID['mainnet-beta'], + // { + // idsSource: 'get-program-accounts', + // }, + // ); + // const group = await client.getGroup( + // new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'), + // ); + // console.log( + // await client.getMangoAccountForOwner( + // group, + // new PublicKey('v3mmtZ8JjXkaAbRRMBiNsjJF1rnN3qsMQqRLMk7Nz2C'), + // 3, + // ), + // ); + // console.log( + // await client.getMangoAccountsForDelegate( + // group, + // new PublicKey('5P9rHX22jb3MDq46VgeaHZ2TxQDKezPxsxNX3MaXyHwT'), + // ), + // ); // // devnet // - // const client = await MangoClient.connect( - // userProvider, - // 'devnet', - // MANGO_V4_ID['devnet'], - // { - // idsSource: 'get-program-accounts', - // }, - // ); + const client = await MangoClient.connect( + userProvider, + 'devnet', + MANGO_V4_ID['devnet'], + { + idsSource: 'get-program-accounts', + }, + ); - // const admin = Keypair.fromSecretKey( - // Buffer.from( - // JSON.parse(fs.readFileSync(process.env.ADMIN_KEYPAIR!, 'utf-8')), - // ), - // ); - // const group = await client.getGroupForCreator(admin.publicKey, 23); - // const mangoAccount = (await client.getMangoAccountForOwner( - // group, - // user.publicKey, - // 0, - // )) as MangoAccount; - // console.log(mangoAccount.tokenConditionalSwaps.length); - // console.log(mangoAccount.tokenConditionalSwaps); - // console.log(mangoAccount.tokenConditionalSwaps[1]); - // console.log(mangoAccount.tokenConditionalSwaps[0]); + const admin = Keypair.fromSecretKey( + Buffer.from( + JSON.parse(fs.readFileSync(process.env.ADMIN_KEYPAIR!, 'utf-8')), + ), + ); + const group = await client.getGroupForCreator(admin.publicKey, 37); + let mangoAccount = (await client.getMangoAccountForOwner( + group, + user.publicKey, + 0, + )) as MangoAccount; + let sig; // let sig = await client.accountExpandV2( // group, // mangoAccount, @@ -84,24 +82,27 @@ async function main(): Promise { // 32, // 8, // ); - // console.log(sig); - // mangoAccount = await client.getOrCreateMangoAccount(group); + console.log(sig); - // let sig = await client.tokenConditionalSwapCreate( + // sig = await client.tokenConditionalSwapStopLoss( // group, // mangoAccount, - // 0 as TokenIndex, - // 1 as TokenIndex, - // 0, - // 73, - // 81, - // TokenConditionalSwapPriceThresholdType.priceOverThreshold, - // 99, - // 101, - // true, - // true, + // group.getFirstBankByTokenIndex(0 as TokenIndex).mint, + // group.getFirstBankByTokenIndex(1 as TokenIndex).mint, + // 1, + // null, + // 25, + // 24, + // 1, // ); // console.log(sig); + + mangoAccount = (await client.getMangoAccountForOwner( + group, + user.publicKey, + 0, + )) as MangoAccount; + console.log(mangoAccount.tokenConditionalSwaps[2].toString(group)); } catch (error) { console.log(error); } diff --git a/ts/client/src/accounts/group.ts b/ts/client/src/accounts/group.ts index df3bc7b0b..0b9461361 100644 --- a/ts/client/src/accounts/group.ts +++ b/ts/client/src/accounts/group.ts @@ -14,7 +14,8 @@ import { MangoClient } from '../client'; import { OPENBOOK_PROGRAM_ID } from '../constants'; import { Id } from '../ids'; import { I80F48, ONE_I80F48 } from '../numbers/I80F48'; -import { toNative, toNativeI80F48, toUiDecimals } from '../utils'; +import { PriceImpact, computePriceImpactOnJup } from '../risk'; +import { buildFetch, toNative, toNativeI80F48, toUiDecimals } from '../utils'; import { Bank, MintInfo, TokenIndex } from './bank'; import { OracleProvider, @@ -80,6 +81,7 @@ export class Group { new Map(), // mintInfosMapByTokenIndex new Map(), // mintInfosMapByMint new Map(), // vaultAmountsMap + [], ); } @@ -115,6 +117,7 @@ export class Group { public mintInfosMapByTokenIndex: Map, public mintInfosMapByMint: Map, public vaultAmountsMap: Map, + public pis: PriceImpact[], ) {} public async reloadAll(client: MangoClient): Promise { @@ -122,6 +125,7 @@ export class Group { // console.time('group.reload'); await Promise.all([ + this.reloadPriceImpactData(), this.reloadAlts(client), this.reloadBanks(client, ids).then(() => Promise.all([ @@ -140,6 +144,27 @@ export class Group { // console.timeEnd('group.reload'); } + public async reloadPriceImpactData(): Promise { + try { + this.pis = await ( + await ( + await buildFetch() + )( + `https://api.mngo.cloud/data/v4/risk/listed-tokens-one-week-price-impacts`, + { + mode: 'cors', + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, + }, + ) + ).json(); + } catch (error) { + console.log(`Error while loading price impact: ${error}`); + } + } + public async reloadAlts(client: MangoClient): Promise { const alts = await Promise.all( this.addressLookupTables @@ -480,6 +505,19 @@ export class Group { return banks[0]; } + /** + * Returns a price impact, between 0 to 1 for a token, + * returns -1 if data is bad + */ + public getPriceImpactByTokenIndex( + tokenIndex: TokenIndex, + usdcAmountUi: number, + ): number { + const bank = this.getFirstBankByTokenIndex(tokenIndex); + const pisBps = computePriceImpactOnJup(this.pis, usdcAmountUi, bank.name); + return (pisBps * 100) / 10000; + } + public getFirstBankForMngo(): Bank { return this.getFirstBankByTokenIndex(this.mngoTokenIndex); } diff --git a/ts/client/src/accounts/mangoAccount.ts b/ts/client/src/accounts/mangoAccount.ts index c446898d6..136408e31 100644 --- a/ts/client/src/accounts/mangoAccount.ts +++ b/ts/client/src/accounts/mangoAccount.ts @@ -5,7 +5,13 @@ import { AccountInfo, PublicKey, TransactionSignature } 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'; -import { toNativeI80F48, toUiDecimals, toUiDecimalsForQuote } from '../utils'; +import { + U64_MAX_BN, + roundTo5, + toNativeI80F48, + toUiDecimals, + toUiDecimalsForQuote, +} from '../utils'; import { Bank, TokenIndex } from './bank'; import { Group } from './group'; import { HealthCache } from './healthCache'; @@ -1808,6 +1814,99 @@ export class TokenConditionalSwap { public allowCreatingDeposits: boolean, public allowCreatingBorrows: boolean, ) {} + + getMaxBuyUi(group: Group): number { + const buyBank = this.getBuyToken(group); + return toUiDecimals(this.maxBuy, buyBank.mintDecimals); + } + + getMaxSellUi(group: Group): number { + const sellBank = this.getSellToken(group); + return toUiDecimals(this.maxSell, sellBank.mintDecimals); + } + + getBoughtUi(group: Group): number { + const buyBank = this.getBuyToken(group); + return toUiDecimals(this.bought, buyBank.mintDecimals); + } + + getSoldUi(group: Group): number { + const sellBank = this.getSellToken(group); + return toUiDecimals(this.sold, sellBank.mintDecimals); + } + + getExpiryTimestamp(): number { + return this.expiryTimestamp.toNumber(); + } + + private priceLimitToUi( + group: Group, + sellTokenPerBuyTokenNative: number, + ): number { + const buyBank = this.getBuyToken(group); + const sellBank = this.getSellToken(group); + const sellTokenPerBuyTokenUi = toUiDecimals( + sellTokenPerBuyTokenNative, + sellBank.mintDecimals - buyBank.mintDecimals, + ); + + // 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.maxSell.eq(U64_MAX_BN)) { + return roundTo5(sellTokenPerBuyTokenUi); + } + + // Stop loss / take profit + const buyTokenPerSellTokenUi = 1 / sellTokenPerBuyTokenUi; + return roundTo5(buyTokenPerSellTokenUi); + } + + getPriceLowerLimitUi(group: Group): number { + return this.priceLimitToUi(group, this.priceLowerLimit); + } + + getPriceUpperLimitUi(group: Group): number { + return this.priceLimitToUi(group, this.priceUpperLimit); + } + + getPricePremium(): number { + return this.pricePremiumFraction * 100; + } + + getBuyToken(group: Group): Bank { + return group.getFirstBankByTokenIndex(this.buyTokenIndex); + } + + getSellToken(group: Group): Bank { + return group.getFirstBankByTokenIndex(this.sellTokenIndex); + } + + getAllowCreatingDeposits(): boolean { + return this.allowCreatingDeposits; + } + + getAllowCreatingBorrows(): boolean { + return this.allowCreatingBorrows; + } + + toString(group: Group): string { + return `getMaxBuy ${this.getMaxBuyUi( + group, + )}, getMaxSell ${this.getMaxSellUi( + group, + )}, getMaxSellUi ${this.getMaxSellUi( + group, + )}, getPriceLowerLimitUi ${this.getPriceLowerLimitUi( + group, + )}, getPriceUpperLimitUi ${this.getPriceUpperLimitUi( + group, + )}, this.priceUpperLimit ${ + this.priceUpperLimit + }, getPricePremium ${this.getPricePremium()}`; + } } export class TokenConditionalSwapDto { diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index b87660957..2d11c22cc 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -761,7 +761,28 @@ export class MangoClient { perpOoCount: number, tokenConditionalSwapCount: number, ): Promise { - const ix = await this.program.methods + const ix = await this.accountExpandV2Ix( + group, + account, + tokenCount, + serum3Count, + perpCount, + perpOoCount, + tokenConditionalSwapCount, + ); + return await this.sendAndConfirmTransactionForGroup(group, [ix]); + } + + public async accountExpandV2Ix( + group: Group, + account: MangoAccount, + tokenCount: number, + serum3Count: number, + perpCount: number, + perpOoCount: number, + tokenConditionalSwapCount: number, + ): Promise { + return await this.program.methods .accountExpandV2( tokenCount, serum3Count, @@ -776,7 +797,6 @@ export class MangoClient { payer: (this.program.provider as AnchorProvider).wallet.publicKey, }) .instruction(); - return await this.sendAndConfirmTransactionForGroup(group, [ix]); } public async editMangoAccount( @@ -3345,6 +3365,122 @@ export class MangoClient { return await this.sendAndConfirmTransactionForGroup(group, [ix]); } + public async tokenConditionalSwapStopLoss( + group: Group, + account: MangoAccount, + buyMintPk: PublicKey, + sellMintPk: PublicKey, + maxSell: number, + expiryTimestamp: number | null, + priceLowerLimit: number, // Note: priceLowerLimit should be higher than priceUpperLimit + priceUpperLimit: number, + pricePremiumFraction: number, + ): Promise { + const buyBank: Bank = group.getFirstBankByMint(buyMintPk); + const sellBank: Bank = group.getFirstBankByMint(sellMintPk); + priceLowerLimit = + (1 / priceLowerLimit) * + Math.pow(10, sellBank.mintDecimals - buyBank.mintDecimals); + priceUpperLimit = + (1 / priceUpperLimit) * + Math.pow(10, sellBank.mintDecimals - buyBank.mintDecimals); + + const ix = await this.program.methods + .tokenConditionalSwapCreate( + U64_MAX_BN, + toNative(maxSell, sellBank.mintDecimals), + expiryTimestamp !== null ? new BN(expiryTimestamp) : U64_MAX_BN, + priceLowerLimit, + priceUpperLimit, + pricePremiumFraction / 100, + true, + false, + ) + .accounts({ + group: group.publicKey, + account: account.publicKey, + authority: (this.program.provider as AnchorProvider).wallet.publicKey, + buyBank: buyBank.publicKey, + sellBank: sellBank.publicKey, + }) + .instruction(); + + const ixs = [ix]; + if (account.tokenConditionalSwaps.length == 0) { + ixs.push( + await this.accountExpandV2Ix( + group, + account, + account.tokens.length, + account.serum3.length, + account.perps.length, + account.perpOpenOrders.length, + 8, + ), + ); + } + + return await this.sendAndConfirmTransactionForGroup(group, ixs); + } + + public async tokenConditionalSwapBuyLimit( + group: Group, + account: MangoAccount, + buyMintPk: PublicKey, + sellMintPk: PublicKey, + maxBuy: number, + expiryTimestamp: number | null, + priceLowerLimit: number, // Note: priceLowerLimit should be lower than priceUpperLimit + priceUpperLimit: number, + pricePremiumFraction: number, + ): Promise { + const buyBank: Bank = group.getFirstBankByMint(buyMintPk); + const sellBank: Bank = group.getFirstBankByMint(sellMintPk); + priceLowerLimit = + priceLowerLimit * + Math.pow(10, sellBank.mintDecimals - buyBank.mintDecimals); + priceUpperLimit = + priceUpperLimit * + Math.pow(10, sellBank.mintDecimals - buyBank.mintDecimals); + + const ix = await this.program.methods + .tokenConditionalSwapCreate( + toNative(maxBuy, buyBank.mintDecimals), + U64_MAX_BN, + expiryTimestamp !== null ? new BN(expiryTimestamp) : U64_MAX_BN, + priceLowerLimit, + priceUpperLimit, + pricePremiumFraction / 100, + true, + false, + ) + .accounts({ + group: group.publicKey, + account: account.publicKey, + authority: (this.program.provider as AnchorProvider).wallet.publicKey, + buyBank: buyBank.publicKey, + sellBank: sellBank.publicKey, + }) + .instruction(); + + const ixs = [ix]; + if (account.tokenConditionalSwaps.length == 0) { + ixs.push( + await this.accountExpandV2Ix( + group, + account, + account.tokens.length, + account.serum3.length, + account.perps.length, + account.perpOpenOrders.length, + 8, + ), + ); + } + + return await this.sendAndConfirmTransactionForGroup(group, ixs); + } + public async tokenConditionalSwapCreate( group: Group, account: MangoAccount, @@ -3381,7 +3517,22 @@ export class MangoClient { }) .instruction(); - return await this.sendAndConfirmTransactionForGroup(group, [ix]); + const ixs = [ix]; + if (account.tokenConditionalSwaps.length == 0) { + ixs.push( + await this.accountExpandV2Ix( + group, + account, + account.tokens.length, + account.serum3.length, + account.perps.length, + account.perpOpenOrders.length, + 8, + ), + ); + } + + return await this.sendAndConfirmTransactionForGroup(group, ixs); } public async tokenConditionalSwapCancel( diff --git a/ts/client/src/mango_v4.ts b/ts/client/src/mango_v4.ts index 2b3bcce74..6961da112 100644 --- a/ts/client/src/mango_v4.ts +++ b/ts/client/src/mango_v4.ts @@ -8,19 +8,12 @@ export type MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "bank", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "vault" - ] + "isSigner": false }, { "name": "vault", @@ -51,27 +44,17 @@ export type MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "bank", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "vault" - ] + "isSigner": false }, { "name": "vault", @@ -194,10 +177,7 @@ export type MangoV4 = { { "name": "group", "isMut": true, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -292,11 +272,7 @@ export type MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "insurance_vault", - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -353,11 +329,7 @@ export type MangoV4 = { { "name": "group", "isMut": true, - "isSigner": false, - "relations": [ - "admin", - "insurance_vault" - ] + "isSigner": false }, { "name": "admin", @@ -388,10 +360,7 @@ export type MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -729,10 +698,7 @@ export type MangoV4 = { { "name": "mintInfo", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "oracle", @@ -897,10 +863,7 @@ export type MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -915,11 +878,7 @@ export type MangoV4 = { { "name": "existingBank", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "mint" - ] + "isSigner": false }, { "name": "bank", @@ -982,11 +941,7 @@ export type MangoV4 = { { "name": "mintInfo", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "mint" - ] + "isSigner": false }, { "name": "payer", @@ -1026,10 +981,7 @@ export type MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -1039,10 +991,7 @@ export type MangoV4 = { { "name": "mintInfo", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "dustVault", @@ -1073,11 +1022,7 @@ export type MangoV4 = { { "name": "mintInfo", "isMut": false, - "isSigner": false, - "relations": [ - "oracle", - "group" - ] + "isSigner": false }, { "name": "oracle", @@ -1183,11 +1128,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "owner" - ] + "isSigner": false }, { "name": "owner", @@ -1235,11 +1176,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "owner" - ] + "isSigner": false }, { "name": "owner", @@ -1291,11 +1228,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "owner" - ] + "isSigner": false }, { "name": "owner", @@ -1329,10 +1262,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "admin", @@ -1358,11 +1288,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "owner" - ] + "isSigner": false }, { "name": "owner", @@ -1398,10 +1324,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -1411,18 +1334,12 @@ export type MangoV4 = { { "name": "daoAccount", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "mngoBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "mngoOracle", @@ -1432,10 +1349,7 @@ export type MangoV4 = { { "name": "feesBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "feesOracle", @@ -1456,10 +1370,7 @@ export type MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "oracle", @@ -1522,10 +1433,7 @@ export type MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -1535,10 +1443,7 @@ export type MangoV4 = { { "name": "oracle", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "solDestination", @@ -1559,10 +1464,7 @@ export type MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -1572,10 +1474,7 @@ export type MangoV4 = { { "name": "oracle", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [ @@ -1598,11 +1497,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "owner" - ] + "isSigner": false }, { "name": "owner", @@ -1612,12 +1507,7 @@ export type MangoV4 = { { "name": "bank", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "vault", - "oracle" - ] + "isSigner": false }, { "name": "vault", @@ -1667,20 +1557,12 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "bank", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "vault", - "oracle" - ] + "isSigner": false }, { "name": "vault", @@ -1730,11 +1612,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "owner" - ] + "isSigner": false }, { "name": "owner", @@ -1744,12 +1622,7 @@ export type MangoV4 = { { "name": "bank", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "vault", - "oracle" - ] + "isSigner": false }, { "name": "vault", @@ -1898,10 +1771,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [] @@ -1928,10 +1798,7 @@ export type MangoV4 = { { "name": "group", "isMut": true, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -1999,18 +1866,12 @@ export type MangoV4 = { { "name": "quoteBank", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "baseBank", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "payer", @@ -2050,10 +1911,7 @@ export type MangoV4 = { { "name": "market", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [ @@ -2083,10 +1941,7 @@ export type MangoV4 = { { "name": "group", "isMut": true, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -2096,18 +1951,12 @@ export type MangoV4 = { { "name": "serumMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "indexReservation", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "solDestination", @@ -2133,10 +1982,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -2146,12 +1992,7 @@ export type MangoV4 = { { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -2216,10 +2057,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -2229,12 +2067,7 @@ export type MangoV4 = { { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -2270,10 +2103,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -2288,12 +2118,7 @@ export type MangoV4 = { { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -2349,9 +2174,6 @@ export type MangoV4 = { "isSigner": false, "docs": [ "The bank that pays for the order, if necessary" - ], - "relations": [ - "group" ] }, { @@ -2425,10 +2247,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -2443,12 +2262,7 @@ export type MangoV4 = { { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -2500,10 +2314,7 @@ export type MangoV4 = { { "name": "account", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -2518,12 +2329,7 @@ export type MangoV4 = { { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -2575,10 +2381,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -2593,12 +2396,7 @@ export type MangoV4 = { { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -2631,10 +2429,7 @@ export type MangoV4 = { { "name": "quoteBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "quoteVault", @@ -2644,10 +2439,7 @@ export type MangoV4 = { { "name": "baseBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "baseVault", @@ -2680,10 +2472,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -2698,12 +2487,7 @@ export type MangoV4 = { { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -2736,10 +2520,7 @@ export type MangoV4 = { { "name": "quoteBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "quoteVault", @@ -2749,10 +2530,7 @@ export type MangoV4 = { { "name": "baseBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "baseVault", @@ -2800,10 +2578,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "openOrders", @@ -2813,12 +2588,7 @@ export type MangoV4 = { { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -2863,10 +2633,7 @@ export type MangoV4 = { { "name": "quoteBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "quoteVault", @@ -2876,10 +2643,7 @@ export type MangoV4 = { { "name": "baseBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "baseVault", @@ -2910,10 +2674,7 @@ export type MangoV4 = { { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -2923,10 +2684,7 @@ export type MangoV4 = { { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [ @@ -2952,18 +2710,12 @@ export type MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "insurance_vault" - ] + "isSigner": false }, { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -2973,18 +2725,12 @@ export type MangoV4 = { { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liabMintInfo", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "quoteVault", @@ -3022,10 +2768,7 @@ export type MangoV4 = { { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -3035,10 +2778,7 @@ export type MangoV4 = { { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [ @@ -3069,10 +2809,7 @@ export type MangoV4 = { { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -3082,10 +2819,7 @@ export type MangoV4 = { { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [ @@ -3109,18 +2843,12 @@ export type MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "insurance_vault" - ] + "isSigner": false }, { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -3130,18 +2858,12 @@ export type MangoV4 = { { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liabMintInfo", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "quoteVault", @@ -3179,10 +2901,7 @@ export type MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -3377,10 +3096,7 @@ export type MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "oracle", @@ -3581,10 +3297,7 @@ export type MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -3594,13 +3307,7 @@ export type MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks", - "event_queue" - ] + "isSigner": false }, { "name": "bids", @@ -3641,10 +3348,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -3654,10 +3358,7 @@ export type MangoV4 = { { "name": "perpMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [] @@ -3673,10 +3374,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -3686,14 +3384,7 @@ export type MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks", - "event_queue", - "oracle" - ] + "isSigner": false }, { "name": "bids", @@ -3773,10 +3464,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -3786,14 +3474,7 @@ export type MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks", - "event_queue", - "oracle" - ] + "isSigner": false }, { "name": "bids", @@ -3879,10 +3560,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -3892,14 +3570,7 @@ export type MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks", - "event_queue", - "oracle" - ] + "isSigner": false }, { "name": "bids", @@ -3987,10 +3658,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -4000,14 +3668,7 @@ export type MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks", - "event_queue", - "oracle" - ] + "isSigner": false }, { "name": "bids", @@ -4101,10 +3762,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -4114,12 +3772,7 @@ export type MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks" - ] + "isSigner": false }, { "name": "bids", @@ -4150,10 +3803,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -4163,12 +3813,7 @@ export type MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks" - ] + "isSigner": false }, { "name": "bids", @@ -4199,10 +3844,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -4212,12 +3854,7 @@ export type MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks" - ] + "isSigner": false }, { "name": "bids", @@ -4248,10 +3885,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -4261,12 +3895,7 @@ export type MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks" - ] + "isSigner": false }, { "name": "bids", @@ -4305,11 +3934,7 @@ export type MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "event_queue" - ] + "isSigner": false }, { "name": "eventQueue", @@ -4335,13 +3960,7 @@ export type MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks", - "oracle" - ] + "isSigner": false }, { "name": "bids", @@ -4372,10 +3991,7 @@ export type MangoV4 = { { "name": "settler", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "settlerOwner", @@ -4385,27 +4001,17 @@ export type MangoV4 = { { "name": "perpMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "oracle" - ] + "isSigner": false }, { "name": "accountA", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "accountB", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "oracle", @@ -4415,10 +4021,7 @@ export type MangoV4 = { { "name": "settleBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "settleOracle", @@ -4439,27 +4042,17 @@ export type MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "oracle" - ] + "isSigner": false }, { "name": "accountA", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "accountB", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "oracle", @@ -4480,19 +4073,12 @@ export type MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "oracle" - ] + "isSigner": false }, { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "oracle", @@ -4502,10 +4088,7 @@ export type MangoV4 = { { "name": "settleBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "settleOracle", @@ -4531,11 +4114,7 @@ export type MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "oracle" - ] + "isSigner": false }, { "name": "oracle", @@ -4545,10 +4124,7 @@ export type MangoV4 = { { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -4558,18 +4134,12 @@ export type MangoV4 = { { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "settleBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "settleVault", @@ -4604,20 +4174,12 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks" - ] + "isSigner": false }, { "name": "bids", @@ -4643,18 +4205,12 @@ export type MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "insurance_vault" - ] + "isSigner": false }, { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -4664,19 +4220,12 @@ export type MangoV4 = { { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "oracle" - ] + "isSigner": false }, { "name": "oracle", @@ -4686,10 +4235,7 @@ export type MangoV4 = { { "name": "settleBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "settleVault", @@ -4725,18 +4271,12 @@ export type MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "insurance_vault" - ] + "isSigner": false }, { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -4746,19 +4286,12 @@ export type MangoV4 = { { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "oracle" - ] + "isSigner": false }, { "name": "oracle", @@ -4768,10 +4301,7 @@ export type MangoV4 = { { "name": "settleBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "settleVault", @@ -4791,10 +4321,7 @@ export type MangoV4 = { { "name": "insuranceBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "insuranceBankVault", @@ -4830,10 +4357,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "authority", @@ -4843,18 +4367,12 @@ export type MangoV4 = { { "name": "buyBank", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "sellBank", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [ @@ -4903,10 +4421,7 @@ export type MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "authority", @@ -4919,18 +4434,12 @@ export type MangoV4 = { "isSigner": false, "docs": [ "The bank's token_index is checked at #1" - ], - "relations": [ - "group" ] }, { "name": "sellBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [ @@ -4955,18 +4464,12 @@ export type MangoV4 = { { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorAuthority", @@ -4999,10 +4502,7 @@ export type MangoV4 = { { "name": "group", "isMut": true, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -5028,10 +4528,7 @@ export type MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -5073,10 +4570,7 @@ export type MangoV4 = { { "name": "account", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [] @@ -10853,19 +10347,12 @@ export const IDL: MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "bank", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "vault" - ] + "isSigner": false }, { "name": "vault", @@ -10896,27 +10383,17 @@ export const IDL: MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "bank", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "vault" - ] + "isSigner": false }, { "name": "vault", @@ -11039,10 +10516,7 @@ export const IDL: MangoV4 = { { "name": "group", "isMut": true, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -11137,11 +10611,7 @@ export const IDL: MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "insurance_vault", - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -11198,11 +10668,7 @@ export const IDL: MangoV4 = { { "name": "group", "isMut": true, - "isSigner": false, - "relations": [ - "admin", - "insurance_vault" - ] + "isSigner": false }, { "name": "admin", @@ -11233,10 +10699,7 @@ export const IDL: MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -11574,10 +11037,7 @@ export const IDL: MangoV4 = { { "name": "mintInfo", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "oracle", @@ -11742,10 +11202,7 @@ export const IDL: MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -11760,11 +11217,7 @@ export const IDL: MangoV4 = { { "name": "existingBank", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "mint" - ] + "isSigner": false }, { "name": "bank", @@ -11827,11 +11280,7 @@ export const IDL: MangoV4 = { { "name": "mintInfo", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "mint" - ] + "isSigner": false }, { "name": "payer", @@ -11871,10 +11320,7 @@ export const IDL: MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -11884,10 +11330,7 @@ export const IDL: MangoV4 = { { "name": "mintInfo", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "dustVault", @@ -11918,11 +11361,7 @@ export const IDL: MangoV4 = { { "name": "mintInfo", "isMut": false, - "isSigner": false, - "relations": [ - "oracle", - "group" - ] + "isSigner": false }, { "name": "oracle", @@ -12028,11 +11467,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "owner" - ] + "isSigner": false }, { "name": "owner", @@ -12080,11 +11515,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "owner" - ] + "isSigner": false }, { "name": "owner", @@ -12136,11 +11567,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "owner" - ] + "isSigner": false }, { "name": "owner", @@ -12174,10 +11601,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "admin", @@ -12203,11 +11627,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "owner" - ] + "isSigner": false }, { "name": "owner", @@ -12243,10 +11663,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -12256,18 +11673,12 @@ export const IDL: MangoV4 = { { "name": "daoAccount", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "mngoBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "mngoOracle", @@ -12277,10 +11688,7 @@ export const IDL: MangoV4 = { { "name": "feesBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "feesOracle", @@ -12301,10 +11709,7 @@ export const IDL: MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "oracle", @@ -12367,10 +11772,7 @@ export const IDL: MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -12380,10 +11782,7 @@ export const IDL: MangoV4 = { { "name": "oracle", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "solDestination", @@ -12404,10 +11803,7 @@ export const IDL: MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -12417,10 +11813,7 @@ export const IDL: MangoV4 = { { "name": "oracle", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [ @@ -12443,11 +11836,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "owner" - ] + "isSigner": false }, { "name": "owner", @@ -12457,12 +11846,7 @@ export const IDL: MangoV4 = { { "name": "bank", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "vault", - "oracle" - ] + "isSigner": false }, { "name": "vault", @@ -12512,20 +11896,12 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "bank", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "vault", - "oracle" - ] + "isSigner": false }, { "name": "vault", @@ -12575,11 +11951,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "owner" - ] + "isSigner": false }, { "name": "owner", @@ -12589,12 +11961,7 @@ export const IDL: MangoV4 = { { "name": "bank", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "vault", - "oracle" - ] + "isSigner": false }, { "name": "vault", @@ -12743,10 +12110,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [] @@ -12773,10 +12137,7 @@ export const IDL: MangoV4 = { { "name": "group", "isMut": true, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -12844,18 +12205,12 @@ export const IDL: MangoV4 = { { "name": "quoteBank", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "baseBank", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "payer", @@ -12895,10 +12250,7 @@ export const IDL: MangoV4 = { { "name": "market", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [ @@ -12928,10 +12280,7 @@ export const IDL: MangoV4 = { { "name": "group", "isMut": true, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -12941,18 +12290,12 @@ export const IDL: MangoV4 = { { "name": "serumMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "indexReservation", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "solDestination", @@ -12978,10 +12321,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -12991,12 +12331,7 @@ export const IDL: MangoV4 = { { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -13061,10 +12396,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -13074,12 +12406,7 @@ export const IDL: MangoV4 = { { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -13115,10 +12442,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -13133,12 +12457,7 @@ export const IDL: MangoV4 = { { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -13194,9 +12513,6 @@ export const IDL: MangoV4 = { "isSigner": false, "docs": [ "The bank that pays for the order, if necessary" - ], - "relations": [ - "group" ] }, { @@ -13270,10 +12586,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -13288,12 +12601,7 @@ export const IDL: MangoV4 = { { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -13345,10 +12653,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -13363,12 +12668,7 @@ export const IDL: MangoV4 = { { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -13420,10 +12720,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -13438,12 +12735,7 @@ export const IDL: MangoV4 = { { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -13476,10 +12768,7 @@ export const IDL: MangoV4 = { { "name": "quoteBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "quoteVault", @@ -13489,10 +12778,7 @@ export const IDL: MangoV4 = { { "name": "baseBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "baseVault", @@ -13525,10 +12811,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -13543,12 +12826,7 @@ export const IDL: MangoV4 = { { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -13581,10 +12859,7 @@ export const IDL: MangoV4 = { { "name": "quoteBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "quoteVault", @@ -13594,10 +12869,7 @@ export const IDL: MangoV4 = { { "name": "baseBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "baseVault", @@ -13645,10 +12917,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "openOrders", @@ -13658,12 +12927,7 @@ export const IDL: MangoV4 = { { "name": "serumMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "serum_program", - "serum_market_external" - ] + "isSigner": false }, { "name": "serumProgram", @@ -13708,10 +12972,7 @@ export const IDL: MangoV4 = { { "name": "quoteBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "quoteVault", @@ -13721,10 +12982,7 @@ export const IDL: MangoV4 = { { "name": "baseBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "baseVault", @@ -13755,10 +13013,7 @@ export const IDL: MangoV4 = { { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -13768,10 +13023,7 @@ export const IDL: MangoV4 = { { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [ @@ -13797,18 +13049,12 @@ export const IDL: MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "insurance_vault" - ] + "isSigner": false }, { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -13818,18 +13064,12 @@ export const IDL: MangoV4 = { { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liabMintInfo", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "quoteVault", @@ -13867,10 +13107,7 @@ export const IDL: MangoV4 = { { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -13880,10 +13117,7 @@ export const IDL: MangoV4 = { { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [ @@ -13914,10 +13148,7 @@ export const IDL: MangoV4 = { { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -13927,10 +13158,7 @@ export const IDL: MangoV4 = { { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [ @@ -13954,18 +13182,12 @@ export const IDL: MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "insurance_vault" - ] + "isSigner": false }, { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -13975,18 +13197,12 @@ export const IDL: MangoV4 = { { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liabMintInfo", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "quoteVault", @@ -14024,10 +13240,7 @@ export const IDL: MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -14222,10 +13435,7 @@ export const IDL: MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "oracle", @@ -14426,10 +13636,7 @@ export const IDL: MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -14439,13 +13646,7 @@ export const IDL: MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks", - "event_queue" - ] + "isSigner": false }, { "name": "bids", @@ -14486,10 +13687,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -14499,10 +13697,7 @@ export const IDL: MangoV4 = { { "name": "perpMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [] @@ -14518,10 +13713,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -14531,14 +13723,7 @@ export const IDL: MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks", - "event_queue", - "oracle" - ] + "isSigner": false }, { "name": "bids", @@ -14618,10 +13803,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -14631,14 +13813,7 @@ export const IDL: MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks", - "event_queue", - "oracle" - ] + "isSigner": false }, { "name": "bids", @@ -14724,10 +13899,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -14737,14 +13909,7 @@ export const IDL: MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks", - "event_queue", - "oracle" - ] + "isSigner": false }, { "name": "bids", @@ -14832,10 +13997,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -14845,14 +14007,7 @@ export const IDL: MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks", - "event_queue", - "oracle" - ] + "isSigner": false }, { "name": "bids", @@ -14946,10 +14101,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -14959,12 +14111,7 @@ export const IDL: MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks" - ] + "isSigner": false }, { "name": "bids", @@ -14995,10 +14142,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -15008,12 +14152,7 @@ export const IDL: MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks" - ] + "isSigner": false }, { "name": "bids", @@ -15044,10 +14183,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -15057,12 +14193,7 @@ export const IDL: MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks" - ] + "isSigner": false }, { "name": "bids", @@ -15093,10 +14224,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "owner", @@ -15106,12 +14234,7 @@ export const IDL: MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks" - ] + "isSigner": false }, { "name": "bids", @@ -15150,11 +14273,7 @@ export const IDL: MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "event_queue" - ] + "isSigner": false }, { "name": "eventQueue", @@ -15180,13 +14299,7 @@ export const IDL: MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks", - "oracle" - ] + "isSigner": false }, { "name": "bids", @@ -15217,10 +14330,7 @@ export const IDL: MangoV4 = { { "name": "settler", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "settlerOwner", @@ -15230,27 +14340,17 @@ export const IDL: MangoV4 = { { "name": "perpMarket", "isMut": false, - "isSigner": false, - "relations": [ - "group", - "oracle" - ] + "isSigner": false }, { "name": "accountA", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "accountB", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "oracle", @@ -15260,10 +14360,7 @@ export const IDL: MangoV4 = { { "name": "settleBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "settleOracle", @@ -15284,27 +14381,17 @@ export const IDL: MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "oracle" - ] + "isSigner": false }, { "name": "accountA", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "accountB", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "oracle", @@ -15325,19 +14412,12 @@ export const IDL: MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "oracle" - ] + "isSigner": false }, { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "oracle", @@ -15347,10 +14427,7 @@ export const IDL: MangoV4 = { { "name": "settleBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "settleOracle", @@ -15376,11 +14453,7 @@ export const IDL: MangoV4 = { { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "oracle" - ] + "isSigner": false }, { "name": "oracle", @@ -15390,10 +14463,7 @@ export const IDL: MangoV4 = { { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -15403,18 +14473,12 @@ export const IDL: MangoV4 = { { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "settleBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "settleVault", @@ -15449,20 +14513,12 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "bids", - "asks" - ] + "isSigner": false }, { "name": "bids", @@ -15488,18 +14544,12 @@ export const IDL: MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "insurance_vault" - ] + "isSigner": false }, { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -15509,19 +14559,12 @@ export const IDL: MangoV4 = { { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "oracle" - ] + "isSigner": false }, { "name": "oracle", @@ -15531,10 +14574,7 @@ export const IDL: MangoV4 = { { "name": "settleBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "settleVault", @@ -15570,18 +14610,12 @@ export const IDL: MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "insurance_vault" - ] + "isSigner": false }, { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorOwner", @@ -15591,19 +14625,12 @@ export const IDL: MangoV4 = { { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "perpMarket", "isMut": true, - "isSigner": false, - "relations": [ - "group", - "oracle" - ] + "isSigner": false }, { "name": "oracle", @@ -15613,10 +14640,7 @@ export const IDL: MangoV4 = { { "name": "settleBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "settleVault", @@ -15636,10 +14660,7 @@ export const IDL: MangoV4 = { { "name": "insuranceBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "insuranceBankVault", @@ -15675,10 +14696,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "authority", @@ -15688,18 +14706,12 @@ export const IDL: MangoV4 = { { "name": "buyBank", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "sellBank", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [ @@ -15748,10 +14760,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "authority", @@ -15764,18 +14773,12 @@ export const IDL: MangoV4 = { "isSigner": false, "docs": [ "The bank's token_index is checked at #1" - ], - "relations": [ - "group" ] }, { "name": "sellBank", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [ @@ -15800,18 +14803,12 @@ export const IDL: MangoV4 = { { "name": "liqee", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqor", "isMut": true, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false }, { "name": "liqorAuthority", @@ -15844,10 +14841,7 @@ export const IDL: MangoV4 = { { "name": "group", "isMut": true, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -15873,10 +14867,7 @@ export const IDL: MangoV4 = { { "name": "group", "isMut": false, - "isSigner": false, - "relations": [ - "admin" - ] + "isSigner": false }, { "name": "admin", @@ -15918,10 +14909,7 @@ export const IDL: MangoV4 = { { "name": "account", "isMut": false, - "isSigner": false, - "relations": [ - "group" - ] + "isSigner": false } ], "args": [] diff --git a/ts/client/src/numbers/numbers.spec.ts b/ts/client/src/numbers/numbers.spec.ts index ecb2d25fa..62abf6568 100644 --- a/ts/client/src/numbers/numbers.spec.ts +++ b/ts/client/src/numbers/numbers.spec.ts @@ -1,9 +1,24 @@ import BN from 'bn.js'; import { expect } from 'chai'; -import { U64_MAX_BN } from '../utils'; +import { U64_MAX_BN, roundTo5 } from '../utils'; import { I80F48 } from './I80F48'; describe('Math', () => { + it('round to accuracy 5', () => { + expect(roundTo5(0.012)).equals(0.012); + expect(roundTo5(0.0123456789)).equals(0.012345); + expect(roundTo5(0.123456789)).equals(0.12345); + expect(roundTo5(1.23456789)).equals(1.2345); + expect(roundTo5(12.3456789)).equals(12.345); + expect(roundTo5(123.456789)).equals(123.45); + expect(roundTo5(1234.56789)).equals(1234.5); + expect(roundTo5(12345.6789)).equals(12346); + expect(roundTo5(123456.789)).equals(123457); + + expect(roundTo5(1.23)).equals(1.2299); + expect(roundTo5(1.2)).equals(1.1999); + }); + it('js number to BN and I80F48', () => { // BN can be only be created from js numbers which are <=2^53 expect(function () { diff --git a/ts/client/src/risk.ts b/ts/client/src/risk.ts index 8ec4ae380..b6dafef5d 100644 --- a/ts/client/src/risk.ts +++ b/ts/client/src/risk.ts @@ -6,20 +6,7 @@ import { Group } from './accounts/group'; import { HealthType, MangoAccount } from './accounts/mangoAccount'; import { MangoClient } from './client'; import { I80F48, ONE_I80F48, ZERO_I80F48 } from './numbers/I80F48'; -import { toUiDecimals, toUiDecimalsForQuote } from './utils'; - -async function buildFetch(): Promise< - ( - input: RequestInfo | URL, - init?: RequestInit | undefined, - ) => Promise -> { - let fetch = globalThis?.fetch; - if (!fetch && process?.versions?.node) { - fetch = (await import('node-fetch')).default; - } - return fetch; -} +import { buildFetch, toUiDecimals, toUiDecimalsForQuote } from './utils'; export interface LiqorPriceImpact { Coin: { val: string; highlight: boolean }; @@ -56,7 +43,7 @@ export interface Risk { liqorEquity: { title: string; data: AccountEquity[] }; } -type PriceImpact = { +export type PriceImpact = { symbol: string; side: 'bid' | 'ask'; target_amount: number; @@ -65,12 +52,15 @@ type PriceImpact = { max_price_impact_percent: number; }; -export async function computePriceImpactOnJup( +/** + * Returns price impact in bps, + * returns -1 if data is missing + */ +export function computePriceImpactOnJup( pis: PriceImpact[], usdcAmount: number, tokenName: string, -): Promise { - console.log(pis); +): number { try { const closestTo = [1000, 5000, 20000, 100000].reduce((prev, curr) => Math.abs(curr - usdcAmount) < Math.abs(prev - usdcAmount) ? curr : prev, @@ -239,7 +229,7 @@ export async function getPriceImpactForLiqor( return sum.add(maxAsset); }, ZERO_I80F48()); - const [pi1, pi2] = await Promise.all([ + const pi1 = !liabsInUsdc.eq(ZERO_I80F48()) && usdcMint.toBase58() !== bank.mint.toBase58() ? computePriceImpactOnJup( @@ -247,8 +237,8 @@ export async function getPriceImpactForLiqor( toUiDecimalsForQuote(liabsInUsdc), bank.name, ) - : Promise.resolve(0), - + : 0; + const pi2 = !assets.eq(ZERO_I80F48()) && usdcMint.toBase58() !== bank.mint.toBase58() ? computePriceImpactOnJup( @@ -256,8 +246,7 @@ export async function getPriceImpactForLiqor( toUiDecimals(assets.mul(bank.price), bank.mintDecimals), bank.name, ) - : Promise.resolve(0), - ]); + : 0; return { Coin: { val: bank.name, highlight: false }, diff --git a/ts/client/src/utils.ts b/ts/client/src/utils.ts index f59fb728a..f926d126e 100644 --- a/ts/client/src/utils.ts +++ b/ts/client/src/utils.ts @@ -66,6 +66,55 @@ export function toUiI80F48(nativeAmount: I80F48, decimals: number): I80F48 { return nativeAmount.div(I80F48.fromNumber(Math.pow(10, decimals))); } +export function roundTo5(number): number { + if (number < 1) { + const numString = number.toString(); + const nonZeroIndex = numString.search(/[1-9]/); + if (nonZeroIndex === -1 || nonZeroIndex >= numString.length - 5) { + return number; + } + return Number(numString.slice(0, nonZeroIndex + 5)); + } else if (number < 10) { + return ( + Math.floor(number) + + Number((number % 1).toString().padEnd(10, '0').slice(0, 6)) + ); + } else if (number < 100) { + return ( + Math.floor(number) + + Number((number % 1).toString().padEnd(10, '0').slice(0, 5)) + ); + } else if (number < 1000) { + return ( + Math.floor(number) + + Number((number % 1).toString().padEnd(10, '0').slice(0, 4)) + ); + } else if (number < 10000) { + return ( + Math.floor(number) + + Number((number % 1).toString().padEnd(10, '0').slice(0, 3)) + ); + } + return Math.round(number); +} + +/// + +export async function buildFetch(): Promise< + ( + input: RequestInfo | URL, + init?: RequestInit | undefined, + ) => Promise +> { + let fetch = globalThis?.fetch; + if (!fetch && process?.versions?.node) { + fetch = (await import('node-fetch')).default; + } + return fetch; +} + +/// + /// /// web3js extensions /// From 27221c1aadf2adb0a7ba92cfe5ff758ea85fad1c Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 28 Jul 2023 08:33:19 +0200 Subject: [PATCH 53/90] v0.17.28 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0a18b83a4..c5a5c0f42 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.17.27", + "version": "0.17.28", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From e4ff67bc2f02d450d9f4a529d08330bec6a6cdf6 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 28 Jul 2023 08:36:58 +0200 Subject: [PATCH 54/90] make public Signed-off-by: microwavedcola1 --- ts/client/src/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index 7e0028e72..6432a7cbc 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -879,7 +879,7 @@ export class MangoClient { ); } - private async getMangoAccountFromAi( + public async getMangoAccountFromAi( mangoAccountPk: PublicKey, ai: AccountInfo, ): Promise { From 046375a480366c3d5da0f411bcc77cdbcd0e286a Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 28 Jul 2023 08:37:30 +0200 Subject: [PATCH 55/90] v0.17.29 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c5a5c0f42..f1b93702a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.17.28", + "version": "0.17.29", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From 7d942cc4ffbb96893ef4d20c406bc095e0d7af3c Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 28 Jul 2023 09:52:37 +0200 Subject: [PATCH 56/90] some logging Signed-off-by: microwavedcola1 --- bin/liquidator/src/trigger_tcs.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bin/liquidator/src/trigger_tcs.rs b/bin/liquidator/src/trigger_tcs.rs index b017bb72a..3d54cd6bc 100644 --- a/bin/liquidator/src/trigger_tcs.rs +++ b/bin/liquidator/src/trigger_tcs.rs @@ -23,6 +23,10 @@ async fn tcs_is_in_price_range( let buy_token_price = mango_client.bank_oracle_price(tcs.buy_token_index).await?; let sell_token_price = mango_client.bank_oracle_price(tcs.sell_token_index).await?; let base_price = (buy_token_price / sell_token_price).to_num(); + info!( + base_price, + tcs.price_lower_limit, tcs.price_upper_limit, "price_in_range", + ); if !tcs.price_in_range(base_price) { return Ok(false); } @@ -40,6 +44,7 @@ fn tcs_has_plausible_premium( // Never take tcs where the fee exceeds the premium and the triggerer exchanges // tokens at below oracle price. if premium < 1.0 { + info!(premium, "tcs_has_plausible_premium premium",); return Ok(false); } @@ -54,6 +59,8 @@ fn tcs_has_plausible_premium( // 1.5 would mean we need to pay 50% more than oracle etc. let cost = buy_info.buy_over_oracle * sell_info.sell_over_oracle; + info!(cost, "tcs_has_plausible_premium cost",); + Ok(cost <= premium) } From 7752d398fe4ce3abbb80229000e5ce764985f71e Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 28 Jul 2023 11:53:50 +0200 Subject: [PATCH 57/90] more logging Signed-off-by: microwavedcola1 --- bin/liquidator/src/trigger_tcs.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bin/liquidator/src/trigger_tcs.rs b/bin/liquidator/src/trigger_tcs.rs index 3d54cd6bc..2093eb4ec 100644 --- a/bin/liquidator/src/trigger_tcs.rs +++ b/bin/liquidator/src/trigger_tcs.rs @@ -70,6 +70,12 @@ async fn tcs_is_interesting( token_swap_info: &token_swap_info::TokenSwapInfoUpdater, now_ts: u64, ) -> anyhow::Result { + info!("{} {}", tcs.is_expired(now_ts), "tcs.is_expired(now_ts)"); + info!( + "{} {}", + tcs.tcs_has_plausible_premium(tcs, token_swap_info), + "tcs.tcs_has_plausible_premium(tcs, token_swap_info)" + ); Ok(!tcs.is_expired(now_ts) && tcs_is_in_price_range(mango_client, tcs).await? && tcs_has_plausible_premium(tcs, token_swap_info)?) @@ -287,6 +293,8 @@ pub async fn maybe_execute_token_conditional_swap( tcs_shuffled.shuffle(&mut rng); } + info!("{} {}", tcs_shuffled.len(), "tcs_shuffled.len()"); + for tcs in tcs_shuffled.iter() { if tcs_is_interesting(mango_client, tcs, token_swap_info, now_ts).await? { return maybe_execute_token_conditional_swap_inner( From e0249cc2556d61534dbe7204fa27cf72b53cc868 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 28 Jul 2023 12:17:48 +0200 Subject: [PATCH 58/90] Fix logging Signed-off-by: microwavedcola1 --- bin/liquidator/src/trigger_tcs.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/liquidator/src/trigger_tcs.rs b/bin/liquidator/src/trigger_tcs.rs index 2093eb4ec..c8397a925 100644 --- a/bin/liquidator/src/trigger_tcs.rs +++ b/bin/liquidator/src/trigger_tcs.rs @@ -73,8 +73,8 @@ async fn tcs_is_interesting( info!("{} {}", tcs.is_expired(now_ts), "tcs.is_expired(now_ts)"); info!( "{} {}", - tcs.tcs_has_plausible_premium(tcs, token_swap_info), - "tcs.tcs_has_plausible_premium(tcs, token_swap_info)" + tcs_has_plausible_premium(tcs, token_swap_info)?, + "tcs_has_plausible_premium(tcs, token_swap_info)" ); Ok(!tcs.is_expired(now_ts) && tcs_is_in_price_range(mango_client, tcs).await? From 5008d3253c2947d9b04ee4c2c7997f2407011c73 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 28 Jul 2023 15:13:01 +0200 Subject: [PATCH 59/90] Fixes for tcs Signed-off-by: microwavedcola1 --- bin/liquidator/src/trigger_tcs.rs | 15 --- .../scripts/archive/conditional-swaps.ts | 120 ++++++------------ ts/client/src/accounts/mangoAccount.ts | 8 +- ts/client/src/client.ts | 23 ++-- 4 files changed, 53 insertions(+), 113 deletions(-) diff --git a/bin/liquidator/src/trigger_tcs.rs b/bin/liquidator/src/trigger_tcs.rs index c8397a925..b017bb72a 100644 --- a/bin/liquidator/src/trigger_tcs.rs +++ b/bin/liquidator/src/trigger_tcs.rs @@ -23,10 +23,6 @@ async fn tcs_is_in_price_range( let buy_token_price = mango_client.bank_oracle_price(tcs.buy_token_index).await?; let sell_token_price = mango_client.bank_oracle_price(tcs.sell_token_index).await?; let base_price = (buy_token_price / sell_token_price).to_num(); - info!( - base_price, - tcs.price_lower_limit, tcs.price_upper_limit, "price_in_range", - ); if !tcs.price_in_range(base_price) { return Ok(false); } @@ -44,7 +40,6 @@ fn tcs_has_plausible_premium( // Never take tcs where the fee exceeds the premium and the triggerer exchanges // tokens at below oracle price. if premium < 1.0 { - info!(premium, "tcs_has_plausible_premium premium",); return Ok(false); } @@ -59,8 +54,6 @@ fn tcs_has_plausible_premium( // 1.5 would mean we need to pay 50% more than oracle etc. let cost = buy_info.buy_over_oracle * sell_info.sell_over_oracle; - info!(cost, "tcs_has_plausible_premium cost",); - Ok(cost <= premium) } @@ -70,12 +63,6 @@ async fn tcs_is_interesting( token_swap_info: &token_swap_info::TokenSwapInfoUpdater, now_ts: u64, ) -> anyhow::Result { - info!("{} {}", tcs.is_expired(now_ts), "tcs.is_expired(now_ts)"); - info!( - "{} {}", - tcs_has_plausible_premium(tcs, token_swap_info)?, - "tcs_has_plausible_premium(tcs, token_swap_info)" - ); Ok(!tcs.is_expired(now_ts) && tcs_is_in_price_range(mango_client, tcs).await? && tcs_has_plausible_premium(tcs, token_swap_info)?) @@ -293,8 +280,6 @@ pub async fn maybe_execute_token_conditional_swap( tcs_shuffled.shuffle(&mut rng); } - info!("{} {}", tcs_shuffled.len(), "tcs_shuffled.len()"); - for tcs in tcs_shuffled.iter() { if tcs_is_interesting(mango_client, tcs, token_swap_info, now_ts).await? { return maybe_execute_token_conditional_swap_inner( diff --git a/ts/client/scripts/archive/conditional-swaps.ts b/ts/client/scripts/archive/conditional-swaps.ts index 435b8f21f..87694fc12 100644 --- a/ts/client/scripts/archive/conditional-swaps.ts +++ b/ts/client/scripts/archive/conditional-swaps.ts @@ -1,108 +1,68 @@ import { AnchorProvider, Wallet } from '@coral-xyz/anchor'; -import { Connection, Keypair } from '@solana/web3.js'; +import { Cluster, Connection, Keypair, PublicKey } from '@solana/web3.js'; import fs from 'fs'; -import { MangoAccount } from '../../src/accounts/mangoAccount'; +import { TokenIndex } from '../../src/accounts/bank'; import { MangoClient } from '../../src/client'; import { MANGO_V4_ID } from '../../src/constants'; +const USER_KEYPAIR = + process.env.USER_KEYPAIR_OVERRIDE || process.env.MB_PAYER_KEYPAIR; +const MANGO_ACCOUNT_PK = process.env.MANGO_ACCOUNT_PK || ''; +const CLUSTER: Cluster = + (process.env.CLUSTER_OVERRIDE as Cluster) || 'mainnet-beta'; +const CLUSTER_URL = + process.env.CLUSTER_URL_OVERRIDE || process.env.MB_CLUSTER_URL; + async function main(): Promise { try { const options = AnchorProvider.defaultOptions(); - const connection = new Connection(process.env.CLUSTER_URL!, options); + const connection = new Connection(CLUSTER_URL!, options); const user = Keypair.fromSecretKey( - Buffer.from( - JSON.parse(fs.readFileSync(process.env.USER_KEYPAIR!, 'utf-8')), - ), + Buffer.from(JSON.parse(fs.readFileSync(USER_KEYPAIR!, 'utf-8'))), ); const userWallet = new Wallet(user); const userProvider = new AnchorProvider(connection, userWallet, options); - // - // mainnet - // - - // const client = await MangoClient.connect( - // userProvider, - // 'mainnet-beta', - // MANGO_V4_ID['mainnet-beta'], - // { - // idsSource: 'get-program-accounts', - // }, - // ); - // const group = await client.getGroup( - // new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'), - // ); - // console.log( - // await client.getMangoAccountForOwner( - // group, - // new PublicKey('v3mmtZ8JjXkaAbRRMBiNsjJF1rnN3qsMQqRLMk7Nz2C'), - // 3, - // ), - // ); - // console.log( - // await client.getMangoAccountsForDelegate( - // group, - // new PublicKey('5P9rHX22jb3MDq46VgeaHZ2TxQDKezPxsxNX3MaXyHwT'), - // ), - // ); - - // - // devnet - // - const client = await MangoClient.connect( userProvider, - 'devnet', - MANGO_V4_ID['devnet'], + CLUSTER, + MANGO_V4_ID[CLUSTER], { idsSource: 'get-program-accounts', }, ); - const admin = Keypair.fromSecretKey( - Buffer.from( - JSON.parse(fs.readFileSync(process.env.ADMIN_KEYPAIR!, 'utf-8')), - ), + const group = await client.getGroup( + new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'), ); - const group = await client.getGroupForCreator(admin.publicKey, 37); - let mangoAccount = (await client.getMangoAccountForOwner( + + let account = await client.getMangoAccount(new PublicKey(MANGO_ACCOUNT_PK)); + await Promise.all( + account.tokenConditionalSwaps.map((tcs, i) => { + if (!tcs.hasData) { + return Promise.resolve(); + } + client.tokenConditionalSwapCancel(group, account, i, tcs.id); + }), + ); + + await client.tokenConditionalSwapStopLoss( group, - user.publicKey, + account, + group.getFirstBankByTokenIndex(0 as TokenIndex).mint, + group.getFirstBankByTokenIndex(6 as TokenIndex).mint, + account.getTokenBalanceUi( + group.getFirstBankByTokenIndex(6 as TokenIndex), + ), + null, + group.getFirstBankByTokenIndex(6 as TokenIndex).uiPrice * 1.1, 0, - )) as MangoAccount; + 2, + ); - let sig; - // let sig = await client.accountExpandV2( - // group, - // mangoAccount, - // 16, - // 8, - // 8, - // 32, - // 8, - // ); - console.log(sig); - - // sig = await client.tokenConditionalSwapStopLoss( - // group, - // mangoAccount, - // group.getFirstBankByTokenIndex(0 as TokenIndex).mint, - // group.getFirstBankByTokenIndex(1 as TokenIndex).mint, - // 1, - // null, - // 25, - // 24, - // 1, - // ); - // console.log(sig); - - mangoAccount = (await client.getMangoAccountForOwner( - group, - user.publicKey, - 0, - )) as MangoAccount; - console.log(mangoAccount.tokenConditionalSwaps[2].toString(group)); + account = await client.getMangoAccount(new PublicKey(MANGO_ACCOUNT_PK)); + console.log(account.tokenConditionalSwaps[0].toString(group)); } catch (error) { console.log(error); } diff --git a/ts/client/src/accounts/mangoAccount.ts b/ts/client/src/accounts/mangoAccount.ts index 136408e31..cdadfdaf3 100644 --- a/ts/client/src/accounts/mangoAccount.ts +++ b/ts/client/src/accounts/mangoAccount.ts @@ -1895,17 +1895,15 @@ export class TokenConditionalSwap { toString(group: Group): string { return `getMaxBuy ${this.getMaxBuyUi( group, - )}, getMaxSell ${this.getMaxSellUi( + )}, getMaxSell ${this.getMaxSellUi(group)}, bought ${this.getBoughtUi( group, - )}, getMaxSellUi ${this.getMaxSellUi( + )}, sold ${this.getSoldUi( group, )}, getPriceLowerLimitUi ${this.getPriceLowerLimitUi( group, )}, getPriceUpperLimitUi ${this.getPriceUpperLimitUi( group, - )}, this.priceUpperLimit ${ - this.priceUpperLimit - }, getPricePremium ${this.getPricePremium()}`; + )}, getPricePremium ${this.getPricePremium()}, expiry ${this.expiryTimestamp.toString()}`; } } diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index 6432a7cbc..e7874d181 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -3382,20 +3382,15 @@ export class MangoClient { ): Promise { const buyBank: Bank = group.getFirstBankByMint(buyMintPk); const sellBank: Bank = group.getFirstBankByMint(sellMintPk); - priceLowerLimit = - (1 / priceLowerLimit) * - Math.pow(10, sellBank.mintDecimals - buyBank.mintDecimals); - priceUpperLimit = - (1 / priceUpperLimit) * - Math.pow(10, sellBank.mintDecimals - buyBank.mintDecimals); - - const ix = await this.program.methods + const tcsIx = await this.program.methods .tokenConditionalSwapCreate( U64_MAX_BN, toNative(maxSell, sellBank.mintDecimals), expiryTimestamp !== null ? new BN(expiryTimestamp) : U64_MAX_BN, - priceLowerLimit, - priceUpperLimit, + (1 / priceLowerLimit) * + Math.pow(10, sellBank.mintDecimals - buyBank.mintDecimals), + (1 / priceUpperLimit) * + Math.pow(10, sellBank.mintDecimals - buyBank.mintDecimals), pricePremiumFraction / 100, true, false, @@ -3409,7 +3404,7 @@ export class MangoClient { }) .instruction(); - const ixs = [ix]; + const ixs: TransactionInstruction[] = []; if (account.tokenConditionalSwaps.length == 0) { ixs.push( await this.accountExpandV2Ix( @@ -3423,6 +3418,7 @@ export class MangoClient { ), ); } + ixs.push(tcsIx); return await this.sendAndConfirmTransactionForGroup(group, ixs); } @@ -3447,7 +3443,7 @@ export class MangoClient { priceUpperLimit * Math.pow(10, sellBank.mintDecimals - buyBank.mintDecimals); - const ix = await this.program.methods + const tcsIx = await this.program.methods .tokenConditionalSwapCreate( toNative(maxBuy, buyBank.mintDecimals), U64_MAX_BN, @@ -3467,7 +3463,7 @@ export class MangoClient { }) .instruction(); - const ixs = [ix]; + const ixs: TransactionInstruction[] = []; if (account.tokenConditionalSwaps.length == 0) { ixs.push( await this.accountExpandV2Ix( @@ -3481,6 +3477,7 @@ export class MangoClient { ), ); } + ixs.push(tcsIx); return await this.sendAndConfirmTransactionForGroup(group, ixs); } From ef2df16f837c2b4a1be05407137f3e353f1cc7a6 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 28 Jul 2023 15:32:50 +0200 Subject: [PATCH 60/90] cleanup tcs Signed-off-by: microwavedcola1 --- .../scripts/archive/conditional-swaps.ts | 47 ++++++++-------- ts/client/src/accounts/group.ts | 2 +- ts/client/src/accounts/mangoAccount.ts | 6 +- ts/client/src/client.ts | 56 ++++++++++++++++--- ts/client/src/constants/index.ts | 2 + ts/client/src/risk.ts | 2 +- 6 files changed, 76 insertions(+), 39 deletions(-) diff --git a/ts/client/scripts/archive/conditional-swaps.ts b/ts/client/scripts/archive/conditional-swaps.ts index 87694fc12..746bbb5d3 100644 --- a/ts/client/scripts/archive/conditional-swaps.ts +++ b/ts/client/scripts/archive/conditional-swaps.ts @@ -1,7 +1,6 @@ import { AnchorProvider, Wallet } from '@coral-xyz/anchor'; import { Cluster, Connection, Keypair, PublicKey } from '@solana/web3.js'; import fs from 'fs'; -import { TokenIndex } from '../../src/accounts/bank'; import { MangoClient } from '../../src/client'; import { MANGO_V4_ID } from '../../src/constants'; @@ -38,30 +37,32 @@ async function main(): Promise { ); let account = await client.getMangoAccount(new PublicKey(MANGO_ACCOUNT_PK)); - await Promise.all( - account.tokenConditionalSwaps.map((tcs, i) => { - if (!tcs.hasData) { - return Promise.resolve(); - } - client.tokenConditionalSwapCancel(group, account, i, tcs.id); - }), - ); + // await Promise.all( + // account.tokenConditionalSwaps.map((tcs, i) => { + // if (!tcs.hasData) { + // return Promise.resolve(); + // } + // client.tokenConditionalSwapCancel(group, account, i, tcs.id); + // }), + // ); - await client.tokenConditionalSwapStopLoss( - group, - account, - group.getFirstBankByTokenIndex(0 as TokenIndex).mint, - group.getFirstBankByTokenIndex(6 as TokenIndex).mint, - account.getTokenBalanceUi( - group.getFirstBankByTokenIndex(6 as TokenIndex), - ), - null, - group.getFirstBankByTokenIndex(6 as TokenIndex).uiPrice * 1.1, - 0, - 2, - ); + // console.log(group.getPriceImpactByTokenIndex(6 as TokenIndex, 10000)); - account = await client.getMangoAccount(new PublicKey(MANGO_ACCOUNT_PK)); + // await client.tokenConditionalSwapStopLoss( + // group, + // account, + // group.getFirstBankByTokenIndex(0 as TokenIndex).mint, + // group.getFirstBankByTokenIndex(6 as TokenIndex).mint, + // account.getTokenBalanceUi( + // group.getFirstBankByTokenIndex(6 as TokenIndex), + // ), + // null, + // group.getFirstBankByTokenIndex(6 as TokenIndex).uiPrice * 1.1, + // 0, + // 2, + // ); + + // account = await client.getMangoAccount(new PublicKey(MANGO_ACCOUNT_PK)); console.log(account.tokenConditionalSwaps[0].toString(group)); } catch (error) { console.log(error); diff --git a/ts/client/src/accounts/group.ts b/ts/client/src/accounts/group.ts index 0b9461361..a1f180cfc 100644 --- a/ts/client/src/accounts/group.ts +++ b/ts/client/src/accounts/group.ts @@ -506,7 +506,7 @@ export class Group { } /** - * Returns a price impact, between 0 to 1 for a token, + * Returns a price impact in percentage, between 0 to 100 for a token, * returns -1 if data is bad */ public getPriceImpactByTokenIndex( diff --git a/ts/client/src/accounts/mangoAccount.ts b/ts/client/src/accounts/mangoAccount.ts index cdadfdaf3..c17ac6d59 100644 --- a/ts/client/src/accounts/mangoAccount.ts +++ b/ts/client/src/accounts/mangoAccount.ts @@ -204,10 +204,6 @@ export class MangoAccount { return this.perps.filter((perp) => perp.isActive()); } - public tokenConditionalSwapsActive(): TokenConditionalSwap[] { - return this.tokenConditionalSwaps.filter((tcs) => tcs.hasData); - } - public perpOrdersActive(): PerpOo[] { return this.perpOpenOrders.filter( (oo) => oo.orderMarket !== PerpOo.OrderMarketUnset, @@ -1835,7 +1831,7 @@ export class TokenConditionalSwap { return toUiDecimals(this.sold, sellBank.mintDecimals); } - getExpiryTimestamp(): number { + getExpiryTimestampInEpochSeconds(): number { return this.expiryTimestamp.toNumber(); } diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index e7874d181..84bbaaf1d 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -66,7 +66,12 @@ import { TokenEditParams, buildIxGate, } from './clientIxParamBuilder'; -import { MANGO_V4_ID, OPENBOOK_PROGRAM_ID, RUST_U64_MAX } from './constants'; +import { + MANGO_V4_ID, + OPENBOOK_PROGRAM_ID, + RUST_U64_MAX, + USDC_MINT, +} from './constants'; import { Id } from './ids'; import { IDL, MangoV4 } from './mango_v4'; import { I80F48 } from './numbers/I80F48'; @@ -3369,29 +3374,62 @@ export class MangoClient { return await this.sendAndConfirmTransactionForGroup(group, [ix]); } + /** + * Example: + * For a stop loss on SOL, assuming SOL/USDC pair + * priceLowerLimit - e.g. + * + * @param group + * @param account + * @param sellMintPk - would be SOL mint + * @param priceLowerLimit - priceLowerLimit would be greater than priceUpperLimit e.g. if SOL is at 25$, then priceLowerLimit could be 22$ + * @param buyMintPk - would be USDC mint + * @param priceUpperLimit - priceLowerLimit would be greater than priceUpperLimit e.g. if SOL is at 25$, then priceUpperLimit could be 0$ + * @param maxSell - max ui amount of tokens to sell, e.g. account.getTokenBalanceUi(solBank) + * @param pricePremiumFraction - premium in % the liquidator earns for executing this stop loss, this can be the slippage usually found for a particular size plus some buffer + * @param expiryTimestamp - is epoch in seconds at which this stop loss should expire, set null if you want it to never expire + * @returns + */ public async tokenConditionalSwapStopLoss( group: Group, account: MangoAccount, - buyMintPk: PublicKey, sellMintPk: PublicKey, - maxSell: number, + priceLowerLimit: number, + buyMintPk: PublicKey | null, + priceUpperLimit: number | null, + maxSell: number | null, + pricePremiumFraction: number | null, expiryTimestamp: number | null, - priceLowerLimit: number, // Note: priceLowerLimit should be higher than priceUpperLimit - priceUpperLimit: number, - pricePremiumFraction: number, ): Promise { - const buyBank: Bank = group.getFirstBankByMint(buyMintPk); + const buyBank: Bank = group.getFirstBankByMint(buyMintPk ?? USDC_MINT); const sellBank: Bank = group.getFirstBankByMint(sellMintPk); + + priceUpperLimit = priceUpperLimit ?? 0; + maxSell = maxSell ?? account.getTokenBalanceUi(sellBank); + pricePremiumFraction = group.getPriceImpactByTokenIndex( + sellBank.tokenIndex, + maxSell * sellBank.uiPrice, + ); + pricePremiumFraction = + pricePremiumFraction != -1 ? pricePremiumFraction : 0.3; + const expiryTimestampBn = + expiryTimestamp !== null ? new BN(expiryTimestamp) : U64_MAX_BN; + const tcsIx = await this.program.methods .tokenConditionalSwapCreate( U64_MAX_BN, toNative(maxSell, sellBank.mintDecimals), - expiryTimestamp !== null ? new BN(expiryTimestamp) : U64_MAX_BN, + expiryTimestampBn, (1 / priceLowerLimit) * Math.pow(10, sellBank.mintDecimals - buyBank.mintDecimals), (1 / priceUpperLimit) * Math.pow(10, sellBank.mintDecimals - buyBank.mintDecimals), - pricePremiumFraction / 100, + pricePremiumFraction != null + ? pricePremiumFraction / 100 + : group.getPriceImpactByTokenIndex( + sellBank.tokenIndex, + maxSell * sellBank.uiPrice, + ), true, false, ) diff --git a/ts/client/src/constants/index.ts b/ts/client/src/constants/index.ts index e0a96c385..45d7d5fff 100644 --- a/ts/client/src/constants/index.ts +++ b/ts/client/src/constants/index.ts @@ -21,3 +21,5 @@ export const MANGO_V4_ID = { devnet: new PublicKey('4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg'), 'mainnet-beta': new PublicKey('4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg'), }; + +export const USDC_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'); diff --git a/ts/client/src/risk.ts b/ts/client/src/risk.ts index b6dafef5d..c11de3791 100644 --- a/ts/client/src/risk.ts +++ b/ts/client/src/risk.ts @@ -53,7 +53,7 @@ export type PriceImpact = { }; /** - * Returns price impact in bps, + * Returns price impact in bps i.e. 0 to 10,000 * returns -1 if data is missing */ export function computePriceImpactOnJup( From fbe0056202365764bf2667f7218d207d46761e71 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 28 Jul 2023 15:44:52 +0200 Subject: [PATCH 61/90] disable for now Signed-off-by: microwavedcola1 --- package.json | 2 +- ts/client/src/client.ts | 110 ++++++++++++++++++++-------------------- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/package.json b/package.json index f1b93702a..fd04a36a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.17.29", + "version": "0.17.30", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index 84bbaaf1d..cb4643c49 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -3461,64 +3461,64 @@ export class MangoClient { return await this.sendAndConfirmTransactionForGroup(group, ixs); } - public async tokenConditionalSwapBuyLimit( - group: Group, - account: MangoAccount, - buyMintPk: PublicKey, - sellMintPk: PublicKey, - maxBuy: number, - expiryTimestamp: number | null, - priceLowerLimit: number, // Note: priceLowerLimit should be lower than priceUpperLimit - priceUpperLimit: number, - pricePremiumFraction: number, - ): Promise { - const buyBank: Bank = group.getFirstBankByMint(buyMintPk); - const sellBank: Bank = group.getFirstBankByMint(sellMintPk); - priceLowerLimit = - priceLowerLimit * - Math.pow(10, sellBank.mintDecimals - buyBank.mintDecimals); - priceUpperLimit = - priceUpperLimit * - Math.pow(10, sellBank.mintDecimals - buyBank.mintDecimals); + // public async tokenConditionalSwapBuyLimit( + // group: Group, + // account: MangoAccount, + // buyMintPk: PublicKey, + // sellMintPk: PublicKey, + // maxBuy: number, + // expiryTimestamp: number | null, + // priceLowerLimit: number, // Note: priceLowerLimit should be lower than priceUpperLimit + // priceUpperLimit: number, + // pricePremiumFraction: number, + // ): Promise { + // const buyBank: Bank = group.getFirstBankByMint(buyMintPk); + // const sellBank: Bank = group.getFirstBankByMint(sellMintPk); + // priceLowerLimit = + // priceLowerLimit * + // Math.pow(10, sellBank.mintDecimals - buyBank.mintDecimals); + // priceUpperLimit = + // priceUpperLimit * + // Math.pow(10, sellBank.mintDecimals - buyBank.mintDecimals); - const tcsIx = await this.program.methods - .tokenConditionalSwapCreate( - toNative(maxBuy, buyBank.mintDecimals), - U64_MAX_BN, - expiryTimestamp !== null ? new BN(expiryTimestamp) : U64_MAX_BN, - priceLowerLimit, - priceUpperLimit, - pricePremiumFraction / 100, - true, - false, - ) - .accounts({ - group: group.publicKey, - account: account.publicKey, - authority: (this.program.provider as AnchorProvider).wallet.publicKey, - buyBank: buyBank.publicKey, - sellBank: sellBank.publicKey, - }) - .instruction(); + // const tcsIx = await this.program.methods + // .tokenConditionalSwapCreate( + // toNative(maxBuy, buyBank.mintDecimals), + // U64_MAX_BN, + // expiryTimestamp !== null ? new BN(expiryTimestamp) : U64_MAX_BN, + // priceLowerLimit, + // priceUpperLimit, + // pricePremiumFraction / 100, + // true, + // false, + // ) + // .accounts({ + // group: group.publicKey, + // account: account.publicKey, + // authority: (this.program.provider as AnchorProvider).wallet.publicKey, + // buyBank: buyBank.publicKey, + // sellBank: sellBank.publicKey, + // }) + // .instruction(); - const ixs: TransactionInstruction[] = []; - if (account.tokenConditionalSwaps.length == 0) { - ixs.push( - await this.accountExpandV2Ix( - group, - account, - account.tokens.length, - account.serum3.length, - account.perps.length, - account.perpOpenOrders.length, - 8, - ), - ); - } - ixs.push(tcsIx); + // const ixs: TransactionInstruction[] = []; + // if (account.tokenConditionalSwaps.length == 0) { + // ixs.push( + // await this.accountExpandV2Ix( + // group, + // account, + // account.tokens.length, + // account.serum3.length, + // account.perps.length, + // account.perpOpenOrders.length, + // 8, + // ), + // ); + // } + // ixs.push(tcsIx); - return await this.sendAndConfirmTransactionForGroup(group, ixs); - } + // return await this.sendAndConfirmTransactionForGroup(group, ixs); + // } public async tokenConditionalSwapCreate( group: Group, From 5f7d704911e8d626290e562fefe81e8fbb02fe92 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 28 Jul 2023 15:57:25 +0200 Subject: [PATCH 62/90] improve tcs further Signed-off-by: microwavedcola1 --- package.json | 2 +- ts/client/src/accounts/mangoAccount.ts | 4 ++++ ts/client/src/client.ts | 20 ++++++++++---------- ts/client/src/constants/index.ts | 4 +++- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index fd04a36a0..440d10a9a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.17.30", + "version": "0.18.0", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { diff --git a/ts/client/src/accounts/mangoAccount.ts b/ts/client/src/accounts/mangoAccount.ts index c17ac6d59..bd32f21a7 100644 --- a/ts/client/src/accounts/mangoAccount.ts +++ b/ts/client/src/accounts/mangoAccount.ts @@ -188,6 +188,10 @@ export class MangoAccount { return this.serum3.filter((serum3) => serum3.isActive()); } + public tokenConditionalSwapsActive(): TokenConditionalSwap[] { + return this.tokenConditionalSwaps.filter((tcs) => tcs.hasData); + } + public perpPositionExistsForMarket(perpMarket: PerpMarket): boolean { return this.perps.some( (pp) => pp.isActive() && pp.marketIndex == perpMarket.perpMarketIndex, diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index cb4643c49..c9aacaea9 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -3577,15 +3577,15 @@ export class MangoClient { public async tokenConditionalSwapCancel( group: Group, account: MangoAccount, - tokenConditionalSwapIndex: number, tokenConditionalSwapId: BN, ): Promise { - const tcs = account - .tokenConditionalSwapsActive() - .find((tcs) => tcs.id.eq(tokenConditionalSwapId)); - if (!tcs) { + const tokenConditionalSwapIndex = account.tokenConditionalSwaps.findIndex( + (tcs) => tcs.id.eq(tokenConditionalSwapId), + ); + if (tokenConditionalSwapIndex == -1) { throw new Error('tcs with id not found'); } + const tcs = account.tokenConditionalSwaps[tokenConditionalSwapIndex]; const buyBank = group.banksMapByTokenIndex.get(tcs.buyTokenIndex)![0]; const sellBank = group.banksMapByTokenIndex.get(tcs.sellTokenIndex)![0]; @@ -3611,17 +3611,17 @@ export class MangoClient { group: Group, liqee: MangoAccount, liqor: MangoAccount, - tokenConditionalSwapIndex: number, tokenConditionalSwapId: BN, maxBuyTokenToLiqee: number, maxSellTokenToLiqor: number, ): Promise { - const tcs = liqee - .tokenConditionalSwapsActive() - .find((tcs) => tcs.id.eq(tokenConditionalSwapId)); - if (!tcs) { + const tokenConditionalSwapIndex = liqee.tokenConditionalSwaps.findIndex( + (tcs) => tcs.id.eq(tokenConditionalSwapId), + ); + if (tokenConditionalSwapIndex == -1) { throw new Error('tcs with id not found'); } + const tcs = liqee.tokenConditionalSwaps[tokenConditionalSwapIndex]; const buyBank = group.banksMapByTokenIndex.get(tcs.buyTokenIndex)![0]; const sellBank = group.banksMapByTokenIndex.get(tcs.sellTokenIndex)![0]; diff --git a/ts/client/src/constants/index.ts b/ts/client/src/constants/index.ts index 45d7d5fff..1d8f39ea4 100644 --- a/ts/client/src/constants/index.ts +++ b/ts/client/src/constants/index.ts @@ -22,4 +22,6 @@ export const MANGO_V4_ID = { 'mainnet-beta': new PublicKey('4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg'), }; -export const USDC_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'); +export const USDC_MINT = new PublicKey( + 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', +); From 09ad578e888ca816257a086dd6bfbe886c19592d Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 28 Jul 2023 15:58:06 +0200 Subject: [PATCH 63/90] v0.18.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 440d10a9a..05c87b08f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.18.0", + "version": "0.18.1", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From ba9a1d0b35fb5cf6562a453f5e0a65db83ed3081 Mon Sep 17 00:00:00 2001 From: Vladimir Guguiev Date: Fri, 28 Jul 2023 16:09:05 +0200 Subject: [PATCH 64/90] Client: use createWithSeed when wrapping SOL (#651) --- ts/client/src/client.ts | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index c9aacaea9..51c9ea447 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -1314,33 +1314,41 @@ export class MangoClient { let wrappedSolAccount: Keypair | undefined; let preInstructions: TransactionInstruction[] = []; let postInstructions: TransactionInstruction[] = []; - const additionalSigners: Signer[] = []; if (mintPk.equals(NATIVE_MINT)) { - wrappedSolAccount = new Keypair(); + // 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( + mangoAccount.owner, + seed, + TOKEN_PROGRAM_ID, + ); + const lamports = nativeAmount.add(new BN(1e7)); preInstructions = [ - SystemProgram.createAccount({ + SystemProgram.createAccountWithSeed({ fromPubkey: mangoAccount.owner, - newAccountPubkey: wrappedSolAccount.publicKey, + basePubkey: mangoAccount.owner, + seed, + newAccountPubkey: wrappedSolAccount, lamports: lamports.toNumber(), space: 165, programId: TOKEN_PROGRAM_ID, }), createInitializeAccount3Instruction( - wrappedSolAccount.publicKey, + wrappedSolAccount, NATIVE_MINT, mangoAccount.owner, ), ]; postInstructions = [ createCloseAccountInstruction( - wrappedSolAccount.publicKey, + wrappedSolAccount, mangoAccount.owner, mangoAccount.owner, ), ]; - additionalSigners.push(wrappedSolAccount); } const healthRemainingAccounts: PublicKey[] = @@ -1366,11 +1374,11 @@ export class MangoClient { ) .instruction(); - return await this.sendAndConfirmTransactionForGroup( - group, - [...preInstructions, ix, ...postInstructions], - { additionalSigners }, - ); + return await this.sendAndConfirmTransactionForGroup(group, [ + ...preInstructions, + ix, + ...postInstructions, + ]); } public async tokenWithdraw( From d7abf175f0474654b262656f5bfb7e1036341573 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 28 Jul 2023 16:11:10 +0200 Subject: [PATCH 65/90] v0.18.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 05c87b08f..f207da104 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.18.1", + "version": "0.18.2", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From ca9b3cecf9ba24649226c8d470aa25e3b029538d Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 28 Jul 2023 16:35:14 +0200 Subject: [PATCH 66/90] Revert "Client: use createWithSeed when wrapping SOL (#651)" This reverts commit ba9a1d0b35fb5cf6562a453f5e0a65db83ed3081. --- ts/client/src/client.ts | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index 51c9ea447..c9aacaea9 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -1314,41 +1314,33 @@ export class MangoClient { let wrappedSolAccount: Keypair | undefined; let preInstructions: TransactionInstruction[] = []; let postInstructions: TransactionInstruction[] = []; + const additionalSigners: Signer[] = []; 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( - mangoAccount.owner, - seed, - TOKEN_PROGRAM_ID, - ); - + wrappedSolAccount = new Keypair(); const lamports = nativeAmount.add(new BN(1e7)); preInstructions = [ - SystemProgram.createAccountWithSeed({ + SystemProgram.createAccount({ fromPubkey: mangoAccount.owner, - basePubkey: mangoAccount.owner, - seed, - newAccountPubkey: wrappedSolAccount, + newAccountPubkey: wrappedSolAccount.publicKey, lamports: lamports.toNumber(), space: 165, programId: TOKEN_PROGRAM_ID, }), createInitializeAccount3Instruction( - wrappedSolAccount, + wrappedSolAccount.publicKey, NATIVE_MINT, mangoAccount.owner, ), ]; postInstructions = [ createCloseAccountInstruction( - wrappedSolAccount, + wrappedSolAccount.publicKey, mangoAccount.owner, mangoAccount.owner, ), ]; + additionalSigners.push(wrappedSolAccount); } const healthRemainingAccounts: PublicKey[] = @@ -1374,11 +1366,11 @@ export class MangoClient { ) .instruction(); - return await this.sendAndConfirmTransactionForGroup(group, [ - ...preInstructions, - ix, - ...postInstructions, - ]); + return await this.sendAndConfirmTransactionForGroup( + group, + [...preInstructions, ix, ...postInstructions], + { additionalSigners }, + ); } public async tokenWithdraw( From 6e7aa8f07524c2a21d592ad6577f8596badf6590 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 28 Jul 2023 16:36:03 +0200 Subject: [PATCH 67/90] v0.18.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f207da104..448a0cdf6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.18.2", + "version": "0.18.3", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From 1dc692c5ac21d2a71b6ce8a6fd1ddd429f20dedd Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Mon, 31 Jul 2023 06:48:58 +0200 Subject: [PATCH 68/90] add missing fields on the group Signed-off-by: microwavedcola1 --- ts/client/src/accounts/group.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ts/client/src/accounts/group.ts b/ts/client/src/accounts/group.ts index a1f180cfc..1bb672bab 100644 --- a/ts/client/src/accounts/group.ts +++ b/ts/client/src/accounts/group.ts @@ -47,6 +47,8 @@ export class Group { ixGate: BN; buybackFeesSwapMangoAccount: PublicKey; buybackFeesExpiryInterval: BN; + tokenConditionalSwapTakerFeeFraction: number; + tokenConditionalSwapMakerFeeFraction: number; }, ): Group { return new Group( @@ -68,6 +70,8 @@ export class Group { obj.ixGate, obj.buybackFeesSwapMangoAccount, obj.buybackFeesExpiryInterval, + obj.tokenConditionalSwapTakerFeeFraction, + obj.tokenConditionalSwapMakerFeeFraction, [], // addressLookupTablesList new Map(), // banksMapByName new Map(), // banksMapByMint @@ -104,6 +108,8 @@ export class Group { public ixGate: BN, public buybackFeesSwapMangoAccount: PublicKey, public buybackFeesExpiryInterval: BN, + public tokenConditionalSwapTakerFeeFraction: number, + public tokenConditionalSwapMakerFeeFraction: number, public addressLookupTablesList: AddressLookupTableAccount[], public banksMapByName: Map, public banksMapByMint: Map, From 681851caf8877408d9c5143c143cb1d71304dce7 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Mon, 31 Jul 2023 06:49:41 +0200 Subject: [PATCH 69/90] v0.18.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 448a0cdf6..81c72e171 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.18.3", + "version": "0.18.4", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From 6e42b041a8ecebad6df05768c1ab711b584a48ce Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Mon, 31 Jul 2023 13:39:09 +0200 Subject: [PATCH 70/90] v0.18.5 --- package.json | 2 +- ts/client/src/client.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 81c72e171..954ab05ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.18.4", + "version": "0.18.5", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index c9aacaea9..a00a3d819 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -3411,7 +3411,7 @@ export class MangoClient { maxSell * sellBank.uiPrice, ); pricePremiumFraction = - pricePremiumFraction != -1 ? pricePremiumFraction : 0.3; + pricePremiumFraction > 0 ? pricePremiumFraction : 0.3; const expiryTimestampBn = expiryTimestamp !== null ? new BN(expiryTimestamp) : U64_MAX_BN; From 68f0502577b99d05bee26aae2188c0aa586ad8d4 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Mon, 31 Jul 2023 14:54:39 +0200 Subject: [PATCH 71/90] cancel all for tcs Signed-off-by: microwavedcola1 --- ts/client/src/client.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index a00a3d819..53c5cb08e 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -3607,6 +3607,35 @@ export class MangoClient { return await this.sendAndConfirmTransactionForGroup(group, [ix]); } + public async tokenConditionalSwapCancelAll( + group: Group, + account: MangoAccount, + ): Promise { + const ixs = await Promise.all( + account.tokenConditionalSwaps + .filter((tcs) => tcs.hasData) + .map(async (tcs, i) => { + const buyBank = group.banksMapByTokenIndex.get(tcs.buyTokenIndex)![0]; + const sellBank = group.banksMapByTokenIndex.get( + tcs.sellTokenIndex, + )![0]; + return await this.program.methods + .tokenConditionalSwapCancel(i, new BN(tcs.id)) + .accounts({ + group: group.publicKey, + account: account.publicKey, + authority: (this.program.provider as AnchorProvider).wallet + .publicKey, + buyBank: buyBank.publicKey, + sellBank: sellBank.publicKey, + }) + .instruction(); + }), + ); + + return await this.sendAndConfirmTransactionForGroup(group, ixs); + } + public async tokenConditionalSwapTrigger( group: Group, liqee: MangoAccount, From 216d105ebd87b50922f6e6615feb765793d5977c Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Mon, 31 Jul 2023 14:55:27 +0200 Subject: [PATCH 72/90] v0.18.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 954ab05ee..85be11849 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.18.5", + "version": "0.18.6", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From ad5d0e305453e10dffaf5795febb39535165f9d9 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 4 Aug 2023 11:29:26 +0200 Subject: [PATCH 73/90] https://github.com/blockworks-foundation/mango-v4/pull/651 make transaction compatible with smart wallets Signed-off-by: microwavedcola1 --- ts/client/src/client.ts | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index 53c5cb08e..c6283db0e 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -1314,33 +1314,41 @@ export class MangoClient { let wrappedSolAccount: Keypair | undefined; let preInstructions: TransactionInstruction[] = []; let postInstructions: TransactionInstruction[] = []; - const additionalSigners: Signer[] = []; if (mintPk.equals(NATIVE_MINT)) { - wrappedSolAccount = new Keypair(); + // 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( + mangoAccount.owner, + seed, + TOKEN_PROGRAM_ID, + ); + const lamports = nativeAmount.add(new BN(1e7)); preInstructions = [ - SystemProgram.createAccount({ + SystemProgram.createAccountWithSeed({ fromPubkey: mangoAccount.owner, - newAccountPubkey: wrappedSolAccount.publicKey, + basePubkey: mangoAccount.owner, + seed, + newAccountPubkey: wrappedSolAccount, lamports: lamports.toNumber(), space: 165, programId: TOKEN_PROGRAM_ID, }), createInitializeAccount3Instruction( - wrappedSolAccount.publicKey, + wrappedSolAccount, NATIVE_MINT, mangoAccount.owner, ), ]; postInstructions = [ createCloseAccountInstruction( - wrappedSolAccount.publicKey, + wrappedSolAccount, mangoAccount.owner, mangoAccount.owner, ), ]; - additionalSigners.push(wrappedSolAccount); } const healthRemainingAccounts: PublicKey[] = @@ -1366,11 +1374,11 @@ export class MangoClient { ) .instruction(); - return await this.sendAndConfirmTransactionForGroup( - group, - [...preInstructions, ix, ...postInstructions], - { additionalSigners }, - ); + return await this.sendAndConfirmTransactionForGroup(group, [ + ...preInstructions, + ix, + ...postInstructions, + ]); } public async tokenWithdraw( From 02f840cb94ceb7749b7ee08f858a6df7001fa98c Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 4 Aug 2023 11:30:14 +0200 Subject: [PATCH 74/90] v0.18.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 85be11849..8b42b75d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.18.6", + "version": "0.18.7", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From 14a2821d87b88cc8ef0fdc344de7ed77b6ca5457 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 4 Aug 2023 19:59:04 +0200 Subject: [PATCH 75/90] Revert "https://github.com/blockworks-foundation/mango-v4/pull/651 make transaction compatible with smart wallets" This reverts commit ad5d0e305453e10dffaf5795febb39535165f9d9. --- ts/client/src/client.ts | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index c6283db0e..53c5cb08e 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -1314,41 +1314,33 @@ export class MangoClient { let wrappedSolAccount: Keypair | undefined; let preInstructions: TransactionInstruction[] = []; let postInstructions: TransactionInstruction[] = []; + const additionalSigners: Signer[] = []; 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( - mangoAccount.owner, - seed, - TOKEN_PROGRAM_ID, - ); - + wrappedSolAccount = new Keypair(); const lamports = nativeAmount.add(new BN(1e7)); preInstructions = [ - SystemProgram.createAccountWithSeed({ + SystemProgram.createAccount({ fromPubkey: mangoAccount.owner, - basePubkey: mangoAccount.owner, - seed, - newAccountPubkey: wrappedSolAccount, + newAccountPubkey: wrappedSolAccount.publicKey, lamports: lamports.toNumber(), space: 165, programId: TOKEN_PROGRAM_ID, }), createInitializeAccount3Instruction( - wrappedSolAccount, + wrappedSolAccount.publicKey, NATIVE_MINT, mangoAccount.owner, ), ]; postInstructions = [ createCloseAccountInstruction( - wrappedSolAccount, + wrappedSolAccount.publicKey, mangoAccount.owner, mangoAccount.owner, ), ]; + additionalSigners.push(wrappedSolAccount); } const healthRemainingAccounts: PublicKey[] = @@ -1374,11 +1366,11 @@ export class MangoClient { ) .instruction(); - return await this.sendAndConfirmTransactionForGroup(group, [ - ...preInstructions, - ix, - ...postInstructions, - ]); + return await this.sendAndConfirmTransactionForGroup( + group, + [...preInstructions, ix, ...postInstructions], + { additionalSigners }, + ); } public async tokenWithdraw( From 3356c45d5ab5ed33be7dfb0c84051cec1ce9d8ae Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 4 Aug 2023 20:00:11 +0200 Subject: [PATCH 76/90] v0.18.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8b42b75d1..dda19a9db 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.18.7", + "version": "0.18.8", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From 43580ca26ab55b989f7179d002d3a154094700a0 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Mon, 7 Aug 2023 09:35:09 +0200 Subject: [PATCH 77/90] expose idl Signed-off-by: microwavedcola1 --- ts/client/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ts/client/src/index.ts b/ts/client/src/index.ts index 04b10883c..a70a20bec 100644 --- a/ts/client/src/index.ts +++ b/ts/client/src/index.ts @@ -19,6 +19,7 @@ export { buildIxGate, } from './clientIxParamBuilder'; export * from './constants'; +export * from './mango_v4'; export * from './numbers/I80F48'; export * from './risk'; export * from './router'; From 66786410c3bb69632fa58bbf9f83df4484e3a6e1 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Mon, 7 Aug 2023 09:35:57 +0200 Subject: [PATCH 78/90] v0.18.9 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dda19a9db..418ca64f5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.18.8", + "version": "0.18.9", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From 776545fcdd3d466f5aeb69c385fbf929c119318d Mon Sep 17 00:00:00 2001 From: microwavedcola1 <89031858+microwavedcola1@users.noreply.github.com> Date: Mon, 7 Aug 2023 13:09:19 +0200 Subject: [PATCH 79/90] token conditional swaps, fixes from review (#653) * tcs fixes from review, and easy to use helper functions * Fixes from review Signed-off-by: microwavedcola1 * Fixes from review Signed-off-by: microwavedcola1 * Fixes from review Signed-off-by: microwavedcola1 * Fixes from review Signed-off-by: microwavedcola1 --------- Signed-off-by: microwavedcola1 --- .../liqtest/liqtest-make-candidates.ts | 1 - .../liqtest/liqtest-make-tcs-candidates.ts | 18 +- ts/client/src/accounts/mangoAccount.ts | 6 +- ts/client/src/client.ts | 331 ++++++++++++------ ts/client/src/utils.ts | 17 + 5 files changed, 243 insertions(+), 130 deletions(-) diff --git a/ts/client/scripts/liqtest/liqtest-make-candidates.ts b/ts/client/scripts/liqtest/liqtest-make-candidates.ts index 4efa8d3be..b64220520 100644 --- a/ts/client/scripts/liqtest/liqtest-make-candidates.ts +++ b/ts/client/scripts/liqtest/liqtest-make-candidates.ts @@ -1,6 +1,5 @@ import { AnchorProvider, BN, Wallet } from '@coral-xyz/anchor'; import { Cluster, Connection, Keypair, PublicKey } from '@solana/web3.js'; -import { assert } from 'console'; import fs from 'fs'; import { Bank } from '../../src/accounts/bank'; import { MangoAccount } from '../../src/accounts/mangoAccount'; diff --git a/ts/client/scripts/liqtest/liqtest-make-tcs-candidates.ts b/ts/client/scripts/liqtest/liqtest-make-tcs-candidates.ts index 987965eff..66f54d9ee 100644 --- a/ts/client/scripts/liqtest/liqtest-make-tcs-candidates.ts +++ b/ts/client/scripts/liqtest/liqtest-make-tcs-candidates.ts @@ -1,19 +1,9 @@ import { AnchorProvider, BN, Wallet } from '@coral-xyz/anchor'; import { Cluster, Connection, Keypair, PublicKey } from '@solana/web3.js'; -import { assert } from 'console'; import fs from 'fs'; import { Bank } from '../../src/accounts/bank'; import { MangoAccount } from '../../src/accounts/mangoAccount'; -import { - PerpMarket, - PerpOrderSide, - PerpOrderType, -} from '../../src/accounts/perp'; -import { - Serum3OrderType, - Serum3SelfTradeBehavior, - Serum3Side, -} from '../../src/accounts/serum3'; +import { PerpMarket } from '../../src/accounts/perp'; import { Builder } from '../../src/builder'; import { MangoClient } from '../../src/client'; import { @@ -181,7 +171,7 @@ async function main() { accounts2.find((account) => account.name == 'LIQTEST, LIQEE1'), ); await client.accountExpandV2(group, account, 4, 4, 4, 4, 4); - await client.tokenConditionalSwapCreate( + await client.tokenConditionalSwapCreateOld( group, account, MINTS.get('SOL')!, @@ -203,7 +193,7 @@ async function main() { accounts2.find((account) => account.name == 'LIQTEST, LIQEE2'), ); await client.accountExpandV2(group, account, 4, 4, 4, 4, 4); - await client.tokenConditionalSwapCreate( + await client.tokenConditionalSwapCreateOld( group, account, MINTS.get('SOL')!, @@ -225,7 +215,7 @@ async function main() { accounts2.find((account) => account.name == 'LIQTEST, LIQEE3'), ); await client.accountExpandV2(group, account, 4, 4, 4, 4, 4); - await client.tokenConditionalSwapCreate( + await client.tokenConditionalSwapCreateOld( group, account, MINTS.get('SOL')!, diff --git a/ts/client/src/accounts/mangoAccount.ts b/ts/client/src/accounts/mangoAccount.ts index bd32f21a7..7f71ec370 100644 --- a/ts/client/src/accounts/mangoAccount.ts +++ b/ts/client/src/accounts/mangoAccount.ts @@ -11,6 +11,7 @@ import { toNativeI80F48, toUiDecimals, toUiDecimalsForQuote, + toUiSellPerBuyTokenPrice, } from '../utils'; import { Bank, TokenIndex } from './bank'; import { Group } from './group'; @@ -1845,9 +1846,10 @@ export class TokenConditionalSwap { ): number { const buyBank = this.getBuyToken(group); const sellBank = this.getSellToken(group); - const sellTokenPerBuyTokenUi = toUiDecimals( + const sellTokenPerBuyTokenUi = toUiSellPerBuyTokenPrice( sellTokenPerBuyTokenNative, - sellBank.mintDecimals - buyBank.mintDecimals, + sellBank, + buyBank, ); // Below are workarounds to know when to show an inverted price in ui diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index 53c5cb08e..885985ae1 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -66,12 +66,7 @@ import { TokenEditParams, buildIxGate, } from './clientIxParamBuilder'; -import { - MANGO_V4_ID, - OPENBOOK_PROGRAM_ID, - RUST_U64_MAX, - USDC_MINT, -} from './constants'; +import { MANGO_V4_ID, OPENBOOK_PROGRAM_ID, RUST_U64_MAX } from './constants'; import { Id } from './ids'; import { IDL, MangoV4 } from './mango_v4'; import { I80F48 } from './numbers/I80F48'; @@ -82,10 +77,13 @@ import { createAssociatedTokenAccountIdempotentInstruction, getAssociatedTokenAddress, toNative, + toNativeSellPerBuyTokenPrice, } from './utils'; import { sendTransaction } from './utils/rpc'; import { NATIVE_MINT, TOKEN_PROGRAM_ID } from './utils/spl'; +export const DEFAULT_TOKEN_CONDITIONAL_SWAP_COUNT = 8; + export enum AccountRetriever { Scanning, Fixed, @@ -3374,64 +3372,230 @@ export class MangoClient { return await this.sendAndConfirmTransactionForGroup(group, [ix]); } - /** - * Example: - * For a stop loss on SOL, assuming SOL/USDC pair - * priceLowerLimit - e.g. - * - * @param group - * @param account - * @param sellMintPk - would be SOL mint - * @param priceLowerLimit - priceLowerLimit would be greater than priceUpperLimit e.g. if SOL is at 25$, then priceLowerLimit could be 22$ - * @param buyMintPk - would be USDC mint - * @param priceUpperLimit - priceLowerLimit would be greater than priceUpperLimit e.g. if SOL is at 25$, then priceUpperLimit could be 0$ - * @param maxSell - max ui amount of tokens to sell, e.g. account.getTokenBalanceUi(solBank) - * @param pricePremiumFraction - premium in % the liquidator earns for executing this stop loss, this can be the slippage usually found for a particular size plus some buffer - * @param expiryTimestamp - is epoch in seconds at which this stop loss should expire, set null if you want it to never expire - * @returns - */ - public async tokenConditionalSwapStopLoss( + public async tcsTakeProfitOnDeposit( group: Group, account: MangoAccount, - sellMintPk: PublicKey, - priceLowerLimit: number, - buyMintPk: PublicKey | null, - priceUpperLimit: number | null, - maxSell: number | null, - pricePremiumFraction: number | null, + sellBank: Bank, + buyBank: Bank, + thresholdPriceUi: number, + thresholdPriceInSellPerBuyToken: boolean, + maxSellUi: number | null, + pricePremium: number | null, expiryTimestamp: number | null, ): Promise { - const buyBank: Bank = group.getFirstBankByMint(buyMintPk ?? USDC_MINT); - const sellBank: Bank = group.getFirstBankByMint(sellMintPk); + if (account.getTokenBalanceUi(sellBank) < 0) { + throw new Error( + `Only allowed to take profits on deposits! Current balance ${account.getTokenBalanceUi( + sellBank, + )}`, + ); + } - priceUpperLimit = priceUpperLimit ?? 0; - maxSell = maxSell ?? account.getTokenBalanceUi(sellBank); - pricePremiumFraction = group.getPriceImpactByTokenIndex( - sellBank.tokenIndex, - maxSell * sellBank.uiPrice, + return await this.tokenConditionalSwapCreate( + group, + account, + sellBank, + buyBank, + thresholdPriceUi, + thresholdPriceInSellPerBuyToken, + Number.MAX_SAFE_INTEGER, + maxSellUi ?? account.getTokenBalanceUi(sellBank), + 'TakeProfitOnDeposit', + pricePremium, + true, + false, + expiryTimestamp, ); - pricePremiumFraction = - pricePremiumFraction > 0 ? pricePremiumFraction : 0.3; + } + + public async tcsStopLossOnDeposit( + group: Group, + account: MangoAccount, + sellBank: Bank, + buyBank: Bank, + thresholdPriceUi: number, + thresholdPriceInSellPerBuyToken: boolean, + maxSellUi: number | null, + pricePremium: number | null, + expiryTimestamp: number | null, + ): Promise { + if (account.getTokenBalanceUi(sellBank) < 0) { + throw new Error( + `Only allowed to set a stop loss on deposits! Current balance ${account.getTokenBalanceUi( + sellBank, + )}`, + ); + } + + return await this.tokenConditionalSwapCreate( + group, + account, + sellBank, + buyBank, + thresholdPriceUi, + thresholdPriceInSellPerBuyToken, + Number.MAX_SAFE_INTEGER, + maxSellUi ?? account.getTokenBalanceUi(sellBank), + 'StopLossOnDeposit', + pricePremium, + true, + false, + expiryTimestamp, + ); + } + + public async tcsTakeProfitOnBorrow( + group: Group, + account: MangoAccount, + sellBank: Bank, + buyBank: Bank, + thresholdPriceUi: number, + thresholdPriceInSellPerBuyToken: boolean, + maxBuyUi: number | null, + pricePremium: number | null, + allowMargin: boolean | null, + expiryTimestamp: number | null, + ): Promise { + if (account.getTokenBalanceUi(buyBank) > 0) { + throw new Error( + `Only allowed to take profits on borrows! Current balance ${account.getTokenBalanceUi( + buyBank, + )}`, + ); + } + + return await this.tokenConditionalSwapCreate( + group, + account, + sellBank, + buyBank, + thresholdPriceUi, + thresholdPriceInSellPerBuyToken, + maxBuyUi ?? -account.getTokenBalanceUi(buyBank), + Number.MAX_SAFE_INTEGER, + 'TakeProfitOnBorrow', + pricePremium, + false, + allowMargin ?? false, + expiryTimestamp, + ); + } + + public async tcsStopLossOnBorrow( + group: Group, + account: MangoAccount, + sellBank: Bank, + buyBank: Bank, + thresholdPriceUi: number, + thresholdPriceInSellPerBuyToken: boolean, + maxBuyUi: number | null, + pricePremium: number | null, + allowMargin: boolean | null, + expiryTimestamp: number | null, + ): Promise { + if (account.getTokenBalanceUi(buyBank) > 0) { + throw new Error( + `Only allowed to set stop loss on borrows! Current balance ${account.getTokenBalanceUi( + buyBank, + )}`, + ); + } + + return await this.tokenConditionalSwapCreate( + group, + account, + sellBank, + buyBank, + thresholdPriceUi, + thresholdPriceInSellPerBuyToken, + maxBuyUi ?? -account.getTokenBalanceUi(buyBank), + Number.MAX_SAFE_INTEGER, + 'StopLossOnBorrow', + pricePremium, + false, + allowMargin ?? false, + expiryTimestamp, + ); + } + + public async tokenConditionalSwapCreate( + group: Group, + account: MangoAccount, + sellBank: Bank, + buyBank: Bank, + thresholdPriceUi: number, + thresholdPriceInSellPerBuyToken: boolean, + maxBuyUi: number, + maxSellUi: number, + tcsIntention: + | 'TakeProfitOnDeposit' + | 'StopLossOnDeposit' + | 'TakeProfitOnBorrow' + | 'StopLossOnBorrow' + | null, + pricePremium: number | null, + allowCreatingDeposits: boolean, + allowCreatingBorrows: boolean, + expiryTimestamp: number | null, + ): Promise { + const maxBuy = toNative(maxBuyUi, buyBank.mintDecimals); + const maxSell = toNative(maxSellUi, sellBank.mintDecimals); + + if (!thresholdPriceInSellPerBuyToken) { + thresholdPriceUi = 1 / thresholdPriceUi; + } + + 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; + } + const expiryTimestampBn = expiryTimestamp !== null ? new BN(expiryTimestamp) : U64_MAX_BN; + if (!pricePremium) { + const buyTokenPriceImpact = group.getPriceImpactByTokenIndex( + buyBank.tokenIndex, + 5000, + ); + const sellTokenPriceImpact = group.getPriceImpactByTokenIndex( + sellBank.tokenIndex, + 5000, + ); + pricePremium = + ((1 + buyTokenPriceImpact / 100) * (1 + sellTokenPriceImpact / 100) - + 1) * + 100; + } + const pricePremiumFraction = pricePremium > 0 ? pricePremium / 100 : 0.03; + const tcsIx = await this.program.methods .tokenConditionalSwapCreate( - U64_MAX_BN, - toNative(maxSell, sellBank.mintDecimals), + maxBuy, + maxSell, expiryTimestampBn, - (1 / priceLowerLimit) * - Math.pow(10, sellBank.mintDecimals - buyBank.mintDecimals), - (1 / priceUpperLimit) * - Math.pow(10, sellBank.mintDecimals - buyBank.mintDecimals), - pricePremiumFraction != null - ? pricePremiumFraction / 100 - : group.getPriceImpactByTokenIndex( - sellBank.tokenIndex, - maxSell * sellBank.uiPrice, - ), - true, - false, + lowerLimit, + upperLimit, + pricePremiumFraction, + allowCreatingDeposits, + allowCreatingBorrows, ) .accounts({ group: group.publicKey, @@ -3452,7 +3616,7 @@ export class MangoClient { account.serum3.length, account.perps.length, account.perpOpenOrders.length, - 8, + DEFAULT_TOKEN_CONDITIONAL_SWAP_COUNT, ), ); } @@ -3461,66 +3625,7 @@ export class MangoClient { return await this.sendAndConfirmTransactionForGroup(group, ixs); } - // public async tokenConditionalSwapBuyLimit( - // group: Group, - // account: MangoAccount, - // buyMintPk: PublicKey, - // sellMintPk: PublicKey, - // maxBuy: number, - // expiryTimestamp: number | null, - // priceLowerLimit: number, // Note: priceLowerLimit should be lower than priceUpperLimit - // priceUpperLimit: number, - // pricePremiumFraction: number, - // ): Promise { - // const buyBank: Bank = group.getFirstBankByMint(buyMintPk); - // const sellBank: Bank = group.getFirstBankByMint(sellMintPk); - // priceLowerLimit = - // priceLowerLimit * - // Math.pow(10, sellBank.mintDecimals - buyBank.mintDecimals); - // priceUpperLimit = - // priceUpperLimit * - // Math.pow(10, sellBank.mintDecimals - buyBank.mintDecimals); - - // const tcsIx = await this.program.methods - // .tokenConditionalSwapCreate( - // toNative(maxBuy, buyBank.mintDecimals), - // U64_MAX_BN, - // expiryTimestamp !== null ? new BN(expiryTimestamp) : U64_MAX_BN, - // priceLowerLimit, - // priceUpperLimit, - // pricePremiumFraction / 100, - // true, - // false, - // ) - // .accounts({ - // group: group.publicKey, - // account: account.publicKey, - // authority: (this.program.provider as AnchorProvider).wallet.publicKey, - // buyBank: buyBank.publicKey, - // sellBank: sellBank.publicKey, - // }) - // .instruction(); - - // const ixs: TransactionInstruction[] = []; - // if (account.tokenConditionalSwaps.length == 0) { - // ixs.push( - // await this.accountExpandV2Ix( - // group, - // account, - // account.tokens.length, - // account.serum3.length, - // account.perps.length, - // account.perpOpenOrders.length, - // 8, - // ), - // ); - // } - // ixs.push(tcsIx); - - // return await this.sendAndConfirmTransactionForGroup(group, ixs); - // } - - public async tokenConditionalSwapCreate( + public async tokenConditionalSwapCreateRaw( group: Group, account: MangoAccount, buyMintPk: PublicKey, diff --git a/ts/client/src/utils.ts b/ts/client/src/utils.ts index f926d126e..1b9541539 100644 --- a/ts/client/src/utils.ts +++ b/ts/client/src/utils.ts @@ -9,6 +9,7 @@ import { VersionedTransaction, } from '@solana/web3.js'; import BN from 'bn.js'; +import { Bank } from './accounts/bank'; import { I80F48 } from './numbers/I80F48'; import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID } from './utils/spl'; @@ -38,6 +39,22 @@ export function toNative(uiAmount: number, decimals: number): BN { return new BN((uiAmount * Math.pow(10, decimals)).toFixed(0)); } +export function toNativeSellPerBuyTokenPrice( + price: number, + sellBank: Bank, + buyBank: Bank, +): number { + return price * Math.pow(10, sellBank.mintDecimals - buyBank.mintDecimals); +} + +export function toUiSellPerBuyTokenPrice( + price: number, + sellBank: Bank, + buyBank: Bank, +): number { + return toUiDecimals(price, sellBank.mintDecimals - buyBank.mintDecimals); +} + export function toUiDecimals( nativeAmount: BN | I80F48 | number, decimals: number, From a7247ad7e79c8c64a2f5372ccf249e18319562ca Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Mon, 7 Aug 2023 13:11:23 +0200 Subject: [PATCH 80/90] fix ts calls Signed-off-by: microwavedcola1 --- ts/client/scripts/liqtest/liqtest-make-tcs-candidates.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ts/client/scripts/liqtest/liqtest-make-tcs-candidates.ts b/ts/client/scripts/liqtest/liqtest-make-tcs-candidates.ts index 66f54d9ee..5a9f75c05 100644 --- a/ts/client/scripts/liqtest/liqtest-make-tcs-candidates.ts +++ b/ts/client/scripts/liqtest/liqtest-make-tcs-candidates.ts @@ -171,7 +171,7 @@ async function main() { accounts2.find((account) => account.name == 'LIQTEST, LIQEE1'), ); await client.accountExpandV2(group, account, 4, 4, 4, 4, 4); - await client.tokenConditionalSwapCreateOld( + await client.tokenConditionalSwapCreateRaw( group, account, MINTS.get('SOL')!, @@ -193,7 +193,7 @@ async function main() { accounts2.find((account) => account.name == 'LIQTEST, LIQEE2'), ); await client.accountExpandV2(group, account, 4, 4, 4, 4, 4); - await client.tokenConditionalSwapCreateOld( + await client.tokenConditionalSwapCreateRaw( group, account, MINTS.get('SOL')!, @@ -215,7 +215,7 @@ async function main() { accounts2.find((account) => account.name == 'LIQTEST, LIQEE3'), ); await client.accountExpandV2(group, account, 4, 4, 4, 4, 4); - await client.tokenConditionalSwapCreateOld( + await client.tokenConditionalSwapCreateRaw( group, account, MINTS.get('SOL')!, From 4c083a9a5690a3aa3c88d81dbbe6e75a503c1bb1 Mon Sep 17 00:00:00 2001 From: microwavedcola1 <89031858+microwavedcola1@users.noreply.github.com> Date: Mon, 7 Aug 2023 13:12:37 +0200 Subject: [PATCH 81/90] ts client: fix getMaxWithdrawWithBorrowForToken (#664) * fix max withdraw amount, wasnt taking into account scaled weights Signed-off-by: microwavedcola1 * Fixes from review Signed-off-by: microwavedcola1 --------- Signed-off-by: microwavedcola1 --- ts/client/src/accounts/bank.ts | 8 ++++++++ ts/client/src/accounts/mangoAccount.ts | 8 ++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/ts/client/src/accounts/bank.ts b/ts/client/src/accounts/bank.ts index 45f78df24..d285e02db 100644 --- a/ts/client/src/accounts/bank.ts +++ b/ts/client/src/accounts/bank.ts @@ -352,6 +352,14 @@ export class Bank implements BankForHealth { ); } + getAssetPrice(): I80F48 { + return this.price.min(I80F48.fromNumber(this.stablePriceModel.stablePrice)); + } + + getLiabPrice(): I80F48 { + return this.price.max(I80F48.fromNumber(this.stablePriceModel.stablePrice)); + } + get price(): I80F48 { if (this._price === undefined) { throw new Error( diff --git a/ts/client/src/accounts/mangoAccount.ts b/ts/client/src/accounts/mangoAccount.ts index 7f71ec370..89e5e2267 100644 --- a/ts/client/src/accounts/mangoAccount.ts +++ b/ts/client/src/accounts/mangoAccount.ts @@ -537,8 +537,8 @@ export class MangoAccount { let existingPositionHealthContrib = ZERO_I80F48(); if (existingTokenDeposits.gt(ZERO_I80F48())) { existingPositionHealthContrib = existingTokenDeposits - .mul(tokenBank.price) - .imul(tokenBank.initAssetWeight); + .mul(tokenBank.getAssetPrice()) + .imul(tokenBank.scaledInitAssetWeight(tokenBank.getAssetPrice())); } // Case 2: token deposits have higher contribution than initHealth, @@ -546,8 +546,8 @@ export class MangoAccount { if (existingPositionHealthContrib.gt(initHealth)) { const withdrawAbleExistingPositionHealthContrib = initHealth; return withdrawAbleExistingPositionHealthContrib - .div(tokenBank.initAssetWeight) - .div(tokenBank.price); + .div(tokenBank.scaledInitAssetWeight(tokenBank.getAssetPrice())) + .div(tokenBank.getAssetPrice()); } // Case 3: withdraw = withdraw existing deposits + borrows until initHealth reaches 0 From 688a632d53a25bb02e11527b048ec9effc0bab2f Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Mon, 7 Aug 2023 13:14:26 +0200 Subject: [PATCH 82/90] v0.18.11 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 418ca64f5..a78a41d42 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.18.9", + "version": "0.18.11", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From f46c633a4fe4cddd6b48616c9d8b9aae0da18bd0 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Tue, 8 Aug 2023 08:24:20 +0200 Subject: [PATCH 83/90] Fix max number Signed-off-by: microwavedcola1 --- ts/client/src/client.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index 885985ae1..fbb03dafe 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -3538,8 +3538,14 @@ export class MangoClient { allowCreatingBorrows: boolean, expiryTimestamp: number | null, ): Promise { - const maxBuy = toNative(maxBuyUi, buyBank.mintDecimals); - const maxSell = toNative(maxSellUi, sellBank.mintDecimals); + const maxBuy = + maxBuyUi == Number.MAX_SAFE_INTEGER + ? U64_MAX_BN + : toNative(maxBuyUi, buyBank.mintDecimals); + const maxSell = + maxBuyUi == Number.MAX_SAFE_INTEGER + ? U64_MAX_BN + : toNative(maxSellUi, sellBank.mintDecimals); if (!thresholdPriceInSellPerBuyToken) { thresholdPriceUi = 1 / thresholdPriceUi; From ada933453e3a283c916589041c5e3b7e6133070c Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Tue, 8 Aug 2023 08:25:00 +0200 Subject: [PATCH 84/90] v0.18.12 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a78a41d42..c9276f01d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.18.11", + "version": "0.18.12", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From cb3159811c7febb2cd286c25251309c27c3f37e8 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Tue, 8 Aug 2023 14:11:11 +0200 Subject: [PATCH 85/90] Fix max sell in tcs client code Signed-off-by: microwavedcola1 --- ts/client/src/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index fbb03dafe..f2fa15887 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -3543,7 +3543,7 @@ export class MangoClient { ? U64_MAX_BN : toNative(maxBuyUi, buyBank.mintDecimals); const maxSell = - maxBuyUi == Number.MAX_SAFE_INTEGER + maxSellUi == Number.MAX_SAFE_INTEGER ? U64_MAX_BN : toNative(maxSellUi, sellBank.mintDecimals); From b40a4e7e9e10daf770790c0b3f41810a4f0b10f2 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Tue, 8 Aug 2023 14:11:49 +0200 Subject: [PATCH 86/90] v0.18.13 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c9276f01d..bba86ae14 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.18.12", + "version": "0.18.13", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From 57554802489f520d730c0261e52bc2f2226e42bd Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Tue, 8 Aug 2023 14:59:43 +0200 Subject: [PATCH 87/90] threshold price ui helper for tcs in ts client Signed-off-by: microwavedcola1 --- .../scripts/archive/conditional-swaps.ts | 77 ++++++++++++++----- ts/client/src/accounts/mangoAccount.ts | 17 ++++ 2 files changed, 74 insertions(+), 20 deletions(-) diff --git a/ts/client/scripts/archive/conditional-swaps.ts b/ts/client/scripts/archive/conditional-swaps.ts index 746bbb5d3..5d70aef9b 100644 --- a/ts/client/scripts/archive/conditional-swaps.ts +++ b/ts/client/scripts/archive/conditional-swaps.ts @@ -1,6 +1,7 @@ import { AnchorProvider, Wallet } from '@coral-xyz/anchor'; import { Cluster, Connection, Keypair, PublicKey } from '@solana/web3.js'; import fs from 'fs'; +import { TokenIndex } from '../../src/accounts/bank'; import { MangoClient } from '../../src/client'; import { MANGO_V4_ID } from '../../src/constants'; @@ -37,32 +38,68 @@ async function main(): Promise { ); let account = await client.getMangoAccount(new PublicKey(MANGO_ACCOUNT_PK)); - // await Promise.all( - // account.tokenConditionalSwaps.map((tcs, i) => { - // if (!tcs.hasData) { - // return Promise.resolve(); - // } - // client.tokenConditionalSwapCancel(group, account, i, tcs.id); - // }), - // ); + await Promise.all( + account.tokenConditionalSwaps.map((tcs, i) => { + if (!tcs.hasData) { + return Promise.resolve(); + } + client.tokenConditionalSwapCancel(group, account, tcs.id); + }), + ); - // console.log(group.getPriceImpactByTokenIndex(6 as TokenIndex, 10000)); - - // await client.tokenConditionalSwapStopLoss( + // const sig = await client.tcsTakeProfitOnDeposit( // group, // account, - // group.getFirstBankByTokenIndex(0 as TokenIndex).mint, - // group.getFirstBankByTokenIndex(6 as TokenIndex).mint, - // account.getTokenBalanceUi( - // group.getFirstBankByTokenIndex(6 as TokenIndex), - // ), + // group.getFirstBankByTokenIndex(4 as TokenIndex), + // group.getFirstBankByTokenIndex(0 as TokenIndex), + // group.getFirstBankByTokenIndex(4 as TokenIndex).uiPrice + 1, + // false, + // null, + // null, // null, - // group.getFirstBankByTokenIndex(6 as TokenIndex).uiPrice * 1.1, - // 0, - // 2, // ); - // account = await client.getMangoAccount(new PublicKey(MANGO_ACCOUNT_PK)); + // const sig = await client.tcsStopLossOnDeposit( + // group, + // account, + // group.getFirstBankByTokenIndex(4 as TokenIndex), + // group.getFirstBankByTokenIndex(0 as TokenIndex), + // group.getFirstBankByTokenIndex(4 as TokenIndex).uiPrice - 1, + // false, + // null, + // null, + // null, + // ); + + // const sig = await client.tcsTakeProfitOnBorrow( + // group, + // account, + // group.getFirstBankByTokenIndex(0 as TokenIndex), + // group.getFirstBankByTokenIndex(4 as TokenIndex), + // group.getFirstBankByTokenIndex(4 as TokenIndex).uiPrice - 1, + // true, + // null, + // null, + // null, + // null, + // ); + + const sig = await client.tcsStopLossOnBorrow( + group, + account, + group.getFirstBankByTokenIndex(0 as TokenIndex), + group.getFirstBankByTokenIndex(4 as TokenIndex), + group.getFirstBankByTokenIndex(4 as TokenIndex).uiPrice + 1, + true, + null, + null, + null, + null, + ); + + console.log(sig); + + account = await client.getMangoAccount(new PublicKey(MANGO_ACCOUNT_PK)); console.log(account.tokenConditionalSwaps[0].toString(group)); } catch (error) { console.log(error); diff --git a/ts/client/src/accounts/mangoAccount.ts b/ts/client/src/accounts/mangoAccount.ts index 89e5e2267..4b6c8bbd3 100644 --- a/ts/client/src/accounts/mangoAccount.ts +++ b/ts/client/src/accounts/mangoAccount.ts @@ -1874,6 +1874,21 @@ export class TokenConditionalSwap { return this.priceLimitToUi(group, this.priceUpperLimit); } + 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); + + // Choose the price closest to oracle + if (o.sub(a).abs().lt(o.sub(b).abs())) { + return this.getPriceLowerLimitUi(group); + } + return this.getPriceUpperLimitUi(group); + } + getPricePremium(): number { return this.pricePremiumFraction * 100; } @@ -1905,6 +1920,8 @@ export class TokenConditionalSwap { group, )}, getPriceUpperLimitUi ${this.getPriceUpperLimitUi( group, + )}, getThresholdPriceUi ${this.getThresholdPriceUi( + group, )}, getPricePremium ${this.getPricePremium()}, expiry ${this.expiryTimestamp.toString()}`; } } From 6d837413002832d36135906158e62bf31bf5d321 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Tue, 8 Aug 2023 15:00:29 +0200 Subject: [PATCH 88/90] v0.18.14 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bba86ae14..e2e0736f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.18.13", + "version": "0.18.14", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From 9a2433263d057b633f75b68c593f0417aa35bfaa Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Wed, 9 Aug 2023 13:32:03 +0200 Subject: [PATCH 89/90] Disallowing PDA token owners currently causes errors when the user wallet is a Smart Wallet (PDA). Fix that Signed-off-by: microwavedcola1 --- ts/client/src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/client/src/utils.ts b/ts/client/src/utils.ts index 1b9541539..b5e219806 100644 --- a/ts/client/src/utils.ts +++ b/ts/client/src/utils.ts @@ -150,7 +150,7 @@ export async function buildFetch(): Promise< export async function getAssociatedTokenAddress( mint: PublicKey, owner: PublicKey, - allowOwnerOffCurve = false, + allowOwnerOffCurve = true, programId = TOKEN_PROGRAM_ID, associatedTokenProgramId = ASSOCIATED_TOKEN_PROGRAM_ID, ): Promise { From 25a90580fd43d15fded5800bdca64964e7d21fc5 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Wed, 9 Aug 2023 15:01:19 +0200 Subject: [PATCH 90/90] Fix group in script Signed-off-by: microwavedcola1 --- ts/client/scripts/archive/mb-user.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ts/client/scripts/archive/mb-user.ts b/ts/client/scripts/archive/mb-user.ts index bb6e55adc..cde12e652 100644 --- a/ts/client/scripts/archive/mb-user.ts +++ b/ts/client/scripts/archive/mb-user.ts @@ -1,5 +1,5 @@ import { AnchorProvider, Wallet } from '@coral-xyz/anchor'; -import { Connection, Keypair } from '@solana/web3.js'; +import { Connection, Keypair, PublicKey } from '@solana/web3.js'; import fs from 'fs'; import { HealthType, MangoAccount } from '../../src/accounts/mangoAccount'; import { @@ -33,7 +33,9 @@ async function main() { ); console.log(`Admin ${admin.publicKey.toBase58()}`); - const group = await client.getGroupForCreator(admin.publicKey, 2); + const group = await client.getGroup( + new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'), + ); console.log(`${group.toString()}`); // create + fetch account