@0dotxyz/p0-ts-sdk 2.5.4 → 2.5.5-alpha.1

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
@@ -128,6 +128,9 @@ var TransactionBuildingErrorCode = /* @__PURE__ */ ((TransactionBuildingErrorCod
128
128
  TransactionBuildingErrorCode2["JUPLEND_STATE_NOT_FOUND"] = "JUPLEND_STATE_NOT_FOUND";
129
129
  TransactionBuildingErrorCode2["SWITCHBOARD_FEED_UPDATE_FAILED"] = "SWITCHBOARD_FEED_UPDATE_FAILED";
130
130
  TransactionBuildingErrorCode2["SWAP_QUOTE_FAILED"] = "SWAP_QUOTE_FAILED";
131
+ TransactionBuildingErrorCode2["TRANSFER_POSITIONS_INVALID_SELECTION"] = "TRANSFER_POSITIONS_INVALID_SELECTION";
132
+ TransactionBuildingErrorCode2["TRANSFER_POSITIONS_UNSUPPORTED_BANK"] = "TRANSFER_POSITIONS_UNSUPPORTED_BANK";
133
+ TransactionBuildingErrorCode2["TRANSFER_POSITIONS_UNSPLITTABLE"] = "TRANSFER_POSITIONS_UNSPLITTABLE";
131
134
  return TransactionBuildingErrorCode2;
132
135
  })(TransactionBuildingErrorCode || {});
133
136
  var TransactionBuildingError = class _TransactionBuildingError extends Error {
@@ -224,6 +227,40 @@ var TransactionBuildingError = class _TransactionBuildingError extends Error {
224
227
  { provider, inputMint, outputMint, reason }
225
228
  );
226
229
  }
230
+ /**
231
+ * The requested set of positions to transfer is invalid (inactive bank on the source,
232
+ * destination overlap, capacity/slot conflict, group/authority mismatch, etc.).
233
+ */
234
+ static transferPositionsInvalidSelection(reason, bankAddresses) {
235
+ return new _TransactionBuildingError(
236
+ "TRANSFER_POSITIONS_INVALID_SELECTION" /* TRANSFER_POSITIONS_INVALID_SELECTION */,
237
+ `Invalid transfer-positions selection: ${reason}`,
238
+ { reason, bankAddresses }
239
+ );
240
+ }
241
+ /**
242
+ * A selected position lives in a bank whose asset tag is not supported by transfer-positions
243
+ * (v1 supports DEFAULT and STAKED only).
244
+ */
245
+ static transferPositionsUnsupportedBank(bankAddress, assetTag, bankSymbol) {
246
+ return new _TransactionBuildingError(
247
+ "TRANSFER_POSITIONS_UNSUPPORTED_BANK" /* TRANSFER_POSITIONS_UNSUPPORTED_BANK */,
248
+ `Bank ${bankSymbol ?? bankAddress} (asset tag ${assetTag}) is not supported by transfer-positions`,
249
+ { bankAddress, assetTag, bankSymbol }
250
+ );
251
+ }
252
+ /**
253
+ * The built transfer transaction exceeds the v0 size / account-lock limits even at the position
254
+ * cap (most likely several integration positions whose reserve accounts overflow the 64-lock cap).
255
+ * Retry with fewer positions in the selection.
256
+ */
257
+ static transferPositionsUnsplittable(reason, sizeBytes, accountCount) {
258
+ return new _TransactionBuildingError(
259
+ "TRANSFER_POSITIONS_UNSPLITTABLE" /* TRANSFER_POSITIONS_UNSPLITTABLE */,
260
+ `Transfer does not fit one transaction: ${reason}`,
261
+ { reason, sizeBytes, accountCount }
262
+ );
263
+ }
227
264
  /**
228
265
  * Generic escape hatch for custom errors
229
266
  */
@@ -69434,6 +69471,23 @@ var MarginfiAccount = class _MarginfiAccount {
69434
69471
  }
69435
69472
  });
69436
69473
  }
69474
+ /**
69475
+ * Atomically move a selected set of positions from this account to a destination account
69476
+ * (same authority, same group) using flashloans, auto-splitting across transactions as needed.
69477
+ *
69478
+ * @see {@link makeTransferPositionsTx} for detailed implementation
69479
+ */
69480
+ async makeTransferPositionsTx(params) {
69481
+ return makeTransferPositionsTx({
69482
+ ...params,
69483
+ marginfiAccount: this,
69484
+ overrideInferAccounts: {
69485
+ authority: this.authority,
69486
+ group: this.group,
69487
+ ...params.overrideInferAccounts
69488
+ }
69489
+ });
69490
+ }
69437
69491
  /**
69438
69492
  * Creates a transaction to repay debt using collateral.
69439
69493
  *
@@ -69893,6 +69947,499 @@ async function composeBridgedSwap(params) {
69893
69947
  if (!transactions) return null;
69894
69948
  return { transactions, firstLegQuote: firstLeg.quoteResponse, secondLegQuote: secondLeg.quoteResponse };
69895
69949
  }
69950
+ var MAX_BALANCES = 16;
69951
+ var DEFAULT_MAX_TRANSFER_POSITIONS = 5;
69952
+ var DEFAULT_BORROW_PADDING_BPS = 10;
69953
+ var CU_IXS = () => [
69954
+ web3_js.ComputeBudgetProgram.setComputeUnitLimit({ units: 14e5 }),
69955
+ web3_js.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1 })
69956
+ ];
69957
+ function requireBank(bankMap, address) {
69958
+ const bank = bankMap.get(address.toBase58());
69959
+ if (!bank) {
69960
+ throw TransactionBuildingError.transferPositionsInvalidSelection(
69961
+ `bank ${address.toBase58()} not found`,
69962
+ [address.toBase58()]
69963
+ );
69964
+ }
69965
+ return bank;
69966
+ }
69967
+ function requireTokenProgram(tokenProgramsByBank, address) {
69968
+ const tp = tokenProgramsByBank.get(address.toBase58());
69969
+ if (!tp) {
69970
+ throw TransactionBuildingError.transferPositionsInvalidSelection(
69971
+ `token program for bank ${address.toBase58()} not provided`,
69972
+ [address.toBase58()]
69973
+ );
69974
+ }
69975
+ return tp;
69976
+ }
69977
+ function classifyAndValidate(params) {
69978
+ const {
69979
+ marginfiAccount: accountA,
69980
+ destinationAccount: accountB,
69981
+ bankAddresses,
69982
+ bankMap,
69983
+ tokenProgramsByBank,
69984
+ assetShareValueMultiplierByBank
69985
+ } = params;
69986
+ const maxPositions = params.maxPositions ?? DEFAULT_MAX_TRANSFER_POSITIONS;
69987
+ if (bankAddresses.length === 0) {
69988
+ throw TransactionBuildingError.transferPositionsInvalidSelection("no positions selected", []);
69989
+ }
69990
+ if (bankAddresses.length > maxPositions) {
69991
+ throw TransactionBuildingError.transferPositionsInvalidSelection(
69992
+ `cannot transfer ${bankAddresses.length} positions in one transaction (max ${maxPositions}); select fewer and transfer in batches`,
69993
+ bankAddresses.map((b) => b.toBase58())
69994
+ );
69995
+ }
69996
+ const activeBalancesA = accountA.balances.filter((b) => b.active);
69997
+ const positions = [];
69998
+ for (const bankAddress of bankAddresses) {
69999
+ const bank = requireBank(bankMap, bankAddress);
70000
+ const tokenProgram = requireTokenProgram(tokenProgramsByBank, bankAddress);
70001
+ const balance = activeBalancesA.find((b) => b.bankPk.equals(bankAddress));
70002
+ if (!balance) {
70003
+ throw TransactionBuildingError.transferPositionsInvalidSelection(
70004
+ `source account has no active position in bank ${bankAddress.toBase58()}`,
70005
+ [bankAddress.toBase58()]
70006
+ );
70007
+ }
70008
+ const side = balance.assetShares.gt(0) ? "collateral" : "debt";
70009
+ const multiplier = assetShareValueMultiplierByBank.get(bankAddress.toBase58());
70010
+ const qty = computeQuantityUi(balance, bank, multiplier);
70011
+ positions.push({
70012
+ bankAddress,
70013
+ side,
70014
+ uiAmount: side === "collateral" ? qty.assets : qty.liabilities,
70015
+ bank,
70016
+ tokenProgram
70017
+ });
70018
+ }
70019
+ const isolatedDebts = positions.filter(
70020
+ (p) => p.side === "debt" && p.bank.config.riskTier === "Isolated" /* Isolated */
70021
+ );
70022
+ if (isolatedDebts.length > 0) {
70023
+ const otherDebts = positions.filter((p) => p.side === "debt").length > 1;
70024
+ const destHasLiabilities = (accountB?.balances ?? []).some(
70025
+ (b) => b.active && b.liabilityShares.gt(0)
70026
+ );
70027
+ if (isolatedDebts.length > 1 || otherDebts || destHasLiabilities) {
70028
+ throw TransactionBuildingError.transferPositionsInvalidSelection(
70029
+ "an isolated-tier debt can only be transferred as the destination account's sole liability",
70030
+ isolatedDebts.map((p) => p.bankAddress.toBase58())
70031
+ );
70032
+ }
70033
+ }
70034
+ if (accountB) {
70035
+ if (!accountB.group.equals(accountA.group)) {
70036
+ throw TransactionBuildingError.transferPositionsInvalidSelection(
70037
+ "destination account is in a different group",
70038
+ [accountB.address.toBase58()]
70039
+ );
70040
+ }
70041
+ if (!accountB.authority.equals(accountA.authority)) {
70042
+ throw TransactionBuildingError.transferPositionsInvalidSelection(
70043
+ "destination account has a different authority",
70044
+ [accountB.address.toBase58()]
70045
+ );
70046
+ }
70047
+ const overlap = positions.filter(
70048
+ (p) => accountB.balances.some((b) => b.active && b.bankPk.equals(p.bankAddress))
70049
+ );
70050
+ if (overlap.length > 0) {
70051
+ throw TransactionBuildingError.transferPositionsInvalidSelection(
70052
+ "destination account already holds a position in a transferred bank",
70053
+ overlap.map((p) => p.bankAddress.toBase58())
70054
+ );
70055
+ }
70056
+ const activeCountB = accountB.balances.filter((b) => b.active).length;
70057
+ if (activeCountB + positions.length > MAX_BALANCES) {
70058
+ throw TransactionBuildingError.transferPositionsInvalidSelection(
70059
+ `destination account cannot hold ${activeCountB + positions.length} positions (max ${MAX_BALANCES})`,
70060
+ positions.map((p) => p.bankAddress.toBase58())
70061
+ );
70062
+ }
70063
+ }
70064
+ return positions;
70065
+ }
70066
+ function buildIntegrationRefreshIxs(args) {
70067
+ const { accountA, destinationAccount, positions, bankMap, bankMetadataMap } = args;
70068
+ const transferredKaminoPks = positions.filter((p) => p.bank.config.assetTag === 3 /* KAMINO */).map((p) => p.bankAddress);
70069
+ const transferredJupPks = positions.filter((p) => p.bank.config.assetTag === 6 /* JUPLEND */).map((p) => p.bankAddress);
70070
+ const ixs = [];
70071
+ ixs.push(
70072
+ ...makeRefreshKaminoBanksIxs(accountA, bankMap, transferredKaminoPks, bankMetadataMap).instructions
70073
+ );
70074
+ if (destinationAccount) {
70075
+ ixs.push(
70076
+ ...makeRefreshKaminoBanksIxs(destinationAccount, bankMap, [], bankMetadataMap).instructions
70077
+ );
70078
+ }
70079
+ ixs.push(
70080
+ ...makeUpdateJupLendRateIxs(accountA, bankMap, transferredJupPks, bankMetadataMap).instructions
70081
+ );
70082
+ if (destinationAccount) {
70083
+ ixs.push(
70084
+ ...makeUpdateJupLendRateIxs(destinationAccount, bankMap, transferredJupPks, bankMetadataMap).instructions
70085
+ );
70086
+ }
70087
+ return ixs;
70088
+ }
70089
+ function dedupeBanks(banks) {
70090
+ const seen = /* @__PURE__ */ new Map();
70091
+ for (const bank of banks) seen.set(bank.address.toBase58(), bank);
70092
+ return [...seen.values()];
70093
+ }
70094
+ async function buildCollateralLegIxs(ctx, position, isSync, observationBanksOverride) {
70095
+ const { bank, tokenProgram, uiAmount } = position;
70096
+ const tag = bank.config.assetTag;
70097
+ const key = bank.address.toBase58();
70098
+ if (tag === 3 /* KAMINO */) {
70099
+ const reserve = ctx.bankMetadataMap[key]?.kaminoStates?.reserveState;
70100
+ if (!reserve) {
70101
+ throw TransactionBuildingError.transferPositionsInvalidSelection(
70102
+ `kamino reserve state missing for bank ${key} (populate bankMetadataMap.kaminoStates)`,
70103
+ [key]
70104
+ );
70105
+ }
70106
+ const multiplier = ctx.assetShareValueMultiplierByBank.get(key) ?? new BigNumber3.BigNumber(1);
70107
+ const cTokenAmount = uiAmount.div(multiplier);
70108
+ const withdraw2 = await makeKaminoWithdrawIx3({
70109
+ program: ctx.program,
70110
+ bank,
70111
+ bankMap: ctx.bankMap,
70112
+ tokenProgram,
70113
+ cTokenAmount,
70114
+ marginfiAccount: ctx.accountA,
70115
+ authority: ctx.accountA.authority,
70116
+ reserve,
70117
+ bankMetadataMap: ctx.bankMetadataMap,
70118
+ withdrawAll: true,
70119
+ isSync,
70120
+ opts: {
70121
+ createAtas: false,
70122
+ wrapAndUnwrapSol: false,
70123
+ overrideInferAccounts: ctx.overrideInferAccounts,
70124
+ observationBanksOverride
70125
+ }
70126
+ });
70127
+ const deposit2 = await makeKaminoDepositIx3({
70128
+ program: ctx.program,
70129
+ bank,
70130
+ tokenProgram,
70131
+ amount: uiAmount,
70132
+ accountAddress: ctx.accountB.address,
70133
+ authority: ctx.accountA.authority,
70134
+ group: ctx.accountB.group,
70135
+ reserve,
70136
+ isSync,
70137
+ opts: { wrapAndUnwrapSol: false, overrideInferAccounts: ctx.overrideInferAccounts }
70138
+ });
70139
+ return { withdrawIxs: withdraw2.instructions, depositIxs: deposit2.instructions };
70140
+ }
70141
+ if (tag === 6 /* JUPLEND */) {
70142
+ const jupLendingState = ctx.bankMetadataMap[key]?.jupLendStates?.jupLendingState;
70143
+ if (!jupLendingState) {
70144
+ throw TransactionBuildingError.transferPositionsInvalidSelection(
70145
+ `juplend lending state missing for bank ${key} (populate bankMetadataMap.jupLendStates)`,
70146
+ [key]
70147
+ );
70148
+ }
70149
+ const withdraw2 = await makeJuplendWithdrawIx2({
70150
+ program: ctx.program,
70151
+ bank,
70152
+ bankMap: ctx.bankMap,
70153
+ tokenProgram,
70154
+ amount: uiAmount,
70155
+ marginfiAccount: ctx.accountA,
70156
+ authority: ctx.accountA.authority,
70157
+ jupLendingState,
70158
+ bankMetadataMap: ctx.bankMetadataMap,
70159
+ withdrawAll: true,
70160
+ opts: {
70161
+ createAtas: false,
70162
+ wrapAndUnwrapSol: false,
70163
+ overrideInferAccounts: ctx.overrideInferAccounts,
70164
+ observationBanksOverride
70165
+ }
70166
+ });
70167
+ const deposit2 = await makeJuplendDepositIx2({
70168
+ program: ctx.program,
70169
+ bank,
70170
+ tokenProgram,
70171
+ amount: uiAmount,
70172
+ accountAddress: ctx.accountB.address,
70173
+ authority: ctx.accountA.authority,
70174
+ group: ctx.accountB.group,
70175
+ isSync,
70176
+ opts: { wrapAndUnwrapSol: false, overrideInferAccounts: ctx.overrideInferAccounts }
70177
+ });
70178
+ return { withdrawIxs: withdraw2.instructions, depositIxs: deposit2.instructions };
70179
+ }
70180
+ if (tag !== 0 /* DEFAULT */ && tag !== 1 /* SOL */ && tag !== 2 /* STAKED */) {
70181
+ throw TransactionBuildingError.transferPositionsUnsupportedBank(key, tag, bank.tokenSymbol);
70182
+ }
70183
+ const withdraw = await makeWithdrawIx3({
70184
+ program: ctx.program,
70185
+ bank,
70186
+ bankMap: ctx.bankMap,
70187
+ tokenProgram,
70188
+ amount: uiAmount,
70189
+ marginfiAccount: ctx.accountA,
70190
+ authority: ctx.accountA.authority,
70191
+ withdrawAll: true,
70192
+ bankMetadataMap: ctx.bankMetadataMap,
70193
+ isSync,
70194
+ opts: {
70195
+ createAtas: false,
70196
+ wrapAndUnwrapSol: false,
70197
+ overrideInferAccounts: ctx.overrideInferAccounts,
70198
+ observationBanksOverride
70199
+ }
70200
+ });
70201
+ const deposit = await makeDepositIx3({
70202
+ program: ctx.program,
70203
+ bank,
70204
+ tokenProgram,
70205
+ amount: uiAmount,
70206
+ accountAddress: ctx.accountB.address,
70207
+ authority: ctx.accountA.authority,
70208
+ group: ctx.accountB.group,
70209
+ isSync,
70210
+ opts: { wrapAndUnwrapSol: false, overrideInferAccounts: ctx.overrideInferAccounts }
70211
+ });
70212
+ return { withdrawIxs: withdraw.instructions, depositIxs: deposit.instructions };
70213
+ }
70214
+ async function buildInnerIxs(ctx, positions, isSync) {
70215
+ const collateral = positions.filter((p) => p.side === "collateral");
70216
+ const debts = positions.filter((p) => p.side === "debt");
70217
+ const collateralBanks = collateral.map((p) => p.bank);
70218
+ const withdrawIxs = [];
70219
+ const depositIxs = [];
70220
+ const borrowIxs = [];
70221
+ const repayIxs = [];
70222
+ for (const position of collateral) {
70223
+ const observationBanksOverride = ctx.groupRateLimiterEnabled ? computeHealthAccountMetas([], true, [position.bank]) : [];
70224
+ const legs = await buildCollateralLegIxs(ctx, position, isSync, observationBanksOverride);
70225
+ withdrawIxs.push(...legs.withdrawIxs);
70226
+ depositIxs.push(...legs.depositIxs);
70227
+ }
70228
+ const transferredKaminoPks = collateral.filter((p) => p.bank.config.assetTag === 3 /* KAMINO */).map((p) => p.bankAddress);
70229
+ const kaminoReRefreshIxs = transferredKaminoPks.length > 0 ? makeRefreshKaminoBanksIxs(
70230
+ ctx.accountA,
70231
+ ctx.bankMap,
70232
+ transferredKaminoPks,
70233
+ ctx.bankMetadataMap
70234
+ ).instructions : [];
70235
+ const borrowedSoFar = [];
70236
+ for (const position of debts) {
70237
+ const { bank, tokenProgram } = position;
70238
+ borrowedSoFar.push(bank);
70239
+ const activeBanks = dedupeBanks([
70240
+ ...ctx.destPreexistingBanks,
70241
+ ...collateralBanks,
70242
+ ...borrowedSoFar
70243
+ ]);
70244
+ const observationBanksOverride = computeHealthAccountMetas(activeBanks);
70245
+ const borrowUi = position.uiAmount.times(1 + ctx.borrowPaddingBps / 1e4);
70246
+ const borrow = await makeBorrowIx3({
70247
+ program: ctx.program,
70248
+ bank,
70249
+ bankMap: ctx.bankMap,
70250
+ tokenProgram,
70251
+ amount: borrowUi,
70252
+ marginfiAccount: ctx.accountB,
70253
+ authority: ctx.accountA.authority,
70254
+ isSync,
70255
+ opts: {
70256
+ createAtas: false,
70257
+ wrapAndUnwrapSol: false,
70258
+ overrideInferAccounts: ctx.overrideInferAccounts,
70259
+ observationBanksOverride
70260
+ }
70261
+ });
70262
+ borrowIxs.push(...borrow.instructions);
70263
+ const repay = await makeRepayIx3({
70264
+ program: ctx.program,
70265
+ bank,
70266
+ tokenProgram,
70267
+ amount: position.uiAmount,
70268
+ accountAddress: ctx.accountA.address,
70269
+ authority: ctx.accountA.authority,
70270
+ repayAll: true,
70271
+ isSync,
70272
+ opts: {
70273
+ wrapAndUnwrapSol: false,
70274
+ overrideInferAccounts: ctx.overrideInferAccounts
70275
+ }
70276
+ });
70277
+ repayIxs.push(...repay.instructions);
70278
+ }
70279
+ return [
70280
+ ...CU_IXS(),
70281
+ ...withdrawIxs,
70282
+ ...kaminoReRefreshIxs,
70283
+ ...depositIxs,
70284
+ ...borrowIxs,
70285
+ ...repayIxs
70286
+ ];
70287
+ }
70288
+ async function buildTransferFlashloanTx(args) {
70289
+ const { program, accountA, projectedActiveBanksA, innerIxs, preIxs, blockhash, luts } = args;
70290
+ const endIndex = preIxs.length + innerIxs.length + 1;
70291
+ const begin = await makeBeginFlashLoanIx3(program, accountA.address, endIndex, accountA.authority);
70292
+ const end = await makeEndFlashLoanIx3(
70293
+ program,
70294
+ accountA.address,
70295
+ projectedActiveBanksA,
70296
+ accountA.authority
70297
+ );
70298
+ const message = new web3_js.TransactionMessage({
70299
+ payerKey: accountA.authority,
70300
+ recentBlockhash: blockhash,
70301
+ instructions: [...preIxs, ...begin.instructions, ...innerIxs, ...end.instructions]
70302
+ }).compileToV0Message(luts);
70303
+ return addTransactionMetadata(new web3_js.VersionedTransaction(message), {
70304
+ addressLookupTables: luts,
70305
+ type: "FLASHLOAN" /* FLASHLOAN */
70306
+ });
70307
+ }
70308
+ function destPreexistingBanksOf(account, bankMap) {
70309
+ if (!account) return [];
70310
+ return dedupeBanks(
70311
+ account.balances.filter((b) => b.active).map((b) => bankMap.get(b.bankPk.toBase58())).filter((b) => Boolean(b))
70312
+ );
70313
+ }
70314
+ async function makeTransferPositionsTx(params) {
70315
+ const {
70316
+ program,
70317
+ connection,
70318
+ marginfiAccount: accountA,
70319
+ bankMap,
70320
+ bankMetadataMap,
70321
+ oraclePrices,
70322
+ assetShareValueMultiplierByBank,
70323
+ addressLookupTableAccounts,
70324
+ crossbarUrl,
70325
+ overrideInferAccounts
70326
+ } = params;
70327
+ const luts = addressLookupTableAccounts ?? [];
70328
+ const borrowPaddingBps = params.borrowPaddingBps ?? DEFAULT_BORROW_PADDING_BPS;
70329
+ const groupRateLimiterEnabled = params.groupRateLimiterEnabled ?? false;
70330
+ const positions = classifyAndValidate(params);
70331
+ let accountB = params.destinationAccount;
70332
+ let createIx;
70333
+ if (!accountB) {
70334
+ const accountIndex = params.createDestinationOpts?.accountIndex ?? await findRandomAvailableAccountIndex(
70335
+ connection,
70336
+ program.programId,
70337
+ accountA.group,
70338
+ accountA.authority
70339
+ );
70340
+ const created = await makeCreateAccountIxWithProjection({
70341
+ program,
70342
+ authority: accountA.authority,
70343
+ group: accountA.group,
70344
+ accountIndex,
70345
+ thirdPartyId: params.createDestinationOpts?.thirdPartyId
70346
+ });
70347
+ accountB = created.account;
70348
+ createIx = created.ix;
70349
+ }
70350
+ const ctx = {
70351
+ program,
70352
+ accountA,
70353
+ accountB,
70354
+ bankMap,
70355
+ bankMetadataMap,
70356
+ assetShareValueMultiplierByBank,
70357
+ borrowPaddingBps,
70358
+ groupRateLimiterEnabled,
70359
+ overrideInferAccounts,
70360
+ destPreexistingBanks: destPreexistingBanksOf(params.destinationAccount, bankMap)
70361
+ };
70362
+ const innerIxs = await buildInnerIxs(ctx, positions, false);
70363
+ const transferred = new Set(positions.map((p) => p.bankAddress.toBase58()));
70364
+ const projectedActiveBanksA = dedupeBanks(
70365
+ accountA.balances.filter((b) => b.active && !transferred.has(b.bankPk.toBase58())).map((b) => requireBank(bankMap, b.bankPk))
70366
+ );
70367
+ const blockhash = (await connection.getLatestBlockhash("confirmed")).blockhash;
70368
+ const preIxs = createIx ? [createIx] : [];
70369
+ const flashloanTx = await buildTransferFlashloanTx({
70370
+ program,
70371
+ accountA,
70372
+ projectedActiveBanksA,
70373
+ innerIxs,
70374
+ preIxs,
70375
+ blockhash,
70376
+ luts
70377
+ });
70378
+ const size = getTxSize(flashloanTx);
70379
+ const keys = getTotalAccountKeys(flashloanTx);
70380
+ if (size > MAX_TX_SIZE || keys > MAX_ACCOUNT_LOCKS) {
70381
+ throw TransactionBuildingError.transferPositionsUnsplittable(
70382
+ `built transaction exceeds size limits (${size} bytes, ${keys} accounts); transfer fewer positions`,
70383
+ size,
70384
+ keys
70385
+ );
70386
+ }
70387
+ const setupIxs = await makeSetupIx({
70388
+ connection,
70389
+ authority: accountA.authority,
70390
+ tokens: positions.map((p) => ({ mint: p.bank.mint, tokenProgram: p.tokenProgram }))
70391
+ });
70392
+ const refreshIxs = buildIntegrationRefreshIxs({
70393
+ accountA,
70394
+ destinationAccount: params.destinationAccount,
70395
+ positions,
70396
+ bankMap,
70397
+ bankMetadataMap
70398
+ });
70399
+ const additionalTxs = [];
70400
+ const preludeIxs = [...setupIxs, ...refreshIxs];
70401
+ if (preludeIxs.length > 0) {
70402
+ const txs = splitInstructionsToFitTransactions([], preludeIxs, {
70403
+ blockhash,
70404
+ payerKey: accountA.authority,
70405
+ luts
70406
+ });
70407
+ additionalTxs.push(
70408
+ ...txs.map(
70409
+ (tx) => addTransactionMetadata(tx, { type: "CREATE_ATA" /* CREATE_ATA */, addressLookupTables: luts })
70410
+ )
70411
+ );
70412
+ }
70413
+ const { instructions: updateFeedIxs, luts: feedLuts } = await makeSmartCrankSwbFeedIx({
70414
+ marginfiAccount: accountA,
70415
+ bankMap,
70416
+ oraclePrices,
70417
+ assetShareValueMultiplierByBank,
70418
+ instructions: innerIxs,
70419
+ program,
70420
+ connection,
70421
+ crossbarUrl
70422
+ });
70423
+ if (updateFeedIxs.length > 0) {
70424
+ const message = new web3_js.TransactionMessage({
70425
+ payerKey: accountA.authority,
70426
+ recentBlockhash: blockhash,
70427
+ instructions: updateFeedIxs
70428
+ }).compileToV0Message(feedLuts);
70429
+ additionalTxs.push(
70430
+ addTransactionMetadata(new web3_js.VersionedTransaction(message), {
70431
+ addressLookupTables: feedLuts,
70432
+ type: "CRANK" /* CRANK */
70433
+ })
70434
+ );
70435
+ }
70436
+ const transactions = [...additionalTxs, flashloanTx];
70437
+ return {
70438
+ transactions,
70439
+ actionTxIndex: additionalTxs.length,
70440
+ destinationAccount: accountB
70441
+ };
70442
+ }
69896
70443
 
69897
70444
  // src/services/account/services/account-simulation.service.ts
69898
70445
  async function simulateAccountHealthCacheWithFallback(params) {
@@ -70302,7 +70849,7 @@ var GROUP_OFFSET = 8;
70302
70849
  var BALANCES_OFFSET = 72;
70303
70850
  var BALANCE_SIZE = 104;
70304
70851
  var BANK_PK_OFFSET_IN_BALANCE = 1;
70305
- var MAX_BALANCES = 16;
70852
+ var MAX_BALANCES2 = 16;
70306
70853
  var scanBankSlots = async (program, group, bank, options) => {
70307
70854
  const connection = program.provider.connection;
70308
70855
  const programId = program.programId;
@@ -70310,7 +70857,7 @@ var scanBankSlots = async (program, group, bank, options) => {
70310
70857
  const groupBytes = group.toBase58();
70311
70858
  const bankBytes = bank.toBase58();
70312
70859
  const slotOffsets = Array.from(
70313
- { length: MAX_BALANCES },
70860
+ { length: MAX_BALANCES2 },
70314
70861
  (_, n) => BALANCES_OFFSET + BANK_PK_OFFSET_IN_BALANCE + n * BALANCE_SIZE
70315
70862
  );
70316
70863
  const scanSlot = (offset) => {
@@ -74341,6 +74888,36 @@ var MarginfiAccountWrapper = class {
74341
74888
  };
74342
74889
  return this.account.makeLoopTx(fullParams);
74343
74890
  }
74891
+ /**
74892
+ * Atomically move a selected set of positions from this account to a destination account with
74893
+ * auto-injected client data.
74894
+ *
74895
+ * Auto-injects: program, connection, marginfiAccount, bankMap, oraclePrices, bankMetadataMap,
74896
+ * assetShareValueMultiplierByBank, addressLookupTables, tokenProgramsByBank, groupRateLimiterEnabled.
74897
+ */
74898
+ async makeTransferPositionsTx(params) {
74899
+ const tokenProgramsByBank = /* @__PURE__ */ new Map();
74900
+ for (const bankAddress of params.bankAddresses) {
74901
+ const bank = this.client.bankMap.get(bankAddress.toBase58());
74902
+ if (!bank) throw new Error(`Bank ${bankAddress.toBase58()} not found`);
74903
+ const mintData = await this.getMintDataFromBank(bank);
74904
+ tokenProgramsByBank.set(bankAddress.toBase58(), mintData.tokenProgram);
74905
+ }
74906
+ const fullParams = {
74907
+ ...params,
74908
+ program: this.client.program,
74909
+ connection: this.client.program.provider.connection,
74910
+ marginfiAccount: this.account,
74911
+ bankMap: this.client.bankMap,
74912
+ oraclePrices: this.client.oraclePriceByBank,
74913
+ bankMetadataMap: this.client.bankIntegrationMap,
74914
+ assetShareValueMultiplierByBank: this.client.assetShareValueMultiplierByBank,
74915
+ addressLookupTableAccounts: this.client.addressLookupTables,
74916
+ tokenProgramsByBank,
74917
+ groupRateLimiterEnabled: isGroupRateLimiterEnabled(this.client.group.rateLimiter)
74918
+ };
74919
+ return this.account.makeTransferPositionsTx(fullParams);
74920
+ }
74344
74921
  /**
74345
74922
  * Creates a repay with collateral transaction with auto-injected client data.
74346
74923
  *
@@ -75285,6 +75862,7 @@ exports.bankRateLimiterRawToDto = bankRateLimiterRawToDto;
75285
75862
  exports.bankRawToDto = bankRawToDto;
75286
75863
  exports.bigNumberToWrappedI80F48 = bigNumberToWrappedI80F48;
75287
75864
  exports.bpsToPercentile = bpsToPercentile;
75865
+ exports.buildCollateralLegIxs = buildCollateralLegIxs;
75288
75866
  exports.calculateApyFromInterest = calculateApyFromInterest;
75289
75867
  exports.calculateInterestFromApy = calculateInterestFromApy;
75290
75868
  exports.capConfidenceInterval = capConfidenceInterval;
@@ -75296,6 +75874,7 @@ exports.checkTitanFeeAccount = checkTitanFeeAccount;
75296
75874
  exports.chunkedGetRawMultipleAccountInfoOrdered = chunkedGetRawMultipleAccountInfoOrdered;
75297
75875
  exports.chunkedGetRawMultipleAccountInfoOrderedWithNulls = chunkedGetRawMultipleAccountInfoOrderedWithNulls;
75298
75876
  exports.chunkedGetRawMultipleAccountInfos = chunkedGetRawMultipleAccountInfos;
75877
+ exports.classifyAndValidate = classifyAndValidate;
75299
75878
  exports.compileFlashloanPrecheck = compileFlashloanPrecheck;
75300
75879
  exports.composeBridgedSwap = composeBridgedSwap;
75301
75880
  exports.composeRemainingAccounts = composeRemainingAccounts;
@@ -75530,6 +76109,7 @@ exports.makeSetupIx = makeSetupIx;
75530
76109
  exports.makeSmartCrankSwbFeedIx = makeSmartCrankSwbFeedIx;
75531
76110
  exports.makeSwapCollateralTx = makeSwapCollateralTx;
75532
76111
  exports.makeSwapDebtTx = makeSwapDebtTx;
76112
+ exports.makeTransferPositionsTx = makeTransferPositionsTx;
75533
76113
  exports.makeTxPriorityIx = makeTxPriorityIx;
75534
76114
  exports.makeUnwrapSolIx = makeUnwrapSolIx;
75535
76115
  exports.makeUpdateDriftMarketIxs = makeUpdateDriftMarketIxs;