@jinn-network/client 0.1.6 → 0.1.7-canary.17a8ecb8

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 (351) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +67 -1
  3. package/deployments/deployment-jinn-mvi-l1-sepolia-fast.json +23 -4
  4. package/deployments/deployment-jinn-mvi-l1-sepolia.json +23 -4
  5. package/deployments/deployment-jinn-mvi-l2-baseSepolia.json +5 -4
  6. package/dist/adapters/mech/adapter.d.ts +38 -1
  7. package/dist/adapters/mech/adapter.js +268 -57
  8. package/dist/adapters/mech/adapter.js.map +1 -1
  9. package/dist/adapters/mech/contracts.d.ts +17 -4
  10. package/dist/adapters/mech/contracts.js +8 -2
  11. package/dist/adapters/mech/contracts.js.map +1 -1
  12. package/dist/adapters/mech/safe-revert.d.ts +20 -0
  13. package/dist/adapters/mech/safe-revert.js +12 -4
  14. package/dist/adapters/mech/safe-revert.js.map +1 -1
  15. package/dist/adapters/mech/safe.d.ts +6 -2
  16. package/dist/adapters/mech/safe.js +32 -11
  17. package/dist/adapters/mech/safe.js.map +1 -1
  18. package/dist/adapters/mech/types.d.ts +6 -1
  19. package/dist/adapters/mech/types.js.map +1 -1
  20. package/dist/adapters/mech/verdict-code.d.ts +1 -0
  21. package/dist/adapters/mech/verdict-code.js +18 -0
  22. package/dist/adapters/mech/verdict-code.js.map +1 -1
  23. package/dist/api/activity-events-endpoint.d.ts +14 -0
  24. package/dist/api/activity-events-endpoint.js +59 -0
  25. package/dist/api/activity-events-endpoint.js.map +1 -0
  26. package/dist/api/admin-endpoint.d.ts +15 -3
  27. package/dist/api/admin-endpoint.js +24 -2
  28. package/dist/api/admin-endpoint.js.map +1 -1
  29. package/dist/api/bootstrap-endpoint.d.ts +1 -2
  30. package/dist/api/bootstrap-endpoint.js +49 -1
  31. package/dist/api/bootstrap-endpoint.js.map +1 -1
  32. package/dist/api/codex-doctor-endpoint.d.ts +73 -0
  33. package/dist/api/codex-doctor-endpoint.js +177 -0
  34. package/dist/api/codex-doctor-endpoint.js.map +1 -0
  35. package/dist/api/discovery-endpoint.d.ts +1 -0
  36. package/dist/api/discovery-endpoint.js +26 -0
  37. package/dist/api/discovery-endpoint.js.map +1 -1
  38. package/dist/api/fleet-build.d.ts +1 -0
  39. package/dist/api/fleet-build.js +2 -1
  40. package/dist/api/fleet-build.js.map +1 -1
  41. package/dist/api/gather-status.d.ts +14 -0
  42. package/dist/api/gather-status.js +494 -19
  43. package/dist/api/gather-status.js.map +1 -1
  44. package/dist/api/hermes-doctor-endpoint.d.ts +117 -0
  45. package/dist/api/hermes-doctor-endpoint.js +229 -23
  46. package/dist/api/hermes-doctor-endpoint.js.map +1 -1
  47. package/dist/api/launcher-status.d.ts +22 -17
  48. package/dist/api/launcher-status.js +13 -11
  49. package/dist/api/launcher-status.js.map +1 -1
  50. package/dist/api/launcher-tasks.d.ts +1 -1
  51. package/dist/api/launcher-tasks.js +12 -8
  52. package/dist/api/launcher-tasks.js.map +1 -1
  53. package/dist/api/portfolio-v0-build.d.ts +10 -0
  54. package/dist/api/portfolio-v0-build.js +24 -5
  55. package/dist/api/portfolio-v0-build.js.map +1 -1
  56. package/dist/api/prediction-v1-build.d.ts +10 -0
  57. package/dist/api/prediction-v1-build.js +7 -1
  58. package/dist/api/prediction-v1-build.js.map +1 -1
  59. package/dist/api/server.d.ts +31 -1
  60. package/dist/api/server.js +72 -1
  61. package/dist/api/server.js.map +1 -1
  62. package/dist/api/setup-endpoints.d.ts +16 -0
  63. package/dist/api/setup-endpoints.js +89 -135
  64. package/dist/api/setup-endpoints.js.map +1 -1
  65. package/dist/api/setup-retry-endpoint.d.ts +19 -0
  66. package/dist/api/setup-retry-endpoint.js +32 -0
  67. package/dist/api/setup-retry-endpoint.js.map +1 -0
  68. package/dist/api/solvernets-endpoints.d.ts +8 -0
  69. package/dist/api/solvernets-endpoints.js +71 -43
  70. package/dist/api/solvernets-endpoints.js.map +1 -1
  71. package/dist/api/status-build.d.ts +112 -0
  72. package/dist/api/status-build.js +98 -18
  73. package/dist/api/status-build.js.map +1 -1
  74. package/dist/api/task-run-routing.d.ts +7 -0
  75. package/dist/api/task-run-routing.js +12 -0
  76. package/dist/api/task-run-routing.js.map +1 -0
  77. package/dist/api/task-runs-build.d.ts +21 -0
  78. package/dist/api/task-runs-build.js +14 -1
  79. package/dist/api/task-runs-build.js.map +1 -1
  80. package/dist/build-info.json +4 -4
  81. package/dist/build-meta.json +1 -1
  82. package/dist/chain-read-errors.d.ts +10 -0
  83. package/dist/chain-read-errors.js +15 -0
  84. package/dist/chain-read-errors.js.map +1 -1
  85. package/dist/cli/commands/auth.js +1 -1
  86. package/dist/cli/commands/auth.js.map +1 -1
  87. package/dist/cli/commands/create.js +3 -2
  88. package/dist/cli/commands/create.js.map +1 -1
  89. package/dist/cli/commands/doctor.d.ts +2 -0
  90. package/dist/cli/commands/doctor.js +2 -0
  91. package/dist/cli/commands/doctor.js.map +1 -1
  92. package/dist/cli/commands/rewards.js +11 -7
  93. package/dist/cli/commands/rewards.js.map +1 -1
  94. package/dist/cli/commands/solver-nets.js +101 -15
  95. package/dist/cli/commands/solver-nets.js.map +1 -1
  96. package/dist/cli/commands/solver-plugins-block.d.ts +33 -0
  97. package/dist/cli/commands/solver-plugins-block.js +118 -0
  98. package/dist/cli/commands/solver-plugins-block.js.map +1 -0
  99. package/dist/cli/commands/solver-plugins-feedback.d.ts +72 -0
  100. package/dist/cli/commands/solver-plugins-feedback.js +262 -0
  101. package/dist/cli/commands/solver-plugins-feedback.js.map +1 -0
  102. package/dist/cli/commands/solver-plugins-read.d.ts +54 -0
  103. package/dist/cli/commands/solver-plugins-read.js +259 -0
  104. package/dist/cli/commands/solver-plugins-read.js.map +1 -0
  105. package/dist/cli/commands/solver-plugins.d.ts +35 -0
  106. package/dist/cli/commands/solver-plugins.js +399 -2
  107. package/dist/cli/commands/solver-plugins.js.map +1 -1
  108. package/dist/cli/commands/status.js +1 -1
  109. package/dist/cli/commands/status.js.map +1 -1
  110. package/dist/cli/commands/tasks.js +101 -11
  111. package/dist/cli/commands/tasks.js.map +1 -1
  112. package/dist/cli/commands/update.d.ts +10 -0
  113. package/dist/cli/commands/update.js +36 -0
  114. package/dist/cli/commands/update.js.map +1 -1
  115. package/dist/cli/introspection-context.js +5 -0
  116. package/dist/cli/introspection-context.js.map +1 -1
  117. package/dist/cli/task-native-readiness.d.ts +10 -1
  118. package/dist/cli/task-native-readiness.js +30 -6
  119. package/dist/cli/task-native-readiness.js.map +1 -1
  120. package/dist/config.d.ts +273 -235
  121. package/dist/config.js +305 -114
  122. package/dist/config.js.map +1 -1
  123. package/dist/daemon/checkpoint-loop.d.ts +48 -0
  124. package/dist/daemon/checkpoint-loop.js +76 -0
  125. package/dist/daemon/checkpoint-loop.js.map +1 -0
  126. package/dist/daemon/creator.d.ts +1 -1
  127. package/dist/daemon/creator.js +7 -3
  128. package/dist/daemon/creator.js.map +1 -1
  129. package/dist/daemon/daemon.d.ts +22 -0
  130. package/dist/daemon/daemon.js +156 -23
  131. package/dist/daemon/daemon.js.map +1 -1
  132. package/dist/daemon/eviction-loop.d.ts +40 -0
  133. package/dist/daemon/eviction-loop.js +67 -0
  134. package/dist/daemon/eviction-loop.js.map +1 -0
  135. package/dist/daemon/gate-logger.d.ts +9 -0
  136. package/dist/daemon/gate-logger.js +2 -0
  137. package/dist/daemon/gate-logger.js.map +1 -0
  138. package/dist/daemon/jinn-claim-loop-wiring.d.ts +33 -0
  139. package/dist/daemon/jinn-claim-loop-wiring.js +40 -0
  140. package/dist/daemon/jinn-claim-loop-wiring.js.map +1 -0
  141. package/dist/daemon/jinn-claim-loop.d.ts +24 -17
  142. package/dist/daemon/jinn-claim-loop.js +77 -23
  143. package/dist/daemon/jinn-claim-loop.js.map +1 -1
  144. package/dist/daemon/readiness-gate.d.ts +1 -4
  145. package/dist/daemon/readiness-gate.js.map +1 -1
  146. package/dist/daemon/skip-log-dedup.d.ts +69 -0
  147. package/dist/daemon/skip-log-dedup.js +106 -0
  148. package/dist/daemon/skip-log-dedup.js.map +1 -0
  149. package/dist/daemon/spend-cap-gate.d.ts +40 -0
  150. package/dist/daemon/spend-cap-gate.js +46 -0
  151. package/dist/daemon/spend-cap-gate.js.map +1 -0
  152. package/dist/dashboard/assets/index-8yHQgi7p.js +345 -0
  153. package/dist/dashboard/assets/index-BOBhJ76-.css +32 -0
  154. package/dist/dashboard/index.html +2 -2
  155. package/dist/discovery/factory.d.ts +17 -5
  156. package/dist/discovery/factory.js +46 -18
  157. package/dist/discovery/factory.js.map +1 -1
  158. package/dist/discovery/http.js +142 -3
  159. package/dist/discovery/http.js.map +1 -1
  160. package/dist/discovery/onchain.d.ts +5 -0
  161. package/dist/discovery/onchain.js +407 -15
  162. package/dist/discovery/onchain.js.map +1 -1
  163. package/dist/discovery/types.d.ts +45 -1
  164. package/dist/discovery/types.js +8 -10
  165. package/dist/discovery/types.js.map +1 -1
  166. package/dist/discovery/with-fallback.d.ts +7 -0
  167. package/dist/discovery/with-fallback.js +10 -0
  168. package/dist/discovery/with-fallback.js.map +1 -1
  169. package/dist/earning/bootstrap.d.ts +92 -1
  170. package/dist/earning/bootstrap.js +203 -63
  171. package/dist/earning/bootstrap.js.map +1 -1
  172. package/dist/earning/contracts.d.ts +14 -0
  173. package/dist/earning/contracts.js +17 -5
  174. package/dist/earning/contracts.js.map +1 -1
  175. package/dist/earning/funding-plan.js +27 -18
  176. package/dist/earning/funding-plan.js.map +1 -1
  177. package/dist/earning/jinn-rewards.d.ts +46 -0
  178. package/dist/earning/jinn-rewards.js +32 -0
  179. package/dist/earning/jinn-rewards.js.map +1 -1
  180. package/dist/earning/safe-adapter.d.ts +2 -0
  181. package/dist/earning/safe-adapter.js +37 -11
  182. package/dist/earning/safe-adapter.js.map +1 -1
  183. package/dist/earning/store.d.ts +8 -0
  184. package/dist/earning/store.js.map +1 -1
  185. package/dist/earning/testnet-setup-migration.d.ts +12 -0
  186. package/dist/earning/testnet-setup-migration.js +27 -1
  187. package/dist/earning/testnet-setup-migration.js.map +1 -1
  188. package/dist/earning/types.d.ts +21 -6
  189. package/dist/earning/viem-clients.d.ts +11 -4
  190. package/dist/earning/viem-clients.js +14 -5
  191. package/dist/earning/viem-clients.js.map +1 -1
  192. package/dist/erc8004/reputation.d.ts +8 -0
  193. package/dist/erc8004/reputation.js +22 -3
  194. package/dist/erc8004/reputation.js.map +1 -1
  195. package/dist/events/types.d.ts +2 -2
  196. package/dist/harnesses/cost-estimates.d.ts +145 -0
  197. package/dist/harnesses/cost-estimates.js +297 -0
  198. package/dist/harnesses/cost-estimates.js.map +1 -0
  199. package/dist/harnesses/engine/engine.d.ts +72 -0
  200. package/dist/harnesses/engine/engine.js +118 -8
  201. package/dist/harnesses/engine/engine.js.map +1 -1
  202. package/dist/harnesses/engine/persistence.d.ts +51 -1
  203. package/dist/harnesses/engine/persistence.js +118 -5
  204. package/dist/harnesses/engine/persistence.js.map +1 -1
  205. package/dist/harnesses/engine/work-dir-reaper.d.ts +65 -0
  206. package/dist/harnesses/engine/work-dir-reaper.js +100 -0
  207. package/dist/harnesses/engine/work-dir-reaper.js.map +1 -0
  208. package/dist/harnesses/impls/hermes-agent/adapter.js +40 -0
  209. package/dist/harnesses/impls/hermes-agent/adapter.js.map +1 -1
  210. package/dist/harnesses/impls/hermes-agent/bootstrap.d.ts +20 -0
  211. package/dist/harnesses/impls/hermes-agent/bootstrap.js +40 -6
  212. package/dist/harnesses/impls/hermes-agent/bootstrap.js.map +1 -1
  213. package/dist/harnesses/impls/hermes-agent/harness.d.ts +59 -1
  214. package/dist/harnesses/impls/hermes-agent/harness.js +104 -0
  215. package/dist/harnesses/impls/hermes-agent/harness.js.map +1 -1
  216. package/dist/harnesses/impls/index.d.ts +7 -0
  217. package/dist/harnesses/impls/index.js +16 -1
  218. package/dist/harnesses/impls/index.js.map +1 -1
  219. package/dist/harnesses/impls/learner/harness.d.ts +38 -4
  220. package/dist/harnesses/impls/learner/harness.js +96 -2
  221. package/dist/harnesses/impls/learner/harness.js.map +1 -1
  222. package/dist/harnesses/impls/learner/plugin-path.d.ts +0 -13
  223. package/dist/harnesses/impls/learner/plugin-path.js +35 -15
  224. package/dist/harnesses/impls/learner/plugin-path.js.map +1 -1
  225. package/dist/harnesses/impls/learner/types.d.ts +11 -0
  226. package/dist/harnesses/impls/stub.d.ts +58 -0
  227. package/dist/harnesses/impls/stub.js +89 -0
  228. package/dist/harnesses/impls/stub.js.map +1 -0
  229. package/dist/harnesses/impls/swe-rebench-v2-evaluator/eval-runner.d.ts +69 -50
  230. package/dist/harnesses/impls/swe-rebench-v2-evaluator/eval-runner.js +178 -93
  231. package/dist/harnesses/impls/swe-rebench-v2-evaluator/eval-runner.js.map +1 -1
  232. package/dist/harnesses/impls/swe-rebench-v2-evaluator/harness.d.ts +12 -1
  233. package/dist/harnesses/impls/swe-rebench-v2-evaluator/harness.js +121 -7
  234. package/dist/harnesses/impls/swe-rebench-v2-evaluator/harness.js.map +1 -1
  235. package/dist/harnesses/impls/swe-rebench-v2-evaluator/hf-fetcher.d.ts +88 -4
  236. package/dist/harnesses/impls/swe-rebench-v2-evaluator/hf-fetcher.js +143 -22
  237. package/dist/harnesses/impls/swe-rebench-v2-evaluator/hf-fetcher.js.map +1 -1
  238. package/dist/harnesses/impls/swe-rebench-v2-evaluator/index.d.ts +6 -0
  239. package/dist/harnesses/impls/swe-rebench-v2-evaluator/index.js +1 -1
  240. package/dist/harnesses/impls/swe-rebench-v2-evaluator/index.js.map +1 -1
  241. package/dist/harnesses/readiness-registry.js +9 -1
  242. package/dist/harnesses/readiness-registry.js.map +1 -1
  243. package/dist/main.js +413 -111
  244. package/dist/main.js.map +1 -1
  245. package/dist/observability/emit-event.d.ts +3 -2
  246. package/dist/observability/emit-event.js +22 -1
  247. package/dist/observability/emit-event.js.map +1 -1
  248. package/dist/operator-errors.d.ts +7 -0
  249. package/dist/operator-errors.js +13 -1
  250. package/dist/operator-errors.js.map +1 -1
  251. package/dist/plugins/learner/.claude-plugin/plugin.json +9 -0
  252. package/dist/plugins/learner/.codex-plugin/plugin.json +39 -0
  253. package/dist/plugins/learner/AGENTS.md +40 -0
  254. package/dist/plugins/learner/CLAUDE.md +33 -0
  255. package/dist/plugins/learner/README.md +59 -0
  256. package/dist/plugins/learner/hooks/hooks.json +16 -0
  257. package/dist/plugins/learner/hooks/session-start +38 -0
  258. package/dist/plugins/learner/skills/learn/SKILL.md +412 -0
  259. package/dist/plugins/learner/skills/learn/analyst-prompt.md +68 -0
  260. package/dist/plugins/learner/skills/learn/consolidator-prompt.md +94 -0
  261. package/dist/plugins/learner/skills/learn/explorer-prompt.md +53 -0
  262. package/dist/plugins/learner/skills/learn/planner-prompt.md +87 -0
  263. package/dist/plugins/learner/skills/learn/promoter-prompt.md +113 -0
  264. package/dist/plugins/learner/skills/learn/step-worker-prompt.md +47 -0
  265. package/dist/plugins/learner/skills/learn/strategist-prompt.md +85 -0
  266. package/dist/preflight/rpc-network.d.ts +40 -0
  267. package/dist/preflight/rpc-network.js +67 -1
  268. package/dist/preflight/rpc-network.js.map +1 -1
  269. package/dist/restart-daemon.d.ts +90 -0
  270. package/dist/restart-daemon.js +95 -0
  271. package/dist/restart-daemon.js.map +1 -0
  272. package/dist/rpc/transport.d.ts +109 -0
  273. package/dist/rpc/transport.js +220 -0
  274. package/dist/rpc/transport.js.map +1 -0
  275. package/dist/scripts/donation-consumption-acceptance.js +7 -28
  276. package/dist/scripts/donation-consumption-acceptance.js.map +1 -1
  277. package/dist/setup/halt-mode.d.ts +14 -0
  278. package/dist/setup/halt-mode.js +17 -0
  279. package/dist/setup/halt-mode.js.map +1 -0
  280. package/dist/solver-nets/prediction-operator-ux.d.ts +1 -2
  281. package/dist/solver-nets/prediction-operator-ux.js +90 -47
  282. package/dist/solver-nets/prediction-operator-ux.js.map +1 -1
  283. package/dist/solver-nets/registry.d.ts +20 -1
  284. package/dist/solver-nets/registry.js +38 -25
  285. package/dist/solver-nets/registry.js.map +1 -1
  286. package/dist/solver-types/_swe-rebench-v2-pool-cache.d.ts +58 -0
  287. package/dist/solver-types/_swe-rebench-v2-pool-cache.js +87 -0
  288. package/dist/solver-types/_swe-rebench-v2-pool-cache.js.map +1 -0
  289. package/dist/solver-types/_swe-rebench-v2-pool.d.ts +9 -2
  290. package/dist/solver-types/_swe-rebench-v2-pool.js +15 -20
  291. package/dist/solver-types/_swe-rebench-v2-pool.js.map +1 -1
  292. package/dist/solver-types/_swe-rebench-v2-substrate.d.ts +1 -0
  293. package/dist/solver-types/_swe-rebench-v2-substrate.js +10 -0
  294. package/dist/solver-types/_swe-rebench-v2-substrate.js.map +1 -1
  295. package/dist/solver-types/_swe-rebench-v2-validated-pool.d.ts +94 -1
  296. package/dist/solver-types/_swe-rebench-v2-validated-pool.js +305 -39
  297. package/dist/solver-types/_swe-rebench-v2-validated-pool.js.map +1 -1
  298. package/dist/solver-types/swe-rebench-v2-auto.d.ts +22 -7
  299. package/dist/solver-types/swe-rebench-v2-auto.js +45 -20
  300. package/dist/solver-types/swe-rebench-v2-auto.js.map +1 -1
  301. package/dist/solver-types/swe-rebench-v2.d.ts +13 -2
  302. package/dist/solver-types/swe-rebench-v2.js +237 -95
  303. package/dist/solver-types/swe-rebench-v2.js.map +1 -1
  304. package/dist/solvernets/daemon-init.d.ts +10 -2
  305. package/dist/solvernets/daemon-init.js +22 -2
  306. package/dist/solvernets/daemon-init.js.map +1 -1
  307. package/dist/solvernets/launched-record-dispatcher.js +35 -7
  308. package/dist/solvernets/launched-record-dispatcher.js.map +1 -1
  309. package/dist/solvernets/store.d.ts +5 -0
  310. package/dist/solvernets/store.js +1 -0
  311. package/dist/solvernets/store.js.map +1 -1
  312. package/dist/spend/credential.d.ts +8 -0
  313. package/dist/spend/credential.js +30 -0
  314. package/dist/spend/credential.js.map +1 -0
  315. package/dist/spend/daemon-config.d.ts +13 -0
  316. package/dist/spend/daemon-config.js +24 -0
  317. package/dist/spend/daemon-config.js.map +1 -0
  318. package/dist/spend/pricing.d.ts +16 -0
  319. package/dist/spend/pricing.js +26 -0
  320. package/dist/spend/pricing.js.map +1 -0
  321. package/dist/spend/record.d.ts +13 -0
  322. package/dist/spend/record.js +30 -0
  323. package/dist/spend/record.js.map +1 -0
  324. package/dist/spend/usage.d.ts +27 -0
  325. package/dist/spend/usage.js +113 -0
  326. package/dist/spend/usage.js.map +1 -0
  327. package/dist/store/store.d.ts +43 -0
  328. package/dist/store/store.js +236 -7
  329. package/dist/store/store.js.map +1 -1
  330. package/dist/tasks/sources.d.ts +18 -1
  331. package/dist/tasks/sources.js +33 -5
  332. package/dist/tasks/sources.js.map +1 -1
  333. package/dist/trajectory/transcript-parsers/types.d.ts +8 -8
  334. package/dist/tx-retry.d.ts +166 -19
  335. package/dist/tx-retry.js +310 -32
  336. package/dist/tx-retry.js.map +1 -1
  337. package/dist/types/payloads/prediction-apy-v0.d.ts +5 -5
  338. package/dist/types/payloads/prediction-v0.d.ts +5 -5
  339. package/dist/types/task-document.d.ts +392 -0
  340. package/dist/types/task-document.js +10 -0
  341. package/dist/types/task-document.js.map +1 -1
  342. package/dist/types/task.d.ts +28 -0
  343. package/dist/util/extract-tx-hash.d.ts +14 -0
  344. package/dist/util/extract-tx-hash.js +19 -0
  345. package/dist/util/extract-tx-hash.js.map +1 -0
  346. package/dist/vendor/@jinn-network/sdk/dist/contracts.js +1 -1
  347. package/dist/vendor/@jinn-network/sdk/dist/solvernets/manifest-schema.d.ts +3 -0
  348. package/dist/vendor/@jinn-network/sdk/dist/solvernets/manifest-schema.js +1 -0
  349. package/package.json +30 -12
  350. package/dist/dashboard/assets/index-DOlzFN8a.css +0 -32
  351. package/dist/dashboard/assets/index-NkZ7CTAT.js +0 -140
@@ -4,11 +4,23 @@ import { privateKeyToAccount } from 'viem/accounts';
4
4
  import { base, baseSepolia } from 'viem/chains';
5
5
  import { PermanentError, parseTask } from '../../types/index.js';
6
6
  import { createClients } from './safe.js';
7
+ /**
8
+ * Coalesce a string-or-array RPC input down to the head URL for display in
9
+ * error contexts (`formatRpcError` expects a single host). The adapter
10
+ * accepts the full fallback chain at the type level; this helper exists so
11
+ * the error-formatting call sites can keep their old signature.
12
+ */
13
+ function rpcUrlForDisplay(rpcUrl) {
14
+ return Array.isArray(rpcUrl) ? rpcUrl[0] : rpcUrl;
15
+ }
7
16
  import { buildResultPayload, uploadToIpfs, cidToDigestHex, fetchFromIpfs, fetchSignedTaskFromIpfs, fetchSignedEnvelopeFromIpfs, } from './ipfs.js';
8
17
  import { canonicalJson } from '../../harnesses/engine/canonical-json.js';
9
- import { SignedEnvelopeSchema } from '../../types/envelope.js';
18
+ import { normalizeEnvelopeRole, SignedEnvelopeSchema } from '../../types/envelope.js';
10
19
  import { submitTask, claimTask as claimTaskOnchain, claimEvaluation as claimEvaluationOnchain, claimDelivery, getMechDeliveryRate, getTimeoutBounds, decodeTaskCreatedLogs, decodeSolutionDeliveryClaimedLogs, decodeDeliverLogs, findLatestDeliveryDataHexForRequest, getMarketplaceRequestDeliveryMech, getTaskCidDigest, callDeliverToMarketplace, canClaimTask, canClaimEvaluation, } from './contracts.js';
20
+ import { isNonRecoverableInnerRevert } from './safe-revert.js';
21
+ import { verdictCodeFromValue } from './verdict-code.js';
11
22
  import { manifestDigestForCid } from './digest.js';
23
+ import { emitStructured } from '../../events/emitter.js';
12
24
  import { withRecoverableRetry } from '../../tx-retry.js';
13
25
  import { formatRpcError } from '../../rpc-error-context.js';
14
26
  import { SOLUTION_ENVELOPE_CID_CONTEXT_KEY, SOLUTION_TASK_CID_CONTEXT_KEY, RESTORATION_TASK_CID_CONTEXT_KEY, } from '../../harnesses/impls/evaluation-context.js';
@@ -16,6 +28,28 @@ import { signTaskV1 } from '../../tasks/signing.js';
16
28
  const ROUTER_REQUEST_CURSOR_CONFIG_KEY = 'mech_router_request_block_cursor_v1';
17
29
  const PENDING_EVALUATION_SOLUTIONS_CONFIG_KEY = 'mech_pending_evaluation_solutions_v1';
18
30
  const DEFAULT_MECH_DELIVER_BACKFILL_LOOKBACK_BLOCKS = 100000n;
31
+ /** Yield to the event loop every N evaluation opportunities so a large retry
32
+ * backlog can't starve the HTTP API mid-cycle. */
33
+ const EVALUATION_RETRY_YIELD_EVERY = 10;
34
+ /**
35
+ * Decide whether a `canClaimEvaluation` failure means the opportunity can NEVER
36
+ * become claimable (terminal, prune it) versus one that could still clear later
37
+ * (transient, keep retrying).
38
+ *
39
+ * Classification is done on the *structured* `revertName` decoded straight from
40
+ * the inner revert data — not by regex-unformatting the operator-facing `reason`
41
+ * string. The format→regex round-trip was fragile: an arg value containing a
42
+ * `(` corrupted the strip, and the `flattenErrorMessage` fallback produced
43
+ * arbitrary text the regex mangled, silently mis-classifying opportunities.
44
+ *
45
+ * A false-keep (re-checking a dead opportunity) only costs one more RPC; a
46
+ * false-prune (dropping a still-claimable opportunity) loses real work — so
47
+ * when in doubt we keep. Anything without a known non-recoverable revert name
48
+ * is treated as transient.
49
+ */
50
+ function isTerminalEvaluationReason(revertName) {
51
+ return isNonRecoverableInnerRevert(revertName);
52
+ }
19
53
  const DEFAULT_ROUTER_LOG_CHUNK_BLOCKS = 9999n;
20
54
  /**
21
55
  * Floor block for the on-chain TaskCreated backlog scan, per chain.
@@ -81,6 +115,17 @@ export class MechAdapter {
81
115
  deliveryBlockCursor = 0n;
82
116
  pendingEvaluations = new Map();
83
117
  observedTasks = new Map();
118
+ /**
119
+ * Read-through cache for `restorationAnnouncementForTaskId` — the restoration
120
+ * task body looked up *while building an evaluation opportunity*. Kept
121
+ * SEPARATE from `observedTasks` (the `watchForTasks` discovery dedup set) on
122
+ * purpose: writing the restoration body into `observedTasks` made the
123
+ * TaskCreated scan skip that taskId as a *restoration* opportunity just
124
+ * because the daemon had built an *evaluation* opportunity for someone
125
+ * else's attempt on it. That blocked the creator's own daemon from claiming
126
+ * its own attempt on a multi-attempt (`maxClaims > 1`) task it posted.
127
+ */
128
+ restorationBodyCache = new Map();
84
129
  requestKinds = new Map();
85
130
  claimedRestorationTaskIds = new Set();
86
131
  evaluationOpportunities = new Map();
@@ -104,7 +149,7 @@ export class MechAdapter {
104
149
  formatRpcError(message, {
105
150
  operation: 'getBlockNumber',
106
151
  chain: this.config.chainId === 84532 ? 'base-sepolia' : 'base',
107
- rpcUrl: this.config.rpcUrl,
152
+ rpcUrl: rpcUrlForDisplay(this.config.rpcUrl),
108
153
  }));
109
154
  },
110
155
  });
@@ -236,6 +281,35 @@ export class MechAdapter {
236
281
  return;
237
282
  this.persistPendingEvaluationSolutions();
238
283
  }
284
+ clearPendingDeliveryRecoveryState(requestId) {
285
+ this.originalStates.delete(requestId);
286
+ this.pendingEvaluations.delete(requestId);
287
+ this.requestKinds.delete(requestId);
288
+ }
289
+ recoveryDeliveryExpirySeconds(requestId) {
290
+ const task = this.originalStates.get(requestId) ?? this.pendingEvaluations.get(requestId);
291
+ const claimPolicy = task?.claimPolicy ?? DEFAULT_MECH_CLAIM_POLICY;
292
+ const normalizeTsToSeconds = (value) => {
293
+ if (value == null)
294
+ return undefined;
295
+ return value > 10_000_000_000 ? Math.floor(value / 1000) : value;
296
+ };
297
+ const submissionDeadlineSeconds = normalizeTsToSeconds(claimPolicy.submissionDeadlineTs);
298
+ if (submissionDeadlineSeconds != null)
299
+ return submissionDeadlineSeconds;
300
+ const claimWindowEndSeconds = normalizeTsToSeconds(claimPolicy.claimWindowEndTs ?? task?.window?.endTs);
301
+ if (claimWindowEndSeconds == null)
302
+ return undefined;
303
+ return claimWindowEndSeconds + claimPolicy.claimLeaseTtlSeconds;
304
+ }
305
+ shouldSkipExpiredRecoveryDelivery(requestId, currentChainTimestampSeconds, recoveryExpirySeconds) {
306
+ if (currentChainTimestampSeconds <= recoveryExpirySeconds)
307
+ return false;
308
+ console.error(`[mech] skipping recovery delivery for ${requestId}: ` +
309
+ `submission deadline expired at ${new Date(recoveryExpirySeconds * 1000).toISOString()}`);
310
+ this.clearPendingDeliveryRecoveryState(requestId);
311
+ return true;
312
+ }
239
313
  async postTask(state) {
240
314
  const restorationState = {
241
315
  ...state,
@@ -264,18 +338,21 @@ export class MechAdapter {
264
338
  const manifestDigest = keccak256(toBytes(signedTask.solverNetManifestCid));
265
339
  const policy = this.contractPolicyForTask(restorationState);
266
340
  const taskSubmission = await submitTask(this.publicClient, this.walletClient, this.config.safeAddress, this.config.routerAddress, restorationDataHex, manifestDigest, policy, deliveryRate, deliveryRate, maxTimeout, this.config.evictionRecovery);
267
- const announcement = {
268
- taskId: taskSubmission.taskId,
269
- task: {
270
- ...restorationState,
271
- signedTask,
272
- context: { ...(restorationState.context ?? {}), [SOLUTION_TASK_CID_CONTEXT_KEY]: restorationTaskCid },
273
- },
274
- taskCid: restorationTaskCid,
275
- onchainCreationTx: taskSubmission.txHash,
276
- onchainCreationBlock: taskSubmission.blockNumber,
277
- };
278
- this.observedTasks.set(taskSubmission.taskId, announcement);
341
+ // Deliberately do NOT seed `observedTasks` with the task we just posted.
342
+ // `observedTasks` is the dedup set for `watchForTasks`: the on-chain
343
+ // TaskCreated scan skips any taskId already in it (so a task is announced
344
+ // to the engine-watcher at most once). Seeding it here marked the
345
+ // creator's own task as "already announced" before it was ever yielded —
346
+ // which permanently prevented the *creator's own daemon* from discovering,
347
+ // claiming, and solving a task it posted. On a multi-attempt task
348
+ // (`maxClaims > 1`) the creator running the solver role is legitimate
349
+ // (the protocol forbids only self-*evaluation*, and that on a per-attempt
350
+ // basis), and on testnet it is the intended single-operator dogfood path
351
+ // — post → claim → solve → grade → settle from one daemon. The dedup is
352
+ // still correct: it now keys only on tasks `watchForTasks` actually
353
+ // yielded. `restorationAnnouncementForTaskId` re-hydrates from chain/IPFS
354
+ // on a cache miss, so dropping the pre-seed costs at most one redundant
355
+ // fetch if the creator later claims its own task.
279
356
  return {
280
357
  taskId: taskSubmission.taskId,
281
358
  taskCid: restorationTaskCid,
@@ -348,7 +425,14 @@ export class MechAdapter {
348
425
  maxClaimsPerOperator: claimPolicy.maxClaimsPerOperator,
349
426
  policyHook: (claimPolicy.policyHook ?? zeroAddress),
350
427
  evaluationPolicy: {
351
- requiredVerdicts: 1,
428
+ // `requiredVerdicts` defaults to 1 but is overridable via the task's
429
+ // claim policy. A value > 1 opens additional verdict claim slots per
430
+ // attempt; combined with the per-evaluator cap below (1), it
431
+ // guarantees an honest evaluator can still claim and deliver a slot
432
+ // even when other evaluators have squatted some — the structural fix
433
+ // for a shared/adversarial testnet where a non-delivering claimer
434
+ // would otherwise permanently lock a single-slot attempt.
435
+ requiredVerdicts: claimPolicy.requiredVerdicts ?? 1,
352
436
  passThreshold: 1,
353
437
  evaluationDeadline: submissionDeadline + BigInt(claimPolicy.claimLeaseTtlSeconds),
354
438
  maxVerdictsPerEvaluator: 1,
@@ -381,7 +465,11 @@ export class MechAdapter {
381
465
  };
382
466
  }
383
467
  async restorationAnnouncementForTaskId(taskId) {
384
- const cached = this.observedTasks.get(taskId);
468
+ // Read-through `restorationBodyCache` — NOT `observedTasks`. This helper is
469
+ // an evaluation-path lookup of a task's restoration body; caching it into
470
+ // the `watchForTasks` discovery dedup set would suppress the creator's own
471
+ // restoration-claim discovery for the same taskId (see field comment).
472
+ const cached = this.restorationBodyCache.get(taskId) ?? this.observedTasks.get(taskId);
385
473
  if (cached)
386
474
  return cached;
387
475
  const taskCidDigest = await getTaskCidDigest(this.publicClient, this.config.routerAddress, taskId);
@@ -394,7 +482,7 @@ export class MechAdapter {
394
482
  task,
395
483
  taskCid,
396
484
  };
397
- this.observedTasks.set(taskId, announcement);
485
+ this.restorationBodyCache.set(taskId, announcement);
398
486
  return announcement;
399
487
  }
400
488
  async restorationAnnouncementFromDigest(params) {
@@ -465,19 +553,40 @@ export class MechAdapter {
465
553
  continue;
466
554
  }
467
555
  try {
556
+ // Yield every hydrated candidate per cycle rather than returning after
557
+ // the first. The engine-watcher (daemon._runEngineWatcherLoop) is the
558
+ // single point of skip-state truth — when its in-flight admission gate
559
+ // fast-skips a candidate (~30s TTL), that skip state never flows back
560
+ // into the adapter's iteration cursor. Yielding only the first
561
+ // candidate per cycle meant a fast-skipped slot starved every
562
+ // subsequent candidate in the round-robin (`fc05f686`) ordering for
563
+ // the duration of the TTL. By driving the full candidate list per
564
+ // cycle we let the engine apply its gate to each one, preserving the
565
+ // round-robin fairness across joined SolverNets. See task 212 live
566
+ // verification in the fix's commit body.
468
567
  yield await this.restorationAnnouncementFromDigest({
469
568
  taskId: candidate.taskId,
470
569
  taskCidDigest: candidate.taskCidDigest,
471
570
  transactionHash: candidate.createdAtTx,
472
571
  blockNumber: candidate.createdAtBlock,
473
572
  });
474
- return;
475
573
  }
476
574
  catch (err) {
477
575
  console.error(`[mech] failed to hydrate subgraph task ${candidate.taskId}:`, err instanceof Error ? err.message : err);
478
576
  }
479
577
  }
480
578
  }
579
+ /**
580
+ * Look up the Deliver-event envelope CID for a pending evaluation solution.
581
+ *
582
+ * Returns `null` when the Deliver event is not present in the configured
583
+ * lookback window. This is a terminal signal for the caller — re-running the
584
+ * same lookup later is deterministically futile when `solution.blockNumber`
585
+ * is set (toBlock is fixed at the SolutionDeliveryClaimed block), and is
586
+ * monotonically less likely to find the event when toBlock follows chain head
587
+ * (the window slides forward, away from any older Deliver event). Callers
588
+ * should prune the pending solution on `null` rather than retry — see #553.
589
+ */
481
590
  async deliveryEnvelopeCidForSolution(solution) {
482
591
  const deliveryMech = await getMarketplaceRequestDeliveryMech(this.publicClient, this.config.mechMarketplaceAddress, solution.requestId);
483
592
  const toBlock = solution.blockNumber != null
@@ -488,8 +597,7 @@ export class MechAdapter {
488
597
  const fromBlock = toBlock > lookback ? toBlock - lookback : 0n;
489
598
  const deliveryDataHex = await findLatestDeliveryDataHexForRequest(this.publicClient, deliveryMech, solution.requestId, fromBlock, toBlock);
490
599
  if (!deliveryDataHex) {
491
- throw new Error(`No Deliver event data found for solution ${solution.requestId} on mech ${deliveryMech} ` +
492
- `between blocks ${fromBlock} and ${toBlock}`);
600
+ return null;
493
601
  }
494
602
  const digest = deliveryDataHex.startsWith('0x') ? deliveryDataHex.slice(2) : deliveryDataHex;
495
603
  return `f01551220${digest}`;
@@ -500,14 +608,34 @@ export class MechAdapter {
500
608
  this.forgetPendingEvaluationSolution(solution.requestId);
501
609
  return undefined;
502
610
  }
503
- const restoration = await this.restorationAnnouncementForTaskId(solution.taskId);
611
+ // Cheap claimability gate FIRST — before the restoration lookup + IPFS
612
+ // fetch. A backlog of terminal opportunities (finalized / evaluation
613
+ // deadline passed / max verdicts reached) must not pay the expensive
614
+ // restoration-announcement cost on every poll cycle. Terminal reasons are
615
+ // pruned from the working set so the loop never re-scans on-chain history;
616
+ // transient reasons are left in place to be retried next cycle.
504
617
  const claimable = await canClaimEvaluation(this.publicClient, this.config.safeAddress, this.config.routerAddress, solution.taskId, solution.attemptIndex, this.config.mechContractAddress);
505
618
  if (!claimable.ok) {
506
- console.log(`[mech] skipping evaluation opportunity ${solution.requestId} for task ${solution.taskId}/${solution.attemptIndex}: ${claimable.reason}`);
507
- this.forgetPendingEvaluationSolution(solution.requestId);
619
+ const terminal = isTerminalEvaluationReason(claimable.revertName);
620
+ console.log(`[mech] skipping evaluation opportunity ${solution.requestId} for task ${solution.taskId}/${solution.attemptIndex}: ${claimable.reason}` +
621
+ (terminal ? ' (terminal — pruned)' : ' (transient — will retry)'));
622
+ if (terminal) {
623
+ this.forgetPendingEvaluationSolution(solution.requestId);
624
+ }
508
625
  return undefined;
509
626
  }
627
+ const restoration = await this.restorationAnnouncementForTaskId(solution.taskId);
510
628
  const solutionEnvelopeCid = await this.deliveryEnvelopeCidForSolution(solution);
629
+ if (solutionEnvelopeCid == null) {
630
+ // #553: Deliver event is not within the configured lookback window. A
631
+ // retry with the same toBlock cannot reach an older event, so this is
632
+ // terminal — prune so the loop never re-pays the canClaimEvaluation +
633
+ // restoration lookup cost on a deterministically-failing opportunity.
634
+ console.log(`[mech] pruning evaluation opportunity ${solution.requestId} for task ${solution.taskId}/${solution.attemptIndex}: ` +
635
+ `no Deliver event found within configured lookback (terminal — pruned)`);
636
+ this.forgetPendingEvaluationSolution(solution.requestId);
637
+ return undefined;
638
+ }
511
639
  const resultPayload = await fetchFromIpfs(this.config.ipfsGatewayUrl, solutionEnvelopeCid);
512
640
  const resultData = resultPayload.data ?? JSON.stringify(resultPayload);
513
641
  const evaluationTask = this.buildEvaluationTask({
@@ -535,15 +663,21 @@ export class MechAdapter {
535
663
  return announcement;
536
664
  }
537
665
  async *retryPendingEvaluationSolutions() {
666
+ let processed = 0;
538
667
  for (const [requestId, solution] of Array.from(this.pendingEvaluationSolutions)) {
668
+ // Yield to the event loop periodically so a large backlog of pending
669
+ // evaluation solutions can't starve the HTTP API mid-cycle.
670
+ if (processed > 0 && processed % EVALUATION_RETRY_YIELD_EVERY === 0) {
671
+ await new Promise((resolve) => setImmediate(resolve));
672
+ }
673
+ processed++;
539
674
  try {
540
675
  const announcement = await this.evaluationAnnouncementForSolution(solution);
541
676
  if (announcement) {
542
677
  yield announcement;
543
678
  }
544
- else {
545
- this.forgetPendingEvaluationSolution(requestId);
546
- }
679
+ // No announcement does NOT mean "forget" — pruning is owned by
680
+ // evaluationAnnouncementForSolution, which only removes terminal cases.
547
681
  }
548
682
  catch (err) {
549
683
  console.error(`[mech] evaluation opportunity retry failed for ${requestId}:`, err);
@@ -605,7 +739,7 @@ export class MechAdapter {
605
739
  console.error('[mech] Error polling for tasks:', formatRpcError(err, {
606
740
  operation: 'pollTaskCreated',
607
741
  chain: this.config.chainId === 84532 ? 'base-sepolia' : 'base',
608
- rpcUrl: this.config.rpcUrl,
742
+ rpcUrl: rpcUrlForDisplay(this.config.rpcUrl),
609
743
  contract: this.config.routerAddress,
610
744
  fromBlock: this.requestBlockCursor + 1n,
611
745
  }));
@@ -676,9 +810,13 @@ export class MechAdapter {
676
810
  async submitVerdictDelivery(requestId, verdictDigest, verdictCode) {
677
811
  await claimDelivery(this.publicClient, this.walletClient, this.config.safeAddress, this.config.routerAddress, requestId, { variant: 'v3', kind: 'verdict', evidenceHash: verdictDigest, verdictCode }, this.config.evictionRecovery);
678
812
  }
679
- async evidenceHashForDelivery(requestId, deliveryDataHex) {
813
+ async deliveryClaimForDelivery(requestId, deliveryDataHex) {
814
+ const fallbackKind = this.requestKinds.get(requestId) ?? 'solution';
680
815
  if (this.config.routerClaimDeliveryVariant !== 'v2' && this.config.routerClaimDeliveryVariant !== 'v3') {
681
- return undefined;
816
+ return {
817
+ evidenceHash: undefined,
818
+ kind: fallbackKind,
819
+ };
682
820
  }
683
821
  const deliveryDigest = deliveryDataHex.startsWith('0x')
684
822
  ? deliveryDataHex.slice(2)
@@ -686,11 +824,6 @@ export class MechAdapter {
686
824
  const envelopeCid = `f01551220${deliveryDigest}`;
687
825
  const rawEnvelope = await fetchSignedEnvelopeFromIpfs(this.config.ipfsGatewayUrl, envelopeCid);
688
826
  const parsed = SignedEnvelopeSchema.parse(rawEnvelope);
689
- // Strip signature to recompute the hash over the unsigned body.
690
- //
691
- // Important: compute over the fetched wire object, not over the parsed
692
- // schema result. The schema normalizes some nested objects and may strip
693
- // extension metadata that was present when the envelope was signed.
694
827
  const rawSigned = rawEnvelope;
695
828
  const { signature: _rawSignature, ...unsignedBody } = rawSigned;
696
829
  const signature = parsed.signature;
@@ -699,22 +832,36 @@ export class MechAdapter {
699
832
  if (recomputed !== signature.hash) {
700
833
  throw new Error(`recomputed hash ${recomputed} !== envelope.signature.hash ${signature.hash}`);
701
834
  }
702
- return recomputed;
835
+ const role = normalizeEnvelopeRole(parsed.role);
836
+ if (role === 'capture') {
837
+ throw new Error(`unsupported delivery envelope role=capture for requestId ${requestId}`);
838
+ }
839
+ const kind = role === 'verdict' ? 'verdict' : 'solution';
840
+ const payload = rawSigned['payload'];
841
+ const rawVerdict = payload != null && typeof payload === 'object'
842
+ ? payload['verdict']
843
+ : undefined;
844
+ return {
845
+ evidenceHash: recomputed,
846
+ kind,
847
+ verdictCode: kind === 'verdict' ? verdictCodeFromValue(rawVerdict) : undefined,
848
+ };
703
849
  }
704
850
  async ensureDeliveryClaimed(requestId, deliveryDataHex) {
705
- let evidenceHash;
851
+ let claimOptions;
706
852
  try {
707
- evidenceHash = await this.evidenceHashForDelivery(requestId, deliveryDataHex);
853
+ claimOptions = await this.deliveryClaimForDelivery(requestId, deliveryDataHex);
708
854
  }
709
855
  catch (err) {
710
- console.error(`[mech] evidenceHash derivation failed for ${requestId} — skipping claim, will retry on next loop:`, err);
856
+ console.error(`[mech] delivery claim metadata derivation failed for ${requestId} — skipping claim, will retry on next loop:`, err);
711
857
  return 'retry';
712
858
  }
713
859
  try {
714
860
  await claimDelivery(this.publicClient, this.walletClient, this.config.safeAddress, this.config.routerAddress, requestId, {
715
861
  variant: this.config.routerClaimDeliveryVariant,
716
- kind: this.requestKinds.get(requestId) ?? 'solution',
717
- evidenceHash,
862
+ kind: claimOptions.kind,
863
+ evidenceHash: claimOptions.evidenceHash,
864
+ verdictCode: claimOptions.verdictCode,
718
865
  }, this.config.evictionRecovery);
719
866
  return 'claimed';
720
867
  }
@@ -728,22 +875,66 @@ export class MechAdapter {
728
875
  return 'already-claimed';
729
876
  }
730
877
  console.error(`[mech] claimDelivery failed for ${requestId}:`, err);
878
+ // Paired SSE signal for the operator-app `claim_failed` notification
879
+ // (OPERATOR-APP-SPEC §2.10). The early-return branches above
880
+ // (`skipped` / `already-claimed`) are not failures and intentionally do not emit.
881
+ emitStructured({
882
+ kind: 'intent',
883
+ message: 'Delivery claim failed',
884
+ requestId,
885
+ errorCode: 'claim_failed',
886
+ details: {
887
+ kind: claimOptions.kind,
888
+ source: 'mech.claimDelivery',
889
+ error: message,
890
+ },
891
+ });
731
892
  return 'retry';
732
893
  }
733
894
  }
895
+ /**
896
+ * Paginate `getLogs` over `[deliveryBlockCursor+1, currentBlock]` chunked by
897
+ * `DEFAULT_ROUTER_LOG_CHUNK_BLOCKS` to honor RPC provider block-range limits
898
+ * (Tenderly base-sepolia caps at 100k; sepolia.base.org ~1k). Advances +
899
+ * persists `deliveryBlockCursor` per chunk so a mid-scan RPC failure on a
900
+ * later chunk does not strand the cursor at the pre-poll value (#552).
901
+ *
902
+ * Yields each chunk's decoded Deliver entries so the consumer can process
903
+ * them with the live "current block" context (needed for the recovery-
904
+ * delivery timestamp cache).
905
+ */
906
+ async *scanDeliveryLogChunks(currentBlock) {
907
+ while (currentBlock > this.deliveryBlockCursor) {
908
+ const chunkStart = this.deliveryBlockCursor + 1n;
909
+ const chunkEnd = chunkStart + DEFAULT_ROUTER_LOG_CHUNK_BLOCKS > currentBlock
910
+ ? currentBlock
911
+ : chunkStart + DEFAULT_ROUTER_LOG_CHUNK_BLOCKS;
912
+ const logs = await this.publicClient.getLogs({
913
+ address: this.config.mechContractAddress,
914
+ fromBlock: chunkStart,
915
+ toBlock: chunkEnd,
916
+ });
917
+ // Advance + persist BEFORE yielding so partial progress is durable even
918
+ // if a downstream throw escapes back through the for-await consumer.
919
+ this.deliveryBlockCursor = chunkEnd;
920
+ if (this.store) {
921
+ this.store.setLastProcessedBlock(this.deliveryBlockCursor);
922
+ }
923
+ yield decodeDeliverLogs(logs);
924
+ }
925
+ }
734
926
  async *watchForDeliveries() {
735
927
  while (!this.stopped) {
736
928
  try {
737
929
  const currentBlock = await this.publicClient.getBlockNumber();
738
- if (currentBlock > this.deliveryBlockCursor) {
739
- const logs = await this.publicClient.getLogs({
740
- address: this.config.mechContractAddress,
741
- fromBlock: this.deliveryBlockCursor + 1n,
742
- toBlock: currentBlock,
743
- });
744
- this.deliveryBlockCursor = currentBlock;
745
- const decoded = decodeDeliverLogs(logs);
746
- for (const { requestId, deliveryDataHex, mechAddress } of decoded) {
930
+ // Caches scoped to the whole poll iteration: the "current block"
931
+ // reference does not change across chunks.
932
+ const blockTimestampSecondsByNumber = new Map();
933
+ let currentBlockTimestampSeconds;
934
+ for await (const decoded of this.scanDeliveryLogChunks(currentBlock)) {
935
+ if (this.stopped)
936
+ break;
937
+ for (const { requestId, deliveryDataHex, mechAddress, blockNumber } of decoded) {
747
938
  // Two concerns, independent:
748
939
  // (a) Did this Safe DELIVER this? → claim it (counter credit goes to msg.sender)
749
940
  // The Deliver event's mechAddress is mechServiceMultisig (the Safe that owns
@@ -753,6 +944,30 @@ export class MechAdapter {
753
944
  const iCreatedRestoration = this.pendingEvaluations.has(requestId);
754
945
  if (!iDelivered && !iCreatedRestoration)
755
946
  continue;
947
+ if (iCreatedRestoration) {
948
+ const recoveryExpirySeconds = this.recoveryDeliveryExpirySeconds(requestId);
949
+ if (recoveryExpirySeconds != null) {
950
+ let deliveryTimestampSeconds;
951
+ if (blockNumber != null) {
952
+ deliveryTimestampSeconds = blockTimestampSecondsByNumber.get(blockNumber);
953
+ if (deliveryTimestampSeconds == null) {
954
+ const deliveryBlockData = await this.publicClient.getBlock({ blockNumber });
955
+ deliveryTimestampSeconds = Number(deliveryBlockData.timestamp);
956
+ blockTimestampSecondsByNumber.set(blockNumber, deliveryTimestampSeconds);
957
+ }
958
+ }
959
+ else {
960
+ if (currentBlockTimestampSeconds == null) {
961
+ const currentBlockData = await this.publicClient.getBlock({ blockNumber: currentBlock });
962
+ currentBlockTimestampSeconds = Number(currentBlockData.timestamp);
963
+ }
964
+ deliveryTimestampSeconds = currentBlockTimestampSeconds;
965
+ }
966
+ if (this.shouldSkipExpiredRecoveryDelivery(requestId, deliveryTimestampSeconds, recoveryExpirySeconds)) {
967
+ continue;
968
+ }
969
+ }
970
+ }
756
971
  // (a) Deliverer-side claim path: if this Safe delivered the request,
757
972
  // claim it first so router counters credit the deliverer.
758
973
  let deliveryClaimStatus;
@@ -788,9 +1003,7 @@ export class MechAdapter {
788
1003
  deliveryMechAddress: mechAddress,
789
1004
  };
790
1005
  // Clean up after yielding
791
- this.originalStates.delete(requestId);
792
- this.pendingEvaluations.delete(requestId);
793
- this.requestKinds.delete(requestId);
1006
+ this.clearPendingDeliveryRecoveryState(requestId);
794
1007
  }
795
1008
  catch (err) {
796
1009
  console.error(`[mech] Failed to parse delivery ${requestId}:`, err);
@@ -802,15 +1015,13 @@ export class MechAdapter {
802
1015
  console.error('[mech] Error polling for deliveries:', formatRpcError(err, {
803
1016
  operation: 'pollDeliveries',
804
1017
  chain: this.config.chainId === 84532 ? 'base-sepolia' : 'base',
805
- rpcUrl: this.config.rpcUrl,
1018
+ rpcUrl: rpcUrlForDisplay(this.config.rpcUrl),
806
1019
  contract: this.config.mechContractAddress,
807
1020
  fromBlock: this.deliveryBlockCursor + 1n,
808
1021
  }));
809
1022
  }
810
- // Persist block cursor for crash recovery
811
- if (this.store && this.deliveryBlockCursor > 0n) {
812
- this.store.setLastProcessedBlock(this.deliveryBlockCursor);
813
- }
1023
+ // Cursor persistence is per-chunk inside the loop above (#552). A poll
1024
+ // that did no chunked work has no progress to persist.
814
1025
  await new Promise(r => setTimeout(r, this.config.pollIntervalMs));
815
1026
  }
816
1027
  }