@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
package/src/idl.ts ADDED
@@ -0,0 +1,7 @@
1
+ // Authoritative Hobba IDL for all consumers (app AnchorProvider, core's own
2
+ // buildHobbaProgram, scripts). Regenerate with `anchor idl build` and copy into
3
+ // src/idl/hobba.json — app/public/hobba.json is a served duplicate kept only
4
+ // for external fetchers and must be refreshed from this copy.
5
+ import idl from "./idl/hobba.json";
6
+
7
+ export const HOBBA_IDL = idl;
package/src/index.ts ADDED
@@ -0,0 +1,21 @@
1
+ // Public surface of @hobba/core — shared on-chain logic consumed by both the
2
+ // Next app and the embeddable widget. `executeDepositBorrow` is the main flow;
3
+ // `executeAction` manages existing positions; `getPosition`/`getLimits` are the
4
+ // headless reads. Lender routing (JupLend default, Kamino for existing Kamino
5
+ // users) happens inside the execute/read functions.
6
+ export * from "./constants";
7
+ export * from "./pdas";
8
+ export * from "./amountUtils";
9
+ export * from "./transactionBuilder";
10
+ export * from "./wallet";
11
+ export { buildHobbaProgram } from "./program";
12
+ export * from "./executeDepositBorrow";
13
+ export * from "./executeAction";
14
+ export * from "./marketConfig";
15
+ export * from "./userState";
16
+ export * from "./position";
17
+ export * from "./positionMetrics";
18
+ export * from "./quote";
19
+ export * from "./limits";
20
+ export * from "./txHelpers";
21
+ export * from "./errors";
@@ -0,0 +1,207 @@
1
+ import { PublicKey, SystemProgram, AccountMeta } from "@solana/web3.js";
2
+ import { TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync } from "@solana/spl-token";
3
+ import {
4
+ JUPLEND_PROGRAM,
5
+ USDC_MINT,
6
+ ATA_PROGRAM_ID,
7
+ } from "../constants";
8
+ import { deriveProgramConfigPda } from "../pdas";
9
+
10
+ interface JuplendUserPdas {
11
+ userStatePda: PublicKey;
12
+ allowlistEntryPda: PublicKey;
13
+ obligationPda: PublicKey; // = position PDA for JupLend
14
+ userMetadataPda: PublicKey;
15
+ userCollateralAta: PublicKey;
16
+ userUsdcAta: PublicKey;
17
+ pdaCollateralAta: PublicKey;
18
+ pdaUsdcAta: PublicKey;
19
+ }
20
+
21
+ const [programConfigPda] = deriveProgramConfigPda();
22
+
23
+ export function getJuplendDepositAccounts(owner: PublicKey, pdas: JuplendUserPdas, collateralMint: PublicKey) {
24
+ return {
25
+ owner,
26
+ userState: pdas.userStatePda,
27
+ userSourceLiquidity: pdas.userCollateralAta,
28
+ pdaSourceLiquidity: pdas.pdaCollateralAta,
29
+ supplyTokenMint: collateralMint,
30
+ tokenProgram: TOKEN_PROGRAM_ID,
31
+ associatedTokenProgram: ATA_PROGRAM_ID,
32
+ systemProgram: SystemProgram.programId,
33
+ programConfig: programConfigPda,
34
+ allowlistEntry: pdas.allowlistEntryPda,
35
+ collateralMint,
36
+ };
37
+ }
38
+
39
+ /// Returns JupLend deposit lender accounts as AccountMeta[] for remaining_accounts.
40
+ /// Layout: [pda_borrow_token_account, juplend_program, ...juplend_operate_accounts]
41
+ export function getJuplendDepositLenderAccounts(
42
+ pdas: JuplendUserPdas,
43
+ ): AccountMeta[] {
44
+ return [
45
+ { pubkey: pdas.pdaUsdcAta, isSigner: false, isWritable: true },
46
+ { pubkey: JUPLEND_PROGRAM, isSigner: false, isWritable: false },
47
+ ];
48
+ }
49
+
50
+ /// Named accounts for combined JupLend deposit+borrow (single operate CPI).
51
+ /// vaultConfig and position are named accounts on this handler (the program
52
+ /// splices them back into the operate list via build_operate_accounts); pass
53
+ /// the operate list's index-0 (vault_config) and index-5 (position) entries.
54
+ export function getJuplendDepositBorrowAccounts(
55
+ owner: PublicKey,
56
+ pdas: JuplendUserPdas,
57
+ collateralMint: PublicKey,
58
+ vaultConfig: PublicKey,
59
+ position: PublicKey,
60
+ ) {
61
+ return {
62
+ owner,
63
+ userState: pdas.userStatePda,
64
+ collateralMint,
65
+ userCollateralAta: pdas.userCollateralAta,
66
+ pdaCollateralAta: pdas.pdaCollateralAta,
67
+ pdaUsdcAta: pdas.pdaUsdcAta,
68
+ userUsdcAta: pdas.userUsdcAta,
69
+ usdcMint: USDC_MINT,
70
+ tokenProgram: TOKEN_PROGRAM_ID,
71
+ associatedTokenProgram: ATA_PROGRAM_ID,
72
+ systemProgram: SystemProgram.programId,
73
+ programConfig: programConfigPda,
74
+ allowlistEntry: pdas.allowlistEntryPda,
75
+ vaultConfig,
76
+ position,
77
+ };
78
+ }
79
+
80
+ /// Remaining accounts for combined deposit+borrow: [juplend_program, ...operate_accounts].
81
+ /// Skips the two duplicated ATAs from the deposit/borrow single-side layouts.
82
+ export function getJuplendDepositBorrowLenderAccounts(): AccountMeta[] {
83
+ return [{ pubkey: JUPLEND_PROGRAM, isSigner: false, isWritable: false }];
84
+ }
85
+
86
+ // Matches surfpool-test.ts testJuplendBorrow accounts exactly
87
+ export function getJuplendBorrowAccounts(
88
+ owner: PublicKey,
89
+ pdas: JuplendUserPdas,
90
+ collateralMint: PublicKey,
91
+ hasVaultShares: boolean = false,
92
+ vaultAccounts?: {
93
+ vault: PublicKey;
94
+ sharesMint: PublicKey;
95
+ sharesAta: PublicKey;
96
+ }
97
+ ) {
98
+ const va = vaultAccounts;
99
+ // Canonical ATA of the placeholder shares mint (the wallet pubkey), so the
100
+ // program's pda_shares_ata == ATA(user_state, shares_mint) check holds when no vault is used.
101
+ const placeholderSharesAta = getAssociatedTokenAddressSync(owner, pdas.userStatePda, true);
102
+ return {
103
+ owner,
104
+ userState: pdas.userStatePda,
105
+ collateralMint,
106
+ pdaUsdcAta: pdas.pdaUsdcAta,
107
+ userUsdcAta: pdas.userUsdcAta,
108
+ tokenProgram: TOKEN_PROGRAM_ID,
109
+ systemProgram: SystemProgram.programId,
110
+ vault: hasVaultShares && va ? va.vault : owner,
111
+ sharesMint: hasVaultShares && va ? va.sharesMint : owner,
112
+ pdaSharesAta: hasVaultShares && va ? va.sharesAta : placeholderSharesAta,
113
+ sharesTokenProgram: hasVaultShares ? TOKEN_PROGRAM_ID : SystemProgram.programId,
114
+ usdcMint: USDC_MINT,
115
+ programConfig: programConfigPda,
116
+ allowlistEntry: pdas.allowlistEntryPda,
117
+ };
118
+ }
119
+
120
+ /// Returns JupLend lender accounts as AccountMeta[] for remaining_accounts.
121
+ /// Layout: [pda_source_liquidity, juplend_program, ...juplend_operate_accounts]
122
+ export function getJuplendBorrowLenderAccounts(
123
+ pdas: JuplendUserPdas,
124
+ ): AccountMeta[] {
125
+ return [
126
+ { pubkey: pdas.pdaCollateralAta, isSigner: false, isWritable: true },
127
+ { pubkey: JUPLEND_PROGRAM, isSigner: false, isWritable: false },
128
+ ];
129
+ }
130
+
131
+ // Matches surfpool-test.ts testJuplendRepay accounts exactly
132
+ export function getJuplendRepayAccounts(owner: PublicKey, pdas: JuplendUserPdas, collateralMint: PublicKey) {
133
+ return {
134
+ owner,
135
+ userState: pdas.userStatePda,
136
+ userUsdcAta: pdas.userUsdcAta,
137
+ pdaUsdcAta: pdas.pdaUsdcAta,
138
+ tokenProgram: TOKEN_PROGRAM_ID,
139
+ programConfig: programConfigPda,
140
+ allowlistEntry: pdas.allowlistEntryPda,
141
+ collateralMint,
142
+ };
143
+ }
144
+
145
+ /// Returns JupLend repay lender accounts as AccountMeta[] for remaining_accounts.
146
+ /// Layout: [pda_source_liquidity, juplend_program, ...juplend_operate_accounts]
147
+ export function getJuplendRepayLenderAccounts(
148
+ pdas: JuplendUserPdas,
149
+ ): AccountMeta[] {
150
+ return [
151
+ { pubkey: pdas.pdaCollateralAta, isSigner: false, isWritable: true },
152
+ { pubkey: JUPLEND_PROGRAM, isSigner: false, isWritable: false },
153
+ ];
154
+ }
155
+
156
+ /// Returns PrepareWithdraw named accounts for JupLend users.
157
+ /// Matches the shared PrepareWithdraw Anchor struct.
158
+ export function getJuplendPrepareWithdrawAccounts(
159
+ owner: PublicKey,
160
+ pdas: JuplendUserPdas,
161
+ collateralMint: PublicKey,
162
+ vaultAccounts: {
163
+ vault: PublicKey;
164
+ sharesMint: PublicKey;
165
+ sharesAta: PublicKey;
166
+ }
167
+ ) {
168
+ const va = vaultAccounts;
169
+ return {
170
+ owner,
171
+ userState: pdas.userStatePda,
172
+ collateralMint,
173
+ programConfig: programConfigPda,
174
+ allowlistEntry: pdas.allowlistEntryPda,
175
+ pdaUsdcAta: pdas.pdaUsdcAta,
176
+ vault: va.vault,
177
+ sharesMint: va.sharesMint,
178
+ pdaSharesAta: va.sharesAta,
179
+ sharesTokenProgram: TOKEN_PROGRAM_ID,
180
+ usdcMint: USDC_MINT,
181
+ tokenProgram: TOKEN_PROGRAM_ID,
182
+ };
183
+ }
184
+
185
+ export function getJuplendWithdrawAccounts(owner: PublicKey, pdas: JuplendUserPdas, collateralMint: PublicKey) {
186
+ return {
187
+ owner,
188
+ userState: pdas.userStatePda,
189
+ pdaDestinationAta: pdas.pdaCollateralAta,
190
+ userDestinationAta: pdas.userCollateralAta,
191
+ tokenProgram: TOKEN_PROGRAM_ID,
192
+ programConfig: programConfigPda,
193
+ allowlistEntry: pdas.allowlistEntryPda,
194
+ collateralMint,
195
+ };
196
+ }
197
+
198
+ /// Returns JupLend withdraw lender accounts as AccountMeta[] for remaining_accounts.
199
+ /// Layout: [pda_borrow_token_account, juplend_program, ...juplend_operate_accounts]
200
+ export function getJuplendWithdrawLenderAccounts(
201
+ pdas: JuplendUserPdas,
202
+ ): AccountMeta[] {
203
+ return [
204
+ { pubkey: pdas.pdaUsdcAta, isSigner: false, isWritable: true },
205
+ { pubkey: JUPLEND_PROGRAM, isSigner: false, isWritable: false },
206
+ ];
207
+ }
@@ -0,0 +1,81 @@
1
+ import { PublicKey, SystemProgram } from "@solana/web3.js";
2
+ import {
3
+ JUPLEND_PROGRAM,
4
+ JUPLEND_VAULT_CONFIG,
5
+ JUPLEND_VAULT_STATE,
6
+ JUPLEND_ORACLE,
7
+ JUPLEND_SUPPLY_RESERVES_LIQUIDITY,
8
+ JUPLEND_BORROW_RESERVES_LIQUIDITY,
9
+ JUPLEND_VAULT_SUPPLY_POSITION,
10
+ JUPLEND_VAULT_BORROW_POSITION,
11
+ JUPLEND_SUPPLY_RATE_MODEL,
12
+ JUPLEND_BORROW_RATE_MODEL,
13
+ JUPLEND_VAULT_SUPPLY_TOKEN_ACCOUNT,
14
+ JUPLEND_VAULT_BORROW_TOKEN_ACCOUNT,
15
+ JUPLEND_LIQUIDITY,
16
+ JUPLEND_LIQUIDITY_PROGRAM,
17
+ JUPLEND_ORACLE_PROGRAM,
18
+ JUPLEND_ORACLE_SOURCE,
19
+ JUPLEND_BRANCH,
20
+ JUPLEND_TICK_0,
21
+ JUPLEND_TICK_1,
22
+ JUPLEND_NEW_BRANCH,
23
+ ATA_PROGRAM_ID,
24
+ CBBTC_MINT,
25
+ USDC_MINT,
26
+ } from "../constants";
27
+ import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
28
+
29
+ interface OperateRemainingResult {
30
+ remaining: { pubkey: PublicKey; isSigner: boolean; isWritable: boolean }[];
31
+ }
32
+
33
+ // Fallback hardcoded remaining accounts for JupLend operate
34
+ // Used when SDK fails (e.g., on new positions)
35
+ export function buildFallbackOperateRemaining(
36
+ position: PublicKey,
37
+ positionTokenAccount: PublicKey,
38
+ currentTick: PublicKey,
39
+ finalTick: PublicKey,
40
+ currentTickId: PublicKey,
41
+ finalTickId: PublicKey
42
+ ): OperateRemainingResult {
43
+ return {
44
+ remaining: [
45
+ { pubkey: JUPLEND_VAULT_CONFIG, isSigner: false, isWritable: true },
46
+ { pubkey: JUPLEND_VAULT_STATE, isSigner: false, isWritable: true },
47
+ { pubkey: CBBTC_MINT, isSigner: false, isWritable: false },
48
+ { pubkey: USDC_MINT, isSigner: false, isWritable: false },
49
+ { pubkey: JUPLEND_ORACLE, isSigner: false, isWritable: false },
50
+ { pubkey: position, isSigner: false, isWritable: true },
51
+ { pubkey: positionTokenAccount, isSigner: false, isWritable: false },
52
+ { pubkey: currentTick, isSigner: false, isWritable: true },
53
+ { pubkey: finalTick, isSigner: false, isWritable: true },
54
+ { pubkey: currentTickId, isSigner: false, isWritable: true },
55
+ { pubkey: finalTickId, isSigner: false, isWritable: true },
56
+ { pubkey: JUPLEND_NEW_BRANCH, isSigner: false, isWritable: true },
57
+ { pubkey: JUPLEND_SUPPLY_RESERVES_LIQUIDITY, isSigner: false, isWritable: true },
58
+ { pubkey: JUPLEND_BORROW_RESERVES_LIQUIDITY, isSigner: false, isWritable: true },
59
+ { pubkey: JUPLEND_VAULT_SUPPLY_POSITION, isSigner: false, isWritable: true },
60
+ { pubkey: JUPLEND_VAULT_BORROW_POSITION, isSigner: false, isWritable: true },
61
+ { pubkey: JUPLEND_SUPPLY_RATE_MODEL, isSigner: false, isWritable: true },
62
+ { pubkey: JUPLEND_BORROW_RATE_MODEL, isSigner: false, isWritable: true },
63
+ { pubkey: JUPLEND_VAULT_SUPPLY_TOKEN_ACCOUNT, isSigner: false, isWritable: true },
64
+ { pubkey: JUPLEND_VAULT_BORROW_TOKEN_ACCOUNT, isSigner: false, isWritable: true },
65
+ { pubkey: JUPLEND_PROGRAM, isSigner: false, isWritable: false }, // supply claim (none)
66
+ { pubkey: JUPLEND_PROGRAM, isSigner: false, isWritable: false }, // borrow claim (none)
67
+ { pubkey: JUPLEND_LIQUIDITY, isSigner: false, isWritable: true },
68
+ { pubkey: JUPLEND_LIQUIDITY_PROGRAM, isSigner: false, isWritable: true },
69
+ { pubkey: JUPLEND_ORACLE_PROGRAM, isSigner: false, isWritable: false },
70
+ { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
71
+ { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
72
+ { pubkey: ATA_PROGRAM_ID, isSigner: false, isWritable: false },
73
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
74
+ // Variable remaining: oracle source, branch, tick arrays
75
+ { pubkey: JUPLEND_ORACLE_SOURCE, isSigner: false, isWritable: false },
76
+ { pubkey: JUPLEND_BRANCH, isSigner: false, isWritable: true },
77
+ { pubkey: JUPLEND_TICK_0, isSigner: false, isWritable: true },
78
+ { pubkey: JUPLEND_TICK_1, isSigner: false, isWritable: true },
79
+ ],
80
+ };
81
+ }
@@ -0,0 +1,86 @@
1
+ import { PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY, SYSVAR_INSTRUCTIONS_PUBKEY, AccountMeta } from "@solana/web3.js";
2
+ import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
3
+ import {
4
+ JUPLEND_PROGRAM,
5
+ JUPLEND_VAULT_CONFIG,
6
+ JUPLEND_VAULT_STATE,
7
+ METAPLEX_PROGRAM,
8
+ ATA_PROGRAM_ID,
9
+ } from "../constants";
10
+ import { deriveDepositConfigPda, deriveOriginConfigPda, deriveProgramConfigPda, deriveUserOriginPda } from "../pdas";
11
+ const [programConfigPda] = deriveProgramConfigPda();
12
+ const [JUPLEND_VAULT_ADMIN] = PublicKey.findProgramAddressSync(
13
+ [Buffer.from("vault_admin")],
14
+ JUPLEND_PROGRAM
15
+ );
16
+
17
+ // Matches surfpool-test.ts createJuplendPosition accounts exactly
18
+ export function getCreateJuplendPositionAccounts(
19
+ owner: PublicKey,
20
+ userStatePda: PublicKey,
21
+ positionPda: PublicKey,
22
+ positionMintPda: PublicKey,
23
+ positionTokenAccount: PublicKey,
24
+ positionMetadata: PublicKey,
25
+ allowlistEntryPda: PublicKey,
26
+ collateralMint: PublicKey,
27
+ vaultState: PublicKey = JUPLEND_VAULT_STATE
28
+ ) {
29
+ return {
30
+ owner,
31
+ userStatePda, // IDL name is "user_state_pda"
32
+ position: positionPda,
33
+ positionMint: positionMintPda,
34
+ positionTokenAccount,
35
+ positionMetadata,
36
+ juplendVaultAdmin: JUPLEND_VAULT_ADMIN,
37
+ juplendVaultState: vaultState,
38
+ juplendProgram: JUPLEND_PROGRAM,
39
+ metadataProgram: METAPLEX_PROGRAM,
40
+ tokenProgram: TOKEN_PROGRAM_ID,
41
+ associatedTokenProgram: ATA_PROGRAM_ID,
42
+ instructionSysvar: SYSVAR_INSTRUCTIONS_PUBKEY,
43
+ programConfig: programConfigPda,
44
+ allowlistEntry: allowlistEntryPda,
45
+ collateralMint,
46
+ rent: SYSVAR_RENT_PUBKEY,
47
+ systemProgram: SystemProgram.programId,
48
+ };
49
+ }
50
+
51
+ // Matches surfpool-test.ts initializeUser accounts exactly
52
+ export function getInitializeUserJuplendAccounts(
53
+ owner: PublicKey,
54
+ userStatePda: PublicKey,
55
+ allowlistEntryPda: PublicKey,
56
+ collateralMint: PublicKey,
57
+ origin: number = 0,
58
+ ) {
59
+ return {
60
+ owner,
61
+ userState: userStatePda,
62
+ collateralMint,
63
+ systemProgram: SystemProgram.programId,
64
+ programConfig: programConfigPda,
65
+ allowlistEntry: allowlistEntryPda,
66
+ depositConfig: deriveDepositConfigPda(collateralMint)[0],
67
+ // Per-wallet origin registration; created on the wallet's first init,
68
+ // later inits keep the stored origin (the origin arg is then ignored).
69
+ userOrigin: deriveUserOriginPda(owner)[0],
70
+ // Seeded by the `origin` instruction arg — always pass explicitly rather
71
+ // than relying on Anchor's IDL auto-resolution.
72
+ originConfig: deriveOriginConfigPda(origin)[0],
73
+ };
74
+ }
75
+
76
+ /// Returns JupLend initialize_user lender accounts as AccountMeta[] for remaining_accounts.
77
+ /// Layout: [position, juplend_vault_config]
78
+ export function getJuplendInitializeUserLenderAccounts(
79
+ positionPda: PublicKey,
80
+ vaultConfig: PublicKey = JUPLEND_VAULT_CONFIG
81
+ ): AccountMeta[] {
82
+ return [
83
+ { pubkey: positionPda, isSigner: false, isWritable: false },
84
+ { pubkey: vaultConfig, isSigner: false, isWritable: false },
85
+ ];
86
+ }
@@ -0,0 +1,41 @@
1
+ import BN from "bn.js";
2
+
3
+ // JupLend position account layout (from SDK decode):
4
+ // 8 (discriminator) + 2 (vaultId) + 4 (nftId) + 32 (positionMint) + 1 (isSupplyOnlyPosition)
5
+ // offset 47: tick (i32 LE, 4 bytes)
6
+ // offset 51: tickId (u32 LE, 4 bytes)
7
+ // offset 55: supplyAmount (u64 LE, 8 bytes) — supply shares
8
+ // offset 63: dustDebtAmount (u64 LE, 8 bytes) — debt in micro-USDC (approx)
9
+ // Total: 71 bytes
10
+
11
+ export function parseJuplendPosition(data: Buffer): {
12
+ collateral: number; // satoshis
13
+ debt: number; // micro-USDC
14
+ tick: number;
15
+ supplyAmount: number; // raw supply shares
16
+ dustDebtAmount: number; // raw dust debt
17
+ } {
18
+ if (data.length < 71) {
19
+ return { collateral: 0, debt: 0, tick: 0, supplyAmount: 0, dustDebtAmount: 0 };
20
+ }
21
+
22
+ const tick = data.readInt32LE(47);
23
+
24
+ // Read u64 values using BN (avoids JS Number precision issues for large u64)
25
+ const supplyBn = new BN(data.slice(55, 63), "le");
26
+ const dustDebtBn = new BN(data.slice(63, 71), "le");
27
+
28
+ const supplyAmount = supplyBn.toNumber();
29
+ const dustDebtAmount = dustDebtBn.toNumber();
30
+
31
+ return {
32
+ // Raw values for exchange rate conversion (done in hook with vault state)
33
+ supplyAmount,
34
+ dustDebtAmount,
35
+ // Debt: dustDebtAmount is approximately micro-USDC
36
+ debt: dustDebtAmount,
37
+ // Collateral: needs exchange rate conversion (set to 0, computed in hook)
38
+ collateral: 0,
39
+ tick,
40
+ };
41
+ }
@@ -0,0 +1,112 @@
1
+ import { PublicKey, TransactionInstruction, SystemProgram } from "@solana/web3.js";
2
+ import { ATA_PROGRAM_ID } from "../constants";
3
+
4
+ interface SdkRemainingEntry {
5
+ pubkey: string;
6
+ writable: boolean;
7
+ }
8
+
9
+ interface SdkOtherIx {
10
+ programId: string;
11
+ keys: { pubkey: string; isSigner: boolean; isWritable: boolean }[];
12
+ data: string; // base64
13
+ }
14
+
15
+ export interface SdkOperateResponse {
16
+ remaining: SdkRemainingEntry[];
17
+ variableRemaining: SdkRemainingEntry[];
18
+ otherIxs: SdkOtherIx[];
19
+ currentTick: string;
20
+ finalTick: string;
21
+ currentTickValue: number | null;
22
+ finalTickValue: number | null;
23
+ }
24
+
25
+ export interface SdkOperateRequest {
26
+ vaultId: number;
27
+ positionId: number;
28
+ colAmount: string;
29
+ debtAmount: string;
30
+ signerPda: string;
31
+ }
32
+
33
+ export async function fetchOperateContext(
34
+ params: SdkOperateRequest,
35
+ apiBaseUrl: string = "",
36
+ ): Promise<SdkOperateResponse> {
37
+ const base = apiBaseUrl.replace(/\/+$/, "");
38
+ const resp = await fetch(`${base}/api/juplend-operate`, {
39
+ method: "POST",
40
+ headers: { "Content-Type": "application/json" },
41
+ body: JSON.stringify(params),
42
+ });
43
+ if (!resp.ok) {
44
+ const errData = await resp.json().catch(() => ({ error: "SDK API failed" }));
45
+ throw new Error(errData.error || `SDK API returned ${resp.status}`);
46
+ }
47
+ return resp.json();
48
+ }
49
+
50
+ // Deserialize SDK remaining accounts into AccountMeta[]
51
+ // The API returns 27 fixed entries; caller adds ATA_PROGRAM + SystemProgram, then variable remaining
52
+ export function deserializeRemaining(
53
+ sdkResp: SdkOperateResponse
54
+ ): { pubkey: PublicKey; isSigner: boolean; isWritable: boolean }[] {
55
+ const fixed = sdkResp.remaining.map((a) => ({
56
+ pubkey: new PublicKey(a.pubkey),
57
+ isSigner: false,
58
+ isWritable: a.writable,
59
+ }));
60
+
61
+ fixed.push({ pubkey: ATA_PROGRAM_ID, isSigner: false, isWritable: false });
62
+ fixed.push({ pubkey: SystemProgram.programId, isSigner: false, isWritable: false });
63
+
64
+ const variable = sdkResp.variableRemaining.map((a) => ({
65
+ pubkey: new PublicKey(a.pubkey),
66
+ isSigner: false,
67
+ isWritable: a.writable,
68
+ }));
69
+
70
+ return [...fixed, ...variable];
71
+ }
72
+
73
+ // Known JupLend instruction discriminators that the deployed program supports
74
+ const KNOWN_JUPLEND_DISCS = new Set([
75
+ "160d3e8d4959b21d", // InitTick
76
+ "386e79a998f156b7", // InitTickIdLiquidation
77
+ ]);
78
+ const JUPLEND_PROGRAM_STR = "jupr81YtYssSyPt8jbnGuiWon5f6x9TcDEFxYe3Bdzi";
79
+
80
+ // Deserialize otherIxs (initTick, initBranch, etc.) and replace signerPda with actual payer.
81
+ // Filters out unrecognized JupLend instructions that cause InstructionFallbackNotFound.
82
+ export function deserializeOtherIxs(
83
+ sdkResp: SdkOperateResponse,
84
+ signerPda: PublicKey,
85
+ actualPayer: PublicKey
86
+ ): TransactionInstruction[] {
87
+ const signerStr = signerPda.toBase58();
88
+ return sdkResp.otherIxs
89
+ .filter((ix) => {
90
+ // Keep non-JupLend instructions as-is
91
+ if (ix.programId !== JUPLEND_PROGRAM_STR) return true;
92
+ // Log ALL JupLend instruction discriminators for debugging
93
+ const disc = Buffer.from(ix.data, "base64").subarray(0, 8).toString("hex");
94
+ console.log("[otherIxs] JupLend instruction disc:", disc, KNOWN_JUPLEND_DISCS.has(disc) ? "(known)" : "(UNKNOWN — filtering out)");
95
+ if (!KNOWN_JUPLEND_DISCS.has(disc)) return false;
96
+ return true;
97
+ })
98
+ .map((ix) =>
99
+ new TransactionInstruction({
100
+ programId: new PublicKey(ix.programId),
101
+ keys: ix.keys.map((k) => {
102
+ const isSigner = k.pubkey === signerStr;
103
+ return {
104
+ pubkey: isSigner ? actualPayer : new PublicKey(k.pubkey),
105
+ isSigner: isSigner ? true : k.isSigner,
106
+ isWritable: k.isWritable,
107
+ };
108
+ }),
109
+ data: Buffer.from(ix.data, "base64"),
110
+ })
111
+ );
112
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * JupLend tick ↔ debt fixed-point math (ported from the JupLend SDK).
3
+ * Pure BN math, browser-safe; shared by /api/juplend-position and any
4
+ * script that needs debt-from-tick without an RPC tick fetch.
5
+ */
6
+ import BN from "bn.js";
7
+
8
+ export const JUPLEND_MIN_TICK = -16383;
9
+ export const JUPLEND_MAX_TICK_VALUE = 16383;
10
+
11
+ /**
12
+ * Tick ratio as an X48 fixed-point BN (scaled by 2^48).
13
+ * debtRaw = ratioX48 * (colRaw + 1) >> 48 + 1
14
+ */
15
+ export function getRatioAtTick(tick: number): BN {
16
+ if (tick < JUPLEND_MIN_TICK || tick > JUPLEND_MAX_TICK_VALUE) {
17
+ throw new Error(`Tick ${tick} out of range [${JUPLEND_MIN_TICK}, ${JUPLEND_MAX_TICK_VALUE}]`);
18
+ }
19
+
20
+ const FACTORS: BN[] = [
21
+ new BN("18446744073709551616"), // FACTOR00 = 2^64
22
+ new BN("18419115400608638658"),
23
+ new BN("18391528108445969703"),
24
+ new BN("18336477419114433396"),
25
+ new BN("18226869890870665593"),
26
+ new BN("18009616477100071088"),
27
+ new BN("17582847377087825313"),
28
+ new BN("16759408633341240198"),
29
+ new BN("15226414841393184936"),
30
+ new BN("12568272644527235157"),
31
+ new BN("8563108841104354677"),
32
+ new BN("3975055583337633975"),
33
+ new BN("856577552520149366"),
34
+ new BN("39775317560084773"),
35
+ new BN("85764505686420"),
36
+ new BN("398745188"),
37
+ ];
38
+
39
+ function mulShift64(n0: BN, n1: BN): BN {
40
+ const product = n0.mul(n1);
41
+ return product.shrn(64);
42
+ }
43
+
44
+ const absTick = Math.abs(tick);
45
+ let factor = FACTORS[0];
46
+ if (absTick & 1) factor = FACTORS[1];
47
+ if (absTick & 2) factor = mulShift64(factor, FACTORS[2]);
48
+ if (absTick & 4) factor = mulShift64(factor, FACTORS[3]);
49
+ if (absTick & 8) factor = mulShift64(factor, FACTORS[4]);
50
+ if (absTick & 16) factor = mulShift64(factor, FACTORS[5]);
51
+ if (absTick & 32) factor = mulShift64(factor, FACTORS[6]);
52
+ if (absTick & 64) factor = mulShift64(factor, FACTORS[7]);
53
+ if (absTick & 128) factor = mulShift64(factor, FACTORS[8]);
54
+ if (absTick & 256) factor = mulShift64(factor, FACTORS[9]);
55
+ if (absTick & 512) factor = mulShift64(factor, FACTORS[10]);
56
+ if (absTick & 1024) factor = mulShift64(factor, FACTORS[11]);
57
+ if (absTick & 2048) factor = mulShift64(factor, FACTORS[12]);
58
+ if (absTick & 4096) factor = mulShift64(factor, FACTORS[13]);
59
+ if (absTick & 8192) factor = mulShift64(factor, FACTORS[14]);
60
+ if (absTick & 16384) factor = mulShift64(factor, FACTORS[15]);
61
+
62
+ let precision = new BN(0);
63
+ if (tick > 0) {
64
+ const maxU128 = new BN(2).pow(new BN(128)).subn(1);
65
+ factor = maxU128.div(factor);
66
+ if (!factor.mod(new BN(65536)).isZero()) {
67
+ precision = new BN(1);
68
+ }
69
+ }
70
+
71
+ return factor.shrn(16).add(precision);
72
+ }
73
+
74
+ /**
75
+ * Debt (in borrow shares) implied by a position's tick and collateral shares.
76
+ * Returns 0 at/below MIN_TICK (no debt).
77
+ */
78
+ export function debtFromTick(colRaw: BN, tick: number): BN {
79
+ if (tick <= JUPLEND_MIN_TICK) return new BN(0);
80
+ const ratio = getRatioAtTick(tick);
81
+ return ratio.mul(colRaw.addn(1)).shrn(48).addn(1);
82
+ }