@reflectmoney/junior 1.0.0

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 (122) hide show
  1. package/README.md +119 -0
  2. package/dist/classes/JuniorTranche.d.ts +201 -0
  3. package/dist/classes/JuniorTranche.js +709 -0
  4. package/dist/classes/PdaClient.d.ts +20 -0
  5. package/dist/classes/PdaClient.js +68 -0
  6. package/dist/classes/index.d.ts +2 -0
  7. package/dist/classes/index.js +18 -0
  8. package/dist/constants/index.d.ts +22 -0
  9. package/dist/constants/index.js +25 -0
  10. package/dist/generated/accounts/asset.d.ts +38 -0
  11. package/dist/generated/accounts/asset.js +74 -0
  12. package/dist/generated/accounts/cooldown.d.ts +40 -0
  13. package/dist/generated/accounts/cooldown.js +79 -0
  14. package/dist/generated/accounts/index.d.ts +12 -0
  15. package/dist/generated/accounts/index.js +28 -0
  16. package/dist/generated/accounts/liquidityPool.d.ts +57 -0
  17. package/dist/generated/accounts/liquidityPool.js +81 -0
  18. package/dist/generated/accounts/settings.d.ts +41 -0
  19. package/dist/generated/accounts/settings.js +80 -0
  20. package/dist/generated/accounts/userPermissions.d.ts +34 -0
  21. package/dist/generated/accounts/userPermissions.js +70 -0
  22. package/dist/generated/errors/index.d.ts +8 -0
  23. package/dist/generated/errors/index.js +24 -0
  24. package/dist/generated/errors/rlp.d.ts +131 -0
  25. package/dist/generated/errors/rlp.js +197 -0
  26. package/dist/generated/index.d.ts +12 -0
  27. package/dist/generated/index.js +28 -0
  28. package/dist/generated/instructions/addAsset.d.ts +80 -0
  29. package/dist/generated/instructions/addAsset.js +188 -0
  30. package/dist/generated/instructions/createPermissionAccount.d.ts +67 -0
  31. package/dist/generated/instructions/createPermissionAccount.js +166 -0
  32. package/dist/generated/instructions/deposit.d.ts +115 -0
  33. package/dist/generated/instructions/deposit.js +294 -0
  34. package/dist/generated/instructions/forceRemoveAsset.d.ts +82 -0
  35. package/dist/generated/instructions/forceRemoveAsset.js +186 -0
  36. package/dist/generated/instructions/freezeFunctionality.d.ts +72 -0
  37. package/dist/generated/instructions/freezeFunctionality.js +175 -0
  38. package/dist/generated/instructions/index.d.ts +23 -0
  39. package/dist/generated/instructions/index.js +39 -0
  40. package/dist/generated/instructions/initializeLp.d.ts +99 -0
  41. package/dist/generated/instructions/initializeLp.js +230 -0
  42. package/dist/generated/instructions/initializePoolReserve.d.ts +83 -0
  43. package/dist/generated/instructions/initializePoolReserve.js +228 -0
  44. package/dist/generated/instructions/initializeRlp.d.ts +67 -0
  45. package/dist/generated/instructions/initializeRlp.js +163 -0
  46. package/dist/generated/instructions/requestWithdrawal.d.ts +99 -0
  47. package/dist/generated/instructions/requestWithdrawal.js +254 -0
  48. package/dist/generated/instructions/slash.d.ts +151 -0
  49. package/dist/generated/instructions/slash.js +212 -0
  50. package/dist/generated/instructions/swap.d.ts +119 -0
  51. package/dist/generated/instructions/swap.js +309 -0
  52. package/dist/generated/instructions/updateActionRole.d.ts +76 -0
  53. package/dist/generated/instructions/updateActionRole.js +174 -0
  54. package/dist/generated/instructions/updateDepositCap.d.ts +71 -0
  55. package/dist/generated/instructions/updateDepositCap.js +156 -0
  56. package/dist/generated/instructions/updateOracle.d.ts +66 -0
  57. package/dist/generated/instructions/updateOracle.js +151 -0
  58. package/dist/generated/instructions/updateRoleHolder.d.ts +80 -0
  59. package/dist/generated/instructions/updateRoleHolder.js +185 -0
  60. package/dist/generated/instructions/withdraw.d.ts +95 -0
  61. package/dist/generated/instructions/withdraw.js +235 -0
  62. package/dist/generated/programs/index.d.ts +8 -0
  63. package/dist/generated/programs/index.js +24 -0
  64. package/dist/generated/programs/rlp.d.ts +75 -0
  65. package/dist/generated/programs/rlp.js +233 -0
  66. package/dist/generated/shared/index.d.ts +49 -0
  67. package/dist/generated/shared/index.js +94 -0
  68. package/dist/generated/types/accessControl.d.ts +20 -0
  69. package/dist/generated/types/accessControl.js +29 -0
  70. package/dist/generated/types/accessLevel.d.ts +16 -0
  71. package/dist/generated/types/accessLevel.js +28 -0
  72. package/dist/generated/types/accessMap.d.ts +20 -0
  73. package/dist/generated/types/accessMap.js +35 -0
  74. package/dist/generated/types/action.d.ts +32 -0
  75. package/dist/generated/types/action.js +44 -0
  76. package/dist/generated/types/actionMapping.d.ts +22 -0
  77. package/dist/generated/types/actionMapping.js +31 -0
  78. package/dist/generated/types/addAssetEvent.d.ts +17 -0
  79. package/dist/generated/types/addAssetEvent.js +30 -0
  80. package/dist/generated/types/createPermissionAccountEvent.d.ts +16 -0
  81. package/dist/generated/types/createPermissionAccountEvent.js +28 -0
  82. package/dist/generated/types/depositEvent.d.ts +27 -0
  83. package/dist/generated/types/depositEvent.js +36 -0
  84. package/dist/generated/types/depositRewardEvent.d.ts +21 -0
  85. package/dist/generated/types/depositRewardEvent.js +30 -0
  86. package/dist/generated/types/forceRemoveAssetEvent.d.ts +18 -0
  87. package/dist/generated/types/forceRemoveAssetEvent.js +32 -0
  88. package/dist/generated/types/freezeProtocolActionEvent.d.ts +20 -0
  89. package/dist/generated/types/freezeProtocolActionEvent.js +29 -0
  90. package/dist/generated/types/index.d.ts +32 -0
  91. package/dist/generated/types/index.js +48 -0
  92. package/dist/generated/types/initializeLiquidityPoolEvent.d.ts +17 -0
  93. package/dist/generated/types/initializeLiquidityPoolEvent.js +30 -0
  94. package/dist/generated/types/initializeRlpEvent.d.ts +15 -0
  95. package/dist/generated/types/initializeRlpEvent.js +22 -0
  96. package/dist/generated/types/killSwitch.d.ts +15 -0
  97. package/dist/generated/types/killSwitch.js +22 -0
  98. package/dist/generated/types/levelRoles.d.ts +18 -0
  99. package/dist/generated/types/levelRoles.js +23 -0
  100. package/dist/generated/types/oracle.d.ts +25 -0
  101. package/dist/generated/types/oracle.js +56 -0
  102. package/dist/generated/types/requestWithdrawEvent.d.ts +21 -0
  103. package/dist/generated/types/requestWithdrawEvent.js +30 -0
  104. package/dist/generated/types/role.d.ts +21 -0
  105. package/dist/generated/types/role.js +33 -0
  106. package/dist/generated/types/slashEvent.d.ts +27 -0
  107. package/dist/generated/types/slashEvent.js +36 -0
  108. package/dist/generated/types/swapEvent.d.ts +23 -0
  109. package/dist/generated/types/swapEvent.js +32 -0
  110. package/dist/generated/types/update.d.ts +16 -0
  111. package/dist/generated/types/update.js +28 -0
  112. package/dist/generated/types/updateActionRoleEvent.d.ts +22 -0
  113. package/dist/generated/types/updateActionRoleEvent.js +31 -0
  114. package/dist/generated/types/updateDepositCapEvent.d.ts +21 -0
  115. package/dist/generated/types/updateDepositCapEvent.js +30 -0
  116. package/dist/generated/types/updateOracleEvent.d.ts +18 -0
  117. package/dist/generated/types/updateOracleEvent.js +32 -0
  118. package/dist/generated/types/updateRoleHolderEvent.d.ts +22 -0
  119. package/dist/generated/types/updateRoleHolderEvent.js +31 -0
  120. package/dist/index.d.ts +3 -0
  121. package/dist/index.js +19 -0
  122. package/package.json +41 -0
@@ -0,0 +1,709 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JuniorTranche = void 0;
4
+ const token_1 = require("@solana-program/token");
5
+ const kit_1 = require("@solana/kit");
6
+ const generated_1 = require("../generated");
7
+ const PdaClient_1 = require("./PdaClient");
8
+ class JuniorTranche {
9
+ connection;
10
+ settings;
11
+ liquidityPools;
12
+ assets;
13
+ constructor(connection) {
14
+ this.connection = connection;
15
+ }
16
+ async load() {
17
+ this.settings = await this.getSettingsData();
18
+ this.liquidityPools = await this.getLiquidityPools();
19
+ this.assets = await this.getAssets();
20
+ }
21
+ async getSettingsData() {
22
+ const [settingsAddress] = await PdaClient_1.PdaClient.deriveSettings();
23
+ const account = await (0, generated_1.fetchSettings)(this.connection, settingsAddress);
24
+ return account.data;
25
+ }
26
+ async getLiquidityPools() {
27
+ const decoder = (0, generated_1.getLiquidityPoolDecoder)();
28
+ const programAccounts = await this.connection
29
+ .getProgramAccounts(generated_1.RLP_PROGRAM_ADDRESS, {
30
+ encoding: "base64",
31
+ withContext: false,
32
+ filters: [
33
+ {
34
+ memcmp: {
35
+ encoding: "base64",
36
+ offset: BigInt(0),
37
+ bytes: Buffer.from(generated_1.LIQUIDITY_POOL_DISCRIMINATOR).toString("base64"),
38
+ },
39
+ },
40
+ ],
41
+ })
42
+ .send();
43
+ return programAccounts.map((account) => {
44
+ const [b64] = account.account.data;
45
+ const bytes = new Uint8Array(Buffer.from(b64, "base64"));
46
+ return {
47
+ address: account.pubkey,
48
+ data: decoder.decode(bytes),
49
+ };
50
+ });
51
+ }
52
+ async getLiquidityPoolData(liquidityPoolId) {
53
+ const [liquidityPoolAddress] = await PdaClient_1.PdaClient.deriveLiquidityPool(liquidityPoolId);
54
+ const account = await (0, generated_1.fetchLiquidityPool)(this.connection, liquidityPoolAddress);
55
+ return account.data;
56
+ }
57
+ async getAssets() {
58
+ if (this.assets?.length > 0) {
59
+ return this.assets;
60
+ }
61
+ const encoder = (0, generated_1.getAssetEncoder)();
62
+ const decoder = (0, generated_1.getAssetDecoder)();
63
+ const programAccounts = await this.connection
64
+ .getProgramAccounts(generated_1.RLP_PROGRAM_ADDRESS, {
65
+ encoding: "base64",
66
+ withContext: false,
67
+ filters: [
68
+ { dataSize: BigInt(encoder.fixedSize) },
69
+ ],
70
+ })
71
+ .send();
72
+ const preSorted = programAccounts
73
+ .map((account) => {
74
+ const [b64] = account.account.data;
75
+ const bytes = new Uint8Array(Buffer.from(b64, "base64"));
76
+ return {
77
+ address: account.pubkey,
78
+ data: decoder.decode(bytes),
79
+ };
80
+ });
81
+ const result = preSorted.sort((a, b) => a.data.index - b.data.index);
82
+ this.assets = result;
83
+ return result;
84
+ }
85
+ async getCooldowns() {
86
+ const encoder = (0, generated_1.getCooldownEncoder)();
87
+ const decoder = (0, generated_1.getCooldownDecoder)();
88
+ const programAccounts = await this.connection
89
+ .getProgramAccounts(generated_1.RLP_PROGRAM_ADDRESS, {
90
+ encoding: "base64",
91
+ withContext: false,
92
+ filters: [
93
+ { dataSize: BigInt(encoder.fixedSize) },
94
+ ],
95
+ })
96
+ .send();
97
+ return programAccounts.map((account) => {
98
+ const [b64] = account.account.data;
99
+ const bytes = new Uint8Array(Buffer.from(b64, "base64"));
100
+ return {
101
+ address: account.pubkey,
102
+ data: decoder.decode(bytes),
103
+ };
104
+ });
105
+ }
106
+ async getCooldownsByUser(user) {
107
+ const allCooldowns = await this.getCooldowns();
108
+ return allCooldowns.filter((c) => c.data.authority === user);
109
+ }
110
+ async getUserPermissions() {
111
+ const decoder = (0, generated_1.getUserPermissionsDecoder)();
112
+ const programAccounts = await this.connection
113
+ .getProgramAccounts(generated_1.RLP_PROGRAM_ADDRESS, {
114
+ encoding: "base64",
115
+ withContext: false,
116
+ filters: [
117
+ {
118
+ memcmp: {
119
+ encoding: "base64",
120
+ offset: BigInt(0),
121
+ bytes: Buffer.from(generated_1.USER_PERMISSIONS_DISCRIMINATOR).toString("base64"),
122
+ },
123
+ },
124
+ ],
125
+ })
126
+ .send();
127
+ return programAccounts.map((account) => {
128
+ const [b64] = account.account.data;
129
+ const bytes = new Uint8Array(Buffer.from(b64, "base64"));
130
+ return {
131
+ address: account.pubkey,
132
+ data: decoder.decode(bytes),
133
+ };
134
+ });
135
+ }
136
+ async getUserPermissionsFromAddress(address) {
137
+ const allPermissions = await this.getUserPermissions();
138
+ return allPermissions.filter((p) => p.data.authority === address);
139
+ }
140
+ async initializeRlp(signer, swapFeeBps) {
141
+ return (0, generated_1.getInitializeRlpInstructionAsync)({
142
+ signer,
143
+ swapFeeBps,
144
+ program: generated_1.RLP_PROGRAM_ADDRESS,
145
+ });
146
+ }
147
+ async initializeLiquidityPool(signer, args) {
148
+ const settings = await this.getSettingsData();
149
+ const [liquidityPoolAddress] = await PdaClient_1.PdaClient.deriveLiquidityPool(settings.liquidityPools);
150
+ return (0, generated_1.getInitializeLpInstructionAsync)({
151
+ signer,
152
+ liquidityPool: liquidityPoolAddress,
153
+ lpTokenMint: args.lpTokenMint,
154
+ cooldownDuration: args.cooldownDuration,
155
+ depositCap: args.depositCap,
156
+ assets: new Uint8Array(args.assets),
157
+ protectedVault: (args.protectedVault ?? null),
158
+ program: generated_1.RLP_PROGRAM_ADDRESS,
159
+ });
160
+ }
161
+ /**
162
+ * Create the canonical pool reserve ATA for a single asset. Required after
163
+ * `initialize_lp` before any deposit/withdraw/swap against that asset can
164
+ * succeed (audit-E02). Idempotent: safe to call again on an already-
165
+ * initialized reserve.
166
+ */
167
+ async initializePoolReserve(signer, liquidityPoolId, assetMint) {
168
+ const [liquidityPoolAddress] = await PdaClient_1.PdaClient.deriveLiquidityPool(liquidityPoolId);
169
+ const [assetPda] = await PdaClient_1.PdaClient.deriveAsset(assetMint);
170
+ return (0, generated_1.getInitializePoolReserveInstructionAsync)({
171
+ signer,
172
+ liquidityPool: liquidityPoolAddress,
173
+ asset: assetPda,
174
+ assetMint,
175
+ liquidityPoolId,
176
+ });
177
+ }
178
+ /**
179
+ * Admin recovery for a permanently-frozen pool asset (audit-M02). Drops the
180
+ * asset from the pool's reserve list. Requires the asset's pool reserve ATA
181
+ * to actually be in the SPL `Frozen` state.
182
+ */
183
+ async forceRemoveAsset(signer, liquidityPoolId, assetMint) {
184
+ const [liquidityPoolAddress] = await PdaClient_1.PdaClient.deriveLiquidityPool(liquidityPoolId);
185
+ const [assetPda] = await PdaClient_1.PdaClient.deriveAsset(assetMint);
186
+ const [poolTokenAccount] = await (0, token_1.findAssociatedTokenPda)({
187
+ mint: assetMint,
188
+ owner: liquidityPoolAddress,
189
+ tokenProgram: token_1.TOKEN_PROGRAM_ADDRESS,
190
+ });
191
+ return (0, generated_1.getForceRemoveAssetInstructionAsync)({
192
+ signer,
193
+ liquidityPool: liquidityPoolAddress,
194
+ asset: assetPda,
195
+ assetMint,
196
+ poolTokenAccount,
197
+ liquidityPoolId,
198
+ program: generated_1.RLP_PROGRAM_ADDRESS,
199
+ });
200
+ }
201
+ async addAsset(signer, assetMint, oracle, accessLevel) {
202
+ return (0, generated_1.getAddAssetInstructionAsync)({
203
+ signer,
204
+ assetMint,
205
+ oracle,
206
+ accessLevel,
207
+ program: generated_1.RLP_PROGRAM_ADDRESS,
208
+ });
209
+ }
210
+ async updateOracle(signer, assetMint, oracle) {
211
+ const assets = await this.getAssets();
212
+ const assetEntry = assets.find((a) => a.data.mint === assetMint);
213
+ if (!assetEntry)
214
+ throw new Error(`Asset not found for mint ${assetMint}`);
215
+ return (0, generated_1.getUpdateOracleInstructionAsync)({
216
+ signer,
217
+ asset: assetEntry.address,
218
+ oracle,
219
+ program: generated_1.RLP_PROGRAM_ADDRESS,
220
+ });
221
+ }
222
+ /**
223
+ * NAV-based junior-tranche slash (audit-M06 redesign). Pulls from this
224
+ * pool's reserve of the proxy's `stablecoinMint` directly into the proxy's
225
+ * vault, capped on-chain at the current mark-to-market loss
226
+ * `principal + commission - vault_value`.
227
+ *
228
+ * The pool must have been initialized with `protectedVault` pinned to the
229
+ * supplied `proxyState`. The proxy's stablecoin_mint must equal the
230
+ * `stablecoinMint` arg, and the pool must whitelist that mint as one of
231
+ * its assets.
232
+ *
233
+ * @param signer Caller (must hold the Slash action role).
234
+ * @param liquidityPoolId The pool index.
235
+ * @param stablecoinMint The proxy's underlying stablecoin (USDC+).
236
+ * @param proxyState The senior tranche's ProxyState account.
237
+ * @param amount Requested amount in raw stablecoin_mint units.
238
+ * Capped at the current gap; pass any positive
239
+ * value up to that cap.
240
+ */
241
+ async slash(signer, liquidityPoolId, stablecoinMint, proxyState, amount) {
242
+ const [liquidityPoolAddress] = await PdaClient_1.PdaClient.deriveLiquidityPool(liquidityPoolId);
243
+ const assets = await this.getAssets();
244
+ const assetEntry = assets.find((a) => a.data.mint === stablecoinMint);
245
+ if (!assetEntry)
246
+ throw new Error(`Asset not found for stablecoin mint ${stablecoinMint}`);
247
+ const oracleAddress = assetEntry.data.oracle.fields[0];
248
+ const [liquidityPoolTokenAccount] = await (0, token_1.findAssociatedTokenPda)({
249
+ mint: stablecoinMint,
250
+ owner: liquidityPoolAddress,
251
+ tokenProgram: token_1.TOKEN_PROGRAM_ADDRESS,
252
+ });
253
+ const [protectedVaultTokenAccount] = await (0, token_1.findAssociatedTokenPda)({
254
+ mint: stablecoinMint,
255
+ owner: proxyState,
256
+ tokenProgram: token_1.TOKEN_PROGRAM_ADDRESS,
257
+ });
258
+ return (0, generated_1.getSlashInstructionAsync)({
259
+ signer,
260
+ liquidityPool: liquidityPoolAddress,
261
+ asset: assetEntry.address,
262
+ stablecoinMint,
263
+ liquidityPoolTokenAccount,
264
+ proxyState,
265
+ protectedVaultTokenAccount,
266
+ oracle: oracleAddress,
267
+ liquidityPoolId,
268
+ amount,
269
+ program: generated_1.RLP_PROGRAM_ADDRESS,
270
+ });
271
+ }
272
+ /**
273
+ * Inject pre-fetched state (useful for testing without an RPC connection).
274
+ */
275
+ loadFromCache(settings, liquidityPools, assets) {
276
+ this.settings = settings;
277
+ this.liquidityPools = liquidityPools;
278
+ this.assets = assets;
279
+ }
280
+ /**
281
+ * Return the assets that belong to a specific liquidity pool,
282
+ * ordered by the pool's `assets` array.
283
+ */
284
+ async getPoolAssets(liquidityPoolId) {
285
+ const lpEntry = this.liquidityPools.find((lp) => lp.data.index === liquidityPoolId);
286
+ if (!lpEntry)
287
+ throw new Error(`Liquidity pool ${liquidityPoolId} not found`);
288
+ const allAssets = await this.getAssets();
289
+ const poolAssetIndices = Array.from(lpEntry.data.assets).slice(0, lpEntry.data.assetCount);
290
+ return poolAssetIndices.map((assetIndex) => {
291
+ const asset = allAssets.find((a) => a.data.index === assetIndex);
292
+ if (!asset)
293
+ throw new Error(`Asset with index ${assetIndex} not found for pool ${liquidityPoolId}`);
294
+ return asset;
295
+ });
296
+ }
297
+ /**
298
+ * Build the remaining accounts needed by calculate_total_pool_value().
299
+ * Per asset (in pool asset order): [pool_ata, asset_pda, oracle, mint]
300
+ */
301
+ async buildPoolValueRemainingAccounts(liquidityPoolAddress, liquidityPoolId) {
302
+ const assets = await this.getPoolAssets(liquidityPoolId);
303
+ const remaining = [];
304
+ for (const asset of assets) {
305
+ const [poolAta] = await (0, token_1.findAssociatedTokenPda)({
306
+ mint: asset.data.mint,
307
+ owner: liquidityPoolAddress,
308
+ tokenProgram: token_1.TOKEN_PROGRAM_ADDRESS,
309
+ });
310
+ const oracleAddress = asset.data.oracle.fields[0];
311
+ remaining.push({ address: poolAta, role: kit_1.AccountRole.READONLY }, { address: asset.address, role: kit_1.AccountRole.READONLY }, { address: oracleAddress, role: kit_1.AccountRole.READONLY }, { address: asset.data.mint, role: kit_1.AccountRole.READONLY });
312
+ }
313
+ return remaining;
314
+ }
315
+ /**
316
+ * Build the remaining accounts needed by withdraw's load_assets,
317
+ * load_reserves, and load_user_token_accounts.
318
+ *
319
+ * Layout (must match on-chain expectations):
320
+ * - First N: asset PDAs (positional, for load_assets)
321
+ * - Then N: pool reserve ATAs (searched by load_reserves)
322
+ * - Then N: user token ATAs (searched by load_user_token_accounts)
323
+ */
324
+ async buildWithdrawRemainingAccounts(signer, liquidityPoolAddress, liquidityPoolId) {
325
+ const assets = await this.getPoolAssets(liquidityPoolId);
326
+ const assetPdas = [];
327
+ const reserves = [];
328
+ const userAtas = [];
329
+ for (const asset of assets) {
330
+ assetPdas.push({ address: asset.address, role: kit_1.AccountRole.READONLY });
331
+ const [poolReserve] = await (0, token_1.findAssociatedTokenPda)({
332
+ mint: asset.data.mint,
333
+ owner: liquidityPoolAddress,
334
+ tokenProgram: token_1.TOKEN_PROGRAM_ADDRESS,
335
+ });
336
+ reserves.push({ address: poolReserve, role: kit_1.AccountRole.WRITABLE });
337
+ const [userAta] = await (0, token_1.findAssociatedTokenPda)({
338
+ mint: asset.data.mint,
339
+ owner: signer.address,
340
+ tokenProgram: token_1.TOKEN_PROGRAM_ADDRESS,
341
+ });
342
+ userAtas.push({ address: userAta, role: kit_1.AccountRole.WRITABLE });
343
+ }
344
+ return [...assetPdas, ...reserves, ...userAtas];
345
+ }
346
+ /**
347
+ * Append remaining account metas to a frozen instruction object,
348
+ * returning a new instruction with the extra accounts.
349
+ */
350
+ appendRemainingAccounts(ix, remaining) {
351
+ const extraMetas = remaining.map((r) => Object.freeze({ address: r.address, role: r.role }));
352
+ return Object.freeze({
353
+ ...ix,
354
+ accounts: [...ix.accounts, ...extraMetas],
355
+ });
356
+ }
357
+ async deposit(signer, amount, mint, liquidityPoolId, minLpTokens) {
358
+ const assets = await this.getAssets();
359
+ const assetEntry = assets.find((a) => a.data.mint === mint);
360
+ if (!assetEntry)
361
+ throw new Error(`Asset not found for mint ${mint}`);
362
+ const oracleAddress = assetEntry.data.oracle.fields[0];
363
+ const lpEntry = this.liquidityPools.find((lp) => lp.data.index === liquidityPoolId);
364
+ if (!lpEntry)
365
+ throw new Error(`Liquidity pool ${liquidityPoolId} not found`);
366
+ const [userAssetAccount] = await (0, token_1.findAssociatedTokenPda)({
367
+ mint,
368
+ owner: signer.address,
369
+ tokenProgram: token_1.TOKEN_PROGRAM_ADDRESS,
370
+ });
371
+ const ix = await (0, generated_1.getDepositInstructionAsync)({
372
+ signer,
373
+ permissions: generated_1.RLP_PROGRAM_ADDRESS,
374
+ liquidityPool: lpEntry.address,
375
+ lpToken: lpEntry.data.lpToken,
376
+ assetMint: mint,
377
+ userAssetAccount,
378
+ oracle: oracleAddress,
379
+ liquidityPoolIndex: liquidityPoolId,
380
+ amount,
381
+ minLpTokens: (minLpTokens ?? null),
382
+ program: generated_1.RLP_PROGRAM_ADDRESS,
383
+ });
384
+ const remaining = await this.buildPoolValueRemainingAccounts(lpEntry.address, liquidityPoolId);
385
+ return this.appendRemainingAccounts(ix, remaining);
386
+ }
387
+ async requestWithdrawal(signer, liquidityPoolId, amount) {
388
+ const lpEntry = this.liquidityPools.find((lp) => lp.data.index === liquidityPoolId);
389
+ if (!lpEntry)
390
+ throw new Error(`Liquidity pool ${liquidityPoolId} not found`);
391
+ const [cooldownAddress] = await PdaClient_1.PdaClient.deriveCooldown(liquidityPoolId, lpEntry.data.cooldowns);
392
+ const [signerLpTokenAccount] = await (0, token_1.findAssociatedTokenPda)({
393
+ mint: lpEntry.data.lpToken,
394
+ owner: signer.address,
395
+ tokenProgram: token_1.TOKEN_PROGRAM_ADDRESS,
396
+ });
397
+ return (0, generated_1.getRequestWithdrawalInstructionAsync)({
398
+ signer,
399
+ permissions: generated_1.RLP_PROGRAM_ADDRESS,
400
+ liquidityPool: lpEntry.address,
401
+ lpTokenMint: lpEntry.data.lpToken,
402
+ signerLpTokenAccount,
403
+ cooldown: cooldownAddress,
404
+ liquidityPoolId,
405
+ amount,
406
+ program: generated_1.RLP_PROGRAM_ADDRESS,
407
+ });
408
+ }
409
+ async withdraw(signer, liquidityPoolId, cooldownId) {
410
+ const lpEntry = this.liquidityPools.find((lp) => lp.data.index === liquidityPoolId);
411
+ if (!lpEntry)
412
+ throw new Error(`Liquidity pool ${liquidityPoolId} not found`);
413
+ const [cooldownAddress] = await PdaClient_1.PdaClient.deriveCooldown(liquidityPoolId, cooldownId);
414
+ // audit-1: withdraw needs the signer's LP token account so any excess in
415
+ // the cooldown ATA can be refunded.
416
+ const [signerLpTokenAccount] = await (0, token_1.findAssociatedTokenPda)({
417
+ mint: lpEntry.data.lpToken,
418
+ owner: signer.address,
419
+ tokenProgram: token_1.TOKEN_PROGRAM_ADDRESS,
420
+ });
421
+ const ix = await (0, generated_1.getWithdrawInstructionAsync)({
422
+ signer,
423
+ liquidityPool: lpEntry.address,
424
+ lpTokenMint: lpEntry.data.lpToken,
425
+ cooldown: cooldownAddress,
426
+ signerLpTokenAccount,
427
+ liquidityPoolId,
428
+ cooldownId,
429
+ program: generated_1.RLP_PROGRAM_ADDRESS,
430
+ });
431
+ const remaining = await this.buildWithdrawRemainingAccounts(signer, lpEntry.address, liquidityPoolId);
432
+ return this.appendRemainingAccounts(ix, remaining);
433
+ }
434
+ async swap(signer, liquidityPoolId, tokenFromMint, tokenToMint, amountIn, minOut) {
435
+ const assets = await this.getAssets();
436
+ const tokenFromEntry = assets.find((a) => a.data.mint === tokenFromMint);
437
+ if (!tokenFromEntry)
438
+ throw new Error(`Asset not found for mint ${tokenFromMint}`);
439
+ const tokenToEntry = assets.find((a) => a.data.mint === tokenToMint);
440
+ if (!tokenToEntry)
441
+ throw new Error(`Asset not found for mint ${tokenToMint}`);
442
+ const tokenFromOracle = tokenFromEntry.data.oracle
443
+ .fields[0];
444
+ const tokenToOracle = tokenToEntry.data.oracle
445
+ .fields[0];
446
+ const lpEntry = this.liquidityPools.find((lp) => lp.data.index === liquidityPoolId);
447
+ if (!lpEntry)
448
+ throw new Error(`Liquidity pool ${liquidityPoolId} not found`);
449
+ const [tokenFromPool] = await (0, token_1.findAssociatedTokenPda)({
450
+ mint: tokenFromMint,
451
+ owner: lpEntry.address,
452
+ tokenProgram: token_1.TOKEN_PROGRAM_ADDRESS,
453
+ });
454
+ const [tokenToPool] = await (0, token_1.findAssociatedTokenPda)({
455
+ mint: tokenToMint,
456
+ owner: lpEntry.address,
457
+ tokenProgram: token_1.TOKEN_PROGRAM_ADDRESS,
458
+ });
459
+ const [tokenFromSignerAccount] = await (0, token_1.findAssociatedTokenPda)({
460
+ mint: tokenFromMint,
461
+ owner: signer.address,
462
+ tokenProgram: token_1.TOKEN_PROGRAM_ADDRESS,
463
+ });
464
+ const [tokenToSignerAccount] = await (0, token_1.findAssociatedTokenPda)({
465
+ mint: tokenToMint,
466
+ owner: signer.address,
467
+ tokenProgram: token_1.TOKEN_PROGRAM_ADDRESS,
468
+ });
469
+ return (0, generated_1.getSwapInstructionAsync)({
470
+ signer,
471
+ liquidityPool: lpEntry.address,
472
+ tokenFrom: tokenFromMint,
473
+ tokenFromOracle,
474
+ tokenTo: tokenToMint,
475
+ tokenToOracle,
476
+ tokenFromPool,
477
+ tokenToPool,
478
+ tokenFromSignerAccount,
479
+ tokenToSignerAccount,
480
+ amountIn,
481
+ minOut: (minOut ?? null),
482
+ program: generated_1.RLP_PROGRAM_ADDRESS,
483
+ });
484
+ }
485
+ async createPermissionAccount(caller, newAdmin) {
486
+ return (0, generated_1.getCreatePermissionAccountInstructionAsync)({
487
+ caller,
488
+ newAdmin,
489
+ program: generated_1.RLP_PROGRAM_ADDRESS,
490
+ });
491
+ }
492
+ async updateRoleHolder(admin, targetAddress, role, update) {
493
+ const [updateAdminPermissions] = await PdaClient_1.PdaClient.deriveUserPermissions(targetAddress);
494
+ return (0, generated_1.getUpdateRoleHolderInstructionAsync)({
495
+ admin,
496
+ updateAdminPermissions,
497
+ address: targetAddress,
498
+ role,
499
+ update,
500
+ program: generated_1.RLP_PROGRAM_ADDRESS,
501
+ });
502
+ }
503
+ async updateActionRole(admin, action, role, update) {
504
+ return (0, generated_1.getUpdateActionRoleInstructionAsync)({
505
+ admin,
506
+ action,
507
+ role,
508
+ update,
509
+ program: generated_1.RLP_PROGRAM_ADDRESS,
510
+ });
511
+ }
512
+ async updateDepositCap(signer, liquidityPoolId, newCap) {
513
+ const lpEntry = this.liquidityPools.find((lp) => lp.data.index === liquidityPoolId);
514
+ if (!lpEntry)
515
+ throw new Error(`Liquidity pool ${liquidityPoolId} not found`);
516
+ return (0, generated_1.getUpdateDepositCapInstructionAsync)({
517
+ signer,
518
+ liquidityPool: lpEntry.address,
519
+ lockupId: liquidityPoolId,
520
+ newCap: newCap,
521
+ program: generated_1.RLP_PROGRAM_ADDRESS,
522
+ });
523
+ }
524
+ async freezeFunctionality(admin, action, freeze) {
525
+ return (0, generated_1.getFreezeFunctionalityInstructionAsync)({
526
+ admin,
527
+ action,
528
+ freeze,
529
+ program: generated_1.RLP_PROGRAM_ADDRESS,
530
+ });
531
+ }
532
+ async findAssetFromMint(mint) {
533
+ const assets = await this.getAssets();
534
+ const entry = assets.find((a) => a.data.mint === mint);
535
+ if (!entry)
536
+ throw new Error(`Asset not found for mint ${mint}`);
537
+ return entry.address;
538
+ }
539
+ getSettings() {
540
+ return this.settings;
541
+ }
542
+ getCachedLiquidityPools() {
543
+ return this.liquidityPools;
544
+ }
545
+ getCachedAssets() {
546
+ return this.assets;
547
+ }
548
+ // ========================================================================
549
+ // Oracle
550
+ // ========================================================================
551
+ /**
552
+ * Fetch and deserialize the Pyth PriceUpdateV2 oracle account for a given
553
+ * asset, returning `{ price, exponent }` ready to pass into
554
+ * `simulateDepositMath`.
555
+ *
556
+ * @param mint The asset mint whose oracle to read, OR a direct oracle
557
+ * address. When a mint is provided the oracle address is
558
+ * looked up from the cached asset list.
559
+ */
560
+ async fetchOraclePrice(mintOrOracle) {
561
+ // Resolve oracle address — try cached assets first, fall back to treating
562
+ // the input as a direct oracle address.
563
+ let oracleAddress = mintOrOracle;
564
+ const assets = await this.getAssets();
565
+ const assetEntry = assets.find((a) => a.data.mint === mintOrOracle);
566
+ if (assetEntry) {
567
+ oracleAddress = assetEntry.data.oracle.fields[0];
568
+ }
569
+ const account = await (0, kit_1.fetchEncodedAccount)(this.connection, oracleAddress);
570
+ if (!account.exists) {
571
+ throw new Error(`Oracle account not found: ${oracleAddress}`);
572
+ }
573
+ return JuniorTranche.deserializePythPrice(new Uint8Array(account.data));
574
+ }
575
+ /**
576
+ * Deserialize a raw Pyth PriceUpdateV2 account buffer into price fields.
577
+ *
578
+ * Borsh layout (after 8-byte Anchor discriminator):
579
+ * write_authority: Pubkey (32)
580
+ * verification_level: Full=[1] (1 byte) | Partial=[0,u8] (2 bytes)
581
+ * price_message {
582
+ * feed_id: [u8;32], price: i64, conf: u64, exponent: i32,
583
+ * publish_time: i64, ...
584
+ * }
585
+ */
586
+ static deserializePythPrice(data) {
587
+ const dv = new DataView(data.buffer, data.byteOffset, data.byteLength);
588
+ // Skip 8-byte discriminator + 32-byte write_authority = offset 40
589
+ const verificationVariant = data[40];
590
+ // Full = 1 byte, Partial = 2 bytes (variant + num_signatures)
591
+ const verificationSize = verificationVariant === 0 ? 2 : 1;
592
+ const pmOffset = 40 + verificationSize; // start of price_message
593
+ // feed_id: 32 bytes, then price(i64), conf(u64), exponent(i32), publish_time(i64)
594
+ const priceOffset = pmOffset + 32;
595
+ const exponentOffset = priceOffset + 8 + 8; // after price + conf
596
+ const publishTimeOffset = exponentOffset + 4;
597
+ return {
598
+ price: dv.getBigInt64(priceOffset, true),
599
+ exponent: dv.getInt32(exponentOffset, true),
600
+ publishTime: dv.getBigInt64(publishTimeOffset, true),
601
+ };
602
+ }
603
+ // ========================================================================
604
+ // Simulation math
605
+ // ========================================================================
606
+ /**
607
+ * Simulate deposit math off-chain.
608
+ *
609
+ * Mirrors the on-chain logic in:
610
+ * - `OraclePrice::mul` (oracle_price.rs)
611
+ * - `LiquidityPool::calculate_total_pool_value`
612
+ * - `LiquidityPool::calculate_lp_tokens_on_deposit`
613
+ *
614
+ * @param depositAmount Raw token amount to deposit (base units).
615
+ * @param depositAssetPrice Oracle price of the deposit asset (`{ price, exponent }`).
616
+ * @param depositAssetDecimals Decimals of the deposit token mint.
617
+ * @param poolReserves Array of { balance, price, exponent, decimals } for every
618
+ * asset currently in the pool (BEFORE the deposit).
619
+ * @param lpTokenSupply Current total supply of the LP token (includes dead shares).
620
+ * @param lpTokenDecimals Decimals of the LP token mint (typically 9).
621
+ * @returns The number of LP tokens the depositor would receive (bigint).
622
+ */
623
+ static simulateDepositMath(params) {
624
+ const { depositAmount, depositAssetPrice, depositAssetDecimals, poolReserves, lpTokenSupply, lpTokenDecimals, } = params;
625
+ // --- PreciseNumber constants (mirrors spl-math) ---
626
+ const ONE = 1000000000000n; // 10^12
627
+ const HALF = ONE / 2n;
628
+ const PRECISION = 18;
629
+ // PreciseNumber::new(x) → x * ONE
630
+ const pNew = (x) => x * ONE;
631
+ // PreciseNumber::to_imprecise(v) → (v + HALF) / ONE
632
+ const pToU64 = (v) => (v + HALF) / ONE;
633
+ // PreciseNumber::checked_mul(a, b) → (a * b + HALF) / ONE
634
+ const pMul = (a, b) => (a * b + HALF) / ONE;
635
+ // PreciseNumber::checked_div(a, b) → (a * ONE + HALF) / b
636
+ const pDiv = (a, b) => (a * ONE + HALF) / b;
637
+ // PreciseNumber::checked_add(a, b) → a + b
638
+ const pAdd = (a, b) => a + b;
639
+ // --- OraclePrice::mul(amount, token_decimals) ---
640
+ const oracleMul = (price, exponent, amount, tokenDecimals) => {
641
+ const decimalAdj = PRECISION - tokenDecimals;
642
+ const normalizedAmount = amount * 10n ** BigInt(decimalAdj);
643
+ if (exponent >= 0) {
644
+ return normalizedAmount * price * 10n ** BigInt(exponent);
645
+ }
646
+ else {
647
+ return (normalizedAmount * price) / 10n ** BigInt(-exponent);
648
+ }
649
+ };
650
+ // --- calculate_total_pool_value ---
651
+ let totalPoolValue = pNew(0n); // precise
652
+ for (const r of poolReserves) {
653
+ if (r.balance > 0n) {
654
+ const rawValue = oracleMul(r.price, r.exponent, r.balance, r.decimals);
655
+ totalPoolValue = pAdd(totalPoolValue, pNew(rawValue));
656
+ }
657
+ }
658
+ // --- deposit_value ---
659
+ const rawDepositValue = oracleMul(depositAssetPrice.price, depositAssetPrice.exponent, depositAmount, depositAssetDecimals);
660
+ const depositValue = pNew(rawDepositValue); // precise
661
+ // --- calculate_lp_tokens_on_deposit ---
662
+ const poolValueImprecise = pToU64(totalPoolValue);
663
+ if (lpTokenSupply === 0n || poolValueImprecise === 0n) {
664
+ // Initial deposit formula: deposit_value / 10^(PRECISION - lp_decimals)
665
+ const scaleDown = pNew(10n ** BigInt(PRECISION - lpTokenDecimals));
666
+ return BigInt(pToU64(pDiv(depositValue, scaleDown)));
667
+ }
668
+ else {
669
+ // Proportional: deposit_value * lp_supply / total_pool_value
670
+ const lpSupplyPrecise = pNew(lpTokenSupply);
671
+ const ratio = pDiv(pMul(depositValue, lpSupplyPrecise), totalPoolValue);
672
+ return BigInt(pToU64(ratio));
673
+ }
674
+ }
675
+ /**
676
+ * Simulate withdrawal math off-chain.
677
+ *
678
+ * Mirrors the on-chain logic in `withdraw()` which computes:
679
+ * `user_share_per_asset = reserve_amount * lp_token_amount / lp_token_supply`
680
+ *
681
+ * Uses PreciseNumber arithmetic to match on-chain rounding.
682
+ *
683
+ * @param lpTokenAmount Amount of LP tokens the user is redeeming.
684
+ * @param lpTokenSupply Current total supply of the LP token.
685
+ * @param reserves Array of { mint, balance } for each pool asset.
686
+ * @returns Array of { mint, amount } the user would receive per asset.
687
+ */
688
+ static simulateWithdrawMath(params) {
689
+ const { lpTokenAmount, lpTokenSupply, reserves } = params;
690
+ const ONE = 1000000000000n;
691
+ const HALF = ONE / 2n;
692
+ const pNew = (x) => x * ONE;
693
+ const pToU64 = (v) => (v + HALF) / ONE;
694
+ const pMul = (a, b) => (a * b + HALF) / ONE;
695
+ const pDiv = (a, b) => (a * ONE + HALF) / b;
696
+ return reserves.map((r) => {
697
+ // PreciseNumber(reserve) * PreciseNumber(lp_amount) / PreciseNumber(lp_supply)
698
+ const reservePrecise = pNew(r.balance);
699
+ const amountPrecise = pNew(lpTokenAmount);
700
+ const supplyPrecise = pNew(lpTokenSupply);
701
+ const share = pDiv(pMul(reservePrecise, amountPrecise), supplyPrecise);
702
+ return {
703
+ mint: r.mint,
704
+ amount: BigInt(pToU64(share)),
705
+ };
706
+ });
707
+ }
708
+ }
709
+ exports.JuniorTranche = JuniorTranche;