Merge pull request #389 from blockworks-foundation/feature/close-mango-account

Feature/close mango account
This commit is contained in:
Adrian Brzeziński 2023-01-17 12:46:42 +01:00 committed by GitHub
commit 6206bbb953
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 140 additions and 19 deletions

View File

@ -56,6 +56,7 @@ import {
createAssociatedTokenAccountIdempotentInstruction, createAssociatedTokenAccountIdempotentInstruction,
getAssociatedTokenAddress, getAssociatedTokenAddress,
toNative, toNative,
U64_MAX_BN,
} from './utils'; } from './utils';
import { sendTransaction } from './utils/rpc'; import { sendTransaction } from './utils/rpc';
@ -809,6 +810,81 @@ export class MangoClient {
); );
} }
public async emptyAndCloseMangoAccount(
group: Group,
mangoAccount: MangoAccount,
): Promise<TransactionSignature> {
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( public async tokenDeposit(
group: Group, group: Group,
mangoAccount: MangoAccount, mangoAccount: MangoAccount,
@ -917,22 +993,28 @@ export class MangoClient {
allowBorrow: boolean, allowBorrow: boolean,
): Promise<TransactionSignature> { ): Promise<TransactionSignature> {
const nativeAmount = toNative(amount, group.getMintDecimals(mintPk)); const nativeAmount = toNative(amount, group.getMintDecimals(mintPk));
return await this.tokenWithdrawNative( const ixes = await this.tokenWithdrawNativeIx(
group, group,
mangoAccount, mangoAccount,
mintPk, mintPk,
nativeAmount, nativeAmount,
allowBorrow, allowBorrow,
); );
return await this.sendAndConfirmTransaction(
[...ixes],
group.addressLookupTablesList,
);
} }
public async tokenWithdrawNative( public async tokenWithdrawNativeIx(
group: Group, group: Group,
mangoAccount: MangoAccount, mangoAccount: MangoAccount,
mintPk: PublicKey, mintPk: PublicKey,
nativeAmount: BN, nativeAmount: BN,
allowBorrow: boolean, allowBorrow: boolean,
): Promise<TransactionSignature> { healthAccountsToExclude: PublicKey[] = [],
): Promise<TransactionInstruction[]> {
const bank = group.getFirstBankByMint(mintPk); const bank = group.getFirstBankByMint(mintPk);
const tokenAccountPk = await getAssociatedTokenAddress( const tokenAccountPk = await getAssociatedTokenAddress(
@ -981,17 +1063,25 @@ export class MangoClient {
tokenAccount: tokenAccountPk, tokenAccount: tokenAccountPk,
}) })
.remainingAccounts( .remainingAccounts(
healthRemainingAccounts.map( healthRemainingAccounts
(pk) => .filter(
({ pubkey: pk, isWritable: false, isSigner: false } as AccountMeta), (accounts) =>
), !healthAccountsToExclude.find((accountsToExclude) =>
accounts.equals(accountsToExclude),
),
)
.map(
(pk) =>
({
pubkey: pk,
isWritable: false,
isSigner: false,
} as AccountMeta),
),
) )
.instruction(); .instruction();
return await this.sendAndConfirmTransaction( return [...preInstructions, ix, ...postInstructions];
[...preInstructions, ix, ...postInstructions],
group.addressLookupTablesList,
);
} }
// Serum // Serum
@ -1718,11 +1808,11 @@ export class MangoClient {
); );
} }
public async perpDeactivatePosition( public async perpDeactivatePositionIx(
group: Group, group: Group,
mangoAccount: MangoAccount, mangoAccount: MangoAccount,
perpMarketIndex: PerpMarketIndex, perpMarketIndex: PerpMarketIndex,
): Promise<TransactionSignature> { ): Promise<TransactionInstruction> {
const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex); const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex);
const healthRemainingAccounts: PublicKey[] = const healthRemainingAccounts: PublicKey[] =
this.buildHealthRemainingAccounts( this.buildHealthRemainingAccounts(
@ -1746,7 +1836,23 @@ export class MangoClient {
({ pubkey: pk, isWritable: false, isSigner: false } as AccountMeta), ({ pubkey: pk, isWritable: false, isSigner: false } as AccountMeta),
), ),
) )
.rpc(); .instruction();
}
public async perpDeactivatePosition(
group: Group,
mangoAccount: MangoAccount,
perpMarketIndex: PerpMarketIndex,
): Promise<TransactionSignature> {
const ix = await this.perpDeactivatePositionIx(
group,
mangoAccount,
perpMarketIndex,
);
return await this.sendAndConfirmTransaction(
[ix],
group.addressLookupTablesList,
);
} }
public async perpPlaceOrder( public async perpPlaceOrder(
@ -2592,7 +2698,6 @@ export class MangoClient {
} }
} }
} }
const mintInfos = tokenPositionIndices const mintInfos = tokenPositionIndices
.filter((tokenIndex) => tokenIndex !== TokenPosition.TokenIndexUnset) .filter((tokenIndex) => tokenIndex !== TokenPosition.TokenIndexUnset)
.map((tokenIndex) => group.mintInfosMapByTokenIndex.get(tokenIndex)!); .map((tokenIndex) => group.mintInfosMapByTokenIndex.get(tokenIndex)!);

View File

@ -114,7 +114,7 @@ async function main() {
} native amount ${nativeFlooredNumber} `, } native amount ${nativeFlooredNumber} `,
); );
await client.tokenWithdrawNative( const withdrawIx = await client.tokenWithdrawNativeIx(
group, group,
mangoAccount, mangoAccount,
group.getFirstBankByTokenIndex(token.tokenIndex).mint, group.getFirstBankByTokenIndex(token.tokenIndex).mint,
@ -123,6 +123,10 @@ async function main() {
) /* see comment in token_withdraw in program */, ) /* see comment in token_withdraw in program */,
false, false,
); );
await this.sendAndConfirmTransaction(
[...withdrawIx],
group.addressLookupTablesList,
);
} }
// reload and print current positions // reload and print current positions

View File

@ -99,7 +99,7 @@ async function closeUserAccount(userKeypairFile: string) {
continue; continue;
} }
await client.tokenWithdrawNative( const withdrawIx = await client.tokenWithdrawNativeIx(
group, group,
mangoAccount, mangoAccount,
group.getFirstBankByTokenIndex(token.tokenIndex)!.mint, group.getFirstBankByTokenIndex(token.tokenIndex)!.mint,
@ -110,6 +110,10 @@ async function closeUserAccount(userKeypairFile: string) {
), ),
false, false,
); );
await this.sendAndConfirmTransaction(
[...withdrawIx],
group.addressLookupTablesList,
);
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error);

View File

@ -152,13 +152,17 @@ async function main() {
try { try {
await setBankPrice(bank, PRICES[liabName] / 2); await setBankPrice(bank, PRICES[liabName] / 2);
await client.tokenWithdrawNative( const withdrawIx = await client.tokenWithdrawNativeIx(
group, group,
mangoAccount, mangoAccount,
liabMint, liabMint,
new BN(liabAmount), new BN(liabAmount),
true, true,
); );
await this.sendAndConfirmTransaction(
[...withdrawIx],
group.addressLookupTablesList,
);
} finally { } finally {
// restore the oracle // restore the oracle
await setBankPrice(bank, PRICES[liabName]); await setBankPrice(bank, PRICES[liabName]);
@ -373,13 +377,17 @@ async function main() {
await setBankPrice(collateralBank, PRICES['SOL'] * 10); await setBankPrice(collateralBank, PRICES['SOL'] * 10);
// Spot-borrow more than the collateral is worth // Spot-borrow more than the collateral is worth
await client.tokenWithdrawNative( const withdrawIx = await client.tokenWithdrawNativeIx(
group, group,
mangoAccount, mangoAccount,
liabMint, liabMint,
new BN(-5000), new BN(-5000),
true, true,
); );
await this.sendAndConfirmTransaction(
[...withdrawIx],
group.addressLookupTablesList,
);
await mangoAccount.reload(client); await mangoAccount.reload(client);
// Execute two trades that leave the account with +$0.011 positive pnl // Execute two trades that leave the account with +$0.011 positive pnl