@jinn-network/client 0.1.1 → 0.1.2-canary.d6e72dfd

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 (317) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/CONTRIBUTING.md +123 -0
  3. package/README.md +210 -37
  4. package/deployments/deployment-claim-registry-baseSepolia.json +13 -0
  5. package/deployments/deployment-jinn-testnet-faucet-baseSepolia-fast.json +15 -0
  6. package/dist/adapters/claim-registry/abi.d.ts +127 -0
  7. package/dist/adapters/claim-registry/abi.js +93 -0
  8. package/dist/adapters/claim-registry/abi.js.map +1 -0
  9. package/dist/adapters/claim-registry/client.d.ts +89 -0
  10. package/dist/adapters/claim-registry/client.js +205 -0
  11. package/dist/adapters/claim-registry/client.js.map +1 -0
  12. package/dist/adapters/mech/adapter.d.ts +1 -0
  13. package/dist/adapters/mech/adapter.js +75 -41
  14. package/dist/adapters/mech/adapter.js.map +1 -1
  15. package/dist/adapters/mech/contracts.d.ts +2 -0
  16. package/dist/adapters/mech/contracts.js +57 -7
  17. package/dist/adapters/mech/contracts.js.map +1 -1
  18. package/dist/adapters/mech/ipfs.d.ts +8 -0
  19. package/dist/adapters/mech/ipfs.js +12 -0
  20. package/dist/adapters/mech/ipfs.js.map +1 -1
  21. package/dist/adapters/mech/types.d.ts +20 -46
  22. package/dist/adapters/mech/types.js +16 -35
  23. package/dist/adapters/mech/types.js.map +1 -1
  24. package/dist/api/gather-status.d.ts +1 -0
  25. package/dist/api/gather-status.js +33 -1
  26. package/dist/api/gather-status.js.map +1 -1
  27. package/dist/api/portfolio-v0-build.d.ts +81 -0
  28. package/dist/api/portfolio-v0-build.js +141 -0
  29. package/dist/api/portfolio-v0-build.js.map +1 -0
  30. package/dist/api/portfolio-v0-doctor.d.ts +37 -0
  31. package/dist/api/portfolio-v0-doctor.js +123 -0
  32. package/dist/api/portfolio-v0-doctor.js.map +1 -0
  33. package/dist/api/rewards-build.js +1 -1
  34. package/dist/api/rewards-build.js.map +1 -1
  35. package/dist/api/status-build.d.ts +7 -0
  36. package/dist/api/status-build.js +1 -0
  37. package/dist/api/status-build.js.map +1 -1
  38. package/dist/bin/jinn-mcp.d.ts +0 -12
  39. package/dist/bin/jinn-mcp.js +5 -14
  40. package/dist/bin/jinn-mcp.js.map +1 -1
  41. package/dist/build-meta.json +1 -1
  42. package/dist/cli/commands/auth.js +115 -25
  43. package/dist/cli/commands/auth.js.map +1 -1
  44. package/dist/cli/commands/bootstrap.js +1 -0
  45. package/dist/cli/commands/bootstrap.js.map +1 -1
  46. package/dist/cli/commands/doctor.js +130 -14
  47. package/dist/cli/commands/doctor.js.map +1 -1
  48. package/dist/cli/commands/fleet-scale.js +1 -0
  49. package/dist/cli/commands/fleet-scale.js.map +1 -1
  50. package/dist/cli/commands/fund-requirements.js +2 -0
  51. package/dist/cli/commands/fund-requirements.js.map +1 -1
  52. package/dist/cli/commands/intents.d.ts +17 -0
  53. package/dist/cli/commands/intents.js +489 -0
  54. package/dist/cli/commands/intents.js.map +1 -0
  55. package/dist/cli/commands/keys-backup.js +13 -11
  56. package/dist/cli/commands/keys-backup.js.map +1 -1
  57. package/dist/cli/commands/mcp.d.ts +3 -0
  58. package/dist/cli/commands/mcp.js +19 -0
  59. package/dist/cli/commands/mcp.js.map +1 -0
  60. package/dist/cli/commands/plugin-install.js +8 -4
  61. package/dist/cli/commands/plugin-install.js.map +1 -1
  62. package/dist/cli/commands/quickstart.js +60 -4
  63. package/dist/cli/commands/quickstart.js.map +1 -1
  64. package/dist/cli/commands/rewards.js +27 -1
  65. package/dist/cli/commands/rewards.js.map +1 -1
  66. package/dist/cli/commands/submit-intent.js +108 -5
  67. package/dist/cli/commands/submit-intent.js.map +1 -1
  68. package/dist/cli/commands/version.js +1 -0
  69. package/dist/cli/commands/version.js.map +1 -1
  70. package/dist/cli/deployment-digest.js +5 -0
  71. package/dist/cli/deployment-digest.js.map +1 -1
  72. package/dist/cli/execution-context.js +1 -0
  73. package/dist/cli/execution-context.js.map +1 -1
  74. package/dist/cli/index.js +4 -0
  75. package/dist/cli/index.js.map +1 -1
  76. package/dist/cli/intent-registry-access.d.ts +64 -0
  77. package/dist/cli/intent-registry-access.js +187 -0
  78. package/dist/cli/intent-registry-access.js.map +1 -0
  79. package/dist/cli/introspection-context.js +1 -0
  80. package/dist/cli/introspection-context.js.map +1 -1
  81. package/dist/cli/password.d.ts +21 -9
  82. package/dist/cli/password.js +45 -24
  83. package/dist/cli/password.js.map +1 -1
  84. package/dist/config.d.ts +110 -8
  85. package/dist/config.js +41 -12
  86. package/dist/config.js.map +1 -1
  87. package/dist/daemon/creator.d.ts +7 -1
  88. package/dist/daemon/creator.js +38 -3
  89. package/dist/daemon/creator.js.map +1 -1
  90. package/dist/daemon/daemon.d.ts +43 -0
  91. package/dist/daemon/daemon.js +87 -2
  92. package/dist/daemon/daemon.js.map +1 -1
  93. package/dist/earning/bootstrap.d.ts +2 -1
  94. package/dist/earning/bootstrap.js +72 -4
  95. package/dist/earning/bootstrap.js.map +1 -1
  96. package/dist/earning/contracts.d.ts +10 -0
  97. package/dist/earning/contracts.js +24 -0
  98. package/dist/earning/contracts.js.map +1 -1
  99. package/dist/earning/jinn-rewards.d.ts +9 -0
  100. package/dist/earning/jinn-rewards.js +7 -0
  101. package/dist/earning/jinn-rewards.js.map +1 -1
  102. package/dist/intents/prediction-apy-v0-auto.d.ts +11 -0
  103. package/dist/intents/prediction-apy-v0-auto.js +46 -0
  104. package/dist/intents/prediction-apy-v0-auto.js.map +1 -0
  105. package/dist/intents/prediction-apy-v0-template.d.ts +8 -0
  106. package/dist/intents/prediction-apy-v0-template.js +22 -0
  107. package/dist/intents/prediction-apy-v0-template.js.map +1 -0
  108. package/dist/intents/prediction-v0-auto.d.ts +53 -0
  109. package/dist/intents/prediction-v0-auto.js +84 -0
  110. package/dist/intents/prediction-v0-auto.js.map +1 -0
  111. package/dist/intents/prediction-v0-template.d.ts +65 -0
  112. package/dist/intents/prediction-v0-template.js +125 -0
  113. package/dist/intents/prediction-v0-template.js.map +1 -0
  114. package/dist/main.js +149 -1
  115. package/dist/main.js.map +1 -1
  116. package/dist/mcp/operator-server.d.ts +1 -1
  117. package/dist/mcp/operator-server.js +1 -1
  118. package/dist/preflight/claude-auth.d.ts +12 -1
  119. package/dist/preflight/claude-auth.js +21 -3
  120. package/dist/preflight/claude-auth.js.map +1 -1
  121. package/dist/restorer/engine/canonical-json.d.ts +18 -0
  122. package/dist/restorer/engine/canonical-json.js +59 -0
  123. package/dist/restorer/engine/canonical-json.js.map +1 -0
  124. package/dist/restorer/engine/claim.d.ts +69 -0
  125. package/dist/restorer/engine/claim.js +104 -0
  126. package/dist/restorer/engine/claim.js.map +1 -0
  127. package/dist/restorer/engine/delivery.d.ts +52 -0
  128. package/dist/restorer/engine/delivery.js +63 -0
  129. package/dist/restorer/engine/delivery.js.map +1 -0
  130. package/dist/restorer/engine/engine.d.ts +203 -0
  131. package/dist/restorer/engine/engine.js +753 -0
  132. package/dist/restorer/engine/engine.js.map +1 -0
  133. package/dist/restorer/engine/manifest-assembly.d.ts +67 -0
  134. package/dist/restorer/engine/manifest-assembly.js +79 -0
  135. package/dist/restorer/engine/manifest-assembly.js.map +1 -0
  136. package/dist/restorer/engine/packaging.d.ts +87 -0
  137. package/dist/restorer/engine/packaging.js +350 -0
  138. package/dist/restorer/engine/packaging.js.map +1 -0
  139. package/dist/restorer/engine/persistence.d.ts +170 -0
  140. package/dist/restorer/engine/persistence.js +381 -0
  141. package/dist/restorer/engine/persistence.js.map +1 -0
  142. package/dist/restorer/engine/recovery.d.ts +22 -0
  143. package/dist/restorer/engine/recovery.js +24 -0
  144. package/dist/restorer/engine/recovery.js.map +1 -0
  145. package/dist/restorer/engine/registry.d.ts +62 -0
  146. package/dist/restorer/engine/registry.js +73 -0
  147. package/dist/restorer/engine/registry.js.map +1 -0
  148. package/dist/restorer/engine/signing.d.ts +30 -0
  149. package/dist/restorer/engine/signing.js +39 -0
  150. package/dist/restorer/engine/signing.js.map +1 -0
  151. package/dist/restorer/engine/state.d.ts +42 -0
  152. package/dist/restorer/engine/state.js +87 -0
  153. package/dist/restorer/engine/state.js.map +1 -0
  154. package/dist/restorer/impls/claude-mcp-hyperliquid/api-wallet.d.ts +64 -0
  155. package/dist/restorer/impls/claude-mcp-hyperliquid/api-wallet.js +96 -0
  156. package/dist/restorer/impls/claude-mcp-hyperliquid/api-wallet.js.map +1 -0
  157. package/dist/restorer/impls/claude-mcp-hyperliquid/index.d.ts +101 -0
  158. package/dist/restorer/impls/claude-mcp-hyperliquid/index.js +710 -0
  159. package/dist/restorer/impls/claude-mcp-hyperliquid/index.js.map +1 -0
  160. package/dist/restorer/impls/claude-mcp-hyperliquid/mcp-tools.d.ts +137 -0
  161. package/dist/restorer/impls/claude-mcp-hyperliquid/mcp-tools.js +865 -0
  162. package/dist/restorer/impls/claude-mcp-hyperliquid/mcp-tools.js.map +1 -0
  163. package/dist/restorer/impls/claude-mcp-hyperliquid/safety-rails.d.ts +74 -0
  164. package/dist/restorer/impls/claude-mcp-hyperliquid/safety-rails.js +74 -0
  165. package/dist/restorer/impls/claude-mcp-hyperliquid/safety-rails.js.map +1 -0
  166. package/dist/restorer/impls/claude-mcp-hyperliquid/session-orchestrator.d.ts +97 -0
  167. package/dist/restorer/impls/claude-mcp-hyperliquid/session-orchestrator.js +226 -0
  168. package/dist/restorer/impls/claude-mcp-hyperliquid/session-orchestrator.js.map +1 -0
  169. package/dist/restorer/impls/claude-mcp-prediction/index.d.ts +43 -0
  170. package/dist/restorer/impls/claude-mcp-prediction/index.js +230 -0
  171. package/dist/restorer/impls/claude-mcp-prediction/index.js.map +1 -0
  172. package/dist/restorer/impls/claude-mcp-prediction/mcp-tools.d.ts +38 -0
  173. package/dist/restorer/impls/claude-mcp-prediction/mcp-tools.js +135 -0
  174. package/dist/restorer/impls/claude-mcp-prediction/mcp-tools.js.map +1 -0
  175. package/dist/restorer/impls/claude-mcp-prediction/prompt.d.ts +8 -0
  176. package/dist/restorer/impls/claude-mcp-prediction/prompt.js +54 -0
  177. package/dist/restorer/impls/claude-mcp-prediction/prompt.js.map +1 -0
  178. package/dist/restorer/impls/claude-mcp-prediction/session-orchestrator.d.ts +36 -0
  179. package/dist/restorer/impls/claude-mcp-prediction/session-orchestrator.js +137 -0
  180. package/dist/restorer/impls/claude-mcp-prediction/session-orchestrator.js.map +1 -0
  181. package/dist/restorer/impls/claude-mcp-prediction/types.d.ts +82 -0
  182. package/dist/restorer/impls/claude-mcp-prediction/types.js +6 -0
  183. package/dist/restorer/impls/claude-mcp-prediction/types.js.map +1 -0
  184. package/dist/restorer/impls/legacy-claude/index.d.ts +45 -0
  185. package/dist/restorer/impls/legacy-claude/index.js +71 -0
  186. package/dist/restorer/impls/legacy-claude/index.js.map +1 -0
  187. package/dist/restorer/impls/portfolio-v0-evaluator/canonical-metrics.d.ts +68 -0
  188. package/dist/restorer/impls/portfolio-v0-evaluator/canonical-metrics.js +117 -0
  189. package/dist/restorer/impls/portfolio-v0-evaluator/canonical-metrics.js.map +1 -0
  190. package/dist/restorer/impls/portfolio-v0-evaluator/checks/availability.d.ts +49 -0
  191. package/dist/restorer/impls/portfolio-v0-evaluator/checks/availability.js +91 -0
  192. package/dist/restorer/impls/portfolio-v0-evaluator/checks/availability.js.map +1 -0
  193. package/dist/restorer/impls/portfolio-v0-evaluator/checks/consistency.d.ts +78 -0
  194. package/dist/restorer/impls/portfolio-v0-evaluator/checks/consistency.js +274 -0
  195. package/dist/restorer/impls/portfolio-v0-evaluator/checks/consistency.js.map +1 -0
  196. package/dist/restorer/impls/portfolio-v0-evaluator/checks/eligibility.d.ts +23 -0
  197. package/dist/restorer/impls/portfolio-v0-evaluator/checks/eligibility.js +49 -0
  198. package/dist/restorer/impls/portfolio-v0-evaluator/checks/eligibility.js.map +1 -0
  199. package/dist/restorer/impls/portfolio-v0-evaluator/checks/integrity.d.ts +25 -0
  200. package/dist/restorer/impls/portfolio-v0-evaluator/checks/integrity.js +44 -0
  201. package/dist/restorer/impls/portfolio-v0-evaluator/checks/integrity.js.map +1 -0
  202. package/dist/restorer/impls/portfolio-v0-evaluator/checks/spec.d.ts +17 -0
  203. package/dist/restorer/impls/portfolio-v0-evaluator/checks/spec.js +43 -0
  204. package/dist/restorer/impls/portfolio-v0-evaluator/checks/spec.js.map +1 -0
  205. package/dist/restorer/impls/portfolio-v0-evaluator/index.d.ts +43 -0
  206. package/dist/restorer/impls/portfolio-v0-evaluator/index.js +431 -0
  207. package/dist/restorer/impls/portfolio-v0-evaluator/index.js.map +1 -0
  208. package/dist/restorer/impls/portfolio-v0-evaluator/score.d.ts +21 -0
  209. package/dist/restorer/impls/portfolio-v0-evaluator/score.js +32 -0
  210. package/dist/restorer/impls/portfolio-v0-evaluator/score.js.map +1 -0
  211. package/dist/restorer/impls/portfolio-v0-evaluator/types.d.ts +32 -0
  212. package/dist/restorer/impls/portfolio-v0-evaluator/types.js +8 -0
  213. package/dist/restorer/impls/portfolio-v0-evaluator/types.js.map +1 -0
  214. package/dist/restorer/impls/prediction-apy-v0-baseline/index.d.ts +39 -0
  215. package/dist/restorer/impls/prediction-apy-v0-baseline/index.js +98 -0
  216. package/dist/restorer/impls/prediction-apy-v0-baseline/index.js.map +1 -0
  217. package/dist/restorer/impls/prediction-apy-v0-baseline/strategy.d.ts +2 -0
  218. package/dist/restorer/impls/prediction-apy-v0-baseline/strategy.js +7 -0
  219. package/dist/restorer/impls/prediction-apy-v0-baseline/strategy.js.map +1 -0
  220. package/dist/restorer/impls/prediction-apy-v0-baseline/types.d.ts +4 -0
  221. package/dist/restorer/impls/prediction-apy-v0-baseline/types.js +2 -0
  222. package/dist/restorer/impls/prediction-apy-v0-baseline/types.js.map +1 -0
  223. package/dist/restorer/impls/prediction-apy-v0-evaluator/canonical-metrics.d.ts +2 -0
  224. package/dist/restorer/impls/prediction-apy-v0-evaluator/canonical-metrics.js +7 -0
  225. package/dist/restorer/impls/prediction-apy-v0-evaluator/canonical-metrics.js.map +1 -0
  226. package/dist/restorer/impls/prediction-apy-v0-evaluator/index.d.ts +39 -0
  227. package/dist/restorer/impls/prediction-apy-v0-evaluator/index.js +186 -0
  228. package/dist/restorer/impls/prediction-apy-v0-evaluator/index.js.map +1 -0
  229. package/dist/restorer/impls/prediction-apy-v0-evaluator/score.d.ts +9 -0
  230. package/dist/restorer/impls/prediction-apy-v0-evaluator/score.js +20 -0
  231. package/dist/restorer/impls/prediction-apy-v0-evaluator/score.js.map +1 -0
  232. package/dist/restorer/impls/prediction-apy-v0-evaluator/types.d.ts +7 -0
  233. package/dist/restorer/impls/prediction-apy-v0-evaluator/types.js +2 -0
  234. package/dist/restorer/impls/prediction-apy-v0-evaluator/types.js.map +1 -0
  235. package/dist/restorer/impls/prediction-v0-baseline/index.d.ts +29 -0
  236. package/dist/restorer/impls/prediction-v0-baseline/index.js +94 -0
  237. package/dist/restorer/impls/prediction-v0-baseline/index.js.map +1 -0
  238. package/dist/restorer/impls/prediction-v0-baseline/strategy.d.ts +8 -0
  239. package/dist/restorer/impls/prediction-v0-baseline/strategy.js +41 -0
  240. package/dist/restorer/impls/prediction-v0-baseline/strategy.js.map +1 -0
  241. package/dist/restorer/impls/prediction-v0-baseline/types.d.ts +7 -0
  242. package/dist/restorer/impls/prediction-v0-baseline/types.js +2 -0
  243. package/dist/restorer/impls/prediction-v0-baseline/types.js.map +1 -0
  244. package/dist/restorer/impls/prediction-v0-evaluator/canonical-metrics.d.ts +20 -0
  245. package/dist/restorer/impls/prediction-v0-evaluator/canonical-metrics.js +66 -0
  246. package/dist/restorer/impls/prediction-v0-evaluator/canonical-metrics.js.map +1 -0
  247. package/dist/restorer/impls/prediction-v0-evaluator/checks/availability.d.ts +9 -0
  248. package/dist/restorer/impls/prediction-v0-evaluator/checks/availability.js +23 -0
  249. package/dist/restorer/impls/prediction-v0-evaluator/checks/availability.js.map +1 -0
  250. package/dist/restorer/impls/prediction-v0-evaluator/checks/eligibility.d.ts +3 -0
  251. package/dist/restorer/impls/prediction-v0-evaluator/checks/eligibility.js +13 -0
  252. package/dist/restorer/impls/prediction-v0-evaluator/checks/eligibility.js.map +1 -0
  253. package/dist/restorer/impls/prediction-v0-evaluator/checks/integrity.d.ts +7 -0
  254. package/dist/restorer/impls/prediction-v0-evaluator/checks/integrity.js +93 -0
  255. package/dist/restorer/impls/prediction-v0-evaluator/checks/integrity.js.map +1 -0
  256. package/dist/restorer/impls/prediction-v0-evaluator/checks/spec.d.ts +5 -0
  257. package/dist/restorer/impls/prediction-v0-evaluator/checks/spec.js +20 -0
  258. package/dist/restorer/impls/prediction-v0-evaluator/checks/spec.js.map +1 -0
  259. package/dist/restorer/impls/prediction-v0-evaluator/index.d.ts +33 -0
  260. package/dist/restorer/impls/prediction-v0-evaluator/index.js +208 -0
  261. package/dist/restorer/impls/prediction-v0-evaluator/index.js.map +1 -0
  262. package/dist/restorer/impls/prediction-v0-evaluator/score.d.ts +8 -0
  263. package/dist/restorer/impls/prediction-v0-evaluator/score.js +15 -0
  264. package/dist/restorer/impls/prediction-v0-evaluator/score.js.map +1 -0
  265. package/dist/restorer/impls/prediction-v0-evaluator/types.d.ts +7 -0
  266. package/dist/restorer/impls/prediction-v0-evaluator/types.js +2 -0
  267. package/dist/restorer/impls/prediction-v0-evaluator/types.js.map +1 -0
  268. package/dist/restorer/types.d.ts +177 -0
  269. package/dist/restorer/types.js +7 -0
  270. package/dist/restorer/types.js.map +1 -0
  271. package/dist/store/store.d.ts +3 -1
  272. package/dist/store/store.js +3 -0
  273. package/dist/store/store.js.map +1 -1
  274. package/dist/types/desired-state.d.ts +53 -0
  275. package/dist/types/desired-state.js +20 -0
  276. package/dist/types/desired-state.js.map +1 -1
  277. package/dist/types/index.d.ts +4 -1
  278. package/dist/types/index.js +4 -1
  279. package/dist/types/index.js.map +1 -1
  280. package/dist/types/portfolio.d.ts +1000 -0
  281. package/dist/types/portfolio.js +168 -0
  282. package/dist/types/portfolio.js.map +1 -0
  283. package/dist/types/prediction-apy.d.ts +919 -0
  284. package/dist/types/prediction-apy.js +121 -0
  285. package/dist/types/prediction-apy.js.map +1 -0
  286. package/dist/types/prediction.d.ts +925 -0
  287. package/dist/types/prediction.js +140 -0
  288. package/dist/types/prediction.js.map +1 -0
  289. package/dist/venues/aave-v3/addresses.d.ts +6 -0
  290. package/dist/venues/aave-v3/addresses.js +19 -0
  291. package/dist/venues/aave-v3/addresses.js.map +1 -0
  292. package/dist/venues/aave-v3/client.d.ts +81 -0
  293. package/dist/venues/aave-v3/client.js +97 -0
  294. package/dist/venues/aave-v3/client.js.map +1 -0
  295. package/dist/venues/chainlink/client.d.ts +99 -0
  296. package/dist/venues/chainlink/client.js +130 -0
  297. package/dist/venues/chainlink/client.js.map +1 -0
  298. package/dist/venues/chainlink/feeds.d.ts +8 -0
  299. package/dist/venues/chainlink/feeds.js +9 -0
  300. package/dist/venues/chainlink/feeds.js.map +1 -0
  301. package/dist/venues/hyperliquid/account-value.d.ts +30 -0
  302. package/dist/venues/hyperliquid/account-value.js +30 -0
  303. package/dist/venues/hyperliquid/account-value.js.map +1 -0
  304. package/dist/venues/hyperliquid/client.d.ts +63 -0
  305. package/dist/venues/hyperliquid/client.js +135 -0
  306. package/dist/venues/hyperliquid/client.js.map +1 -0
  307. package/dist/venues/hyperliquid/grid.d.ts +36 -0
  308. package/dist/venues/hyperliquid/grid.js +61 -0
  309. package/dist/venues/hyperliquid/grid.js.map +1 -0
  310. package/dist/venues/hyperliquid/types.d.ts +81 -0
  311. package/dist/venues/hyperliquid/types.js +8 -0
  312. package/dist/venues/hyperliquid/types.js.map +1 -0
  313. package/dist/withdraw/run-withdraw-plan.js +2 -0
  314. package/dist/withdraw/run-withdraw-plan.js.map +1 -1
  315. package/docker-compose.yml +44 -0
  316. package/package.json +12 -1
  317. package/skills/jinn-operator/SKILL.md +85 -0
@@ -0,0 +1,710 @@
1
+ /**
2
+ * claude-mcp-hyperliquid — Reference RestorerImpl for portfolio.v0 — §8.
3
+ *
4
+ * Spawns Claude Code session(s) with HL-specific MCP tools (§8.2).
5
+ * Safety rails enforced at the tool boundary (§8.3).
6
+ * Sequential session cadence per §8.4.
7
+ * API wallet managed in implStateDir (§8.1).
8
+ *
9
+ * OPERATOR PREREQUISITE (v0):
10
+ * Before this impl can place trades, the operator must:
11
+ * 1. Let the daemon boot once (or call canAttempt()) to generate the API wallet.
12
+ * 2. Approve the generated API wallet address on their HL master account
13
+ * (Settings → API Wallets → Add in the HL UI).
14
+ * 3. Set approved:true in <implStateDir>/api-wallet.json
15
+ *
16
+ * The impl will surface the wallet address and instructions in canAttempt()
17
+ * if the wallet is not yet approved.
18
+ *
19
+ * Programmatic approval is a §8.5 future item.
20
+ */
21
+ import { writeFileSync, mkdirSync, chmodSync, existsSync } from 'node:fs';
22
+ import { join } from 'node:path';
23
+ import { HyperliquidClient, HL_MAINNET_BASE_URL, HL_TESTNET_BASE_URL } from '../../../venues/hyperliquid/client.js';
24
+ import { getUnifiedAccountValue } from '../../../venues/hyperliquid/account-value.js';
25
+ import { PortfolioV0IntentSchema, PortfolioV0EligibilitySchema, } from '../../../types/portfolio.js';
26
+ import { equityCurve, equityReturnPct, maxDrawdownPct, closedTradesCount, tradedNotionalMultiple, } from '../portfolio-v0-evaluator/canonical-metrics.js';
27
+ import { provisionApiWallet, loadApiWalletState, markApiWalletApproved, saveApiWalletState, walletStatePath, } from './api-wallet.js';
28
+ import { buildHlTools } from './mcp-tools.js';
29
+ import { createRateLimitState, DEFAULT_SAFETY_CONFIG as DEFAULT_SAFETY_CONFIG_IMPORTED } from './safety-rails.js';
30
+ import { runSessionLoop } from './session-orchestrator.js';
31
+ const DEFAULT_IMPL_STATE_DIR = '/tmp/jinn-engine-impl-state/claude-mcp-hyperliquid';
32
+ // ── Impl ───────────────────────────────────────────────────────────────────────
33
+ export class ClaudeMcpHyperliquidImpl {
34
+ name = 'claude-mcp-hyperliquid';
35
+ version = '1.0.0';
36
+ config;
37
+ constructor(config = {}) {
38
+ this.config = config;
39
+ }
40
+ supports(ctx) {
41
+ return ctx.kind === 'portfolio.v0' && ctx.type !== 'evaluation';
42
+ }
43
+ async canAttempt(intent) {
44
+ if (!intent.spec || intent.spec['kind'] !== 'portfolio.v0') {
45
+ return { ok: false, reason: 'spec.kind is not portfolio.v0' };
46
+ }
47
+ // Validate the intent shape
48
+ const parseResult = PortfolioV0IntentSchema.safeParse(intent);
49
+ if (!parseResult.success) {
50
+ return {
51
+ ok: false,
52
+ reason: `Invalid portfolio.v0 intent: ${parseResult.error.message}`,
53
+ };
54
+ }
55
+ // implStateDir is not available in canAttempt — we can only check
56
+ // if the intent is structurally valid. API wallet check is done at run() time.
57
+ return { ok: true };
58
+ }
59
+ /** Resolve the impl state directory used by enable / readiness flows. */
60
+ resolveImplStateDir() {
61
+ return this.config.implStateDir ?? DEFAULT_IMPL_STATE_DIR;
62
+ }
63
+ async isReady() {
64
+ const implStateDir = this.resolveImplStateDir();
65
+ const wallet = loadApiWalletState(implStateDir);
66
+ if (!wallet) {
67
+ return {
68
+ ready: false,
69
+ reason: 'HL api-wallet not provisioned',
70
+ nextStep: {
71
+ description: 'Run `jinn intents enable portfolio.v0 --hl-master <your-HL-master-address>` to generate the api-wallet and complete HL approval.',
72
+ cli: 'jinn intents enable portfolio.v0 --hl-master 0x...',
73
+ },
74
+ };
75
+ }
76
+ if (!wallet.approved) {
77
+ return {
78
+ ready: false,
79
+ reason: 'HL api-wallet generated but not approved on HL',
80
+ nextStep: {
81
+ description: `Approve address ${wallet.address} on Hyperliquid (Settings → API Wallets → Add), then re-run \`jinn intents enable portfolio.v0 --confirm-approved\`.`,
82
+ cli: 'jinn intents enable portfolio.v0 --confirm-approved',
83
+ url: 'https://app.hyperliquid-testnet.xyz/API',
84
+ },
85
+ };
86
+ }
87
+ if (!wallet.masterAddress) {
88
+ return {
89
+ ready: false,
90
+ reason: 'HL master address not recorded in api-wallet state',
91
+ nextStep: {
92
+ description: 'Re-run enable with the master address to record it: `jinn intents enable portfolio.v0 --hl-master 0x...`.',
93
+ cli: 'jinn intents enable portfolio.v0 --hl-master 0x...',
94
+ },
95
+ };
96
+ }
97
+ return { ready: true };
98
+ }
99
+ enableMetadata() {
100
+ return {
101
+ description: 'portfolio.v0 — 24h managed-trading intent executed against a Hyperliquid master account. Requires you to (1) bring an HL master with USDC, and (2) approve a generated api-wallet as an HL agent.',
102
+ requiredArgs: [
103
+ { name: 'hl-master', description: 'Your Hyperliquid master address (holds USDC; approves the api-wallet).', required: true },
104
+ ],
105
+ externalResources: [
106
+ { name: 'HL Testnet Settings → API Wallets', url: 'https://app.hyperliquid-testnet.xyz/API' },
107
+ { name: 'HL Mainnet Settings → API Wallets', url: 'https://app.hyperliquid.xyz/API' },
108
+ ],
109
+ };
110
+ }
111
+ async onEnable(args) {
112
+ const implStateDir = this.resolveImplStateDir();
113
+ const hlMaster = args['hl-master'];
114
+ const confirmApproved = args['confirm-approved'] !== undefined;
115
+ // Ensure the impl state directory exists so we can write the wallet file.
116
+ if (!existsSync(implStateDir)) {
117
+ mkdirSync(implStateDir, { recursive: true, mode: 0o700 });
118
+ }
119
+ const existing = loadApiWalletState(implStateDir);
120
+ // Already enabled: idempotent no-op.
121
+ if (existing && existing.approved && existing.masterAddress) {
122
+ return {
123
+ status: 'ready',
124
+ details: {
125
+ apiWalletAddress: existing.address,
126
+ masterAddress: existing.masterAddress,
127
+ },
128
+ };
129
+ }
130
+ // First invocation: need --hl-master to know which account this wallet belongs to.
131
+ if (!existing && !hlMaster) {
132
+ return {
133
+ status: 'missing_args',
134
+ required: [
135
+ { name: 'hl-master', description: 'Your Hyperliquid master address (holds USDC).', required: true },
136
+ ],
137
+ example: { cli: 'jinn intents enable portfolio.v0 --hl-master 0x...' },
138
+ };
139
+ }
140
+ // State transition 1: no wallet yet — generate it and tell the agent to have the operator approve on HL.
141
+ if (!existing) {
142
+ const wallet = provisionApiWallet(implStateDir);
143
+ // Record the master so we don't re-ask on the next invocation.
144
+ saveApiWalletState(implStateDir, { ...wallet, masterAddress: hlMaster });
145
+ return {
146
+ status: 'waiting_for_external_action',
147
+ action: {
148
+ description: `API wallet ${wallet.address} generated. Open the Hyperliquid UI, go to Settings → API Wallets → Add, and approve this address under your master ${hlMaster}. Once done, re-run this command with --confirm-approved.`,
149
+ url: 'https://app.hyperliquid-testnet.xyz/API',
150
+ },
151
+ details: {
152
+ apiWalletAddress: wallet.address,
153
+ masterAddress: hlMaster,
154
+ walletStatePath: walletStatePath(implStateDir),
155
+ },
156
+ nextInvocation: {
157
+ cli: 'jinn intents enable portfolio.v0 --confirm-approved',
158
+ purpose: 'Record the operator-confirmed HL approval and enable portfolio.v0 claims.',
159
+ },
160
+ };
161
+ }
162
+ // State transition 2: wallet exists, not yet confirmed — if --confirm-approved is passed, flip the flag.
163
+ if (!existing.approved && !confirmApproved) {
164
+ return {
165
+ status: 'waiting_for_external_action',
166
+ action: {
167
+ description: `Approve api-wallet address ${existing.address} on Hyperliquid under master ${existing.masterAddress ?? hlMaster ?? '<set via --hl-master>'}, then re-run with --confirm-approved.`,
168
+ url: 'https://app.hyperliquid-testnet.xyz/API',
169
+ },
170
+ details: {
171
+ apiWalletAddress: existing.address,
172
+ masterAddress: existing.masterAddress ?? hlMaster,
173
+ },
174
+ nextInvocation: {
175
+ cli: 'jinn intents enable portfolio.v0 --confirm-approved',
176
+ purpose: 'Record the operator-confirmed HL approval.',
177
+ },
178
+ };
179
+ }
180
+ // State transition 3: --confirm-approved passed — persist approval and optionally update the master.
181
+ const updated = markApiWalletApproved(implStateDir);
182
+ const resolvedMaster = hlMaster ?? updated.masterAddress;
183
+ if (!resolvedMaster) {
184
+ return {
185
+ status: 'missing_args',
186
+ required: [
187
+ { name: 'hl-master', description: 'Master address was never recorded; pass --hl-master now.', required: true },
188
+ ],
189
+ example: { cli: 'jinn intents enable portfolio.v0 --hl-master 0x... --confirm-approved' },
190
+ };
191
+ }
192
+ if (updated.masterAddress !== resolvedMaster) {
193
+ saveApiWalletState(implStateDir, { ...updated, masterAddress: resolvedMaster });
194
+ }
195
+ return {
196
+ status: 'ready',
197
+ details: {
198
+ apiWalletAddress: updated.address,
199
+ masterAddress: resolvedMaster,
200
+ },
201
+ };
202
+ }
203
+ async onDisable() {
204
+ // Intentionally no-op on key material: operators may re-enable later
205
+ // and re-generating the wallet would force a fresh HL approval round-trip.
206
+ // The config-level disable (removing the impl from `restorers.disabled`)
207
+ // is handled by the `jinn intents disable` verb, not the impl itself.
208
+ }
209
+ async run(ctx) {
210
+ const { intent, implStateDir, workingDir, log, abort, msUntilEndTs } = ctx;
211
+ const testDeps = this.config._testDeps;
212
+ log({ level: 'info', msg: 'claude-mcp-hyperliquid: starting', data: { implStateDir, workingDir } });
213
+ // ── Parse intent ──────────────────────────────────────────────────────────
214
+ const portfolioIntent = PortfolioV0IntentSchema.parse(intent);
215
+ const { masterAddress, venue } = portfolioIntent.spec.account;
216
+ const eligibility = PortfolioV0EligibilitySchema.parse(portfolioIntent.eligibility ?? {});
217
+ const window = portfolioIntent.window;
218
+ // ── Select HL client ──────────────────────────────────────────────────────
219
+ const hlBaseUrl = venue === 'hyperliquid-mainnet' ? HL_MAINNET_BASE_URL : HL_TESTNET_BASE_URL;
220
+ const hlClient = testDeps?.hlClient ?? new HyperliquidClient(hlBaseUrl);
221
+ // ── Provision API wallet ──────────────────────────────────────────────────
222
+ const walletState = provisionApiWallet(implStateDir);
223
+ if (!walletState.approved && !testDeps) {
224
+ log({
225
+ level: 'warn',
226
+ msg: 'claude-mcp-hyperliquid: API wallet not approved — write tools will fail. Operator action required.',
227
+ data: {
228
+ apiWalletAddress: walletState.address,
229
+ instructions: [
230
+ '1. Go to Hyperliquid Settings → API Wallets → Add',
231
+ `2. Approve address: ${walletState.address}`,
232
+ '3. Set approved:true in <implStateDir>/api-wallet.json',
233
+ ],
234
+ },
235
+ });
236
+ // Continue anyway — read-only sessions still work; write tools will fail at the HL exchange layer
237
+ }
238
+ // Sanity check: the agent key stored in api-wallet.json was approved by a
239
+ // specific master. HL routes trades to THAT master, regardless of what the
240
+ // intent's spec says. If the operator switched masters (new wallet, new
241
+ // approval) without updating the intent, every trade would silently land
242
+ // on the wrong account and pre/post snapshots would read the wrong equity.
243
+ // Fail fast with a clear remedy.
244
+ if (!testDeps
245
+ && walletState.masterAddress !== undefined
246
+ && walletState.masterAddress.toLowerCase() !== masterAddress.toLowerCase()) {
247
+ throw new Error(`E_MASTER_MISMATCH: api-wallet.json says the agent ${walletState.address} is approved by ` +
248
+ `${walletState.masterAddress}, but the intent's spec.account.masterAddress is ${masterAddress}. ` +
249
+ `HL would route trades to the agent's approver (${walletState.masterAddress}) while pre/post ` +
250
+ `snapshots would read from the intent's master (${masterAddress}) — silent fund-on-the-wrong-account bug. ` +
251
+ `Remedy: either (a) update ~/.jinn-client/portfolio-v0-intent.json to use masterAddress=${walletState.masterAddress}, ` +
252
+ `or (b) approve the agent on the intent's master (${masterAddress}) via the HL UI and update ${implStateDir}/api-wallet.json.`);
253
+ }
254
+ // ── Take pre-snapshot ─────────────────────────────────────────────────────
255
+ //
256
+ // Pre/post snapshots source equity from HL's unified view: perps
257
+ // `marginSummary.accountValue` + spot USDC balance. This matches the
258
+ // `portfolio` endpoint's `accountValueHistory` (which reports the unified
259
+ // figure too), so restorer-claimed equity and evaluator-rederived grid
260
+ // points compare cleanly — and accounts parked on spot are not ignored.
261
+ log({ level: 'info', msg: 'claude-mcp-hyperliquid: taking pre-snapshot' });
262
+ const preUnified = await getUnifiedAccountValue(hlClient, masterAddress);
263
+ const preSnapshot = {
264
+ capturedAt: Date.now(),
265
+ hlTime: preUnified.clearinghouseState.time,
266
+ payload: buildSnapshotPayload(preUnified),
267
+ };
268
+ log({
269
+ level: 'info',
270
+ msg: 'claude-mcp-hyperliquid: pre-snapshot captured',
271
+ data: {
272
+ accountValue: preUnified.accountValue,
273
+ perpsAccountValue: preUnified.perpsAccountValue,
274
+ spotUsdc: preUnified.spotUsdc,
275
+ },
276
+ });
277
+ // ── Set up session infrastructure ─────────────────────────────────────────
278
+ const writeOps = [];
279
+ const rateLimitState = createRateLimitState();
280
+ // Build HL tools (shared across sessions via closure)
281
+ const hlTools = buildHlTools({
282
+ hlClient,
283
+ hlBaseUrl,
284
+ apiWalletPrivateKey: walletState.privateKey,
285
+ apiWalletAddress: walletState.address,
286
+ masterAddress,
287
+ safetyConfig: this.config.safetyConfig
288
+ ? { ...DEFAULT_SAFETY_CONFIG_IMPORTED, ...this.config.safetyConfig }
289
+ : undefined,
290
+ rateLimitState,
291
+ onWriteOp: (op) => writeOps.push(op),
292
+ });
293
+ // ── MCP config for Claude sessions ────────────────────────────────────────
294
+ // The HL tools are served via an in-process MCP server written to a temp script.
295
+ // We create a per-run MCP config that includes both:
296
+ // 1. The existing jinn-client MCP server (tool: submit_restoration_result etc)
297
+ // 2. A new HL-specific MCP server (tool: hl_*)
298
+ //
299
+ // The HL server is an inline node script that creates a McpServer with the tools.
300
+ // We write it to workingDir/mcp/hl-server.ts and reference it in the config.
301
+ const hlMcpServerPath = join(workingDir, 'mcp', 'hl-server.mjs');
302
+ // Config file path — written by _writeHlMcpServerScript alongside the script
303
+ const hlMcpConfigPath = join(workingDir, 'mcp', 'hl-server-config.json');
304
+ mkdirSync(join(workingDir, 'mcp'), { recursive: true });
305
+ _writeHlMcpServerScript(hlMcpServerPath, {
306
+ hlBaseUrl,
307
+ apiWalletPrivateKey: walletState.privateKey,
308
+ apiWalletAddress: walletState.address,
309
+ masterAddress,
310
+ safetyConfig: this.config.safetyConfig,
311
+ });
312
+ const { command: jinnMcpCommand, args: jinnMcpArgs } = _resolveJinnMcpLauncher();
313
+ const mcpConfigPath = join(workingDir, 'mcp', 'mcp-config.json');
314
+ writeFileSync(mcpConfigPath, JSON.stringify({
315
+ mcpServers: {
316
+ 'jinn-client': {
317
+ command: jinnMcpCommand,
318
+ args: jinnMcpArgs,
319
+ env: {
320
+ DESIRED_STATE_ID: intent.id,
321
+ DESIRED_STATE_DESCRIPTION: intent.description,
322
+ DESIRED_STATE_CONTEXT: intent.context ? JSON.stringify(intent.context) : '',
323
+ DESIRED_STATE_TYPE: intent.type ?? '',
324
+ RESTORATION_REQUEST_ID: intent.restorationRequestId ?? '',
325
+ REQUEST_ID: intent.restorationRequestId ?? '',
326
+ },
327
+ },
328
+ 'jinn-hl': {
329
+ command: process.execPath,
330
+ // Pass config file path as argv[2] — private key is in config file, not script body
331
+ args: [hlMcpServerPath, hlMcpConfigPath],
332
+ },
333
+ },
334
+ }));
335
+ // ── Session loop ──────────────────────────────────────────────────────────
336
+ const rationale = [];
337
+ let allSessions = [];
338
+ if (testDeps?.runSession) {
339
+ // Test mode: use injected runner instead of spawning Claude
340
+ allSessions = await _runTestSessions(testDeps.runSession, intent, portfolioIntent, workingDir, abort, msUntilEndTs, log, this.config);
341
+ }
342
+ else {
343
+ // Production mode: spawn Claude with MCP config
344
+ const preAccountValue = parseFloat(preUnified.accountValue);
345
+ const loopResult = await runSessionLoop((sessionNum, sessionId) => buildSessionPrompt({
346
+ sessionNum,
347
+ sessionId,
348
+ intent: portfolioIntent,
349
+ preAccountValue,
350
+ msUntilEndTs: msUntilEndTs(),
351
+ }), {
352
+ claudePath: this.config.claudePath ?? 'claude',
353
+ claudeModel: this.config.claudeModel,
354
+ mcpConfigPath,
355
+ workingDir,
356
+ abort,
357
+ msUntilEndTs,
358
+ log,
359
+ getMids: () => hlClient.allMids(),
360
+ // Write a per-session outcome.json as soon as each Claude subprocess
361
+ // exits. Consumed by gather-status.ts / portfolio-v0-build.ts to
362
+ // surface `recentClaudeOutcomes` in /v1/status — operators can see
363
+ // trading activity before the 24h window closes.
364
+ //
365
+ // NOTE: the daemon-side `writeOps` closure was initially used for
366
+ // tool-call counts, but the actual MCP tool invocations happen in
367
+ // Claude's hl-server.mjs SUBPROCESS — the daemon's callback is never
368
+ // wired to that server. So the authoritative counter for real trading
369
+ // is HL's own userFillsByTime: ask HL what filled between the
370
+ // session's start/end (±5s padding for clock skew) and report that.
371
+ onSessionEnd: async (sr) => {
372
+ try {
373
+ const outcomePath = join(workingDir, 'sessions', sr.sessionId, 'outcome.json');
374
+ let fillsInWindow = [];
375
+ let fillsQueryError = null;
376
+ try {
377
+ // Give HL a moment to propagate fills before we query.
378
+ await new Promise(r => setTimeout(r, 2000));
379
+ const { fills } = await hlClient.userFillsByTime(masterAddress, sr.startedAt - 5_000, sr.endedAt + 5_000);
380
+ fillsInWindow = fills.map(f => ({ coin: f.coin, dir: f.dir, oid: f.oid }));
381
+ }
382
+ catch (err) {
383
+ fillsQueryError = err instanceof Error ? err.message : String(err);
384
+ }
385
+ const coinCounts = {};
386
+ for (const f of fillsInWindow) {
387
+ coinCounts[f.coin] = (coinCounts[f.coin] ?? 0) + 1;
388
+ }
389
+ const outcome = {
390
+ schemaVersion: 2,
391
+ requestId: ctx.intent.restorationRequestId ?? ctx.intent.id,
392
+ sessionId: sr.sessionId,
393
+ startedAt: sr.startedAt,
394
+ endedAt: sr.endedAt,
395
+ durationMs: sr.endedAt - sr.startedAt,
396
+ aborted: sr.aborted,
397
+ fillsInSessionWindow: fillsInWindow.length,
398
+ coinCounts,
399
+ ...(fillsQueryError !== null ? { fillsQueryError } : {}),
400
+ };
401
+ writeFileSync(outcomePath, JSON.stringify(outcome, null, 2), { encoding: 'utf-8' });
402
+ }
403
+ catch (err) {
404
+ log({
405
+ level: 'warn',
406
+ msg: 'claude-mcp-hyperliquid: failed to write session outcome.json',
407
+ data: { sessionId: sr.sessionId, err: err instanceof Error ? err.message : String(err) },
408
+ });
409
+ }
410
+ },
411
+ config: {
412
+ cadenceMs: this.config.cadenceMs,
413
+ sessionMaxMs: this.config.sessionMaxMs,
414
+ trackedCoins: [], // could be populated from intent context
415
+ },
416
+ });
417
+ allSessions = loopResult.sessions;
418
+ }
419
+ // ── Post-session: match fills to sessions ─────────────────────────────────
420
+ log({ level: 'info', msg: 'claude-mcp-hyperliquid: matching fills to sessions' });
421
+ let windowFills = [];
422
+ try {
423
+ const fillsResult = await hlClient.userFillsByTime(masterAddress, window.startTs, window.endTs);
424
+ windowFills = fillsResult.fills;
425
+ }
426
+ catch (e) {
427
+ log({ level: 'warn', msg: 'claude-mcp-hyperliquid: failed to fetch window fills', data: { err: e instanceof Error ? e.message : String(e) } });
428
+ }
429
+ // Match fills to sessions by timestamp
430
+ for (const session of allSessions) {
431
+ const sessionFillTids = windowFills
432
+ .filter((f) => f.time >= session.startedAt - 5000 && f.time <= session.endedAt + 5000)
433
+ .map((f) => f.tid);
434
+ session.initiatedFillTids = sessionFillTids;
435
+ if (sessionFillTids.length > 0) {
436
+ rationale.push({
437
+ ts: session.startedAt,
438
+ sessionId: session.sessionId,
439
+ note: `Session completed with ${sessionFillTids.length} fills`,
440
+ relatedFillTids: sessionFillTids,
441
+ });
442
+ }
443
+ }
444
+ // ── Take post-snapshot ────────────────────────────────────────────────────
445
+ log({ level: 'info', msg: 'claude-mcp-hyperliquid: taking post-snapshot' });
446
+ let postUnified;
447
+ try {
448
+ postUnified = await getUnifiedAccountValue(hlClient, masterAddress);
449
+ }
450
+ catch (e) {
451
+ log({ level: 'error', msg: 'claude-mcp-hyperliquid: failed to take post-snapshot', data: { err: e instanceof Error ? e.message : String(e) } });
452
+ // Use pre-snapshot as fallback
453
+ postUnified = preUnified;
454
+ }
455
+ const postSnapshot = {
456
+ capturedAt: Date.now(),
457
+ hlTime: postUnified.clearinghouseState.time,
458
+ payload: buildSnapshotPayload(postUnified),
459
+ };
460
+ // ── Compute canonical metrics (import from evaluator — §8 task discipline) ─
461
+ const preValue = parseFloat(preUnified.accountValue);
462
+ const postValue = parseFloat(postUnified.accountValue);
463
+ const fillTimes = windowFills.map((f) => f.time);
464
+ const curve = equityCurve(preSnapshot.capturedAt, preValue, postSnapshot.capturedAt, postValue, fillTimes);
465
+ const equityReturn = equityReturnPct(preValue, postValue);
466
+ const drawdown = maxDrawdownPct(curve);
467
+ const closedTrades = closedTradesCount(windowFills);
468
+ const notional = tradedNotionalMultiple(windowFills, preValue);
469
+ log({
470
+ level: 'info',
471
+ msg: 'claude-mcp-hyperliquid: metrics computed',
472
+ data: { equityReturn, drawdown, closedTrades, notional },
473
+ });
474
+ // ── Build artifacts list (transcripts) ────────────────────────────────────
475
+ const artifacts = allSessions.map((session) => ({
476
+ path: `sessions/${session.sessionId}/transcript.txt`,
477
+ role: 'session_transcript',
478
+ metadata: {
479
+ sessionId: session.sessionId,
480
+ startedAt: session.startedAt,
481
+ endedAt: session.endedAt,
482
+ modelId: this.config.claudeModel ?? 'unknown',
483
+ initiatedFillTids: session.initiatedFillTids,
484
+ },
485
+ tags: ['transcript', 'session'],
486
+ access: { kind: 'open' },
487
+ }));
488
+ // ── Return RestorationOutput ──────────────────────────────────────────────
489
+ return {
490
+ venueRef: { name: venue === 'hyperliquid-mainnet' ? 'hyperliquid-mainnet' : 'hyperliquid-testnet' },
491
+ preSnapshot,
492
+ postSnapshot,
493
+ fills: windowFills,
494
+ gating: {
495
+ equityReturnPct: String(equityReturn),
496
+ maxDrawdownPct: String(drawdown),
497
+ closedTradesCount: closedTrades,
498
+ tradedNotionalMultiple: String(notional),
499
+ },
500
+ informational: {
501
+ sessionCount: allSessions.length,
502
+ totalWriteOps: writeOps.length,
503
+ preAccountValue: String(preValue),
504
+ postAccountValue: String(postValue),
505
+ windowFillCount: windowFills.length,
506
+ minReturnPct: portfolioIntent.spec.target.minReturnPct,
507
+ maxDrawdownConstraint: portfolioIntent.spec.constraint.maxDrawdownPct,
508
+ minClosedTrades: eligibility.minClosedTrades,
509
+ minTradedNotionalMultiple: eligibility.minTradedNotionalMultiple,
510
+ },
511
+ artifacts,
512
+ rationale,
513
+ };
514
+ }
515
+ }
516
+ // ── Snapshot payload ──────────────────────────────────────────────────────────
517
+ /**
518
+ * Build the on-manifest snapshot payload from a unified account-value result.
519
+ *
520
+ * The top-level `accountValue` is the canonical figure used for metrics and
521
+ * consistency checks (matches HL's `portfolio` endpoint unified equity).
522
+ * The raw perps and spot payloads are included verbatim for auditability.
523
+ */
524
+ function buildSnapshotPayload(unified) {
525
+ return {
526
+ accountValue: unified.accountValue,
527
+ perpsAccountValue: unified.perpsAccountValue,
528
+ spotUsdc: unified.spotUsdc,
529
+ clearinghouseState: unified.clearinghouseState,
530
+ spotClearinghouseState: unified.spotClearinghouseState,
531
+ };
532
+ }
533
+ function buildSessionPrompt(params) {
534
+ const { sessionNum, sessionId, intent, preAccountValue, msUntilEndTs } = params;
535
+ const minsRemaining = Math.round(msUntilEndTs / 60_000);
536
+ const { minReturnPct } = intent.spec.target;
537
+ const { maxDrawdownPct } = intent.spec.constraint;
538
+ const { masterAddress } = intent.spec.account;
539
+ return `EXECUTE IMMEDIATELY. DO NOT WRITE TEXT BEFORE YOUR FIRST TOOL CALL. DO NOT ASK QUESTIONS.
540
+
541
+ Your first action this turn MUST be a tool call to \`hl_account_unified\`. Begin now.
542
+
543
+ You are an autonomous trader managing a Hyperliquid perpetuals portfolio. Every turn, you call tools, observe results, and decide the next action. You never defer to a human — there is no human in the loop. The only way to satisfy the intent is to open positions, close them, and repeat enough times to hit the targets below.
544
+
545
+ ## Verdict criteria (ALL four must pass for a PASS verdict)
546
+ - Equity return ≥ ${minReturnPct}% over the 24h window
547
+ - Max drawdown ≤ ${maxDrawdownPct}% peak-to-trough
548
+ - At least 20 closed trades
549
+ - Traded notional ≥ 5× starting equity (~$${(preAccountValue * 5).toFixed(2)})
550
+
551
+ Missing any one → REJECTED → zero rewards. Inaction = REJECTED.
552
+
553
+ ## Context
554
+ - Intent: ${intent.description}
555
+ - Master account: ${masterAddress}
556
+ - Starting equity: $${preAccountValue.toFixed(2)}
557
+ - Session ${sessionNum} (${sessionId})
558
+ - Time remaining in window: ${minsRemaining} min
559
+ - You run again in ~30 minutes, or sooner on ≥2% mid-move. Use this session fully.
560
+
561
+ ## Tools you will call (all are MCP tools — call them by name)
562
+ Read (prefer these):
563
+ - \`hl_account_unified\` — PREFERRED single-call read: unified equity (perps+spot), positions, open orders
564
+ - \`hl_all_mids\` — current mid prices for all assets
565
+ - \`hl_user_fills\` — recent fills (check turnover + trade count)
566
+ - \`hl_meta\` — asset metadata (max leverage, size decimals)
567
+ Read (niche):
568
+ - \`hl_clearinghouse_state\` — perps-only snapshot; DO NOT use for decisions (hides spot USDC)
569
+ - \`hl_portfolio\` — historical equity curve
570
+ Write: \`hl_open_position\`, \`hl_close_position\`, \`hl_modify_position\`, \`hl_cancel_orders\`
571
+
572
+ ## Risk rails (tool-enforced — not suggestions)
573
+ - Max 25% of account value per position
574
+ - Max 10× leverage
575
+ - Max 50 bps slippage
576
+ - **Every \`hl_open_position\` MUST include both \`tp\` (take-profit) and \`sl\` (stop-loss)**. The tool rejects bare opens with \`TPSL_REQUIRED\` unless you pass \`bypassRiskRails=true\`. Only bypass for a scalp you will close THIS turn. Between turns you're unprotected — ~30 min of tail risk is enough to break the ${maxDrawdownPct}% drawdown on a 5–10× move.
577
+ - Sensible starting ranges: sl at 1–2% adverse from mid; tp at 1–3% favorable. Example long at mid $2300: \`sl=2265\` (−1.5%), \`tp=2345\` (+2%).
578
+ - The tool validates tp/sl are on the correct sides of mid. A long with sl≥mid or tp≤mid returns \`SL_INVALID\`/\`TP_INVALID\`.
579
+
580
+ ## Your turn (do this sequence in tool calls, not prose)
581
+ 1. \`hl_account_unified\` — one call, gives you unified equity + positions + open orders
582
+ 2. \`hl_all_mids\` — current prices for majors (BTC, ETH, SOL, at minimum)
583
+ 3. \`hl_user_fills\` — recent fills so you know your turnover + trade count so far
584
+ 4. Based on the read tool outputs, open/adjust/close positions with the write tools. Aim for at least 2–3 position actions in this session. You have 20+ trades to hit across the window — aggressive, disciplined turnover is required, not careful inaction.
585
+ 5. Emit a ONE-PARAGRAPH rationale as your final text message after the tool calls. Not before.
586
+
587
+ Begin with \`hl_account_unified\` now. No preamble.`;
588
+ }
589
+ // ── Test session runner ────────────────────────────────────────────────────────
590
+ async function _runTestSessions(runSession, intent, portfolioIntent, workingDir, abort, msUntilEndTs, log, config) {
591
+ const sessions = [];
592
+ let sessionNum = 0;
593
+ // Run a single test session (tests don't need full cadence)
594
+ if (!abort.aborted && msUntilEndTs() > 0) {
595
+ sessionNum++;
596
+ const sessionId = `test-session-${sessionNum}`;
597
+ const startedAt = Date.now();
598
+ const sessionDir = join(workingDir, 'sessions', sessionId);
599
+ mkdirSync(sessionDir, { recursive: true });
600
+ const transcriptPath = join(sessionDir, 'transcript.txt');
601
+ let stdout = '';
602
+ try {
603
+ const result = await runSession(sessionId, `Session ${sessionNum} for ${intent.id}`);
604
+ stdout = result.stdout;
605
+ }
606
+ catch (e) {
607
+ log({ level: 'warn', msg: 'claude-mcp-hyperliquid: test session threw', data: { err: e instanceof Error ? e.message : String(e) } });
608
+ }
609
+ writeFileSync(transcriptPath, stdout || '(test session — no output)');
610
+ sessions.push({
611
+ sessionId,
612
+ startedAt,
613
+ endedAt: Date.now(),
614
+ transcriptPath,
615
+ aborted: abort.aborted,
616
+ stdout,
617
+ initiatedFillTids: [],
618
+ });
619
+ }
620
+ return sessions;
621
+ }
622
+ /**
623
+ * Write a thin ESM wrapper script to disk that delegates to the compiled
624
+ * startMcpServer() from this package. This is spawned as a subprocess by
625
+ * Claude's --mcp-config.
626
+ *
627
+ * Security: the config (including apiWalletPrivateKey) is written to a
628
+ * SEPARATE file (hl-server-config.json) with mode 0o600. The script body
629
+ * receives only the config file path via process.argv[2] — no private key
630
+ * or secret data appears in the script source.
631
+ *
632
+ * The import path is resolved at write-time from __dirname (this module's
633
+ * location) so it always points to the compiled mcp-tools.js alongside this
634
+ * file. The daemon MUST run from a compiled build (dist/) — `yarn build`
635
+ * produces `dist/restorer/impls/claude-mcp-hyperliquid/mcp-tools.js`, which
636
+ * this wrapper imports by absolute path from plain Node. Running from tsx
637
+ * against src/ would leave mcp-tools.js non-existent and cause Claude to
638
+ * silently spawn with no HL tools loaded — hence the explicit existence
639
+ * check and `E_DAEMON_MUST_RUN_FROM_DIST` error below.
640
+ */
641
+ export function _writeHlMcpServerScript(outPath, hlConfig) {
642
+ // Absolute path to the compiled mcp-tools module, alongside this file.
643
+ const mcpToolsPath = join(__dirname, 'mcp-tools.js');
644
+ // Production guardrail: the generated wrapper is loaded by plain Node (the
645
+ // Claude subprocess's --mcp-config launches `node hl-server.mjs`), which
646
+ // can only import `.js`. Running the daemon via tsx against src/ leaves
647
+ // mcp-tools.js non-existent and Claude spawns with zero HL tools — a
648
+ // silent failure that looks like "Claude refused to trade." Skipped under
649
+ // NODE_ENV=test so vitest runs don't need a preceding `yarn build`.
650
+ if (process.env['NODE_ENV'] !== 'test' && !existsSync(mcpToolsPath)) {
651
+ throw new Error(`E_DAEMON_MUST_RUN_FROM_DIST: ${mcpToolsPath} does not exist. ` +
652
+ `The Jinn daemon must run from a compiled build (e.g. \`node dist/bin/jinn.js run\`), ` +
653
+ `not directly via tsx/ts-node against src/. ` +
654
+ `If you're developing, run \`yarn dev\` (build + run alias) instead of \`yarn jinn run\`. ` +
655
+ `If you installed via npm, the compiled artifact ships in the package — reinstall with ` +
656
+ `\`npm install -g @jinn-network/client@latest\` and use the \`jinn\` binary directly.`);
657
+ }
658
+ // Write config (including private key) to a separate file alongside the script,
659
+ // with restricted permissions — private key must not be world-readable.
660
+ const scriptDir = join(outPath, '..');
661
+ const configPath = join(scriptDir, 'hl-server-config.json');
662
+ // Write config with restricted permissions — private key must not be world-readable
663
+ writeFileSync(configPath, JSON.stringify(hlConfig), { encoding: 'utf-8', mode: 0o600 });
664
+ // Explicitly chmod in case the umask relaxed the mode
665
+ chmodSync(configPath, 0o600);
666
+ const script = `#!/usr/bin/env node
667
+ // Auto-generated HL MCP server wrapper — do not edit.
668
+ // Delegates to the compiled mcp-tools module so the real EIP-712 signing path
669
+ // is always active. See: ${mcpToolsPath}
670
+ //
671
+ // The private key is NOT embedded here — it is read from the config file
672
+ // passed as process.argv[2]. The config file is created at mode 0o600.
673
+ //
674
+ // We deliberately do NOT unlink the config file on exit. Each Claude session
675
+ // spawns a fresh hl-server.mjs child (via --mcp-config); deleting the config
676
+ // when session N exits means session N+1 starts with no HL tools and hangs
677
+ // until sessionMaxMs. The working directory is ephemeral — it gets cleared
678
+ // on next attempt — so leaving the mode-0o600 config in place is safe.
679
+ import { readFileSync } from 'node:fs';
680
+ import { startMcpServer } from ${JSON.stringify(mcpToolsPath)};
681
+
682
+ const configPath = process.argv[2];
683
+ if (!configPath) {
684
+ process.stderr.write('Usage: hl-server.mjs <config-file-path>\\n');
685
+ process.exit(1);
686
+ }
687
+
688
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'));
689
+ await startMcpServer(config);
690
+ `.trim();
691
+ writeFileSync(outPath, script, { encoding: 'utf-8', mode: 0o600 });
692
+ }
693
+ // ── Jinn MCP launcher resolution ──────────────────────────────────────────────
694
+ import { dirname } from 'node:path';
695
+ import { fileURLToPath } from 'node:url';
696
+ const __dirname = dirname(fileURLToPath(import.meta.url));
697
+ function _resolveJinnMcpLauncher() {
698
+ const mcpDir = join(__dirname, '..', '..', '..', 'mcp');
699
+ const js = join(mcpDir, 'server.js');
700
+ const ts = join(mcpDir, 'server.ts');
701
+ if (existsSync(js)) {
702
+ return { command: process.execPath, args: [js] };
703
+ }
704
+ if (existsSync(ts)) {
705
+ return { command: process.execPath, args: ['--import', 'tsx', ts] };
706
+ }
707
+ return { command: process.execPath, args: [js] };
708
+ }
709
+ export default ClaudeMcpHyperliquidImpl;
710
+ //# sourceMappingURL=index.js.map