diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index dabed3da8..93c9e4d7c 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -56,6 +56,7 @@ import { createAssociatedTokenAccountIdempotentInstruction, getAssociatedTokenAddress, toNative, + U64_MAX_BN, } from './utils'; import { sendTransaction } from './utils/rpc'; @@ -809,6 +810,81 @@ export class MangoClient { ); } + public async emptyAndCloseMangoAccount( + group: Group, + mangoAccount: MangoAccount, + ): Promise { + const instructions: TransactionInstruction[] = []; + const healthAccountsToExclude: PublicKey[] = []; + + for (const serum3Account of mangoAccount.serum3Active()) { + const serum3Market = group.serum3MarketsMapByMarketIndex.get( + serum3Account.marketIndex, + )!; + + const closeOOIx = await this.serum3CloseOpenOrdersIx( + group, + mangoAccount, + serum3Market.serumMarketExternal, + ); + healthAccountsToExclude.push(serum3Account.openOrders); + instructions.push(closeOOIx); + } + + for (const perp of mangoAccount.perpActive()) { + const perpMarketIndex = perp.marketIndex; + const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex); + const deactivatingPositionIx = await this.perpDeactivatePositionIx( + group, + mangoAccount, + perpMarketIndex, + ); + healthAccountsToExclude.push(perpMarket.publicKey); + instructions.push(deactivatingPositionIx); + } + + for (const index in mangoAccount.tokensActive()) { + const indexNum = Number(index); + const accountsToExclude = [...healthAccountsToExclude]; + const token = mangoAccount.tokensActive()[indexNum]; + const bank = group.getFirstBankByTokenIndex(token.tokenIndex); + //to withdraw from all token accounts we need to exclude previous tokens pubkeys + //used to build health remaining accounts + if (indexNum !== 0) { + for (let i = indexNum; i--; i >= 0) { + const prevToken = mangoAccount.tokensActive()[i]; + const prevBank = group.getFirstBankByTokenIndex(prevToken.tokenIndex); + accountsToExclude.push(prevBank.publicKey, prevBank.oracle); + } + } + const withdrawIx = await this.tokenWithdrawNativeIx( + group, + mangoAccount, + bank.mint, + U64_MAX_BN, + false, + [...accountsToExclude], + ); + instructions.push(...withdrawIx); + } + + const closeIx = await this.program.methods + .accountClose(false) + .accounts({ + group: group.publicKey, + account: mangoAccount.publicKey, + owner: (this.program.provider as AnchorProvider).wallet.publicKey, + solDestination: mangoAccount.owner, + }) + .instruction(); + instructions.push(closeIx); + + return await this.sendAndConfirmTransaction( + [...instructions], + group.addressLookupTablesList, + ); + } + public async tokenDeposit( group: Group, mangoAccount: MangoAccount, @@ -917,22 +993,28 @@ export class MangoClient { allowBorrow: boolean, ): Promise { const nativeAmount = toNative(amount, group.getMintDecimals(mintPk)); - return await this.tokenWithdrawNative( + const ixes = await this.tokenWithdrawNativeIx( group, mangoAccount, mintPk, nativeAmount, allowBorrow, ); + + return await this.sendAndConfirmTransaction( + [...ixes], + group.addressLookupTablesList, + ); } - public async tokenWithdrawNative( + public async tokenWithdrawNativeIx( group: Group, mangoAccount: MangoAccount, mintPk: PublicKey, nativeAmount: BN, allowBorrow: boolean, - ): Promise { + healthAccountsToExclude: PublicKey[] = [], + ): Promise { const bank = group.getFirstBankByMint(mintPk); const tokenAccountPk = await getAssociatedTokenAddress( @@ -981,17 +1063,25 @@ export class MangoClient { tokenAccount: tokenAccountPk, }) .remainingAccounts( - healthRemainingAccounts.map( - (pk) => - ({ pubkey: pk, isWritable: false, isSigner: false } as AccountMeta), - ), + healthRemainingAccounts + .filter( + (accounts) => + !healthAccountsToExclude.find((accountsToExclude) => + accounts.equals(accountsToExclude), + ), + ) + .map( + (pk) => + ({ + pubkey: pk, + isWritable: false, + isSigner: false, + } as AccountMeta), + ), ) .instruction(); - return await this.sendAndConfirmTransaction( - [...preInstructions, ix, ...postInstructions], - group.addressLookupTablesList, - ); + return [...preInstructions, ix, ...postInstructions]; } // Serum @@ -1718,11 +1808,11 @@ export class MangoClient { ); } - public async perpDeactivatePosition( + public async perpDeactivatePositionIx( group: Group, mangoAccount: MangoAccount, perpMarketIndex: PerpMarketIndex, - ): Promise { + ): Promise { const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex); const healthRemainingAccounts: PublicKey[] = this.buildHealthRemainingAccounts( @@ -1746,7 +1836,23 @@ export class MangoClient { ({ pubkey: pk, isWritable: false, isSigner: false } as AccountMeta), ), ) - .rpc(); + .instruction(); + } + + public async perpDeactivatePosition( + group: Group, + mangoAccount: MangoAccount, + perpMarketIndex: PerpMarketIndex, + ): Promise { + const ix = await this.perpDeactivatePositionIx( + group, + mangoAccount, + perpMarketIndex, + ); + return await this.sendAndConfirmTransaction( + [ix], + group.addressLookupTablesList, + ); } public async perpPlaceOrder( @@ -2592,7 +2698,6 @@ export class MangoClient { } } } - const mintInfos = tokenPositionIndices .filter((tokenIndex) => tokenIndex !== TokenPosition.TokenIndexUnset) .map((tokenIndex) => group.mintInfosMapByTokenIndex.get(tokenIndex)!); diff --git a/ts/client/src/scripts/devnet-user-close-account.ts b/ts/client/src/scripts/devnet-user-close-account.ts index cb6ad9f24..292a23166 100644 --- a/ts/client/src/scripts/devnet-user-close-account.ts +++ b/ts/client/src/scripts/devnet-user-close-account.ts @@ -114,7 +114,7 @@ async function main() { } native amount ${nativeFlooredNumber} `, ); - await client.tokenWithdrawNative( + const withdrawIx = await client.tokenWithdrawNativeIx( group, mangoAccount, group.getFirstBankByTokenIndex(token.tokenIndex).mint, @@ -123,6 +123,10 @@ async function main() { ) /* see comment in token_withdraw in program */, false, ); + await this.sendAndConfirmTransaction( + [...withdrawIx], + group.addressLookupTablesList, + ); } // reload and print current positions diff --git a/ts/client/src/scripts/mb-close-account.ts b/ts/client/src/scripts/mb-close-account.ts index 9ae717f6d..49f21f75e 100644 --- a/ts/client/src/scripts/mb-close-account.ts +++ b/ts/client/src/scripts/mb-close-account.ts @@ -99,7 +99,7 @@ async function closeUserAccount(userKeypairFile: string) { continue; } - await client.tokenWithdrawNative( + const withdrawIx = await client.tokenWithdrawNativeIx( group, mangoAccount, group.getFirstBankByTokenIndex(token.tokenIndex)!.mint, @@ -110,6 +110,10 @@ async function closeUserAccount(userKeypairFile: string) { ), false, ); + await this.sendAndConfirmTransaction( + [...withdrawIx], + group.addressLookupTablesList, + ); } } catch (error) { console.log(error); diff --git a/ts/client/src/scripts/mb-liqtest-make-candidates.ts b/ts/client/src/scripts/mb-liqtest-make-candidates.ts index 041d5ea39..e73ac05f3 100644 --- a/ts/client/src/scripts/mb-liqtest-make-candidates.ts +++ b/ts/client/src/scripts/mb-liqtest-make-candidates.ts @@ -152,13 +152,17 @@ async function main() { try { await setBankPrice(bank, PRICES[liabName] / 2); - await client.tokenWithdrawNative( + const withdrawIx = await client.tokenWithdrawNativeIx( group, mangoAccount, liabMint, new BN(liabAmount), true, ); + await this.sendAndConfirmTransaction( + [...withdrawIx], + group.addressLookupTablesList, + ); } finally { // restore the oracle await setBankPrice(bank, PRICES[liabName]); @@ -373,13 +377,17 @@ async function main() { await setBankPrice(collateralBank, PRICES['SOL'] * 10); // Spot-borrow more than the collateral is worth - await client.tokenWithdrawNative( + const withdrawIx = await client.tokenWithdrawNativeIx( group, mangoAccount, liabMint, new BN(-5000), true, ); + await this.sendAndConfirmTransaction( + [...withdrawIx], + group.addressLookupTablesList, + ); await mangoAccount.reload(client); // Execute two trades that leave the account with +$0.011 positive pnl