@0dotxyz/p0-ts-sdk 2.5.5-alpha.4 → 2.5.5-alpha.6

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/index.cjs CHANGED
@@ -131,6 +131,7 @@ var TransactionBuildingErrorCode = /* @__PURE__ */ ((TransactionBuildingErrorCod
131
131
  TransactionBuildingErrorCode2["TRANSFER_POSITIONS_INVALID_SELECTION"] = "TRANSFER_POSITIONS_INVALID_SELECTION";
132
132
  TransactionBuildingErrorCode2["TRANSFER_POSITIONS_UNSUPPORTED_BANK"] = "TRANSFER_POSITIONS_UNSUPPORTED_BANK";
133
133
  TransactionBuildingErrorCode2["TRANSFER_POSITIONS_UNSPLITTABLE"] = "TRANSFER_POSITIONS_UNSPLITTABLE";
134
+ TransactionBuildingErrorCode2["BRIDGE_CONFLICT"] = "BRIDGE_CONFLICT";
134
135
  return TransactionBuildingErrorCode2;
135
136
  })(TransactionBuildingErrorCode || {});
136
137
  var TransactionBuildingError = class _TransactionBuildingError extends Error {
@@ -261,6 +262,19 @@ var TransactionBuildingError = class _TransactionBuildingError extends Error {
261
262
  { reason, sizeBytes, accountCount }
262
263
  );
263
264
  }
265
+ /**
266
+ * A bridged (double-hop) swap could not route because every bridge-token candidate bank
267
+ * conflicts with an existing opposite-side position on the account (marginfi forbids holding an
268
+ * asset and a liability on the same bank).
269
+ */
270
+ static bridgeConflict(conflictingBanks, bridgeTokenSide) {
271
+ const banksList = conflictingBanks.map((c) => c.symbol ?? c.mint).join(", ");
272
+ return new _TransactionBuildingError(
273
+ "BRIDGE_CONFLICT" /* BRIDGE_CONFLICT */,
274
+ `Every bridge-token candidate conflicts with an existing opposite-side position: ${banksList}`,
275
+ { conflictingBanks, bridgeTokenSide }
276
+ );
277
+ }
264
278
  /**
265
279
  * Generic escape hatch for custom errors
266
280
  */
@@ -277,6 +291,9 @@ var DECOMPOSABLE_SWAP_ERROR_CODES = /* @__PURE__ */ new Set([
277
291
  function isDecomposableSwapError(e) {
278
292
  return e instanceof TransactionBuildingError && DECOMPOSABLE_SWAP_ERROR_CODES.has(e.code);
279
293
  }
294
+ function isBridgeConflictError(e) {
295
+ return e instanceof TransactionBuildingError && e.code === "BRIDGE_CONFLICT" /* BRIDGE_CONFLICT */;
296
+ }
280
297
  var PDA_BANK_LIQUIDITY_VAULT_AUTH_SEED = Buffer.from("liquidity_vault_auth");
281
298
  var PDA_BANK_INSURANCE_VAULT_AUTH_SEED = Buffer.from("insurance_vault_auth");
282
299
  var PDA_BANK_FEE_VAULT_AUTH_SEED = Buffer.from("fee_vault_auth");
@@ -436,6 +453,7 @@ var PRIORITY_TX_SIZE = 44;
436
453
  var WSOL_MINT = new web3_js.PublicKey("So11111111111111111111111111111111111111112");
437
454
  var LST_MINT = new web3_js.PublicKey("LSTxxxnJzKDFSLr4dUkPcmCf5VyryEqzPLz5j4bpxFp");
438
455
  var USDC_MINT = new web3_js.PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
456
+ var USDT_MINT = new web3_js.PublicKey("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB");
439
457
  var USDC_DECIMALS = 6;
440
458
 
441
459
  // src/utils/accounting.utils.ts
@@ -67237,1775 +67255,298 @@ function annotateFit(route, req) {
67237
67255
  };
67238
67256
  }
67239
67257
 
67240
- // src/services/account/actions/loop.ts
67241
- async function makeLoopTx(params) {
67242
- const {
67243
- program,
67244
- marginfiAccount,
67245
- bankMap,
67246
- depositOpts,
67247
- borrowOpts,
67248
- bankMetadataMap,
67249
- addressLookupTableAccounts,
67250
- connection,
67251
- oraclePrices,
67252
- crossbarUrl,
67253
- additionalIxs = []
67254
- } = params;
67255
- const blockhash = (await connection.getLatestBlockhash("confirmed")).blockhash;
67256
- const setupIxs = await makeSetupIx({
67257
- connection,
67258
- authority: marginfiAccount.authority,
67259
- tokens: [
67260
- {
67261
- mint: borrowOpts.borrowBank.mint,
67262
- tokenProgram: borrowOpts.tokenProgram
67263
- },
67264
- {
67265
- mint: depositOpts.depositBank.mint,
67266
- tokenProgram: depositOpts.tokenProgram
67267
- }
67268
- ]
67269
- });
67270
- const refreshIntegrationIxs = makeRefreshIntegrationBanksIxs(
67271
- params.marginfiAccount,
67272
- params.bankMap,
67273
- [depositOpts.depositBank.address],
67274
- params.bankMetadataMap,
67275
- [borrowOpts.borrowBank.address, depositOpts.depositBank.address]
67276
- );
67277
- const { flashloanTx, setupInstructions, swapQuote, depositIxs, borrowIxs } = await buildLoopFlashloanTx({
67278
- ...params,
67279
- blockhash
67280
- });
67281
- const jupiterSetupInstructions = setupInstructions.filter((ix) => {
67282
- if (ix.programId.equals(web3_js.ComputeBudgetProgram.programId)) {
67283
- return false;
67284
- }
67285
- if (ix.programId.equals(ASSOCIATED_TOKEN_PROGRAM_ID)) {
67286
- const mintKey = ix.keys[3]?.pubkey;
67287
- if (mintKey?.equals(depositOpts.depositBank.mint) || mintKey?.equals(borrowOpts.borrowBank.mint)) {
67288
- return false;
67289
- }
67290
- }
67291
- return true;
67292
- });
67293
- setupIxs.push(...jupiterSetupInstructions);
67294
- const { instructions: updateFeedIxs, luts: feedLuts } = await makeSmartCrankSwbFeedIx({
67295
- marginfiAccount,
67296
- bankMap,
67297
- oraclePrices,
67298
- assetShareValueMultiplierByBank: params.assetShareValueMultiplierByBank,
67299
- instructions: [...borrowIxs.instructions, ...depositIxs.instructions],
67300
- program,
67301
- connection,
67302
- crossbarUrl
67303
- });
67304
- let additionalTxs = [];
67305
- if (depositOpts.depositBank.mint.equals(NATIVE_MINT) && depositOpts.inputDepositAmount) {
67306
- setupIxs.push(
67307
- ...makeWrapSolIxs(marginfiAccount.authority, new BigNumber3.BigNumber(depositOpts.inputDepositAmount))
67308
- );
67258
+ // src/models/balance.ts
67259
+ var Balance = class _Balance {
67260
+ constructor(active, bankPk, assetShares, liabilityShares, emissionsOutstanding, lastUpdate) {
67261
+ this.active = active;
67262
+ this.bankPk = bankPk;
67263
+ this.assetShares = assetShares;
67264
+ this.liabilityShares = liabilityShares;
67265
+ this.emissionsOutstanding = emissionsOutstanding;
67266
+ this.lastUpdate = lastUpdate;
67309
67267
  }
67310
- if (setupIxs.length > 0 || additionalIxs.length > 0 || refreshIntegrationIxs.instructions.length > 0) {
67311
- const ixs = [...additionalIxs, ...setupIxs, ...refreshIntegrationIxs.instructions];
67312
- const txs = splitInstructionsToFitTransactions([], ixs, {
67313
- blockhash,
67314
- payerKey: marginfiAccount.authority,
67315
- luts: addressLookupTableAccounts ?? []
67316
- });
67317
- additionalTxs.push(
67318
- ...txs.map(
67319
- (tx) => addTransactionMetadata(tx, {
67320
- type: "CREATE_ATA" /* CREATE_ATA */,
67321
- addressLookupTables: addressLookupTableAccounts
67322
- })
67323
- )
67268
+ static from(balanceRaw) {
67269
+ const props = parseBalanceRaw(balanceRaw);
67270
+ return new _Balance(
67271
+ props.active,
67272
+ props.bankPk,
67273
+ props.assetShares,
67274
+ props.liabilityShares,
67275
+ props.emissionsOutstanding,
67276
+ props.lastUpdate
67324
67277
  );
67325
67278
  }
67326
- if (updateFeedIxs.length > 0) {
67327
- const message = new web3_js.TransactionMessage({
67328
- payerKey: marginfiAccount.authority,
67329
- recentBlockhash: blockhash,
67330
- instructions: updateFeedIxs
67331
- }).compileToV0Message(feedLuts);
67332
- additionalTxs.push(
67333
- addTransactionMetadata(new web3_js.VersionedTransaction(message), {
67334
- addressLookupTables: feedLuts,
67335
- type: "CRANK" /* CRANK */
67336
- })
67279
+ static fromBalanceType(balance) {
67280
+ return new _Balance(
67281
+ balance.active,
67282
+ balance.bankPk,
67283
+ balance.assetShares,
67284
+ balance.liabilityShares,
67285
+ balance.emissionsOutstanding,
67286
+ balance.lastUpdate
67337
67287
  );
67338
67288
  }
67339
- const transactions = [...additionalTxs, flashloanTx];
67340
- return {
67341
- transactions,
67342
- actionTxIndex: transactions.length - 1,
67343
- quoteResponse: swapQuote
67344
- };
67345
- }
67346
- async function buildLoopFlashloanTx(params) {
67347
- const { depositOpts } = params;
67348
- const { descriptor, swapNeeded, borrowIxs, depositIxs } = await buildLoopNonSwapIxs(params);
67349
- if (!swapNeeded) {
67350
- const flashloanTx2 = await finalizeLoopFlashloanTx({
67351
- params,
67352
- innerIxs: descriptor.innerIxs,
67353
- luts: descriptor.luts,
67354
- swapIxCount: 0,
67355
- swapLutCount: 0,
67356
- sizeConstraint: descriptor.sizeConstraint
67289
+ static createEmpty(bankPk) {
67290
+ const balance = createEmptyBalance(bankPk);
67291
+ return this.fromBalanceType(balance);
67292
+ }
67293
+ computeUsdValue(bank, oraclePrice, marginRequirement = 2 /* Equity */, assetShareValueMultiplier, activeEmodeWeights) {
67294
+ return computeBalanceUsdValue({
67295
+ balance: this,
67296
+ bank,
67297
+ oraclePrice,
67298
+ marginRequirement,
67299
+ assetShareValueMultiplier,
67300
+ activeEmodeWeights
67357
67301
  });
67358
- return {
67359
- flashloanTx: flashloanTx2,
67360
- setupInstructions: [],
67361
- swapQuote: void 0,
67362
- borrowIxs,
67363
- depositIxs
67364
- };
67365
67302
  }
67366
- const engineResult = await runLoopSwapEngine(descriptor, params);
67367
- const finalIxs = [...descriptor.innerIxs];
67368
- finalIxs.splice(descriptor.swapSlotIndex, 0, ...engineResult.swapInstructions);
67369
- const principalNative = depositOpts.loopMode === "DEPOSIT" ? uiToNative(depositOpts.inputDepositAmount, depositOpts.depositBank.mintDecimals) : new BN9__default.default(0);
67370
- const finalDepositNative = engineResult.outputAmountNative.add(principalNative);
67371
- const depositIxPosition = descriptor.depositIxIndex + engineResult.swapInstructions.length;
67372
- patchDepositAmount(finalIxs[depositIxPosition], finalDepositNative);
67373
- const luts = [...descriptor.luts, ...engineResult.swapLuts];
67374
- const flashloanTx = await finalizeLoopFlashloanTx({
67375
- params,
67376
- innerIxs: finalIxs,
67377
- luts,
67378
- swapIxCount: engineResult.swapInstructions.length,
67379
- swapLutCount: engineResult.swapLuts.length,
67380
- sizeConstraint: descriptor.sizeConstraint
67381
- });
67382
- return {
67383
- flashloanTx,
67384
- setupInstructions: engineResult.setupInstructions,
67385
- swapQuote: engineResult.quoteResponse,
67386
- borrowIxs,
67387
- depositIxs
67388
- };
67389
- }
67390
- async function buildLoopNonSwapIxs(params) {
67391
- const {
67392
- program,
67393
- marginfiAccount,
67394
- bankMap,
67395
- borrowOpts,
67396
- depositOpts,
67397
- bankMetadataMap,
67398
- addressLookupTableAccounts,
67399
- overrideInferAccounts
67400
- } = params;
67401
- const cuRequestIxs = [
67402
- web3_js.ComputeBudgetProgram.setComputeUnitLimit({ units: 12e5 }),
67403
- web3_js.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1 })
67404
- ];
67405
- const swapNeeded = !depositOpts.depositBank.mint.equals(borrowOpts.borrowBank.mint);
67406
- const destinationTokenAccount = getAssociatedTokenAddressSync(
67407
- new web3_js.PublicKey(depositOpts.depositBank.mint),
67408
- marginfiAccount.authority,
67409
- true,
67410
- depositOpts.tokenProgram.equals(TOKEN_2022_PROGRAM_ID) ? TOKEN_2022_PROGRAM_ID : void 0
67411
- );
67412
- const principalUi = depositOpts.loopMode === "DEPOSIT" ? depositOpts.inputDepositAmount : 0;
67413
- let depositAmountUi;
67414
- let sizeConstraint = 0;
67415
- let maxSwapTotalAccounts = 0;
67416
- if (!swapNeeded) {
67417
- depositAmountUi = borrowOpts.borrowAmount + principalUi;
67418
- } else {
67419
- const swapConstraints = await computeFlashloanSwapConstraints({
67420
- program,
67421
- marginfiAccount,
67422
- bankMap,
67423
- bankMetadataMap,
67424
- addressLookupTableAccounts: addressLookupTableAccounts ?? [],
67425
- primaryIx: {
67426
- type: "borrow",
67427
- bank: borrowOpts.borrowBank,
67428
- tokenProgram: borrowOpts.tokenProgram
67429
- },
67430
- secondaryIx: {
67431
- type: "deposit",
67432
- bank: depositOpts.depositBank,
67433
- tokenProgram: depositOpts.tokenProgram
67434
- },
67435
- overrideInferAccounts
67303
+ getUsdValueWithPriceBias(bank, oraclePrice, marginRequirement = 2 /* Equity */, assetShareValueMultiplier, activeEmodeWeights) {
67304
+ return getBalanceUsdValueWithPriceBias({
67305
+ balance: this,
67306
+ bank,
67307
+ oraclePrice,
67308
+ marginRequirement,
67309
+ assetShareValueMultiplier,
67310
+ activeEmodeWeights
67436
67311
  });
67437
- sizeConstraint = swapConstraints.sizeConstraint;
67438
- maxSwapTotalAccounts = swapConstraints.maxSwapTotalAccounts;
67439
- const estimateUi = borrowOpts.borrowAmount * borrowOpts.marketPrice / depositOpts.marketPrice;
67440
- depositAmountUi = estimateUi + principalUi;
67441
67312
  }
67442
- const borrowIxs = await makeBorrowIx3({
67443
- program,
67444
- bank: borrowOpts.borrowBank,
67445
- bankMap,
67446
- tokenProgram: borrowOpts.tokenProgram,
67447
- amount: borrowOpts.borrowAmount,
67448
- marginfiAccount,
67449
- authority: marginfiAccount.authority,
67450
- isSync: false,
67451
- opts: {
67452
- createAtas: false,
67453
- wrapAndUnwrapSol: false,
67454
- overrideInferAccounts
67455
- }
67456
- });
67457
- const depositIxs = await buildDepositIxs(params, depositAmountUi);
67458
- const innerIxsBeforeSwap = [...cuRequestIxs, ...borrowIxs.instructions];
67459
- const swapSlotIndex = innerIxsBeforeSwap.length;
67460
- const innerIxs = [...innerIxsBeforeSwap, ...depositIxs.instructions];
67461
- const depositIxIndex = innerIxs.findIndex(isDepositIx);
67462
- if (depositIxIndex < 0) {
67463
- throw new Error(
67464
- "buildLoopNonSwapIxs: could not locate deposit instruction for amount patching"
67465
- );
67313
+ computeQuantity(bank) {
67314
+ return computeQuantity(this, bank);
67466
67315
  }
67467
- const descriptor = {
67468
- innerIxs,
67469
- swapSlotIndex,
67470
- depositIxIndex,
67471
- inputMint: borrowOpts.borrowBank.mint.toBase58(),
67472
- outputMint: depositOpts.depositBank.mint.toBase58(),
67473
- inputDecimals: borrowOpts.borrowBank.mintDecimals,
67474
- outputDecimals: depositOpts.depositBank.mintDecimals,
67475
- inAmountNative: uiToNative(
67476
- borrowOpts.borrowAmount,
67477
- borrowOpts.borrowBank.mintDecimals
67478
- ).toNumber(),
67479
- destinationTokenAccount,
67480
- sizeConstraint,
67481
- maxSwapTotalAccounts,
67482
- luts: addressLookupTableAccounts ?? []
67483
- };
67484
- return { descriptor, swapNeeded, borrowIxs, depositIxs };
67485
- }
67486
- async function buildDepositIxs(params, amountUi) {
67487
- const { program, marginfiAccount, depositOpts, bankMetadataMap, overrideInferAccounts } = params;
67488
- switch (depositOpts.depositBank.config.assetTag) {
67489
- case 3 /* KAMINO */: {
67490
- const reserve = bankMetadataMap[depositOpts.depositBank.address.toBase58()]?.kaminoStates?.reserveState;
67491
- if (!reserve) {
67492
- throw TransactionBuildingError.kaminoReserveNotFound(
67493
- depositOpts.depositBank.address.toBase58(),
67494
- depositOpts.depositBank.mint.toBase58(),
67495
- depositOpts.depositBank.tokenSymbol
67496
- );
67497
- }
67498
- return makeKaminoDepositIx3({
67499
- program,
67500
- bank: depositOpts.depositBank,
67501
- tokenProgram: depositOpts.tokenProgram,
67502
- amount: amountUi,
67503
- accountAddress: marginfiAccount.address,
67504
- authority: marginfiAccount.authority,
67505
- group: marginfiAccount.group,
67506
- reserve,
67507
- opts: {
67508
- wrapAndUnwrapSol: false,
67509
- overrideInferAccounts
67510
- }
67511
- });
67512
- }
67513
- case 4 /* DRIFT */: {
67514
- const driftState = bankMetadataMap[depositOpts.depositBank.address.toBase58()]?.driftStates;
67515
- if (!driftState) {
67516
- throw TransactionBuildingError.driftStateNotFound(
67517
- depositOpts.depositBank.address.toBase58(),
67518
- depositOpts.depositBank.mint.toBase58(),
67519
- depositOpts.depositBank.tokenSymbol
67520
- );
67521
- }
67522
- return makeDriftDepositIx3({
67523
- program,
67524
- bank: depositOpts.depositBank,
67525
- tokenProgram: depositOpts.tokenProgram,
67526
- amount: amountUi,
67527
- accountAddress: marginfiAccount.address,
67528
- authority: marginfiAccount.authority,
67529
- group: marginfiAccount.group,
67530
- driftMarketIndex: driftState.spotMarketState.marketIndex,
67531
- driftOracle: driftState.spotMarketState.oracle,
67532
- opts: {
67533
- wrapAndUnwrapSol: false,
67534
- overrideInferAccounts
67535
- }
67536
- });
67537
- }
67538
- case 6 /* JUPLEND */: {
67539
- return makeJuplendDepositIx2({
67540
- program,
67541
- bank: depositOpts.depositBank,
67542
- tokenProgram: depositOpts.tokenProgram,
67543
- amount: amountUi,
67544
- accountAddress: marginfiAccount.address,
67545
- authority: marginfiAccount.authority,
67546
- group: marginfiAccount.group,
67547
- opts: {
67548
- wrapAndUnwrapSol: false,
67549
- overrideInferAccounts
67550
- }
67551
- });
67552
- }
67553
- default: {
67554
- return makeDepositIx3({
67555
- program,
67556
- bank: depositOpts.depositBank,
67557
- tokenProgram: depositOpts.tokenProgram,
67558
- amount: amountUi,
67559
- accountAddress: marginfiAccount.address,
67560
- authority: marginfiAccount.authority,
67561
- group: marginfiAccount.group,
67562
- opts: {
67563
- wrapAndUnwrapSol: false,
67564
- overrideInferAccounts
67565
- }
67566
- });
67567
- }
67316
+ computeQuantityUi(bank, assetShareValueMultiplier) {
67317
+ return computeQuantityUi(this, bank, assetShareValueMultiplier);
67568
67318
  }
67569
- }
67570
- async function runLoopSwapEngine(descriptor, params) {
67571
- const { connection, swapOpts, marginfiAccount, swapEngineRunner } = params;
67572
- if (swapOpts.swapIxs) {
67573
- return {
67574
- swapInstructions: swapOpts.swapIxs.instructions,
67575
- setupInstructions: [],
67576
- swapLuts: swapOpts.swapIxs.lookupTables,
67577
- quoteResponse: {
67578
- inAmount: String(descriptor.inAmountNative),
67579
- outAmount: "0",
67580
- otherAmountThreshold: "0",
67581
- slippageBps: 0
67582
- },
67583
- outputAmountNative: new BN9__default.default(0)
67584
- };
67319
+ computeTotalOutstandingEmissions(bank) {
67320
+ return computeTotalOutstandingEmissions(this, bank);
67585
67321
  }
67586
- const runEngine = swapEngineRunner ?? runSwapEngine;
67587
- const engineResult = await runEngine({
67588
- inputMint: descriptor.inputMint,
67589
- outputMint: descriptor.outputMint,
67590
- amountNative: descriptor.inAmountNative,
67591
- inputDecimals: descriptor.inputDecimals,
67592
- outputDecimals: descriptor.outputDecimals,
67593
- ...swapEngineQuoteFieldsFromOpts(swapOpts),
67594
- taker: marginfiAccount.authority,
67595
- destinationTokenAccount: descriptor.destinationTokenAccount,
67596
- connection,
67597
- footprint: {
67598
- instructions: descriptor.innerIxs,
67599
- luts: descriptor.luts,
67600
- payer: marginfiAccount.authority,
67601
- sizeConstraint: descriptor.sizeConstraint,
67602
- maxSwapTotalAccounts: descriptor.maxSwapTotalAccounts
67603
- },
67604
- providers: swapEngineProvidersFromOpts(swapOpts)
67605
- });
67606
- return {
67607
- swapInstructions: engineResult.swapInstructions,
67608
- setupInstructions: engineResult.setupInstructions,
67609
- swapLuts: engineResult.swapLuts,
67610
- quoteResponse: engineResult.quoteResponse,
67611
- outputAmountNative: engineResult.outputAmountNative
67612
- };
67613
- }
67614
- async function finalizeLoopFlashloanTx({
67615
- params,
67616
- innerIxs,
67617
- luts,
67618
- swapIxCount,
67619
- swapLutCount,
67620
- sizeConstraint
67621
- }) {
67622
- const { program, marginfiAccount, bankMap, swapOpts, blockhash } = params;
67623
- if (swapIxCount > 0) {
67624
- compileFlashloanPrecheck({
67625
- allIxs: innerIxs,
67626
- payer: marginfiAccount.authority,
67627
- luts,
67628
- sizeConstraint,
67629
- swapIxCount,
67630
- swapLutCount
67631
- });
67322
+ computeClaimedEmissions(bank, currentTimestamp) {
67323
+ return computeClaimedEmissions(this, bank, currentTimestamp);
67632
67324
  }
67633
- const flashloanTx = await makeFlashLoanTx({
67634
- program,
67635
- marginfiAccount,
67636
- bankMap,
67637
- addressLookupTableAccounts: luts,
67638
- blockhash,
67639
- ixs: innerIxs
67640
- });
67641
- const txSize = getTxSize(flashloanTx);
67642
- const totalKeys = getTotalAccountKeys(flashloanTx);
67643
- if (txSize > MAX_TX_SIZE || totalKeys > MAX_ACCOUNT_LOCKS) {
67644
- throw TransactionBuildingError.swapSizeExceededLoop(
67645
- txSize,
67646
- totalKeys,
67647
- swapOpts.swapConfig?.provider
67325
+ };
67326
+
67327
+ // src/models/health-cache.ts
67328
+ var HealthCache = class _HealthCache {
67329
+ constructor(assetValue, liabilityValue, assetValueMaint, liabilityValueMaint, assetValueEquity, liabilityValueEquity, timestamp, flags, prices, simulationStatus) {
67330
+ this.assetValue = assetValue;
67331
+ this.liabilityValue = liabilityValue;
67332
+ this.assetValueMaint = assetValueMaint;
67333
+ this.liabilityValueMaint = liabilityValueMaint;
67334
+ this.assetValueEquity = assetValueEquity;
67335
+ this.liabilityValueEquity = liabilityValueEquity;
67336
+ this.timestamp = timestamp;
67337
+ this.flags = flags;
67338
+ this.prices = prices;
67339
+ this.simulationStatus = simulationStatus;
67340
+ }
67341
+ static fromHealthCacheType(healthCacheType) {
67342
+ return new _HealthCache(
67343
+ healthCacheType.assetValue,
67344
+ healthCacheType.liabilityValue,
67345
+ healthCacheType.assetValueMaint,
67346
+ healthCacheType.liabilityValueMaint,
67347
+ healthCacheType.assetValueEquity,
67348
+ healthCacheType.liabilityValueEquity,
67349
+ healthCacheType.timestamp,
67350
+ healthCacheType.flags,
67351
+ healthCacheType.prices,
67352
+ healthCacheType.simulationStatus
67648
67353
  );
67649
67354
  }
67650
- return flashloanTx;
67651
- }
67652
- async function makeSwapCollateralTx(params) {
67653
- const {
67654
- program,
67655
- marginfiAccount,
67656
- connection,
67657
- bankMap,
67658
- oraclePrices,
67659
- withdrawOpts,
67660
- depositOpts,
67661
- bankMetadataMap,
67662
- assetShareValueMultiplierByBank,
67663
- addressLookupTableAccounts,
67664
- crossbarUrl,
67665
- additionalIxs = []
67666
- } = params;
67667
- const blockhash = (await connection.getLatestBlockhash("confirmed")).blockhash;
67668
- const setupIxs = await makeSetupIx({
67669
- connection,
67670
- authority: marginfiAccount.authority,
67671
- tokens: [
67672
- { mint: withdrawOpts.withdrawBank.mint, tokenProgram: withdrawOpts.tokenProgram },
67673
- { mint: depositOpts.depositBank.mint, tokenProgram: depositOpts.tokenProgram }
67674
- ]
67675
- });
67676
- const refreshIntegrationIxs = makeRefreshIntegrationBanksIxs(
67677
- marginfiAccount,
67678
- bankMap,
67679
- [withdrawOpts.withdrawBank.address, depositOpts.depositBank.address],
67680
- bankMetadataMap
67681
- );
67682
- const { flashloanTx, setupInstructions, swapQuote, withdrawIxs, depositIxs } = await buildSwapCollateralFlashloanTx({
67683
- ...params,
67684
- blockhash
67685
- });
67686
- const jupiterSetupInstructions = setupInstructions.filter((ix) => {
67687
- if (ix.programId.equals(web3_js.ComputeBudgetProgram.programId)) {
67688
- return false;
67689
- }
67690
- if (ix.programId.equals(ASSOCIATED_TOKEN_PROGRAM_ID)) {
67691
- const mintKey = ix.keys[3]?.pubkey;
67692
- if (mintKey?.equals(withdrawOpts.withdrawBank.mint) || mintKey?.equals(depositOpts.depositBank.mint)) {
67693
- return false;
67694
- }
67695
- }
67696
- return true;
67697
- });
67698
- setupIxs.push(...jupiterSetupInstructions);
67699
- const { instructions: updateFeedIxs, luts: feedLuts } = await makeSmartCrankSwbFeedIx({
67700
- marginfiAccount,
67701
- bankMap,
67702
- oraclePrices,
67703
- assetShareValueMultiplierByBank,
67704
- instructions: [...withdrawIxs.instructions, ...depositIxs.instructions],
67705
- program,
67706
- connection,
67707
- crossbarUrl
67708
- });
67709
- let additionalTxs = [];
67710
- if (setupIxs.length > 0 || refreshIntegrationIxs.instructions.length > 0) {
67711
- const ixs = [...setupIxs, ...refreshIntegrationIxs.instructions];
67712
- const txs = splitInstructionsToFitTransactions([], ixs, {
67713
- blockhash,
67714
- payerKey: marginfiAccount.authority,
67715
- luts: addressLookupTableAccounts ?? []
67716
- });
67717
- additionalTxs.push(
67718
- ...txs.map(
67719
- (tx) => addTransactionMetadata(tx, {
67720
- type: "CREATE_ATA" /* CREATE_ATA */,
67721
- addressLookupTables: addressLookupTableAccounts
67722
- })
67723
- )
67724
- );
67355
+ static from(healthCacheRaw) {
67356
+ return this.fromHealthCacheType(parseHealthCacheRaw(healthCacheRaw));
67725
67357
  }
67726
- if (updateFeedIxs.length > 0) {
67727
- const message = new web3_js.TransactionMessage({
67728
- payerKey: marginfiAccount.authority,
67729
- recentBlockhash: blockhash,
67730
- instructions: updateFeedIxs
67731
- }).compileToV0Message(feedLuts);
67732
- additionalTxs.push(
67733
- addTransactionMetadata(new web3_js.VersionedTransaction(message), {
67734
- addressLookupTables: feedLuts,
67735
- type: "CRANK" /* CRANK */
67736
- })
67737
- );
67358
+ };
67359
+
67360
+ // src/models/account.ts
67361
+ var MarginfiAccount = class _MarginfiAccount {
67362
+ constructor(address, group, authority, balances, accountFlags, emissionsDestinationAccount, healthCache) {
67363
+ this.address = address;
67364
+ this.group = group;
67365
+ this.authority = authority;
67366
+ this.balances = balances;
67367
+ this.accountFlags = accountFlags;
67368
+ this.emissionsDestinationAccount = emissionsDestinationAccount;
67369
+ this.healthCache = healthCache;
67738
67370
  }
67739
- const transactions = [...additionalTxs, flashloanTx];
67740
- return {
67741
- transactions,
67742
- actionTxIndex: transactions.length - 1,
67743
- quoteResponse: swapQuote
67744
- };
67745
- }
67746
- async function buildSwapCollateralFlashloanTx({
67747
- program,
67748
- marginfiAccount,
67749
- bankMap,
67750
- withdrawOpts,
67751
- depositOpts,
67752
- swapOpts,
67753
- bankMetadataMap,
67754
- assetShareValueMultiplierByBank,
67755
- addressLookupTableAccounts,
67756
- connection,
67757
- overrideInferAccounts,
67758
- blockhash,
67759
- swapEngineRunner
67760
- }) {
67761
- const {
67762
- withdrawBank,
67763
- tokenProgram: withdrawTokenProgram,
67764
- totalPositionAmount,
67765
- withdrawAmount
67766
- } = withdrawOpts;
67767
- const { depositBank, tokenProgram: depositTokenProgram } = depositOpts;
67768
- if (withdrawAmount !== void 0 && withdrawAmount <= 0) {
67769
- throw new Error("withdrawAmount must be greater than 0");
67371
+ /**
67372
+ * Fetches a marginfi account from on-chain data.
67373
+ *
67374
+ * @param address - The public key of the marginfi account
67375
+ * @param program - The Marginfi program instance
67376
+ *
67377
+ * @returns Promise resolving to a MarginfiAccount instance
67378
+ */
67379
+ static async fetch(address, program) {
67380
+ const data = await program.account.marginfiAccount.fetch(address);
67381
+ return _MarginfiAccount.fromAccountParsed(address, data);
67770
67382
  }
67771
- const actualWithdrawAmount = Math.min(withdrawAmount ?? totalPositionAmount, totalPositionAmount);
67772
- const isFullWithdraw = isWholePosition(
67773
- { amount: totalPositionAmount, isLending: true },
67774
- actualWithdrawAmount,
67775
- withdrawBank.mintDecimals
67776
- );
67777
- const cuRequestIxs = [
67778
- web3_js.ComputeBudgetProgram.setComputeUnitLimit({ units: 12e5 }),
67779
- web3_js.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1 })
67780
- ];
67781
- let amountToDeposit;
67782
- let swapInstructions = [];
67783
- let setupInstructions = [];
67784
- let swapLookupTables = [];
67785
- let swapQuote;
67786
- let sizeConstraintUsed = 0;
67787
- let withdrawIxs;
67788
- switch (withdrawOpts.withdrawBank.config.assetTag) {
67789
- case 3 /* KAMINO */: {
67790
- const reserve = bankMetadataMap[withdrawOpts.withdrawBank.address.toBase58()]?.kaminoStates?.reserveState;
67791
- if (!reserve) {
67792
- throw TransactionBuildingError.kaminoReserveNotFound(
67793
- withdrawOpts.withdrawBank.address.toBase58(),
67794
- withdrawOpts.withdrawBank.mint.toBase58(),
67795
- withdrawOpts.withdrawBank.tokenSymbol
67796
- );
67797
- }
67798
- const multiplier = assetShareValueMultiplierByBank.get(withdrawOpts.withdrawBank.address.toBase58()) ?? new BigNumber3.BigNumber(1);
67799
- const adjustedAmount = new BigNumber3.BigNumber(actualWithdrawAmount).div(multiplier).times(1.0001).toNumber();
67800
- withdrawIxs = await makeKaminoWithdrawIx3({
67801
- program,
67802
- bank: withdrawBank,
67803
- bankMap,
67804
- tokenProgram: withdrawTokenProgram,
67805
- cTokenAmount: adjustedAmount,
67806
- marginfiAccount,
67807
- authority: marginfiAccount.authority,
67808
- reserve,
67809
- withdrawAll: isFullWithdraw,
67810
- isSync: false,
67811
- opts: {
67812
- createAtas: false,
67813
- wrapAndUnwrapSol: false,
67814
- overrideInferAccounts
67815
- }
67816
- });
67817
- break;
67818
- }
67819
- case 4 /* DRIFT */: {
67820
- const driftState = bankMetadataMap[withdrawOpts.withdrawBank.address.toBase58()]?.driftStates;
67821
- if (!driftState) {
67822
- throw TransactionBuildingError.driftStateNotFound(
67823
- withdrawOpts.withdrawBank.address.toBase58(),
67824
- withdrawOpts.withdrawBank.mint.toBase58(),
67825
- withdrawOpts.withdrawBank.tokenSymbol
67826
- );
67827
- }
67828
- withdrawIxs = await makeDriftWithdrawIx3({
67829
- program,
67830
- bank: withdrawOpts.withdrawBank,
67831
- bankMap,
67832
- tokenProgram: withdrawOpts.tokenProgram,
67833
- amount: actualWithdrawAmount,
67834
- marginfiAccount,
67835
- authority: marginfiAccount.authority,
67836
- driftSpotMarket: driftState.spotMarketState,
67837
- userRewards: driftState.userRewards,
67838
- withdrawAll: isFullWithdraw,
67839
- isSync: false,
67840
- opts: {
67841
- createAtas: false,
67842
- wrapAndUnwrapSol: false,
67843
- overrideInferAccounts
67844
- }
67845
- });
67846
- break;
67847
- }
67848
- case 6 /* JUPLEND */: {
67849
- const jupLendState = bankMetadataMap[withdrawOpts.withdrawBank.address.toBase58()]?.jupLendStates;
67850
- if (!jupLendState) {
67851
- throw TransactionBuildingError.jupLendStateNotFound(
67852
- withdrawOpts.withdrawBank.address.toBase58(),
67853
- withdrawOpts.withdrawBank.mint.toBase58(),
67854
- withdrawOpts.withdrawBank.tokenSymbol
67855
- );
67856
- }
67857
- withdrawIxs = await makeJuplendWithdrawIx2({
67858
- program,
67859
- bank: withdrawBank,
67860
- bankMap,
67861
- tokenProgram: withdrawTokenProgram,
67862
- amount: actualWithdrawAmount,
67863
- marginfiAccount,
67864
- authority: marginfiAccount.authority,
67865
- jupLendingState: jupLendState.jupLendingState,
67866
- withdrawAll: isFullWithdraw,
67867
- opts: {
67868
- createAtas: false,
67869
- wrapAndUnwrapSol: false,
67870
- overrideInferAccounts
67871
- }
67872
- });
67873
- break;
67874
- }
67875
- default: {
67876
- withdrawIxs = await makeWithdrawIx3({
67877
- program,
67878
- bank: withdrawBank,
67879
- bankMap,
67880
- tokenProgram: withdrawTokenProgram,
67881
- amount: actualWithdrawAmount,
67882
- marginfiAccount,
67883
- authority: marginfiAccount.authority,
67884
- withdrawAll: isFullWithdraw,
67885
- isSync: false,
67886
- opts: {
67887
- createAtas: false,
67888
- wrapAndUnwrapSol: false,
67889
- overrideInferAccounts
67890
- }
67891
- });
67892
- break;
67893
- }
67383
+ static decodeAccountRaw(encoded, idl) {
67384
+ return decodeAccountRaw(encoded, idl);
67894
67385
  }
67895
- const swapNeeded = !depositBank.mint.equals(withdrawBank.mint);
67896
- amountToDeposit = swapNeeded ? 0 : actualWithdrawAmount;
67897
- let depositIxs;
67898
- switch (depositBank.config.assetTag) {
67899
- case 3 /* KAMINO */: {
67900
- const reserve = bankMetadataMap[depositBank.address.toBase58()]?.kaminoStates?.reserveState;
67901
- if (!reserve) {
67902
- throw TransactionBuildingError.kaminoReserveNotFound(
67903
- depositBank.address.toBase58(),
67904
- depositBank.mint.toBase58(),
67905
- depositBank.tokenSymbol
67906
- );
67907
- }
67908
- depositIxs = await makeKaminoDepositIx3({
67909
- program,
67910
- bank: depositBank,
67911
- tokenProgram: depositTokenProgram,
67912
- amount: amountToDeposit,
67913
- accountAddress: marginfiAccount.address,
67914
- authority: marginfiAccount.authority,
67915
- group: marginfiAccount.group,
67916
- reserve,
67917
- opts: {
67918
- wrapAndUnwrapSol: false,
67919
- overrideInferAccounts
67920
- }
67921
- });
67922
- break;
67923
- }
67924
- case 4 /* DRIFT */: {
67925
- const driftState = bankMetadataMap[depositBank.address.toBase58()]?.driftStates;
67926
- if (!driftState) {
67927
- throw TransactionBuildingError.driftStateNotFound(
67928
- depositBank.address.toBase58(),
67929
- depositBank.mint.toBase58(),
67930
- depositBank.tokenSymbol
67931
- );
67932
- }
67933
- const driftMarketIndex = driftState.spotMarketState.marketIndex;
67934
- const driftOracle = driftState.spotMarketState.oracle;
67935
- depositIxs = await makeDriftDepositIx3({
67936
- program,
67937
- bank: depositBank,
67938
- tokenProgram: depositTokenProgram,
67939
- amount: amountToDeposit,
67940
- accountAddress: marginfiAccount.address,
67941
- authority: marginfiAccount.authority,
67942
- group: marginfiAccount.group,
67943
- driftMarketIndex,
67944
- driftOracle,
67945
- opts: {
67946
- wrapAndUnwrapSol: false,
67947
- overrideInferAccounts
67948
- }
67949
- });
67950
- break;
67951
- }
67952
- case 6 /* JUPLEND */: {
67953
- depositIxs = await makeJuplendDepositIx2({
67954
- program,
67955
- bank: depositBank,
67956
- tokenProgram: depositTokenProgram,
67957
- amount: amountToDeposit,
67958
- accountAddress: marginfiAccount.address,
67959
- authority: marginfiAccount.authority,
67960
- group: marginfiAccount.group,
67961
- opts: {
67962
- wrapAndUnwrapSol: false,
67963
- overrideInferAccounts
67964
- }
67965
- });
67966
- break;
67967
- }
67968
- default: {
67969
- depositIxs = await makeDepositIx3({
67970
- program,
67971
- bank: depositBank,
67972
- tokenProgram: depositTokenProgram,
67973
- amount: amountToDeposit,
67974
- accountAddress: marginfiAccount.address,
67975
- authority: marginfiAccount.authority,
67976
- group: marginfiAccount.group,
67977
- opts: {
67978
- wrapAndUnwrapSol: false,
67979
- overrideInferAccounts
67980
- }
67981
- });
67982
- break;
67983
- }
67386
+ static fromAccountType(account) {
67387
+ return new _MarginfiAccount(
67388
+ account.address,
67389
+ account.group,
67390
+ account.authority,
67391
+ account.balances.map((b) => Balance.fromBalanceType(b)),
67392
+ account.accountFlags,
67393
+ account.emissionsDestinationAccount,
67394
+ account.healthCache
67395
+ );
67984
67396
  }
67985
- if (swapNeeded) {
67986
- const destinationTokenAccount = getAssociatedTokenAddressSync(
67987
- depositBank.mint,
67988
- marginfiAccount.authority,
67989
- true,
67990
- depositTokenProgram.equals(TOKEN_2022_PROGRAM_ID) ? TOKEN_2022_PROGRAM_ID : void 0
67397
+ /**
67398
+ * Creates a MarginfiAccount instance from parsed account data.
67399
+ *
67400
+ * @param marginfiAccountPk - The public key of the marginfi account
67401
+ * @param accountData - The raw account data from the program
67402
+ *
67403
+ * @returns A new MarginfiAccount instance
67404
+ */
67405
+ static fromAccountParsed(marginfiAccountPk, accountData) {
67406
+ const props = parseMarginfiAccountRaw(marginfiAccountPk, accountData);
67407
+ return new _MarginfiAccount(
67408
+ props.address,
67409
+ props.group,
67410
+ props.authority,
67411
+ props.balances.map((b) => Balance.fromBalanceType(b)),
67412
+ props.accountFlags,
67413
+ props.emissionsDestinationAccount,
67414
+ HealthCache.fromHealthCacheType(props.healthCache)
67991
67415
  );
67992
- const swapConstraints = await computeFlashloanSwapConstraints({
67993
- program,
67994
- marginfiAccount,
67995
- bankMap,
67996
- bankMetadataMap,
67997
- addressLookupTableAccounts: addressLookupTableAccounts ?? [],
67998
- primaryIx: { type: "withdraw", bank: withdrawBank, tokenProgram: withdrawTokenProgram },
67999
- secondaryIx: { type: "deposit", bank: depositBank, tokenProgram: depositTokenProgram },
68000
- overrideInferAccounts
68001
- });
68002
- sizeConstraintUsed = swapConstraints.sizeConstraint;
68003
- const runEngine = swapEngineRunner ?? runSwapEngine;
68004
- const engineResult = await runEngine({
68005
- inputMint: withdrawBank.mint.toBase58(),
68006
- outputMint: depositBank.mint.toBase58(),
68007
- amountNative: uiToNative(actualWithdrawAmount, withdrawBank.mintDecimals).toNumber(),
68008
- inputDecimals: withdrawBank.mintDecimals,
68009
- outputDecimals: depositBank.mintDecimals,
68010
- ...swapEngineQuoteFieldsFromOpts(swapOpts),
68011
- taker: marginfiAccount.authority,
68012
- destinationTokenAccount,
68013
- connection,
68014
- footprint: {
68015
- instructions: [...cuRequestIxs, ...withdrawIxs.instructions, ...depositIxs.instructions],
68016
- luts: addressLookupTableAccounts ?? [],
68017
- payer: marginfiAccount.authority,
68018
- sizeConstraint: swapConstraints.sizeConstraint,
68019
- maxSwapTotalAccounts: swapConstraints.maxSwapTotalAccounts
68020
- },
68021
- providers: swapEngineProvidersFromOpts(swapOpts)
68022
- });
68023
- const depositIxToPatch = depositIxs.instructions.find(isDepositIx);
68024
- if (!depositIxToPatch) {
68025
- throw new Error("swap-collateral: could not locate deposit instruction for amount patching");
68026
- }
68027
- patchDepositAmount(depositIxToPatch, engineResult.outputAmountNative);
68028
- swapInstructions = engineResult.swapInstructions;
68029
- setupInstructions = engineResult.setupInstructions;
68030
- swapLookupTables = engineResult.swapLuts;
68031
- swapQuote = engineResult.quoteResponse;
68032
67416
  }
68033
- const luts = [...addressLookupTableAccounts ?? [], ...swapLookupTables];
68034
- const allNonFlIxs = [
68035
- ...cuRequestIxs,
68036
- ...withdrawIxs.instructions,
68037
- ...swapInstructions,
68038
- ...depositIxs.instructions
68039
- ];
68040
- if (swapInstructions.length > 0) {
68041
- compileFlashloanPrecheck({
68042
- allIxs: allNonFlIxs,
68043
- payer: marginfiAccount.authority,
68044
- luts,
68045
- sizeConstraint: sizeConstraintUsed,
68046
- swapIxCount: swapInstructions.length,
68047
- swapLutCount: swapLookupTables.length
67417
+ /**
67418
+ * Simulates and updates the health cache for this account.
67419
+ *
67420
+ * Fetches current oracle prices and computes fresh health values. Returns a new
67421
+ * account instance with updated health cache (does not mutate this instance).
67422
+ *
67423
+ * @param params - Configuration for health cache simulation (excluding marginfiAccount)
67424
+ * @returns Object containing the updated account and any errors
67425
+ */
67426
+ async simulateHealthCache(params) {
67427
+ const accountWithHealthCache = await simulateAccountHealthCacheWithFallback({
67428
+ marginfiAccount: this,
67429
+ ...params
68048
67430
  });
67431
+ return {
67432
+ account: _MarginfiAccount.fromAccountType(accountWithHealthCache.marginfiAccount),
67433
+ error: accountWithHealthCache.error
67434
+ };
68049
67435
  }
68050
- const flashloanTx = await makeFlashLoanTx({
68051
- program,
68052
- marginfiAccount,
68053
- bankMap,
68054
- addressLookupTableAccounts: luts,
68055
- blockhash,
68056
- ixs: allNonFlIxs,
68057
- isSync: false
68058
- });
68059
- const txSize = getTxSize(flashloanTx);
68060
- const totalKeys = getTotalAccountKeys(flashloanTx);
68061
- if (txSize > MAX_TX_SIZE || totalKeys > MAX_ACCOUNT_LOCKS) {
68062
- throw TransactionBuildingError.swapSizeExceededPositionSwap(
68063
- txSize,
68064
- totalKeys,
68065
- swapOpts.swapConfig?.provider
68066
- );
67436
+ static fromAccountDataRaw(marginfiAccountPk, rawData, idl) {
67437
+ const marginfiAccountData = _MarginfiAccount.decodeAccountRaw(rawData, idl);
67438
+ return _MarginfiAccount.fromAccountParsed(marginfiAccountPk, marginfiAccountData);
68067
67439
  }
68068
- return {
68069
- flashloanTx,
68070
- setupInstructions,
68071
- swapQuote,
68072
- withdrawIxs,
68073
- depositIxs
68074
- };
68075
- }
68076
- async function makeSwapDebtTx(params) {
68077
- const {
68078
- program,
68079
- marginfiAccount,
68080
- connection,
68081
- bankMap,
68082
- oraclePrices,
68083
- repayOpts,
68084
- borrowOpts,
68085
- bankMetadataMap,
68086
- assetShareValueMultiplierByBank,
68087
- addressLookupTableAccounts,
68088
- crossbarUrl,
68089
- additionalIxs = []
68090
- } = params;
68091
- const blockhash = (await connection.getLatestBlockhash("confirmed")).blockhash;
68092
- const setupIxs = await makeSetupIx({
68093
- connection,
68094
- authority: marginfiAccount.authority,
68095
- tokens: [
68096
- { mint: repayOpts.repayBank.mint, tokenProgram: repayOpts.tokenProgram },
68097
- { mint: borrowOpts.borrowBank.mint, tokenProgram: borrowOpts.tokenProgram }
68098
- ]
68099
- });
68100
- const refreshIntegrationIxs = makeRefreshIntegrationBanksIxs(
68101
- marginfiAccount,
68102
- bankMap,
68103
- [],
68104
- bankMetadataMap,
68105
- [repayOpts.repayBank.address, borrowOpts.borrowBank.address]
68106
- );
68107
- const { flashloanTx, setupInstructions, swapQuote, borrowIxs, repayIxs } = await buildSwapDebtFlashloanTx({
68108
- ...params,
68109
- blockhash
68110
- });
68111
- const jupiterSetupInstructions = setupInstructions.filter((ix) => {
68112
- if (ix.programId.equals(web3_js.ComputeBudgetProgram.programId)) {
68113
- return false;
68114
- }
68115
- if (ix.programId.equals(ASSOCIATED_TOKEN_PROGRAM_ID)) {
68116
- const mintKey = ix.keys[3]?.pubkey;
68117
- if (mintKey?.equals(repayOpts.repayBank.mint) || mintKey?.equals(borrowOpts.borrowBank.mint)) {
68118
- return false;
68119
- }
68120
- }
68121
- return true;
68122
- });
68123
- setupIxs.push(...jupiterSetupInstructions);
68124
- const { instructions: updateFeedIxs, luts: feedLuts } = await makeSmartCrankSwbFeedIx({
68125
- marginfiAccount,
68126
- bankMap,
68127
- oraclePrices,
68128
- assetShareValueMultiplierByBank,
68129
- instructions: [...borrowIxs.instructions, ...repayIxs.instructions],
68130
- program,
68131
- connection,
68132
- crossbarUrl
68133
- });
68134
- let additionalTxs = [];
68135
- if (setupIxs.length > 0 || refreshIntegrationIxs.instructions.length > 0) {
68136
- const ixs = [...additionalIxs, ...setupIxs, ...refreshIntegrationIxs.instructions];
68137
- const txs = splitInstructionsToFitTransactions([], ixs, {
68138
- blockhash,
68139
- payerKey: marginfiAccount.authority,
68140
- luts: addressLookupTableAccounts ?? []
68141
- });
68142
- additionalTxs.push(
68143
- ...txs.map(
68144
- (tx) => addTransactionMetadata(tx, {
68145
- type: "CREATE_ATA" /* CREATE_ATA */,
68146
- addressLookupTables: addressLookupTableAccounts
68147
- })
68148
- )
68149
- );
67440
+ // ----------------------------------------------------------------------------
67441
+ // Attributes
67442
+ // ----------------------------------------------------------------------------
67443
+ /**
67444
+ * Gets all balances that are currently active (non-zero).
67445
+ *
67446
+ * @returns Array of active Balance instances
67447
+ */
67448
+ get activeBalances() {
67449
+ return this.balances.filter((b) => b.active);
68150
67450
  }
68151
- if (updateFeedIxs.length > 0) {
68152
- const message = new web3_js.TransactionMessage({
68153
- payerKey: marginfiAccount.authority,
68154
- recentBlockhash: blockhash,
68155
- instructions: updateFeedIxs
68156
- }).compileToV0Message(feedLuts);
68157
- additionalTxs.push(
68158
- addTransactionMetadata(new web3_js.VersionedTransaction(message), {
68159
- addressLookupTables: feedLuts,
68160
- type: "CRANK" /* CRANK */
68161
- })
68162
- );
67451
+ /**
67452
+ * Gets the balance for a specific bank.
67453
+ *
67454
+ * @param bankPk - The public key of the bank
67455
+ *
67456
+ * @returns The Balance instance for the bank (may be empty)
67457
+ */
67458
+ getBalance(bankPk) {
67459
+ return Balance.fromBalanceType(getBalance(bankPk, this.balances));
68163
67460
  }
68164
- const transactions = [...additionalTxs, flashloanTx];
68165
- return {
68166
- transactions,
68167
- actionTxIndex: transactions.length - 1,
68168
- quoteResponse: swapQuote
68169
- };
68170
- }
68171
- async function buildSwapDebtFlashloanTx({
68172
- program,
68173
- marginfiAccount,
68174
- bankMap,
68175
- repayOpts,
68176
- borrowOpts,
68177
- swapOpts,
68178
- bankMetadataMap,
68179
- addressLookupTableAccounts,
68180
- connection,
68181
- overrideInferAccounts,
68182
- blockhash,
68183
- swapEngineRunner
68184
- }) {
68185
- const {
68186
- repayBank,
68187
- tokenProgram: repayTokenProgram,
68188
- totalPositionAmount,
68189
- repayAmount
68190
- } = repayOpts;
68191
- const { borrowBank, tokenProgram: borrowTokenProgram } = borrowOpts;
68192
- if (repayAmount !== void 0 && repayAmount <= 0) {
68193
- throw new Error("repayAmount must be greater than 0");
67461
+ /**
67462
+ * Checks if the account is disabled.
67463
+ *
67464
+ * @returns True if the account is disabled, false otherwise
67465
+ */
67466
+ get isDisabled() {
67467
+ return this.accountFlags.includes(1 /* ACCOUNT_DISABLED */);
68194
67468
  }
68195
- const actualRepayAmount = Math.min(repayAmount ?? totalPositionAmount, totalPositionAmount);
68196
- const cuRequestIxs = [
68197
- web3_js.ComputeBudgetProgram.setComputeUnitLimit({ units: 12e5 }),
68198
- web3_js.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1 })
68199
- ];
68200
- const destinationTokenAccount = getAssociatedTokenAddressSync(
68201
- repayBank.mint,
68202
- marginfiAccount.authority,
68203
- true,
68204
- repayTokenProgram.equals(TOKEN_2022_PROGRAM_ID) ? TOKEN_2022_PROGRAM_ID : void 0
68205
- );
68206
- const estimatedBorrowAmount = computeBorrowEstimateForRepay({
68207
- repayTargetUi: actualRepayAmount,
68208
- repayMarketPrice: repayOpts.marketPrice,
68209
- borrowMarketPrice: borrowOpts.marketPrice,
68210
- slippageBps: swapOpts.swapConfig?.slippageBps,
68211
- isRepayAll: actualRepayAmount >= totalPositionAmount
68212
- });
68213
- const swapConstraints = await computeFlashloanSwapConstraints({
68214
- program,
68215
- marginfiAccount,
68216
- bankMap,
68217
- bankMetadataMap,
68218
- addressLookupTableAccounts: addressLookupTableAccounts ?? [],
68219
- primaryIx: { type: "borrow", bank: borrowBank, tokenProgram: borrowTokenProgram },
68220
- secondaryIx: { type: "repay", bank: repayBank, tokenProgram: repayTokenProgram },
68221
- overrideInferAccounts
68222
- });
68223
- const footprintBorrowIxs = await makeBorrowIx3({
68224
- program,
68225
- bank: borrowBank,
68226
- bankMap,
68227
- tokenProgram: borrowTokenProgram,
68228
- amount: estimatedBorrowAmount,
68229
- marginfiAccount,
68230
- authority: marginfiAccount.authority,
68231
- isSync: true,
68232
- opts: { createAtas: false, wrapAndUnwrapSol: false, overrideInferAccounts }
68233
- });
68234
- const footprintRepayIxs = await makeRepayIx3({
68235
- program,
68236
- bank: repayBank,
68237
- tokenProgram: repayTokenProgram,
68238
- amount: actualRepayAmount,
68239
- accountAddress: marginfiAccount.address,
68240
- authority: marginfiAccount.authority,
68241
- isSync: true,
68242
- opts: { wrapAndUnwrapSol: false, overrideInferAccounts }
68243
- });
68244
- const runEngine = swapEngineRunner ?? runSwapEngine;
68245
- const engineResult = await runEngine({
68246
- inputMint: borrowBank.mint.toBase58(),
68247
- outputMint: repayBank.mint.toBase58(),
68248
- amountNative: uiToNative(estimatedBorrowAmount, borrowBank.mintDecimals).toNumber(),
68249
- inputDecimals: borrowBank.mintDecimals,
68250
- outputDecimals: repayBank.mintDecimals,
68251
- ...swapEngineQuoteFieldsFromOpts(swapOpts),
68252
- taker: marginfiAccount.authority,
68253
- destinationTokenAccount,
68254
- connection,
68255
- footprint: {
68256
- instructions: [
68257
- ...cuRequestIxs,
68258
- ...footprintBorrowIxs.instructions,
68259
- ...footprintRepayIxs.instructions
68260
- ],
68261
- luts: addressLookupTableAccounts ?? [],
68262
- payer: marginfiAccount.authority,
68263
- sizeConstraint: swapConstraints.sizeConstraint,
68264
- maxSwapTotalAccounts: swapConstraints.maxSwapTotalAccounts
68265
- },
68266
- providers: swapEngineProvidersFromOpts(swapOpts)
68267
- });
68268
- const quoteResponse = engineResult.quoteResponse;
68269
- const outAmount = nativeToUi(quoteResponse.outAmount, repayBank.mintDecimals);
68270
- const outAmountThreshold = nativeToUi(quoteResponse.otherAmountThreshold, repayBank.mintDecimals);
68271
- const amountToRepay = outAmount > totalPositionAmount ? totalPositionAmount : outAmountThreshold;
68272
- const borrowAmount = nativeToUi(quoteResponse.inAmount, borrowBank.mintDecimals);
68273
- const borrowIxs = await makeBorrowIx3({
68274
- program,
68275
- bank: borrowBank,
68276
- bankMap,
68277
- tokenProgram: borrowTokenProgram,
68278
- amount: borrowAmount,
68279
- marginfiAccount,
68280
- authority: marginfiAccount.authority,
68281
- isSync: true,
68282
- opts: {
68283
- createAtas: false,
68284
- wrapAndUnwrapSol: false,
68285
- overrideInferAccounts
68286
- }
68287
- });
68288
- const repayIxs = await makeRepayIx3({
68289
- program,
68290
- bank: repayBank,
68291
- tokenProgram: repayTokenProgram,
68292
- amount: amountToRepay,
68293
- accountAddress: marginfiAccount.address,
68294
- authority: marginfiAccount.authority,
68295
- repayAll: isWholePosition(
68296
- {
68297
- amount: totalPositionAmount,
68298
- isLending: false
68299
- },
68300
- amountToRepay,
68301
- repayBank.mintDecimals
68302
- ),
68303
- isSync: true,
68304
- opts: {
68305
- wrapAndUnwrapSol: false,
68306
- overrideInferAccounts
68307
- }
68308
- });
68309
- const luts = [...addressLookupTableAccounts ?? [], ...engineResult.swapLuts];
68310
- const allNonFlIxs = [
68311
- ...cuRequestIxs,
68312
- ...borrowIxs.instructions,
68313
- ...engineResult.swapInstructions,
68314
- ...repayIxs.instructions
68315
- ];
68316
- compileFlashloanPrecheck({
68317
- allIxs: allNonFlIxs,
68318
- payer: marginfiAccount.authority,
68319
- luts,
68320
- sizeConstraint: swapConstraints.sizeConstraint,
68321
- swapIxCount: engineResult.swapInstructions.length,
68322
- swapLutCount: engineResult.swapLuts.length
68323
- });
68324
- const flashloanTx = await makeFlashLoanTx({
68325
- program,
68326
- marginfiAccount,
68327
- bankMap,
68328
- addressLookupTableAccounts: luts,
68329
- blockhash,
68330
- ixs: allNonFlIxs,
68331
- isSync: true
68332
- });
68333
- const txSize = getTxSize(flashloanTx);
68334
- const totalKeys = getTotalAccountKeys(flashloanTx);
68335
- if (txSize > MAX_TX_SIZE || totalKeys > MAX_ACCOUNT_LOCKS) {
68336
- throw TransactionBuildingError.swapSizeExceededPositionSwap(
68337
- txSize,
68338
- totalKeys,
68339
- swapOpts.swapConfig?.provider
68340
- );
67469
+ /**
67470
+ * Checks if the account is currently in a flash loan.
67471
+ *
67472
+ * @returns True if a flash loan is active, false otherwise
67473
+ */
67474
+ get isFlashLoanEnabled() {
67475
+ return this.accountFlags.includes(2 /* ACCOUNT_IN_FLASHLOAN */);
68341
67476
  }
68342
- return {
68343
- flashloanTx,
68344
- setupInstructions: engineResult.setupInstructions,
68345
- swapQuote: quoteResponse,
68346
- borrowIxs,
68347
- repayIxs
68348
- };
68349
- }
68350
- var DEFAULT_ROLL_SLIPPAGE_BPS = 50;
68351
- var TRADE_PT_EVENT_AMOUNT_OUT_OFFSET = 138;
68352
- var MERGE_EVENT_AMOUNT_SY_OUT_OFFSET = 296;
68353
- async function makeRollPtTx(params) {
68354
- const {
68355
- program,
68356
- marginfiAccount,
68357
- connection,
68358
- bankMap,
68359
- oraclePrices,
68360
- withdrawOpts,
68361
- depositOpts,
68362
- rollOpts,
68363
- assetShareValueMultiplierByBank,
68364
- addressLookupTableAccounts,
68365
- crossbarUrl
68366
- } = params;
68367
- if (!rollOpts.maturedMarket && !rollOpts.maturedVault) {
68368
- throw new Error("roll-pt: rollOpts.maturedMarket or maturedVault is required");
67477
+ /**
67478
+ * Checks if account authority transfer is enabled.
67479
+ *
67480
+ * @returns True if authority transfer is allowed, false otherwise
67481
+ */
67482
+ get isTransferAccountAuthorityEnabled() {
67483
+ return this.accountFlags.includes(8 /* ACCOUNT_TRANSFER_AUTHORITY_ALLOWED */);
68369
67484
  }
68370
- const merge = await resolveExponentMergeContext({
68371
- connection,
68372
- owner: marginfiAccount.authority,
68373
- market: rollOpts.maturedMarket,
68374
- vault: rollOpts.maturedVault,
68375
- ptYtTokenProgram: withdrawOpts.tokenProgram,
68376
- syTokenProgram: rollOpts.syTokenProgram
68377
- });
68378
- const clmm = await resolveExponentClmmTradePtContext({
68379
- connection,
68380
- owner: marginfiAccount.authority,
68381
- market: rollOpts.successorMarket,
68382
- ptTokenProgram: depositOpts.tokenProgram,
68383
- syTokenProgram: rollOpts.syTokenProgram
68384
- });
68385
- const blockhash = (await connection.getLatestBlockhash("confirmed")).blockhash;
68386
- const setupIxs = await makeSetupIx({
68387
- connection,
68388
- authority: marginfiAccount.authority,
68389
- tokens: [
68390
- { mint: withdrawOpts.withdrawBank.mint, tokenProgram: withdrawOpts.tokenProgram },
68391
- { mint: merge.mergeAccounts.mintYt, tokenProgram: withdrawOpts.tokenProgram },
68392
- { mint: merge.underlying.mint, tokenProgram: merge.underlying.tokenProgram },
68393
- { mint: depositOpts.depositBank.mint, tokenProgram: depositOpts.tokenProgram }
68394
- ]
68395
- });
68396
- const { flashloanTx, swapQuote, withdrawIxs, depositIxs } = await buildRollPtFlashloanTx({
68397
- params,
68398
- merge,
68399
- clmm,
68400
- setupIxs,
68401
- blockhash
68402
- });
68403
- const { instructions: updateFeedIxs, luts: feedLuts } = await makeSmartCrankSwbFeedIx({
68404
- marginfiAccount,
68405
- bankMap,
68406
- oraclePrices,
68407
- assetShareValueMultiplierByBank,
68408
- instructions: [...withdrawIxs.instructions, ...depositIxs.instructions],
68409
- program,
68410
- connection,
68411
- crossbarUrl
68412
- });
68413
- const additionalTxs = [];
68414
- if (setupIxs.length > 0) {
68415
- const txs = splitInstructionsToFitTransactions([], setupIxs, {
68416
- blockhash,
68417
- payerKey: marginfiAccount.authority,
68418
- luts: addressLookupTableAccounts ?? []
68419
- });
68420
- additionalTxs.push(
68421
- ...txs.map(
68422
- (tx) => addTransactionMetadata(tx, {
68423
- type: "CREATE_ATA" /* CREATE_ATA */,
68424
- addressLookupTables: addressLookupTableAccounts
68425
- })
68426
- )
68427
- );
67485
+ /**
67486
+ * Sets the health cache for this account.
67487
+ *
67488
+ * Note: This mutates the account instance. Consider using simulateHealthCache()
67489
+ * for a pure functional approach that returns a new account instance.
67490
+ *
67491
+ * @param value - The new health cache to set
67492
+ */
67493
+ setHealthCache(value) {
67494
+ this.healthCache = value;
68428
67495
  }
68429
- if (updateFeedIxs.length > 0) {
68430
- const message = new web3_js.TransactionMessage({
68431
- payerKey: marginfiAccount.authority,
68432
- recentBlockhash: blockhash,
68433
- instructions: updateFeedIxs
68434
- }).compileToV0Message(feedLuts);
68435
- additionalTxs.push(
68436
- addTransactionMetadata(new web3_js.VersionedTransaction(message), {
68437
- addressLookupTables: feedLuts,
68438
- type: "CRANK" /* CRANK */
68439
- })
68440
- );
67496
+ /**
67497
+ * Computes free collateral using cached health values.
67498
+ *
67499
+ * Free collateral represents the amount of collateral that is not backing any liabilities.
67500
+ *
67501
+ * @param opts - Optional configuration
67502
+ * @param opts.clamped - If true, clamps negative values to zero
67503
+ *
67504
+ * @returns The free collateral amount in USD
67505
+ */
67506
+ computeFreeCollateralFromCache(opts) {
67507
+ return computeFreeCollateralFromCache(this, opts);
68441
67508
  }
68442
- const transactions = [...additionalTxs, flashloanTx];
68443
- return {
68444
- transactions,
68445
- actionTxIndex: transactions.length - 1,
68446
- quoteResponse: swapQuote
68447
- };
68448
- }
68449
- async function buildRollPtFlashloanTx({
68450
- params,
68451
- merge,
68452
- clmm,
68453
- setupIxs,
68454
- blockhash
68455
- }) {
68456
- const {
68457
- program,
68458
- marginfiAccount,
68459
- bankMap,
68460
- withdrawOpts,
68461
- depositOpts,
68462
- bankMetadataMap,
68463
- connection,
68464
- addressLookupTableAccounts,
68465
- overrideInferAccounts,
68466
- rollOpts
68467
- } = params;
68468
- const { withdrawBank, tokenProgram: withdrawTokenProgram, totalPositionAmount, withdrawAmount } = withdrawOpts;
68469
- const { depositBank, tokenProgram: depositTokenProgram } = depositOpts;
68470
- const authority = marginfiAccount.authority;
68471
- if (withdrawAmount !== void 0 && withdrawAmount <= 0) {
68472
- throw new Error("withdrawAmount must be greater than 0");
67509
+ /**
67510
+ * Computes free collateral from balances using Initial margin requirements.
67511
+ *
67512
+ * Free collateral represents the amount of value available for new borrows or withdrawals.
67513
+ * By default, negative values are clamped to zero.
67514
+ *
67515
+ * @param params - Configuration for free collateral computation (excluding activeBalances)
67516
+ * @returns Free collateral value in USD (clamped to zero by default)
67517
+ */
67518
+ computeFreeCollateralFromBalances(params) {
67519
+ return computeFreeCollateralFromBalances({
67520
+ activeBalances: this.activeBalances,
67521
+ ...params
67522
+ });
68473
67523
  }
68474
- const actualWithdrawAmount = Math.min(withdrawAmount ?? totalPositionAmount, totalPositionAmount);
68475
- const isFullWithdraw = isWholePosition(
68476
- { amount: totalPositionAmount, isLending: true },
68477
- actualWithdrawAmount,
68478
- withdrawBank.mintDecimals
68479
- );
68480
- const withdrawNative = BigInt(
68481
- uiToNative(actualWithdrawAmount, withdrawBank.mintDecimals).toString()
68482
- );
68483
- const cuRequestIxs = [
68484
- web3_js.ComputeBudgetProgram.setComputeUnitLimit({ units: 12e5 }),
68485
- web3_js.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1 })
68486
- ];
68487
- const withdrawIxs = await makeWithdrawIx3({
68488
- program,
68489
- bank: withdrawBank,
68490
- bankMap,
68491
- tokenProgram: withdrawTokenProgram,
68492
- amount: actualWithdrawAmount,
68493
- marginfiAccount,
68494
- authority,
68495
- withdrawAll: isFullWithdraw,
68496
- isSync: false,
68497
- opts: { createAtas: false, wrapAndUnwrapSol: false, overrideInferAccounts }
68498
- });
68499
- const mergeIx = makeExponentMergeIx(merge.mergeAccounts, withdrawNative);
68500
- const depositIxs = await makeDepositIx3({
68501
- program,
68502
- bank: depositBank,
68503
- tokenProgram: depositTokenProgram,
68504
- amount: 0,
68505
- accountAddress: marginfiAccount.address,
68506
- authority,
68507
- group: marginfiAccount.group,
68508
- opts: { wrapAndUnwrapSol: false, overrideInferAccounts }
68509
- });
68510
- let luts;
68511
- if (rollOpts.lookupTable) {
68512
- const fetched = (await connection.getAddressLookupTable(rollOpts.lookupTable)).value;
68513
- if (!fetched) {
68514
- throw new Error(`roll-pt: PT-roll lookup table not found: ${rollOpts.lookupTable.toBase58()}`);
68515
- }
68516
- luts = [fetched, merge.addressLookupTable, clmm.addressLookupTable];
68517
- } else {
68518
- luts = [
68519
- ...addressLookupTableAccounts ?? [],
68520
- merge.addressLookupTable,
68521
- clmm.addressLookupTable
68522
- ];
67524
+ /**
67525
+ * Computes health components using cached health values.
67526
+ *
67527
+ * Returns weighted asset and liability values based on the margin requirement type.
67528
+ *
67529
+ * @param marginReqType - The margin requirement type (Initial, Maintenance, or Equity)
67530
+ *
67531
+ * @returns Object containing assets and liabilities values in USD
67532
+ */
67533
+ computeHealthComponentsFromCache(marginRequirement) {
67534
+ return computeHealthComponentsFromCache(this, marginRequirement);
68523
67535
  }
68524
- const redeemIxs = [...setupIxs, ...cuRequestIxs, ...withdrawIxs.instructions, mergeIx];
68525
- const mergeReturn = await simulateRollReturn({
68526
- program,
68527
- marginfiAccount,
68528
- bankMap,
68529
- connection,
68530
- blockhash,
68531
- luts,
68532
- ixs: redeemIxs,
68533
- programId: EXPONENT_CORE_PROGRAM_ID,
68534
- label: "merge"
68535
- });
68536
- const syExact = readReturnU64(mergeReturn, MERGE_EVENT_AMOUNT_SY_OUT_OFFSET, "merge amount_sy_out");
68537
- if (syExact <= 0n) {
68538
- throw new Error("roll-pt: merge would redeem 0 SY (empty/invalid matured vault state)");
68539
- }
68540
- const exactPtOut = await quoteClmmTradeOut({
68541
- connection,
68542
- clmm,
68543
- amountInSyNative: syExact,
68544
- payer: authority
68545
- });
68546
- const slippageBps = rollOpts.slippageBps ?? DEFAULT_ROLL_SLIPPAGE_BPS;
68547
- const minPtOut = exactPtOut * BigInt(1e4 - slippageBps) / 10000n;
68548
- if (minPtOut <= 0n) {
68549
- throw new Error("roll-pt: quoted PT out is 0 (insufficient CLMM liquidity for this size)");
68550
- }
68551
- const tradeIx = makeExponentClmmTradePtIx(
68552
- clmm.tradePtAccounts,
68553
- exponentClmmBuyPtArgs({ amountInSyNative: syExact, minPtOutNative: minPtOut })
68554
- );
68555
- const depositIxToPatch = depositIxs.instructions.find(isDepositIx);
68556
- if (!depositIxToPatch) {
68557
- throw new Error("roll-pt: could not locate deposit instruction for amount patching");
68558
- }
68559
- patchDepositAmount(depositIxToPatch, new BN9__default.default(minPtOut.toString()));
68560
- const allNonFlIxs = [
68561
- ...cuRequestIxs,
68562
- ...withdrawIxs.instructions,
68563
- mergeIx,
68564
- tradeIx,
68565
- ...depositIxs.instructions
68566
- ];
68567
- const { sizeConstraint } = computeFlashLoanNonSwapBudget({
68568
- program,
68569
- marginfiAccount,
68570
- bankMap,
68571
- addressLookupTableAccounts: luts,
68572
- ixs: allNonFlIxs
68573
- });
68574
- compileFlashloanPrecheck({
68575
- allIxs: allNonFlIxs,
68576
- payer: authority,
68577
- luts,
68578
- sizeConstraint,
68579
- swapIxCount: 0,
68580
- swapLutCount: 0
68581
- });
68582
- const flashloanTx = await makeFlashLoanTx({
68583
- program,
68584
- marginfiAccount,
68585
- bankMap,
68586
- addressLookupTableAccounts: luts,
68587
- blockhash,
68588
- ixs: allNonFlIxs,
68589
- isSync: false
68590
- });
68591
- const txSize = getTxSize(flashloanTx);
68592
- const totalKeys = getTotalAccountKeys(flashloanTx);
68593
- if (txSize > MAX_TX_SIZE || totalKeys > MAX_ACCOUNT_LOCKS) {
68594
- throw TransactionBuildingError.swapSizeExceededPositionSwap(txSize, totalKeys, void 0);
68595
- }
68596
- const swapQuote = {
68597
- inAmount: syExact.toString(),
68598
- outAmount: exactPtOut.toString(),
68599
- otherAmountThreshold: minPtOut.toString(),
68600
- slippageBps
68601
- };
68602
- return { flashloanTx, swapQuote, withdrawIxs, depositIxs };
68603
- }
68604
- async function simulateRollReturn({
68605
- program,
68606
- marginfiAccount,
68607
- bankMap,
68608
- connection,
68609
- blockhash,
68610
- luts,
68611
- ixs,
68612
- programId,
68613
- label
68614
- }) {
68615
- const quoteTx = await makeFlashLoanTx({
68616
- program,
68617
- marginfiAccount,
68618
- bankMap,
68619
- addressLookupTableAccounts: luts,
68620
- blockhash,
68621
- ixs,
68622
- isSync: false
68623
- });
68624
- const sim = await connection.simulateTransaction(quoteTx, {
68625
- sigVerify: false,
68626
- replaceRecentBlockhash: true
68627
- });
68628
- const logs = sim.value.logs ?? [];
68629
- if (process.env.ROLL_DEBUG) {
68630
- console.error(
68631
- `[roll ${label} sim] err:`,
68632
- JSON.stringify(sim.value.err),
68633
- "returnData:",
68634
- JSON.stringify(sim.value.returnData),
68635
- "logsLen:",
68636
- logs.length,
68637
- "truncated:",
68638
- logs.some((l) => l.includes("truncated"))
68639
- );
68640
- }
68641
- const prefix = `Program return: ${programId.toBase58()} `;
68642
- const line = [...logs].reverse().find((l) => l.startsWith(prefix));
68643
- if (!line) {
68644
- throw new Error(
68645
- `roll-pt: could not read ${label} return data from quote simulation (err=${JSON.stringify(
68646
- sim.value.err
68647
- )})`
68648
- );
68649
- }
68650
- return Buffer.from(line.slice(prefix.length), "base64");
68651
- }
68652
- function readReturnU64(data, offset, what) {
68653
- if (data.length < offset + 8) {
68654
- throw new Error(`roll-pt: ${what} return data too short (${data.length} bytes)`);
68655
- }
68656
- return data.readBigUInt64LE(offset);
68657
- }
68658
- async function quoteClmmTradeOut({
68659
- connection,
68660
- clmm,
68661
- amountInSyNative,
68662
- payer
68663
- }) {
68664
- const excluded = /* @__PURE__ */ new Set([
68665
- clmm.tradePtAccounts.tokenSyEscrow.toBase58(),
68666
- clmm.tradePtAccounts.tokenFeeTreasurySy.toBase58()
68667
- ]);
68668
- const largest = await connection.getTokenLargestAccounts(clmm.sy.mint);
68669
- const funded = largest.value.find(
68670
- (a) => !excluded.has(a.address.toBase58()) && BigInt(a.amount) >= amountInSyNative
68671
- );
68672
- if (!funded) {
68673
- throw new Error(
68674
- "roll-pt: no SY holder large enough to quote the buy \u2014 the roll size exceeds available CLMM liquidity for this pair"
68675
- );
68676
- }
68677
- const parsed = await connection.getParsedAccountInfo(funded.address);
68678
- const info = parsed.value?.data?.parsed?.info;
68679
- if (!info?.owner) throw new Error("roll-pt: could not resolve the quote SY holder's owner");
68680
- const trader = new web3_js.PublicKey(info.owner);
68681
- const ptTokenProgram = clmm.pt.tokenProgram ?? TOKEN_PROGRAM_ID;
68682
- const tokenPtTrader = getAssociatedTokenAddressSync(clmm.pt.mint, trader, true, ptTokenProgram);
68683
- const quoteIx = makeExponentClmmTradePtIx(
68684
- { ...clmm.tradePtAccounts, trader, tokenSyTrader: funded.address, tokenPtTrader },
68685
- exponentClmmBuyPtArgs({ amountInSyNative, minPtOutNative: 1n })
68686
- );
68687
- const createPtAta = createAssociatedTokenAccountIdempotentInstruction(
68688
- payer,
68689
- tokenPtTrader,
68690
- trader,
68691
- clmm.pt.mint,
68692
- ptTokenProgram
68693
- );
68694
- const { blockhash } = await connection.getLatestBlockhash("confirmed");
68695
- const message = new web3_js.TransactionMessage({
68696
- payerKey: payer,
68697
- recentBlockhash: blockhash,
68698
- instructions: [createPtAta, quoteIx]
68699
- }).compileToV0Message([clmm.addressLookupTable]);
68700
- const sim = await connection.simulateTransaction(new web3_js.VersionedTransaction(message), {
68701
- sigVerify: false,
68702
- replaceRecentBlockhash: true
68703
- });
68704
- const rd = sim.value.returnData;
68705
- if (process.env.ROLL_DEBUG) {
68706
- console.error("[roll trade quote] err:", JSON.stringify(sim.value.err), "returnData?", !!rd);
68707
- }
68708
- if (!rd?.data || rd.programId !== EXPONENT_CLMM_PROGRAM_ID.toBase58()) {
68709
- throw new Error(
68710
- `roll-pt: CLMM trade quote produced no return data (err=${JSON.stringify(sim.value.err)})`
68711
- );
68712
- }
68713
- const data = Buffer.from(rd.data[0], rd.data[1]);
68714
- return readReturnU64(data, TRADE_PT_EVENT_AMOUNT_OUT_OFFSET, "trade_pt amount_out");
68715
- }
68716
-
68717
- // src/models/balance.ts
68718
- var Balance = class _Balance {
68719
- constructor(active, bankPk, assetShares, liabilityShares, emissionsOutstanding, lastUpdate) {
68720
- this.active = active;
68721
- this.bankPk = bankPk;
68722
- this.assetShares = assetShares;
68723
- this.liabilityShares = liabilityShares;
68724
- this.emissionsOutstanding = emissionsOutstanding;
68725
- this.lastUpdate = lastUpdate;
68726
- }
68727
- static from(balanceRaw) {
68728
- const props = parseBalanceRaw(balanceRaw);
68729
- return new _Balance(
68730
- props.active,
68731
- props.bankPk,
68732
- props.assetShares,
68733
- props.liabilityShares,
68734
- props.emissionsOutstanding,
68735
- props.lastUpdate
68736
- );
68737
- }
68738
- static fromBalanceType(balance) {
68739
- return new _Balance(
68740
- balance.active,
68741
- balance.bankPk,
68742
- balance.assetShares,
68743
- balance.liabilityShares,
68744
- balance.emissionsOutstanding,
68745
- balance.lastUpdate
68746
- );
68747
- }
68748
- static createEmpty(bankPk) {
68749
- const balance = createEmptyBalance(bankPk);
68750
- return this.fromBalanceType(balance);
68751
- }
68752
- computeUsdValue(bank, oraclePrice, marginRequirement = 2 /* Equity */, assetShareValueMultiplier, activeEmodeWeights) {
68753
- return computeBalanceUsdValue({
68754
- balance: this,
68755
- bank,
68756
- oraclePrice,
68757
- marginRequirement,
68758
- assetShareValueMultiplier,
68759
- activeEmodeWeights
68760
- });
68761
- }
68762
- getUsdValueWithPriceBias(bank, oraclePrice, marginRequirement = 2 /* Equity */, assetShareValueMultiplier, activeEmodeWeights) {
68763
- return getBalanceUsdValueWithPriceBias({
68764
- balance: this,
68765
- bank,
68766
- oraclePrice,
68767
- marginRequirement,
68768
- assetShareValueMultiplier,
68769
- activeEmodeWeights
68770
- });
68771
- }
68772
- computeQuantity(bank) {
68773
- return computeQuantity(this, bank);
68774
- }
68775
- computeQuantityUi(bank, assetShareValueMultiplier) {
68776
- return computeQuantityUi(this, bank, assetShareValueMultiplier);
68777
- }
68778
- computeTotalOutstandingEmissions(bank) {
68779
- return computeTotalOutstandingEmissions(this, bank);
68780
- }
68781
- computeClaimedEmissions(bank, currentTimestamp) {
68782
- return computeClaimedEmissions(this, bank, currentTimestamp);
68783
- }
68784
- };
68785
-
68786
- // src/models/health-cache.ts
68787
- var HealthCache = class _HealthCache {
68788
- constructor(assetValue, liabilityValue, assetValueMaint, liabilityValueMaint, assetValueEquity, liabilityValueEquity, timestamp, flags, prices, simulationStatus) {
68789
- this.assetValue = assetValue;
68790
- this.liabilityValue = liabilityValue;
68791
- this.assetValueMaint = assetValueMaint;
68792
- this.liabilityValueMaint = liabilityValueMaint;
68793
- this.assetValueEquity = assetValueEquity;
68794
- this.liabilityValueEquity = liabilityValueEquity;
68795
- this.timestamp = timestamp;
68796
- this.flags = flags;
68797
- this.prices = prices;
68798
- this.simulationStatus = simulationStatus;
68799
- }
68800
- static fromHealthCacheType(healthCacheType) {
68801
- return new _HealthCache(
68802
- healthCacheType.assetValue,
68803
- healthCacheType.liabilityValue,
68804
- healthCacheType.assetValueMaint,
68805
- healthCacheType.liabilityValueMaint,
68806
- healthCacheType.assetValueEquity,
68807
- healthCacheType.liabilityValueEquity,
68808
- healthCacheType.timestamp,
68809
- healthCacheType.flags,
68810
- healthCacheType.prices,
68811
- healthCacheType.simulationStatus
68812
- );
68813
- }
68814
- static from(healthCacheRaw) {
68815
- return this.fromHealthCacheType(parseHealthCacheRaw(healthCacheRaw));
68816
- }
68817
- };
68818
-
68819
- // src/models/account.ts
68820
- var MarginfiAccount = class _MarginfiAccount {
68821
- constructor(address, group, authority, balances, accountFlags, emissionsDestinationAccount, healthCache) {
68822
- this.address = address;
68823
- this.group = group;
68824
- this.authority = authority;
68825
- this.balances = balances;
68826
- this.accountFlags = accountFlags;
68827
- this.emissionsDestinationAccount = emissionsDestinationAccount;
68828
- this.healthCache = healthCache;
68829
- }
68830
- /**
68831
- * Fetches a marginfi account from on-chain data.
68832
- *
68833
- * @param address - The public key of the marginfi account
68834
- * @param program - The Marginfi program instance
68835
- *
68836
- * @returns Promise resolving to a MarginfiAccount instance
68837
- */
68838
- static async fetch(address, program) {
68839
- const data = await program.account.marginfiAccount.fetch(address);
68840
- return _MarginfiAccount.fromAccountParsed(address, data);
68841
- }
68842
- static decodeAccountRaw(encoded, idl) {
68843
- return decodeAccountRaw(encoded, idl);
68844
- }
68845
- static fromAccountType(account) {
68846
- return new _MarginfiAccount(
68847
- account.address,
68848
- account.group,
68849
- account.authority,
68850
- account.balances.map((b) => Balance.fromBalanceType(b)),
68851
- account.accountFlags,
68852
- account.emissionsDestinationAccount,
68853
- account.healthCache
68854
- );
68855
- }
68856
- /**
68857
- * Creates a MarginfiAccount instance from parsed account data.
68858
- *
68859
- * @param marginfiAccountPk - The public key of the marginfi account
68860
- * @param accountData - The raw account data from the program
68861
- *
68862
- * @returns A new MarginfiAccount instance
68863
- */
68864
- static fromAccountParsed(marginfiAccountPk, accountData) {
68865
- const props = parseMarginfiAccountRaw(marginfiAccountPk, accountData);
68866
- return new _MarginfiAccount(
68867
- props.address,
68868
- props.group,
68869
- props.authority,
68870
- props.balances.map((b) => Balance.fromBalanceType(b)),
68871
- props.accountFlags,
68872
- props.emissionsDestinationAccount,
68873
- HealthCache.fromHealthCacheType(props.healthCache)
68874
- );
68875
- }
68876
- /**
68877
- * Simulates and updates the health cache for this account.
68878
- *
68879
- * Fetches current oracle prices and computes fresh health values. Returns a new
68880
- * account instance with updated health cache (does not mutate this instance).
68881
- *
68882
- * @param params - Configuration for health cache simulation (excluding marginfiAccount)
68883
- * @returns Object containing the updated account and any errors
68884
- */
68885
- async simulateHealthCache(params) {
68886
- const accountWithHealthCache = await simulateAccountHealthCacheWithFallback({
68887
- marginfiAccount: this,
68888
- ...params
68889
- });
68890
- return {
68891
- account: _MarginfiAccount.fromAccountType(accountWithHealthCache.marginfiAccount),
68892
- error: accountWithHealthCache.error
68893
- };
68894
- }
68895
- static fromAccountDataRaw(marginfiAccountPk, rawData, idl) {
68896
- const marginfiAccountData = _MarginfiAccount.decodeAccountRaw(rawData, idl);
68897
- return _MarginfiAccount.fromAccountParsed(marginfiAccountPk, marginfiAccountData);
68898
- }
68899
- // ----------------------------------------------------------------------------
68900
- // Attributes
68901
- // ----------------------------------------------------------------------------
68902
- /**
68903
- * Gets all balances that are currently active (non-zero).
68904
- *
68905
- * @returns Array of active Balance instances
68906
- */
68907
- get activeBalances() {
68908
- return this.balances.filter((b) => b.active);
68909
- }
68910
- /**
68911
- * Gets the balance for a specific bank.
68912
- *
68913
- * @param bankPk - The public key of the bank
68914
- *
68915
- * @returns The Balance instance for the bank (may be empty)
68916
- */
68917
- getBalance(bankPk) {
68918
- return Balance.fromBalanceType(getBalance(bankPk, this.balances));
68919
- }
68920
- /**
68921
- * Checks if the account is disabled.
68922
- *
68923
- * @returns True if the account is disabled, false otherwise
68924
- */
68925
- get isDisabled() {
68926
- return this.accountFlags.includes(1 /* ACCOUNT_DISABLED */);
68927
- }
68928
- /**
68929
- * Checks if the account is currently in a flash loan.
68930
- *
68931
- * @returns True if a flash loan is active, false otherwise
68932
- */
68933
- get isFlashLoanEnabled() {
68934
- return this.accountFlags.includes(2 /* ACCOUNT_IN_FLASHLOAN */);
68935
- }
68936
- /**
68937
- * Checks if account authority transfer is enabled.
68938
- *
68939
- * @returns True if authority transfer is allowed, false otherwise
68940
- */
68941
- get isTransferAccountAuthorityEnabled() {
68942
- return this.accountFlags.includes(8 /* ACCOUNT_TRANSFER_AUTHORITY_ALLOWED */);
68943
- }
68944
- /**
68945
- * Sets the health cache for this account.
68946
- *
68947
- * Note: This mutates the account instance. Consider using simulateHealthCache()
68948
- * for a pure functional approach that returns a new account instance.
68949
- *
68950
- * @param value - The new health cache to set
68951
- */
68952
- setHealthCache(value) {
68953
- this.healthCache = value;
68954
- }
68955
- /**
68956
- * Computes free collateral using cached health values.
68957
- *
68958
- * Free collateral represents the amount of collateral that is not backing any liabilities.
68959
- *
68960
- * @param opts - Optional configuration
68961
- * @param opts.clamped - If true, clamps negative values to zero
68962
- *
68963
- * @returns The free collateral amount in USD
68964
- */
68965
- computeFreeCollateralFromCache(opts) {
68966
- return computeFreeCollateralFromCache(this, opts);
68967
- }
68968
- /**
68969
- * Computes free collateral from balances using Initial margin requirements.
68970
- *
68971
- * Free collateral represents the amount of value available for new borrows or withdrawals.
68972
- * By default, negative values are clamped to zero.
68973
- *
68974
- * @param params - Configuration for free collateral computation (excluding activeBalances)
68975
- * @returns Free collateral value in USD (clamped to zero by default)
68976
- */
68977
- computeFreeCollateralFromBalances(params) {
68978
- return computeFreeCollateralFromBalances({
68979
- activeBalances: this.activeBalances,
68980
- ...params
68981
- });
68982
- }
68983
- /**
68984
- * Computes health components using cached health values.
68985
- *
68986
- * Returns weighted asset and liability values based on the margin requirement type.
68987
- *
68988
- * @param marginReqType - The margin requirement type (Initial, Maintenance, or Equity)
68989
- *
68990
- * @returns Object containing assets and liabilities values in USD
68991
- */
68992
- computeHealthComponentsFromCache(marginRequirement) {
68993
- return computeHealthComponentsFromCache(this, marginRequirement);
68994
- }
68995
- /**
68996
- * Computes health components from balances with price bias.
68997
- *
68998
- * Returns weighted asset and liability values using conservative pricing
68999
- * (Lowest for assets, Highest for liabilities).
69000
- *
69001
- * @param params - Configuration for health computation (excluding activeBalances)
69002
- * @returns Object containing assets and liabilities values in USD
69003
- */
69004
- computeHealthComponentsFromBalances(params) {
69005
- return computeHealthComponentsFromBalances({
69006
- activeBalances: this.activeBalances,
69007
- ...params
69008
- });
67536
+ /**
67537
+ * Computes health components from balances with price bias.
67538
+ *
67539
+ * Returns weighted asset and liability values using conservative pricing
67540
+ * (Lowest for assets, Highest for liabilities).
67541
+ *
67542
+ * @param params - Configuration for health computation (excluding activeBalances)
67543
+ * @returns Object containing assets and liabilities values in USD
67544
+ */
67545
+ computeHealthComponentsFromBalances(params) {
67546
+ return computeHealthComponentsFromBalances({
67547
+ activeBalances: this.activeBalances,
67548
+ ...params
67549
+ });
69009
67550
  }
69010
67551
  /**
69011
67552
  * Computes the total account value (equity).
@@ -69410,6 +67951,86 @@ var MarginfiAccount = class _MarginfiAccount {
69410
67951
  }
69411
67952
  });
69412
67953
  }
67954
+ /**
67955
+ * Creates a loop transaction with a transparent bridged (double-hop) fallback.
67956
+ *
67957
+ * One call: tries the direct {@link makeLoopTx} first; if its borrow→deposit swap can't fit one
67958
+ * transaction (size / account-locks) or has no route, it loops the deposit asset against a
67959
+ * value-equivalent borrow of a high-liquidity bridge token, then debt-swaps the bridge debt to
67960
+ * the requested borrow asset — both legs composed into ONE atomic Jito bundle.
67961
+ *
67962
+ * Bridge candidates default to USDC → wSOL → USDT and can be reordered/overridden via
67963
+ * `params.bridgeOpts.bridgeCandidateMints`; `bridgeOpts` also accepts known token programs (skips RPC
67964
+ * lookups), a bundle-size ceiling, and an abort signal. `result.bridgeMint` is set only when the
67965
+ * bridged path was used.
67966
+ *
67967
+ * Intended for existing accounts — a fresh account's loop fits the direct path, so flows that
67968
+ * create the account in the same action should call {@link makeLoopTx} directly.
67969
+ *
67970
+ * @param params - Loop transaction parameters plus optional `bridgeOpts`
67971
+ * @returns Object containing transactions, action index, merged swap quote, and the bridge mint
67972
+ *
67973
+ * @see {@link makeBridgedLoopTx} for detailed implementation
67974
+ */
67975
+ async makeBridgedLoopTx(params) {
67976
+ return makeBridgedLoopTx({
67977
+ ...params,
67978
+ marginfiAccount: this,
67979
+ overrideInferAccounts: {
67980
+ authority: this.authority,
67981
+ group: this.group,
67982
+ ...params.overrideInferAccounts
67983
+ }
67984
+ });
67985
+ }
67986
+ /**
67987
+ * Creates a collateral-swap transaction with a transparent bridged (double-hop) fallback.
67988
+ *
67989
+ * One call: tries the direct {@link makeSwapCollateralTx} first; if the swap `A → C` can't fit
67990
+ * one transaction or has no route, it decomposes into `A → bridge` + `bridge → C` through a
67991
+ * high-liquidity bridge collateral, both legs composed into ONE atomic Jito bundle. See
67992
+ * {@link makeBridgedLoopTx} for the `bridgeOpts` knobs.
67993
+ *
67994
+ * @param params - Swap collateral transaction parameters plus optional `bridgeOpts`
67995
+ * @returns Object containing transactions, action index, merged swap quote, and the bridge mint
67996
+ *
67997
+ * @see {@link makeBridgedSwapCollateralTx} for detailed implementation
67998
+ */
67999
+ async makeBridgedSwapCollateralTx(params) {
68000
+ return makeBridgedSwapCollateralTx({
68001
+ ...params,
68002
+ marginfiAccount: this,
68003
+ overrideInferAccounts: {
68004
+ authority: this.authority,
68005
+ group: this.group,
68006
+ ...params.overrideInferAccounts
68007
+ }
68008
+ });
68009
+ }
68010
+ /**
68011
+ * Creates a debt-swap transaction with a transparent bridged (double-hop) fallback.
68012
+ *
68013
+ * One call: tries the direct {@link makeSwapDebtTx} first; if the swap `A → C` can't fit one
68014
+ * transaction or has no route, the first leg repays A by borrowing a bridge token and the second
68015
+ * leg repays exactly that bridge debt while borrowing C — both legs composed into ONE atomic
68016
+ * Jito bundle. See {@link makeBridgedLoopTx} for the `bridgeOpts` knobs.
68017
+ *
68018
+ * @param params - Swap debt transaction parameters plus optional `bridgeOpts`
68019
+ * @returns Object containing transactions, action index, merged swap quote, and the bridge mint
68020
+ *
68021
+ * @see {@link makeBridgedSwapDebtTx} for detailed implementation
68022
+ */
68023
+ async makeBridgedSwapDebtTx(params) {
68024
+ return makeBridgedSwapDebtTx({
68025
+ ...params,
68026
+ marginfiAccount: this,
68027
+ overrideInferAccounts: {
68028
+ authority: this.authority,
68029
+ group: this.group,
68030
+ ...params.overrideInferAccounts
68031
+ }
68032
+ });
68033
+ }
69413
68034
  /**
69414
68035
  * Creates a transaction to repay debt using collateral.
69415
68036
  *
@@ -69449,425 +68070,2168 @@ var MarginfiAccount = class _MarginfiAccount {
69449
68070
  }
69450
68071
  });
69451
68072
  }
69452
- /**
69453
- * Creates a transaction to swap one collateral position to another using a flash loan.
69454
- *
69455
- * A swap collateral transaction:
69456
- * 1. Withdraws existing collateral via flash loan
69457
- * 2. Swaps collateral to new asset (via Jupiter)
69458
- * 3. Deposits swapped assets as new collateral
69459
- *
69460
- * This allows users to change their collateral type (e.g., JitoSOL -> mSOL) without
69461
- * withdrawing and affecting their health during the swap.
69462
- *
69463
- * @param params - Swap collateral transaction parameters
69464
- * @param params.connection - Solana connection instance
69465
- * @param params.oraclePrices - Map of current oracle prices
69466
- * @param params.withdrawOpts - Withdraw configuration (bank, amount, tokenProgram)
69467
- * @param params.depositOpts - Deposit configuration (bank, tokenProgram)
69468
- * @param params.swapOpts - Swap configuration (venue, slippage, fees)
69469
- * @param params.addressLookupTableAccounts - Address lookup tables
69470
- * @param params.overrideInferAccounts - Optional account overrides
69471
- * @param params.additionalIxs - Additional instructions to include
69472
- * @param params.crossbarUrl - Crossbar URL for oracle updates
69473
- *
69474
- * @returns Object containing transactions array, action index, and swap quote
69475
- *
69476
- * @throws {TransactionBuildingError} If swap exceeds transaction size limits
69477
- * @throws {TransactionBuildingError} If Kamino reserve not found
69478
- *
69479
- * @see {@link makeSwapCollateralTx} for detailed implementation
69480
- */
69481
- async makeSwapCollateralTx(params) {
69482
- return makeSwapCollateralTx({
69483
- ...params,
69484
- marginfiAccount: this,
69485
- overrideInferAccounts: {
69486
- authority: this.authority,
69487
- group: this.group,
69488
- ...params.overrideInferAccounts
69489
- }
68073
+ /**
68074
+ * Creates a transaction to swap one collateral position to another using a flash loan.
68075
+ *
68076
+ * A swap collateral transaction:
68077
+ * 1. Withdraws existing collateral via flash loan
68078
+ * 2. Swaps collateral to new asset (via Jupiter)
68079
+ * 3. Deposits swapped assets as new collateral
68080
+ *
68081
+ * This allows users to change their collateral type (e.g., JitoSOL -> mSOL) without
68082
+ * withdrawing and affecting their health during the swap.
68083
+ *
68084
+ * @param params - Swap collateral transaction parameters
68085
+ * @param params.connection - Solana connection instance
68086
+ * @param params.oraclePrices - Map of current oracle prices
68087
+ * @param params.withdrawOpts - Withdraw configuration (bank, amount, tokenProgram)
68088
+ * @param params.depositOpts - Deposit configuration (bank, tokenProgram)
68089
+ * @param params.swapOpts - Swap configuration (venue, slippage, fees)
68090
+ * @param params.addressLookupTableAccounts - Address lookup tables
68091
+ * @param params.overrideInferAccounts - Optional account overrides
68092
+ * @param params.additionalIxs - Additional instructions to include
68093
+ * @param params.crossbarUrl - Crossbar URL for oracle updates
68094
+ *
68095
+ * @returns Object containing transactions array, action index, and swap quote
68096
+ *
68097
+ * @throws {TransactionBuildingError} If swap exceeds transaction size limits
68098
+ * @throws {TransactionBuildingError} If Kamino reserve not found
68099
+ *
68100
+ * @see {@link makeSwapCollateralTx} for detailed implementation
68101
+ */
68102
+ async makeSwapCollateralTx(params) {
68103
+ return makeSwapCollateralTx({
68104
+ ...params,
68105
+ marginfiAccount: this,
68106
+ overrideInferAccounts: {
68107
+ authority: this.authority,
68108
+ group: this.group,
68109
+ ...params.overrideInferAccounts
68110
+ }
68111
+ });
68112
+ }
68113
+ /**
68114
+ * Creates a transaction to roll a matured Exponent PT collateral position into its
68115
+ * next-maturity PT, in one flash-loan-wrapped bundle
68116
+ * (withdraw PT_old → `wrapper_merge` to base → swap-engine buy PT_new → deposit).
68117
+ * See {@link makeRollPtTx}.
68118
+ *
68119
+ * @param params - Roll-PT parameters (`withdrawOpts`/`depositOpts`/`swapOpts`/`rollOpts`).
68120
+ */
68121
+ async makeRollPtTx(params) {
68122
+ return makeRollPtTx({
68123
+ ...params,
68124
+ marginfiAccount: this,
68125
+ overrideInferAccounts: {
68126
+ authority: this.authority,
68127
+ group: this.group,
68128
+ ...params.overrideInferAccounts
68129
+ }
68130
+ });
68131
+ }
68132
+ /**
68133
+ * Creates a transaction to swap one debt position to another using a flash loan.
68134
+ *
68135
+ * A swap debt transaction:
68136
+ * 1. Borrows new asset via flash loan (new debt)
68137
+ * 2. Swaps new asset to old debt asset (via Jupiter)
68138
+ * 3. Repays old debt with swapped assets
68139
+ *
68140
+ * This allows users to change their debt type (e.g., USDC debt -> SOL debt) without
68141
+ * repaying and affecting their health during the swap.
68142
+ *
68143
+ * @param params - Swap debt transaction parameters
68144
+ * @param params.connection - Solana connection instance
68145
+ * @param params.oraclePrices - Map of current oracle prices
68146
+ * @param params.repayOpts - Repay configuration (bank, amount, tokenProgram)
68147
+ * @param params.borrowOpts - Borrow configuration (bank, tokenProgram)
68148
+ * @param params.swapOpts - Swap configuration (venue, slippage, fees)
68149
+ * @param params.addressLookupTableAccounts - Address lookup tables
68150
+ * @param params.overrideInferAccounts - Optional account overrides
68151
+ * @param params.additionalIxs - Additional instructions to include
68152
+ * @param params.crossbarUrl - Crossbar URL for oracle updates
68153
+ *
68154
+ * @returns Object containing transactions array, action index, and swap quote
68155
+ *
68156
+ * @throws {TransactionBuildingError} If swap exceeds transaction size limits
68157
+ *
68158
+ * @see {@link makeSwapDebtTx} for detailed implementation
68159
+ */
68160
+ async makeSwapDebtTx(params) {
68161
+ return makeSwapDebtTx({
68162
+ ...params,
68163
+ marginfiAccount: this,
68164
+ overrideInferAccounts: {
68165
+ authority: this.authority,
68166
+ group: this.group,
68167
+ ...params.overrideInferAccounts
68168
+ }
68169
+ });
68170
+ }
68171
+ /**
68172
+ * Creates a deposit transaction.
68173
+ *
68174
+ * @param params - Parameters for the deposit transaction
68175
+ * @returns Promise resolving to an ExtendedTransaction
68176
+ *
68177
+ * @see {@link makeDepositTx} for detailed implementation
68178
+ */
68179
+ async makeDepositTx(params) {
68180
+ return makeDepositTx({
68181
+ ...params,
68182
+ accountAddress: this.address,
68183
+ authority: this.authority,
68184
+ group: this.group
68185
+ });
68186
+ }
68187
+ /**
68188
+ * Creates a Drift deposit transaction.
68189
+ *
68190
+ * @param params - Parameters for the Drift deposit transaction
68191
+ * @returns Promise resolving to an ExtendedV0Transaction
68192
+ *
68193
+ * @see {@link makeDriftDepositTx} for detailed implementation
68194
+ */
68195
+ async makeDriftDepositTx(params) {
68196
+ return makeDriftDepositTx({
68197
+ ...params,
68198
+ accountAddress: this.address,
68199
+ authority: this.authority,
68200
+ group: this.group
68201
+ });
68202
+ }
68203
+ /**
68204
+ * Creates a Kamino deposit transaction.
68205
+ *
68206
+ * @param params - Parameters for the Kamino deposit transaction
68207
+ * @returns Promise resolving to an ExtendedV0Transaction
68208
+ *
68209
+ * @see {@link makeKaminoDepositTx} for detailed implementation
68210
+ */
68211
+ async makeKaminoDepositTx(params) {
68212
+ return makeKaminoDepositTx({
68213
+ ...params,
68214
+ accountAddress: this.address,
68215
+ authority: this.authority,
68216
+ group: this.group
68217
+ });
68218
+ }
68219
+ /**
68220
+ * Creates a borrow transaction.
68221
+ *
68222
+ * @param params - Parameters for the borrow transaction
68223
+ * @returns Promise resolving to a TransactionBuilderResult
68224
+ *
68225
+ * @see {@link makeBorrowTx} for detailed implementation
68226
+ */
68227
+ async makeBorrowTx(params) {
68228
+ return makeBorrowTx({
68229
+ ...params,
68230
+ marginfiAccount: this,
68231
+ opts: {
68232
+ ...params.opts,
68233
+ overrideInferAccounts: {
68234
+ authority: this.authority,
68235
+ ...params.opts?.overrideInferAccounts
68236
+ }
68237
+ }
68238
+ });
68239
+ }
68240
+ /**
68241
+ * Creates a repay transaction.
68242
+ *
68243
+ * @param params - Parameters for the repay transaction
68244
+ * @returns Promise resolving to an ExtendedTransaction
68245
+ *
68246
+ * @see {@link makeRepayTx} for detailed implementation
68247
+ */
68248
+ async makeRepayTx(params) {
68249
+ return makeRepayTx({
68250
+ ...params,
68251
+ accountAddress: this.address,
68252
+ authority: this.authority
68253
+ });
68254
+ }
68255
+ /**
68256
+ * Creates a withdraw transaction.
68257
+ *
68258
+ * @param params - Parameters for the withdraw transaction
68259
+ * @returns Promise resolving to a TransactionBuilderResult
68260
+ *
68261
+ * @see {@link makeWithdrawTx} for detailed implementation
68262
+ */
68263
+ async makeWithdrawTx(params) {
68264
+ return makeWithdrawTx({
68265
+ ...params,
68266
+ marginfiAccount: this,
68267
+ opts: {
68268
+ ...params.opts,
68269
+ overrideInferAccounts: {
68270
+ authority: this.authority,
68271
+ group: this.group,
68272
+ ...params.opts?.overrideInferAccounts
68273
+ }
68274
+ }
68275
+ });
68276
+ }
68277
+ /**
68278
+ * Creates a Drift withdraw transaction.
68279
+ *
68280
+ * @param params - Parameters for the Drift withdraw transaction
68281
+ * @returns Promise resolving to an ExtendedV0Transaction
68282
+ *
68283
+ * @see {@link makeDriftWithdrawTx} for detailed implementation
68284
+ */
68285
+ async makeDriftWithdrawTx(params) {
68286
+ return makeDriftWithdrawTx({
68287
+ ...params,
68288
+ marginfiAccount: this,
68289
+ opts: {
68290
+ ...params.opts,
68291
+ overrideInferAccounts: {
68292
+ authority: this.authority,
68293
+ group: this.group,
68294
+ ...params.opts?.overrideInferAccounts
68295
+ }
68296
+ }
68297
+ });
68298
+ }
68299
+ /**
68300
+ * Creates a Kamino withdraw transaction.
68301
+ *
68302
+ * @param params - Parameters for the Kamino withdraw transaction
68303
+ * @returns Promise resolving to a TransactionBuilderResult
68304
+ *
68305
+ * @see {@link makeKaminoWithdrawTx} for detailed implementation
68306
+ */
68307
+ async makeKaminoWithdrawTx(params) {
68308
+ return makeKaminoWithdrawTx({
68309
+ ...params,
68310
+ marginfiAccount: this,
68311
+ opts: {
68312
+ ...params.opts,
68313
+ overrideInferAccounts: {
68314
+ authority: this.authority,
68315
+ group: this.group,
68316
+ ...params.opts?.overrideInferAccounts
68317
+ }
68318
+ }
68319
+ });
68320
+ }
68321
+ /**
68322
+ * Creates a flash loan transaction.
68323
+ *
68324
+ * @param params - Parameters for the flash loan transaction
68325
+ * @returns Promise resolving to flash loan transaction details
68326
+ *
68327
+ * @see {@link makeFlashLoanTx} for detailed implementation
68328
+ */
68329
+ async makeFlashLoanTx(params) {
68330
+ return makeFlashLoanTx({
68331
+ ...params,
68332
+ marginfiAccount: this
68333
+ });
68334
+ }
68335
+ };
68336
+
68337
+ // src/services/account/actions/bridge-swap.ts
68338
+ var MAX_BRIDGED_BUNDLE_TXS = 5;
68339
+ function classifyTxs(txs) {
68340
+ const out = { setups: [], cranks: [], flashloans: [] };
68341
+ for (const tx of txs) {
68342
+ if (tx.type === "CREATE_ATA" /* CREATE_ATA */) out.setups.push(tx);
68343
+ else if (tx.type === "CRANK" /* CRANK */) out.cranks.push(tx);
68344
+ else out.flashloans.push(tx);
68345
+ }
68346
+ return out;
68347
+ }
68348
+ function ixIdentity(ix) {
68349
+ const keys = ix.keys.map((k) => k.pubkey.toBase58()).join(",");
68350
+ return `${ix.programId.toBase58()}|${keys}|${Buffer.from(ix.data).toString("base64")}`;
68351
+ }
68352
+ function mergeSetupTxs(txs, payer, blockhash) {
68353
+ if (txs.length === 0) return null;
68354
+ if (txs.length === 1) return txs[0];
68355
+ const lutMap = /* @__PURE__ */ new Map();
68356
+ const seen = /* @__PURE__ */ new Set();
68357
+ const ixs = [];
68358
+ for (const tx of txs) {
68359
+ const luts2 = tx.addressLookupTables ?? [];
68360
+ luts2.forEach((l) => lutMap.set(l.key.toBase58(), l));
68361
+ const msg = decompileV0Transaction(tx, luts2);
68362
+ for (const ix of msg.instructions) {
68363
+ const id = ixIdentity(ix);
68364
+ if (seen.has(id)) continue;
68365
+ seen.add(id);
68366
+ ixs.push(ix);
68367
+ }
68368
+ }
68369
+ const luts = [...lutMap.values()];
68370
+ const split = splitInstructionsToFitTransactions([], ixs, { blockhash, payerKey: payer, luts });
68371
+ if (split.length !== 1) return null;
68372
+ return addTransactionMetadata(split[0], {
68373
+ type: "CREATE_ATA" /* CREATE_ATA */,
68374
+ addressLookupTables: luts
68375
+ });
68376
+ }
68377
+ function projectAccountAfterFirstLeg(account, firstLegFlashloanTxs, program, banksMap, multipliers) {
68378
+ const ixs = [];
68379
+ for (const tx of firstLegFlashloanTxs) {
68380
+ const luts = tx.addressLookupTables ?? [];
68381
+ ixs.push(...decompileV0Transaction(tx, luts).instructions);
68382
+ }
68383
+ const { projectedBalances } = computeProjectedActiveBalancesNoCpi({
68384
+ account,
68385
+ instructions: ixs,
68386
+ program,
68387
+ banksMap,
68388
+ assetShareValueMultiplierByBank: multipliers
68389
+ });
68390
+ return new MarginfiAccount(
68391
+ account.address,
68392
+ account.group,
68393
+ account.authority,
68394
+ projectedBalances.map((b) => Balance.fromBalanceType(b)),
68395
+ account.accountFlags,
68396
+ account.emissionsDestinationAccount,
68397
+ account.healthCache
68398
+ );
68399
+ }
68400
+ function composeBundle(firstLegTxs, secondLegTxs, payer, blockhash, maxBundleTxs) {
68401
+ const c1 = classifyTxs(firstLegTxs);
68402
+ const c2 = classifyTxs(secondLegTxs);
68403
+ const mergedSetup = mergeSetupTxs([...c1.setups, ...c2.setups], payer, blockhash);
68404
+ if ([...c1.setups, ...c2.setups].length > 0 && !mergedSetup) return null;
68405
+ const result = [
68406
+ ...mergedSetup ? [mergedSetup] : [],
68407
+ ...c1.cranks,
68408
+ ...c1.flashloans,
68409
+ // firstLegFL(s)
68410
+ ...c2.cranks,
68411
+ ...c2.flashloans
68412
+ // secondLegFL(s)
68413
+ ];
68414
+ if (result.length > maxBundleTxs) return null;
68415
+ return result;
68416
+ }
68417
+ function compoundQuoteRisk(firstLeg, secondLeg) {
68418
+ const compound = (a, b) => {
68419
+ if (a == null && b == null) return void 0;
68420
+ const x = Number(a ?? 0);
68421
+ const y = Number(b ?? 0);
68422
+ return String(1 - (1 - x) * (1 - y));
68423
+ };
68424
+ return {
68425
+ slippageBps: Math.round(
68426
+ (1 - (1 - firstLeg.slippageBps / 1e4) * (1 - secondLeg.slippageBps / 1e4)) * 1e4
68427
+ ),
68428
+ priceImpactPct: compound(firstLeg.priceImpactPct, secondLeg.priceImpactPct)
68429
+ };
68430
+ }
68431
+ function mergeBridgeQuotes(firstLeg, secondLeg) {
68432
+ return {
68433
+ inAmount: firstLeg.inAmount,
68434
+ outAmount: secondLeg.outAmount,
68435
+ otherAmountThreshold: secondLeg.otherAmountThreshold,
68436
+ ...compoundQuoteRisk(firstLeg, secondLeg),
68437
+ provider: firstLeg.provider
68438
+ };
68439
+ }
68440
+ function mergeBridgeQuotesDebt(firstLeg, secondLeg) {
68441
+ return {
68442
+ inAmount: firstLeg.outAmount,
68443
+ outAmount: secondLeg.inAmount,
68444
+ otherAmountThreshold: secondLeg.inAmount,
68445
+ ...compoundQuoteRisk(firstLeg, secondLeg),
68446
+ provider: firstLeg.provider
68447
+ };
68448
+ }
68449
+ function mergeBridgeQuotesLoop(firstLeg, secondLeg) {
68450
+ return {
68451
+ inAmount: secondLeg.inAmount,
68452
+ outAmount: firstLeg.outAmount,
68453
+ otherAmountThreshold: firstLeg.otherAmountThreshold,
68454
+ ...compoundQuoteRisk(firstLeg, secondLeg),
68455
+ provider: firstLeg.provider
68456
+ };
68457
+ }
68458
+ function blockhashOf(leg) {
68459
+ const tx = leg.transactions[0];
68460
+ return tx ? tx.message.recentBlockhash : web3_js.PublicKey.default.toBase58();
68461
+ }
68462
+ async function composeBridgedSwap(params) {
68463
+ const {
68464
+ firstLeg,
68465
+ buildSecondLeg,
68466
+ marginfiAccount,
68467
+ program,
68468
+ banksMap,
68469
+ assetShareValueMultiplierByBank,
68470
+ feePayer,
68471
+ maxBundleTxs = MAX_BRIDGED_BUNDLE_TXS
68472
+ } = params;
68473
+ if (!firstLeg.quoteResponse) return null;
68474
+ const projectedAccount = projectAccountAfterFirstLeg(
68475
+ marginfiAccount,
68476
+ classifyTxs(firstLeg.transactions).flashloans,
68477
+ program,
68478
+ banksMap,
68479
+ assetShareValueMultiplierByBank
68480
+ );
68481
+ const secondLeg = await buildSecondLeg(projectedAccount);
68482
+ if (!secondLeg.quoteResponse) return null;
68483
+ const transactions = composeBundle(
68484
+ firstLeg.transactions,
68485
+ secondLeg.transactions,
68486
+ feePayer,
68487
+ blockhashOf(firstLeg),
68488
+ maxBundleTxs
68489
+ );
68490
+ if (!transactions) return null;
68491
+ return { transactions, firstLegQuote: firstLeg.quoteResponse, secondLegQuote: secondLeg.quoteResponse };
68492
+ }
68493
+ async function makeSwapDebtTx(params) {
68494
+ const {
68495
+ program,
68496
+ marginfiAccount,
68497
+ connection,
68498
+ bankMap,
68499
+ oraclePrices,
68500
+ repayOpts,
68501
+ borrowOpts,
68502
+ bankMetadataMap,
68503
+ assetShareValueMultiplierByBank,
68504
+ addressLookupTableAccounts,
68505
+ crossbarUrl,
68506
+ additionalIxs = []
68507
+ } = params;
68508
+ const blockhash = (await connection.getLatestBlockhash("confirmed")).blockhash;
68509
+ const setupIxs = await makeSetupIx({
68510
+ connection,
68511
+ authority: marginfiAccount.authority,
68512
+ tokens: [
68513
+ { mint: repayOpts.repayBank.mint, tokenProgram: repayOpts.tokenProgram },
68514
+ { mint: borrowOpts.borrowBank.mint, tokenProgram: borrowOpts.tokenProgram }
68515
+ ]
68516
+ });
68517
+ const refreshIntegrationIxs = makeRefreshIntegrationBanksIxs(
68518
+ marginfiAccount,
68519
+ bankMap,
68520
+ [],
68521
+ bankMetadataMap,
68522
+ [repayOpts.repayBank.address, borrowOpts.borrowBank.address]
68523
+ );
68524
+ const { flashloanTx, setupInstructions, swapQuote, borrowIxs, repayIxs } = await buildSwapDebtFlashloanTx({
68525
+ ...params,
68526
+ blockhash
68527
+ });
68528
+ const jupiterSetupInstructions = setupInstructions.filter((ix) => {
68529
+ if (ix.programId.equals(web3_js.ComputeBudgetProgram.programId)) {
68530
+ return false;
68531
+ }
68532
+ if (ix.programId.equals(ASSOCIATED_TOKEN_PROGRAM_ID)) {
68533
+ const mintKey = ix.keys[3]?.pubkey;
68534
+ if (mintKey?.equals(repayOpts.repayBank.mint) || mintKey?.equals(borrowOpts.borrowBank.mint)) {
68535
+ return false;
68536
+ }
68537
+ }
68538
+ return true;
68539
+ });
68540
+ setupIxs.push(...jupiterSetupInstructions);
68541
+ const { instructions: updateFeedIxs, luts: feedLuts } = await makeSmartCrankSwbFeedIx({
68542
+ marginfiAccount,
68543
+ bankMap,
68544
+ oraclePrices,
68545
+ assetShareValueMultiplierByBank,
68546
+ instructions: [...borrowIxs.instructions, ...repayIxs.instructions],
68547
+ program,
68548
+ connection,
68549
+ crossbarUrl
68550
+ });
68551
+ let additionalTxs = [];
68552
+ if (setupIxs.length > 0 || refreshIntegrationIxs.instructions.length > 0) {
68553
+ const ixs = [...additionalIxs, ...setupIxs, ...refreshIntegrationIxs.instructions];
68554
+ const txs = splitInstructionsToFitTransactions([], ixs, {
68555
+ blockhash,
68556
+ payerKey: marginfiAccount.authority,
68557
+ luts: addressLookupTableAccounts ?? []
68558
+ });
68559
+ additionalTxs.push(
68560
+ ...txs.map(
68561
+ (tx) => addTransactionMetadata(tx, {
68562
+ type: "CREATE_ATA" /* CREATE_ATA */,
68563
+ addressLookupTables: addressLookupTableAccounts
68564
+ })
68565
+ )
68566
+ );
68567
+ }
68568
+ if (updateFeedIxs.length > 0) {
68569
+ const message = new web3_js.TransactionMessage({
68570
+ payerKey: marginfiAccount.authority,
68571
+ recentBlockhash: blockhash,
68572
+ instructions: updateFeedIxs
68573
+ }).compileToV0Message(feedLuts);
68574
+ additionalTxs.push(
68575
+ addTransactionMetadata(new web3_js.VersionedTransaction(message), {
68576
+ addressLookupTables: feedLuts,
68577
+ type: "CRANK" /* CRANK */
68578
+ })
68579
+ );
68580
+ }
68581
+ const transactions = [...additionalTxs, flashloanTx];
68582
+ return {
68583
+ transactions,
68584
+ actionTxIndex: transactions.length - 1,
68585
+ quoteResponse: swapQuote
68586
+ };
68587
+ }
68588
+ async function buildSwapDebtFlashloanTx({
68589
+ program,
68590
+ marginfiAccount,
68591
+ bankMap,
68592
+ repayOpts,
68593
+ borrowOpts,
68594
+ swapOpts,
68595
+ bankMetadataMap,
68596
+ addressLookupTableAccounts,
68597
+ connection,
68598
+ overrideInferAccounts,
68599
+ blockhash,
68600
+ swapEngineRunner
68601
+ }) {
68602
+ const {
68603
+ repayBank,
68604
+ tokenProgram: repayTokenProgram,
68605
+ totalPositionAmount,
68606
+ repayAmount
68607
+ } = repayOpts;
68608
+ const { borrowBank, tokenProgram: borrowTokenProgram } = borrowOpts;
68609
+ if (repayAmount !== void 0 && repayAmount <= 0) {
68610
+ throw new Error("repayAmount must be greater than 0");
68611
+ }
68612
+ const actualRepayAmount = Math.min(repayAmount ?? totalPositionAmount, totalPositionAmount);
68613
+ const cuRequestIxs = [
68614
+ web3_js.ComputeBudgetProgram.setComputeUnitLimit({ units: 12e5 }),
68615
+ web3_js.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1 })
68616
+ ];
68617
+ const destinationTokenAccount = getAssociatedTokenAddressSync(
68618
+ repayBank.mint,
68619
+ marginfiAccount.authority,
68620
+ true,
68621
+ repayTokenProgram.equals(TOKEN_2022_PROGRAM_ID) ? TOKEN_2022_PROGRAM_ID : void 0
68622
+ );
68623
+ const estimatedBorrowAmount = computeBorrowEstimateForRepay({
68624
+ repayTargetUi: actualRepayAmount,
68625
+ repayMarketPrice: repayOpts.marketPrice,
68626
+ borrowMarketPrice: borrowOpts.marketPrice,
68627
+ slippageBps: swapOpts.swapConfig?.slippageBps,
68628
+ isRepayAll: actualRepayAmount >= totalPositionAmount
68629
+ });
68630
+ const swapConstraints = await computeFlashloanSwapConstraints({
68631
+ program,
68632
+ marginfiAccount,
68633
+ bankMap,
68634
+ bankMetadataMap,
68635
+ addressLookupTableAccounts: addressLookupTableAccounts ?? [],
68636
+ primaryIx: { type: "borrow", bank: borrowBank, tokenProgram: borrowTokenProgram },
68637
+ secondaryIx: { type: "repay", bank: repayBank, tokenProgram: repayTokenProgram },
68638
+ overrideInferAccounts
68639
+ });
68640
+ const footprintBorrowIxs = await makeBorrowIx3({
68641
+ program,
68642
+ bank: borrowBank,
68643
+ bankMap,
68644
+ tokenProgram: borrowTokenProgram,
68645
+ amount: estimatedBorrowAmount,
68646
+ marginfiAccount,
68647
+ authority: marginfiAccount.authority,
68648
+ isSync: true,
68649
+ opts: { createAtas: false, wrapAndUnwrapSol: false, overrideInferAccounts }
68650
+ });
68651
+ const footprintRepayIxs = await makeRepayIx3({
68652
+ program,
68653
+ bank: repayBank,
68654
+ tokenProgram: repayTokenProgram,
68655
+ amount: actualRepayAmount,
68656
+ accountAddress: marginfiAccount.address,
68657
+ authority: marginfiAccount.authority,
68658
+ isSync: true,
68659
+ opts: { wrapAndUnwrapSol: false, overrideInferAccounts }
68660
+ });
68661
+ const runEngine = swapEngineRunner ?? runSwapEngine;
68662
+ const engineResult = await runEngine({
68663
+ inputMint: borrowBank.mint.toBase58(),
68664
+ outputMint: repayBank.mint.toBase58(),
68665
+ amountNative: uiToNative(estimatedBorrowAmount, borrowBank.mintDecimals).toNumber(),
68666
+ inputDecimals: borrowBank.mintDecimals,
68667
+ outputDecimals: repayBank.mintDecimals,
68668
+ ...swapEngineQuoteFieldsFromOpts(swapOpts),
68669
+ taker: marginfiAccount.authority,
68670
+ destinationTokenAccount,
68671
+ connection,
68672
+ footprint: {
68673
+ instructions: [
68674
+ ...cuRequestIxs,
68675
+ ...footprintBorrowIxs.instructions,
68676
+ ...footprintRepayIxs.instructions
68677
+ ],
68678
+ luts: addressLookupTableAccounts ?? [],
68679
+ payer: marginfiAccount.authority,
68680
+ sizeConstraint: swapConstraints.sizeConstraint,
68681
+ maxSwapTotalAccounts: swapConstraints.maxSwapTotalAccounts
68682
+ },
68683
+ providers: swapEngineProvidersFromOpts(swapOpts)
68684
+ });
68685
+ const quoteResponse = engineResult.quoteResponse;
68686
+ const outAmount = nativeToUi(quoteResponse.outAmount, repayBank.mintDecimals);
68687
+ const outAmountThreshold = nativeToUi(quoteResponse.otherAmountThreshold, repayBank.mintDecimals);
68688
+ const amountToRepay = outAmount > totalPositionAmount ? totalPositionAmount : outAmountThreshold;
68689
+ const borrowAmount = nativeToUi(quoteResponse.inAmount, borrowBank.mintDecimals);
68690
+ const borrowIxs = await makeBorrowIx3({
68691
+ program,
68692
+ bank: borrowBank,
68693
+ bankMap,
68694
+ tokenProgram: borrowTokenProgram,
68695
+ amount: borrowAmount,
68696
+ marginfiAccount,
68697
+ authority: marginfiAccount.authority,
68698
+ isSync: true,
68699
+ opts: {
68700
+ createAtas: false,
68701
+ wrapAndUnwrapSol: false,
68702
+ overrideInferAccounts
68703
+ }
68704
+ });
68705
+ const repayIxs = await makeRepayIx3({
68706
+ program,
68707
+ bank: repayBank,
68708
+ tokenProgram: repayTokenProgram,
68709
+ amount: amountToRepay,
68710
+ accountAddress: marginfiAccount.address,
68711
+ authority: marginfiAccount.authority,
68712
+ repayAll: isWholePosition(
68713
+ {
68714
+ amount: totalPositionAmount,
68715
+ isLending: false
68716
+ },
68717
+ amountToRepay,
68718
+ repayBank.mintDecimals
68719
+ ),
68720
+ isSync: true,
68721
+ opts: {
68722
+ wrapAndUnwrapSol: false,
68723
+ overrideInferAccounts
68724
+ }
68725
+ });
68726
+ const luts = [...addressLookupTableAccounts ?? [], ...engineResult.swapLuts];
68727
+ const allNonFlIxs = [
68728
+ ...cuRequestIxs,
68729
+ ...borrowIxs.instructions,
68730
+ ...engineResult.swapInstructions,
68731
+ ...repayIxs.instructions
68732
+ ];
68733
+ compileFlashloanPrecheck({
68734
+ allIxs: allNonFlIxs,
68735
+ payer: marginfiAccount.authority,
68736
+ luts,
68737
+ sizeConstraint: swapConstraints.sizeConstraint,
68738
+ swapIxCount: engineResult.swapInstructions.length,
68739
+ swapLutCount: engineResult.swapLuts.length
68740
+ });
68741
+ const flashloanTx = await makeFlashLoanTx({
68742
+ program,
68743
+ marginfiAccount,
68744
+ bankMap,
68745
+ addressLookupTableAccounts: luts,
68746
+ blockhash,
68747
+ ixs: allNonFlIxs,
68748
+ isSync: true
68749
+ });
68750
+ const txSize = getTxSize(flashloanTx);
68751
+ const totalKeys = getTotalAccountKeys(flashloanTx);
68752
+ if (txSize > MAX_TX_SIZE || totalKeys > MAX_ACCOUNT_LOCKS) {
68753
+ throw TransactionBuildingError.swapSizeExceededPositionSwap(
68754
+ txSize,
68755
+ totalKeys,
68756
+ swapOpts.swapConfig?.provider
68757
+ );
68758
+ }
68759
+ return {
68760
+ flashloanTx,
68761
+ setupInstructions: engineResult.setupInstructions,
68762
+ swapQuote: quoteResponse,
68763
+ borrowIxs,
68764
+ repayIxs
68765
+ };
68766
+ }
68767
+ async function makeBridgedSwapDebtTx(params) {
68768
+ const { bridgeOpts, ...directParams } = params;
68769
+ try {
68770
+ return await makeSwapDebtTx(directParams);
68771
+ } catch (directError) {
68772
+ if (!isDecomposableSwapError(directError)) throw directError;
68773
+ const bridged = await tryBridgedDebtSwap(directParams, bridgeOpts);
68774
+ if (bridged) return bridged;
68775
+ throw directError;
68776
+ }
68777
+ }
68778
+ async function tryBridgedDebtSwap(params, bridgeOpts) {
68779
+ const sourceBank = params.repayOpts.repayBank;
68780
+ const destinationBank = params.borrowOpts.borrowBank;
68781
+ const repayAmount = params.repayOpts.repayAmount ?? params.repayOpts.totalPositionAmount;
68782
+ const oraclePriceOf = (bank) => params.oraclePrices.get(bank.address.toBase58())?.priceRealtime.price.toNumber() ?? 0;
68783
+ const { usableBridgeBanks, conflictingBridgeBanks } = selectSwapBridges({
68784
+ sourceMint: sourceBank.mint,
68785
+ destinationMint: destinationBank.mint,
68786
+ bankMap: params.bankMap,
68787
+ marginfiAccount: params.marginfiAccount,
68788
+ bridgeTokenSide: "borrow",
68789
+ bridgeCandidateMints: bridgeOpts?.bridgeCandidateMints
68790
+ });
68791
+ const tokenProgramCache = new Map(bridgeOpts?.tokenProgramByMint);
68792
+ return tryBridgeCandidates({
68793
+ usableBridgeBanks,
68794
+ conflictingBridgeBanks,
68795
+ bridgeTokenSide: "borrow",
68796
+ abortSignal: bridgeOpts?.abortSignal,
68797
+ buildBundleThroughBridge: async (bridgeBank) => {
68798
+ const bridgeTokenProgram = await resolveTokenProgramForMint(
68799
+ bridgeBank.mint,
68800
+ params.connection,
68801
+ tokenProgramCache
68802
+ );
68803
+ const firstLeg = await makeSwapDebtTx({
68804
+ ...sharedBridgeLegContext(params),
68805
+ repayOpts: {
68806
+ totalPositionAmount: params.repayOpts.totalPositionAmount,
68807
+ repayAmount,
68808
+ repayBank: sourceBank,
68809
+ tokenProgram: params.repayOpts.tokenProgram,
68810
+ marketPrice: oraclePriceOf(sourceBank)
68811
+ },
68812
+ borrowOpts: {
68813
+ borrowBank: bridgeBank,
68814
+ tokenProgram: bridgeTokenProgram,
68815
+ marketPrice: oraclePriceOf(bridgeBank)
68816
+ }
68817
+ });
68818
+ if (!firstLeg.quoteResponse) return null;
68819
+ const bridgeBorrowedUi = nativeToUi(firstLeg.quoteResponse.inAmount, bridgeBank.mintDecimals);
68820
+ if (bridgeBorrowedUi <= 0) return null;
68821
+ const result = await composeBridgedSwap({
68822
+ firstLeg,
68823
+ buildSecondLeg: (projectedAccount) => makeSwapDebtTx({
68824
+ ...sharedBridgeLegContext(params),
68825
+ marginfiAccount: projectedAccount,
68826
+ repayOpts: {
68827
+ totalPositionAmount: bridgeBorrowedUi,
68828
+ repayAmount: bridgeBorrowedUi,
68829
+ repayBank: bridgeBank,
68830
+ tokenProgram: bridgeTokenProgram,
68831
+ marketPrice: oraclePriceOf(bridgeBank)
68832
+ },
68833
+ borrowOpts: {
68834
+ borrowBank: destinationBank,
68835
+ tokenProgram: params.borrowOpts.tokenProgram,
68836
+ marketPrice: oraclePriceOf(destinationBank)
68837
+ }
68838
+ }),
68839
+ marginfiAccount: params.marginfiAccount,
68840
+ program: params.program,
68841
+ banksMap: params.bankMap,
68842
+ assetShareValueMultiplierByBank: params.assetShareValueMultiplierByBank,
68843
+ feePayer: params.overrideInferAccounts?.authority ?? params.marginfiAccount.authority,
68844
+ maxBundleTxs: bridgeOpts?.maxBundleTxs
68845
+ });
68846
+ if (!result) return null;
68847
+ return {
68848
+ transactions: result.transactions,
68849
+ actionTxIndex: result.transactions.length - 1,
68850
+ quoteResponse: mergeBridgeQuotesDebt(result.firstLegQuote, result.secondLegQuote),
68851
+ bridgeMint: bridgeBank.mint
68852
+ };
68853
+ }
68854
+ });
68855
+ }
68856
+
68857
+ // src/services/account/actions/loop.ts
68858
+ async function makeLoopTx(params) {
68859
+ const {
68860
+ program,
68861
+ marginfiAccount,
68862
+ bankMap,
68863
+ depositOpts,
68864
+ borrowOpts,
68865
+ bankMetadataMap,
68866
+ addressLookupTableAccounts,
68867
+ connection,
68868
+ oraclePrices,
68869
+ crossbarUrl,
68870
+ additionalIxs = []
68871
+ } = params;
68872
+ const blockhash = (await connection.getLatestBlockhash("confirmed")).blockhash;
68873
+ const setupIxs = await makeSetupIx({
68874
+ connection,
68875
+ authority: marginfiAccount.authority,
68876
+ tokens: [
68877
+ {
68878
+ mint: borrowOpts.borrowBank.mint,
68879
+ tokenProgram: borrowOpts.tokenProgram
68880
+ },
68881
+ {
68882
+ mint: depositOpts.depositBank.mint,
68883
+ tokenProgram: depositOpts.tokenProgram
68884
+ }
68885
+ ]
68886
+ });
68887
+ const refreshIntegrationIxs = makeRefreshIntegrationBanksIxs(
68888
+ params.marginfiAccount,
68889
+ params.bankMap,
68890
+ [depositOpts.depositBank.address],
68891
+ params.bankMetadataMap,
68892
+ [borrowOpts.borrowBank.address, depositOpts.depositBank.address]
68893
+ );
68894
+ const { flashloanTx, setupInstructions, swapQuote, depositIxs, borrowIxs } = await buildLoopFlashloanTx({
68895
+ ...params,
68896
+ blockhash
68897
+ });
68898
+ const jupiterSetupInstructions = setupInstructions.filter((ix) => {
68899
+ if (ix.programId.equals(web3_js.ComputeBudgetProgram.programId)) {
68900
+ return false;
68901
+ }
68902
+ if (ix.programId.equals(ASSOCIATED_TOKEN_PROGRAM_ID)) {
68903
+ const mintKey = ix.keys[3]?.pubkey;
68904
+ if (mintKey?.equals(depositOpts.depositBank.mint) || mintKey?.equals(borrowOpts.borrowBank.mint)) {
68905
+ return false;
68906
+ }
68907
+ }
68908
+ return true;
68909
+ });
68910
+ setupIxs.push(...jupiterSetupInstructions);
68911
+ const { instructions: updateFeedIxs, luts: feedLuts } = await makeSmartCrankSwbFeedIx({
68912
+ marginfiAccount,
68913
+ bankMap,
68914
+ oraclePrices,
68915
+ assetShareValueMultiplierByBank: params.assetShareValueMultiplierByBank,
68916
+ instructions: [...borrowIxs.instructions, ...depositIxs.instructions],
68917
+ program,
68918
+ connection,
68919
+ crossbarUrl
68920
+ });
68921
+ let additionalTxs = [];
68922
+ if (depositOpts.depositBank.mint.equals(NATIVE_MINT) && depositOpts.inputDepositAmount) {
68923
+ setupIxs.push(
68924
+ ...makeWrapSolIxs(marginfiAccount.authority, new BigNumber3.BigNumber(depositOpts.inputDepositAmount))
68925
+ );
68926
+ }
68927
+ if (setupIxs.length > 0 || additionalIxs.length > 0 || refreshIntegrationIxs.instructions.length > 0) {
68928
+ const ixs = [...additionalIxs, ...setupIxs, ...refreshIntegrationIxs.instructions];
68929
+ const txs = splitInstructionsToFitTransactions([], ixs, {
68930
+ blockhash,
68931
+ payerKey: marginfiAccount.authority,
68932
+ luts: addressLookupTableAccounts ?? []
68933
+ });
68934
+ additionalTxs.push(
68935
+ ...txs.map(
68936
+ (tx) => addTransactionMetadata(tx, {
68937
+ type: "CREATE_ATA" /* CREATE_ATA */,
68938
+ addressLookupTables: addressLookupTableAccounts
68939
+ })
68940
+ )
68941
+ );
68942
+ }
68943
+ if (updateFeedIxs.length > 0) {
68944
+ const message = new web3_js.TransactionMessage({
68945
+ payerKey: marginfiAccount.authority,
68946
+ recentBlockhash: blockhash,
68947
+ instructions: updateFeedIxs
68948
+ }).compileToV0Message(feedLuts);
68949
+ additionalTxs.push(
68950
+ addTransactionMetadata(new web3_js.VersionedTransaction(message), {
68951
+ addressLookupTables: feedLuts,
68952
+ type: "CRANK" /* CRANK */
68953
+ })
68954
+ );
68955
+ }
68956
+ const transactions = [...additionalTxs, flashloanTx];
68957
+ return {
68958
+ transactions,
68959
+ actionTxIndex: transactions.length - 1,
68960
+ quoteResponse: swapQuote
68961
+ };
68962
+ }
68963
+ async function buildLoopFlashloanTx(params) {
68964
+ const { depositOpts } = params;
68965
+ const { descriptor, swapNeeded, borrowIxs, depositIxs } = await buildLoopNonSwapIxs(params);
68966
+ if (!swapNeeded) {
68967
+ const flashloanTx2 = await finalizeLoopFlashloanTx({
68968
+ params,
68969
+ innerIxs: descriptor.innerIxs,
68970
+ luts: descriptor.luts,
68971
+ swapIxCount: 0,
68972
+ swapLutCount: 0,
68973
+ sizeConstraint: descriptor.sizeConstraint
69490
68974
  });
68975
+ return {
68976
+ flashloanTx: flashloanTx2,
68977
+ setupInstructions: [],
68978
+ swapQuote: void 0,
68979
+ borrowIxs,
68980
+ depositIxs
68981
+ };
69491
68982
  }
69492
- /**
69493
- * Creates a transaction to roll a matured Exponent PT collateral position into its
69494
- * next-maturity PT, in one flash-loan-wrapped bundle
69495
- * (withdraw PT_old `wrapper_merge` to base swap-engine buy PT_new → deposit).
69496
- * See {@link makeRollPtTx}.
69497
- *
69498
- * @param params - Roll-PT parameters (`withdrawOpts`/`depositOpts`/`swapOpts`/`rollOpts`).
69499
- */
69500
- async makeRollPtTx(params) {
69501
- return makeRollPtTx({
69502
- ...params,
69503
- marginfiAccount: this,
69504
- overrideInferAccounts: {
69505
- authority: this.authority,
69506
- group: this.group,
69507
- ...params.overrideInferAccounts
69508
- }
68983
+ const engineResult = await runLoopSwapEngine(descriptor, params);
68984
+ const finalIxs = [...descriptor.innerIxs];
68985
+ finalIxs.splice(descriptor.swapSlotIndex, 0, ...engineResult.swapInstructions);
68986
+ const principalNative = depositOpts.loopMode === "DEPOSIT" ? uiToNative(depositOpts.inputDepositAmount, depositOpts.depositBank.mintDecimals) : new BN9__default.default(0);
68987
+ const finalDepositNative = engineResult.outputAmountNative.add(principalNative);
68988
+ const depositIxPosition = descriptor.depositIxIndex + engineResult.swapInstructions.length;
68989
+ patchDepositAmount(finalIxs[depositIxPosition], finalDepositNative);
68990
+ const luts = [...descriptor.luts, ...engineResult.swapLuts];
68991
+ const flashloanTx = await finalizeLoopFlashloanTx({
68992
+ params,
68993
+ innerIxs: finalIxs,
68994
+ luts,
68995
+ swapIxCount: engineResult.swapInstructions.length,
68996
+ swapLutCount: engineResult.swapLuts.length,
68997
+ sizeConstraint: descriptor.sizeConstraint
68998
+ });
68999
+ return {
69000
+ flashloanTx,
69001
+ setupInstructions: engineResult.setupInstructions,
69002
+ swapQuote: engineResult.quoteResponse,
69003
+ borrowIxs,
69004
+ depositIxs
69005
+ };
69006
+ }
69007
+ async function buildLoopNonSwapIxs(params) {
69008
+ const {
69009
+ program,
69010
+ marginfiAccount,
69011
+ bankMap,
69012
+ borrowOpts,
69013
+ depositOpts,
69014
+ bankMetadataMap,
69015
+ addressLookupTableAccounts,
69016
+ overrideInferAccounts
69017
+ } = params;
69018
+ const cuRequestIxs = [
69019
+ web3_js.ComputeBudgetProgram.setComputeUnitLimit({ units: 12e5 }),
69020
+ web3_js.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1 })
69021
+ ];
69022
+ const swapNeeded = !depositOpts.depositBank.mint.equals(borrowOpts.borrowBank.mint);
69023
+ const destinationTokenAccount = getAssociatedTokenAddressSync(
69024
+ new web3_js.PublicKey(depositOpts.depositBank.mint),
69025
+ marginfiAccount.authority,
69026
+ true,
69027
+ depositOpts.tokenProgram.equals(TOKEN_2022_PROGRAM_ID) ? TOKEN_2022_PROGRAM_ID : void 0
69028
+ );
69029
+ const principalUi = depositOpts.loopMode === "DEPOSIT" ? depositOpts.inputDepositAmount : 0;
69030
+ let depositAmountUi;
69031
+ let sizeConstraint = 0;
69032
+ let maxSwapTotalAccounts = 0;
69033
+ if (!swapNeeded) {
69034
+ depositAmountUi = borrowOpts.borrowAmount + principalUi;
69035
+ } else {
69036
+ const swapConstraints = await computeFlashloanSwapConstraints({
69037
+ program,
69038
+ marginfiAccount,
69039
+ bankMap,
69040
+ bankMetadataMap,
69041
+ addressLookupTableAccounts: addressLookupTableAccounts ?? [],
69042
+ primaryIx: {
69043
+ type: "borrow",
69044
+ bank: borrowOpts.borrowBank,
69045
+ tokenProgram: borrowOpts.tokenProgram
69046
+ },
69047
+ secondaryIx: {
69048
+ type: "deposit",
69049
+ bank: depositOpts.depositBank,
69050
+ tokenProgram: depositOpts.tokenProgram
69051
+ },
69052
+ overrideInferAccounts
69509
69053
  });
69054
+ sizeConstraint = swapConstraints.sizeConstraint;
69055
+ maxSwapTotalAccounts = swapConstraints.maxSwapTotalAccounts;
69056
+ const estimateUi = borrowOpts.borrowAmount * borrowOpts.marketPrice / depositOpts.marketPrice;
69057
+ depositAmountUi = estimateUi + principalUi;
69510
69058
  }
69511
- /**
69512
- * Creates a transaction to swap one debt position to another using a flash loan.
69513
- *
69514
- * A swap debt transaction:
69515
- * 1. Borrows new asset via flash loan (new debt)
69516
- * 2. Swaps new asset to old debt asset (via Jupiter)
69517
- * 3. Repays old debt with swapped assets
69518
- *
69519
- * This allows users to change their debt type (e.g., USDC debt -> SOL debt) without
69520
- * repaying and affecting their health during the swap.
69521
- *
69522
- * @param params - Swap debt transaction parameters
69523
- * @param params.connection - Solana connection instance
69524
- * @param params.oraclePrices - Map of current oracle prices
69525
- * @param params.repayOpts - Repay configuration (bank, amount, tokenProgram)
69526
- * @param params.borrowOpts - Borrow configuration (bank, tokenProgram)
69527
- * @param params.swapOpts - Swap configuration (venue, slippage, fees)
69528
- * @param params.addressLookupTableAccounts - Address lookup tables
69529
- * @param params.overrideInferAccounts - Optional account overrides
69530
- * @param params.additionalIxs - Additional instructions to include
69531
- * @param params.crossbarUrl - Crossbar URL for oracle updates
69532
- *
69533
- * @returns Object containing transactions array, action index, and swap quote
69534
- *
69535
- * @throws {TransactionBuildingError} If swap exceeds transaction size limits
69536
- *
69537
- * @see {@link makeSwapDebtTx} for detailed implementation
69538
- */
69539
- async makeSwapDebtTx(params) {
69540
- return makeSwapDebtTx({
69541
- ...params,
69542
- marginfiAccount: this,
69543
- overrideInferAccounts: {
69544
- authority: this.authority,
69545
- group: this.group,
69546
- ...params.overrideInferAccounts
69059
+ const borrowIxs = await makeBorrowIx3({
69060
+ program,
69061
+ bank: borrowOpts.borrowBank,
69062
+ bankMap,
69063
+ tokenProgram: borrowOpts.tokenProgram,
69064
+ amount: borrowOpts.borrowAmount,
69065
+ marginfiAccount,
69066
+ authority: marginfiAccount.authority,
69067
+ isSync: false,
69068
+ opts: {
69069
+ createAtas: false,
69070
+ wrapAndUnwrapSol: false,
69071
+ overrideInferAccounts
69072
+ }
69073
+ });
69074
+ const depositIxs = await buildDepositIxs(params, depositAmountUi);
69075
+ const innerIxsBeforeSwap = [...cuRequestIxs, ...borrowIxs.instructions];
69076
+ const swapSlotIndex = innerIxsBeforeSwap.length;
69077
+ const innerIxs = [...innerIxsBeforeSwap, ...depositIxs.instructions];
69078
+ const depositIxIndex = innerIxs.findIndex(isDepositIx);
69079
+ if (depositIxIndex < 0) {
69080
+ throw new Error(
69081
+ "buildLoopNonSwapIxs: could not locate deposit instruction for amount patching"
69082
+ );
69083
+ }
69084
+ const descriptor = {
69085
+ innerIxs,
69086
+ swapSlotIndex,
69087
+ depositIxIndex,
69088
+ inputMint: borrowOpts.borrowBank.mint.toBase58(),
69089
+ outputMint: depositOpts.depositBank.mint.toBase58(),
69090
+ inputDecimals: borrowOpts.borrowBank.mintDecimals,
69091
+ outputDecimals: depositOpts.depositBank.mintDecimals,
69092
+ inAmountNative: uiToNative(
69093
+ borrowOpts.borrowAmount,
69094
+ borrowOpts.borrowBank.mintDecimals
69095
+ ).toNumber(),
69096
+ destinationTokenAccount,
69097
+ sizeConstraint,
69098
+ maxSwapTotalAccounts,
69099
+ luts: addressLookupTableAccounts ?? []
69100
+ };
69101
+ return { descriptor, swapNeeded, borrowIxs, depositIxs };
69102
+ }
69103
+ async function buildDepositIxs(params, amountUi) {
69104
+ const { program, marginfiAccount, depositOpts, bankMetadataMap, overrideInferAccounts } = params;
69105
+ switch (depositOpts.depositBank.config.assetTag) {
69106
+ case 3 /* KAMINO */: {
69107
+ const reserve = bankMetadataMap[depositOpts.depositBank.address.toBase58()]?.kaminoStates?.reserveState;
69108
+ if (!reserve) {
69109
+ throw TransactionBuildingError.kaminoReserveNotFound(
69110
+ depositOpts.depositBank.address.toBase58(),
69111
+ depositOpts.depositBank.mint.toBase58(),
69112
+ depositOpts.depositBank.tokenSymbol
69113
+ );
69547
69114
  }
69548
- });
69115
+ return makeKaminoDepositIx3({
69116
+ program,
69117
+ bank: depositOpts.depositBank,
69118
+ tokenProgram: depositOpts.tokenProgram,
69119
+ amount: amountUi,
69120
+ accountAddress: marginfiAccount.address,
69121
+ authority: marginfiAccount.authority,
69122
+ group: marginfiAccount.group,
69123
+ reserve,
69124
+ opts: {
69125
+ wrapAndUnwrapSol: false,
69126
+ overrideInferAccounts
69127
+ }
69128
+ });
69129
+ }
69130
+ case 4 /* DRIFT */: {
69131
+ const driftState = bankMetadataMap[depositOpts.depositBank.address.toBase58()]?.driftStates;
69132
+ if (!driftState) {
69133
+ throw TransactionBuildingError.driftStateNotFound(
69134
+ depositOpts.depositBank.address.toBase58(),
69135
+ depositOpts.depositBank.mint.toBase58(),
69136
+ depositOpts.depositBank.tokenSymbol
69137
+ );
69138
+ }
69139
+ return makeDriftDepositIx3({
69140
+ program,
69141
+ bank: depositOpts.depositBank,
69142
+ tokenProgram: depositOpts.tokenProgram,
69143
+ amount: amountUi,
69144
+ accountAddress: marginfiAccount.address,
69145
+ authority: marginfiAccount.authority,
69146
+ group: marginfiAccount.group,
69147
+ driftMarketIndex: driftState.spotMarketState.marketIndex,
69148
+ driftOracle: driftState.spotMarketState.oracle,
69149
+ opts: {
69150
+ wrapAndUnwrapSol: false,
69151
+ overrideInferAccounts
69152
+ }
69153
+ });
69154
+ }
69155
+ case 6 /* JUPLEND */: {
69156
+ return makeJuplendDepositIx2({
69157
+ program,
69158
+ bank: depositOpts.depositBank,
69159
+ tokenProgram: depositOpts.tokenProgram,
69160
+ amount: amountUi,
69161
+ accountAddress: marginfiAccount.address,
69162
+ authority: marginfiAccount.authority,
69163
+ group: marginfiAccount.group,
69164
+ opts: {
69165
+ wrapAndUnwrapSol: false,
69166
+ overrideInferAccounts
69167
+ }
69168
+ });
69169
+ }
69170
+ default: {
69171
+ return makeDepositIx3({
69172
+ program,
69173
+ bank: depositOpts.depositBank,
69174
+ tokenProgram: depositOpts.tokenProgram,
69175
+ amount: amountUi,
69176
+ accountAddress: marginfiAccount.address,
69177
+ authority: marginfiAccount.authority,
69178
+ group: marginfiAccount.group,
69179
+ opts: {
69180
+ wrapAndUnwrapSol: false,
69181
+ overrideInferAccounts
69182
+ }
69183
+ });
69184
+ }
69549
69185
  }
69550
- /**
69551
- * Creates a deposit transaction.
69552
- *
69553
- * @param params - Parameters for the deposit transaction
69554
- * @returns Promise resolving to an ExtendedTransaction
69555
- *
69556
- * @see {@link makeDepositTx} for detailed implementation
69557
- */
69558
- async makeDepositTx(params) {
69559
- return makeDepositTx({
69560
- ...params,
69561
- accountAddress: this.address,
69562
- authority: this.authority,
69563
- group: this.group
69564
- });
69186
+ }
69187
+ async function runLoopSwapEngine(descriptor, params) {
69188
+ const { connection, swapOpts, marginfiAccount, swapEngineRunner } = params;
69189
+ if (swapOpts.swapIxs) {
69190
+ return {
69191
+ swapInstructions: swapOpts.swapIxs.instructions,
69192
+ setupInstructions: [],
69193
+ swapLuts: swapOpts.swapIxs.lookupTables,
69194
+ quoteResponse: {
69195
+ inAmount: String(descriptor.inAmountNative),
69196
+ outAmount: "0",
69197
+ otherAmountThreshold: "0",
69198
+ slippageBps: 0
69199
+ },
69200
+ outputAmountNative: new BN9__default.default(0)
69201
+ };
69565
69202
  }
69566
- /**
69567
- * Creates a Drift deposit transaction.
69568
- *
69569
- * @param params - Parameters for the Drift deposit transaction
69570
- * @returns Promise resolving to an ExtendedV0Transaction
69571
- *
69572
- * @see {@link makeDriftDepositTx} for detailed implementation
69573
- */
69574
- async makeDriftDepositTx(params) {
69575
- return makeDriftDepositTx({
69576
- ...params,
69577
- accountAddress: this.address,
69578
- authority: this.authority,
69579
- group: this.group
69203
+ const runEngine = swapEngineRunner ?? runSwapEngine;
69204
+ const engineResult = await runEngine({
69205
+ inputMint: descriptor.inputMint,
69206
+ outputMint: descriptor.outputMint,
69207
+ amountNative: descriptor.inAmountNative,
69208
+ inputDecimals: descriptor.inputDecimals,
69209
+ outputDecimals: descriptor.outputDecimals,
69210
+ ...swapEngineQuoteFieldsFromOpts(swapOpts),
69211
+ taker: marginfiAccount.authority,
69212
+ destinationTokenAccount: descriptor.destinationTokenAccount,
69213
+ connection,
69214
+ footprint: {
69215
+ instructions: descriptor.innerIxs,
69216
+ luts: descriptor.luts,
69217
+ payer: marginfiAccount.authority,
69218
+ sizeConstraint: descriptor.sizeConstraint,
69219
+ maxSwapTotalAccounts: descriptor.maxSwapTotalAccounts
69220
+ },
69221
+ providers: swapEngineProvidersFromOpts(swapOpts)
69222
+ });
69223
+ return {
69224
+ swapInstructions: engineResult.swapInstructions,
69225
+ setupInstructions: engineResult.setupInstructions,
69226
+ swapLuts: engineResult.swapLuts,
69227
+ quoteResponse: engineResult.quoteResponse,
69228
+ outputAmountNative: engineResult.outputAmountNative
69229
+ };
69230
+ }
69231
+ async function finalizeLoopFlashloanTx({
69232
+ params,
69233
+ innerIxs,
69234
+ luts,
69235
+ swapIxCount,
69236
+ swapLutCount,
69237
+ sizeConstraint
69238
+ }) {
69239
+ const { program, marginfiAccount, bankMap, swapOpts, blockhash } = params;
69240
+ if (swapIxCount > 0) {
69241
+ compileFlashloanPrecheck({
69242
+ allIxs: innerIxs,
69243
+ payer: marginfiAccount.authority,
69244
+ luts,
69245
+ sizeConstraint,
69246
+ swapIxCount,
69247
+ swapLutCount
69580
69248
  });
69581
69249
  }
69582
- /**
69583
- * Creates a Kamino deposit transaction.
69584
- *
69585
- * @param params - Parameters for the Kamino deposit transaction
69586
- * @returns Promise resolving to an ExtendedV0Transaction
69587
- *
69588
- * @see {@link makeKaminoDepositTx} for detailed implementation
69589
- */
69590
- async makeKaminoDepositTx(params) {
69591
- return makeKaminoDepositTx({
69592
- ...params,
69593
- accountAddress: this.address,
69594
- authority: this.authority,
69595
- group: this.group
69596
- });
69250
+ const flashloanTx = await makeFlashLoanTx({
69251
+ program,
69252
+ marginfiAccount,
69253
+ bankMap,
69254
+ addressLookupTableAccounts: luts,
69255
+ blockhash,
69256
+ ixs: innerIxs
69257
+ });
69258
+ const txSize = getTxSize(flashloanTx);
69259
+ const totalKeys = getTotalAccountKeys(flashloanTx);
69260
+ if (txSize > MAX_TX_SIZE || totalKeys > MAX_ACCOUNT_LOCKS) {
69261
+ throw TransactionBuildingError.swapSizeExceededLoop(
69262
+ txSize,
69263
+ totalKeys,
69264
+ swapOpts.swapConfig?.provider
69265
+ );
69597
69266
  }
69598
- /**
69599
- * Creates a borrow transaction.
69600
- *
69601
- * @param params - Parameters for the borrow transaction
69602
- * @returns Promise resolving to a TransactionBuilderResult
69603
- *
69604
- * @see {@link makeBorrowTx} for detailed implementation
69605
- */
69606
- async makeBorrowTx(params) {
69607
- return makeBorrowTx({
69608
- ...params,
69609
- marginfiAccount: this,
69610
- opts: {
69611
- ...params.opts,
69612
- overrideInferAccounts: {
69613
- authority: this.authority,
69614
- ...params.opts?.overrideInferAccounts
69267
+ return flashloanTx;
69268
+ }
69269
+ async function makeBridgedLoopTx(params) {
69270
+ const { bridgeOpts, ...loopParams } = params;
69271
+ try {
69272
+ return await makeLoopTx(loopParams);
69273
+ } catch (directError) {
69274
+ if (!isDecomposableSwapError(directError)) throw directError;
69275
+ const bridged = await tryBridgedLoop(loopParams, bridgeOpts);
69276
+ if (bridged) return bridged;
69277
+ throw directError;
69278
+ }
69279
+ }
69280
+ async function tryBridgedLoop(params, bridgeOpts) {
69281
+ const { depositBank } = params.depositOpts;
69282
+ const { borrowBank } = params.borrowOpts;
69283
+ const { usableBridgeBanks, conflictingBridgeBanks } = selectSwapBridges({
69284
+ sourceMint: depositBank.mint,
69285
+ destinationMint: borrowBank.mint,
69286
+ bankMap: params.bankMap,
69287
+ marginfiAccount: params.marginfiAccount,
69288
+ bridgeTokenSide: "borrow",
69289
+ bridgeCandidateMints: bridgeOpts?.bridgeCandidateMints
69290
+ });
69291
+ const oraclePriceOf = (bank) => params.oraclePrices.get(bank.address.toBase58())?.priceRealtime.price.toNumber() ?? 0;
69292
+ const borrowBankPrice = oraclePriceOf(borrowBank);
69293
+ if (borrowBankPrice <= 0) return null;
69294
+ const tokenProgramCache = new Map(bridgeOpts?.tokenProgramByMint);
69295
+ return tryBridgeCandidates({
69296
+ usableBridgeBanks,
69297
+ conflictingBridgeBanks,
69298
+ bridgeTokenSide: "borrow",
69299
+ abortSignal: bridgeOpts?.abortSignal,
69300
+ buildBundleThroughBridge: async (bridgeBank) => {
69301
+ const bridgeBankPrice = oraclePriceOf(bridgeBank);
69302
+ if (bridgeBankPrice <= 0) return null;
69303
+ const bridgeBorrowUi = params.borrowOpts.borrowAmount * borrowBankPrice / bridgeBankPrice;
69304
+ if (bridgeBorrowUi <= 0) return null;
69305
+ const bridgeTokenProgram = await resolveTokenProgramForMint(
69306
+ bridgeBank.mint,
69307
+ params.connection,
69308
+ tokenProgramCache
69309
+ );
69310
+ const firstLeg = await makeLoopTx({
69311
+ ...params,
69312
+ depositOpts: {
69313
+ ...params.depositOpts,
69314
+ marketPrice: oraclePriceOf(depositBank)
69315
+ },
69316
+ borrowOpts: {
69317
+ borrowAmount: bridgeBorrowUi,
69318
+ borrowBank: bridgeBank,
69319
+ tokenProgram: bridgeTokenProgram,
69320
+ marketPrice: bridgeBankPrice
69615
69321
  }
69322
+ });
69323
+ if (!firstLeg.quoteResponse) return null;
69324
+ const result = await composeBridgedSwap({
69325
+ firstLeg,
69326
+ // Second leg: debt-swap the bridge debt → X (repay exactly the bridge the first leg
69327
+ // borrowed — exact, so no slippage residual; repay-all clears it — and borrow X).
69328
+ buildSecondLeg: (projectedAccount) => makeSwapDebtTx({
69329
+ ...sharedBridgeLegContext(params),
69330
+ marginfiAccount: projectedAccount,
69331
+ repayOpts: {
69332
+ totalPositionAmount: bridgeBorrowUi,
69333
+ repayAmount: bridgeBorrowUi,
69334
+ repayBank: bridgeBank,
69335
+ tokenProgram: bridgeTokenProgram,
69336
+ marketPrice: bridgeBankPrice
69337
+ },
69338
+ borrowOpts: {
69339
+ borrowBank,
69340
+ tokenProgram: params.borrowOpts.tokenProgram,
69341
+ marketPrice: borrowBankPrice
69342
+ }
69343
+ }),
69344
+ marginfiAccount: params.marginfiAccount,
69345
+ program: params.program,
69346
+ banksMap: params.bankMap,
69347
+ assetShareValueMultiplierByBank: params.assetShareValueMultiplierByBank,
69348
+ feePayer: params.overrideInferAccounts?.authority ?? params.marginfiAccount.authority,
69349
+ maxBundleTxs: bridgeOpts?.maxBundleTxs
69350
+ });
69351
+ if (!result) return null;
69352
+ return {
69353
+ transactions: result.transactions,
69354
+ actionTxIndex: result.transactions.length - 1,
69355
+ quoteResponse: mergeBridgeQuotesLoop(result.firstLegQuote, result.secondLegQuote),
69356
+ bridgeMint: bridgeBank.mint
69357
+ };
69358
+ }
69359
+ });
69360
+ }
69361
+ async function makeSwapCollateralTx(params) {
69362
+ const {
69363
+ program,
69364
+ marginfiAccount,
69365
+ connection,
69366
+ bankMap,
69367
+ oraclePrices,
69368
+ withdrawOpts,
69369
+ depositOpts,
69370
+ bankMetadataMap,
69371
+ assetShareValueMultiplierByBank,
69372
+ addressLookupTableAccounts,
69373
+ crossbarUrl,
69374
+ additionalIxs = []
69375
+ } = params;
69376
+ const blockhash = (await connection.getLatestBlockhash("confirmed")).blockhash;
69377
+ const setupIxs = await makeSetupIx({
69378
+ connection,
69379
+ authority: marginfiAccount.authority,
69380
+ tokens: [
69381
+ { mint: withdrawOpts.withdrawBank.mint, tokenProgram: withdrawOpts.tokenProgram },
69382
+ { mint: depositOpts.depositBank.mint, tokenProgram: depositOpts.tokenProgram }
69383
+ ]
69384
+ });
69385
+ const refreshIntegrationIxs = makeRefreshIntegrationBanksIxs(
69386
+ marginfiAccount,
69387
+ bankMap,
69388
+ [withdrawOpts.withdrawBank.address, depositOpts.depositBank.address],
69389
+ bankMetadataMap
69390
+ );
69391
+ const { flashloanTx, setupInstructions, swapQuote, withdrawIxs, depositIxs } = await buildSwapCollateralFlashloanTx({
69392
+ ...params,
69393
+ blockhash
69394
+ });
69395
+ const jupiterSetupInstructions = setupInstructions.filter((ix) => {
69396
+ if (ix.programId.equals(web3_js.ComputeBudgetProgram.programId)) {
69397
+ return false;
69398
+ }
69399
+ if (ix.programId.equals(ASSOCIATED_TOKEN_PROGRAM_ID)) {
69400
+ const mintKey = ix.keys[3]?.pubkey;
69401
+ if (mintKey?.equals(withdrawOpts.withdrawBank.mint) || mintKey?.equals(depositOpts.depositBank.mint)) {
69402
+ return false;
69616
69403
  }
69404
+ }
69405
+ return true;
69406
+ });
69407
+ setupIxs.push(...jupiterSetupInstructions);
69408
+ const { instructions: updateFeedIxs, luts: feedLuts } = await makeSmartCrankSwbFeedIx({
69409
+ marginfiAccount,
69410
+ bankMap,
69411
+ oraclePrices,
69412
+ assetShareValueMultiplierByBank,
69413
+ instructions: [...withdrawIxs.instructions, ...depositIxs.instructions],
69414
+ program,
69415
+ connection,
69416
+ crossbarUrl
69417
+ });
69418
+ let additionalTxs = [];
69419
+ if (setupIxs.length > 0 || refreshIntegrationIxs.instructions.length > 0) {
69420
+ const ixs = [...setupIxs, ...refreshIntegrationIxs.instructions];
69421
+ const txs = splitInstructionsToFitTransactions([], ixs, {
69422
+ blockhash,
69423
+ payerKey: marginfiAccount.authority,
69424
+ luts: addressLookupTableAccounts ?? []
69617
69425
  });
69426
+ additionalTxs.push(
69427
+ ...txs.map(
69428
+ (tx) => addTransactionMetadata(tx, {
69429
+ type: "CREATE_ATA" /* CREATE_ATA */,
69430
+ addressLookupTables: addressLookupTableAccounts
69431
+ })
69432
+ )
69433
+ );
69618
69434
  }
69619
- /**
69620
- * Creates a repay transaction.
69621
- *
69622
- * @param params - Parameters for the repay transaction
69623
- * @returns Promise resolving to an ExtendedTransaction
69624
- *
69625
- * @see {@link makeRepayTx} for detailed implementation
69626
- */
69627
- async makeRepayTx(params) {
69628
- return makeRepayTx({
69629
- ...params,
69630
- accountAddress: this.address,
69631
- authority: this.authority
69632
- });
69435
+ if (updateFeedIxs.length > 0) {
69436
+ const message = new web3_js.TransactionMessage({
69437
+ payerKey: marginfiAccount.authority,
69438
+ recentBlockhash: blockhash,
69439
+ instructions: updateFeedIxs
69440
+ }).compileToV0Message(feedLuts);
69441
+ additionalTxs.push(
69442
+ addTransactionMetadata(new web3_js.VersionedTransaction(message), {
69443
+ addressLookupTables: feedLuts,
69444
+ type: "CRANK" /* CRANK */
69445
+ })
69446
+ );
69633
69447
  }
69634
- /**
69635
- * Creates a withdraw transaction.
69636
- *
69637
- * @param params - Parameters for the withdraw transaction
69638
- * @returns Promise resolving to a TransactionBuilderResult
69639
- *
69640
- * @see {@link makeWithdrawTx} for detailed implementation
69641
- */
69642
- async makeWithdrawTx(params) {
69643
- return makeWithdrawTx({
69644
- ...params,
69645
- marginfiAccount: this,
69646
- opts: {
69647
- ...params.opts,
69648
- overrideInferAccounts: {
69649
- authority: this.authority,
69650
- group: this.group,
69651
- ...params.opts?.overrideInferAccounts
69448
+ const transactions = [...additionalTxs, flashloanTx];
69449
+ return {
69450
+ transactions,
69451
+ actionTxIndex: transactions.length - 1,
69452
+ quoteResponse: swapQuote
69453
+ };
69454
+ }
69455
+ async function buildSwapCollateralFlashloanTx({
69456
+ program,
69457
+ marginfiAccount,
69458
+ bankMap,
69459
+ withdrawOpts,
69460
+ depositOpts,
69461
+ swapOpts,
69462
+ bankMetadataMap,
69463
+ assetShareValueMultiplierByBank,
69464
+ addressLookupTableAccounts,
69465
+ connection,
69466
+ overrideInferAccounts,
69467
+ blockhash,
69468
+ swapEngineRunner
69469
+ }) {
69470
+ const {
69471
+ withdrawBank,
69472
+ tokenProgram: withdrawTokenProgram,
69473
+ totalPositionAmount,
69474
+ withdrawAmount
69475
+ } = withdrawOpts;
69476
+ const { depositBank, tokenProgram: depositTokenProgram } = depositOpts;
69477
+ if (withdrawAmount !== void 0 && withdrawAmount <= 0) {
69478
+ throw new Error("withdrawAmount must be greater than 0");
69479
+ }
69480
+ const actualWithdrawAmount = Math.min(withdrawAmount ?? totalPositionAmount, totalPositionAmount);
69481
+ const isFullWithdraw = isWholePosition(
69482
+ { amount: totalPositionAmount, isLending: true },
69483
+ actualWithdrawAmount,
69484
+ withdrawBank.mintDecimals
69485
+ );
69486
+ const cuRequestIxs = [
69487
+ web3_js.ComputeBudgetProgram.setComputeUnitLimit({ units: 12e5 }),
69488
+ web3_js.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1 })
69489
+ ];
69490
+ let amountToDeposit;
69491
+ let swapInstructions = [];
69492
+ let setupInstructions = [];
69493
+ let swapLookupTables = [];
69494
+ let swapQuote;
69495
+ let sizeConstraintUsed = 0;
69496
+ let withdrawIxs;
69497
+ switch (withdrawOpts.withdrawBank.config.assetTag) {
69498
+ case 3 /* KAMINO */: {
69499
+ const reserve = bankMetadataMap[withdrawOpts.withdrawBank.address.toBase58()]?.kaminoStates?.reserveState;
69500
+ if (!reserve) {
69501
+ throw TransactionBuildingError.kaminoReserveNotFound(
69502
+ withdrawOpts.withdrawBank.address.toBase58(),
69503
+ withdrawOpts.withdrawBank.mint.toBase58(),
69504
+ withdrawOpts.withdrawBank.tokenSymbol
69505
+ );
69506
+ }
69507
+ const multiplier = assetShareValueMultiplierByBank.get(withdrawOpts.withdrawBank.address.toBase58()) ?? new BigNumber3.BigNumber(1);
69508
+ const adjustedAmount = new BigNumber3.BigNumber(actualWithdrawAmount).div(multiplier).times(1.0001).toNumber();
69509
+ withdrawIxs = await makeKaminoWithdrawIx3({
69510
+ program,
69511
+ bank: withdrawBank,
69512
+ bankMap,
69513
+ tokenProgram: withdrawTokenProgram,
69514
+ cTokenAmount: adjustedAmount,
69515
+ marginfiAccount,
69516
+ authority: marginfiAccount.authority,
69517
+ reserve,
69518
+ withdrawAll: isFullWithdraw,
69519
+ isSync: false,
69520
+ opts: {
69521
+ createAtas: false,
69522
+ wrapAndUnwrapSol: false,
69523
+ overrideInferAccounts
69652
69524
  }
69525
+ });
69526
+ break;
69527
+ }
69528
+ case 4 /* DRIFT */: {
69529
+ const driftState = bankMetadataMap[withdrawOpts.withdrawBank.address.toBase58()]?.driftStates;
69530
+ if (!driftState) {
69531
+ throw TransactionBuildingError.driftStateNotFound(
69532
+ withdrawOpts.withdrawBank.address.toBase58(),
69533
+ withdrawOpts.withdrawBank.mint.toBase58(),
69534
+ withdrawOpts.withdrawBank.tokenSymbol
69535
+ );
69653
69536
  }
69654
- });
69655
- }
69656
- /**
69657
- * Creates a Drift withdraw transaction.
69658
- *
69659
- * @param params - Parameters for the Drift withdraw transaction
69660
- * @returns Promise resolving to an ExtendedV0Transaction
69661
- *
69662
- * @see {@link makeDriftWithdrawTx} for detailed implementation
69663
- */
69664
- async makeDriftWithdrawTx(params) {
69665
- return makeDriftWithdrawTx({
69666
- ...params,
69667
- marginfiAccount: this,
69668
- opts: {
69669
- ...params.opts,
69670
- overrideInferAccounts: {
69671
- authority: this.authority,
69672
- group: this.group,
69673
- ...params.opts?.overrideInferAccounts
69537
+ withdrawIxs = await makeDriftWithdrawIx3({
69538
+ program,
69539
+ bank: withdrawOpts.withdrawBank,
69540
+ bankMap,
69541
+ tokenProgram: withdrawOpts.tokenProgram,
69542
+ amount: actualWithdrawAmount,
69543
+ marginfiAccount,
69544
+ authority: marginfiAccount.authority,
69545
+ driftSpotMarket: driftState.spotMarketState,
69546
+ userRewards: driftState.userRewards,
69547
+ withdrawAll: isFullWithdraw,
69548
+ isSync: false,
69549
+ opts: {
69550
+ createAtas: false,
69551
+ wrapAndUnwrapSol: false,
69552
+ overrideInferAccounts
69674
69553
  }
69554
+ });
69555
+ break;
69556
+ }
69557
+ case 6 /* JUPLEND */: {
69558
+ const jupLendState = bankMetadataMap[withdrawOpts.withdrawBank.address.toBase58()]?.jupLendStates;
69559
+ if (!jupLendState) {
69560
+ throw TransactionBuildingError.jupLendStateNotFound(
69561
+ withdrawOpts.withdrawBank.address.toBase58(),
69562
+ withdrawOpts.withdrawBank.mint.toBase58(),
69563
+ withdrawOpts.withdrawBank.tokenSymbol
69564
+ );
69675
69565
  }
69676
- });
69566
+ withdrawIxs = await makeJuplendWithdrawIx2({
69567
+ program,
69568
+ bank: withdrawBank,
69569
+ bankMap,
69570
+ tokenProgram: withdrawTokenProgram,
69571
+ amount: actualWithdrawAmount,
69572
+ marginfiAccount,
69573
+ authority: marginfiAccount.authority,
69574
+ jupLendingState: jupLendState.jupLendingState,
69575
+ withdrawAll: isFullWithdraw,
69576
+ opts: {
69577
+ createAtas: false,
69578
+ wrapAndUnwrapSol: false,
69579
+ overrideInferAccounts
69580
+ }
69581
+ });
69582
+ break;
69583
+ }
69584
+ default: {
69585
+ withdrawIxs = await makeWithdrawIx3({
69586
+ program,
69587
+ bank: withdrawBank,
69588
+ bankMap,
69589
+ tokenProgram: withdrawTokenProgram,
69590
+ amount: actualWithdrawAmount,
69591
+ marginfiAccount,
69592
+ authority: marginfiAccount.authority,
69593
+ withdrawAll: isFullWithdraw,
69594
+ isSync: false,
69595
+ opts: {
69596
+ createAtas: false,
69597
+ wrapAndUnwrapSol: false,
69598
+ overrideInferAccounts
69599
+ }
69600
+ });
69601
+ break;
69602
+ }
69677
69603
  }
69678
- /**
69679
- * Creates a Kamino withdraw transaction.
69680
- *
69681
- * @param params - Parameters for the Kamino withdraw transaction
69682
- * @returns Promise resolving to a TransactionBuilderResult
69683
- *
69684
- * @see {@link makeKaminoWithdrawTx} for detailed implementation
69685
- */
69686
- async makeKaminoWithdrawTx(params) {
69687
- return makeKaminoWithdrawTx({
69688
- ...params,
69689
- marginfiAccount: this,
69690
- opts: {
69691
- ...params.opts,
69692
- overrideInferAccounts: {
69693
- authority: this.authority,
69694
- group: this.group,
69695
- ...params.opts?.overrideInferAccounts
69604
+ const swapNeeded = !depositBank.mint.equals(withdrawBank.mint);
69605
+ amountToDeposit = swapNeeded ? 0 : actualWithdrawAmount;
69606
+ let depositIxs;
69607
+ switch (depositBank.config.assetTag) {
69608
+ case 3 /* KAMINO */: {
69609
+ const reserve = bankMetadataMap[depositBank.address.toBase58()]?.kaminoStates?.reserveState;
69610
+ if (!reserve) {
69611
+ throw TransactionBuildingError.kaminoReserveNotFound(
69612
+ depositBank.address.toBase58(),
69613
+ depositBank.mint.toBase58(),
69614
+ depositBank.tokenSymbol
69615
+ );
69616
+ }
69617
+ depositIxs = await makeKaminoDepositIx3({
69618
+ program,
69619
+ bank: depositBank,
69620
+ tokenProgram: depositTokenProgram,
69621
+ amount: amountToDeposit,
69622
+ accountAddress: marginfiAccount.address,
69623
+ authority: marginfiAccount.authority,
69624
+ group: marginfiAccount.group,
69625
+ reserve,
69626
+ opts: {
69627
+ wrapAndUnwrapSol: false,
69628
+ overrideInferAccounts
69696
69629
  }
69630
+ });
69631
+ break;
69632
+ }
69633
+ case 4 /* DRIFT */: {
69634
+ const driftState = bankMetadataMap[depositBank.address.toBase58()]?.driftStates;
69635
+ if (!driftState) {
69636
+ throw TransactionBuildingError.driftStateNotFound(
69637
+ depositBank.address.toBase58(),
69638
+ depositBank.mint.toBase58(),
69639
+ depositBank.tokenSymbol
69640
+ );
69697
69641
  }
69642
+ const driftMarketIndex = driftState.spotMarketState.marketIndex;
69643
+ const driftOracle = driftState.spotMarketState.oracle;
69644
+ depositIxs = await makeDriftDepositIx3({
69645
+ program,
69646
+ bank: depositBank,
69647
+ tokenProgram: depositTokenProgram,
69648
+ amount: amountToDeposit,
69649
+ accountAddress: marginfiAccount.address,
69650
+ authority: marginfiAccount.authority,
69651
+ group: marginfiAccount.group,
69652
+ driftMarketIndex,
69653
+ driftOracle,
69654
+ opts: {
69655
+ wrapAndUnwrapSol: false,
69656
+ overrideInferAccounts
69657
+ }
69658
+ });
69659
+ break;
69660
+ }
69661
+ case 6 /* JUPLEND */: {
69662
+ depositIxs = await makeJuplendDepositIx2({
69663
+ program,
69664
+ bank: depositBank,
69665
+ tokenProgram: depositTokenProgram,
69666
+ amount: amountToDeposit,
69667
+ accountAddress: marginfiAccount.address,
69668
+ authority: marginfiAccount.authority,
69669
+ group: marginfiAccount.group,
69670
+ opts: {
69671
+ wrapAndUnwrapSol: false,
69672
+ overrideInferAccounts
69673
+ }
69674
+ });
69675
+ break;
69676
+ }
69677
+ default: {
69678
+ depositIxs = await makeDepositIx3({
69679
+ program,
69680
+ bank: depositBank,
69681
+ tokenProgram: depositTokenProgram,
69682
+ amount: amountToDeposit,
69683
+ accountAddress: marginfiAccount.address,
69684
+ authority: marginfiAccount.authority,
69685
+ group: marginfiAccount.group,
69686
+ opts: {
69687
+ wrapAndUnwrapSol: false,
69688
+ overrideInferAccounts
69689
+ }
69690
+ });
69691
+ break;
69692
+ }
69693
+ }
69694
+ if (swapNeeded) {
69695
+ const destinationTokenAccount = getAssociatedTokenAddressSync(
69696
+ depositBank.mint,
69697
+ marginfiAccount.authority,
69698
+ true,
69699
+ depositTokenProgram.equals(TOKEN_2022_PROGRAM_ID) ? TOKEN_2022_PROGRAM_ID : void 0
69700
+ );
69701
+ const swapConstraints = await computeFlashloanSwapConstraints({
69702
+ program,
69703
+ marginfiAccount,
69704
+ bankMap,
69705
+ bankMetadataMap,
69706
+ addressLookupTableAccounts: addressLookupTableAccounts ?? [],
69707
+ primaryIx: { type: "withdraw", bank: withdrawBank, tokenProgram: withdrawTokenProgram },
69708
+ secondaryIx: { type: "deposit", bank: depositBank, tokenProgram: depositTokenProgram },
69709
+ overrideInferAccounts
69710
+ });
69711
+ sizeConstraintUsed = swapConstraints.sizeConstraint;
69712
+ const runEngine = swapEngineRunner ?? runSwapEngine;
69713
+ const engineResult = await runEngine({
69714
+ inputMint: withdrawBank.mint.toBase58(),
69715
+ outputMint: depositBank.mint.toBase58(),
69716
+ amountNative: uiToNative(actualWithdrawAmount, withdrawBank.mintDecimals).toNumber(),
69717
+ inputDecimals: withdrawBank.mintDecimals,
69718
+ outputDecimals: depositBank.mintDecimals,
69719
+ ...swapEngineQuoteFieldsFromOpts(swapOpts),
69720
+ taker: marginfiAccount.authority,
69721
+ destinationTokenAccount,
69722
+ connection,
69723
+ footprint: {
69724
+ instructions: [...cuRequestIxs, ...withdrawIxs.instructions, ...depositIxs.instructions],
69725
+ luts: addressLookupTableAccounts ?? [],
69726
+ payer: marginfiAccount.authority,
69727
+ sizeConstraint: swapConstraints.sizeConstraint,
69728
+ maxSwapTotalAccounts: swapConstraints.maxSwapTotalAccounts
69729
+ },
69730
+ providers: swapEngineProvidersFromOpts(swapOpts)
69698
69731
  });
69732
+ const depositIxToPatch = depositIxs.instructions.find(isDepositIx);
69733
+ if (!depositIxToPatch) {
69734
+ throw new Error("swap-collateral: could not locate deposit instruction for amount patching");
69735
+ }
69736
+ patchDepositAmount(depositIxToPatch, engineResult.outputAmountNative);
69737
+ swapInstructions = engineResult.swapInstructions;
69738
+ setupInstructions = engineResult.setupInstructions;
69739
+ swapLookupTables = engineResult.swapLuts;
69740
+ swapQuote = engineResult.quoteResponse;
69699
69741
  }
69700
- /**
69701
- * Creates a flash loan transaction.
69702
- *
69703
- * @param params - Parameters for the flash loan transaction
69704
- * @returns Promise resolving to flash loan transaction details
69705
- *
69706
- * @see {@link makeFlashLoanTx} for detailed implementation
69707
- */
69708
- async makeFlashLoanTx(params) {
69709
- return makeFlashLoanTx({
69710
- ...params,
69711
- marginfiAccount: this
69742
+ const luts = [...addressLookupTableAccounts ?? [], ...swapLookupTables];
69743
+ const allNonFlIxs = [
69744
+ ...cuRequestIxs,
69745
+ ...withdrawIxs.instructions,
69746
+ ...swapInstructions,
69747
+ ...depositIxs.instructions
69748
+ ];
69749
+ if (swapInstructions.length > 0) {
69750
+ compileFlashloanPrecheck({
69751
+ allIxs: allNonFlIxs,
69752
+ payer: marginfiAccount.authority,
69753
+ luts,
69754
+ sizeConstraint: sizeConstraintUsed,
69755
+ swapIxCount: swapInstructions.length,
69756
+ swapLutCount: swapLookupTables.length
69757
+ });
69758
+ }
69759
+ const flashloanTx = await makeFlashLoanTx({
69760
+ program,
69761
+ marginfiAccount,
69762
+ bankMap,
69763
+ addressLookupTableAccounts: luts,
69764
+ blockhash,
69765
+ ixs: allNonFlIxs,
69766
+ isSync: false
69767
+ });
69768
+ const txSize = getTxSize(flashloanTx);
69769
+ const totalKeys = getTotalAccountKeys(flashloanTx);
69770
+ if (txSize > MAX_TX_SIZE || totalKeys > MAX_ACCOUNT_LOCKS) {
69771
+ throw TransactionBuildingError.swapSizeExceededPositionSwap(
69772
+ txSize,
69773
+ totalKeys,
69774
+ swapOpts.swapConfig?.provider
69775
+ );
69776
+ }
69777
+ return {
69778
+ flashloanTx,
69779
+ setupInstructions,
69780
+ swapQuote,
69781
+ withdrawIxs,
69782
+ depositIxs
69783
+ };
69784
+ }
69785
+ var SECOND_LEG_ROUNDING_HEADROOM_NATIVE = 10;
69786
+ async function makeBridgedSwapCollateralTx(params) {
69787
+ const { bridgeOpts, ...directParams } = params;
69788
+ try {
69789
+ return await makeSwapCollateralTx(directParams);
69790
+ } catch (directError) {
69791
+ if (!isDecomposableSwapError(directError)) throw directError;
69792
+ const bridged = await tryBridgedCollateralSwap(directParams, bridgeOpts);
69793
+ if (bridged) return bridged;
69794
+ throw directError;
69795
+ }
69796
+ }
69797
+ async function tryBridgedCollateralSwap(params, bridgeOpts) {
69798
+ const sourceBank = params.withdrawOpts.withdrawBank;
69799
+ const destinationBank = params.depositOpts.depositBank;
69800
+ const withdrawAmount = params.withdrawOpts.withdrawAmount ?? params.withdrawOpts.totalPositionAmount;
69801
+ const { usableBridgeBanks, conflictingBridgeBanks } = selectSwapBridges({
69802
+ sourceMint: sourceBank.mint,
69803
+ destinationMint: destinationBank.mint,
69804
+ bankMap: params.bankMap,
69805
+ marginfiAccount: params.marginfiAccount,
69806
+ bridgeTokenSide: "deposit",
69807
+ bridgeCandidateMints: bridgeOpts?.bridgeCandidateMints
69808
+ });
69809
+ const tokenProgramCache = new Map(bridgeOpts?.tokenProgramByMint);
69810
+ return tryBridgeCandidates({
69811
+ usableBridgeBanks,
69812
+ conflictingBridgeBanks,
69813
+ bridgeTokenSide: "deposit",
69814
+ abortSignal: bridgeOpts?.abortSignal,
69815
+ buildBundleThroughBridge: async (bridgeBank) => {
69816
+ const bridgeTokenProgram = await resolveTokenProgramForMint(
69817
+ bridgeBank.mint,
69818
+ params.connection,
69819
+ tokenProgramCache
69820
+ );
69821
+ const firstLeg = await makeSwapCollateralTx({
69822
+ ...sharedBridgeLegContext(params),
69823
+ withdrawOpts: {
69824
+ totalPositionAmount: params.withdrawOpts.totalPositionAmount,
69825
+ withdrawAmount,
69826
+ withdrawBank: sourceBank,
69827
+ tokenProgram: params.withdrawOpts.tokenProgram
69828
+ },
69829
+ depositOpts: { depositBank: bridgeBank, tokenProgram: bridgeTokenProgram }
69830
+ });
69831
+ if (!firstLeg.quoteResponse) return null;
69832
+ const bridgeMinOutNative = Number(firstLeg.quoteResponse.otherAmountThreshold);
69833
+ const secondLegAmountNative = bridgeMinOutNative - SECOND_LEG_ROUNDING_HEADROOM_NATIVE;
69834
+ if (secondLegAmountNative <= 0) return null;
69835
+ const secondLegAmountUi = nativeToUi(secondLegAmountNative, bridgeBank.mintDecimals);
69836
+ const hasBridgeDeposit = params.marginfiAccount.balances.some(
69837
+ (b) => b.active && b.bankPk.equals(bridgeBank.address) && b.assetShares.gt(0)
69838
+ );
69839
+ const result = await composeBridgedSwap({
69840
+ firstLeg,
69841
+ // The second leg builds against the first leg's projected effect.
69842
+ buildSecondLeg: (projectedAccount) => makeSwapCollateralTx({
69843
+ ...sharedBridgeLegContext(params),
69844
+ marginfiAccount: projectedAccount,
69845
+ withdrawOpts: {
69846
+ totalPositionAmount: hasBridgeDeposit ? nativeToUi(bridgeMinOutNative, bridgeBank.mintDecimals) : secondLegAmountUi,
69847
+ withdrawAmount: secondLegAmountUi,
69848
+ withdrawBank: bridgeBank,
69849
+ tokenProgram: bridgeTokenProgram
69850
+ },
69851
+ depositOpts: params.depositOpts
69852
+ }),
69853
+ marginfiAccount: params.marginfiAccount,
69854
+ program: params.program,
69855
+ banksMap: params.bankMap,
69856
+ assetShareValueMultiplierByBank: params.assetShareValueMultiplierByBank,
69857
+ feePayer: params.overrideInferAccounts?.authority ?? params.marginfiAccount.authority,
69858
+ maxBundleTxs: bridgeOpts?.maxBundleTxs
69859
+ });
69860
+ if (!result) return null;
69861
+ return {
69862
+ transactions: result.transactions,
69863
+ actionTxIndex: result.transactions.length - 1,
69864
+ quoteResponse: mergeBridgeQuotes(result.firstLegQuote, result.secondLegQuote),
69865
+ bridgeMint: bridgeBank.mint
69866
+ };
69867
+ }
69868
+ });
69869
+ }
69870
+ var DEFAULT_ROLL_SLIPPAGE_BPS = 50;
69871
+ var TRADE_PT_EVENT_AMOUNT_OUT_OFFSET = 138;
69872
+ var MERGE_EVENT_AMOUNT_SY_OUT_OFFSET = 296;
69873
+ async function makeRollPtTx(params) {
69874
+ const {
69875
+ program,
69876
+ marginfiAccount,
69877
+ connection,
69878
+ bankMap,
69879
+ oraclePrices,
69880
+ withdrawOpts,
69881
+ depositOpts,
69882
+ rollOpts,
69883
+ assetShareValueMultiplierByBank,
69884
+ addressLookupTableAccounts,
69885
+ crossbarUrl
69886
+ } = params;
69887
+ if (!rollOpts.maturedMarket && !rollOpts.maturedVault) {
69888
+ throw new Error("roll-pt: rollOpts.maturedMarket or maturedVault is required");
69889
+ }
69890
+ const merge = await resolveExponentMergeContext({
69891
+ connection,
69892
+ owner: marginfiAccount.authority,
69893
+ market: rollOpts.maturedMarket,
69894
+ vault: rollOpts.maturedVault,
69895
+ ptYtTokenProgram: withdrawOpts.tokenProgram,
69896
+ syTokenProgram: rollOpts.syTokenProgram
69897
+ });
69898
+ const clmm = await resolveExponentClmmTradePtContext({
69899
+ connection,
69900
+ owner: marginfiAccount.authority,
69901
+ market: rollOpts.successorMarket,
69902
+ ptTokenProgram: depositOpts.tokenProgram,
69903
+ syTokenProgram: rollOpts.syTokenProgram
69904
+ });
69905
+ const blockhash = (await connection.getLatestBlockhash("confirmed")).blockhash;
69906
+ const setupIxs = await makeSetupIx({
69907
+ connection,
69908
+ authority: marginfiAccount.authority,
69909
+ tokens: [
69910
+ { mint: withdrawOpts.withdrawBank.mint, tokenProgram: withdrawOpts.tokenProgram },
69911
+ { mint: merge.mergeAccounts.mintYt, tokenProgram: withdrawOpts.tokenProgram },
69912
+ { mint: merge.underlying.mint, tokenProgram: merge.underlying.tokenProgram },
69913
+ { mint: depositOpts.depositBank.mint, tokenProgram: depositOpts.tokenProgram }
69914
+ ]
69915
+ });
69916
+ const { flashloanTx, swapQuote, withdrawIxs, depositIxs } = await buildRollPtFlashloanTx({
69917
+ params,
69918
+ merge,
69919
+ clmm,
69920
+ setupIxs,
69921
+ blockhash
69922
+ });
69923
+ const { instructions: updateFeedIxs, luts: feedLuts } = await makeSmartCrankSwbFeedIx({
69924
+ marginfiAccount,
69925
+ bankMap,
69926
+ oraclePrices,
69927
+ assetShareValueMultiplierByBank,
69928
+ instructions: [...withdrawIxs.instructions, ...depositIxs.instructions],
69929
+ program,
69930
+ connection,
69931
+ crossbarUrl
69932
+ });
69933
+ const additionalTxs = [];
69934
+ if (setupIxs.length > 0) {
69935
+ const txs = splitInstructionsToFitTransactions([], setupIxs, {
69936
+ blockhash,
69937
+ payerKey: marginfiAccount.authority,
69938
+ luts: addressLookupTableAccounts ?? []
69712
69939
  });
69940
+ additionalTxs.push(
69941
+ ...txs.map(
69942
+ (tx) => addTransactionMetadata(tx, {
69943
+ type: "CREATE_ATA" /* CREATE_ATA */,
69944
+ addressLookupTables: addressLookupTableAccounts
69945
+ })
69946
+ )
69947
+ );
69713
69948
  }
69714
- };
69715
-
69716
- // src/services/account/actions/bridge-swap.ts
69717
- var MAX_BRIDGED_BUNDLE_TXS = 5;
69718
- function classifyTxs(txs) {
69719
- const out = { setups: [], cranks: [], flashloans: [] };
69720
- for (const tx of txs) {
69721
- if (tx.type === "CREATE_ATA" /* CREATE_ATA */) out.setups.push(tx);
69722
- else if (tx.type === "CRANK" /* CRANK */) out.cranks.push(tx);
69723
- else out.flashloans.push(tx);
69949
+ if (updateFeedIxs.length > 0) {
69950
+ const message = new web3_js.TransactionMessage({
69951
+ payerKey: marginfiAccount.authority,
69952
+ recentBlockhash: blockhash,
69953
+ instructions: updateFeedIxs
69954
+ }).compileToV0Message(feedLuts);
69955
+ additionalTxs.push(
69956
+ addTransactionMetadata(new web3_js.VersionedTransaction(message), {
69957
+ addressLookupTables: feedLuts,
69958
+ type: "CRANK" /* CRANK */
69959
+ })
69960
+ );
69724
69961
  }
69725
- return out;
69726
- }
69727
- function ixIdentity(ix) {
69728
- const keys = ix.keys.map((k) => k.pubkey.toBase58()).join(",");
69729
- return `${ix.programId.toBase58()}|${keys}|${Buffer.from(ix.data).toString("base64")}`;
69962
+ const transactions = [...additionalTxs, flashloanTx];
69963
+ return {
69964
+ transactions,
69965
+ actionTxIndex: transactions.length - 1,
69966
+ quoteResponse: swapQuote
69967
+ };
69730
69968
  }
69731
- function mergeSetupTxs(txs, payer, blockhash) {
69732
- if (txs.length === 0) return null;
69733
- if (txs.length === 1) return txs[0];
69734
- const lutMap = /* @__PURE__ */ new Map();
69735
- const seen = /* @__PURE__ */ new Set();
69736
- const ixs = [];
69737
- for (const tx of txs) {
69738
- const luts2 = tx.addressLookupTables ?? [];
69739
- luts2.forEach((l) => lutMap.set(l.key.toBase58(), l));
69740
- const msg = decompileV0Transaction(tx, luts2);
69741
- for (const ix of msg.instructions) {
69742
- const id = ixIdentity(ix);
69743
- if (seen.has(id)) continue;
69744
- seen.add(id);
69745
- ixs.push(ix);
69746
- }
69969
+ async function buildRollPtFlashloanTx({
69970
+ params,
69971
+ merge,
69972
+ clmm,
69973
+ setupIxs,
69974
+ blockhash
69975
+ }) {
69976
+ const {
69977
+ program,
69978
+ marginfiAccount,
69979
+ bankMap,
69980
+ withdrawOpts,
69981
+ depositOpts,
69982
+ bankMetadataMap,
69983
+ connection,
69984
+ addressLookupTableAccounts,
69985
+ overrideInferAccounts,
69986
+ rollOpts
69987
+ } = params;
69988
+ const { withdrawBank, tokenProgram: withdrawTokenProgram, totalPositionAmount, withdrawAmount } = withdrawOpts;
69989
+ const { depositBank, tokenProgram: depositTokenProgram } = depositOpts;
69990
+ const authority = marginfiAccount.authority;
69991
+ if (withdrawAmount !== void 0 && withdrawAmount <= 0) {
69992
+ throw new Error("withdrawAmount must be greater than 0");
69747
69993
  }
69748
- const luts = [...lutMap.values()];
69749
- const split = splitInstructionsToFitTransactions([], ixs, { blockhash, payerKey: payer, luts });
69750
- if (split.length !== 1) return null;
69751
- return addTransactionMetadata(split[0], {
69752
- type: "CREATE_ATA" /* CREATE_ATA */,
69753
- addressLookupTables: luts
69994
+ const actualWithdrawAmount = Math.min(withdrawAmount ?? totalPositionAmount, totalPositionAmount);
69995
+ const isFullWithdraw = isWholePosition(
69996
+ { amount: totalPositionAmount, isLending: true },
69997
+ actualWithdrawAmount,
69998
+ withdrawBank.mintDecimals
69999
+ );
70000
+ const withdrawNative = BigInt(
70001
+ uiToNative(actualWithdrawAmount, withdrawBank.mintDecimals).toString()
70002
+ );
70003
+ const cuRequestIxs = [
70004
+ web3_js.ComputeBudgetProgram.setComputeUnitLimit({ units: 12e5 }),
70005
+ web3_js.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1 })
70006
+ ];
70007
+ const withdrawIxs = await makeWithdrawIx3({
70008
+ program,
70009
+ bank: withdrawBank,
70010
+ bankMap,
70011
+ tokenProgram: withdrawTokenProgram,
70012
+ amount: actualWithdrawAmount,
70013
+ marginfiAccount,
70014
+ authority,
70015
+ withdrawAll: isFullWithdraw,
70016
+ isSync: false,
70017
+ opts: { createAtas: false, wrapAndUnwrapSol: false, overrideInferAccounts }
69754
70018
  });
69755
- }
69756
- function projectAccountAfterFirstLeg(account, firstLegFlashloanTxs, program, banksMap, multipliers) {
69757
- const ixs = [];
69758
- for (const tx of firstLegFlashloanTxs) {
69759
- const luts = tx.addressLookupTables ?? [];
69760
- ixs.push(...decompileV0Transaction(tx, luts).instructions);
70019
+ const mergeIx = makeExponentMergeIx(merge.mergeAccounts, withdrawNative);
70020
+ const depositIxs = await makeDepositIx3({
70021
+ program,
70022
+ bank: depositBank,
70023
+ tokenProgram: depositTokenProgram,
70024
+ amount: 0,
70025
+ accountAddress: marginfiAccount.address,
70026
+ authority,
70027
+ group: marginfiAccount.group,
70028
+ opts: { wrapAndUnwrapSol: false, overrideInferAccounts }
70029
+ });
70030
+ let luts;
70031
+ if (rollOpts.lookupTable) {
70032
+ const fetched = (await connection.getAddressLookupTable(rollOpts.lookupTable)).value;
70033
+ if (!fetched) {
70034
+ throw new Error(`roll-pt: PT-roll lookup table not found: ${rollOpts.lookupTable.toBase58()}`);
70035
+ }
70036
+ luts = [fetched, merge.addressLookupTable, clmm.addressLookupTable];
70037
+ } else {
70038
+ luts = [
70039
+ ...addressLookupTableAccounts ?? [],
70040
+ merge.addressLookupTable,
70041
+ clmm.addressLookupTable
70042
+ ];
69761
70043
  }
69762
- const { projectedBalances } = computeProjectedActiveBalancesNoCpi({
69763
- account,
69764
- instructions: ixs,
70044
+ const redeemIxs = [...setupIxs, ...cuRequestIxs, ...withdrawIxs.instructions, mergeIx];
70045
+ const mergeReturn = await simulateRollReturn({
69765
70046
  program,
69766
- banksMap,
69767
- assetShareValueMultiplierByBank: multipliers
70047
+ marginfiAccount,
70048
+ bankMap,
70049
+ connection,
70050
+ blockhash,
70051
+ luts,
70052
+ ixs: redeemIxs,
70053
+ programId: EXPONENT_CORE_PROGRAM_ID,
70054
+ label: "merge"
69768
70055
  });
69769
- return new MarginfiAccount(
69770
- account.address,
69771
- account.group,
69772
- account.authority,
69773
- projectedBalances.map((b) => Balance.fromBalanceType(b)),
69774
- account.accountFlags,
69775
- account.emissionsDestinationAccount,
69776
- account.healthCache
70056
+ const syExact = readReturnU64(mergeReturn, MERGE_EVENT_AMOUNT_SY_OUT_OFFSET, "merge amount_sy_out");
70057
+ if (syExact <= 0n) {
70058
+ throw new Error("roll-pt: merge would redeem 0 SY (empty/invalid matured vault state)");
70059
+ }
70060
+ const exactPtOut = await quoteClmmTradeOut({
70061
+ connection,
70062
+ clmm,
70063
+ amountInSyNative: syExact,
70064
+ payer: authority
70065
+ });
70066
+ const slippageBps = rollOpts.slippageBps ?? DEFAULT_ROLL_SLIPPAGE_BPS;
70067
+ const minPtOut = exactPtOut * BigInt(1e4 - slippageBps) / 10000n;
70068
+ if (minPtOut <= 0n) {
70069
+ throw new Error("roll-pt: quoted PT out is 0 (insufficient CLMM liquidity for this size)");
70070
+ }
70071
+ const tradeIx = makeExponentClmmTradePtIx(
70072
+ clmm.tradePtAccounts,
70073
+ exponentClmmBuyPtArgs({ amountInSyNative: syExact, minPtOutNative: minPtOut })
69777
70074
  );
69778
- }
69779
- function composeBundle(firstLegTxs, secondLegTxs, payer, blockhash, maxBundleTxs) {
69780
- const c1 = classifyTxs(firstLegTxs);
69781
- const c2 = classifyTxs(secondLegTxs);
69782
- const mergedSetup = mergeSetupTxs([...c1.setups, ...c2.setups], payer, blockhash);
69783
- if ([...c1.setups, ...c2.setups].length > 0 && !mergedSetup) return null;
69784
- const result = [
69785
- ...mergedSetup ? [mergedSetup] : [],
69786
- ...c1.cranks,
69787
- ...c1.flashloans,
69788
- // firstLegFL(s)
69789
- ...c2.cranks,
69790
- ...c2.flashloans
69791
- // secondLegFL(s)
70075
+ const depositIxToPatch = depositIxs.instructions.find(isDepositIx);
70076
+ if (!depositIxToPatch) {
70077
+ throw new Error("roll-pt: could not locate deposit instruction for amount patching");
70078
+ }
70079
+ patchDepositAmount(depositIxToPatch, new BN9__default.default(minPtOut.toString()));
70080
+ const allNonFlIxs = [
70081
+ ...cuRequestIxs,
70082
+ ...withdrawIxs.instructions,
70083
+ mergeIx,
70084
+ tradeIx,
70085
+ ...depositIxs.instructions
69792
70086
  ];
69793
- if (result.length > maxBundleTxs) return null;
69794
- return result;
69795
- }
69796
- function compoundQuoteRisk(firstLeg, secondLeg) {
69797
- const compound = (a, b) => {
69798
- if (a == null && b == null) return void 0;
69799
- const x = Number(a ?? 0);
69800
- const y = Number(b ?? 0);
69801
- return String(1 - (1 - x) * (1 - y));
69802
- };
69803
- return {
69804
- slippageBps: Math.round(
69805
- (1 - (1 - firstLeg.slippageBps / 1e4) * (1 - secondLeg.slippageBps / 1e4)) * 1e4
69806
- ),
69807
- priceImpactPct: compound(firstLeg.priceImpactPct, secondLeg.priceImpactPct)
69808
- };
69809
- }
69810
- function mergeBridgeQuotes(firstLeg, secondLeg) {
69811
- return {
69812
- inAmount: firstLeg.inAmount,
69813
- outAmount: secondLeg.outAmount,
69814
- otherAmountThreshold: secondLeg.otherAmountThreshold,
69815
- ...compoundQuoteRisk(firstLeg, secondLeg),
69816
- provider: firstLeg.provider
69817
- };
69818
- }
69819
- function mergeBridgeQuotesDebt(firstLeg, secondLeg) {
69820
- return {
69821
- inAmount: firstLeg.outAmount,
69822
- outAmount: secondLeg.inAmount,
69823
- otherAmountThreshold: secondLeg.inAmount,
69824
- ...compoundQuoteRisk(firstLeg, secondLeg),
69825
- provider: firstLeg.provider
69826
- };
69827
- }
69828
- function mergeBridgeQuotesLoop(firstLeg, secondLeg) {
69829
- return {
69830
- inAmount: secondLeg.inAmount,
69831
- outAmount: firstLeg.outAmount,
69832
- otherAmountThreshold: firstLeg.otherAmountThreshold,
69833
- ...compoundQuoteRisk(firstLeg, secondLeg),
69834
- provider: firstLeg.provider
69835
- };
69836
- }
69837
- function blockhashOf(leg) {
69838
- const tx = leg.transactions[0];
69839
- return tx ? tx.message.recentBlockhash : web3_js.PublicKey.default.toBase58();
69840
- }
69841
- async function composeBridgedSwap(params) {
69842
- const {
69843
- firstLeg,
69844
- buildSecondLeg,
70087
+ const { sizeConstraint } = computeFlashLoanNonSwapBudget({
70088
+ program,
69845
70089
  marginfiAccount,
70090
+ bankMap,
70091
+ addressLookupTableAccounts: luts,
70092
+ ixs: allNonFlIxs
70093
+ });
70094
+ compileFlashloanPrecheck({
70095
+ allIxs: allNonFlIxs,
70096
+ payer: authority,
70097
+ luts,
70098
+ sizeConstraint,
70099
+ swapIxCount: 0,
70100
+ swapLutCount: 0
70101
+ });
70102
+ const flashloanTx = await makeFlashLoanTx({
69846
70103
  program,
69847
- banksMap,
69848
- assetShareValueMultiplierByBank,
69849
- feePayer,
69850
- maxBundleTxs = MAX_BRIDGED_BUNDLE_TXS
69851
- } = params;
69852
- if (!firstLeg.quoteResponse) return null;
69853
- const projectedAccount = projectAccountAfterFirstLeg(
69854
70104
  marginfiAccount,
69855
- classifyTxs(firstLeg.transactions).flashloans,
70105
+ bankMap,
70106
+ addressLookupTableAccounts: luts,
70107
+ blockhash,
70108
+ ixs: allNonFlIxs,
70109
+ isSync: false
70110
+ });
70111
+ const txSize = getTxSize(flashloanTx);
70112
+ const totalKeys = getTotalAccountKeys(flashloanTx);
70113
+ if (txSize > MAX_TX_SIZE || totalKeys > MAX_ACCOUNT_LOCKS) {
70114
+ throw TransactionBuildingError.swapSizeExceededPositionSwap(txSize, totalKeys, void 0);
70115
+ }
70116
+ const swapQuote = {
70117
+ inAmount: syExact.toString(),
70118
+ outAmount: exactPtOut.toString(),
70119
+ otherAmountThreshold: minPtOut.toString(),
70120
+ slippageBps
70121
+ };
70122
+ return { flashloanTx, swapQuote, withdrawIxs, depositIxs };
70123
+ }
70124
+ async function simulateRollReturn({
70125
+ program,
70126
+ marginfiAccount,
70127
+ bankMap,
70128
+ connection,
70129
+ blockhash,
70130
+ luts,
70131
+ ixs,
70132
+ programId,
70133
+ label
70134
+ }) {
70135
+ const quoteTx = await makeFlashLoanTx({
69856
70136
  program,
69857
- banksMap,
69858
- assetShareValueMultiplierByBank
70137
+ marginfiAccount,
70138
+ bankMap,
70139
+ addressLookupTableAccounts: luts,
70140
+ blockhash,
70141
+ ixs,
70142
+ isSync: false
70143
+ });
70144
+ const sim = await connection.simulateTransaction(quoteTx, {
70145
+ sigVerify: false,
70146
+ replaceRecentBlockhash: true
70147
+ });
70148
+ const logs = sim.value.logs ?? [];
70149
+ if (process.env.ROLL_DEBUG) {
70150
+ console.error(
70151
+ `[roll ${label} sim] err:`,
70152
+ JSON.stringify(sim.value.err),
70153
+ "returnData:",
70154
+ JSON.stringify(sim.value.returnData),
70155
+ "logsLen:",
70156
+ logs.length,
70157
+ "truncated:",
70158
+ logs.some((l) => l.includes("truncated"))
70159
+ );
70160
+ }
70161
+ const prefix = `Program return: ${programId.toBase58()} `;
70162
+ const line = [...logs].reverse().find((l) => l.startsWith(prefix));
70163
+ if (!line) {
70164
+ throw new Error(
70165
+ `roll-pt: could not read ${label} return data from quote simulation (err=${JSON.stringify(
70166
+ sim.value.err
70167
+ )})`
70168
+ );
70169
+ }
70170
+ return Buffer.from(line.slice(prefix.length), "base64");
70171
+ }
70172
+ function readReturnU64(data, offset, what) {
70173
+ if (data.length < offset + 8) {
70174
+ throw new Error(`roll-pt: ${what} return data too short (${data.length} bytes)`);
70175
+ }
70176
+ return data.readBigUInt64LE(offset);
70177
+ }
70178
+ async function quoteClmmTradeOut({
70179
+ connection,
70180
+ clmm,
70181
+ amountInSyNative,
70182
+ payer
70183
+ }) {
70184
+ const excluded = /* @__PURE__ */ new Set([
70185
+ clmm.tradePtAccounts.tokenSyEscrow.toBase58(),
70186
+ clmm.tradePtAccounts.tokenFeeTreasurySy.toBase58()
70187
+ ]);
70188
+ const largest = await connection.getTokenLargestAccounts(clmm.sy.mint);
70189
+ const funded = largest.value.find(
70190
+ (a) => !excluded.has(a.address.toBase58()) && BigInt(a.amount) >= amountInSyNative
69859
70191
  );
69860
- const secondLeg = await buildSecondLeg(projectedAccount);
69861
- if (!secondLeg.quoteResponse) return null;
69862
- const transactions = composeBundle(
69863
- firstLeg.transactions,
69864
- secondLeg.transactions,
69865
- feePayer,
69866
- blockhashOf(firstLeg),
69867
- maxBundleTxs
70192
+ if (!funded) {
70193
+ throw new Error(
70194
+ "roll-pt: no SY holder large enough to quote the buy \u2014 the roll size exceeds available CLMM liquidity for this pair"
70195
+ );
70196
+ }
70197
+ const parsed = await connection.getParsedAccountInfo(funded.address);
70198
+ const info = parsed.value?.data?.parsed?.info;
70199
+ if (!info?.owner) throw new Error("roll-pt: could not resolve the quote SY holder's owner");
70200
+ const trader = new web3_js.PublicKey(info.owner);
70201
+ const ptTokenProgram = clmm.pt.tokenProgram ?? TOKEN_PROGRAM_ID;
70202
+ const tokenPtTrader = getAssociatedTokenAddressSync(clmm.pt.mint, trader, true, ptTokenProgram);
70203
+ const quoteIx = makeExponentClmmTradePtIx(
70204
+ { ...clmm.tradePtAccounts, trader, tokenSyTrader: funded.address, tokenPtTrader },
70205
+ exponentClmmBuyPtArgs({ amountInSyNative, minPtOutNative: 1n })
69868
70206
  );
69869
- if (!transactions) return null;
69870
- return { transactions, firstLegQuote: firstLeg.quoteResponse, secondLegQuote: secondLeg.quoteResponse };
70207
+ const createPtAta = createAssociatedTokenAccountIdempotentInstruction(
70208
+ payer,
70209
+ tokenPtTrader,
70210
+ trader,
70211
+ clmm.pt.mint,
70212
+ ptTokenProgram
70213
+ );
70214
+ const { blockhash } = await connection.getLatestBlockhash("confirmed");
70215
+ const message = new web3_js.TransactionMessage({
70216
+ payerKey: payer,
70217
+ recentBlockhash: blockhash,
70218
+ instructions: [createPtAta, quoteIx]
70219
+ }).compileToV0Message([clmm.addressLookupTable]);
70220
+ const sim = await connection.simulateTransaction(new web3_js.VersionedTransaction(message), {
70221
+ sigVerify: false,
70222
+ replaceRecentBlockhash: true
70223
+ });
70224
+ const rd = sim.value.returnData;
70225
+ if (process.env.ROLL_DEBUG) {
70226
+ console.error("[roll trade quote] err:", JSON.stringify(sim.value.err), "returnData?", !!rd);
70227
+ }
70228
+ if (!rd?.data || rd.programId !== EXPONENT_CLMM_PROGRAM_ID.toBase58()) {
70229
+ throw new Error(
70230
+ `roll-pt: CLMM trade quote produced no return data (err=${JSON.stringify(sim.value.err)})`
70231
+ );
70232
+ }
70233
+ const data = Buffer.from(rd.data[0], rd.data[1]);
70234
+ return readReturnU64(data, TRADE_PT_EVENT_AMOUNT_OUT_OFFSET, "trade_pt amount_out");
69871
70235
  }
69872
70236
  var MAX_BALANCES = 16;
69873
70237
  var DEFAULT_MAX_TRANSFER_POSITIONS = 5;
@@ -71350,30 +71714,99 @@ function patchDepositAmount(ix, amountNative) {
71350
71714
  }
71351
71715
 
71352
71716
  // src/services/account/utils/bridge.utils.ts
71353
- function accountConflictsWithBridge(account, bankPk, side) {
71354
- const balance = account.balances.find((b) => b.active && b.bankPk.equals(bankPk));
71717
+ function accountConflictsWithBridgeBank(marginfiAccount, bridgeBankPk, bridgeTokenSide) {
71718
+ const balance = marginfiAccount.balances.find((b) => b.active && b.bankPk.equals(bridgeBankPk));
71355
71719
  if (!balance) return false;
71356
- return side === "deposit" ? balance.liabilityShares.gt(0) : balance.assetShares.gt(0);
71720
+ return bridgeTokenSide === "deposit" ? balance.liabilityShares.gt(0) : balance.assetShares.gt(0);
71357
71721
  }
71358
- function resolveBridgeBanks(params) {
71359
- const { orderedBridgeMints, banks, marginfiAccount, side } = params;
71360
- const passesSideFilter = side === "borrow" ? isStandardBorrowable : isStandardDepositable;
71361
- const bridges = [];
71362
- const conflicts = [];
71722
+ function resolveBridgeCandidateBanks(params) {
71723
+ const { prioritizedBridgeCandidateMints, groupBanks, marginfiAccount, bridgeTokenSide } = params;
71724
+ const passesSideFilter = bridgeTokenSide === "borrow" ? isStandardBorrowable : isStandardDepositable;
71725
+ const usableBridgeBanks = [];
71726
+ const conflictingBridgeBanks = [];
71363
71727
  const seenMints = /* @__PURE__ */ new Set();
71364
- for (const mint of orderedBridgeMints) {
71728
+ for (const mint of prioritizedBridgeCandidateMints) {
71365
71729
  const mintKey = mint.toBase58();
71366
71730
  if (seenMints.has(mintKey)) continue;
71367
71731
  seenMints.add(mintKey);
71368
- const bank = banks.find((b) => b.mint.equals(mint) && passesSideFilter(b));
71732
+ const bank = groupBanks.find((b) => b.mint.equals(mint) && passesSideFilter(b));
71369
71733
  if (!bank) continue;
71370
- if (accountConflictsWithBridge(marginfiAccount, bank.address, side)) {
71371
- conflicts.push(bank);
71734
+ if (accountConflictsWithBridgeBank(marginfiAccount, bank.address, bridgeTokenSide)) {
71735
+ conflictingBridgeBanks.push(bank);
71372
71736
  } else {
71373
- bridges.push(bank);
71737
+ usableBridgeBanks.push(bank);
71738
+ }
71739
+ }
71740
+ return { usableBridgeBanks, conflictingBridgeBanks };
71741
+ }
71742
+
71743
+ // src/services/account/utils/bridge-routing.utils.ts
71744
+ var DEFAULT_BRIDGE_MINTS = [USDC_MINT, WSOL_MINT, USDT_MINT];
71745
+ async function resolveTokenProgramForMint(mint, connection, tokenProgramCacheByMint) {
71746
+ const mintKey = mint.toBase58();
71747
+ const cached = tokenProgramCacheByMint.get(mintKey);
71748
+ if (cached) return cached;
71749
+ const owner = (await connection.getAccountInfo(mint))?.owner ?? TOKEN_PROGRAM_ID;
71750
+ tokenProgramCacheByMint.set(mintKey, owner);
71751
+ return owner;
71752
+ }
71753
+ function selectSwapBridges(args) {
71754
+ const prioritizedCandidateMints = (args.bridgeCandidateMints ?? DEFAULT_BRIDGE_MINTS).filter(
71755
+ (mint) => !mint.equals(args.sourceMint) && !mint.equals(args.destinationMint)
71756
+ );
71757
+ return resolveBridgeCandidateBanks({
71758
+ prioritizedBridgeCandidateMints: prioritizedCandidateMints,
71759
+ groupBanks: [...args.bankMap.values()],
71760
+ marginfiAccount: args.marginfiAccount,
71761
+ bridgeTokenSide: args.bridgeTokenSide
71762
+ });
71763
+ }
71764
+ function isAbortError(e) {
71765
+ return e instanceof DOMException && e.name === "AbortError";
71766
+ }
71767
+ async function tryBridgeCandidates(args) {
71768
+ for (const bridgeBank of args.usableBridgeBanks) {
71769
+ if (args.abortSignal?.aborted) {
71770
+ throw new DOMException("Operation was aborted", "AbortError");
71771
+ }
71772
+ try {
71773
+ const result = await args.buildBundleThroughBridge(bridgeBank);
71774
+ if (result) return result;
71775
+ } catch (e) {
71776
+ if (isAbortError(e)) throw e;
71777
+ console.warn(
71778
+ `[bridge-routing] candidate ${bridgeBank.tokenSymbol ?? bridgeBank.mint.toBase58()} failed:`,
71779
+ e instanceof Error ? e.message : e
71780
+ );
71374
71781
  }
71375
71782
  }
71376
- return { bridges, conflicts };
71783
+ if (args.usableBridgeBanks.length === 0 && args.conflictingBridgeBanks.length > 0) {
71784
+ throw TransactionBuildingError.bridgeConflict(
71785
+ args.conflictingBridgeBanks.map((bank) => ({
71786
+ bankAddress: bank.address.toBase58(),
71787
+ mint: bank.mint.toBase58(),
71788
+ symbol: bank.tokenSymbol
71789
+ })),
71790
+ args.bridgeTokenSide
71791
+ );
71792
+ }
71793
+ return null;
71794
+ }
71795
+ function sharedBridgeLegContext(params) {
71796
+ return {
71797
+ program: params.program,
71798
+ marginfiAccount: params.marginfiAccount,
71799
+ connection: params.connection,
71800
+ bankMap: params.bankMap,
71801
+ oraclePrices: params.oraclePrices,
71802
+ bankMetadataMap: params.bankMetadataMap,
71803
+ assetShareValueMultiplierByBank: params.assetShareValueMultiplierByBank,
71804
+ swapOpts: params.swapOpts,
71805
+ addressLookupTableAccounts: params.addressLookupTableAccounts,
71806
+ overrideInferAccounts: params.overrideInferAccounts,
71807
+ crossbarUrl: params.crossbarUrl,
71808
+ swapEngineRunner: params.swapEngineRunner
71809
+ };
71377
71810
  }
71378
71811
 
71379
71812
  // src/services/price/utils/smart-crank.utils.ts
@@ -75256,6 +75689,82 @@ var MarginfiAccountWrapper = class {
75256
75689
  };
75257
75690
  return this.account.makeSwapDebtTx(fullParams);
75258
75691
  }
75692
+ /**
75693
+ * Creates a loop (leverage) transaction with a transparent bridged (double-hop) fallback and
75694
+ * auto-injected client data.
75695
+ *
75696
+ * One call: tries the direct {@link makeLoopTx} first; if its borrow→deposit swap can't fit one
75697
+ * transaction or has no route, it loops the deposit asset against a value-equivalent borrow of a
75698
+ * bridge token (USDC/wSOL/USDT by default, override via `bridgeOpts.bridgeCandidateMints`) and
75699
+ * debt-swaps that bridge debt to the requested borrow asset — one atomic Jito bundle.
75700
+ * `result.bridgeMint` is set only when the bridged path was used.
75701
+ *
75702
+ * Auto-injects: program, marginfiAccount, bankMap, oraclePrices, bankMetadataMap,
75703
+ * addressLookupTables, assetShareValueMultiplierByBank
75704
+ *
75705
+ * @param params - Loop parameters (user provides: connection, depositOpts, borrowOpts, swapOpts, bridgeOpts?, etc.)
75706
+ */
75707
+ async makeBridgedLoopTx(params) {
75708
+ return this.account.makeBridgedLoopTx({
75709
+ ...params,
75710
+ program: this.client.program,
75711
+ bankMap: this.client.bankMap,
75712
+ oraclePrices: this.client.oraclePriceByBank,
75713
+ bankMetadataMap: this.client.bankIntegrationMap,
75714
+ addressLookupTableAccounts: this.client.addressLookupTables,
75715
+ assetShareValueMultiplierByBank: this.client.assetShareValueMultiplierByBank
75716
+ });
75717
+ }
75718
+ /**
75719
+ * Creates a collateral-swap transaction with a transparent bridged (double-hop) fallback and
75720
+ * auto-injected client data.
75721
+ *
75722
+ * One call: tries the direct {@link makeSwapCollateralTx} first; if the swap `A → C` can't fit
75723
+ * one transaction or has no route, it decomposes into `A → bridge` + `bridge → C` through a
75724
+ * bridge token, composed as one atomic Jito bundle. `result.bridgeMint` is set only when the
75725
+ * bridged path was used.
75726
+ *
75727
+ * Auto-injects: program, marginfiAccount, bankMap, oraclePrices, bankMetadataMap,
75728
+ * addressLookupTables, assetShareValueMultiplierByBank
75729
+ *
75730
+ * @param params - Swap collateral parameters (user provides: connection, withdrawOpts, depositOpts, swapOpts, bridgeOpts?, etc.)
75731
+ */
75732
+ async makeBridgedSwapCollateralTx(params) {
75733
+ return this.account.makeBridgedSwapCollateralTx({
75734
+ ...params,
75735
+ program: this.client.program,
75736
+ bankMap: this.client.bankMap,
75737
+ oraclePrices: this.client.oraclePriceByBank,
75738
+ bankMetadataMap: this.client.bankIntegrationMap,
75739
+ addressLookupTableAccounts: this.client.addressLookupTables,
75740
+ assetShareValueMultiplierByBank: this.client.assetShareValueMultiplierByBank
75741
+ });
75742
+ }
75743
+ /**
75744
+ * Creates a debt-swap transaction with a transparent bridged (double-hop) fallback and
75745
+ * auto-injected client data.
75746
+ *
75747
+ * One call: tries the direct {@link makeSwapDebtTx} first; if the swap `A → C` can't fit one
75748
+ * transaction or has no route, the first leg repays A by borrowing a bridge token and the second
75749
+ * leg repays exactly that bridge debt while borrowing C — one atomic Jito bundle.
75750
+ * `result.bridgeMint` is set only when the bridged path was used.
75751
+ *
75752
+ * Auto-injects: program, marginfiAccount, bankMap, oraclePrices, bankMetadataMap,
75753
+ * addressLookupTables, assetShareValueMultiplierByBank
75754
+ *
75755
+ * @param params - Swap debt parameters (user provides: connection, repayOpts, borrowOpts, swapOpts, bridgeOpts?, etc.)
75756
+ */
75757
+ async makeBridgedSwapDebtTx(params) {
75758
+ return this.account.makeBridgedSwapDebtTx({
75759
+ ...params,
75760
+ program: this.client.program,
75761
+ bankMap: this.client.bankMap,
75762
+ oraclePrices: this.client.oraclePriceByBank,
75763
+ bankMetadataMap: this.client.bankIntegrationMap,
75764
+ addressLookupTableAccounts: this.client.addressLookupTables,
75765
+ assetShareValueMultiplierByBank: this.client.assetShareValueMultiplierByBank
75766
+ });
75767
+ }
75259
75768
  /**
75260
75769
  * Creates a deposit transaction with auto-injected client data.
75261
75770
  *
@@ -76025,6 +76534,7 @@ exports.Bank = Bank;
76025
76534
  exports.BankConfig = BankConfig;
76026
76535
  exports.BankConfigFlag = BankConfigFlag;
76027
76536
  exports.BankVaultType = BankVaultType;
76537
+ exports.DEFAULT_BRIDGE_MINTS = DEFAULT_BRIDGE_MINTS;
76028
76538
  exports.DEFAULT_CROSSBAR_URL = DEFAULT_CROSSBAR_URL;
76029
76539
  exports.DEFAULT_FALLBACK_CROSSBAR_URL = DEFAULT_FALLBACK_CROSSBAR_URL;
76030
76540
  exports.DEFAULT_ORACLE_MAX_AGE = DEFAULT_ORACLE_MAX_AGE;
@@ -76098,9 +76608,10 @@ exports.TransactionConfigMap = TransactionConfigMap;
76098
76608
  exports.TransactionType = TransactionType;
76099
76609
  exports.USDC_DECIMALS = USDC_DECIMALS;
76100
76610
  exports.USDC_MINT = USDC_MINT;
76611
+ exports.USDT_MINT = USDT_MINT;
76101
76612
  exports.WSOL_MINT = WSOL_MINT;
76102
76613
  exports.ZERO_ORACLE_KEY = ZERO_ORACLE_KEY;
76103
- exports.accountConflictsWithBridge = accountConflictsWithBridge;
76614
+ exports.accountConflictsWithBridgeBank = accountConflictsWithBridgeBank;
76104
76615
  exports.accountFlagToBN = accountFlagToBN;
76105
76616
  exports.addOracleToBanksIx = addOracleToBanksIx;
76106
76617
  exports.addTransactionMetadata = addTransactionMetadata;
@@ -76305,6 +76816,7 @@ exports.hasEmodeEntryFlag = hasEmodeEntryFlag;
76305
76816
  exports.hasEmodeFlag = hasEmodeFlag;
76306
76817
  exports.hasHealthCacheFlag = hasHealthCacheFlag;
76307
76818
  exports.healthCacheToDto = healthCacheToDto;
76819
+ exports.isBridgeConflictError = isBridgeConflictError;
76308
76820
  exports.isDecomposableSwapError = isDecomposableSwapError;
76309
76821
  exports.isDepositIx = isDepositIx;
76310
76822
  exports.isFlashloan = isFlashloan;
@@ -76319,6 +76831,9 @@ exports.makeAddPermissionlessStakedBankIx = makeAddPermissionlessStakedBankIx;
76319
76831
  exports.makeBeginFlashLoanIx = makeBeginFlashLoanIx3;
76320
76832
  exports.makeBorrowIx = makeBorrowIx3;
76321
76833
  exports.makeBorrowTx = makeBorrowTx;
76834
+ exports.makeBridgedLoopTx = makeBridgedLoopTx;
76835
+ exports.makeBridgedSwapCollateralTx = makeBridgedSwapCollateralTx;
76836
+ exports.makeBridgedSwapDebtTx = makeBridgedSwapDebtTx;
76322
76837
  exports.makeBulkRepayTx = makeBulkRepayTx;
76323
76838
  exports.makeBulkWithdrawTx = makeBulkWithdrawTx;
76324
76839
  exports.makeBundleTipIx = makeBundleTipIx;
@@ -76406,10 +76921,12 @@ exports.patchDepositAmount = patchDepositAmount;
76406
76921
  exports.requireBank = requireBank;
76407
76922
  exports.requireTokenProgram = requireTokenProgram;
76408
76923
  exports.resolveAmount = resolveAmount;
76409
- exports.resolveBridgeBanks = resolveBridgeBanks;
76924
+ exports.resolveBridgeCandidateBanks = resolveBridgeCandidateBanks;
76925
+ exports.resolveTokenProgramForMint = resolveTokenProgramForMint;
76410
76926
  exports.runSwapEngine = runSwapEngine;
76411
76927
  exports.selectLutsForAccountAction = selectLutsForAccountAction;
76412
76928
  exports.selectLutsForBanks = selectLutsForBanks;
76929
+ exports.selectSwapBridges = selectSwapBridges;
76413
76930
  exports.serializeBankConfigOpt = serializeBankConfigOpt;
76414
76931
  exports.serializeInstruction = serializeInstruction;
76415
76932
  exports.serializeInterestRateConfig = serializeInterestRateConfig;
@@ -76420,6 +76937,7 @@ exports.serializeOracleSetupToIndex = serializeOracleSetupToIndex;
76420
76937
  exports.serializeRiskTier = serializeRiskTier;
76421
76938
  exports.serializeSwapEngineRequest = serializeSwapEngineRequest;
76422
76939
  exports.serializeSwapEngineResult = serializeSwapEngineResult;
76940
+ exports.sharedBridgeLegContext = sharedBridgeLegContext;
76423
76941
  exports.shortenAddress = shortenAddress;
76424
76942
  exports.simulateAccountHealthCache = simulateAccountHealthCache;
76425
76943
  exports.simulateAccountHealthCacheWithFallback = simulateAccountHealthCacheWithFallback;
@@ -76435,6 +76953,7 @@ exports.toEmodeSettingsDto = toEmodeSettingsDto;
76435
76953
  exports.toInterestRateConfigDto = toInterestRateConfigDto;
76436
76954
  exports.toJupiterConfig = toJupiterConfig;
76437
76955
  exports.toNumber = toNumber;
76956
+ exports.tryBridgeCandidates = tryBridgeCandidates;
76438
76957
  exports.uiToNative = uiToNative;
76439
76958
  exports.uiToNativeBigNumber = uiToNativeBigNumber;
76440
76959
  exports.validatorStakeGroupToDto = validatorStakeGroupToDto;