@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
@@ -0,0 +1,172 @@
1
+ import {
2
+ Connection,
3
+ PublicKey,
4
+ TransactionInstruction,
5
+ TransactionMessage,
6
+ VersionedMessage,
7
+ VersionedTransaction,
8
+ } from "@solana/web3.js"
9
+
10
+ import type {
11
+ LoopscaleInstructionName,
12
+ LoopscaleTransactionResponse,
13
+ LoopscaleVersionedTransactionBatchResponse,
14
+ LoopscaleVersionedTransactionResponse,
15
+ } from "./types"
16
+ import { LOOPSCALE_DISCRIMINATORS, LOOPSCALE_PROGRAM_ID } from "../policyBuilders"
17
+
18
+ const DISCRIMINATOR_HEX_TO_NAME = new Map<string, LoopscaleInstructionName>(
19
+ Object.entries(LOOPSCALE_DISCRIMINATORS).map(([name, discriminator]) => [
20
+ Buffer.from(discriminator).toString("hex"),
21
+ name as LoopscaleInstructionName,
22
+ ]),
23
+ )
24
+
25
+ function buildTransactionFromResponse(
26
+ response: LoopscaleVersionedTransactionResponse,
27
+ ): VersionedTransaction {
28
+ const message = VersionedMessage.deserialize(Buffer.from(response.message, "base64"))
29
+ const transaction = new VersionedTransaction(message)
30
+ const signerKeys = message.staticAccountKeys.slice(0, message.header.numRequiredSignatures)
31
+
32
+ for (const signature of response.signatures) {
33
+ const signer = new PublicKey(signature.publicKey)
34
+ const bytes = Buffer.from(signature.signature, "base64")
35
+ if (bytes.length !== 64) {
36
+ throw new Error(`Invalid signature for ${signature.publicKey}`)
37
+ }
38
+
39
+ const signerIndex = signerKeys.findIndex((candidate) => candidate.equals(signer))
40
+ if (signerIndex >= 0) {
41
+ transaction.signatures[signerIndex] = new Uint8Array(bytes)
42
+ }
43
+ }
44
+
45
+ return transaction
46
+ }
47
+
48
+ async function resolveAddressLookupTables(
49
+ connection: Connection,
50
+ transaction: VersionedTransaction,
51
+ ) {
52
+ return Promise.all(
53
+ transaction.message.addressTableLookups.map(async (lookup) => {
54
+ const result = await connection.getAddressLookupTable(lookup.accountKey)
55
+ if (!result.value) {
56
+ throw new Error(`Missing lookup table ${lookup.accountKey.toBase58()}`)
57
+ }
58
+ return result.value
59
+ }),
60
+ )
61
+ }
62
+
63
+ async function findInstructionByName(
64
+ connection: Connection,
65
+ transactions: VersionedTransaction[],
66
+ name: LoopscaleInstructionName,
67
+ ): Promise<TransactionInstruction | null> {
68
+ for (const transaction of transactions) {
69
+ const addressLookupTableAccounts = await resolveAddressLookupTables(connection, transaction)
70
+ const instructions = TransactionMessage.decompile(transaction.message, { addressLookupTableAccounts }).instructions
71
+ const match = instructions.find((instruction) => identifyLoopscaleInstruction(instruction) === name)
72
+ if (match) {
73
+ return match
74
+ }
75
+ }
76
+
77
+ return null
78
+ }
79
+
80
+ /**
81
+ * Identify a Loopscale strategy or loan instruction from its discriminator.
82
+ *
83
+ * Returns `null` for non-Loopscale instructions and for unsupported Loopscale
84
+ * instructions outside the strategies + loans scope of this SDK module.
85
+ */
86
+ export function identifyLoopscaleInstruction(
87
+ instruction: TransactionInstruction,
88
+ ): LoopscaleInstructionName | null {
89
+ if (!instruction.programId.equals(LOOPSCALE_PROGRAM_ID)) {
90
+ return null
91
+ }
92
+
93
+ const discriminator = Buffer.from(instruction.data).subarray(0, 8).toString("hex")
94
+ return DISCRIMINATOR_HEX_TO_NAME.get(discriminator) ?? null
95
+ }
96
+
97
+ /**
98
+ * Deserialize a raw Loopscale transaction response into a `VersionedTransaction`.
99
+ */
100
+ export function deserializeLoopscaleTransactionResponse(
101
+ response: LoopscaleVersionedTransactionResponse,
102
+ ): VersionedTransaction {
103
+ return buildTransactionFromResponse(response)
104
+ }
105
+
106
+ /**
107
+ * Deserialize a raw Loopscale transaction batch response into `VersionedTransaction`s.
108
+ */
109
+ export function deserializeLoopscaleTransactionBatchResponse(
110
+ response: LoopscaleVersionedTransactionBatchResponse,
111
+ ): VersionedTransaction[] {
112
+ return response.map((entry) => deserializeLoopscaleTransactionResponse(entry))
113
+ }
114
+
115
+ /**
116
+ * Extract the raw transaction responses from any supported Loopscale transaction response shape.
117
+ */
118
+ export function getLoopscaleTransactionResponses(
119
+ response: LoopscaleTransactionResponse,
120
+ ): LoopscaleVersionedTransactionResponse[] {
121
+ if (Array.isArray(response)) {
122
+ return response
123
+ }
124
+
125
+ if ("transactions" in response) {
126
+ return response.transactions
127
+ }
128
+
129
+ if ("transaction" in response) {
130
+ return [response.transaction]
131
+ }
132
+
133
+ return [response]
134
+ }
135
+
136
+ /**
137
+ * Extract the strategy PDA created by a Loopscale `createStrategy` response.
138
+ */
139
+ export async function extractCreatedStrategyAddress(
140
+ connection: Connection,
141
+ response: LoopscaleTransactionResponse,
142
+ ): Promise<PublicKey> {
143
+ const transactions = getLoopscaleTransactionResponses(response)
144
+ .map((entry) => deserializeLoopscaleTransactionResponse(entry))
145
+ const instruction = await findInstructionByName(connection, transactions, "createStrategy")
146
+
147
+ const strategy = instruction?.keys[3]?.pubkey
148
+ if (!strategy) {
149
+ throw new Error("Loopscale createStrategy response did not contain a createStrategy instruction")
150
+ }
151
+
152
+ return strategy
153
+ }
154
+
155
+ /**
156
+ * Extract the nonce public key used by a Loopscale `createStrategy` response.
157
+ */
158
+ export async function extractCreatedStrategyNonce(
159
+ connection: Connection,
160
+ response: LoopscaleTransactionResponse,
161
+ ): Promise<PublicKey> {
162
+ const transactions = getLoopscaleTransactionResponses(response)
163
+ .map((entry) => deserializeLoopscaleTransactionResponse(entry))
164
+ const instruction = await findInstructionByName(connection, transactions, "createStrategy")
165
+
166
+ const nonce = instruction?.keys[2]?.pubkey
167
+ if (!nonce) {
168
+ throw new Error("Loopscale createStrategy response did not contain the strategy nonce account")
169
+ }
170
+
171
+ return nonce
172
+ }
@@ -0,0 +1,57 @@
1
+ export {
2
+ LoopscaleClient,
3
+ deserializeLoopscaleTransactionResponse,
4
+ deserializeLoopscaleTransactionBatchResponse,
5
+ extractCreatedStrategyAddress,
6
+ extractCreatedStrategyNonce,
7
+ getLoopscaleTransactionResponses,
8
+ identifyLoopscaleInstruction,
9
+ } from "./client"
10
+
11
+ export type {
12
+ AddCollateralArgs,
13
+ CollateralAllocationUpdateArgs,
14
+ CollateralParamsUpdateArgs,
15
+ EditStrategySettingsArgs,
16
+ ExternalYieldSourceParams,
17
+ LoopscaleBorrowPrincipalParams,
18
+ LoopscaleClientConfig,
19
+ LoopscaleCloseLoanParams,
20
+ LoopscaleCloseStrategyParams,
21
+ LoopscaleCreateLoanParams,
22
+ LoopscaleCreateLoanResponse,
23
+ LoopscaleCreateStrategyParams,
24
+ LoopscaleDepositCollateralParams,
25
+ LoopscaleDepositStrategyParams,
26
+ LoopscaleExpectedLoanInfo,
27
+ LoopscaleGetStrategiesParams,
28
+ LoopscaleInstructionName,
29
+ LoopscaleLoanInfoParams,
30
+ LoopscaleLoanInfoResponse,
31
+ LoopscaleLoanTransactionResponse,
32
+ LoopscaleMaxQuote,
33
+ LoopscaleMaxQuoteParams,
34
+ LoopscaleQuote,
35
+ LoopscaleQuoteParams,
36
+ LoopscaleRepayLoanSimpleParams,
37
+ LoopscaleStrategyInfoResponse,
38
+ LoopscaleTransactionResponse,
39
+ LoopscaleTransactionSignature,
40
+ LoopscaleTransactionsResponse,
41
+ LoopscaleUpdateStrategyParams,
42
+ LoopscaleVersionedTransactionBatchResponse,
43
+ LoopscaleVersionedTransactionResponse,
44
+ LoopscaleWithdrawCollateralParams,
45
+ LoopscaleWithdrawStrategyParams,
46
+ RemoveCollateralArgs,
47
+ StrategyCollateralUpdates,
48
+ StrategyDuration,
49
+ TxnExternalYieldSourceArgs,
50
+ } from "./types"
51
+
52
+ export type {
53
+ LoopscaleBuildTransactionsContext,
54
+ LoopscaleBuiltTransaction,
55
+ LoopscalePreparedTransaction,
56
+ LoopscaleVaultPreparationContext,
57
+ } from "./prepared-types"
@@ -0,0 +1,435 @@
1
+ import {
2
+ AddressLookupTableProgram,
3
+ ComputeBudgetProgram,
4
+ PublicKey,
5
+ TransactionInstruction,
6
+ TransactionMessage,
7
+ VersionedTransaction,
8
+ } from "@solana/web3.js"
9
+
10
+ import {
11
+ resolveConstraintIndices,
12
+ resolveHookAccounts,
13
+ resolvePolicyMatchForVault,
14
+ } from "../policyMatcher"
15
+ import { wrapInstructionsInSyncTransaction } from "../syncTransaction"
16
+ import {
17
+ createVaultSyncTransaction,
18
+ type LoopscaleInstruction,
19
+ loopscaleAction,
20
+ } from "../vault-interaction"
21
+ import {
22
+ deserializeLoopscaleTransactionResponse,
23
+ getLoopscaleTransactionResponses,
24
+ identifyLoopscaleInstruction,
25
+ } from "./helpers"
26
+ import type {
27
+ LoopscalePreparedTransaction,
28
+ LoopscaleVaultPreparationContext,
29
+ } from "./prepared-types"
30
+ import type {
31
+ LoopscaleTransactionResponse,
32
+ } from "./types"
33
+
34
+ type SupportedPreparedInstructionName =
35
+ | "borrowPrincipal"
36
+ | "closeLoan"
37
+ | "closeStrategy"
38
+ | "createLoan"
39
+ | "createStrategy"
40
+ | "depositCollateral"
41
+ | "depositStrategy"
42
+ | "repayPrincipal"
43
+ | "updateStrategy"
44
+ | "updateWeightMatrix"
45
+ | "withdrawCollateral"
46
+ | "withdrawStrategy"
47
+
48
+ const LOOPSCALE_WRAPPERS: Record<
49
+ SupportedPreparedInstructionName,
50
+ (instruction: TransactionInstruction) => LoopscaleInstruction
51
+ > = {
52
+ borrowPrincipal: (instruction) => loopscaleAction.borrowPrincipal({ instruction }),
53
+ closeLoan: (instruction) => loopscaleAction.closeLoan({ instruction }),
54
+ closeStrategy: (instruction) => loopscaleAction.closeStrategy({ instruction }),
55
+ createLoan: (instruction) => loopscaleAction.createLoan({ instruction }),
56
+ createStrategy: (instruction) => loopscaleAction.createStrategy({ instruction }),
57
+ depositCollateral: (instruction) => loopscaleAction.depositCollateral({ instruction }),
58
+ depositStrategy: (instruction) => loopscaleAction.depositStrategy({ instruction }),
59
+ repayPrincipal: (instruction) => loopscaleAction.repayPrincipal({ instruction }),
60
+ updateStrategy: (instruction) => loopscaleAction.updateStrategy({ instruction }),
61
+ updateWeightMatrix: (instruction) => loopscaleAction.updateWeightMatrix({ instruction }),
62
+ withdrawCollateral: (instruction) => loopscaleAction.withdrawCollateral({ instruction }),
63
+ withdrawStrategy: (instruction) => loopscaleAction.withdrawStrategy({ instruction }),
64
+ }
65
+
66
+ async function decompileTransaction(
67
+ context: LoopscaleVaultPreparationContext,
68
+ transaction: VersionedTransaction,
69
+ ): Promise<TransactionInstruction[]> {
70
+ const addressLookupTableAccounts = await Promise.all(
71
+ transaction.message.addressTableLookups.map(async (lookup) => {
72
+ const result = await context.connection.getAddressLookupTable(lookup.accountKey)
73
+ if (!result.value) {
74
+ throw new Error(`Missing lookup table ${lookup.accountKey.toBase58()}`)
75
+ }
76
+ return result.value
77
+ }),
78
+ )
79
+
80
+ return TransactionMessage.decompile(transaction.message, { addressLookupTableAccounts }).instructions
81
+ }
82
+
83
+ function wrapLoopscaleInstruction(instruction: TransactionInstruction): LoopscaleInstruction {
84
+ const name = identifyLoopscaleInstruction(instruction)
85
+ if (!name) {
86
+ throw new Error("Encountered a non-Loopscale or unsupported Loopscale instruction in a sync segment")
87
+ }
88
+
89
+ if (!(name in LOOPSCALE_WRAPPERS)) {
90
+ throw new Error(`Loopscale instruction ${name} is not supported through Exponent CPI execution`)
91
+ }
92
+
93
+ return LOOPSCALE_WRAPPERS[name as SupportedPreparedInstructionName](instruction)
94
+ }
95
+
96
+ function getLoopscaleInstructionSegment(instructions: TransactionInstruction[]): {
97
+ firstLoopscaleIndex: number
98
+ lastLoopscaleIndex: number
99
+ } | null {
100
+ const loopscaleIndices = instructions
101
+ .map((instruction, index) => identifyLoopscaleInstruction(instruction) !== null ? index : -1)
102
+ .filter((index) => index !== -1)
103
+
104
+ if (loopscaleIndices.length === 0) {
105
+ return null
106
+ }
107
+
108
+ return {
109
+ firstLoopscaleIndex: loopscaleIndices[0]!,
110
+ lastLoopscaleIndex: loopscaleIndices[loopscaleIndices.length - 1]!,
111
+ }
112
+ }
113
+
114
+ function splitLoopscaleInstructions(
115
+ instructions: TransactionInstruction[],
116
+ segment: { firstLoopscaleIndex: number; lastLoopscaleIndex: number },
117
+ ): {
118
+ prefixInstructions: TransactionInstruction[]
119
+ syncInstructions: LoopscaleInstruction[]
120
+ suffixInstructions: TransactionInstruction[]
121
+ } {
122
+ const syncInstructions: LoopscaleInstruction[] = []
123
+ const prefixInstructions: TransactionInstruction[] = []
124
+ const suffixInstructions: TransactionInstruction[] = []
125
+
126
+ for (let index = 0; index < instructions.length; index += 1) {
127
+ const instruction = instructions[index]!
128
+ const name = identifyLoopscaleInstruction(instruction)
129
+ const isInsideSyncSegment = index >= segment.firstLoopscaleIndex && index <= segment.lastLoopscaleIndex
130
+
131
+ if (isInsideSyncSegment) {
132
+ if (name === null) {
133
+ throw new Error(
134
+ "Loopscale response contains non-Loopscale instructions interleaved within the sync segment",
135
+ )
136
+ }
137
+
138
+ syncInstructions.push(wrapLoopscaleInstruction(instruction))
139
+ continue
140
+ }
141
+
142
+ if (index < segment.firstLoopscaleIndex) {
143
+ prefixInstructions.push(instruction)
144
+ continue
145
+ }
146
+
147
+ suffixInstructions.push(instruction)
148
+ }
149
+
150
+ return { prefixInstructions, syncInstructions, suffixInstructions }
151
+ }
152
+
153
+ function mergeLookupTableAddresses(
154
+ left: readonly PublicKey[],
155
+ right: readonly PublicKey[],
156
+ ): PublicKey[] {
157
+ const seen = new Set<string>()
158
+ const merged = [...left, ...right]
159
+
160
+ return merged.filter((address) => {
161
+ const key = address.toBase58()
162
+ if (seen.has(key)) {
163
+ return false
164
+ }
165
+ seen.add(key)
166
+ return true
167
+ })
168
+ }
169
+
170
+ function isComputeBudgetInstruction(instruction: TransactionInstruction): boolean {
171
+ return instruction.programId.equals(ComputeBudgetProgram.programId)
172
+ }
173
+
174
+ function isIgnorableLoopscaleTopLevelInstruction(
175
+ instruction: TransactionInstruction,
176
+ ): boolean {
177
+ return instruction.programId.equals(AddressLookupTableProgram.programId)
178
+ }
179
+
180
+ function getExtraSignerKeys(
181
+ instruction: TransactionInstruction,
182
+ signer: PublicKey,
183
+ ): PublicKey[] {
184
+ return instruction.keys
185
+ .filter((key) => key.isSigner && !key.pubkey.equals(signer))
186
+ .map((key) => key.pubkey)
187
+ }
188
+
189
+ function instructionNeedsVaultWrapping(
190
+ instruction: TransactionInstruction,
191
+ context: LoopscaleVaultPreparationContext,
192
+ ): boolean {
193
+ const extraSignerKeys = getExtraSignerKeys(instruction, context.signer)
194
+ if (extraSignerKeys.length === 0) {
195
+ return false
196
+ }
197
+
198
+ const allVaultSignerKeys = extraSignerKeys.every((key) => key.equals(context.vaultPda))
199
+ if (!allVaultSignerKeys) {
200
+ throw new Error(
201
+ `Loopscale response contains a top-level instruction with unsupported signer requirements: ${extraSignerKeys
202
+ .map((key) => key.toBase58())
203
+ .join(", ")}`,
204
+ )
205
+ }
206
+
207
+ return true
208
+ }
209
+
210
+ async function wrapVaultSignedInstructions(
211
+ instructions: TransactionInstruction[],
212
+ context: LoopscaleVaultPreparationContext,
213
+ ): Promise<TransactionInstruction> {
214
+ let resolvedPolicyPda = context.policyPda
215
+ let resolvedConstraintIndices = context.constraintIndices
216
+
217
+ if (!resolvedPolicyPda) {
218
+ if (!context.vaultAddress) {
219
+ throw new Error(
220
+ "Loopscale top-level vault-signed instructions require either context.policyPda or context.vaultAddress",
221
+ )
222
+ }
223
+
224
+ const match = await resolvePolicyMatchForVault(
225
+ context.connection,
226
+ context.vaultAddress,
227
+ context.signer,
228
+ instructions,
229
+ )
230
+ resolvedPolicyPda = match.policyPda
231
+ resolvedConstraintIndices ??= match.constraintIndices
232
+ } else {
233
+ resolvedConstraintIndices ??= await resolveConstraintIndices(
234
+ context.connection,
235
+ resolvedPolicyPda,
236
+ instructions,
237
+ )
238
+ }
239
+
240
+ let resolvedLeadingAccounts = context.leadingAccounts
241
+ let resolvedPreHookAccounts = context.preHookAccounts
242
+ let resolvedPostHookAccounts = context.postHookAccounts
243
+ if (
244
+ context.vaultAddress
245
+ && (!resolvedLeadingAccounts || !resolvedPreHookAccounts || !resolvedPostHookAccounts)
246
+ ) {
247
+ const hooks = await resolveHookAccounts(
248
+ context.connection,
249
+ resolvedPolicyPda,
250
+ context.vaultAddress,
251
+ context.signer,
252
+ )
253
+ resolvedLeadingAccounts ??= hooks.leadingAccounts
254
+ resolvedPreHookAccounts ??= hooks.preHookAccounts
255
+ resolvedPostHookAccounts ??= hooks.postHookAccounts
256
+ }
257
+
258
+ return wrapInstructionsInSyncTransaction({
259
+ policyPda: resolvedPolicyPda,
260
+ vaultPda: context.vaultPda,
261
+ signer: context.signer,
262
+ instructions,
263
+ accountIndex: context.accountIndex,
264
+ constraintIndices: resolvedConstraintIndices,
265
+ leadingAccounts: resolvedLeadingAccounts,
266
+ preHookAccounts: resolvedPreHookAccounts,
267
+ postHookAccounts: resolvedPostHookAccounts,
268
+ squadsProgram: context.squadsProgram,
269
+ })
270
+ }
271
+
272
+ async function prepareTopLevelInstructions({
273
+ instructions,
274
+ context,
275
+ rawLookupTableAddresses,
276
+ }: {
277
+ instructions: TransactionInstruction[]
278
+ context: LoopscaleVaultPreparationContext
279
+ rawLookupTableAddresses: PublicKey[]
280
+ }): Promise<LoopscalePreparedTransaction[]> {
281
+ const preparedTransactions: LoopscalePreparedTransaction[] = []
282
+ let pendingComputeInstructions: TransactionInstruction[] = []
283
+ let currentInstructions: TransactionInstruction[] = []
284
+ let currentMode: "standalone" | "wrapped" | null = null
285
+
286
+ const flush = async () => {
287
+ if (currentInstructions.length === 0) {
288
+ pendingComputeInstructions = []
289
+ currentMode = null
290
+ return
291
+ }
292
+
293
+ if (currentMode === "standalone") {
294
+ preparedTransactions.push({
295
+ setupInstructions: [],
296
+ instructions: [...pendingComputeInstructions, ...currentInstructions],
297
+ signers: [],
298
+ addressLookupTableAddresses: rawLookupTableAddresses,
299
+ requiresLoopscaleCoSign: false,
300
+ })
301
+ } else {
302
+ const wrappedInstruction = await wrapVaultSignedInstructions(currentInstructions, context)
303
+ preparedTransactions.push({
304
+ setupInstructions: [],
305
+ instructions: [...pendingComputeInstructions, wrappedInstruction],
306
+ signers: [],
307
+ addressLookupTableAddresses: [],
308
+ requiresLoopscaleCoSign: false,
309
+ })
310
+ }
311
+
312
+ pendingComputeInstructions = []
313
+ currentInstructions = []
314
+ currentMode = null
315
+ }
316
+
317
+ for (const instruction of instructions) {
318
+ if (isIgnorableLoopscaleTopLevelInstruction(instruction)) {
319
+ continue
320
+ }
321
+
322
+ if (isComputeBudgetInstruction(instruction)) {
323
+ pendingComputeInstructions.push(instruction)
324
+ continue
325
+ }
326
+
327
+ const nextMode = instructionNeedsVaultWrapping(instruction, context) ? "wrapped" : "standalone"
328
+ if (currentMode && currentMode !== nextMode) {
329
+ await flush()
330
+ }
331
+
332
+ currentMode = nextMode
333
+ currentInstructions.push(instruction)
334
+ }
335
+
336
+ await flush()
337
+ return preparedTransactions
338
+ }
339
+
340
+ /**
341
+ * Internal adapter used by `LoopscaleClient.prepareVaultTransactions(...)`.
342
+ *
343
+ * It converts raw Loopscale responses into manager-ready transaction pieces:
344
+ * standalone top-level transactions, wrapped top-level vault-signed
345
+ * transactions, or wrapped Loopscale sync transactions.
346
+ */
347
+ export async function prepareLoopscaleVaultTransactions({
348
+ response,
349
+ context,
350
+ }: {
351
+ response: LoopscaleTransactionResponse
352
+ context: LoopscaleVaultPreparationContext
353
+ }): Promise<LoopscalePreparedTransaction[]> {
354
+ const rawResponses = getLoopscaleTransactionResponses(response)
355
+ const preparedTransactions: LoopscalePreparedTransaction[] = []
356
+
357
+ for (const rawResponse of rawResponses) {
358
+ const rawTransaction = deserializeLoopscaleTransactionResponse(rawResponse)
359
+ const instructions = await decompileTransaction(context, rawTransaction)
360
+ const segment = getLoopscaleInstructionSegment(instructions)
361
+ const rawLookupTableAddresses = rawTransaction.message.addressTableLookups.map((lookup) => lookup.accountKey)
362
+
363
+ if (!segment) {
364
+ preparedTransactions.push(
365
+ ...(await prepareTopLevelInstructions({
366
+ instructions,
367
+ context,
368
+ rawLookupTableAddresses,
369
+ })),
370
+ )
371
+ continue
372
+ }
373
+
374
+ const {
375
+ prefixInstructions,
376
+ syncInstructions,
377
+ suffixInstructions,
378
+ } = splitLoopscaleInstructions(instructions, segment)
379
+ const syncResult = await createVaultSyncTransaction({
380
+ instructions: syncInstructions,
381
+ owner: context.vaultPda,
382
+ connection: context.connection,
383
+ policyPda: context.policyPda,
384
+ vaultPda: context.vaultPda,
385
+ signer: context.signer,
386
+ accountIndex: context.accountIndex,
387
+ constraintIndices: context.constraintIndices,
388
+ vaultAddress: context.vaultAddress,
389
+ leadingAccounts: context.leadingAccounts,
390
+ preHookAccounts: context.preHookAccounts,
391
+ postHookAccounts: context.postHookAccounts,
392
+ squadsProgram: context.squadsProgram,
393
+ autoManagePositions: false,
394
+ })
395
+
396
+ const prefixComputeInstructions = prefixInstructions.filter(isComputeBudgetInstruction)
397
+ const prefixNonComputeInstructions = prefixInstructions.filter(
398
+ (instruction) => !isComputeBudgetInstruction(instruction),
399
+ )
400
+
401
+ preparedTransactions.push(
402
+ ...(await prepareTopLevelInstructions({
403
+ instructions: prefixNonComputeInstructions,
404
+ context,
405
+ rawLookupTableAddresses,
406
+ })),
407
+ )
408
+
409
+ preparedTransactions.push({
410
+ setupInstructions: syncResult.setupInstructions,
411
+ instructions: [
412
+ ...prefixComputeInstructions,
413
+ ...syncResult.preInstructions,
414
+ syncResult.instruction,
415
+ ...syncResult.postInstructions,
416
+ ],
417
+ signers: syncResult.signers,
418
+ addressLookupTableAddresses: mergeLookupTableAddresses(
419
+ syncResult.addressLookupTableAddresses,
420
+ rawLookupTableAddresses,
421
+ ),
422
+ requiresLoopscaleCoSign: true,
423
+ })
424
+
425
+ preparedTransactions.push(
426
+ ...(await prepareTopLevelInstructions({
427
+ instructions: suffixInstructions,
428
+ context,
429
+ rawLookupTableAddresses,
430
+ })),
431
+ )
432
+ }
433
+
434
+ return preparedTransactions
435
+ }