@haven-fi/solauto-sdk 1.0.830 → 1.0.833
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/services/flashLoans/marginfiFlProvider.d.ts +3 -0
- package/dist/services/flashLoans/marginfiFlProvider.d.ts.map +1 -1
- package/dist/services/flashLoans/marginfiFlProvider.js +31 -1
- package/dist/services/transactions/manager/transactionsManager.d.ts +1 -0
- package/dist/services/transactions/manager/transactionsManager.d.ts.map +1 -1
- package/dist/services/transactions/manager/transactionsManager.js +41 -23
- package/dist/utils/marginfi/general.d.ts.map +1 -1
- package/dist/utils/marginfi/general.js +10 -1
- package/package.json +1 -1
- package/src/services/flashLoans/marginfiFlProvider.ts +47 -1
- package/src/services/transactions/manager/transactionsManager.ts +64 -37
- package/src/utils/marginfi/general.ts +12 -3
- package/tests/unit/marginfiRemainingAccounts.ts +45 -0
- package/tests/unit/transactionsManager.ts +69 -0
|
@@ -11,6 +11,8 @@ export declare class MarginfiFlProvider extends FlProviderBase {
|
|
|
11
11
|
private debtImfiAccount;
|
|
12
12
|
private supplyRemainingAccounts;
|
|
13
13
|
private debtRemainingAccounts;
|
|
14
|
+
private supplyBankHealthAccounts;
|
|
15
|
+
private debtBankHealthAccounts;
|
|
14
16
|
initialize(): Promise<void>;
|
|
15
17
|
private setAvailableBanks;
|
|
16
18
|
private setIntermediaryAccount;
|
|
@@ -19,6 +21,7 @@ export declare class MarginfiFlProvider extends FlProviderBase {
|
|
|
19
21
|
liquiditySource(source: TokenType): PublicKey;
|
|
20
22
|
private liquidityBank;
|
|
21
23
|
private iMfiAccount;
|
|
24
|
+
private bankHealthAccounts;
|
|
22
25
|
liquidityAvailable(source: TokenType): bigint;
|
|
23
26
|
flFeeBps(source: TokenType, signerFlashLoan?: boolean): number;
|
|
24
27
|
flashBorrow(flashLoan: FlashLoanDetails, destTokenAccount: PublicKey): TransactionBuilder;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"marginfiFlProvider.d.ts","sourceRoot":"","sources":["../../../src/services/flashLoans/marginfiFlProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAA8B,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAML,kBAAkB,EACnB,MAAM,0BAA0B,CAAC;AAclC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"marginfiFlProvider.d.ts","sourceRoot":"","sources":["../../../src/services/flashLoans/marginfiFlProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAA8B,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAML,kBAAkB,EACnB,MAAM,0BAA0B,CAAC;AAclC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAmBlD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAQ5C,qBAAa,kBAAmB,SAAQ,cAAc;IACpD,OAAO,CAAC,wBAAwB,CAAqB;IACrD,OAAO,CAAC,yBAAyB,CAAQ;IACzC,OAAO,CAAC,uBAAuB,CAAQ;IAEvC,OAAO,CAAC,iBAAiB,CAAe;IACxC,OAAO,CAAC,eAAe,CAAe;IACtC,OAAO,CAAC,uBAAuB,CAAiB;IAChD,OAAO,CAAC,qBAAqB,CAAiB;IAC9C,OAAO,CAAC,wBAAwB,CAAiB;IACjD,OAAO,CAAC,sBAAsB,CAAiB;IAEzC,UAAU;YAiBF,iBAAiB;YAgEjB,sBAAsB;IAsD9B,sBAAsB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAyC3D,gBAAgB,IAAI,SAAS,EAAE;IAY/B,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS;IAI7C,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,kBAAkB;IAM1B,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM;IAI7C,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,eAAe,CAAC,EAAE,OAAO,GAAG,MAAM;IAc9D,WAAW,CACT,SAAS,EAAE,gBAAgB,EAC3B,gBAAgB,EAAE,SAAS,GAC1B,kBAAkB;IAsDrB,UAAU,CAAC,SAAS,EAAE,gBAAgB,GAAG,kBAAkB;IAiD3D,YAAY,CACV,eAAe,EAAE,SAAS,EAC1B,IAAI,EAAE,SAAS,EACf,aAAa,EAAE,SAAS;CAW3B"}
|
|
@@ -51,6 +51,22 @@ class MarginfiFlProvider extends flProviderBase_1.FlProviderBase {
|
|
|
51
51
|
const debtBanks = mapBanksAndBalances(this.debtMint);
|
|
52
52
|
this.supplyBankLiquiditySource = supplyBanks[0][1];
|
|
53
53
|
this.debtBankLiquiditySource = debtBanks[0][1];
|
|
54
|
+
const [supplyPriceOracle, debtPriceOracle] = await Promise.all([
|
|
55
|
+
(0, utils_1.getMarginfiPriceOracle)(this.umi, {
|
|
56
|
+
data: this.supplyBankLiquiditySource,
|
|
57
|
+
}),
|
|
58
|
+
(0, utils_1.getMarginfiPriceOracle)(this.umi, {
|
|
59
|
+
data: this.debtBankLiquiditySource,
|
|
60
|
+
}),
|
|
61
|
+
]);
|
|
62
|
+
this.supplyBankHealthAccounts = [
|
|
63
|
+
(0, utils_1.getAccountMeta)((0, umi_web3js_adapters_1.toWeb3JsPublicKey)(this.supplyBankLiquiditySource.publicKey)),
|
|
64
|
+
(0, utils_1.getAccountMeta)(supplyPriceOracle),
|
|
65
|
+
];
|
|
66
|
+
this.debtBankHealthAccounts = [
|
|
67
|
+
(0, utils_1.getAccountMeta)((0, umi_web3js_adapters_1.toWeb3JsPublicKey)(this.debtBankLiquiditySource.publicKey)),
|
|
68
|
+
(0, utils_1.getAccountMeta)(debtPriceOracle),
|
|
69
|
+
];
|
|
54
70
|
}
|
|
55
71
|
async setIntermediaryAccount(sources) {
|
|
56
72
|
const compatibleMarginfiAccounts = this.existingMarginfiAccounts.filter((x) => x.group.toString() == this.liquidityBank(sources[0]).group);
|
|
@@ -137,6 +153,11 @@ class MarginfiFlProvider extends flProviderBase_1.FlProviderBase {
|
|
|
137
153
|
? this.supplyImfiAccount
|
|
138
154
|
: this.debtImfiAccount;
|
|
139
155
|
}
|
|
156
|
+
bankHealthAccounts(source) {
|
|
157
|
+
return source === generated_1.TokenType.Supply
|
|
158
|
+
? this.supplyBankHealthAccounts
|
|
159
|
+
: this.debtBankHealthAccounts;
|
|
160
|
+
}
|
|
140
161
|
liquidityAvailable(source) {
|
|
141
162
|
return (0, utils_1.getBankLiquidityAvailableBaseUnit)(this.liquidityBank(source), false);
|
|
142
163
|
}
|
|
@@ -154,6 +175,15 @@ class MarginfiFlProvider extends flProviderBase_1.FlProviderBase {
|
|
|
154
175
|
const bank = this.liquidityBank(flashLoan.liquiditySource);
|
|
155
176
|
const associatedBankAccs = (0, utils_1.findMarginfiAccounts)((0, umi_web3js_adapters_1.toWeb3JsPublicKey)(bank.publicKey));
|
|
156
177
|
const iMfiAccount = this.iMfiAccount(flashLoan.liquiditySource);
|
|
178
|
+
const existingRemainingAccounts = flashLoan.liquiditySource === generated_1.TokenType.Supply
|
|
179
|
+
? this.supplyRemainingAccounts
|
|
180
|
+
: this.debtRemainingAccounts;
|
|
181
|
+
// Marginfi's circuit-breaker gate scans every active bank during borrow,
|
|
182
|
+
// including borrows made while the account is inside a flash loan.
|
|
183
|
+
const borrowRemainingAccounts = [...existingRemainingAccounts];
|
|
184
|
+
if (!borrowRemainingAccounts.some((account) => account.pubkey.toString() === bank.publicKey.toString())) {
|
|
185
|
+
borrowRemainingAccounts.push(...this.bankHealthAccounts(flashLoan.liquiditySource));
|
|
186
|
+
}
|
|
157
187
|
return (0, umi_1.transactionBuilder)()
|
|
158
188
|
.add((0, marginfi_1.lendingAccountStartFlashloan)(this.umi, {
|
|
159
189
|
endIndex: 0, // We set this after building the transaction
|
|
@@ -170,7 +200,7 @@ class MarginfiFlProvider extends flProviderBase_1.FlProviderBase {
|
|
|
170
200
|
marginfiAccount: (0, umi_1.publicKey)(iMfiAccount.accountPk),
|
|
171
201
|
marginfiGroup: this.liquidityBank(flashLoan.liquiditySource).group,
|
|
172
202
|
signer: this.signer,
|
|
173
|
-
}));
|
|
203
|
+
}).addRemainingAccounts((0, utils_1.composeRemainingAccounts)(borrowRemainingAccounts)));
|
|
174
204
|
}
|
|
175
205
|
flashRepay(flashLoan) {
|
|
176
206
|
if (flashLoan.signerFlashLoan) {
|
|
@@ -60,6 +60,7 @@ export declare class TransactionsManager<T extends TxHandler> {
|
|
|
60
60
|
private shouldProceedToSend;
|
|
61
61
|
private refreshItemSets;
|
|
62
62
|
private processTransactionsAtomically;
|
|
63
|
+
private processTransactionSetsSequentially;
|
|
63
64
|
private sendJitoBundle;
|
|
64
65
|
private processTransactionSet;
|
|
65
66
|
protected sendTransaction(tx: TransactionBuilder, name: string, attemptNum: number, priorityFeeSetting?: PriorityFeeSetting, txRunType?: TransactionRunType): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transactionsManager.d.ts","sourceRoot":"","sources":["../../../../src/services/transactions/manager/transactionsManager.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EACL,kBAAkB,EAElB,kBAAkB,EACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAIL,aAAa,EAKd,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,OAAO,EAEL,YAAY,EACZ,eAAe,EAEhB,MAAM,UAAU,CAAC;AAGlB,qBAAa,wBAAyB,SAAQ,KAAK;gBACrC,OAAO,EAAE,MAAM;CAK5B;AAED,oBAAY,iBAAiB;IAC3B,OAAO,YAAY;IACnB,UAAU,eAAe;IACzB,MAAM,WAAW;IACjB,UAAU,eAAe;IACzB,MAAM,WAAW;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,0BAA0B,GAAG,wBAAwB,EAAE,CAAC;AAEpE,UAAU,WAAW;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,uBAAuB,CAAC,CAAC,SAAS,SAAS;IAC1D,SAAS,EAAE,CAAC,CAAC;IACb,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAChE,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC;AAED,qBAAa,mBAAmB,CAAC,CAAC,SAAS,SAAS;IAClD,SAAS,CAAC,SAAS,EAAE,CAAC,CAAC;IACvB,SAAS,CAAC,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAC1E,SAAS,CAAC,SAAS,CAAC,EAAE,kBAAkB,CAAC;IACzC,SAAS,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;IACjD,SAAS,CAAC,UAAU,EAAE,OAAO,CAAC;IAC9B,SAAS,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACxC,SAAS,CAAC,QAAQ,EAAE,0BAA0B,CAAM;IACpD,SAAS,CAAC,YAAY,EAAE,YAAY,CAAC;IACrC,SAAS,CAAC,eAAe,EAAE,MAAM,CAAC;IAClC,SAAS,CAAC,YAAY,EAAE,MAAM,CAAC;IAC/B,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC;IAC7B,SAAS,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;gBAEhC,IAAI,EAAE,uBAAuB,CAAC,CAAC,CAAC;YAoB9B,uBAAuB;IAwDrC,OAAO,CAAC,YAAY;YAiCN,aAAa;IAwB3B,SAAS,CAAC,4BAA4B,CACpC,SAAS,EAAE,KAAK,GAAG,SAAS,EAC5B,UAAU,EAAE,MAAM;IAcpB,OAAO,CAAC,mBAAmB;IAkBd,IAAI,CACf,KAAK,EAAE,eAAe,EAAE,GACvB,OAAO,CAAC,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"transactionsManager.d.ts","sourceRoot":"","sources":["../../../../src/services/transactions/manager/transactionsManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -161,11 +161,7 @@ class TransactionsManager {
|
|
|
161
161
|
await this.processTransactionsAtomically(itemSets);
|
|
162
162
|
}
|
|
163
163
|
else {
|
|
164
|
-
|
|
165
|
-
while (currentIndex < itemSets.length) {
|
|
166
|
-
await this.processTransactionSet(itemSets, currentIndex);
|
|
167
|
-
currentIndex++;
|
|
168
|
-
}
|
|
164
|
+
await this.processTransactionSetsSequentially(itemSets);
|
|
169
165
|
}
|
|
170
166
|
return this.statuses;
|
|
171
167
|
}
|
|
@@ -215,27 +211,49 @@ class TransactionsManager {
|
|
|
215
211
|
return newItemSets;
|
|
216
212
|
}
|
|
217
213
|
async processTransactionsAtomically(itemSets) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if (
|
|
228
|
-
|
|
214
|
+
let bundleSendAttempted = false;
|
|
215
|
+
try {
|
|
216
|
+
await (0, utils_1.retryWithExponentialBackoff)(async (attemptNum, prevError) => {
|
|
217
|
+
if (prevError &&
|
|
218
|
+
this.statuses.filter((x) => x.simulationSuccessful).length >
|
|
219
|
+
this.signableRetries) {
|
|
220
|
+
throw prevError;
|
|
221
|
+
}
|
|
222
|
+
this.priorityFeeSetting = this.getUpdatedPriorityFeeSetting(prevError, attemptNum);
|
|
223
|
+
if (attemptNum > 0) {
|
|
224
|
+
const refreshedSets = await this.refreshItemSets(itemSets, attemptNum, prevError);
|
|
225
|
+
if (!refreshedSets || !refreshedSets.length) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
itemSets = refreshedSets;
|
|
230
|
+
}
|
|
229
231
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
+
if (!this.shouldProceedToSend(itemSets, attemptNum)) {
|
|
233
|
+
return;
|
|
232
234
|
}
|
|
235
|
+
bundleSendAttempted = true;
|
|
236
|
+
await this.sendJitoBundle(itemSets, attemptNum);
|
|
237
|
+
}, this.totalRetries, this.retryDelay, this.errorsToThrow);
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
const explicitlyNonRetryable = this.errorsToThrow?.some((errorType) => error instanceof errorType);
|
|
241
|
+
if (!bundleSendAttempted ||
|
|
242
|
+
itemSets.length <= 1 ||
|
|
243
|
+
explicitlyNonRetryable ||
|
|
244
|
+
this.abortController?.signal.aborted) {
|
|
245
|
+
throw error;
|
|
233
246
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
247
|
+
(0, utils_1.consoleLog)("Jito bundle failed after all retries; retrying one transaction at a time");
|
|
248
|
+
await this.processTransactionSetsSequentially(itemSets);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async processTransactionSetsSequentially(itemSets) {
|
|
252
|
+
let currentIndex = 0;
|
|
253
|
+
while (currentIndex < itemSets.length) {
|
|
254
|
+
await this.processTransactionSet(itemSets, currentIndex);
|
|
255
|
+
currentIndex++;
|
|
256
|
+
}
|
|
239
257
|
}
|
|
240
258
|
async sendJitoBundle(itemSets, attemptNum) {
|
|
241
259
|
let transactions = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"general.d.ts","sourceRoot":"","sources":["../../../src/utils/marginfi/general.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,OAAO,EAAa,GAAG,EAAE,MAAM,0BAA0B,CAAC;AAKhF,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAEL,uBAAuB,EACxB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,OAAO,EACP,IAAI,EAIJ,eAAe,EAGhB,MAAM,6BAA6B,CAAC;AASrC,wBAAgB,4BAA4B,CAAC,GAAG,CAAC,EAAE,UAAU,GAAG,OAAO,CActE;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,GAAG,EAAE,WAAW,CAAC,EAAE,UAAU,OASxE;AAED,wBAAsB,yBAAyB,CAC7C,GAAG,EAAE,GAAG,EACR,eAAe,EAAE,uBAAuB,GACvC,OAAO,CAAC,SAAS,EAAE,CAAC,CA8BtB;AAED,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS;;;;;GAanE;AAED,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,GAAG,EACR,IAAI,EAAE;IAAE,EAAE,CAAC,EAAE,SAAS,CAAC;IAAC,IAAI,CAAC,EAAE,IAAI,CAAA;CAAE,sBAiBtC;AAED,UAAU,wBAAyB,SAAQ,qBAAqB;IAC9D,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,SAAS,GACd,wBAAwB,CAyB1B;AAED,wBAAsB,0CAA0C,CAC9D,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,WAAW,EAAE,CAAC,CAWxB;AAED,wBAAgB,oCAAoC,CAClD,UAAU,EAAE,IAAI,EAChB,QAAQ,EAAE,IAAI,EACd,WAAW,EAAE,MAAM,GAClB,CAAC,MAAM,EAAE,MAAM,CAAC,CA6BlB;AAED,wBAAgB,oBAAoB,CAAC,eAAe,EAAE,eAAe,WASpE;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"general.d.ts","sourceRoot":"","sources":["../../../src/utils/marginfi/general.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,OAAO,EAAa,GAAG,EAAE,MAAM,0BAA0B,CAAC;AAKhF,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAEL,uBAAuB,EACxB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,OAAO,EACP,IAAI,EAIJ,eAAe,EAGhB,MAAM,6BAA6B,CAAC;AASrC,wBAAgB,4BAA4B,CAAC,GAAG,CAAC,EAAE,UAAU,GAAG,OAAO,CActE;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,GAAG,EAAE,WAAW,CAAC,EAAE,UAAU,OASxE;AAED,wBAAsB,yBAAyB,CAC7C,GAAG,EAAE,GAAG,EACR,eAAe,EAAE,uBAAuB,GACvC,OAAO,CAAC,SAAS,EAAE,CAAC,CA8BtB;AAED,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS;;;;;GAanE;AAED,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,GAAG,EACR,IAAI,EAAE;IAAE,EAAE,CAAC,EAAE,SAAS,CAAC;IAAC,IAAI,CAAC,EAAE,IAAI,CAAA;CAAE,sBAiBtC;AAED,UAAU,wBAAyB,SAAQ,qBAAqB;IAC9D,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,SAAS,GACd,wBAAwB,CAyB1B;AAED,wBAAsB,0CAA0C,CAC9D,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,WAAW,EAAE,CAAC,CAWxB;AAED,wBAAgB,oCAAoC,CAClD,UAAU,EAAE,IAAI,EAChB,QAAQ,EAAE,IAAI,EACd,WAAW,EAAE,MAAM,GAClB,CAAC,MAAM,EAAE,MAAM,CAAC,CA6BlB;AAED,wBAAgB,oBAAoB,CAAC,eAAe,EAAE,eAAe,WASpE;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,CAoB3E"}
|
|
@@ -141,6 +141,15 @@ function marginfiAccountEmpty(marginfiAccount) {
|
|
|
141
141
|
function composeRemainingAccounts(accs) {
|
|
142
142
|
const banksAndOracles = accs.reduce((acc, _, i) => i % 2 === 0 ? [...acc, [accs[i], accs[i + 1]]] : acc, []);
|
|
143
143
|
return banksAndOracles
|
|
144
|
-
.sort((a, b) =>
|
|
144
|
+
.sort((a, b) => {
|
|
145
|
+
const aBytes = (0, umi_web3js_adapters_1.toWeb3JsPublicKey)(a[0].pubkey).toBuffer();
|
|
146
|
+
const bBytes = (0, umi_web3js_adapters_1.toWeb3JsPublicKey)(b[0].pubkey).toBuffer();
|
|
147
|
+
for (let i = 0; i < aBytes.length; i++) {
|
|
148
|
+
if (aBytes[i] !== bBytes[i]) {
|
|
149
|
+
return bBytes[i] - aBytes[i];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return 0;
|
|
153
|
+
})
|
|
145
154
|
.flat();
|
|
146
155
|
}
|
package/package.json
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
fromBaseUnit,
|
|
31
31
|
getBankLiquidityAvailableBaseUnit,
|
|
32
32
|
getEmptyMarginfiAccountsByAuthority,
|
|
33
|
+
getAccountMeta,
|
|
33
34
|
getMarginfiPriceOracle,
|
|
34
35
|
getRemainingAccountsForMarginfiHealthCheck,
|
|
35
36
|
getTokenAccount,
|
|
@@ -56,6 +57,8 @@ export class MarginfiFlProvider extends FlProviderBase {
|
|
|
56
57
|
private debtImfiAccount!: IMFIAccount;
|
|
57
58
|
private supplyRemainingAccounts!: AccountMeta[];
|
|
58
59
|
private debtRemainingAccounts!: AccountMeta[];
|
|
60
|
+
private supplyBankHealthAccounts!: AccountMeta[];
|
|
61
|
+
private debtBankHealthAccounts!: AccountMeta[];
|
|
59
62
|
|
|
60
63
|
async initialize() {
|
|
61
64
|
await this.setAvailableBanks();
|
|
@@ -117,6 +120,25 @@ export class MarginfiFlProvider extends FlProviderBase {
|
|
|
117
120
|
|
|
118
121
|
this.supplyBankLiquiditySource = supplyBanks[0][1];
|
|
119
122
|
this.debtBankLiquiditySource = debtBanks[0][1];
|
|
123
|
+
|
|
124
|
+
const [supplyPriceOracle, debtPriceOracle] = await Promise.all([
|
|
125
|
+
getMarginfiPriceOracle(this.umi, {
|
|
126
|
+
data: this.supplyBankLiquiditySource,
|
|
127
|
+
}),
|
|
128
|
+
getMarginfiPriceOracle(this.umi, {
|
|
129
|
+
data: this.debtBankLiquiditySource,
|
|
130
|
+
}),
|
|
131
|
+
]);
|
|
132
|
+
this.supplyBankHealthAccounts = [
|
|
133
|
+
getAccountMeta(
|
|
134
|
+
toWeb3JsPublicKey(this.supplyBankLiquiditySource.publicKey)
|
|
135
|
+
),
|
|
136
|
+
getAccountMeta(supplyPriceOracle),
|
|
137
|
+
];
|
|
138
|
+
this.debtBankHealthAccounts = [
|
|
139
|
+
getAccountMeta(toWeb3JsPublicKey(this.debtBankLiquiditySource.publicKey)),
|
|
140
|
+
getAccountMeta(debtPriceOracle),
|
|
141
|
+
];
|
|
120
142
|
}
|
|
121
143
|
|
|
122
144
|
private async setIntermediaryAccount(sources: TokenType[]) {
|
|
@@ -242,6 +264,12 @@ export class MarginfiFlProvider extends FlProviderBase {
|
|
|
242
264
|
: this.debtImfiAccount;
|
|
243
265
|
}
|
|
244
266
|
|
|
267
|
+
private bankHealthAccounts(source: TokenType): AccountMeta[] {
|
|
268
|
+
return source === TokenType.Supply
|
|
269
|
+
? this.supplyBankHealthAccounts
|
|
270
|
+
: this.debtBankHealthAccounts;
|
|
271
|
+
}
|
|
272
|
+
|
|
245
273
|
liquidityAvailable(source: TokenType): bigint {
|
|
246
274
|
return getBankLiquidityAvailableBaseUnit(this.liquidityBank(source), false);
|
|
247
275
|
}
|
|
@@ -273,6 +301,22 @@ export class MarginfiFlProvider extends FlProviderBase {
|
|
|
273
301
|
toWeb3JsPublicKey(bank.publicKey)
|
|
274
302
|
);
|
|
275
303
|
const iMfiAccount = this.iMfiAccount(flashLoan.liquiditySource)!;
|
|
304
|
+
const existingRemainingAccounts =
|
|
305
|
+
flashLoan.liquiditySource === TokenType.Supply
|
|
306
|
+
? this.supplyRemainingAccounts
|
|
307
|
+
: this.debtRemainingAccounts;
|
|
308
|
+
// Marginfi's circuit-breaker gate scans every active bank during borrow,
|
|
309
|
+
// including borrows made while the account is inside a flash loan.
|
|
310
|
+
const borrowRemainingAccounts = [...existingRemainingAccounts];
|
|
311
|
+
if (
|
|
312
|
+
!borrowRemainingAccounts.some(
|
|
313
|
+
(account) => account.pubkey.toString() === bank.publicKey.toString()
|
|
314
|
+
)
|
|
315
|
+
) {
|
|
316
|
+
borrowRemainingAccounts.push(
|
|
317
|
+
...this.bankHealthAccounts(flashLoan.liquiditySource)
|
|
318
|
+
);
|
|
319
|
+
}
|
|
276
320
|
|
|
277
321
|
return transactionBuilder()
|
|
278
322
|
.add(
|
|
@@ -295,7 +339,9 @@ export class MarginfiFlProvider extends FlProviderBase {
|
|
|
295
339
|
marginfiAccount: publicKey(iMfiAccount.accountPk),
|
|
296
340
|
marginfiGroup: this.liquidityBank(flashLoan.liquiditySource).group,
|
|
297
341
|
signer: this.signer,
|
|
298
|
-
})
|
|
342
|
+
}).addRemainingAccounts(
|
|
343
|
+
composeRemainingAccounts(borrowRemainingAccounts)
|
|
344
|
+
)
|
|
299
345
|
);
|
|
300
346
|
}
|
|
301
347
|
|
|
@@ -279,11 +279,7 @@ export class TransactionsManager<T extends TxHandler> {
|
|
|
279
279
|
if (this.atomically) {
|
|
280
280
|
await this.processTransactionsAtomically(itemSets);
|
|
281
281
|
} else {
|
|
282
|
-
|
|
283
|
-
while (currentIndex < itemSets.length) {
|
|
284
|
-
await this.processTransactionSet(itemSets, currentIndex);
|
|
285
|
-
currentIndex++;
|
|
286
|
-
}
|
|
282
|
+
await this.processTransactionSetsSequentially(itemSets);
|
|
287
283
|
}
|
|
288
284
|
|
|
289
285
|
return this.statuses;
|
|
@@ -366,44 +362,75 @@ export class TransactionsManager<T extends TxHandler> {
|
|
|
366
362
|
}
|
|
367
363
|
|
|
368
364
|
private async processTransactionsAtomically(itemSets: TransactionSet[]) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
prevError,
|
|
381
|
-
attemptNum
|
|
382
|
-
);
|
|
365
|
+
let bundleSendAttempted = false;
|
|
366
|
+
try {
|
|
367
|
+
await retryWithExponentialBackoff(
|
|
368
|
+
async (attemptNum, prevError) => {
|
|
369
|
+
if (
|
|
370
|
+
prevError &&
|
|
371
|
+
this.statuses.filter((x) => x.simulationSuccessful).length >
|
|
372
|
+
this.signableRetries
|
|
373
|
+
) {
|
|
374
|
+
throw prevError;
|
|
375
|
+
}
|
|
383
376
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
attemptNum,
|
|
388
|
-
prevError
|
|
377
|
+
this.priorityFeeSetting = this.getUpdatedPriorityFeeSetting(
|
|
378
|
+
prevError,
|
|
379
|
+
attemptNum
|
|
389
380
|
);
|
|
390
|
-
|
|
381
|
+
|
|
382
|
+
if (attemptNum > 0) {
|
|
383
|
+
const refreshedSets = await this.refreshItemSets(
|
|
384
|
+
itemSets,
|
|
385
|
+
attemptNum,
|
|
386
|
+
prevError
|
|
387
|
+
);
|
|
388
|
+
if (!refreshedSets || !refreshedSets.length) {
|
|
389
|
+
return;
|
|
390
|
+
} else {
|
|
391
|
+
itemSets = refreshedSets;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (!this.shouldProceedToSend(itemSets, attemptNum)) {
|
|
391
396
|
return;
|
|
392
|
-
} else {
|
|
393
|
-
itemSets = refreshedSets;
|
|
394
397
|
}
|
|
395
|
-
}
|
|
396
398
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
399
|
+
bundleSendAttempted = true;
|
|
400
|
+
await this.sendJitoBundle(itemSets, attemptNum);
|
|
401
|
+
},
|
|
402
|
+
this.totalRetries,
|
|
403
|
+
this.retryDelay,
|
|
404
|
+
this.errorsToThrow
|
|
405
|
+
);
|
|
406
|
+
} catch (error: any) {
|
|
407
|
+
const explicitlyNonRetryable = this.errorsToThrow?.some(
|
|
408
|
+
(errorType) => error instanceof errorType
|
|
409
|
+
);
|
|
410
|
+
if (
|
|
411
|
+
!bundleSendAttempted ||
|
|
412
|
+
itemSets.length <= 1 ||
|
|
413
|
+
explicitlyNonRetryable ||
|
|
414
|
+
this.abortController?.signal.aborted
|
|
415
|
+
) {
|
|
416
|
+
throw error;
|
|
417
|
+
}
|
|
400
418
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
this.
|
|
405
|
-
|
|
406
|
-
|
|
419
|
+
consoleLog(
|
|
420
|
+
"Jito bundle failed after all retries; retrying one transaction at a time"
|
|
421
|
+
);
|
|
422
|
+
await this.processTransactionSetsSequentially(itemSets);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
private async processTransactionSetsSequentially(
|
|
427
|
+
itemSets: TransactionSet[]
|
|
428
|
+
) {
|
|
429
|
+
let currentIndex = 0;
|
|
430
|
+
while (currentIndex < itemSets.length) {
|
|
431
|
+
await this.processTransactionSet(itemSets, currentIndex);
|
|
432
|
+
currentIndex++;
|
|
433
|
+
}
|
|
407
434
|
}
|
|
408
435
|
|
|
409
436
|
private async sendJitoBundle(itemSets: TransactionSet[], attemptNum: number) {
|
|
@@ -227,8 +227,17 @@ export function composeRemainingAccounts(accs: AccountMeta[]): AccountMeta[] {
|
|
|
227
227
|
[]
|
|
228
228
|
);
|
|
229
229
|
return banksAndOracles
|
|
230
|
-
.sort((a, b) =>
|
|
231
|
-
|
|
232
|
-
|
|
230
|
+
.sort((a, b) => {
|
|
231
|
+
const aBytes = toWeb3JsPublicKey(a[0].pubkey).toBuffer();
|
|
232
|
+
const bBytes = toWeb3JsPublicKey(b[0].pubkey).toBuffer();
|
|
233
|
+
|
|
234
|
+
for (let i = 0; i < aBytes.length; i++) {
|
|
235
|
+
if (aBytes[i] !== bBytes[i]) {
|
|
236
|
+
return bBytes[i] - aBytes[i];
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return 0;
|
|
241
|
+
})
|
|
233
242
|
.flat();
|
|
234
243
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { assert } from "chai";
|
|
2
|
+
import { describe, it } from "mocha";
|
|
3
|
+
import { AccountMeta, publicKey } from "@metaplex-foundation/umi";
|
|
4
|
+
import { composeRemainingAccounts } from "../../src";
|
|
5
|
+
|
|
6
|
+
function readonlyMeta(address: string): AccountMeta {
|
|
7
|
+
return {
|
|
8
|
+
pubkey: publicKey(address),
|
|
9
|
+
isSigner: false,
|
|
10
|
+
isWritable: false,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe("Marginfi remaining accounts", () => {
|
|
15
|
+
it("sorts bank groups by descending public-key bytes", () => {
|
|
16
|
+
// Base58 locale ordering puts DMoq before DeyH, but Marginfi's Rust
|
|
17
|
+
// comparator orders their decoded public-key bytes in the opposite order.
|
|
18
|
+
const deYhBank = readonlyMeta(
|
|
19
|
+
"DeyH7QxWvnbbaVB4zFrf4hoq7Q8z1ZT14co42BGwGtfM"
|
|
20
|
+
);
|
|
21
|
+
const deYhOracle = readonlyMeta(
|
|
22
|
+
"SysvarC1ock11111111111111111111111111111111"
|
|
23
|
+
);
|
|
24
|
+
const dMoqBank = readonlyMeta(
|
|
25
|
+
"DMoqjmsuoru986HgfjqrKEvPv8YBufvBGADHUonkadC5"
|
|
26
|
+
);
|
|
27
|
+
const dMoqOracle = readonlyMeta(
|
|
28
|
+
"SysvarRent111111111111111111111111111111111"
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const result = composeRemainingAccounts([
|
|
32
|
+
dMoqBank,
|
|
33
|
+
dMoqOracle,
|
|
34
|
+
deYhBank,
|
|
35
|
+
deYhOracle,
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
assert.deepEqual(
|
|
39
|
+
result.map((account) => account.pubkey.toString()),
|
|
40
|
+
[deYhBank, deYhOracle, dMoqBank, dMoqOracle].map((account) =>
|
|
41
|
+
account.pubkey.toString()
|
|
42
|
+
)
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { describe, it } from "mocha";
|
|
3
|
+
import { TransactionsManager } from "../../src";
|
|
4
|
+
|
|
5
|
+
function createManager(abortController?: AbortController) {
|
|
6
|
+
return new TransactionsManager({
|
|
7
|
+
txHandler: {
|
|
8
|
+
defaultLookupTables: () => [],
|
|
9
|
+
umi: {},
|
|
10
|
+
} as any,
|
|
11
|
+
retryConfig: { totalRetries: 1, retryDelay: 0 },
|
|
12
|
+
abortController,
|
|
13
|
+
}) as any;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe("TransactionsManager bundle fallback", () => {
|
|
17
|
+
it("retries a failed multi-transaction bundle sequentially", async () => {
|
|
18
|
+
const manager = createManager();
|
|
19
|
+
const calls: string[] = [];
|
|
20
|
+
const itemSets = [{}, {}, {}] as any[];
|
|
21
|
+
|
|
22
|
+
manager.shouldProceedToSend = () => true;
|
|
23
|
+
manager.sendJitoBundle = async () => {
|
|
24
|
+
calls.push("bundle");
|
|
25
|
+
throw new Error("bundle failed");
|
|
26
|
+
};
|
|
27
|
+
manager.processTransactionSet = async (
|
|
28
|
+
receivedSets: any[],
|
|
29
|
+
currentIndex: number
|
|
30
|
+
) => {
|
|
31
|
+
expect(receivedSets).to.equal(itemSets);
|
|
32
|
+
calls.push(`single:${currentIndex}`);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
await manager.processTransactionsAtomically(itemSets);
|
|
36
|
+
|
|
37
|
+
expect(calls).to.deep.equal([
|
|
38
|
+
"bundle",
|
|
39
|
+
"single:0",
|
|
40
|
+
"single:1",
|
|
41
|
+
"single:2",
|
|
42
|
+
]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("does not fall back after the request is aborted", async () => {
|
|
46
|
+
const abortController = new AbortController();
|
|
47
|
+
const manager = createManager(abortController);
|
|
48
|
+
let sequentialAttempts = 0;
|
|
49
|
+
|
|
50
|
+
manager.shouldProceedToSend = () => true;
|
|
51
|
+
manager.sendJitoBundle = async () => {
|
|
52
|
+
abortController.abort();
|
|
53
|
+
throw new Error("bundle aborted");
|
|
54
|
+
};
|
|
55
|
+
manager.processTransactionSet = async () => {
|
|
56
|
+
sequentialAttempts++;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
let thrownError: Error | undefined;
|
|
60
|
+
try {
|
|
61
|
+
await manager.processTransactionsAtomically([{}, {}]);
|
|
62
|
+
} catch (error: any) {
|
|
63
|
+
thrownError = error;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
expect(thrownError?.message).to.equal("bundle aborted");
|
|
67
|
+
expect(sequentialAttempts).to.equal(0);
|
|
68
|
+
});
|
|
69
|
+
});
|