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(