Flash loan 3 minimal example (#90)

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
microwavedcola1 2022-07-04 12:09:33 +02:00 committed by GitHub
parent 6a99eb893b
commit 39284c5705
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 643 additions and 49 deletions

4
keeper/.env.devnet Normal file
View File

@ -0,0 +1,4 @@
RPC_URL=https://mango.devnet.rpcpool.com
PAYER_KEYPAIR=~/.config/solana/mango-devnet.json
ADMIN_KEYPAIR=~/.config/solana/admin.json
MANGO_ACCOUNT_NAME=Account

4
keeper/.env.mainnet-beta Normal file
View File

@ -0,0 +1,4 @@
RPC_URL=https://mango.rpcpool.com/
PAYER_KEYPAIR=~/.config/solana/mango-mainnet.json
ADMIN_KEYPAIR=~/.config/solana/mango-mainnet.json
MANGO_ACCOUNT_NAME=Account

View File

@ -79,15 +79,15 @@ pub async fn loop_update_index(mango_client: Arc<MangoClient>, token_index: Toke
&mango_v4::instruction::UpdateIndex {},
),
};
let mut foo = bank_pubkeys_for_a_token
let mut banks = bank_pubkeys_for_a_token
.iter()
.map(|bank_pubkey| AccountMeta {
pubkey: *bank_pubkey,
is_signer: false,
is_writable: false,
is_writable: true,
})
.collect::<Vec<_>>();
ix.accounts.append(&mut foo);
ix.accounts.append(&mut banks);
ix
})
.send();

View File

@ -57,6 +57,8 @@ pub fn serum3_close_open_orders(ctx: Context<Serum3CloseOpenOrders>) -> Result<(
// close OO
//
cpi_close_open_orders(ctx.accounts)?;
// TODO: decrement in_use_count on the base token and quote token
account.serum3.deactivate(serum_market.market_index)?;
Ok(())

View File

@ -60,7 +60,7 @@ pub fn new_fixed_order_account_retriever<'a, 'info>(
let expected_ais = cm!(active_token_len * 2 // banks + oracles
+ active_perp_len // PerpMarkets
+ active_serum3_len); // open_orders
require!(ais.len() == expected_ais, MangoError::SomeError);
require_eq!(ais.len(), expected_ais, MangoError::SomeError);
Ok(FixedOrderAccountRetriever {
ais: ais

View File

@ -70,16 +70,16 @@
"mangoProgramId": "m43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD",
"banks": [
{
"name": "USDC",
"publicKey": "BYq994HWWuXC1UC7ByTiDLQBsnBQQAzJaLvMCQU3priu"
"name": "SOL",
"publicKey": "6XDZbWax9imnCEaKokorQtwRY7dGj9HnZNk612ibZu4v"
},
{
"name": "BTC",
"publicKey": "96e336hgaPWbbHqPbpPcmr1pbTY1CALtFWkNk2tD4ZmK"
"publicKey": "9pJ3V2WvGWtWomiUsRLHvMguoZ9SUCWwZxfvaqdRDq44"
},
{
"name": "SOL",
"publicKey": "J3dxEFhJG1maNva8W9u4XzxCR1DQm6qfkgrkY29Ujrh3"
"name": "USDC",
"publicKey": "41gvXTAYM4xckLYqxPXWwjpr6WM3GZEytM9QZSGKqfjX"
}
],
"stubOracles": [
@ -89,10 +89,6 @@
}
],
"mintInfos": [
{
"name": "USDC",
"publicKey": "42bXYX5sidUkrijuArv4D11g1aAm9947ht6qUB8Me5Q3"
},
{
"name": "BTC",
"publicKey": "4KZkrJQRvrVy8BWmbUKk7YWezMAsSb97ZMHFLUtj6Q5u"
@ -100,10 +96,14 @@
{
"name": "SOL",
"publicKey": "EvjoLBh7ej78C1yA7jaLcHQfoGx3KkDBnZbrztSZFGmC"
},
{
"name": "USDC",
"publicKey": "42bXYX5sidUkrijuArv4D11g1aAm9947ht6qUB8Me5Q3"
}
],
"serum3Markets": [],
"perpMarkets": []
}
]
}
}

View File

@ -41,6 +41,12 @@ export class Group {
);
}
public findSerum3Market(marketIndex: number): Serum3Market | undefined {
return Array.from(this.serum3MarketsMap.values()).find(
(serum3Market) => serum3Market.marketIndex === marketIndex,
);
}
public async reloadAll(client: MangoClient) {
let ids: Id | undefined = undefined;
@ -52,7 +58,7 @@ export class Group {
await Promise.all([
this.reloadBanks(client, ids),
this.reloadMintInfos(client, ids),
this.reloadSerum3Markets(client, ids).then,
this.reloadSerum3Markets(client, ids),
this.reloadPerpMarkets(client, ids),
]);
// requires reloadSerum3Markets to have finished loading
@ -62,6 +68,7 @@ export class Group {
public async reloadBanks(client: MangoClient, ids?: Id) {
let banks: Bank[];
if (ids) {
banks = (
await client.program.account.bank.fetchMultiple(ids.getBanks())

View File

@ -99,37 +99,44 @@ export class MangoAccount {
return ta ? ta.uiBorrows(bank) : 0;
}
tokens_active(): TokenPosition[] {
tokensActive(): TokenPosition[] {
return this.tokens.filter((token) => token.isActive());
}
serum3Active(): Serum3Orders[] {
return this.serum3.filter((serum3) => serum3.isActive());
}
perpActive(): PerpPositions[] {
return this.perps.filter((perp) => perp.isActive());
}
toString(group?: Group): string {
return (
'tokens:' +
JSON.stringify(
this.tokens
.filter((token) => token.tokenIndex != TokenPosition.TokenIndexUnset)
.map((token) => token.toString(group)),
null,
4,
) +
'\nserum:' +
JSON.stringify(
this.serum3.filter(
(serum3) => serum3.marketIndex != Serum3Orders.Serum3MarketIndexUnset,
),
null,
4,
) +
'\nperps:' +
JSON.stringify(
this.perps.filter(
(perp) => perp.marketIndex != PerpPositions.PerpMarketIndexUnset,
),
null,
4,
)
);
let res = '';
res = res + ' name: ' + this.name;
res =
this.tokensActive().length > 0
? res +
'\n tokens:' +
JSON.stringify(
this.tokensActive().map((token) => token.toString(group)),
null,
4,
)
: res + '';
res =
this.serum3Active().length > 0
? res + '\n serum:' + JSON.stringify(this.serum3Active(), null, 4)
: res + '';
res =
this.perpActive().length > 0
? res + '\n perps:' + JSON.stringify(this.perpActive(), null, 4)
: res + '';
return res;
}
}
@ -229,6 +236,10 @@ export class Serum3Orders {
public baseTokenIndex: number,
public quoteTokenIndex: number,
) {}
public isActive(): boolean {
return this.marketIndex !== Serum3Orders.Serum3MarketIndexUnset;
}
}
export class Serum3PositionDto {
@ -264,6 +275,10 @@ export class PerpPositions {
public takerBaseLots: number,
public takerQuoteLots: number,
) {}
isActive(): boolean {
return this.marketIndex != PerpPositions.PerpMarketIndexUnset;
}
}
export class PerpPositionDto {

View File

@ -51,6 +51,8 @@ import {
toU64,
} from './utils';
// TODO: replace ui values with native as input wherever possible
// TODO: replace token/market names with token or market indices
export class MangoClient {
constructor(
public program: Program<MangoV4>,
@ -438,11 +440,13 @@ export class MangoClient {
}
public async closeMangoAccount(
group: Group,
mangoAccount: MangoAccount,
): Promise<TransactionSignature> {
return await this.program.methods
.closeAccount()
.accounts({
group: group.publicKey,
account: mangoAccount.publicKey,
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
solDestination: mangoAccount.owner,
@ -521,6 +525,9 @@ export class MangoClient {
.rpc({ skipPreflight: true });
}
/**
* @deprecated
*/
public async tokenWithdraw(
group: Group,
mangoAccount: MangoAccount,
@ -556,6 +563,41 @@ export class MangoClient {
.rpc({ skipPreflight: true });
}
public async tokenWithdraw2(
group: Group,
mangoAccount: MangoAccount,
tokenName: string,
nativeAmount: number,
allowBorrow: boolean,
) {
const bank = group.banksMap.get(tokenName)!;
const tokenAccountPk = await getAssociatedTokenAddress(
bank.mint,
mangoAccount.owner,
);
const healthRemainingAccounts: PublicKey[] =
await this.buildHealthRemainingAccounts(group, mangoAccount, [bank]);
return await this.program.methods
.tokenWithdraw(new BN(nativeAmount), allowBorrow)
.accounts({
group: group.publicKey,
account: mangoAccount.publicKey,
bank: bank.publicKey,
vault: bank.vault,
tokenAccount: tokenAccountPk,
})
.remainingAccounts(
healthRemainingAccounts.map(
(pk) =>
({ pubkey: pk, isWritable: false, isSigner: false } as AccountMeta),
),
)
.rpc({ skipPreflight: true });
}
// Serum
public async serum3RegisterMarket(
@ -961,12 +1003,13 @@ export class MangoClient {
payer: (this.program.provider as AnchorProvider).wallet.publicKey,
})
.preInstructions([
// TODO: try to pick up sizes of bookside and eventqueue from IDL, so we can stay in sync with program
SystemProgram.createAccount({
programId: this.program.programId,
space: 8 + 90152,
space: 8 + 90136,
lamports:
await this.program.provider.connection.getMinimumBalanceForRentExemption(
90160,
90144,
),
fromPubkey: (this.program.provider as AnchorProvider).wallet
.publicKey,
@ -974,10 +1017,10 @@ export class MangoClient {
}),
SystemProgram.createAccount({
programId: this.program.programId,
space: 8 + 90152,
space: 8 + 90136,
lamports:
await this.program.provider.connection.getMinimumBalanceForRentExemption(
90160,
90144,
),
fromPubkey: (this.program.provider as AnchorProvider).wallet
.publicKey,
@ -985,10 +1028,10 @@ export class MangoClient {
}),
SystemProgram.createAccount({
programId: this.program.programId,
space: 8 + 102424,
space: 8 + 102416,
lamports:
await this.program.provider.connection.getMinimumBalanceForRentExemption(
102432,
102424,
),
fromPubkey: (this.program.provider as AnchorProvider).wallet
.publicKey,
@ -1607,7 +1650,7 @@ export class MangoClient {
/// private
private async buildHealthRemainingAccounts(
public async buildHealthRemainingAccounts(
group: Group,
mangoAccount: MangoAccount,
banks?: Bank[] /** TODO for serum3PlaceOrder we are just ingoring this atm */,

View File

@ -0,0 +1,126 @@
import { AnchorProvider, Wallet } from '@project-serum/anchor';
import { Connection, Keypair } from '@solana/web3.js';
import fs from 'fs';
import { Serum3Side } from '../accounts/serum3';
import { MangoClient } from '../client';
import { MANGO_V4_ID } from '../constants';
// note: either use finalized or expect closing certain things to fail and having to runs scrript multiple times
async function main() {
const options = AnchorProvider.defaultOptions();
// note: see note above
// options.commitment = 'finalized';
const connection = new Connection(
'https://mango.devnet.rpcpool.com',
options,
);
// user
const user = Keypair.fromSecretKey(
Buffer.from(
JSON.parse(fs.readFileSync(process.env.USER_KEYPAIR!, 'utf-8')),
),
);
const userWallet = new Wallet(user);
const userProvider = new AnchorProvider(connection, userWallet, options);
const client = await MangoClient.connect(
userProvider,
'devnet',
MANGO_V4_ID['devnet'],
);
console.log(`User ${userWallet.publicKey.toBase58()}`);
try {
// fetch group
const admin = Keypair.fromSecretKey(
Buffer.from(
JSON.parse(fs.readFileSync(process.env.ADMIN_KEYPAIR!, 'utf-8')),
),
);
const group = await client.getGroupForAdmin(admin.publicKey, 0);
console.log(`Found group ${group.publicKey.toBase58()}`);
// fetch account
const mangoAccount = (
await client.getMangoAccountForOwner(group, user.publicKey)
)[0];
console.log(`...found mangoAccount ${mangoAccount.publicKey}`);
console.log(mangoAccount.toString());
// close mango account serum3 positions, closing might require cancelling orders and settling
for (const serum3Account of mangoAccount.serum3Active()) {
let orders = await client.getSerum3Orders(
group,
group.findSerum3Market(serum3Account.marketIndex).name,
);
for (const order of orders) {
console.log(
` - Order orderId ${order.orderId}, ${order.side}, ${order.price}, ${order.size}`,
);
console.log(` - Cancelling order with ${order.orderId}`);
await client.serum3CancelOrder(
group,
mangoAccount,
'BTC/USDC',
order.side === 'buy' ? Serum3Side.bid : Serum3Side.ask,
order.orderId,
);
}
await client.serum3SettleFunds(
group,
mangoAccount,
group.findSerum3Market(serum3Account.marketIndex).name,
);
await client.serum3CloseOpenOrders(
group,
mangoAccount,
group.findSerum3Market(serum3Account.marketIndex).name,
);
}
// we closed a serum account, this changes the health accounts we are passing in for future ixs
await mangoAccount.reload(client);
// withdraw all tokens
for (const token of mangoAccount.tokensActive()) {
let native = token.native(group.findBank(token.tokenIndex));
// to avoid rounding issues
if (native.toNumber() < 1) {
continue;
}
let nativeFlooredNumber = Math.floor(native.toNumber());
console.log(
`withdrawing token ${
group.findBank(token.tokenIndex).name
} native amount ${nativeFlooredNumber} `,
);
await client.tokenWithdraw2(
group,
mangoAccount,
group.findBank(token.tokenIndex).name,
nativeFlooredNumber,
false,
);
}
// reload and print current positions
await mangoAccount.reload(client);
console.log(`...mangoAccount ${mangoAccount.publicKey}`);
console.log(mangoAccount.toString());
// close account
console.log(`Close mango account...`);
const res = await client.closeMangoAccount(group, mangoAccount);
} catch (error) {
console.log(error);
}
process.exit();
}
main();

View File

@ -86,6 +86,8 @@ async function main() {
})),
);
console.log(toDump);
// adds ids for group in existing ids.json
const existingGroup = idsJson.groups.find((group) => group.name == groupName);
if (existingGroup) {

View File

@ -0,0 +1,118 @@
import { AnchorProvider, Wallet } from '@project-serum/anchor';
import { Connection, Keypair } from '@solana/web3.js';
import fs from 'fs';
import { Serum3Side } from '../accounts/serum3';
import { MangoClient } from '../client';
async function main() {
const options = AnchorProvider.defaultOptions();
const connection = new Connection(process.env.CLUSTER_URL, options);
// user
const user = Keypair.fromSecretKey(
Buffer.from(
JSON.parse(
fs.readFileSync(process.env.MANGO_MAINNET_PAYER_KEYPAIR!, 'utf-8'),
),
),
);
const userWallet = new Wallet(user);
const userProvider = new AnchorProvider(connection, userWallet, options);
const client = await MangoClient.connectForGroupName(
userProvider,
'mainnet-beta.microwavedcola' /* Use ids json instead of getProgramAccounts */,
);
console.log(`User ${userWallet.publicKey.toBase58()}`);
// admin
const admin = Keypair.fromSecretKey(
Buffer.from(
JSON.parse(
fs.readFileSync(process.env.MANGO_MAINNET_PAYER_KEYPAIR!, 'utf-8'),
),
),
);
console.log(`Admin ${admin.publicKey.toBase58()}`);
try {
// fetch group
const group = await client.getGroupForAdmin(admin.publicKey);
console.log(`Found group ${group.publicKey.toBase58()}`);
// account
console.log(`Creating mangoaccount...`);
const mangoAccount = (
await client.getMangoAccountForOwner(group, user.publicKey)
)[0];
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
console.log(mangoAccount.toString(group));
// cancel serum3 accounts, closing might require cancelling orders and settling
for (const serum3Account of mangoAccount.serum3Active()) {
let orders = await client.getSerum3Orders(
group,
group.findSerum3Market(serum3Account.marketIndex).name,
);
for (const order of orders) {
console.log(
` - Order orderId ${order.orderId}, ${order.side}, ${order.price}, ${order.size}`,
);
console.log(` - Cancelling order with ${order.orderId}`);
await client.serum3CancelOrder(
group,
mangoAccount,
'BTC/USDC',
order.side === 'buy' ? Serum3Side.bid : Serum3Side.ask,
order.orderId,
);
}
await client.serum3SettleFunds(
group,
mangoAccount,
group.findSerum3Market(serum3Account.marketIndex).name,
);
await client.serum3CloseOpenOrders(
group,
mangoAccount,
group.findSerum3Market(serum3Account.marketIndex).name,
);
}
// we closed a serum account, this changes the health accounts we are passing in for future ixs
await mangoAccount.reload(client);
// withdraw all tokens
for (const token of mangoAccount.tokensActive()) {
const native = token.native(group.findBank(token.tokenIndex));
console.log(
`token native ${native} ${group.findBank(token.tokenIndex).name}`,
);
if (native.toNumber() < 1) {
continue;
}
await client.tokenWithdraw2(
group,
mangoAccount,
group.findBank(token.tokenIndex).name,
token.native(group.findBank(token.tokenIndex)).toNumber(),
false,
);
}
await mangoAccount.reload(client);
console.log(`...mangoAccount ${mangoAccount.publicKey}`);
console.log(mangoAccount.toString());
console.log(`Close mango account...`);
const res = await client.closeMangoAccount(group, mangoAccount);
} catch (error) {
console.log(error);
}
process.exit();
}
main();

View File

@ -0,0 +1,273 @@
import { Jupiter } from '@jup-ag/core';
import { AnchorProvider, Wallet } from '@project-serum/anchor';
import {
AccountMeta,
Connection,
Keypair,
SYSVAR_INSTRUCTIONS_PUBKEY,
TransactionInstruction,
} from '@solana/web3.js';
import BN from 'bn.js';
import fs from 'fs';
import { QUOTE_DECIMALS } from '../accounts/bank';
import { MangoClient } from '../client';
import { getAssociatedTokenAddress } from '../utils';
// NOTE: we assume that ATA for source and target already exist for wallet
async function main() {
const options = AnchorProvider.defaultOptions();
const connection = new Connection(process.env.CLUSTER_URL, options);
// load user key
const user = Keypair.fromSecretKey(
Buffer.from(
JSON.parse(
fs.readFileSync(process.env.MANGO_MAINNET_PAYER_KEYPAIR!, 'utf-8'),
),
),
);
const userWallet = new Wallet(user);
const userProvider = new AnchorProvider(connection, userWallet, options);
const client = await MangoClient.connectForGroupName(
userProvider,
'mainnet-beta.microwavedcola',
);
console.log(`User ${userWallet.publicKey.toBase58()}`);
// load admin key
const admin = Keypair.fromSecretKey(
Buffer.from(
JSON.parse(
fs.readFileSync(process.env.MANGO_MAINNET_PAYER_KEYPAIR!, 'utf-8'),
),
),
);
console.log(`Admin ${admin.publicKey.toBase58()}`);
// fetch group
const group = await client.getGroupForAdmin(admin.publicKey, 0);
console.log(`Found group ${group.publicKey.toBase58()}`);
console.log(`start btc bank ${group.banksMap.get('BTC').toString()}`);
// create + fetch account
console.log(`Creating mangoaccount...`);
const mangoAccount = await client.getOrCreateMangoAccount(
group,
user.publicKey,
0,
'my_mango_account',
);
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
console.log(`start balance \n${mangoAccount.toString(group)}`);
//
// flash loan 3
//
if (true) {
// source of swap
const sourceBank = group.banksMap.get('USDC');
// target of swap
const targetBank = group.banksMap.get('BTC');
// 0.2$, at 1BTC=20,000$, 0.2$=0.00001BTC
const sourceAmount = 2 * Math.pow(10, QUOTE_DECIMALS - 1);
console.log(`Flash loaning ${sourceBank.name} to ${targetBank.name}`);
// jupiter route
const jupiter = await Jupiter.load({
connection: client.program.provider.connection,
cluster: 'mainnet-beta',
user: mangoAccount.owner, // or public key
// platformFeeAndAccounts: NO_PLATFORM_FEE,
routeCacheDuration: 10_000, // Will not refetch data on computeRoutes for up to 10 seconds
});
const routes = await jupiter.computeRoutes({
inputMint: sourceBank.mint, // Mint address of the input token
outputMint: targetBank.mint, // Mint address of the output token
inputAmount: sourceAmount, // raw input amount of tokens
slippage: 5, // The slippage in % terms
forceFetch: false, // false is the default value => will use cache if not older than routeCacheDuration
});
const routesInfosWithoutRaydium = routes.routesInfos.filter((r) => {
if (r.marketInfos.length > 1) {
for (const mkt of r.marketInfos) {
if (mkt.amm.label === 'Raydium' || mkt.amm.label === 'Serum')
return false;
}
}
return true;
});
// loop until we manage first successful swap
let res;
let i = 0;
while (true) {
const instructions: TransactionInstruction[] = [];
// select a route and fetch+build its tx
const selectedRoute = routesInfosWithoutRaydium[i];
const { transactions } = await jupiter.exchange({
routeInfo: selectedRoute,
});
const { setupTransaction, swapTransaction } = transactions;
for (const ix of swapTransaction.instructions) {
if (
ix.programId.toBase58() ===
'JUP2jxvXaqu7NQY1GmNF4m1vodw12LVXYxbFL2uJvfo'
) {
instructions.push(ix);
}
}
// run jup setup in a separate tx, ideally this should be packed before flashLoanBegin in same tx,
// but it increases chance of flash loan tx to exceed tx size limit
if (setupTransaction) {
await this.program.provider.sendAndConfirm(setupTransaction);
}
// flash loan start ix - takes a loan for source token,
// flash loan end ix - returns increase in all token account's amounts to respective vaults,
const healthRemainingAccounts = await client.buildHealthRemainingAccounts(
group,
mangoAccount,
[sourceBank, targetBank], // we would be taking a sol loan potentially
);
// 1. build flash loan end ix
const flashLoadnEndIx = await client.program.methods
.flashLoan3End()
.accounts({
account: mangoAccount.publicKey,
owner: (client.program.provider as AnchorProvider).wallet.publicKey,
})
.remainingAccounts([
...healthRemainingAccounts.map(
(pk) =>
({
pubkey: pk,
isWritable: false,
isSigner: false,
} as AccountMeta),
),
{
pubkey: sourceBank.vault,
isWritable: true,
isSigner: false,
} as AccountMeta,
{
pubkey: targetBank.vault,
isWritable: true,
isSigner: false,
} as AccountMeta,
{
pubkey: await getAssociatedTokenAddress(
sourceBank.mint,
mangoAccount.owner,
),
isWritable: true, // increase in this address amount is transferred back to the sourceBank.vault above in this case whatever is residual of source bank loan
isSigner: false,
} as AccountMeta,
{
pubkey: await getAssociatedTokenAddress(
targetBank.mint,
mangoAccount.owner,
),
isWritable: true, // increase in this address amount is transferred back to the targetBank.vault above in this case whatever is result of swap
isSigner: false,
} as AccountMeta,
])
.instruction();
instructions.push(flashLoadnEndIx);
// 2. build flash loan start ix, add end ix as a post ix
try {
res = await client.program.methods
.flashLoan3Begin([
new BN(sourceAmount),
new BN(
0,
) /* we don't care about borrowing the target amount, this is just a dummy */,
])
.accounts({
group: group.publicKey,
// for observing ixs in the entire tx,
// e.g. apart from flash loan start and end no other ix should target mango v4 program
// e.g. forbid FlashLoan3Begin been called from CPI
instructions: SYSVAR_INSTRUCTIONS_PUBKEY,
})
.remainingAccounts([
{
pubkey: sourceBank.publicKey,
isWritable: true, // metadata for flash loan is updated
isSigner: false,
} as AccountMeta,
{
pubkey: targetBank.publicKey,
isWritable: true, // this is a dummy, its just done so that we match flash loan start and end ix
isSigner: false,
} as AccountMeta,
{
pubkey: sourceBank.vault,
isWritable: true,
isSigner: false,
} as AccountMeta,
{
pubkey: targetBank.vault,
isWritable: true, // this is a dummy, its just done so that we match flash loan start and end ix
isSigner: false,
} as AccountMeta,
{
pubkey: await getAssociatedTokenAddress(
sourceBank.mint,
mangoAccount.owner,
),
isWritable: true, // token transfer i.e. loan to a desired token account e.g. user's ATA when using a route made for a specific user
isSigner: false,
} as AccountMeta,
{
pubkey: await getAssociatedTokenAddress(
targetBank.mint,
mangoAccount.owner,
),
isWritable: false, // this is a dummy, its just done so that we match flash loan start and end ix
isSigner: false,
} as AccountMeta,
])
.postInstructions(instructions)
.rpc();
// break when success
break;
} catch (error) {
console.log(error);
if (
(error.toString() as string).includes('Transaction too large:') ||
(error.toString() as string).includes(
'encoding overruns Uint8Array',
) ||
(error.toString() as string).includes(
'The value of "offset" is out of range. It must be >= 0 and <= 1231. Received 1232',
) ||
(error.toString() as string).includes(
'The value of "value" is out of range. It must be >= 0 and <= 255. Received',
) ||
i > 10
) {
console.log(`route ${i} was bad, trying next one...`);
i++;
} else {
throw error; // let others bubble up
}
}
}
console.log(`success tx - https://explorer.solana.com/tx/${res}`);
group.reloadBanks(client);
console.log(`end btc bank ${group.banksMap.get('BTC').toString()}`);
await mangoAccount.reload(client);
console.log(`end balance \n${mangoAccount.toString(group)}`);
}
}
main();