@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.
- package/build/client/vaults/index.d.ts +2 -0
- package/build/client/vaults/index.js +2 -0
- package/build/client/vaults/index.js.map +1 -1
- package/build/client/vaults/types/index.d.ts +2 -0
- package/build/client/vaults/types/index.js +2 -0
- package/build/client/vaults/types/index.js.map +1 -1
- package/build/client/vaults/types/kaminoFarmEntry.d.ts +15 -0
- package/build/client/vaults/types/kaminoFarmEntry.js +17 -0
- package/build/client/vaults/types/kaminoFarmEntry.js.map +1 -0
- package/build/client/vaults/types/kaminoObligationEntry.d.ts +21 -4
- package/build/client/vaults/types/kaminoObligationEntry.js +2 -1
- package/build/client/vaults/types/kaminoObligationEntry.js.map +1 -1
- package/build/client/vaults/types/positionUpdate.d.ts +9 -0
- package/build/client/vaults/types/positionUpdate.js +23 -0
- package/build/client/vaults/types/positionUpdate.js.map +1 -1
- package/build/client/vaults/types/proposalAction.js +0 -3
- package/build/client/vaults/types/proposalAction.js.map +1 -1
- package/build/client/vaults/types/reserveFarmMapping.d.ts +19 -0
- package/build/client/vaults/types/reserveFarmMapping.js +18 -0
- package/build/client/vaults/types/reserveFarmMapping.js.map +1 -0
- package/build/client/vaults/types/strategyPosition.d.ts +5 -0
- package/build/client/vaults/types/strategyPosition.js +5 -0
- package/build/client/vaults/types/strategyPosition.js.map +1 -1
- package/build/exponentVaults/aumCalculator.d.ts +25 -4
- package/build/exponentVaults/aumCalculator.js +236 -15
- package/build/exponentVaults/aumCalculator.js.map +1 -1
- package/build/exponentVaults/fetcher.d.ts +52 -0
- package/build/exponentVaults/fetcher.js +199 -0
- package/build/exponentVaults/fetcher.js.map +1 -0
- package/build/exponentVaults/index.d.ts +10 -9
- package/build/exponentVaults/index.js +26 -8
- package/build/exponentVaults/index.js.map +1 -1
- package/build/exponentVaults/kamino-farms.d.ts +144 -0
- package/build/exponentVaults/kamino-farms.js +396 -0
- package/build/exponentVaults/kamino-farms.js.map +1 -0
- package/build/exponentVaults/loopscale/client.d.ts +240 -0
- package/build/exponentVaults/loopscale/client.js +590 -0
- package/build/exponentVaults/loopscale/client.js.map +1 -0
- package/build/exponentVaults/loopscale/client.test.d.ts +1 -0
- package/build/exponentVaults/loopscale/client.test.js +183 -0
- package/build/exponentVaults/loopscale/client.test.js.map +1 -0
- package/build/exponentVaults/loopscale/helpers.d.ts +29 -0
- package/build/exponentVaults/loopscale/helpers.js +119 -0
- package/build/exponentVaults/loopscale/helpers.js.map +1 -0
- package/build/exponentVaults/loopscale/index.d.ts +3 -0
- package/build/exponentVaults/loopscale/index.js +12 -0
- package/build/exponentVaults/loopscale/index.js.map +1 -0
- package/build/exponentVaults/loopscale/prepared-transactions.d.ts +13 -0
- package/build/exponentVaults/loopscale/prepared-transactions.js +271 -0
- package/build/exponentVaults/loopscale/prepared-transactions.js.map +1 -0
- package/build/exponentVaults/loopscale/prepared-transactions.test.d.ts +1 -0
- package/build/exponentVaults/loopscale/prepared-transactions.test.js +400 -0
- package/build/exponentVaults/loopscale/prepared-transactions.test.js.map +1 -0
- package/build/exponentVaults/loopscale/prepared-types.d.ts +62 -0
- package/build/exponentVaults/loopscale/prepared-types.js +3 -0
- package/build/exponentVaults/loopscale/prepared-types.js.map +1 -0
- package/build/exponentVaults/loopscale/response-plan.d.ts +69 -0
- package/build/exponentVaults/loopscale/response-plan.js +141 -0
- package/build/exponentVaults/loopscale/response-plan.js.map +1 -0
- package/build/exponentVaults/loopscale/response-plan.test.d.ts +1 -0
- package/build/exponentVaults/loopscale/response-plan.test.js +139 -0
- package/build/exponentVaults/loopscale/response-plan.test.js.map +1 -0
- package/build/exponentVaults/loopscale/send-plan.d.ts +75 -0
- package/build/exponentVaults/loopscale/send-plan.js +235 -0
- package/build/exponentVaults/loopscale/send-plan.js.map +1 -0
- package/build/exponentVaults/loopscale/types.d.ts +443 -0
- package/build/exponentVaults/loopscale/types.js +3 -0
- package/build/exponentVaults/loopscale/types.js.map +1 -0
- package/build/exponentVaults/loopscale-client.d.ts +113 -524
- package/build/exponentVaults/loopscale-client.js +296 -539
- package/build/exponentVaults/loopscale-client.js.map +1 -1
- package/build/exponentVaults/loopscale-client.test.d.ts +1 -0
- package/build/exponentVaults/loopscale-client.test.js +162 -0
- package/build/exponentVaults/loopscale-client.test.js.map +1 -0
- package/build/exponentVaults/loopscale-client.types.d.ts +425 -0
- package/build/exponentVaults/loopscale-client.types.js +3 -0
- package/build/exponentVaults/loopscale-client.types.js.map +1 -0
- package/build/exponentVaults/loopscale-execution.d.ts +125 -0
- package/build/exponentVaults/loopscale-execution.js +341 -0
- package/build/exponentVaults/loopscale-execution.js.map +1 -0
- package/build/exponentVaults/loopscale-execution.test.d.ts +1 -0
- package/build/exponentVaults/loopscale-execution.test.js +139 -0
- package/build/exponentVaults/loopscale-execution.test.js.map +1 -0
- package/build/exponentVaults/loopscale-vault.d.ts +115 -0
- package/build/exponentVaults/loopscale-vault.js +275 -0
- package/build/exponentVaults/loopscale-vault.js.map +1 -0
- package/build/exponentVaults/loopscale-vault.test.d.ts +1 -0
- package/build/exponentVaults/loopscale-vault.test.js +102 -0
- package/build/exponentVaults/loopscale-vault.test.js.map +1 -0
- package/build/exponentVaults/policyBuilders.d.ts +62 -0
- package/build/exponentVaults/policyBuilders.js +119 -2
- package/build/exponentVaults/policyBuilders.js.map +1 -1
- package/build/exponentVaults/pricePathResolver.d.ts +45 -0
- package/build/exponentVaults/pricePathResolver.js +198 -0
- package/build/exponentVaults/pricePathResolver.js.map +1 -0
- package/build/exponentVaults/pricePathResolver.test.d.ts +1 -0
- package/build/exponentVaults/pricePathResolver.test.js +369 -0
- package/build/exponentVaults/pricePathResolver.test.js.map +1 -0
- package/build/exponentVaults/syncTransaction.js +4 -1
- package/build/exponentVaults/syncTransaction.js.map +1 -1
- package/build/exponentVaults/titan-quote.js +170 -36
- package/build/exponentVaults/titan-quote.js.map +1 -1
- package/build/exponentVaults/vault-instruction-types.d.ts +363 -0
- package/build/exponentVaults/vault-instruction-types.js +128 -0
- package/build/exponentVaults/vault-instruction-types.js.map +1 -0
- package/build/exponentVaults/vault-interaction.d.ts +203 -343
- package/build/exponentVaults/vault-interaction.js +1894 -426
- package/build/exponentVaults/vault-interaction.js.map +1 -1
- package/build/exponentVaults/vault-interaction.kamino-vault.test.d.ts +1 -0
- package/build/exponentVaults/vault-interaction.kamino-vault.test.js +143 -0
- package/build/exponentVaults/vault-interaction.kamino-vault.test.js.map +1 -0
- package/build/exponentVaults/vault.d.ts +51 -2
- package/build/exponentVaults/vault.js +324 -48
- package/build/exponentVaults/vault.js.map +1 -1
- package/build/exponentVaults/vaultTransactionBuilder.d.ts +100 -134
- package/build/exponentVaults/vaultTransactionBuilder.js +383 -285
- package/build/exponentVaults/vaultTransactionBuilder.js.map +1 -1
- package/build/exponentVaults/vaultTransactionBuilder.test.d.ts +1 -0
- package/build/exponentVaults/vaultTransactionBuilder.test.js +297 -0
- package/build/exponentVaults/vaultTransactionBuilder.test.js.map +1 -0
- package/build/marketThree.d.ts +6 -2
- package/build/marketThree.js +10 -8
- package/build/marketThree.js.map +1 -1
- package/package.json +34 -32
- package/src/client/vaults/index.ts +2 -0
- package/src/client/vaults/types/index.ts +2 -0
- package/src/client/vaults/types/kaminoFarmEntry.ts +32 -0
- package/src/client/vaults/types/kaminoObligationEntry.ts +6 -3
- package/src/client/vaults/types/positionUpdate.ts +62 -0
- package/src/client/vaults/types/proposalAction.ts +0 -3
- package/src/client/vaults/types/reserveFarmMapping.ts +35 -0
- package/src/client/vaults/types/strategyPosition.ts +18 -1
- package/src/exponentVaults/aumCalculator.ts +353 -16
- package/src/exponentVaults/fetcher.ts +257 -0
- package/src/exponentVaults/index.ts +65 -40
- package/src/exponentVaults/kamino-farms.ts +538 -0
- package/src/exponentVaults/loopscale/client.ts +808 -0
- package/src/exponentVaults/loopscale/helpers.ts +172 -0
- package/src/exponentVaults/loopscale/index.ts +57 -0
- package/src/exponentVaults/loopscale/prepared-transactions.ts +435 -0
- package/src/exponentVaults/loopscale/prepared-types.ts +73 -0
- package/src/exponentVaults/loopscale/types.ts +466 -0
- package/src/exponentVaults/policyBuilders.ts +170 -0
- package/src/exponentVaults/pricePathResolver.test.ts +466 -0
- package/src/exponentVaults/pricePathResolver.ts +273 -0
- package/src/exponentVaults/syncTransaction.ts +6 -1
- package/src/exponentVaults/titan-quote.ts +231 -45
- package/src/exponentVaults/vault-instruction-types.ts +493 -0
- package/src/exponentVaults/vault-interaction.kamino-vault.test.ts +149 -0
- package/src/exponentVaults/vault-interaction.ts +2818 -799
- package/src/exponentVaults/vault.ts +474 -63
- package/src/exponentVaults/vaultTransactionBuilder.test.ts +349 -0
- package/src/exponentVaults/vaultTransactionBuilder.ts +581 -433
- package/src/marketThree.ts +14 -6
- 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
|
+
}
|