@exponent-labs/exponent-sdk 0.9.0 → 0.9.2

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.
Files changed (155) hide show
  1. package/build/client/vaults/index.d.ts +2 -0
  2. package/build/client/vaults/index.js +2 -0
  3. package/build/client/vaults/index.js.map +1 -1
  4. package/build/client/vaults/types/index.d.ts +2 -0
  5. package/build/client/vaults/types/index.js +2 -0
  6. package/build/client/vaults/types/index.js.map +1 -1
  7. package/build/client/vaults/types/kaminoFarmEntry.d.ts +15 -0
  8. package/build/client/vaults/types/kaminoFarmEntry.js +17 -0
  9. package/build/client/vaults/types/kaminoFarmEntry.js.map +1 -0
  10. package/build/client/vaults/types/kaminoObligationEntry.d.ts +21 -4
  11. package/build/client/vaults/types/kaminoObligationEntry.js +2 -1
  12. package/build/client/vaults/types/kaminoObligationEntry.js.map +1 -1
  13. package/build/client/vaults/types/positionUpdate.d.ts +9 -0
  14. package/build/client/vaults/types/positionUpdate.js +23 -0
  15. package/build/client/vaults/types/positionUpdate.js.map +1 -1
  16. package/build/client/vaults/types/proposalAction.js +0 -3
  17. package/build/client/vaults/types/proposalAction.js.map +1 -1
  18. package/build/client/vaults/types/reserveFarmMapping.d.ts +19 -0
  19. package/build/client/vaults/types/reserveFarmMapping.js +18 -0
  20. package/build/client/vaults/types/reserveFarmMapping.js.map +1 -0
  21. package/build/client/vaults/types/strategyPosition.d.ts +5 -0
  22. package/build/client/vaults/types/strategyPosition.js +5 -0
  23. package/build/client/vaults/types/strategyPosition.js.map +1 -1
  24. package/build/exponentVaults/aumCalculator.d.ts +25 -4
  25. package/build/exponentVaults/aumCalculator.js +236 -15
  26. package/build/exponentVaults/aumCalculator.js.map +1 -1
  27. package/build/exponentVaults/fetcher.d.ts +52 -0
  28. package/build/exponentVaults/fetcher.js +199 -0
  29. package/build/exponentVaults/fetcher.js.map +1 -0
  30. package/build/exponentVaults/index.d.ts +10 -9
  31. package/build/exponentVaults/index.js +26 -8
  32. package/build/exponentVaults/index.js.map +1 -1
  33. package/build/exponentVaults/kamino-farms.d.ts +144 -0
  34. package/build/exponentVaults/kamino-farms.js +396 -0
  35. package/build/exponentVaults/kamino-farms.js.map +1 -0
  36. package/build/exponentVaults/loopscale/client.d.ts +240 -0
  37. package/build/exponentVaults/loopscale/client.js +590 -0
  38. package/build/exponentVaults/loopscale/client.js.map +1 -0
  39. package/build/exponentVaults/loopscale/client.test.d.ts +1 -0
  40. package/build/exponentVaults/loopscale/client.test.js +183 -0
  41. package/build/exponentVaults/loopscale/client.test.js.map +1 -0
  42. package/build/exponentVaults/loopscale/helpers.d.ts +29 -0
  43. package/build/exponentVaults/loopscale/helpers.js +119 -0
  44. package/build/exponentVaults/loopscale/helpers.js.map +1 -0
  45. package/build/exponentVaults/loopscale/index.d.ts +3 -0
  46. package/build/exponentVaults/loopscale/index.js +12 -0
  47. package/build/exponentVaults/loopscale/index.js.map +1 -0
  48. package/build/exponentVaults/loopscale/prepared-transactions.d.ts +13 -0
  49. package/build/exponentVaults/loopscale/prepared-transactions.js +271 -0
  50. package/build/exponentVaults/loopscale/prepared-transactions.js.map +1 -0
  51. package/build/exponentVaults/loopscale/prepared-transactions.test.d.ts +1 -0
  52. package/build/exponentVaults/loopscale/prepared-transactions.test.js +400 -0
  53. package/build/exponentVaults/loopscale/prepared-transactions.test.js.map +1 -0
  54. package/build/exponentVaults/loopscale/prepared-types.d.ts +62 -0
  55. package/build/exponentVaults/loopscale/prepared-types.js +3 -0
  56. package/build/exponentVaults/loopscale/prepared-types.js.map +1 -0
  57. package/build/exponentVaults/loopscale/response-plan.d.ts +69 -0
  58. package/build/exponentVaults/loopscale/response-plan.js +141 -0
  59. package/build/exponentVaults/loopscale/response-plan.js.map +1 -0
  60. package/build/exponentVaults/loopscale/response-plan.test.d.ts +1 -0
  61. package/build/exponentVaults/loopscale/response-plan.test.js +139 -0
  62. package/build/exponentVaults/loopscale/response-plan.test.js.map +1 -0
  63. package/build/exponentVaults/loopscale/send-plan.d.ts +75 -0
  64. package/build/exponentVaults/loopscale/send-plan.js +235 -0
  65. package/build/exponentVaults/loopscale/send-plan.js.map +1 -0
  66. package/build/exponentVaults/loopscale/types.d.ts +443 -0
  67. package/build/exponentVaults/loopscale/types.js +3 -0
  68. package/build/exponentVaults/loopscale/types.js.map +1 -0
  69. package/build/exponentVaults/loopscale-client.d.ts +113 -524
  70. package/build/exponentVaults/loopscale-client.js +296 -539
  71. package/build/exponentVaults/loopscale-client.js.map +1 -1
  72. package/build/exponentVaults/loopscale-client.test.d.ts +1 -0
  73. package/build/exponentVaults/loopscale-client.test.js +162 -0
  74. package/build/exponentVaults/loopscale-client.test.js.map +1 -0
  75. package/build/exponentVaults/loopscale-client.types.d.ts +425 -0
  76. package/build/exponentVaults/loopscale-client.types.js +3 -0
  77. package/build/exponentVaults/loopscale-client.types.js.map +1 -0
  78. package/build/exponentVaults/loopscale-execution.d.ts +125 -0
  79. package/build/exponentVaults/loopscale-execution.js +341 -0
  80. package/build/exponentVaults/loopscale-execution.js.map +1 -0
  81. package/build/exponentVaults/loopscale-execution.test.d.ts +1 -0
  82. package/build/exponentVaults/loopscale-execution.test.js +139 -0
  83. package/build/exponentVaults/loopscale-execution.test.js.map +1 -0
  84. package/build/exponentVaults/loopscale-vault.d.ts +115 -0
  85. package/build/exponentVaults/loopscale-vault.js +275 -0
  86. package/build/exponentVaults/loopscale-vault.js.map +1 -0
  87. package/build/exponentVaults/loopscale-vault.test.d.ts +1 -0
  88. package/build/exponentVaults/loopscale-vault.test.js +102 -0
  89. package/build/exponentVaults/loopscale-vault.test.js.map +1 -0
  90. package/build/exponentVaults/policyBuilders.d.ts +62 -0
  91. package/build/exponentVaults/policyBuilders.js +119 -2
  92. package/build/exponentVaults/policyBuilders.js.map +1 -1
  93. package/build/exponentVaults/pricePathResolver.d.ts +45 -0
  94. package/build/exponentVaults/pricePathResolver.js +198 -0
  95. package/build/exponentVaults/pricePathResolver.js.map +1 -0
  96. package/build/exponentVaults/pricePathResolver.test.d.ts +1 -0
  97. package/build/exponentVaults/pricePathResolver.test.js +369 -0
  98. package/build/exponentVaults/pricePathResolver.test.js.map +1 -0
  99. package/build/exponentVaults/syncTransaction.js +4 -1
  100. package/build/exponentVaults/syncTransaction.js.map +1 -1
  101. package/build/exponentVaults/titan-quote.js +170 -36
  102. package/build/exponentVaults/titan-quote.js.map +1 -1
  103. package/build/exponentVaults/vault-instruction-types.d.ts +363 -0
  104. package/build/exponentVaults/vault-instruction-types.js +128 -0
  105. package/build/exponentVaults/vault-instruction-types.js.map +1 -0
  106. package/build/exponentVaults/vault-interaction.d.ts +203 -343
  107. package/build/exponentVaults/vault-interaction.js +1894 -426
  108. package/build/exponentVaults/vault-interaction.js.map +1 -1
  109. package/build/exponentVaults/vault-interaction.kamino-vault.test.d.ts +1 -0
  110. package/build/exponentVaults/vault-interaction.kamino-vault.test.js +143 -0
  111. package/build/exponentVaults/vault-interaction.kamino-vault.test.js.map +1 -0
  112. package/build/exponentVaults/vault.d.ts +51 -2
  113. package/build/exponentVaults/vault.js +324 -48
  114. package/build/exponentVaults/vault.js.map +1 -1
  115. package/build/exponentVaults/vaultTransactionBuilder.d.ts +100 -134
  116. package/build/exponentVaults/vaultTransactionBuilder.js +383 -285
  117. package/build/exponentVaults/vaultTransactionBuilder.js.map +1 -1
  118. package/build/exponentVaults/vaultTransactionBuilder.test.d.ts +1 -0
  119. package/build/exponentVaults/vaultTransactionBuilder.test.js +297 -0
  120. package/build/exponentVaults/vaultTransactionBuilder.test.js.map +1 -0
  121. package/build/marketThree.d.ts +6 -2
  122. package/build/marketThree.js +10 -8
  123. package/build/marketThree.js.map +1 -1
  124. package/package.json +34 -32
  125. package/src/client/vaults/index.ts +2 -0
  126. package/src/client/vaults/types/index.ts +2 -0
  127. package/src/client/vaults/types/kaminoFarmEntry.ts +32 -0
  128. package/src/client/vaults/types/kaminoObligationEntry.ts +6 -3
  129. package/src/client/vaults/types/positionUpdate.ts +62 -0
  130. package/src/client/vaults/types/proposalAction.ts +0 -3
  131. package/src/client/vaults/types/reserveFarmMapping.ts +35 -0
  132. package/src/client/vaults/types/strategyPosition.ts +18 -1
  133. package/src/exponentVaults/aumCalculator.ts +353 -16
  134. package/src/exponentVaults/fetcher.ts +257 -0
  135. package/src/exponentVaults/index.ts +65 -40
  136. package/src/exponentVaults/kamino-farms.ts +538 -0
  137. package/src/exponentVaults/loopscale/client.ts +808 -0
  138. package/src/exponentVaults/loopscale/helpers.ts +172 -0
  139. package/src/exponentVaults/loopscale/index.ts +57 -0
  140. package/src/exponentVaults/loopscale/prepared-transactions.ts +435 -0
  141. package/src/exponentVaults/loopscale/prepared-types.ts +73 -0
  142. package/src/exponentVaults/loopscale/types.ts +466 -0
  143. package/src/exponentVaults/policyBuilders.ts +170 -0
  144. package/src/exponentVaults/pricePathResolver.test.ts +466 -0
  145. package/src/exponentVaults/pricePathResolver.ts +273 -0
  146. package/src/exponentVaults/syncTransaction.ts +6 -1
  147. package/src/exponentVaults/titan-quote.ts +231 -45
  148. package/src/exponentVaults/vault-instruction-types.ts +493 -0
  149. package/src/exponentVaults/vault-interaction.kamino-vault.test.ts +149 -0
  150. package/src/exponentVaults/vault-interaction.ts +2818 -799
  151. package/src/exponentVaults/vault.ts +474 -63
  152. package/src/exponentVaults/vaultTransactionBuilder.test.ts +349 -0
  153. package/src/exponentVaults/vaultTransactionBuilder.ts +581 -433
  154. package/src/marketThree.ts +14 -6
  155. package/src/exponentVaults/loopscale-client.ts +0 -1373
@@ -1,33 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.bundleForJito = exports.VaultTransactionBuilder = void 0;
3
+ exports.VaultTransactionBuilder = void 0;
4
4
  const web3_js_1 = require("@solana/web3.js");
5
- const loopscale_client_1 = require("./loopscale-client");
5
+ const loopscale_1 = require("./loopscale");
6
6
  const vault_interaction_1 = require("./vault-interaction");
7
7
  const syncTransaction_1 = require("./syncTransaction");
8
- // ============================================================================
9
- // Jito constants
10
- // ============================================================================
11
- const JITO_TIP_ADDRESSES = [
12
- new web3_js_1.PublicKey("96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5"),
13
- new web3_js_1.PublicKey("HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe"),
14
- new web3_js_1.PublicKey("Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY"),
15
- new web3_js_1.PublicKey("ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49"),
16
- new web3_js_1.PublicKey("DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh"),
17
- new web3_js_1.PublicKey("ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt"),
18
- new web3_js_1.PublicKey("DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL"),
19
- new web3_js_1.PublicKey("3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT"),
20
- ];
21
- const JITO_REGIONAL_URLS = [
22
- "https://amsterdam.mainnet.block-engine.jito.wtf",
23
- "https://frankfurt.mainnet.block-engine.jito.wtf",
24
- "https://ny.mainnet.block-engine.jito.wtf",
25
- "https://tokyo.mainnet.block-engine.jito.wtf",
26
- ];
27
- const JITO_MAINNET_URL = "https://mainnet.block-engine.jito.wtf";
28
- const JITO_BUNDLES_PATH = "/api/v1/bundles";
29
- const JITO_MAX_RETRIES = 10;
30
- const JITO_RETRY_DELAY_MS = 1000;
8
+ const environment_1 = require("../environment");
9
+ const SCOPE_PROGRAM_ID = new web3_js_1.PublicKey("HFn8GnPADiny6XqUoWE8uRPPxb29ikn4yTuPa9MF2fWJ");
31
10
  // ============================================================================
32
11
  // VaultTransactionBuilder
33
12
  // ============================================================================
@@ -35,42 +14,44 @@ const JITO_RETRY_DELAY_MS = 1000;
35
14
  * Orchestrates strategy vault transaction construction.
36
15
  *
37
16
  * Replaces the per-recipe boilerplate of manually assembling compute budgets,
38
- * price refresh instructions, sync transaction wrapping, ALT gathering, and
39
- * setup transaction splitting.
17
+ * sync transaction wrapping, ALT gathering, setup transaction splitting, and
18
+ * forward-looking Kamino/CLMM/yield position tracking.
40
19
  *
41
- * **Lifecycle:** create → add actions → build → sendJitoBundle
20
+ * **Lifecycle:** create → add actions → build → send()
42
21
  *
43
22
  * @example Single action
44
23
  * ```ts
45
24
  * const result = await VaultTransactionBuilder
46
- * .create({ vault, connection, signer: manager.publicKey, vaultPda, pricesAccount })
25
+ * .create({ vault, connection, signer: manager.publicKey })
47
26
  * .addActions([clmmAction.buyPt({ market, ptOutMin, syInMax })])
48
27
  * .build()
49
28
  *
50
- * await result.sendJitoBundle({ signers: [manager], tipLamports: 10_000 })
29
+ * await result.send({ signers: [manager] })
51
30
  * ```
52
31
  *
53
32
  * @example Multi-step — each addActions() becomes its own sync transaction
54
33
  * ```ts
55
34
  * const result = await VaultTransactionBuilder
56
- * .create({ vault, connection, signer: manager.publicKey, vaultPda, pricesAccount })
35
+ * .create({ vault, connection, signer: manager.publicKey })
57
36
  * .addActions([kaminoAction.deposit("MAIN", "USDC", amount)]) // sync tx 1
58
37
  * .addActions([kaminoAction.borrow("MAIN", "SOL", amount)]) // sync tx 2
59
38
  * .build()
60
39
  *
61
- * await result.sendJitoBundle({ signers: [manager], tipLamports: 10_000 })
40
+ * await result.send({ signers: [manager] })
62
41
  * ```
63
42
  *
64
43
  * @example Mixed Kamino + Loopscale — co-signing handled automatically
65
44
  * ```ts
45
+ * const loopscaleDeposit = await loopscale.depositStrategy({ ... })
46
+ *
66
47
  * const result = await VaultTransactionBuilder
67
- * .create({ vault, connection, signer: manager.publicKey, vaultPda, pricesAccount })
48
+ * .create({ vault, connection, signer: manager.publicKey })
68
49
  * .addActions([kaminoAction.deposit(...)]) // signed normally
69
- * .addLoopscaleActions([loopscaleAction.createStrategy({ ... })]) // co-signed automatically
50
+ * .addLoopscaleResponse(loopscaleDeposit) // co-signed automatically
70
51
  * .build()
71
52
  *
72
- * // sendJitoBundle auto-creates a LoopscaleClient and co-signs Loopscale steps
73
- * await result.sendJitoBundle({ signers: [manager], tipLamports: 10_000 })
53
+ * // send() auto-creates a LoopscaleClient for Loopscale co-signing
54
+ * await result.send({ signers: [manager] })
74
55
  * ```
75
56
  */
76
57
  class VaultTransactionBuilder {
@@ -83,7 +64,7 @@ class VaultTransactionBuilder {
83
64
  /**
84
65
  * Create a new builder instance.
85
66
  *
86
- * @param config - Builder configuration including the vault, connection, signer, and prices.
67
+ * @param config - Builder configuration including the vault, connection, and signer.
87
68
  * @returns A new {@link VaultTransactionBuilder} ready for action chaining.
88
69
  */
89
70
  static create(config) {
@@ -115,70 +96,143 @@ class VaultTransactionBuilder {
115
96
  addActions(instructions, options) {
116
97
  this.stepCounter++;
117
98
  const label = options?.label ?? `step-${this.stepCounter}`;
118
- this.steps.push({ label, instructions: [...instructions], isLoopscale: false, options: options ?? {} });
99
+ this.steps.push({ kind: "instructions", label, instructions: [...instructions], options: options ?? {} });
119
100
  return this;
120
101
  }
121
102
  /**
122
- * Add Loopscale vault instructions as a new step.
103
+ * Add a raw Loopscale response as a new step.
123
104
  *
124
- * Each call creates a separate Squads sync transaction, marked for Loopscale co-signing.
125
- * At send time, these steps are automatically co-signed via an internally created
126
- * LoopscaleClient no separate configuration needed.
105
+ * The builder will flatten the response into manager-facing transactions,
106
+ * preserving Loopscale-signed segments while keeping local setup/top-level
107
+ * transactions outside the Loopscale signing domain.
127
108
  *
128
- * **Warning:** Passing multiple Loopscale actions in a single array packs them into one
129
- * sync transaction. This can exceed the 1232-byte limit the builder will throw at
130
- * build time if oversized. Loopscale transactions are particularly account-heavy;
131
- * prefer one action per call.
109
+ * @param response - Raw Loopscale response returned by {@link LoopscaleClient}.
110
+ * Only `label` is supported for Loopscale steps. Compute overrides are
111
+ * preserved on the raw Loopscale response and are not applied by the
112
+ * builder.
132
113
  *
133
- * @param instructions - Array of Loopscale VaultInstruction descriptors (from loopscaleAction).
134
- * @param options - Optional per-step compute budget overrides and label.
114
+ * @param options - Optional step label.
135
115
  * @returns `this` for chaining.
136
116
  */
137
- addLoopscaleActions(instructions, options) {
117
+ addLoopscaleResponse(response, options) {
138
118
  this.stepCounter++;
139
119
  const label = options?.label ?? `loopscale-step-${this.stepCounter}`;
140
- this.steps.push({ label, instructions: [...instructions], isLoopscale: true, options: options ?? {} });
120
+ this.steps.push({ kind: "loopscale-response", label, response, options: options ?? {} });
141
121
  return this;
142
122
  }
143
123
  /**
144
124
  * Build TransactionSets from the configured steps.
145
125
  *
146
126
  * Produces an ordered array of TransactionSets:
147
- * 1. **Price refresh** (index 0) oracle price update instructions
148
- * 2. **Setup transactions** — token account creation (only if needed)
149
- * 3. **Sync transactions** — one per step, wrapped in Squads sync transaction
127
+ * 1. **Setup transactions** token account creation / builder-managed setup (only if needed)
128
+ * 2. **Sync transactions** — one per step, wrapped in Squads sync transaction
129
+ *
130
+ * Same-transaction `UpdatePrice` instructions are injected directly into any
131
+ * setup or sync set that can hit AUM recalculation.
150
132
  *
151
- * The price refresh must land in the same slot as the sync transactions.
152
- * Use `result.sendJitoBundle()` for atomic same-slot execution.
133
+ * Use `result.send()` for sequential execution.
153
134
  *
154
- * @returns A {@link VaultTransactionBuildResult} with TransactionSets and a sendJitoBundle method.
135
+ * @returns A {@link VaultTransactionBuildResult} with TransactionSets and a send method.
155
136
  * @throws If no steps have been configured (call addActions first).
156
137
  * @throws If any assembled transaction exceeds the 1232-byte Solana packet limit.
157
138
  */
158
139
  async build() {
159
140
  if (this.steps.length === 0) {
160
- throw new Error("No steps configured. Call addActions() or addLoopscaleActions() before build().");
141
+ throw new Error("No steps configured. Call addActions() or addLoopscaleResponse() before build().");
161
142
  }
162
- const { vault, connection, signer, vaultPda, pricesAccount } = this.config;
143
+ const { vault, connection, signer, pricesAccount } = this.config;
144
+ const vaultPda = this.config.vaultPda ?? vault.state.squadsVault;
145
+ const extraLookupTableAddresses = this.config.lookupTableAddresses ?? [];
146
+ const autoManagePositions = this.config.autoManagePositions ?? true;
163
147
  const squadsProgram = this.config.squadsProgram ?? syncTransaction_1.SQUADS_PROGRAM_ID;
164
148
  const defaultCU = this.config.computeUnitLimit ?? 1_400_000;
165
149
  const defaultHeap = this.config.heapFrameBytes ?? 256 * 1024;
166
- // 1. Price refresh TransactionSet
167
- const priceOpts = this.config.priceRefreshOptions ?? {};
168
- const priceIxs = await vault.ixsUpdateStrategyVaultPrices(pricesAccount);
169
- const priceSet = {
170
- label: priceOpts.label ?? "price-refresh",
171
- instructions: buildComputeBudgetIxs(priceOpts.computeUnitLimit ?? defaultCU, priceOpts.heapFrameBytes ?? defaultHeap, priceOpts.priorityFee).concat(priceIxs),
172
- signers: [],
173
- addressLookupTableAddresses: vault.state.addressLookupTable
174
- ? [vault.state.addressLookupTable]
175
- : [],
176
- };
177
- // 2. Per-step sync TransactionSets
178
- const transactionSets = [priceSet];
179
- const allAltAddresses = new Set(priceSet.addressLookupTableAddresses.map((a) => a.toBase58()));
150
+ const transactionSets = [];
151
+ const allAltAddresses = new Set([
152
+ ...(vault.state.addressLookupTable ? [vault.state.addressLookupTable.toBase58()] : []),
153
+ ...extraLookupTableAddresses.map((address) => address.toBase58()),
154
+ ]);
155
+ // Share a single setup context across all steps so that tracked accounts,
156
+ // positions, and orderbooks carry over — preventing duplicate setup txs.
157
+ const sharedSetupContext = (0, vault_interaction_1.createStrategySetupContext)({
158
+ connection,
159
+ env: environment_1.LOCAL_ENV,
160
+ owner: vaultPda,
161
+ signer,
162
+ vaultAddress: vault.selfAddress,
163
+ vaultPda,
164
+ accountIndex: 0,
165
+ squadsProgram,
166
+ autoManagePositions,
167
+ pricesAccount,
168
+ });
169
+ let loopscaleClient;
180
170
  for (const step of this.steps) {
181
- const syncResult = await (0, vault_interaction_1.createVaultSyncTransaction)({
171
+ if (step.kind === "loopscale-response") {
172
+ loopscaleClient ??= createBuilderLoopscaleClient({
173
+ connection,
174
+ vaultPda,
175
+ loopscale: this.config.loopscale,
176
+ });
177
+ const preparedTransactions = await loopscaleClient.prepareVaultTransactions({
178
+ response: step.response,
179
+ context: {
180
+ connection,
181
+ signer,
182
+ vaultPda,
183
+ vaultAddress: vault.selfAddress,
184
+ squadsProgram,
185
+ },
186
+ });
187
+ const stepPriceRefreshIxs = await (0, vault_interaction_1.buildSetupStatePriceRefreshInstructions)(sharedSetupContext);
188
+ for (const [preparedIndex, preparedTransaction] of preparedTransactions.entries()) {
189
+ const txLabel = preparedTransactions.length === 1
190
+ ? step.label
191
+ : `${step.label}-${preparedIndex + 1}`;
192
+ if (preparedTransaction.setupInstructions.length > 0) {
193
+ const setupAltAddresses = mergeAddressLookupTableAddresses(extraLookupTableAddresses, vault.state.addressLookupTable ? [vault.state.addressLookupTable] : []);
194
+ const setupSet = {
195
+ label: `${txLabel}-setup`,
196
+ instructions: [
197
+ web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: defaultCU }),
198
+ ...preparedTransaction.setupInstructions,
199
+ ],
200
+ signers: [],
201
+ addressLookupTableAddresses: setupAltAddresses,
202
+ coSignWithLoopscale: false,
203
+ };
204
+ if (touchesManagedVaultPrograms(setupSet.instructions, vault.programId, squadsProgram)) {
205
+ setupSet.instructions = insertAfterLeadingComputeBudgetAndScopeInstructions(setupSet.instructions, stepPriceRefreshIxs);
206
+ }
207
+ transactionSets.push(setupSet);
208
+ for (const alt of setupSet.addressLookupTableAddresses) {
209
+ allAltAddresses.add(alt.toBase58());
210
+ }
211
+ }
212
+ const shouldInjectLocalLoopscalePriceRefresh = (!preparedTransaction.requiresLoopscaleCoSign
213
+ && touchesManagedVaultPrograms(preparedTransaction.instructions, vault.programId, squadsProgram));
214
+ const loopscaleSet = {
215
+ label: txLabel,
216
+ instructions: preparedTransaction.instructions,
217
+ signers: preparedTransaction.signers,
218
+ addressLookupTableAddresses: shouldInjectLocalLoopscalePriceRefresh
219
+ ? mergeAddressLookupTableAddresses(preparedTransaction.addressLookupTableAddresses, extraLookupTableAddresses, vault.state.addressLookupTable ? [vault.state.addressLookupTable] : [])
220
+ : mergeAddressLookupTableAddresses(preparedTransaction.addressLookupTableAddresses, extraLookupTableAddresses),
221
+ coSignWithLoopscale: preparedTransaction.requiresLoopscaleCoSign,
222
+ };
223
+ if (shouldInjectLocalLoopscalePriceRefresh) {
224
+ loopscaleSet.instructions = insertAfterLeadingComputeBudgetAndScopeInstructions(loopscaleSet.instructions, stepPriceRefreshIxs);
225
+ }
226
+ transactionSets.push(loopscaleSet);
227
+ for (const alt of loopscaleSet.addressLookupTableAddresses) {
228
+ allAltAddresses.add(alt.toBase58());
229
+ }
230
+ }
231
+ continue;
232
+ }
233
+ const stepCU = step.options.computeUnitLimit ?? defaultCU;
234
+ const stepHeap = step.options.heapFrameBytes ?? defaultHeap;
235
+ const syncResults = await (0, vault_interaction_1.createVaultSyncTransactions)({
182
236
  instructions: step.instructions,
183
237
  owner: vaultPda,
184
238
  connection,
@@ -186,171 +240,172 @@ class VaultTransactionBuilder {
186
240
  signer,
187
241
  vaultAddress: vault.selfAddress,
188
242
  squadsProgram,
243
+ autoManagePositions,
244
+ setupContext: sharedSetupContext,
189
245
  });
190
- if (syncResult.setupInstructions.length > 0) {
191
- transactionSets.push({
192
- label: `${step.label}-setup`,
246
+ const stepPriceRefreshIxs = await (0, vault_interaction_1.buildSetupStatePriceRefreshInstructions)(sharedSetupContext);
247
+ for (const [syncIndex, syncResult] of syncResults.entries()) {
248
+ const txLabel = syncResults.length === 1
249
+ ? step.label
250
+ : `${step.label}-${syncIndex + 1}`;
251
+ if (syncResult.setupInstructions.length > 0) {
252
+ const setupSet = {
253
+ label: `${txLabel}-setup`,
254
+ instructions: [
255
+ web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: defaultCU }),
256
+ ...syncResult.setupInstructions,
257
+ ],
258
+ signers: [],
259
+ addressLookupTableAddresses: mergeAddressLookupTableAddresses(syncResult.addressLookupTableAddresses, extraLookupTableAddresses, vault.state.addressLookupTable ? [vault.state.addressLookupTable] : []),
260
+ coSignWithLoopscale: false,
261
+ };
262
+ if (shouldInjectPriceRefresh(setupSet, vault.programId, squadsProgram)) {
263
+ setupSet.instructions = insertAfterLeadingComputeBudgetAndScopeInstructions(setupSet.instructions, stepPriceRefreshIxs);
264
+ }
265
+ transactionSets.push(setupSet);
266
+ for (const alt of setupSet.addressLookupTableAddresses) {
267
+ allAltAddresses.add(alt.toBase58());
268
+ }
269
+ }
270
+ const syncSet = {
271
+ label: txLabel,
193
272
  instructions: [
194
- web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 }),
195
- ...syncResult.setupInstructions,
273
+ ...buildComputeBudgetIxs(stepCU, stepHeap, step.options.priorityFee),
274
+ ...syncResult.preInstructions,
275
+ syncResult.instruction,
276
+ ...syncResult.postInstructions,
196
277
  ],
197
- signers: [],
198
- addressLookupTableAddresses: [],
199
- });
200
- }
201
- const stepCU = step.options.computeUnitLimit ?? defaultCU;
202
- const stepHeap = step.options.heapFrameBytes ?? defaultHeap;
203
- const syncSet = {
204
- label: step.label,
205
- instructions: [
206
- ...buildComputeBudgetIxs(stepCU, stepHeap, step.options.priorityFee),
207
- ...syncResult.preInstructions,
208
- syncResult.instruction,
209
- ...syncResult.postInstructions,
210
- ],
211
- signers: syncResult.signers,
212
- addressLookupTableAddresses: [
213
- ...syncResult.addressLookupTableAddresses,
214
- ...(vault.state.addressLookupTable ? [vault.state.addressLookupTable] : []),
215
- ],
216
- };
217
- transactionSets.push(syncSet);
218
- for (const alt of syncSet.addressLookupTableAddresses) {
219
- allAltAddresses.add(alt.toBase58());
278
+ signers: syncResult.signers,
279
+ addressLookupTableAddresses: mergeAddressLookupTableAddresses(syncResult.addressLookupTableAddresses, extraLookupTableAddresses, vault.state.addressLookupTable ? [vault.state.addressLookupTable] : []),
280
+ coSignWithLoopscale: false,
281
+ };
282
+ if (shouldInjectPriceRefresh(syncSet, vault.programId, squadsProgram)) {
283
+ syncSet.instructions = insertAfterLeadingComputeBudgetAndScopeInstructions(syncSet.instructions, stepPriceRefreshIxs);
284
+ }
285
+ transactionSets.push(syncSet);
286
+ for (const alt of syncSet.addressLookupTableAddresses) {
287
+ allAltAddresses.add(alt.toBase58());
288
+ }
220
289
  }
221
290
  }
222
291
  const lookupTableAddresses = [...allAltAddresses].map((a) => new web3_js_1.PublicKey(a));
223
- // Validate transaction sizes catch oversized txs early with a clear error
224
- const altAccounts = await resolveAltAccounts(connection, lookupTableAddresses);
225
- for (const txSet of transactionSets) {
226
- const txAltAccounts = altAccounts.filter((alt) => txSet.addressLookupTableAddresses.some((addr) => addr.equals(alt.key)));
227
- try {
228
- const testMsg = new web3_js_1.TransactionMessage({
229
- payerKey: signer,
230
- recentBlockhash: "1".repeat(44),
231
- instructions: txSet.instructions,
232
- }).compileToV0Message(txAltAccounts);
233
- const testTx = new web3_js_1.VersionedTransaction(testMsg);
234
- const serialized = testTx.serialize();
235
- if (serialized.length > 1232) {
236
- throw new Error(`Transaction "${txSet.label}" is oversized (${serialized.length} bytes, limit 1232). ` +
237
- `This usually means too many actions were packed into a single addActions() call. ` +
238
- `Split them into separate addActions() calls so each gets its own sync transaction.`);
239
- }
240
- }
241
- catch (e) {
242
- if (e instanceof Error && e.message.includes("oversized"))
243
- throw e;
292
+ // Resolve the vault's persistent ALT to determine which account keys it
293
+ // already covers. Any keys NOT covered will need an ephemeral ALT.
294
+ const persistentAltAccounts = await resolveAltAccounts(connection, lookupTableAddresses);
295
+ const persistentAltKeys = new Set();
296
+ for (const alt of persistentAltAccounts) {
297
+ for (const addr of alt.state.addresses) {
298
+ persistentAltKeys.add(addr.toBase58());
244
299
  }
245
300
  }
301
+ // Collect every unique account key referenced by any transaction set.
302
+ const allUniqueKeys = collectUniqueAccountKeys(transactionSets, signer);
303
+ const ephemeralAltEntries = allUniqueKeys.filter((k) => !persistentAltKeys.has(k.toBase58()));
304
+ // Validate transaction sizes. When an ephemeral ALT will be created,
305
+ // skip validation here — send() will validate after ALT activation.
306
+ if (ephemeralAltEntries.length === 0) {
307
+ validateTransactionSizes(transactionSets, persistentAltAccounts, signer);
308
+ }
246
309
  const labels = transactionSets.map((ts) => ts.label);
247
- // Capture state for sendJitoBundle closure
310
+ // Capture state for send() closure
248
311
  const closedConnection = connection;
249
312
  const closedSigner = signer;
313
+ const closedVaultPda = vaultPda;
250
314
  const closedLookupTableAddresses = lookupTableAddresses;
251
- const closedSteps = this.steps;
315
+ const closedLoopscaleConfig = this.config.loopscale;
252
316
  return {
253
317
  transactionSets,
254
318
  lookupTableAddresses,
255
319
  labels,
256
- async sendJitoBundle(options) {
320
+ async send(options) {
321
+ const commitment = options.commitment ?? "confirmed";
322
+ const payer = options.signers[0];
323
+ // ── Ephemeral ALT creation ───────────────────────────────────────
324
+ let ephemeralAlt = null;
325
+ let ephemeralAltAccount;
326
+ if (ephemeralAltEntries.length > 0) {
327
+ const slot = await closedConnection.getSlot("processed");
328
+ const recentSlot = Math.max(slot - 1, 0);
329
+ const [createIx, altAddress] = web3_js_1.AddressLookupTableProgram.createLookupTable({
330
+ authority: payer.publicKey,
331
+ payer: payer.publicKey,
332
+ recentSlot,
333
+ });
334
+ // Chunk extend instructions — each can hold ~20 addresses safely
335
+ const extendChunks = chunkArray(ephemeralAltEntries, 20);
336
+ const extendIxs = extendChunks.map((chunk) => web3_js_1.AddressLookupTableProgram.extendLookupTable({
337
+ lookupTable: altAddress,
338
+ authority: payer.publicKey,
339
+ payer: payer.publicKey,
340
+ addresses: chunk,
341
+ }));
342
+ // Send create + first extend in one transaction
343
+ const altSetupIxs = [createIx, extendIxs[0]];
344
+ await sendAndConfirmTransaction(closedConnection, altSetupIxs, [payer], commitment);
345
+ // Send remaining extend chunks if any
346
+ for (let i = 1; i < extendIxs.length; i++) {
347
+ await sendAndConfirmTransaction(closedConnection, [extendIxs[i]], [payer], commitment);
348
+ }
349
+ // Wait for ALT entries to become active (usable in the slot AFTER extension)
350
+ await waitForSlotAdvance(closedConnection, slot, "processed");
351
+ // Resolve the ephemeral ALT account for use in transactions
352
+ const resolved = await closedConnection.getAddressLookupTable(altAddress);
353
+ if (!resolved.value) {
354
+ throw new Error(`Ephemeral ALT ${altAddress.toBase58()} not found after creation`);
355
+ }
356
+ ephemeralAltAccount = resolved.value;
357
+ ephemeralAlt = { address: altAddress, authority: payer.publicKey };
358
+ }
359
+ // ── Resolve all ALT accounts (persistent + ephemeral) ────────────
257
360
  const resolvedAltAccounts = await resolveAltAccounts(closedConnection, closedLookupTableAddresses);
258
- const { blockhash, lastValidBlockHeight } = await closedConnection.getLatestBlockhash();
259
- // Auto-create LoopscaleClient if any step needs co-signing
260
- const hasLoopscale = closedSteps.some((s) => s.isLoopscale);
361
+ if (ephemeralAltAccount) {
362
+ resolvedAltAccounts.push(ephemeralAltAccount);
363
+ }
364
+ // If we skipped build-time validation, validate now with the full ALT set
365
+ if (ephemeralAltEntries.length > 0) {
366
+ validateTransactionSizes(transactionSets, resolvedAltAccounts, closedSigner, ephemeralAltAccount);
367
+ }
368
+ // ── Auto-create LoopscaleClient if any step needs co-signing ─────
369
+ const hasLoopscale = transactionSets.some((txSet) => txSet.coSignWithLoopscale);
261
370
  let loopscaleClient;
262
371
  if (hasLoopscale) {
263
- loopscaleClient = new loopscale_client_1.LoopscaleClient({
372
+ loopscaleClient = createBuilderLoopscaleClient({
264
373
  connection: closedConnection,
265
- userWallet: closedSigner,
374
+ vaultPda: closedVaultPda,
375
+ loopscale: closedLoopscaleConfig,
266
376
  });
267
377
  }
268
- const allTxs = [];
269
- for (let i = 0; i < transactionSets.length; i++) {
270
- const txSet = transactionSets[i];
271
- const isLast = i === transactionSets.length - 1;
272
- const instructions = [...txSet.instructions];
273
- if (isLast && options.tipLamports > 0) {
274
- instructions.push(createJitoTipIx(options.tipLamports, options.signers[0].publicKey));
275
- }
276
- const txAltAccounts = resolvedAltAccounts.filter((alt) => txSet.addressLookupTableAddresses.some((addr) => addr.equals(alt.key)));
378
+ // ── Send transactions ────────────────────────────────────────────
379
+ const signatures = [];
380
+ for (const txSet of transactionSets) {
381
+ const { blockhash, lastValidBlockHeight } = await closedConnection.getLatestBlockhash();
382
+ // Include the ephemeral ALT for every transaction set
383
+ const txAltAccounts = resolvedAltAccounts.filter((alt) => txSet.addressLookupTableAddresses.some((addr) => addr.equals(alt.key))
384
+ || (ephemeralAltAccount && alt.key.equals(ephemeralAltAccount.key)));
277
385
  const msg = new web3_js_1.TransactionMessage({
278
- payerKey: options.signers[0].publicKey,
386
+ payerKey: payer.publicKey,
279
387
  recentBlockhash: blockhash,
280
- instructions,
388
+ instructions: txSet.instructions,
281
389
  }).compileToV0Message(txAltAccounts);
282
390
  let tx = new web3_js_1.VersionedTransaction(msg);
283
- // Co-sign Loopscale steps, sign everything else normally
284
- const step = closedSteps.find((s) => s.label === txSet.label);
285
- if (step?.isLoopscale && loopscaleClient) {
391
+ if (txSet.coSignWithLoopscale && loopscaleClient) {
286
392
  tx.sign([...options.signers, ...txSet.signers]);
287
393
  tx = await loopscaleClient.coSign(tx);
288
394
  }
289
395
  else {
290
396
  tx.sign([...options.signers, ...txSet.signers]);
291
397
  }
292
- allTxs.push(tx);
293
- }
294
- // Custom send escape hatch (for testing)
295
- if (options.customSend) {
296
- const sigs = await options.customSend(allTxs, labels);
297
- return { signatures: sigs, bundleId: "" };
398
+ const sig = await closedConnection.sendTransaction(tx);
399
+ await closedConnection.confirmTransaction({ signature: sig, blockhash, lastValidBlockHeight }, commitment);
400
+ signatures.push(sig);
298
401
  }
299
- const serialized = allTxs.map((tx) => Buffer.from(tx.serialize()));
300
- const result = await sendJitoBundleRpc(serialized, options.jitoAuthKey);
301
- const commitment = options.commitment ?? "confirmed";
302
- if (result.signatures.length > 0) {
303
- await closedConnection.confirmTransaction({ signature: result.signatures[0], blockhash, lastValidBlockHeight }, commitment).catch(() => { });
304
- }
305
- return result;
402
+ return { signatures, ephemeralAlt };
306
403
  },
307
404
  };
308
405
  }
309
406
  }
310
407
  exports.VaultTransactionBuilder = VaultTransactionBuilder;
311
408
  // ============================================================================
312
- // bundleForJito — standalone utility
313
- // ============================================================================
314
- /**
315
- * Package TransactionSets into Jito-compatible unsigned VersionedTransactions.
316
- *
317
- * All transactions share the same blockhash and a Jito tip instruction is appended
318
- * to the last transaction. The returned transactions are **unsigned** — the caller
319
- * is responsible for signing (useful for web app flows where a wallet adapter handles signing).
320
- *
321
- * For a fully managed sign-and-send flow, use {@link VaultTransactionBuildResult.sendJitoBundle} instead.
322
- *
323
- * @param transactionSets - Ordered TransactionSets from `builder.build()`.
324
- * @param params.connection - Solana RPC connection for ALT resolution and blockhash fetching.
325
- * @param params.payer - Public key of the fee payer (used in the V0 message header).
326
- * @param params.tipLamports - Jito tip amount in lamports, appended to the last transaction.
327
- * @param params.lookupTableAddresses - All ALT addresses from the build result.
328
- * @returns Unsigned VersionedTransactions ready for wallet signing.
329
- */
330
- async function bundleForJito(transactionSets, params) {
331
- const { connection, payer, tipLamports, lookupTableAddresses } = params;
332
- const altAccounts = await resolveAltAccounts(connection, lookupTableAddresses);
333
- const { blockhash } = await connection.getLatestBlockhash();
334
- const transactions = [];
335
- for (let i = 0; i < transactionSets.length; i++) {
336
- const txSet = transactionSets[i];
337
- const isLast = i === transactionSets.length - 1;
338
- const instructions = [...txSet.instructions];
339
- if (isLast && tipLamports > 0) {
340
- instructions.push(createJitoTipIx(tipLamports, payer));
341
- }
342
- const txAltAccounts = altAccounts.filter((alt) => txSet.addressLookupTableAddresses.some((addr) => addr.equals(alt.key)));
343
- const messageV0 = new web3_js_1.TransactionMessage({
344
- payerKey: payer,
345
- recentBlockhash: blockhash,
346
- instructions,
347
- }).compileToV0Message(txAltAccounts);
348
- transactions.push(new web3_js_1.VersionedTransaction(messageV0));
349
- }
350
- return transactions;
351
- }
352
- exports.bundleForJito = bundleForJito;
353
- // ============================================================================
354
409
  // Internal helpers
355
410
  // ============================================================================
356
411
  /** Build compute budget instructions: CU limit + heap frame + optional priority fee. */
@@ -364,6 +419,56 @@ function buildComputeBudgetIxs(computeUnitLimit, heapFrameBytes, priorityFee) {
364
419
  }
365
420
  return ixs;
366
421
  }
422
+ function insertAfterLeadingComputeBudgetAndScopeInstructions(instructions, extraInstructions) {
423
+ if (extraInstructions.length === 0) {
424
+ return instructions;
425
+ }
426
+ let insertIndex = 0;
427
+ while (insertIndex < instructions.length
428
+ && instructions[insertIndex]?.programId.equals(web3_js_1.ComputeBudgetProgram.programId)) {
429
+ insertIndex += 1;
430
+ }
431
+ while (insertIndex < instructions.length
432
+ && instructions[insertIndex]?.programId.equals(SCOPE_PROGRAM_ID)) {
433
+ insertIndex += 1;
434
+ }
435
+ return [
436
+ ...instructions.slice(0, insertIndex),
437
+ ...extraInstructions,
438
+ ...instructions.slice(insertIndex),
439
+ ];
440
+ }
441
+ function shouldInjectPriceRefresh(txSet, programId, squadsProgram) {
442
+ if (!txSet.label.endsWith("-setup")) {
443
+ return true;
444
+ }
445
+ return txSet.instructions.some((instruction) => instruction.programId.equals(programId) || instruction.programId.equals(squadsProgram));
446
+ }
447
+ function touchesManagedVaultPrograms(instructions, programId, squadsProgram) {
448
+ return instructions.some((instruction) => instruction.programId.equals(programId) || instruction.programId.equals(squadsProgram));
449
+ }
450
+ function mergeAddressLookupTableAddresses(...addressGroups) {
451
+ const seen = new Set();
452
+ const merged = [];
453
+ for (const group of addressGroups) {
454
+ for (const address of group) {
455
+ const key = address.toBase58();
456
+ if (seen.has(key))
457
+ continue;
458
+ seen.add(key);
459
+ merged.push(address);
460
+ }
461
+ }
462
+ return merged;
463
+ }
464
+ function createBuilderLoopscaleClient(params) {
465
+ return new loopscale_1.LoopscaleClient({
466
+ connection: params.connection,
467
+ userWallet: params.vaultPda,
468
+ baseUrl: params.loopscale?.baseUrl,
469
+ debug: params.loopscale?.debug,
470
+ });
471
+ }
367
472
  /** Fetch and resolve AddressLookupTableAccounts from on-chain, filtering nulls. */
368
473
  async function resolveAltAccounts(connection, addresses) {
369
474
  const results = await Promise.all(addresses.map((addr) => connection.getAddressLookupTable(addr)));
@@ -371,100 +476,93 @@ async function resolveAltAccounts(connection, addresses) {
371
476
  .map((r) => r.value)
372
477
  .filter((v) => v !== null);
373
478
  }
374
- /** Create a Jito tip transfer instruction to a random tip address. */
375
- function createJitoTipIx(tipLamports, payer) {
376
- const tipReceiver = JITO_TIP_ADDRESSES[Math.floor(Math.random() * JITO_TIP_ADDRESSES.length)];
377
- return web3_js_1.SystemProgram.transfer({
378
- fromPubkey: payer,
379
- toPubkey: tipReceiver,
380
- lamports: BigInt(Math.floor(tipLamports)),
381
- });
382
- }
383
- /**
384
- * Send a bundle of serialized transactions to Jito block engine endpoints.
385
- * Fans out to regional endpoints (best-effort) and retries the mainnet endpoint.
386
- */
387
- async function sendJitoBundleRpc(serializedTransactions, authKey) {
388
- if (serializedTransactions.length > 5) {
389
- throw new Error("Jito bundles support at most 5 transactions");
390
- }
391
- const signatures = serializedTransactions.map((tx) => {
392
- const vtx = web3_js_1.VersionedTransaction.deserialize(tx);
393
- return Buffer.from(vtx.signatures[0]).toString("base64");
394
- });
395
- const requestBody = {
396
- jsonrpc: "2.0",
397
- id: 1,
398
- method: "sendBundle",
399
- params: [serializedTransactions.map((tx) => tx.toString("base64")), { encoding: "base64" }],
479
+ /** Collect all unique account keys referenced across all transaction sets. */
480
+ function collectUniqueAccountKeys(transactionSets, signer) {
481
+ const seen = new Set();
482
+ const keys = [];
483
+ const add = (pubkey) => {
484
+ const str = pubkey.toBase58();
485
+ if (!seen.has(str)) {
486
+ seen.add(str);
487
+ keys.push(pubkey);
488
+ }
400
489
  };
401
- const headers = { "Content-Type": "application/json" };
402
- if (authKey)
403
- headers["x-jito-auth"] = authKey;
404
- // Fan out to regional endpoints (best-effort, don't await)
405
- const regionalPromises = JITO_REGIONAL_URLS.map((url) => fetch(`${url}${JITO_BUNDLES_PATH}`, { method: "POST", headers, body: JSON.stringify(requestBody) })
406
- .catch(() => { }));
407
- Promise.allSettled(regionalPromises);
408
- // Retry mainnet endpoint
409
- for (let attempt = 0; attempt < JITO_MAX_RETRIES; attempt++) {
410
- try {
411
- const response = await fetch(`${JITO_MAINNET_URL}${JITO_BUNDLES_PATH}`, {
412
- method: "POST",
413
- headers,
414
- body: JSON.stringify(requestBody),
415
- });
416
- const data = (await response.json().catch(() => undefined));
417
- if (response.ok && !data?.error && typeof data?.result === "string") {
418
- return { bundleId: data.result, signatures };
419
- }
420
- if (response.status === 400) {
421
- return { bundleId: "", signatures };
490
+ add(signer);
491
+ for (const txSet of transactionSets) {
492
+ for (const ix of txSet.instructions) {
493
+ add(ix.programId);
494
+ for (const key of ix.keys) {
495
+ add(key.pubkey);
422
496
  }
423
497
  }
424
- catch { }
425
- await new Promise((r) => setTimeout(r, JITO_RETRY_DELAY_MS));
426
498
  }
427
- return { bundleId: "", signatures };
499
+ return keys;
428
500
  }
429
- // ── ALT auto-extension (reserved for future use) ──
430
- /**
431
- * Auto-extend an address lookup table with all unique accounts from the given instructions.
432
- * Batches in groups of 30 addresses (Solana limit per extend instruction).
433
- * Waits 2 seconds after extension for ALT propagation.
434
- *
435
- * @internal Not currently wired into any flow. Reserved for when ALT management
436
- * needs to be handled by the builder (e.g., oversized Loopscale transactions).
437
- */
438
- async function autoExtendAlt(connection, lookupTable, authority, instructions) {
439
- const seen = new Set();
440
- const addresses = [];
441
- for (const ix of instructions) {
442
- for (const key of [ix.programId, ...ix.keys.map((k) => k.pubkey)]) {
443
- const keyStr = key.toBase58();
444
- if (!seen.has(keyStr)) {
445
- seen.add(keyStr);
446
- addresses.push(key);
501
+ /** Validate that every transaction set can serialize within the 1232-byte limit. */
502
+ function validateTransactionSizes(transactionSets, altAccounts, signer, extraAlt) {
503
+ for (const txSet of transactionSets) {
504
+ const txAltAccounts = altAccounts.filter((alt) => txSet.addressLookupTableAddresses.some((addr) => addr.equals(alt.key)));
505
+ if (extraAlt && !txAltAccounts.some((a) => a.key.equals(extraAlt.key))) {
506
+ txAltAccounts.push(extraAlt);
507
+ }
508
+ try {
509
+ const testMsg = new web3_js_1.TransactionMessage({
510
+ payerKey: signer,
511
+ recentBlockhash: web3_js_1.PublicKey.default.toBase58(),
512
+ instructions: txSet.instructions,
513
+ }).compileToV0Message(txAltAccounts);
514
+ const testTx = new web3_js_1.VersionedTransaction(testMsg);
515
+ const serialized = testTx.serialize();
516
+ if (serialized.length > 1232) {
517
+ throw new Error(`Transaction "${txSet.label}" is oversized (${serialized.length} bytes, limit 1232). ` +
518
+ `This usually means too many actions were packed into a single addActions() call. ` +
519
+ `Split them into separate addActions() calls so each gets its own sync transaction.`);
520
+ }
521
+ }
522
+ catch (e) {
523
+ if (e instanceof Error) {
524
+ const ixCount = txSet.instructions.length;
525
+ const altCount = txAltAccounts.length;
526
+ const accountCount = txSet.instructions.reduce((sum, ix) => sum + ix.keys.length, 0);
527
+ throw new Error(`Transaction "${txSet.label}" failed to serialize: ${e.message}. ` +
528
+ `Transaction has ${ixCount} instructions, ${accountCount} account keys, ` +
529
+ `and ${altCount} address lookup table(s). ` +
530
+ `Try splitting actions into separate addActions() calls.`);
447
531
  }
532
+ throw e;
448
533
  }
449
534
  }
450
- for (let i = 0; i < addresses.length; i += 30) {
451
- const batch = addresses.slice(i, i + 30);
452
- const extendIx = web3_js_1.AddressLookupTableProgram.extendLookupTable({
453
- payer: authority.publicKey,
454
- authority: authority.publicKey,
455
- lookupTable,
456
- addresses: batch,
457
- });
458
- const { blockhash } = await connection.getLatestBlockhash();
459
- const msg = new web3_js_1.TransactionMessage({
460
- payerKey: authority.publicKey,
461
- recentBlockhash: blockhash,
462
- instructions: [extendIx],
463
- }).compileToV0Message();
464
- const tx = new web3_js_1.VersionedTransaction(msg);
465
- tx.sign([authority]);
466
- await connection.sendRawTransaction(tx.serialize());
535
+ }
536
+ /** Split an array into chunks of the given size. */
537
+ function chunkArray(arr, size) {
538
+ const chunks = [];
539
+ for (let i = 0; i < arr.length; i += size) {
540
+ chunks.push(arr.slice(i, i + size));
541
+ }
542
+ return chunks;
543
+ }
544
+ /** Build, sign, send, and confirm a simple transaction. */
545
+ async function sendAndConfirmTransaction(connection, instructions, signers, commitment) {
546
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(commitment);
547
+ const msg = new web3_js_1.TransactionMessage({
548
+ payerKey: signers[0].publicKey,
549
+ recentBlockhash: blockhash,
550
+ instructions,
551
+ }).compileToV0Message();
552
+ const tx = new web3_js_1.VersionedTransaction(msg);
553
+ tx.sign(signers);
554
+ const sig = await connection.sendTransaction(tx);
555
+ await connection.confirmTransaction({ signature: sig, blockhash, lastValidBlockHeight }, commitment);
556
+ return sig;
557
+ }
558
+ /** Poll until the current slot advances past the target slot. */
559
+ async function waitForSlotAdvance(connection, targetSlot, commitment = "processed") {
560
+ for (let i = 0; i < 60; i++) {
561
+ const currentSlot = await connection.getSlot(commitment);
562
+ if (currentSlot > targetSlot)
563
+ return;
564
+ await new Promise((r) => setTimeout(r, 400));
467
565
  }
468
- await new Promise((r) => setTimeout(r, 2000));
566
+ throw new Error("Timed out waiting for slot to advance after ephemeral ALT creation");
469
567
  }
470
568
  //# sourceMappingURL=vaultTransactionBuilder.js.map