@hobba-io/core 0.1.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 (190) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +87 -0
  3. package/dist/amountUtils.d.ts +15 -0
  4. package/dist/amountUtils.d.ts.map +1 -0
  5. package/dist/amountUtils.js +59 -0
  6. package/dist/amountUtils.js.map +1 -0
  7. package/dist/constants.d.ts +148 -0
  8. package/dist/constants.d.ts.map +1 -0
  9. package/dist/constants.js +231 -0
  10. package/dist/constants.js.map +1 -0
  11. package/dist/errors.d.ts +7 -0
  12. package/dist/errors.d.ts.map +1 -0
  13. package/dist/errors.js +29 -0
  14. package/dist/errors.js.map +1 -0
  15. package/dist/executeAction.d.ts +36 -0
  16. package/dist/executeAction.d.ts.map +1 -0
  17. package/dist/executeAction.js +606 -0
  18. package/dist/executeAction.js.map +1 -0
  19. package/dist/executeDepositBorrow.d.ts +49 -0
  20. package/dist/executeDepositBorrow.d.ts.map +1 -0
  21. package/dist/executeDepositBorrow.js +444 -0
  22. package/dist/executeDepositBorrow.js.map +1 -0
  23. package/dist/idl/hobba.json +5072 -0
  24. package/dist/idl.d.ts +783 -0
  25. package/dist/idl.d.ts.map +1 -0
  26. package/dist/idl.js +7 -0
  27. package/dist/idl.js.map +1 -0
  28. package/dist/index.d.ts +17 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +22 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/juplend/accountResolvers.d.ts +106 -0
  33. package/dist/juplend/accountResolvers.d.ts.map +1 -0
  34. package/dist/juplend/accountResolvers.js +148 -0
  35. package/dist/juplend/accountResolvers.js.map +1 -0
  36. package/dist/juplend/operateRemaining.d.ts +11 -0
  37. package/dist/juplend/operateRemaining.d.ts.map +1 -0
  38. package/dist/juplend/operateRemaining.js +46 -0
  39. package/dist/juplend/operateRemaining.js.map +1 -0
  40. package/dist/juplend/positionInit.d.ts +34 -0
  41. package/dist/juplend/positionInit.d.ts.map +1 -0
  42. package/dist/juplend/positionInit.js +56 -0
  43. package/dist/juplend/positionInit.js.map +1 -0
  44. package/dist/juplend/positionParser.d.ts +8 -0
  45. package/dist/juplend/positionParser.d.ts.map +1 -0
  46. package/dist/juplend/positionParser.js +30 -0
  47. package/dist/juplend/positionParser.js.map +1 -0
  48. package/dist/juplend/sdkApi.d.ts +39 -0
  49. package/dist/juplend/sdkApi.d.ts.map +1 -0
  50. package/dist/juplend/sdkApi.js +68 -0
  51. package/dist/juplend/sdkApi.js.map +1 -0
  52. package/dist/juplend/tickMath.d.ts +19 -0
  53. package/dist/juplend/tickMath.d.ts.map +1 -0
  54. package/dist/juplend/tickMath.js +91 -0
  55. package/dist/juplend/tickMath.js.map +1 -0
  56. package/dist/juplend/tickUtils.d.ts +20 -0
  57. package/dist/juplend/tickUtils.d.ts.map +1 -0
  58. package/dist/juplend/tickUtils.js +115 -0
  59. package/dist/juplend/tickUtils.js.map +1 -0
  60. package/dist/juplend/vaultConfigParser.d.ts +25 -0
  61. package/dist/juplend/vaultConfigParser.d.ts.map +1 -0
  62. package/dist/juplend/vaultConfigParser.js +38 -0
  63. package/dist/juplend/vaultConfigParser.js.map +1 -0
  64. package/dist/kamino/accountResolvers.d.ts +110 -0
  65. package/dist/kamino/accountResolvers.d.ts.map +1 -0
  66. package/dist/kamino/accountResolvers.js +220 -0
  67. package/dist/kamino/accountResolvers.js.map +1 -0
  68. package/dist/kamino/obligationParser.d.ts +13 -0
  69. package/dist/kamino/obligationParser.d.ts.map +1 -0
  70. package/dist/kamino/obligationParser.js +92 -0
  71. package/dist/kamino/obligationParser.js.map +1 -0
  72. package/dist/kamino/refreshInstructions.d.ts +4 -0
  73. package/dist/kamino/refreshInstructions.d.ts.map +1 -0
  74. package/dist/kamino/refreshInstructions.js +32 -0
  75. package/dist/kamino/refreshInstructions.js.map +1 -0
  76. package/dist/limits.d.ts +87 -0
  77. package/dist/limits.d.ts.map +1 -0
  78. package/dist/limits.js +174 -0
  79. package/dist/limits.js.map +1 -0
  80. package/dist/marketConfig.d.ts +35 -0
  81. package/dist/marketConfig.d.ts.map +1 -0
  82. package/dist/marketConfig.js +85 -0
  83. package/dist/marketConfig.js.map +1 -0
  84. package/dist/oracle/scopePrice.d.ts +7 -0
  85. package/dist/oracle/scopePrice.d.ts.map +1 -0
  86. package/dist/oracle/scopePrice.js +31 -0
  87. package/dist/oracle/scopePrice.js.map +1 -0
  88. package/dist/pdas.d.ts +33 -0
  89. package/dist/pdas.d.ts.map +1 -0
  90. package/dist/pdas.js +116 -0
  91. package/dist/pdas.js.map +1 -0
  92. package/dist/position.d.ts +45 -0
  93. package/dist/position.d.ts.map +1 -0
  94. package/dist/position.js +68 -0
  95. package/dist/position.js.map +1 -0
  96. package/dist/positionMetrics.d.ts +108 -0
  97. package/dist/positionMetrics.d.ts.map +1 -0
  98. package/dist/positionMetrics.js +238 -0
  99. package/dist/positionMetrics.js.map +1 -0
  100. package/dist/program.d.ts +12 -0
  101. package/dist/program.d.ts.map +1 -0
  102. package/dist/program.js +24 -0
  103. package/dist/program.js.map +1 -0
  104. package/dist/quote.d.ts +90 -0
  105. package/dist/quote.d.ts.map +1 -0
  106. package/dist/quote.js +198 -0
  107. package/dist/quote.js.map +1 -0
  108. package/dist/rates/borrowRates.d.ts +44 -0
  109. package/dist/rates/borrowRates.d.ts.map +1 -0
  110. package/dist/rates/borrowRates.js +229 -0
  111. package/dist/rates/borrowRates.js.map +1 -0
  112. package/dist/rates/collateralInfo.d.ts +21 -0
  113. package/dist/rates/collateralInfo.d.ts.map +1 -0
  114. package/dist/rates/collateralInfo.js +104 -0
  115. package/dist/rates/collateralInfo.js.map +1 -0
  116. package/dist/rates/constants.d.ts +22 -0
  117. package/dist/rates/constants.d.ts.map +1 -0
  118. package/dist/rates/constants.js +70 -0
  119. package/dist/rates/constants.js.map +1 -0
  120. package/dist/rates/index.d.ts +11 -0
  121. package/dist/rates/index.d.ts.map +1 -0
  122. package/dist/rates/index.js +11 -0
  123. package/dist/rates/index.js.map +1 -0
  124. package/dist/rates/kaminoApi.d.ts +24 -0
  125. package/dist/rates/kaminoApi.d.ts.map +1 -0
  126. package/dist/rates/kaminoApi.js +98 -0
  127. package/dist/rates/kaminoApi.js.map +1 -0
  128. package/dist/rates/supplyRates.d.ts +23 -0
  129. package/dist/rates/supplyRates.d.ts.map +1 -0
  130. package/dist/rates/supplyRates.js +67 -0
  131. package/dist/rates/supplyRates.js.map +1 -0
  132. package/dist/transactionBuilder.d.ts +5 -0
  133. package/dist/transactionBuilder.d.ts.map +1 -0
  134. package/dist/transactionBuilder.js +287 -0
  135. package/dist/transactionBuilder.js.map +1 -0
  136. package/dist/txHelpers.d.ts +16 -0
  137. package/dist/txHelpers.d.ts.map +1 -0
  138. package/dist/txHelpers.js +82 -0
  139. package/dist/txHelpers.js.map +1 -0
  140. package/dist/userState.d.ts +21 -0
  141. package/dist/userState.d.ts.map +1 -0
  142. package/dist/userState.js +60 -0
  143. package/dist/userState.js.map +1 -0
  144. package/dist/vault/vaultAccounts.d.ts +82 -0
  145. package/dist/vault/vaultAccounts.d.ts.map +1 -0
  146. package/dist/vault/vaultAccounts.js +275 -0
  147. package/dist/vault/vaultAccounts.js.map +1 -0
  148. package/dist/wallet.d.ts +12 -0
  149. package/dist/wallet.d.ts.map +1 -0
  150. package/dist/wallet.js +2 -0
  151. package/dist/wallet.js.map +1 -0
  152. package/package.json +63 -0
  153. package/src/amountUtils.ts +69 -0
  154. package/src/constants.ts +263 -0
  155. package/src/errors.ts +33 -0
  156. package/src/executeAction.ts +884 -0
  157. package/src/executeDepositBorrow.ts +677 -0
  158. package/src/idl/hobba.json +5072 -0
  159. package/src/idl.ts +7 -0
  160. package/src/index.ts +21 -0
  161. package/src/juplend/accountResolvers.ts +207 -0
  162. package/src/juplend/operateRemaining.ts +81 -0
  163. package/src/juplend/positionInit.ts +86 -0
  164. package/src/juplend/positionParser.ts +41 -0
  165. package/src/juplend/sdkApi.ts +112 -0
  166. package/src/juplend/tickMath.ts +82 -0
  167. package/src/juplend/tickUtils.ts +163 -0
  168. package/src/juplend/vaultConfigParser.ts +45 -0
  169. package/src/kamino/accountResolvers.ts +299 -0
  170. package/src/kamino/obligationParser.ts +123 -0
  171. package/src/kamino/refreshInstructions.ts +45 -0
  172. package/src/limits.ts +222 -0
  173. package/src/marketConfig.ts +139 -0
  174. package/src/oracle/scopePrice.ts +31 -0
  175. package/src/pdas.ts +226 -0
  176. package/src/position.ts +131 -0
  177. package/src/positionMetrics.ts +387 -0
  178. package/src/program.ts +30 -0
  179. package/src/quote.ts +310 -0
  180. package/src/rates/borrowRates.ts +270 -0
  181. package/src/rates/collateralInfo.ts +136 -0
  182. package/src/rates/constants.ts +87 -0
  183. package/src/rates/index.ts +38 -0
  184. package/src/rates/kaminoApi.ts +134 -0
  185. package/src/rates/supplyRates.ts +102 -0
  186. package/src/transactionBuilder.ts +324 -0
  187. package/src/txHelpers.ts +107 -0
  188. package/src/userState.ts +91 -0
  189. package/src/vault/vaultAccounts.ts +365 -0
  190. package/src/wallet.ts +12 -0
@@ -0,0 +1,606 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+ import { createAssociatedTokenAccountInstruction, createCloseAccountInstruction, getAssociatedTokenAddress, NATIVE_MINT, TOKEN_PROGRAM_ID, } from "@solana/spl-token";
3
+ import BN from "bn.js";
4
+ import { CBBTC_MINT, SOL_MINT, USDC_MINT, JUPLEND_ORACLE, JUPLEND_PROGRAM, LENDING_PROVIDER_JUPLEND, } from "./constants";
5
+ import { deriveUserStatePda, deriveAllowlistEntryPda, deriveObligationPda, deriveFarmPdas, deriveUserTokenAccounts, deriveJuplendPositionMintPda, deriveJuplendTickPda, deriveJuplendTickIdLiquidationPda, } from "./pdas";
6
+ import { getDepositAccounts, getKaminoDepositLenderAccounts, getBorrowAccounts, getKaminoBorrowLenderAccounts, getRepayAccounts, getKaminoRepayLenderAccounts, getWithdrawAccounts, getKaminoWithdrawLenderAccounts, getPrepareWithdrawAccounts, getKaminoPrepareWithdrawLenderAccounts, } from "./kamino/accountResolvers";
7
+ import { buildRefreshReserveIx, buildRefreshObligationIx, } from "./kamino/refreshInstructions";
8
+ import { underlyingToCTokens } from "./kamino/obligationParser";
9
+ import { getJuplendDepositAccounts, getJuplendDepositLenderAccounts, getJuplendBorrowAccounts, getJuplendBorrowLenderAccounts, getJuplendRepayAccounts, getJuplendRepayLenderAccounts, getJuplendPrepareWithdrawAccounts, getJuplendWithdrawAccounts, getJuplendWithdrawLenderAccounts, } from "./juplend/accountResolvers";
10
+ import { buildFallbackOperateRemaining } from "./juplend/operateRemaining";
11
+ import { isTickError, parseTickMismatch, resolveTickAccounts, buildInitTickIx, } from "./juplend/tickUtils";
12
+ import { fetchOperateContext, deserializeRemaining, deserializeOtherIxs, } from "./juplend/sdkApi";
13
+ import { MAIN_MARKET_CONFIG } from "./marketConfig";
14
+ import { getVaultInfo, resolveWithdrawFixed, getKvaultWithdrawRemaining, getPerenaWithdrawAccountsWithMarginfi, } from "./vault/vaultAccounts";
15
+ import { sendLegacyTx, sendVersionedTx } from "./transactionBuilder";
16
+ import { buildHobbaProgram } from "./program";
17
+ import { resolveKaminoMarketConfig } from "./position";
18
+ import { fetchUserState } from "./userState";
19
+ import { buildWrapSolIxs, fetchMarketLut, getSharesAtaForVault, makeSafeGetAccountInfo, resolveVaultLuts, } from "./txHelpers";
20
+ /**
21
+ * Manage an existing Hobba position: deposit / borrow / repay / withdraw.
22
+ *
23
+ * Lender routing happens inside: the (owner, collateral) UserState decides
24
+ * whether the JupLend or Kamino flow runs — integrators never pick a lender.
25
+ * Both flows are lifted verbatim from the app's page.tsx handleJuplendAction /
26
+ * handleKaminoAction so app and headless integrators share one implementation.
27
+ */
28
+ export async function executeAction(params) {
29
+ const { connection, signer, collateralMint, action, amount } = params;
30
+ if (!signer.publicKey || !signer.signTransaction) {
31
+ throw new Error("Wallet not connected");
32
+ }
33
+ const owner = signer.publicKey;
34
+ const base = (params.apiBaseUrl ?? "").replace(/\/+$/, "");
35
+ const setStep = (s) => params.onStep?.(s);
36
+ const program = params.program ?? buildHobbaProgram(connection, owner);
37
+ const userState = await fetchUserState(connection, owner, collateralMint);
38
+ if (!userState) {
39
+ throw new Error("No Hobba position for this collateral — use executeDepositBorrow first");
40
+ }
41
+ const [usPda] = deriveUserStatePda(owner, collateralMint);
42
+ const [allowlistPda] = deriveAllowlistEntryPda(owner);
43
+ const tokens = await deriveUserTokenAccounts(owner, usPda, collateralMint);
44
+ const safeGetAccountInfo = makeSafeGetAccountInfo(connection);
45
+ setStep(`Processing ${action}...`);
46
+ if (userState.lendingProvider === LENDING_PROVIDER_JUPLEND) {
47
+ const signature = await juplendAction({ connection, signer, program, collateralMint, action, amount, base, userState }, owner, usPda, allowlistPda, tokens, safeGetAccountInfo);
48
+ return { lender: "juplend", signature };
49
+ }
50
+ const mc = resolveKaminoMarketConfig(userState.lendingMarket, collateralMint);
51
+ const signature = await kaminoAction({ connection, signer, program, collateralMint, action, amount, base, userState }, owner, usPda, allowlistPda, tokens, mc, safeGetAccountInfo);
52
+ return { lender: "kamino", signature };
53
+ }
54
+ // --- JupLend action flow (lifted from app page.tsx handleJuplendAction) ---
55
+ async function juplendAction(ctx, owner, usPda, allowlistPda, tokens, safeGetAccountInfo) {
56
+ const { connection, signer, program, collateralMint, action, amount, base, userState } = ctx;
57
+ const isSol = collateralMint.equals(SOL_MINT);
58
+ const positionAddress = userState.obligation;
59
+ const positionInfo = await connection.getAccountInfo(positionAddress);
60
+ if (!positionInfo)
61
+ throw new Error("JupLend position not found");
62
+ const vaultId = positionInfo.data.readUInt16LE(8);
63
+ const positionId = positionInfo.data.readUInt32LE(10);
64
+ const [positionMintPda] = deriveJuplendPositionMintPda(vaultId, positionId);
65
+ const positionTokenAccount = await getAssociatedTokenAddress(positionMintPda, usPda, true);
66
+ const juplendPdas = {
67
+ userStatePda: usPda,
68
+ allowlistEntryPda: allowlistPda,
69
+ obligationPda: positionAddress,
70
+ userMetadataPda: usPda,
71
+ userCollateralAta: tokens.userCollateralAta,
72
+ userUsdcAta: tokens.userUsdcAta,
73
+ pdaCollateralAta: tokens.pdaCollateralAta,
74
+ pdaUsdcAta: tokens.pdaUsdcAta,
75
+ };
76
+ const juplendLut = await fetchMarketLut(base, "juplend");
77
+ if (!juplendLut)
78
+ throw new Error("JupLend LUT not found.");
79
+ const ataIxs = [];
80
+ if (action === "deposit") {
81
+ const pdaColInfo = await safeGetAccountInfo(tokens.pdaCollateralAta);
82
+ if (!pdaColInfo)
83
+ ataIxs.push(createAssociatedTokenAccountInstruction(owner, tokens.pdaCollateralAta, usPda, collateralMint));
84
+ }
85
+ else if (action === "borrow") {
86
+ const pdaUsdcInfo = await safeGetAccountInfo(tokens.pdaUsdcAta);
87
+ if (!pdaUsdcInfo)
88
+ ataIxs.push(createAssociatedTokenAccountInstruction(owner, tokens.pdaUsdcAta, usPda, USDC_MINT));
89
+ const userUsdcInfo = await safeGetAccountInfo(tokens.userUsdcAta);
90
+ if (!userUsdcInfo)
91
+ ataIxs.push(createAssociatedTokenAccountInstruction(owner, tokens.userUsdcAta, owner, USDC_MINT));
92
+ const vaultType = userState.currentVaultType;
93
+ if (vaultType !== "none") {
94
+ const sharesAta = getSharesAtaForVault(vaultType, tokens);
95
+ let hasVaultShares = false;
96
+ try {
97
+ const sharesBal = await connection.getTokenAccountBalance(sharesAta);
98
+ hasVaultShares = BigInt(sharesBal.value.amount) > BigInt(0);
99
+ }
100
+ catch {
101
+ /* no shares */
102
+ }
103
+ if (hasVaultShares) {
104
+ const vi = getVaultInfo(vaultType);
105
+ let vaultRemaining;
106
+ if (vi.provider === "perena") {
107
+ vaultRemaining = getPerenaWithdrawAccountsWithMarginfi(usPda, tokens.pdaUsdcAta, sharesAta);
108
+ }
109
+ else {
110
+ const withdrawFixed = await resolveWithdrawFixed(connection, vi.vault);
111
+ vaultRemaining = getKvaultWithdrawRemaining(vi, withdrawFixed);
112
+ }
113
+ const juplendLender = getJuplendBorrowLenderAccounts(juplendPdas);
114
+ const borrowIx = await program.methods
115
+ .borrow(amount, vaultRemaining.length)
116
+ .accounts(getJuplendBorrowAccounts(owner, juplendPdas, collateralMint, true, {
117
+ vault: vi.vault,
118
+ sharesMint: vi.sharesMint,
119
+ sharesAta,
120
+ }))
121
+ .remainingAccounts([...vaultRemaining, ...juplendLender])
122
+ .instruction();
123
+ const vaultLuts = await resolveVaultLuts(base, vi);
124
+ return await sendVersionedTx(connection, [...ataIxs, borrowIx], signer, vaultLuts, 1400000);
125
+ }
126
+ }
127
+ }
128
+ let wrapIxs = [];
129
+ if (isSol && action === "deposit") {
130
+ wrapIxs = await buildWrapSolIxs(owner, tokens.userCollateralAta, amount.addn(1), safeGetAccountInfo);
131
+ }
132
+ const withdrawAtaIxs = [];
133
+ const unwrapIxs = [];
134
+ if (isSol && action === "withdraw") {
135
+ const ataInfo = await safeGetAccountInfo(tokens.userCollateralAta);
136
+ if (!ataInfo)
137
+ withdrawAtaIxs.push(createAssociatedTokenAccountInstruction(owner, tokens.userCollateralAta, owner, NATIVE_MINT));
138
+ unwrapIxs.push(createCloseAccountInstruction(tokens.userCollateralAta, owner, owner, [], TOKEN_PROGRAM_ID));
139
+ }
140
+ const colAmount = action === "deposit" ? amount.toString() : action === "withdraw" ? "-" + amount.toString() : "0";
141
+ const debtAmount = action === "borrow" ? amount.toString() : action === "repay" ? "-" + amount.toString() : "0";
142
+ let sdkResult = null;
143
+ for (let attempt = 0; attempt < 2; attempt++) {
144
+ try {
145
+ sdkResult = await fetchOperateContext({ vaultId, positionId, colAmount, debtAmount, signerPda: usPda.toBase58() }, base);
146
+ break;
147
+ }
148
+ catch (apiErr) {
149
+ console.warn("[action] SDK API failed (attempt", attempt + 1, "):", apiErr.message);
150
+ if (attempt === 0)
151
+ await new Promise((r) => setTimeout(r, 500));
152
+ }
153
+ }
154
+ if (!sdkResult && !collateralMint.equals(CBBTC_MINT)) {
155
+ throw new Error(`SDK API failed for ${action} on vault ${vaultId}. SOL vault requires SDK for remaining accounts.`);
156
+ }
157
+ const buildIx = async (rem) => {
158
+ if (action === "deposit") {
159
+ const juplendDepositLender = getJuplendDepositLenderAccounts(juplendPdas);
160
+ return program.methods
161
+ .deposit(amount)
162
+ .accounts(getJuplendDepositAccounts(owner, juplendPdas, collateralMint))
163
+ .remainingAccounts([...juplendDepositLender, ...rem])
164
+ .instruction();
165
+ }
166
+ else if (action === "borrow") {
167
+ const juplendLender = getJuplendBorrowLenderAccounts(juplendPdas);
168
+ return program.methods
169
+ .borrow(amount, 0)
170
+ .accounts(getJuplendBorrowAccounts(owner, juplendPdas, collateralMint))
171
+ .remainingAccounts([...juplendLender, ...rem])
172
+ .instruction();
173
+ }
174
+ else if (action === "repay") {
175
+ const juplendRepayLender = getJuplendRepayLenderAccounts(juplendPdas);
176
+ return program.methods
177
+ .repay(amount)
178
+ .accounts(getJuplendRepayAccounts(owner, juplendPdas, collateralMint))
179
+ .remainingAccounts([...juplendRepayLender, ...rem])
180
+ .instruction();
181
+ }
182
+ else {
183
+ const juplendWithdrawLender = getJuplendWithdrawLenderAccounts(juplendPdas);
184
+ return program.methods
185
+ .withdraw(amount)
186
+ .accounts(getJuplendWithdrawAccounts(owner, juplendPdas, collateralMint))
187
+ .remainingAccounts([...juplendWithdrawLender, ...rem])
188
+ .instruction();
189
+ }
190
+ };
191
+ // Check vault shares for prepareWithdraw (withdraw only)
192
+ let withdrawHasVaultShares = false;
193
+ let withdrawVaultInfo = null;
194
+ if (action === "withdraw") {
195
+ const vaultType = userState.currentVaultType;
196
+ if (vaultType !== "none") {
197
+ const sharesAta = getSharesAtaForVault(vaultType, tokens);
198
+ try {
199
+ const sharesBal = await connection.getTokenAccountBalance(sharesAta);
200
+ withdrawHasVaultShares = BigInt(sharesBal.value.amount) > BigInt(0);
201
+ }
202
+ catch {
203
+ // ATA doesn't exist = no shares
204
+ }
205
+ if (withdrawHasVaultShares) {
206
+ const vi = getVaultInfo(vaultType);
207
+ const vaAccounts = { vault: vi.vault, sharesMint: vi.sharesMint, sharesAta };
208
+ let vaultRemaining;
209
+ if (vi.provider === "perena") {
210
+ vaultRemaining = getPerenaWithdrawAccountsWithMarginfi(usPda, tokens.pdaUsdcAta, sharesAta);
211
+ }
212
+ else {
213
+ const withdrawFixed = await resolveWithdrawFixed(connection, vi.vault);
214
+ vaultRemaining = getKvaultWithdrawRemaining(vi, withdrawFixed);
215
+ }
216
+ withdrawVaultInfo = { vi, vaAccounts, vaultRemaining };
217
+ }
218
+ }
219
+ }
220
+ // prepareWithdraw for JupLend withdraw with vault shares. Lender remaining (6):
221
+ // [position, vault_state(mut), vault_config, supply_token_reserves_liquidity,
222
+ // borrow_token_reserves_liquidity, juplend_program] — pulled from the same
223
+ // operate account list the withdraw CPI uses (indices 0, 1, 12, 13).
224
+ const buildPrepareWithdrawIx = async (withdrawAmt, operateRemaining) => {
225
+ if (!withdrawVaultInfo)
226
+ throw new Error("No vault info for prepareWithdraw");
227
+ const { vaAccounts, vaultRemaining } = withdrawVaultInfo;
228
+ const juplendPrepLender = [
229
+ { pubkey: positionAddress, isSigner: false, isWritable: false },
230
+ { pubkey: operateRemaining[1].pubkey, isSigner: false, isWritable: true }, // vault_state
231
+ { pubkey: operateRemaining[0].pubkey, isSigner: false, isWritable: false }, // vault_config
232
+ { pubkey: operateRemaining[12].pubkey, isSigner: false, isWritable: false }, // supply reserves liquidity
233
+ { pubkey: operateRemaining[13].pubkey, isSigner: false, isWritable: false }, // borrow reserves liquidity
234
+ { pubkey: JUPLEND_PROGRAM, isSigner: false, isWritable: false },
235
+ ];
236
+ return program.methods
237
+ .prepareWithdraw(vaultRemaining.length, withdrawAmt)
238
+ .accounts(getJuplendPrepareWithdrawAccounts(owner, juplendPdas, collateralMint, vaAccounts))
239
+ .remainingAccounts([...vaultRemaining, ...juplendPrepLender])
240
+ .instruction();
241
+ };
242
+ if (sdkResult) {
243
+ const sdkRemaining = deserializeRemaining(sdkResult);
244
+ const otherIxs = deserializeOtherIxs(sdkResult, usPda, owner);
245
+ if (withdrawHasVaultShares && withdrawVaultInfo) {
246
+ // Single TX: prepareWithdraw (vault unwind) + withdraw (combined repay +
247
+ // collateral withdrawal). SDK call needs proportional debt for tick prediction.
248
+ const withdrawAmt = amount;
249
+ let proportionalDebt = new BN(0);
250
+ try {
251
+ const posResp = await fetch(`${base}/api/juplend-position?address=${positionAddress.toBase58()}`);
252
+ const posData = await posResp.json();
253
+ const totalCollateral = new BN(posData.collateral);
254
+ const totalDebt = new BN(posData.debt);
255
+ if (totalCollateral.gt(new BN(0))) {
256
+ proportionalDebt = withdrawAmt.gte(totalCollateral)
257
+ ? totalDebt
258
+ : totalDebt.mul(withdrawAmt).div(totalCollateral);
259
+ }
260
+ }
261
+ catch (e) {
262
+ console.warn("[withdraw] Failed to fetch position data for LTV calc:", e.message);
263
+ }
264
+ const combinedSdk = await fetchOperateContext({
265
+ vaultId,
266
+ positionId,
267
+ colAmount: "-" + withdrawAmt.toString(),
268
+ debtAmount: "-" + proportionalDebt.toString(),
269
+ signerPda: usPda.toBase58(),
270
+ }, base);
271
+ const wdRemaining = deserializeRemaining(combinedSdk);
272
+ const combinedOtherIxs = deserializeOtherIxs(combinedSdk, usPda, owner);
273
+ // Override SDK's currentTick with the actual on-chain tick if they differ
274
+ const onChainTick = positionInfo.data.readInt32LE(47);
275
+ const sdkCurrentTick = combinedSdk.currentTickValue;
276
+ if (sdkCurrentTick != null && sdkCurrentTick !== onChainTick) {
277
+ wdRemaining[7] = {
278
+ pubkey: deriveJuplendTickPda(vaultId, onChainTick),
279
+ isSigner: false,
280
+ isWritable: true,
281
+ };
282
+ }
283
+ const prepIx = await buildPrepareWithdrawIx(withdrawAmt, wdRemaining);
284
+ const juplendWithdrawLender = getJuplendWithdrawLenderAccounts(juplendPdas);
285
+ const buildWdIx = () => program.methods
286
+ .withdraw(withdrawAmt)
287
+ .accounts(getJuplendWithdrawAccounts(owner, juplendPdas, collateralMint))
288
+ .remainingAccounts([...juplendWithdrawLender, ...wdRemaining])
289
+ .instruction();
290
+ const luts = await resolveVaultLuts(base, withdrawVaultInfo.vi, [juplendLut]);
291
+ // Pre-sign rebuilder: refetch combinedSdk, recompute remaining + other ixs.
292
+ const rebuildOnTickError = async () => {
293
+ const fresh = await fetchOperateContext({
294
+ vaultId,
295
+ positionId,
296
+ colAmount: "-" + withdrawAmt.toString(),
297
+ debtAmount: "-" + proportionalDebt.toString(),
298
+ signerPda: usPda.toBase58(),
299
+ }, base).catch(() => null);
300
+ if (!fresh)
301
+ return null;
302
+ const freshWdRemaining = deserializeRemaining(fresh);
303
+ const freshOnChainTick = positionInfo.data.readInt32LE(47);
304
+ if (fresh.currentTickValue != null && fresh.currentTickValue !== freshOnChainTick) {
305
+ freshWdRemaining[7] = {
306
+ pubkey: deriveJuplendTickPda(vaultId, freshOnChainTick),
307
+ isSigner: false,
308
+ isWritable: true,
309
+ };
310
+ }
311
+ const freshOtherIxs = deserializeOtherIxs(fresh, usPda, owner);
312
+ const freshWdIx = await program.methods
313
+ .withdraw(withdrawAmt)
314
+ .accounts(getJuplendWithdrawAccounts(owner, juplendPdas, collateralMint))
315
+ .remainingAccounts([...juplendWithdrawLender, ...freshWdRemaining])
316
+ .instruction();
317
+ return [...ataIxs, ...withdrawAtaIxs, ...freshOtherIxs, prepIx, freshWdIx, ...unwrapIxs];
318
+ };
319
+ // Single TX with tick correction retry (post-sign fallback for parseable mismatch)
320
+ for (let attempt = 0; attempt < 3; attempt++) {
321
+ try {
322
+ const wdIx = await buildWdIx();
323
+ return await sendVersionedTx(connection, [...ataIxs, ...withdrawAtaIxs, ...combinedOtherIxs, prepIx, wdIx, ...unwrapIxs], signer, luts, 1400000, false, rebuildOnTickError);
324
+ }
325
+ catch (err) {
326
+ if (!isTickError(err.message))
327
+ throw err;
328
+ const correctTick = parseTickMismatch(err.message);
329
+ if (correctTick !== null && attempt < 2) {
330
+ const correctedTickPda = deriveJuplendTickPda(vaultId, correctTick);
331
+ const tickInfo = await connection.getAccountInfo(correctedTickPda).catch(() => null);
332
+ if (!tickInfo) {
333
+ combinedOtherIxs.push(buildInitTickIx(vaultId, correctTick, owner));
334
+ }
335
+ wdRemaining[8] = { pubkey: correctedTickPda, isSigner: false, isWritable: true };
336
+ continue;
337
+ }
338
+ throw err;
339
+ }
340
+ }
341
+ throw new Error("Withdraw failed after tick correction attempts");
342
+ }
343
+ // Simple withdraw (no vault shares) — and the deposit/borrow/repay actions
344
+ const ix = await buildIx(sdkRemaining);
345
+ const luts = [juplendLut];
346
+ const rebuildOnTickError = async () => {
347
+ const fresh = await fetchOperateContext({ vaultId, positionId, colAmount, debtAmount, signerPda: usPda.toBase58() }, base).catch(() => null);
348
+ if (!fresh)
349
+ return null;
350
+ const freshRemaining = deserializeRemaining(fresh);
351
+ const freshOtherIxs = deserializeOtherIxs(fresh, usPda, owner);
352
+ const freshIx = await buildIx(freshRemaining);
353
+ return [...ataIxs, ...withdrawAtaIxs, ...wrapIxs, ...freshOtherIxs, freshIx, ...unwrapIxs];
354
+ };
355
+ try {
356
+ return await sendVersionedTx(connection, [...ataIxs, ...withdrawAtaIxs, ...wrapIxs, ...otherIxs, ix, ...unwrapIxs], signer, luts, 800000, false, rebuildOnTickError);
357
+ }
358
+ catch (txErr) {
359
+ // Belt-and-suspenders: post-sign retry for the parseable tick mismatch case
360
+ if (isTickError(txErr.message)) {
361
+ const correctTick = parseTickMismatch(txErr.message);
362
+ if (correctTick !== null) {
363
+ sdkRemaining[8] = {
364
+ pubkey: deriveJuplendTickPda(vaultId, correctTick),
365
+ isSigner: false,
366
+ isWritable: true,
367
+ };
368
+ sdkRemaining[10] = {
369
+ pubkey: deriveJuplendTickIdLiquidationPda(vaultId, correctTick),
370
+ isSigner: false,
371
+ isWritable: true,
372
+ };
373
+ const retryIx = await buildIx(sdkRemaining);
374
+ return await sendVersionedTx(connection, [...ataIxs, ...withdrawAtaIxs, ...wrapIxs, ...otherIxs, retryIx, ...unwrapIxs], signer, luts, 800000);
375
+ }
376
+ }
377
+ throw txErr;
378
+ }
379
+ }
380
+ // SDK unavailable — cbBTC fallback with hardcoded remaining accounts
381
+ const positionTick = positionInfo.data.length >= 83 ? positionInfo.data.readInt32LE(47) : -16383;
382
+ if (action === "borrow" || action === "repay") {
383
+ const oracleInfo = await connection.getAccountInfo(JUPLEND_ORACLE);
384
+ if (!oracleInfo)
385
+ throw new Error("JupLend oracle account not found on-chain.");
386
+ }
387
+ const currentTickPda = deriveJuplendTickPda(vaultId, positionTick);
388
+ const currentTickIdPda = deriveJuplendTickIdLiquidationPda(vaultId, positionTick);
389
+ let finalTickPda = currentTickPda;
390
+ let finalTickIdPda = currentTickIdPda;
391
+ const tickAccounts = await resolveTickAccounts(connection, owner, positionTick, positionTick, vaultId);
392
+ const buildRemaining = () => buildFallbackOperateRemaining(positionAddress, positionTokenAccount, currentTickPda, finalTickPda, currentTickIdPda, finalTickIdPda).remaining;
393
+ const fallbackRemaining = buildRemaining();
394
+ const ix = await buildIx(fallbackRemaining);
395
+ const fallbackPrepIxs = [];
396
+ if (withdrawHasVaultShares && withdrawVaultInfo) {
397
+ fallbackPrepIxs.push(await buildPrepareWithdrawIx(amount, fallbackRemaining));
398
+ }
399
+ const fallbackLuts = withdrawVaultInfo
400
+ ? await resolveVaultLuts(base, withdrawVaultInfo.vi, [juplendLut])
401
+ : [juplendLut];
402
+ const fallbackCU = withdrawHasVaultShares ? 1400000 : 600000;
403
+ try {
404
+ return await sendVersionedTx(connection, [...ataIxs, ...withdrawAtaIxs, ...wrapIxs, ...tickAccounts.initIxs, ...fallbackPrepIxs, ix, ...unwrapIxs], signer, fallbackLuts, fallbackCU);
405
+ }
406
+ catch (err) {
407
+ if (isTickError(err.message)) {
408
+ const correctFinalTick = parseTickMismatch(err.message);
409
+ if (correctFinalTick !== null) {
410
+ finalTickPda = deriveJuplendTickPda(vaultId, correctFinalTick);
411
+ finalTickIdPda = deriveJuplendTickIdLiquidationPda(vaultId, correctFinalTick);
412
+ const retryTickAccounts = await resolveTickAccounts(connection, owner, positionTick, correctFinalTick, vaultId);
413
+ const retryRemaining = buildRemaining();
414
+ const retryIx = await buildIx(retryRemaining);
415
+ const retryPrepIxs = [];
416
+ if (withdrawHasVaultShares && withdrawVaultInfo) {
417
+ retryPrepIxs.push(await buildPrepareWithdrawIx(amount, retryRemaining));
418
+ }
419
+ return await sendVersionedTx(connection, [...ataIxs, ...withdrawAtaIxs, ...wrapIxs, ...retryTickAccounts.initIxs, ...retryPrepIxs, retryIx, ...unwrapIxs], signer, fallbackLuts, fallbackCU);
420
+ }
421
+ }
422
+ throw err;
423
+ }
424
+ }
425
+ // --- Kamino action flow (lifted from app page.tsx handleKaminoAction) ---
426
+ async function kaminoAction(ctx, owner, usPda, allowlistPda, tokens, mc, safeGetAccountInfo) {
427
+ const { connection, signer, program, collateralMint, action, amount, base, userState } = ctx;
428
+ const isSol = collateralMint.equals(SOL_MINT);
429
+ // Use the obligation stored in userState (set during init) rather than re-deriving
430
+ const obligPda = userState.obligation;
431
+ const [derivedObligPda] = deriveObligationPda(usPda, mc.market);
432
+ if (!obligPda.equals(derivedObligPda)) {
433
+ console.warn("[action] Stored obligation", obligPda.toBase58(), "differs from derived", derivedObligPda.toBase58(), "— using stored");
434
+ }
435
+ const { obligationFarmUserStatePda, obligationFarmDebtUserStatePda } = deriveFarmPdas(obligPda, mc.collateralFarmState, mc.usdcReserveFarmDebt);
436
+ const pdas = {
437
+ userStatePda: usPda,
438
+ allowlistEntryPda: allowlistPda,
439
+ obligationPda: obligPda,
440
+ userMetadataPda: usPda,
441
+ obligationFarmUserStatePda,
442
+ obligationFarmDebtUserStatePda,
443
+ ...tokens,
444
+ };
445
+ const refreshCollateralIx = buildRefreshReserveIx(mc.collateralReserve, mc.market, mc.scopePrices);
446
+ const refreshUsdcIx = buildRefreshReserveIx(mc.usdcReserve, mc.market, mc.scopePrices);
447
+ const refreshReserves = [];
448
+ const obligInfo = await connection.getAccountInfo(obligPda);
449
+ const obligationExists = obligInfo !== null && obligInfo.data.length > 0;
450
+ if (obligInfo && obligInfo.data.length > 1208) {
451
+ const depositReserveKey = new PublicKey(obligInfo.data.slice(96, 128));
452
+ if (!depositReserveKey.equals(PublicKey.default))
453
+ refreshReserves.push(mc.collateralReserve);
454
+ const borrowReserveKey = new PublicKey(obligInfo.data.slice(1208, 1240));
455
+ if (!borrowReserveKey.equals(PublicKey.default))
456
+ refreshReserves.push(mc.usdcReserve);
457
+ }
458
+ // Only build refreshObligation if the obligation exists on-chain
459
+ const refreshObligIxs = obligationExists
460
+ ? [buildRefreshObligationIx(mc.market, obligPda, refreshReserves)]
461
+ : [];
462
+ const lutParam = mc.market.equals(MAIN_MARKET_CONFIG.market) ? "main" : "xstocks";
463
+ const marketLut = await fetchMarketLut(base, lutParam);
464
+ if (action === "deposit") {
465
+ let wrapIxs = [];
466
+ if (isSol) {
467
+ wrapIxs = await buildWrapSolIxs(owner, tokens.userCollateralAta, amount, safeGetAccountInfo);
468
+ }
469
+ const kaminoDepositLender = getKaminoDepositLenderAccounts(pdas, mc);
470
+ const ix = await program.methods
471
+ .deposit(amount)
472
+ .accounts(getDepositAccounts(owner, pdas, mc))
473
+ .remainingAccounts(kaminoDepositLender)
474
+ .instruction();
475
+ return await sendLegacyTx(connection, [...wrapIxs, refreshCollateralIx, refreshUsdcIx, ...refreshObligIxs, ix], signer);
476
+ }
477
+ else if (action === "borrow") {
478
+ const ataIxs = [];
479
+ const pdaUsdcInfo = await safeGetAccountInfo(tokens.pdaUsdcAta);
480
+ if (!pdaUsdcInfo)
481
+ ataIxs.push(createAssociatedTokenAccountInstruction(owner, tokens.pdaUsdcAta, usPda, USDC_MINT));
482
+ const userUsdcInfo = await safeGetAccountInfo(tokens.userUsdcAta);
483
+ if (!userUsdcInfo)
484
+ ataIxs.push(createAssociatedTokenAccountInstruction(owner, tokens.userUsdcAta, owner, USDC_MINT));
485
+ let hasVaultShares = false;
486
+ const vaultType = userState.currentVaultType;
487
+ if (vaultType !== "none") {
488
+ const sharesAtaToCheck = getSharesAtaForVault(vaultType, tokens);
489
+ try {
490
+ const sharesBal = await connection.getTokenAccountBalance(sharesAtaToCheck);
491
+ hasVaultShares = BigInt(sharesBal.value.amount) > BigInt(0);
492
+ }
493
+ catch {
494
+ /* no shares */
495
+ }
496
+ }
497
+ if (hasVaultShares) {
498
+ const vi = getVaultInfo(vaultType);
499
+ const sharesAta = getSharesAtaForVault(vaultType, tokens);
500
+ let vaultRemaining;
501
+ if (vi.provider === "perena") {
502
+ vaultRemaining = getPerenaWithdrawAccountsWithMarginfi(usPda, tokens.pdaUsdcAta, sharesAta);
503
+ }
504
+ else {
505
+ const withdrawFixed = await resolveWithdrawFixed(connection, vi.vault);
506
+ vaultRemaining = getKvaultWithdrawRemaining(vi, withdrawFixed);
507
+ }
508
+ const kaminoLender = getKaminoBorrowLenderAccounts(pdas, mc, true);
509
+ const ix = await program.methods
510
+ .borrow(amount, vaultRemaining.length)
511
+ .accounts(getBorrowAccounts(owner, pdas, mc, true, {
512
+ vault: vi.vault,
513
+ sharesMint: vi.sharesMint,
514
+ sharesAta,
515
+ }))
516
+ .remainingAccounts([...vaultRemaining, ...kaminoLender])
517
+ .instruction();
518
+ const vaultLuts = await resolveVaultLuts(base, vi, marketLut ? [marketLut] : []);
519
+ return await sendVersionedTx(connection, [...ataIxs, refreshCollateralIx, refreshUsdcIx, ...refreshObligIxs, ix], signer, vaultLuts, 1400000);
520
+ }
521
+ else {
522
+ const kaminoLender = getKaminoBorrowLenderAccounts(pdas, mc, false);
523
+ const ix = await program.methods
524
+ .borrow(amount, 0)
525
+ .accounts(getBorrowAccounts(owner, pdas, mc, false))
526
+ .remainingAccounts([...kaminoLender])
527
+ .instruction();
528
+ return await sendLegacyTx(connection, [...ataIxs, refreshCollateralIx, refreshUsdcIx, ...refreshObligIxs, ix], signer);
529
+ }
530
+ }
531
+ else if (action === "repay") {
532
+ const kaminoRepayLender = getKaminoRepayLenderAccounts(pdas, mc);
533
+ const ix = await program.methods
534
+ .repay(amount)
535
+ .accounts(getRepayAccounts(owner, pdas, mc))
536
+ .remainingAccounts([...kaminoRepayLender])
537
+ .instruction();
538
+ return await sendLegacyTx(connection, [refreshCollateralIx, refreshUsdcIx, ...refreshObligIxs, ix], signer);
539
+ }
540
+ else {
541
+ const underlyingAmount = amount;
542
+ const reserveInfo = await connection.getAccountInfo(mc.collateralReserve);
543
+ const withdrawAmount = reserveInfo
544
+ ? new BN(underlyingToCTokens(underlyingAmount.toNumber(), reserveInfo.data))
545
+ : underlyingAmount;
546
+ const withdrawAtaIxs = [];
547
+ if (isSol) {
548
+ const ataInfo = await safeGetAccountInfo(tokens.userCollateralAta);
549
+ if (!ataInfo)
550
+ withdrawAtaIxs.push(createAssociatedTokenAccountInstruction(owner, tokens.userCollateralAta, owner, NATIVE_MINT));
551
+ }
552
+ let hasVaultShares = false;
553
+ const vaultType = userState.currentVaultType;
554
+ if (vaultType !== "none") {
555
+ const sharesAtaToCheck = getSharesAtaForVault(vaultType, tokens);
556
+ try {
557
+ const sharesBal = await connection.getTokenAccountBalance(sharesAtaToCheck);
558
+ hasVaultShares = BigInt(sharesBal.value.amount) > BigInt(0);
559
+ }
560
+ catch {
561
+ /* no shares */
562
+ }
563
+ }
564
+ // For SOL: close wSOL ATA to unwrap back to native SOL
565
+ const unwrapIxs = isSol
566
+ ? [createCloseAccountInstruction(tokens.userCollateralAta, owner, owner, [], TOKEN_PROGRAM_ID)]
567
+ : [];
568
+ if (hasVaultShares) {
569
+ const vi = getVaultInfo(vaultType);
570
+ const sharesAta = getSharesAtaForVault(vaultType, tokens);
571
+ const vaAccounts = { vault: vi.vault, sharesMint: vi.sharesMint, sharesAta };
572
+ let vaultRemaining;
573
+ if (vi.provider === "perena") {
574
+ vaultRemaining = getPerenaWithdrawAccountsWithMarginfi(usPda, tokens.pdaUsdcAta, sharesAta);
575
+ }
576
+ else {
577
+ const withdrawFixed = await resolveWithdrawFixed(connection, vi.vault);
578
+ vaultRemaining = getKvaultWithdrawRemaining(vi, withdrawFixed);
579
+ }
580
+ const kaminoPrepLender = getKaminoPrepareWithdrawLenderAccounts(pdas, mc);
581
+ const prepareIx = await program.methods
582
+ .prepareWithdraw(vaultRemaining.length, underlyingAmount)
583
+ .accounts(getPrepareWithdrawAccounts(owner, pdas, mc, vaAccounts))
584
+ .remainingAccounts([...vaultRemaining, ...kaminoPrepLender])
585
+ .instruction();
586
+ const kaminoWithdrawLender = getKaminoWithdrawLenderAccounts(pdas, mc);
587
+ const withdrawIx = await program.methods
588
+ .withdraw(withdrawAmount)
589
+ .accounts(getWithdrawAccounts(owner, pdas, mc))
590
+ .remainingAccounts(kaminoWithdrawLender)
591
+ .instruction();
592
+ const vaultLutsW = await resolveVaultLuts(base, vi, marketLut ? [marketLut] : []);
593
+ return await sendVersionedTx(connection, [...withdrawAtaIxs, prepareIx, withdrawIx, ...unwrapIxs], signer, vaultLutsW, 1400000);
594
+ }
595
+ else {
596
+ const kaminoWithdrawLender = getKaminoWithdrawLenderAccounts(pdas, mc);
597
+ const ix = await program.methods
598
+ .withdraw(withdrawAmount)
599
+ .accounts(getWithdrawAccounts(owner, pdas, mc))
600
+ .remainingAccounts(kaminoWithdrawLender)
601
+ .instruction();
602
+ return await sendLegacyTx(connection, [...withdrawAtaIxs, refreshCollateralIx, refreshUsdcIx, ...refreshObligIxs, ix, ...unwrapIxs], signer);
603
+ }
604
+ }
605
+ }
606
+ //# sourceMappingURL=executeAction.js.map