@0xsequence/catapult 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (393) hide show
  1. package/.eslintrc.json +29 -0
  2. package/.github/workflows/ci.yml +181 -0
  3. package/CONCEPT.md +24 -0
  4. package/README.md +772 -0
  5. package/contracts/checked-call.huff +65 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +16 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/commands/common.d.ts +11 -0
  11. package/dist/commands/common.d.ts.map +1 -0
  12. package/dist/commands/common.js +73 -0
  13. package/dist/commands/common.js.map +1 -0
  14. package/dist/commands/dry.d.ts +3 -0
  15. package/dist/commands/dry.d.ts.map +1 -0
  16. package/dist/commands/dry.js +171 -0
  17. package/dist/commands/dry.js.map +1 -0
  18. package/dist/commands/etherscan.d.ts +3 -0
  19. package/dist/commands/etherscan.d.ts.map +1 -0
  20. package/dist/commands/etherscan.js +323 -0
  21. package/dist/commands/etherscan.js.map +1 -0
  22. package/dist/commands/index.d.ts +6 -0
  23. package/dist/commands/index.d.ts.map +1 -0
  24. package/dist/commands/index.js +22 -0
  25. package/dist/commands/index.js.map +1 -0
  26. package/dist/commands/list.d.ts +3 -0
  27. package/dist/commands/list.d.ts.map +1 -0
  28. package/dist/commands/list.js +259 -0
  29. package/dist/commands/list.js.map +1 -0
  30. package/dist/commands/run.d.ts +3 -0
  31. package/dist/commands/run.d.ts.map +1 -0
  32. package/dist/commands/run.js +96 -0
  33. package/dist/commands/run.js.map +1 -0
  34. package/dist/commands/utils.d.ts +3 -0
  35. package/dist/commands/utils.d.ts.map +1 -0
  36. package/dist/commands/utils.js +46 -0
  37. package/dist/commands/utils.js.map +1 -0
  38. package/dist/index.d.ts +4 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +58 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/lib/__tests__/deployer-events.spec.d.ts +2 -0
  43. package/dist/lib/__tests__/deployer-events.spec.d.ts.map +1 -0
  44. package/dist/lib/__tests__/deployer-events.spec.js +260 -0
  45. package/dist/lib/__tests__/deployer-events.spec.js.map +1 -0
  46. package/dist/lib/__tests__/deployer.spec.d.ts +2 -0
  47. package/dist/lib/__tests__/deployer.spec.d.ts.map +1 -0
  48. package/dist/lib/__tests__/deployer.spec.js +884 -0
  49. package/dist/lib/__tests__/deployer.spec.js.map +1 -0
  50. package/dist/lib/__tests__/network-utils.spec.d.ts +2 -0
  51. package/dist/lib/__tests__/network-utils.spec.d.ts.map +1 -0
  52. package/dist/lib/__tests__/network-utils.spec.js +140 -0
  53. package/dist/lib/__tests__/network-utils.spec.js.map +1 -0
  54. package/dist/lib/contracts/__tests__/repository.spec.d.ts +2 -0
  55. package/dist/lib/contracts/__tests__/repository.spec.d.ts.map +1 -0
  56. package/dist/lib/contracts/__tests__/repository.spec.js +321 -0
  57. package/dist/lib/contracts/__tests__/repository.spec.js.map +1 -0
  58. package/dist/lib/contracts/repository.d.ts +27 -0
  59. package/dist/lib/contracts/repository.d.ts.map +1 -0
  60. package/dist/lib/contracts/repository.js +241 -0
  61. package/dist/lib/contracts/repository.js.map +1 -0
  62. package/dist/lib/core/__tests__/engine.spec.d.ts +2 -0
  63. package/dist/lib/core/__tests__/engine.spec.d.ts.map +1 -0
  64. package/dist/lib/core/__tests__/engine.spec.js +1212 -0
  65. package/dist/lib/core/__tests__/engine.spec.js.map +1 -0
  66. package/dist/lib/core/__tests__/graph.spec.d.ts +2 -0
  67. package/dist/lib/core/__tests__/graph.spec.d.ts.map +1 -0
  68. package/dist/lib/core/__tests__/graph.spec.js +116 -0
  69. package/dist/lib/core/__tests__/graph.spec.js.map +1 -0
  70. package/dist/lib/core/__tests__/json-integration.spec.d.ts +2 -0
  71. package/dist/lib/core/__tests__/json-integration.spec.d.ts.map +1 -0
  72. package/dist/lib/core/__tests__/json-integration.spec.js +300 -0
  73. package/dist/lib/core/__tests__/json-integration.spec.js.map +1 -0
  74. package/dist/lib/core/__tests__/loader.spec.d.ts +2 -0
  75. package/dist/lib/core/__tests__/loader.spec.d.ts.map +1 -0
  76. package/dist/lib/core/__tests__/loader.spec.js +288 -0
  77. package/dist/lib/core/__tests__/loader.spec.js.map +1 -0
  78. package/dist/lib/core/__tests__/multi-platform-verification.spec.d.ts +2 -0
  79. package/dist/lib/core/__tests__/multi-platform-verification.spec.d.ts.map +1 -0
  80. package/dist/lib/core/__tests__/multi-platform-verification.spec.js +342 -0
  81. package/dist/lib/core/__tests__/multi-platform-verification.spec.js.map +1 -0
  82. package/dist/lib/core/__tests__/resolver.spec.d.ts +2 -0
  83. package/dist/lib/core/__tests__/resolver.spec.d.ts.map +1 -0
  84. package/dist/lib/core/__tests__/resolver.spec.js +1367 -0
  85. package/dist/lib/core/__tests__/resolver.spec.js.map +1 -0
  86. package/dist/lib/core/__tests__/static-action.spec.d.ts +2 -0
  87. package/dist/lib/core/__tests__/static-action.spec.d.ts.map +1 -0
  88. package/dist/lib/core/__tests__/static-action.spec.js +136 -0
  89. package/dist/lib/core/__tests__/static-action.spec.js.map +1 -0
  90. package/dist/lib/core/context.d.ts +29 -0
  91. package/dist/lib/core/context.d.ts.map +1 -0
  92. package/dist/lib/core/context.js +88 -0
  93. package/dist/lib/core/context.js.map +1 -0
  94. package/dist/lib/core/engine.d.ts +25 -0
  95. package/dist/lib/core/engine.d.ts.map +1 -0
  96. package/dist/lib/core/engine.js +1191 -0
  97. package/dist/lib/core/engine.js.map +1 -0
  98. package/dist/lib/core/graph.d.ts +18 -0
  99. package/dist/lib/core/graph.d.ts.map +1 -0
  100. package/dist/lib/core/graph.js +158 -0
  101. package/dist/lib/core/graph.js.map +1 -0
  102. package/dist/lib/core/loader.d.ts +25 -0
  103. package/dist/lib/core/loader.d.ts.map +1 -0
  104. package/dist/lib/core/loader.js +248 -0
  105. package/dist/lib/core/loader.js.map +1 -0
  106. package/dist/lib/core/resolver.d.ts +20 -0
  107. package/dist/lib/core/resolver.d.ts.map +1 -0
  108. package/dist/lib/core/resolver.js +307 -0
  109. package/dist/lib/core/resolver.js.map +1 -0
  110. package/dist/lib/deployer.d.ts +39 -0
  111. package/dist/lib/deployer.d.ts.map +1 -0
  112. package/dist/lib/deployer.js +533 -0
  113. package/dist/lib/deployer.js.map +1 -0
  114. package/dist/lib/events/__tests__/event-system.spec.d.ts +2 -0
  115. package/dist/lib/events/__tests__/event-system.spec.d.ts.map +1 -0
  116. package/dist/lib/events/__tests__/event-system.spec.js +256 -0
  117. package/dist/lib/events/__tests__/event-system.spec.js.map +1 -0
  118. package/dist/lib/events/cli-adapter.d.ts +13 -0
  119. package/dist/lib/events/cli-adapter.d.ts.map +1 -0
  120. package/dist/lib/events/cli-adapter.js +244 -0
  121. package/dist/lib/events/cli-adapter.js.map +1 -0
  122. package/dist/lib/events/emitter.d.ts +11 -0
  123. package/dist/lib/events/emitter.d.ts.map +1 -0
  124. package/dist/lib/events/emitter.js +29 -0
  125. package/dist/lib/events/emitter.js.map +1 -0
  126. package/dist/lib/events/index.d.ts +4 -0
  127. package/dist/lib/events/index.d.ts.map +1 -0
  128. package/dist/lib/events/index.js +20 -0
  129. package/dist/lib/events/index.js.map +1 -0
  130. package/dist/lib/events/types.d.ts +368 -0
  131. package/dist/lib/events/types.d.ts.map +1 -0
  132. package/dist/lib/events/types.js +3 -0
  133. package/dist/lib/events/types.js.map +1 -0
  134. package/dist/lib/index.d.ts +5 -0
  135. package/dist/lib/index.d.ts.map +1 -0
  136. package/dist/lib/index.js +44 -0
  137. package/dist/lib/index.js.map +1 -0
  138. package/dist/lib/network-loader.d.ts +3 -0
  139. package/dist/lib/network-loader.d.ts.map +1 -0
  140. package/dist/lib/network-loader.js +80 -0
  141. package/dist/lib/network-loader.js.map +1 -0
  142. package/dist/lib/network-match.d.ts +3 -0
  143. package/dist/lib/network-match.d.ts.map +1 -0
  144. package/dist/lib/network-match.js +62 -0
  145. package/dist/lib/network-match.js.map +1 -0
  146. package/dist/lib/network-utils.d.ts +4 -0
  147. package/dist/lib/network-utils.d.ts.map +1 -0
  148. package/dist/lib/network-utils.js +39 -0
  149. package/dist/lib/network-utils.js.map +1 -0
  150. package/dist/lib/parsers/__tests__/buildinfo.spec.d.ts +2 -0
  151. package/dist/lib/parsers/__tests__/buildinfo.spec.d.ts.map +1 -0
  152. package/dist/lib/parsers/__tests__/buildinfo.spec.js +132 -0
  153. package/dist/lib/parsers/__tests__/buildinfo.spec.js.map +1 -0
  154. package/dist/lib/parsers/__tests__/job.spec.d.ts +2 -0
  155. package/dist/lib/parsers/__tests__/job.spec.d.ts.map +1 -0
  156. package/dist/lib/parsers/__tests__/job.spec.js +318 -0
  157. package/dist/lib/parsers/__tests__/job.spec.js.map +1 -0
  158. package/dist/lib/parsers/__tests__/template.spec.d.ts +2 -0
  159. package/dist/lib/parsers/__tests__/template.spec.d.ts.map +1 -0
  160. package/dist/lib/parsers/__tests__/template.spec.js +126 -0
  161. package/dist/lib/parsers/__tests__/template.spec.js.map +1 -0
  162. package/dist/lib/parsers/artifact/__tests__/artifact.spec.d.ts +2 -0
  163. package/dist/lib/parsers/artifact/__tests__/artifact.spec.d.ts.map +1 -0
  164. package/dist/lib/parsers/artifact/__tests__/artifact.spec.js +128 -0
  165. package/dist/lib/parsers/artifact/__tests__/artifact.spec.js.map +1 -0
  166. package/dist/lib/parsers/artifact/foundry-1.2.d.ts +3 -0
  167. package/dist/lib/parsers/artifact/foundry-1.2.d.ts.map +1 -0
  168. package/dist/lib/parsers/artifact/foundry-1.2.js +82 -0
  169. package/dist/lib/parsers/artifact/foundry-1.2.js.map +1 -0
  170. package/dist/lib/parsers/artifact/index.d.ts +3 -0
  171. package/dist/lib/parsers/artifact/index.d.ts.map +1 -0
  172. package/dist/lib/parsers/artifact/index.js +17 -0
  173. package/dist/lib/parsers/artifact/index.js.map +1 -0
  174. package/dist/lib/parsers/artifact/types.d.ts +3 -0
  175. package/dist/lib/parsers/artifact/types.d.ts.map +1 -0
  176. package/dist/lib/parsers/artifact/types.js +3 -0
  177. package/dist/lib/parsers/artifact/types.js.map +1 -0
  178. package/dist/lib/parsers/buildinfo.d.ts +5 -0
  179. package/dist/lib/parsers/buildinfo.d.ts.map +1 -0
  180. package/dist/lib/parsers/buildinfo.js +85 -0
  181. package/dist/lib/parsers/buildinfo.js.map +1 -0
  182. package/dist/lib/parsers/constants.d.ts +4 -0
  183. package/dist/lib/parsers/constants.d.ts.map +1 -0
  184. package/dist/lib/parsers/constants.js +45 -0
  185. package/dist/lib/parsers/constants.js.map +1 -0
  186. package/dist/lib/parsers/index.d.ts +5 -0
  187. package/dist/lib/parsers/index.d.ts.map +1 -0
  188. package/dist/lib/parsers/index.js +21 -0
  189. package/dist/lib/parsers/index.js.map +1 -0
  190. package/dist/lib/parsers/job.d.ts +3 -0
  191. package/dist/lib/parsers/job.d.ts.map +1 -0
  192. package/dist/lib/parsers/job.js +74 -0
  193. package/dist/lib/parsers/job.js.map +1 -0
  194. package/dist/lib/parsers/template.d.ts +3 -0
  195. package/dist/lib/parsers/template.d.ts.map +1 -0
  196. package/dist/lib/parsers/template.js +91 -0
  197. package/dist/lib/parsers/template.js.map +1 -0
  198. package/dist/lib/std/templates/assured-deployment.yaml +45 -0
  199. package/dist/lib/std/templates/erc-2470.yaml +67 -0
  200. package/dist/lib/std/templates/min-balance.yaml +32 -0
  201. package/dist/lib/std/templates/nano-universal-deployer.yaml +59 -0
  202. package/dist/lib/std/templates/raw-erc-2470.yaml +59 -0
  203. package/dist/lib/std/templates/raw-nano-universal-deployer.yaml +51 -0
  204. package/dist/lib/std/templates/raw-sequence-universal-deployer-2.yaml +48 -0
  205. package/dist/lib/std/templates/sequence-universal-deployer-2.yaml +57 -0
  206. package/dist/lib/types/__tests__/json-request-action.spec.d.ts +2 -0
  207. package/dist/lib/types/__tests__/json-request-action.spec.d.ts.map +1 -0
  208. package/dist/lib/types/__tests__/json-request-action.spec.js +219 -0
  209. package/dist/lib/types/__tests__/json-request-action.spec.js.map +1 -0
  210. package/dist/lib/types/__tests__/read-json-value.spec.d.ts +2 -0
  211. package/dist/lib/types/__tests__/read-json-value.spec.d.ts.map +1 -0
  212. package/dist/lib/types/__tests__/read-json-value.spec.js +233 -0
  213. package/dist/lib/types/__tests__/read-json-value.spec.js.map +1 -0
  214. package/dist/lib/types/actions.d.ts +74 -0
  215. package/dist/lib/types/actions.d.ts.map +1 -0
  216. package/dist/lib/types/actions.js +18 -0
  217. package/dist/lib/types/actions.js.map +1 -0
  218. package/dist/lib/types/artifacts.d.ts +15 -0
  219. package/dist/lib/types/artifacts.d.ts.map +1 -0
  220. package/dist/lib/types/artifacts.js +3 -0
  221. package/dist/lib/types/artifacts.js.map +1 -0
  222. package/dist/lib/types/buildinfo.d.ts +112 -0
  223. package/dist/lib/types/buildinfo.d.ts.map +1 -0
  224. package/dist/lib/types/buildinfo.js +3 -0
  225. package/dist/lib/types/buildinfo.js.map +1 -0
  226. package/dist/lib/types/conditions.d.ts +17 -0
  227. package/dist/lib/types/conditions.d.ts.map +1 -0
  228. package/dist/lib/types/conditions.js +21 -0
  229. package/dist/lib/types/conditions.js.map +1 -0
  230. package/dist/lib/types/contracts.d.ts +14 -0
  231. package/dist/lib/types/contracts.d.ts.map +1 -0
  232. package/dist/lib/types/contracts.js +3 -0
  233. package/dist/lib/types/contracts.js.map +1 -0
  234. package/dist/lib/types/definitions.d.ts +51 -0
  235. package/dist/lib/types/definitions.d.ts.map +1 -0
  236. package/dist/lib/types/definitions.js +3 -0
  237. package/dist/lib/types/definitions.js.map +1 -0
  238. package/dist/lib/types/index.d.ts +9 -0
  239. package/dist/lib/types/index.d.ts.map +1 -0
  240. package/dist/lib/types/index.js +25 -0
  241. package/dist/lib/types/index.js.map +1 -0
  242. package/dist/lib/types/network.d.ts +9 -0
  243. package/dist/lib/types/network.d.ts.map +1 -0
  244. package/dist/lib/types/network.js +3 -0
  245. package/dist/lib/types/network.js.map +1 -0
  246. package/dist/lib/types/project.d.ts +5 -0
  247. package/dist/lib/types/project.d.ts.map +1 -0
  248. package/dist/lib/types/project.js +3 -0
  249. package/dist/lib/types/project.js.map +1 -0
  250. package/dist/lib/types/task.d.ts +9 -0
  251. package/dist/lib/types/task.d.ts.map +1 -0
  252. package/dist/lib/types/task.js +3 -0
  253. package/dist/lib/types/task.js.map +1 -0
  254. package/dist/lib/types/values.d.ts +78 -0
  255. package/dist/lib/types/values.d.ts.map +1 -0
  256. package/dist/lib/types/values.js +3 -0
  257. package/dist/lib/types/values.js.map +1 -0
  258. package/dist/lib/utils/validation.d.ts +5 -0
  259. package/dist/lib/utils/validation.d.ts.map +1 -0
  260. package/dist/lib/utils/validation.js +77 -0
  261. package/dist/lib/utils/validation.js.map +1 -0
  262. package/dist/lib/validation/contract-references.d.ts +12 -0
  263. package/dist/lib/validation/contract-references.d.ts.map +1 -0
  264. package/dist/lib/validation/contract-references.js +112 -0
  265. package/dist/lib/validation/contract-references.js.map +1 -0
  266. package/dist/lib/validation/index.d.ts +1 -0
  267. package/dist/lib/validation/index.d.ts.map +1 -0
  268. package/dist/lib/validation/index.js +2 -0
  269. package/dist/lib/validation/index.js.map +1 -0
  270. package/dist/lib/verification/__tests__/etherscan.spec.d.ts +2 -0
  271. package/dist/lib/verification/__tests__/etherscan.spec.d.ts.map +1 -0
  272. package/dist/lib/verification/__tests__/etherscan.spec.js +565 -0
  273. package/dist/lib/verification/__tests__/etherscan.spec.js.map +1 -0
  274. package/dist/lib/verification/__tests__/sourcify.spec.d.ts +2 -0
  275. package/dist/lib/verification/__tests__/sourcify.spec.d.ts.map +1 -0
  276. package/dist/lib/verification/__tests__/sourcify.spec.js +212 -0
  277. package/dist/lib/verification/__tests__/sourcify.spec.js.map +1 -0
  278. package/dist/lib/verification/etherscan.d.ts +56 -0
  279. package/dist/lib/verification/etherscan.d.ts.map +1 -0
  280. package/dist/lib/verification/etherscan.js +340 -0
  281. package/dist/lib/verification/etherscan.js.map +1 -0
  282. package/dist/lib/verification/sourcify.d.ts +12 -0
  283. package/dist/lib/verification/sourcify.d.ts.map +1 -0
  284. package/dist/lib/verification/sourcify.js +227 -0
  285. package/dist/lib/verification/sourcify.js.map +1 -0
  286. package/eslint.config.js +48 -0
  287. package/examples/jobs/guards-v1.yaml +17 -0
  288. package/examples/jobs/sequence-seq-0001-patch.yaml +59 -0
  289. package/examples/jobs/sequence-v1.yaml +59 -0
  290. package/examples/templates/sequence-factory-v1.yaml +56 -0
  291. package/jest.config.js +25 -0
  292. package/package.json +68 -0
  293. package/src/cli.ts +17 -0
  294. package/src/commands/common.ts +61 -0
  295. package/src/commands/dry.ts +208 -0
  296. package/src/commands/etherscan.ts +360 -0
  297. package/src/commands/index.ts +5 -0
  298. package/src/commands/list.ts +249 -0
  299. package/src/commands/run.ts +136 -0
  300. package/src/commands/utils.ts +52 -0
  301. package/src/index.ts +67 -0
  302. package/src/lib/__tests__/deployer-events.spec.ts +338 -0
  303. package/src/lib/__tests__/deployer.spec.ts +1204 -0
  304. package/src/lib/__tests__/network-utils.spec.ts +181 -0
  305. package/src/lib/artifacts/__tests__/fixtures/contract1.json +19 -0
  306. package/src/lib/artifacts/__tests__/fixtures/contract2.json +19 -0
  307. package/src/lib/artifacts/__tests__/fixtures/duplicate-name.json +19 -0
  308. package/src/lib/artifacts/__tests__/fixtures/nested/nested-contract.json +18 -0
  309. package/src/lib/artifacts/__tests__/fixtures/not-an-artifact.json +8 -0
  310. package/src/lib/artifacts/__tests__/fixtures/readme.txt +2 -0
  311. package/src/lib/contracts/__tests__/repository.spec.ts +344 -0
  312. package/src/lib/contracts/repository.ts +313 -0
  313. package/src/lib/core/__tests__/engine.spec.ts +1514 -0
  314. package/src/lib/core/__tests__/graph.spec.ts +125 -0
  315. package/src/lib/core/__tests__/json-integration.spec.ts +360 -0
  316. package/src/lib/core/__tests__/loader.spec.ts +334 -0
  317. package/src/lib/core/__tests__/multi-platform-verification.spec.ts +406 -0
  318. package/src/lib/core/__tests__/resolver.spec.ts +1693 -0
  319. package/src/lib/core/__tests__/static-action.spec.ts +172 -0
  320. package/src/lib/core/context.ts +127 -0
  321. package/src/lib/core/engine.ts +1531 -0
  322. package/src/lib/core/graph.ts +252 -0
  323. package/src/lib/core/loader.ts +263 -0
  324. package/src/lib/core/resolver.ts +498 -0
  325. package/src/lib/deployer.ts +768 -0
  326. package/src/lib/events/__tests__/event-system.spec.ts +343 -0
  327. package/src/lib/events/cli-adapter.ts +325 -0
  328. package/src/lib/events/emitter.ts +62 -0
  329. package/src/lib/events/index.ts +3 -0
  330. package/src/lib/events/types.ts +469 -0
  331. package/src/lib/index.ts +14 -0
  332. package/src/lib/network-loader.ts +59 -0
  333. package/src/lib/network-utils.ts +64 -0
  334. package/src/lib/parsers/__tests__/buildinfo.spec.ts +122 -0
  335. package/src/lib/parsers/__tests__/fixtures/buildinfo/invalid-bytecode-buildinfo.json +62 -0
  336. package/src/lib/parsers/__tests__/fixtures/buildinfo/invalid-json.txt +2 -0
  337. package/src/lib/parsers/__tests__/fixtures/buildinfo/multi-contract-buildinfo.json +89 -0
  338. package/src/lib/parsers/__tests__/fixtures/buildinfo/no-contracts-buildinfo.json +17 -0
  339. package/src/lib/parsers/__tests__/fixtures/buildinfo/simple-buildinfo.json +63 -0
  340. package/src/lib/parsers/__tests__/fixtures/buildinfo/wrong-format.json +4 -0
  341. package/src/lib/parsers/__tests__/job.spec.ts +335 -0
  342. package/src/lib/parsers/__tests__/template.spec.ts +111 -0
  343. package/src/lib/parsers/artifact/__tests__/artifact.spec.ts +117 -0
  344. package/src/lib/parsers/artifact/__tests__/fixtures/empty-bytecode.json +5 -0
  345. package/src/lib/parsers/artifact/__tests__/fixtures/hardhat-artifact.json +67 -0
  346. package/src/lib/parsers/artifact/__tests__/fixtures/invalid-bytecode.json +5 -0
  347. package/src/lib/parsers/artifact/__tests__/fixtures/invalid-json.txt +11 -0
  348. package/src/lib/parsers/artifact/__tests__/fixtures/minimal-artifact.json +5 -0
  349. package/src/lib/parsers/artifact/__tests__/fixtures/missing-abi.json +4 -0
  350. package/src/lib/parsers/artifact/__tests__/fixtures/missing-bytecode.json +11 -0
  351. package/src/lib/parsers/artifact/__tests__/fixtures/missing-contract-name.json +11 -0
  352. package/src/lib/parsers/artifact/__tests__/fixtures/simple-artifact.json +40 -0
  353. package/src/lib/parsers/artifact/__tests__/fixtures/wrong-types.json +7 -0
  354. package/src/lib/parsers/artifact/foundry-1.2.ts +72 -0
  355. package/src/lib/parsers/artifact/index.ts +27 -0
  356. package/src/lib/parsers/artifact/types.ts +9 -0
  357. package/src/lib/parsers/buildinfo.ts +127 -0
  358. package/src/lib/parsers/constants.ts +56 -0
  359. package/src/lib/parsers/index.ts +5 -0
  360. package/src/lib/parsers/job.ts +101 -0
  361. package/src/lib/parsers/template.ts +131 -0
  362. package/src/lib/std/templates/assured-deployment.yaml +45 -0
  363. package/src/lib/std/templates/erc-2470.yaml +67 -0
  364. package/src/lib/std/templates/min-balance.yaml +32 -0
  365. package/src/lib/std/templates/nano-universal-deployer.yaml +59 -0
  366. package/src/lib/std/templates/raw-erc-2470.yaml +59 -0
  367. package/src/lib/std/templates/raw-nano-universal-deployer.yaml +51 -0
  368. package/src/lib/std/templates/raw-sequence-universal-deployer-2.yaml +48 -0
  369. package/src/lib/std/templates/sequence-universal-deployer-2.yaml +57 -0
  370. package/src/lib/types/__tests__/json-request-action.spec.ts +243 -0
  371. package/src/lib/types/__tests__/read-json-value.spec.ts +264 -0
  372. package/src/lib/types/actions.ts +127 -0
  373. package/src/lib/types/artifacts.ts +21 -0
  374. package/src/lib/types/buildinfo.ts +116 -0
  375. package/src/lib/types/conditions.ts +50 -0
  376. package/src/lib/types/contracts.ts +23 -0
  377. package/src/lib/types/definitions.ts +68 -0
  378. package/src/lib/types/index.ts +8 -0
  379. package/src/lib/types/network.ts +22 -0
  380. package/src/lib/types/project.ts +9 -0
  381. package/src/lib/types/task.ts +9 -0
  382. package/src/lib/types/values.ts +116 -0
  383. package/src/lib/utils/validation.ts +116 -0
  384. package/src/lib/validation/contract-references.ts +210 -0
  385. package/src/lib/validation/index.ts +1 -0
  386. package/src/lib/verification/__tests__/etherscan.spec.ts +710 -0
  387. package/src/lib/verification/__tests__/sourcify.spec.ts +288 -0
  388. package/src/lib/verification/etherscan.ts +546 -0
  389. package/src/lib/verification/sourcify.ts +248 -0
  390. package/test_validation/artifacts/TestContract.json +9 -0
  391. package/test_validation/jobs/test-missing.yaml +16 -0
  392. package/test_validation/networks.yaml +3 -0
  393. package/tsconfig.json +36 -0
@@ -0,0 +1,1531 @@
1
+ import { Job, Template, Action, JobAction, isPrimitiveActionType, Condition } from '../types'
2
+ import { Contract } from '../types/contracts'
3
+ import { ExecutionContext } from './context'
4
+ import { ValueResolver, ResolutionScope } from './resolver'
5
+ import { validateAddress, validateHexData, validateBigNumberish, validateRawTransaction } from '../utils/validation'
6
+ import { DeploymentEventEmitter, deploymentEvents } from '../events'
7
+ import { createDefaultVerificationRegistry, VerificationPlatformRegistry } from '../verification/etherscan'
8
+ import { BuildInfo } from '../types/buildinfo'
9
+ import { ethers } from 'ethers'
10
+
11
+ /**
12
+ * The ExecutionEngine is the core component that runs jobs and their actions.
13
+ * It interprets the declarative YAML files, resolves values, interacts with the
14
+ * blockchain, and manages the overall execution flow.
15
+ */
16
+ export class ExecutionEngine {
17
+ private readonly resolver: ValueResolver
18
+ private readonly templates: Map<string, Template>
19
+ private readonly events: DeploymentEventEmitter
20
+ private readonly verificationRegistry: VerificationPlatformRegistry
21
+ private readonly noPostCheckConditions: boolean
22
+
23
+ constructor(templates: Map<string, Template>, eventEmitter?: DeploymentEventEmitter, verificationRegistry?: VerificationPlatformRegistry, noPostCheckConditions?: boolean) {
24
+ this.resolver = new ValueResolver()
25
+ this.templates = templates
26
+ this.events = eventEmitter || deploymentEvents
27
+ this.verificationRegistry = verificationRegistry || createDefaultVerificationRegistry()
28
+ this.noPostCheckConditions = noPostCheckConditions ?? false
29
+ }
30
+
31
+ /**
32
+ * Computes retry configuration for post-execution checks, tuned for local vs public networks.
33
+ * Local (anvil/hardhat): 50ms delay for ~5s total (100 retries => 101 attempts)
34
+ * Public: 2000ms delay for ~30s total (15 retries => 16 attempts)
35
+ */
36
+ private getPostCheckRetryConfig(context: ExecutionContext): { retries: number; delayMs: number } {
37
+ const network = context.getNetwork()
38
+ const isLocal =
39
+ network.chainId === 31337 ||
40
+ network.chainId === 1337 ||
41
+ /localhost|127\.0\.0\.1/i.test(network.rpcUrl)
42
+
43
+ if (isLocal) {
44
+ return { retries: 100, delayMs: 50 }
45
+ }
46
+ return { retries: 15, delayMs: 2000 }
47
+ }
48
+
49
+ /**
50
+ * Executes a single job against a given network context.
51
+ * @param job The Job object to execute.
52
+ * @param context The ExecutionContext for the target network.
53
+ */
54
+ public async executeJob(job: Job, context: ExecutionContext): Promise<void> {
55
+ this.events.emitEvent({
56
+ type: 'job_started',
57
+ level: 'info',
58
+ data: {
59
+ jobName: job.name,
60
+ jobVersion: job.version,
61
+ networkName: context.getNetwork().name,
62
+ chainId: context.getNetwork().chainId
63
+ }
64
+ })
65
+
66
+ // Set context path for relative artifact resolution
67
+ const previousContextPath = context.getContextPath()
68
+ context.setContextPath(job._path)
69
+
70
+ try {
71
+ const executionOrder = this.topologicalSortActions(job)
72
+
73
+ for (const actionName of executionOrder) {
74
+ const action = job.actions.find(a => a.name === actionName)
75
+ if (!action) {
76
+ // This should be unreachable if topological sort is correct
77
+ throw new Error(`Internal error: Action "${actionName}" not found in job "${job.name}".`)
78
+ }
79
+ await this.executeAction(action, context, new Map())
80
+ }
81
+
82
+ // If post-check conditions are enabled, re-evaluate job-level skip conditions with retry to handle RPC propagation lag
83
+ if (!this.noPostCheckConditions && job.skip_condition) {
84
+ const { retries, delayMs } = this.getPostCheckRetryConfig(context)
85
+ const shouldSkip = await this.retryBooleanCheck(
86
+ async () => this.evaluateSkipConditions(job.skip_condition!, context, new Map()),
87
+ retries,
88
+ delayMs
89
+ )
90
+ if (!shouldSkip) {
91
+ // If skip conditions don't evaluate to true after execution, the job failed
92
+ throw new Error(`Job "${job.name}" failed post-execution check: skip conditions did not evaluate to true`)
93
+ }
94
+ }
95
+ } finally {
96
+ // Restore previous context path
97
+ context.setContextPath(previousContextPath)
98
+ }
99
+
100
+ this.events.emitEvent({
101
+ type: 'job_completed',
102
+ level: 'info',
103
+ data: {
104
+ jobName: job.name,
105
+ networkName: context.getNetwork().name,
106
+ chainId: context.getNetwork().chainId
107
+ }
108
+ })
109
+ }
110
+
111
+ /**
112
+ * The central dispatcher for executing any action, whether it's a primitive
113
+ * or a call to another template.
114
+ * @param action The action to execute.
115
+ * @param context The global execution context.
116
+ * @param scope The local resolution scope, used for template arguments.
117
+ */
118
+ private async executeAction(
119
+ action: JobAction | Action,
120
+ context: ExecutionContext,
121
+ scope: ResolutionScope,
122
+ ): Promise<void> {
123
+ const actionName = 'name' in action ? action.name : action.type
124
+ // For JobAction, get template or type; for Action, get type
125
+ const templateName = 'template' in action
126
+ ? (action.template || action.type)
127
+ : action.type
128
+
129
+ if (!templateName) {
130
+ throw new Error(`Action "${actionName}": missing both template and type fields`)
131
+ }
132
+
133
+ // Emit action start with a guaranteed, meaningful name
134
+ const printableName =
135
+ (typeof actionName === 'string' && actionName.trim().length > 0)
136
+ ? actionName
137
+ : (isPrimitiveActionType(templateName) ? templateName : `template:${templateName}`)
138
+ this.events.emitEvent({
139
+ type: 'action_started',
140
+ level: 'info',
141
+ data: {
142
+ actionName: printableName,
143
+ jobName: 'unknown' // We'll need to pass job context later
144
+ }
145
+ })
146
+
147
+ // 1. Evaluate skip conditions for the action itself.
148
+ const shouldSkip = await this.evaluateSkipConditions(action.skip_condition, context, scope)
149
+ if (shouldSkip) {
150
+ this.events.emitEvent({
151
+ type: 'action_skipped',
152
+ level: 'info',
153
+ data: {
154
+ actionName: actionName,
155
+ reason: 'condition met'
156
+ }
157
+ })
158
+
159
+ // Process static outputs even when action is skipped
160
+ // This is important for static outputs that don't depend on action execution
161
+ const hasCustomOutput = 'name' in action && action.name &&
162
+ (action as any).output &&
163
+ typeof (action as any).output === 'object' &&
164
+ !Array.isArray((action as any).output)
165
+
166
+ if (hasCustomOutput) {
167
+ const customOutput = (action as any).output
168
+ // Custom output map provided by job action: resolve each mapping within the current scope
169
+ for (const [key, value] of Object.entries(customOutput)) {
170
+ const resolvedOutput = await this.resolver.resolve(value as any, context, scope)
171
+ const outputKey = `${action.name}.${key}`
172
+ context.setOutput(outputKey, resolvedOutput)
173
+ this.events.emitEvent({
174
+ type: 'output_stored',
175
+ level: 'debug',
176
+ data: {
177
+ outputKey,
178
+ value: resolvedOutput
179
+ }
180
+ })
181
+ }
182
+ }
183
+
184
+ return
185
+ }
186
+
187
+ // 2. Differentiate between a primitive action and a template call.
188
+ if (isPrimitiveActionType(templateName)) {
189
+ // Check if custom outputs are specified
190
+ const hasCustomOutput = 'name' in action && action.name &&
191
+ (action as any).output &&
192
+ typeof (action as any).output === 'object' &&
193
+ !Array.isArray((action as any).output)
194
+
195
+ // Convert JobAction to Action for primitive execution
196
+ const primitiveAction: Action = 'template' in action
197
+ ? {
198
+ type: (action.type || action.template) as any,
199
+ name: action.name,
200
+ arguments: action.arguments,
201
+ skip_condition: action.skip_condition,
202
+ depends_on: action.depends_on
203
+ }
204
+ : action as Action
205
+
206
+ // Execute primitive with information about custom outputs
207
+ await this.executePrimitive(primitiveAction, context, scope, hasCustomOutput)
208
+
209
+ // Handle custom outputs for primitive actions (similar to template logic)
210
+ if (hasCustomOutput) {
211
+ const customOutput = (action as any).output
212
+ // Custom output map provided by job action: resolve each mapping within the current scope
213
+ for (const [key, value] of Object.entries(customOutput)) {
214
+ const resolvedOutput = await this.resolver.resolve(value as any, context, scope)
215
+ const outputKey = `${action.name}.${key}`
216
+ context.setOutput(outputKey, resolvedOutput)
217
+ this.events.emitEvent({
218
+ type: 'output_stored',
219
+ level: 'debug',
220
+ data: {
221
+ outputKey,
222
+ value: resolvedOutput
223
+ }
224
+ })
225
+ }
226
+ }
227
+ } else {
228
+ await this.executeTemplate(action, templateName, context, scope)
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Executes a template, including its setup, skip conditions, actions, and outputs.
234
+ * @param callingAction The action from the parent job/template that is calling this template.
235
+ * @param templateName The name of the template to execute.
236
+ * @param context The global execution context.
237
+ */
238
+ private async executeTemplate(
239
+ callingAction: JobAction | Action,
240
+ templateName: string,
241
+ context: ExecutionContext,
242
+ parentScope: ResolutionScope = new Map(),
243
+ ): Promise<void> {
244
+ const template = this.templates.get(templateName)
245
+ if (!template) {
246
+ const actionName = 'name' in callingAction ? callingAction.name : callingAction.type
247
+ throw new Error(`Template "${templateName}" not found for action "${actionName}".`)
248
+ }
249
+ this.events.emitEvent({
250
+ type: 'template_entered',
251
+ level: 'debug',
252
+ data: {
253
+ templateName: template.name
254
+ }
255
+ })
256
+
257
+ // 1. Create and populate a new resolution scope for this template call.
258
+ // NOTE: We resolve arguments in the CURRENT context (which should be the job's context)
259
+ // before changing to the template's context. This ensures artifact references in
260
+ // job arguments are resolved relative to the job, not the template.
261
+ const templateScope: ResolutionScope = new Map()
262
+ if ('arguments' in callingAction) {
263
+ for (const [key, value] of Object.entries(callingAction.arguments)) {
264
+ // Resolve the argument value in the parent's context, preserving the caller's local scope
265
+ // so that template arguments from the caller are available when invoking nested templates.
266
+ const resolvedValue = await this.resolver.resolve(value, context, parentScope)
267
+ templateScope.set(key, resolvedValue)
268
+ }
269
+ }
270
+
271
+ // Set context path for relative artifact resolution within the template
272
+ const previousContextPath = context.getContextPath()
273
+ context.setContextPath(template._path)
274
+
275
+ try {
276
+
277
+ // 2. Handle template-level setup block.
278
+ if (template.setup) {
279
+ // Check setup skip conditions before executing setup actions
280
+ if (template.setup.skip_condition && await this.evaluateSkipConditions(template.setup.skip_condition, context, templateScope)) {
281
+ this.events.emitEvent({
282
+ type: 'template_setup_skipped',
283
+ level: 'info',
284
+ data: {
285
+ templateName: template.name,
286
+ reason: 'setup skip condition met'
287
+ }
288
+ })
289
+ } else if (template.setup.actions) {
290
+ this.events.emitEvent({
291
+ type: 'template_setup_started',
292
+ level: 'debug',
293
+ data: {
294
+ templateName: template.name
295
+ }
296
+ })
297
+ for (const setupAction of template.setup.actions) {
298
+ // Setup actions are executed with the new template scope.
299
+ await this.executeAction(setupAction, context, templateScope)
300
+ }
301
+ this.events.emitEvent({
302
+ type: 'template_setup_completed',
303
+ level: 'debug',
304
+ data: {
305
+ templateName: template.name
306
+ }
307
+ })
308
+ }
309
+ }
310
+
311
+ // 3. Evaluate template-level skip conditions.
312
+ const templateSkipConditions = template.skip_condition
313
+ const templateShouldSkip = await this.evaluateSkipConditions(templateSkipConditions, context, templateScope)
314
+ if (templateShouldSkip) {
315
+ this.events.emitEvent({
316
+ type: 'template_skipped',
317
+ level: 'info',
318
+ data: {
319
+ templateName: template.name,
320
+ reason: 'condition met'
321
+ }
322
+ })
323
+ // Even if we skip the main actions, we must still process the outputs,
324
+ // as they might be pre-computable (e.g., a CREATE2 address).
325
+ } else {
326
+ // 4. Execute the main actions within the template.
327
+ for (const templateAction of template.actions) {
328
+ await this.executeAction(templateAction, context, templateScope)
329
+ }
330
+ }
331
+
332
+ // If post-check conditions are enabled, re-evaluate template-level skip conditions with retry to handle RPC propagation lag
333
+ if (!this.noPostCheckConditions && template.skip_condition) {
334
+ const { retries, delayMs } = this.getPostCheckRetryConfig(context)
335
+ const shouldSkip = await this.retryBooleanCheck(
336
+ async () => this.evaluateSkipConditions(template.skip_condition!, context, templateScope),
337
+ retries,
338
+ delayMs
339
+ )
340
+ if (!shouldSkip) {
341
+ // If skip conditions don't evaluate to true after execution, the template failed
342
+ throw new Error(`Template "${template.name}" failed post-execution check: skip conditions did not evaluate to true`)
343
+ }
344
+ }
345
+
346
+ // 5. Resolve and store the template's outputs into the global context.
347
+ // If the calling action (job action) specified a custom "output" map, that overrides the template outputs.
348
+ if ('name' in callingAction) {
349
+ const actionName = callingAction.name
350
+ const customOutput = (callingAction as any).output
351
+ if (customOutput && typeof customOutput === 'object' && !Array.isArray(customOutput)) {
352
+ // Custom output map provided by job action: resolve each mapping within the template scope
353
+ for (const [key, value] of Object.entries(customOutput)) {
354
+ const resolvedOutput = await this.resolver.resolve(value as any, context, templateScope)
355
+ const outputKey = `${actionName}.${key}`
356
+ context.setOutput(outputKey, resolvedOutput)
357
+ this.events.emitEvent({
358
+ type: 'output_stored',
359
+ level: 'debug',
360
+ data: {
361
+ outputKey,
362
+ value: resolvedOutput
363
+ }
364
+ })
365
+ }
366
+ } else if (template.outputs) {
367
+ // Default behavior: use template-defined outputs
368
+ for (const [key, value] of Object.entries(template.outputs)) {
369
+ const resolvedOutput = await this.resolver.resolve(value, context, templateScope)
370
+ const outputKey = `${actionName}.${key}`
371
+ context.setOutput(outputKey, resolvedOutput)
372
+ this.events.emitEvent({
373
+ type: 'output_stored',
374
+ level: 'debug',
375
+ data: {
376
+ outputKey,
377
+ value: resolvedOutput
378
+ }
379
+ })
380
+ }
381
+ }
382
+ }
383
+
384
+ this.events.emitEvent({
385
+ type: 'template_exited',
386
+ level: 'debug',
387
+ data: {
388
+ templateName: template.name
389
+ }
390
+ })
391
+ } finally {
392
+ // Restore previous context path
393
+ context.setContextPath(previousContextPath)
394
+ }
395
+ }
396
+
397
+ /**
398
+ * Executes a primitive, built-in action.
399
+ * @param action The primitive action to execute.
400
+ * @param context The global execution context.
401
+ * @param scope The local resolution scope.
402
+ * @param hasCustomOutput Whether custom outputs are specified for this action.
403
+ */
404
+ private async executePrimitive(
405
+ action: Action,
406
+ context: ExecutionContext,
407
+ scope: ResolutionScope,
408
+ hasCustomOutput: boolean = false,
409
+ ): Promise<void> {
410
+ const actionName = action.name || action.type
411
+ this.events.emitEvent({
412
+ type: 'primitive_action',
413
+ level: 'debug',
414
+ data: {
415
+ actionType: action.type
416
+ }
417
+ })
418
+
419
+ switch (action.type) {
420
+ case 'send-transaction': {
421
+ const resolvedTo = await this.resolver.resolve(action.arguments.to, context, scope)
422
+ const resolvedData = action.arguments.data ? await this.resolver.resolve(action.arguments.data, context, scope) : '0x'
423
+ const resolvedValue = action.arguments.value ? await this.resolver.resolve(action.arguments.value, context, scope) : 0
424
+ const resolvedGasMultiplier = action.arguments.gasMultiplier !== undefined ? await this.resolver.resolve(action.arguments.gasMultiplier, context, scope) : undefined
425
+
426
+ // Validate and convert types
427
+ const to = validateAddress(resolvedTo, actionName)
428
+ const data = validateHexData(resolvedData, actionName, 'data')
429
+ const value = validateBigNumberish(resolvedValue, actionName, 'value')
430
+
431
+ // Validate gas multiplier if provided
432
+ let gasMultiplier: number | undefined
433
+ if (resolvedGasMultiplier !== undefined) {
434
+ if (typeof resolvedGasMultiplier !== 'number' || resolvedGasMultiplier <= 0) {
435
+ throw new Error(`Action "${actionName}": gasMultiplier must be a positive number, got: ${resolvedGasMultiplier}`)
436
+ }
437
+ gasMultiplier = resolvedGasMultiplier
438
+ }
439
+
440
+ // Prepare transaction parameters
441
+ const txParams: any = { to, data, value }
442
+
443
+ // Handle gas limit with optional multiplier
444
+ const network = context.getNetwork()
445
+ if (network.gasLimit) {
446
+ const baseGasLimit = network.gasLimit
447
+ txParams.gasLimit = gasMultiplier ? Math.floor(baseGasLimit * gasMultiplier) : baseGasLimit
448
+ } else if (gasMultiplier) {
449
+ // If gasMultiplier is specified but no network gasLimit, estimate gas first
450
+ const signer = await context.getResolvedSigner()
451
+ const estimatedGas = await signer.estimateGas({ to, data, value })
452
+ txParams.gasLimit = Math.floor(Number(estimatedGas) * gasMultiplier)
453
+ }
454
+
455
+ const signer = await context.getResolvedSigner()
456
+ const tx = await signer.sendTransaction(txParams)
457
+
458
+ this.events.emitEvent({
459
+ type: 'transaction_sent',
460
+ level: 'info',
461
+ data: {
462
+ to,
463
+ value: value.toString(),
464
+ dataPreview: String(data).substring(0, 42),
465
+ txHash: tx.hash
466
+ }
467
+ })
468
+
469
+ const receipt = await tx.wait()
470
+ if (!receipt || receipt.status !== 1) {
471
+ throw new Error(`Transaction for action "${actionName}" failed (reverted). Hash: ${tx.hash}`)
472
+ }
473
+
474
+ this.events.emitEvent({
475
+ type: 'transaction_confirmed',
476
+ level: 'info',
477
+ data: {
478
+ txHash: tx.hash,
479
+ blockNumber: receipt.blockNumber
480
+ }
481
+ })
482
+
483
+ if (action.name && !hasCustomOutput) {
484
+ context.setOutput(`${action.name}.hash`, tx.hash)
485
+ context.setOutput(`${action.name}.receipt`, receipt)
486
+ }
487
+ break
488
+ }
489
+ case 'send-signed-transaction': {
490
+ const resolvedRawTx = await this.resolver.resolve(action.arguments.transaction, context, scope)
491
+
492
+ // Validate and convert type
493
+ const rawTx = validateRawTransaction(resolvedRawTx, actionName)
494
+
495
+ const tx = await context.provider.broadcastTransaction(rawTx)
496
+
497
+ this.events.emitEvent({
498
+ type: 'transaction_sent',
499
+ level: 'info',
500
+ data: {
501
+ to: '',
502
+ value: '0',
503
+ dataPreview: 'signed transaction',
504
+ txHash: tx.hash
505
+ }
506
+ })
507
+
508
+ const receipt = await tx.wait()
509
+ if (!receipt || receipt.status !== 1) {
510
+ throw new Error(`Signed transaction for action "${actionName}" failed (reverted). Hash: ${tx.hash}`)
511
+ }
512
+
513
+ this.events.emitEvent({
514
+ type: 'transaction_confirmed',
515
+ level: 'info',
516
+ data: {
517
+ txHash: tx.hash,
518
+ blockNumber: receipt.blockNumber
519
+ }
520
+ })
521
+
522
+ if (action.name && !hasCustomOutput) {
523
+ context.setOutput(`${action.name}.hash`, tx.hash)
524
+ context.setOutput(`${action.name}.receipt`, receipt)
525
+ }
526
+ break
527
+ }
528
+ case 'verify-contract': {
529
+ const actionName = action.name || action.type
530
+
531
+ // Resolve arguments
532
+ const resolvedAddress = await this.resolver.resolve(action.arguments.address, context, scope)
533
+ const resolvedContract = await this.resolver.resolve(action.arguments.contract, context, scope)
534
+ const resolvedConstructorArgs = action.arguments.constructorArguments
535
+ ? await this.resolver.resolve(action.arguments.constructorArguments, context, scope)
536
+ : undefined
537
+ const resolvedPlatform = action.arguments.platform
538
+ ? await this.resolver.resolve(action.arguments.platform, context, scope)
539
+ : 'all'
540
+
541
+ // Validate inputs
542
+ const address = validateAddress(resolvedAddress, actionName)
543
+
544
+ if (!resolvedContract || typeof resolvedContract !== 'object') {
545
+ throw new Error(`Action "${actionName}": contract must be a Contract object`)
546
+ }
547
+
548
+ const contract = resolvedContract as Contract
549
+
550
+ // Handle platform validation - allow string, array of strings, or 'all'
551
+ let platformsToTry: string[]
552
+ if (resolvedPlatform === 'all') {
553
+ platformsToTry = ['all']
554
+ } else if (typeof resolvedPlatform === 'string') {
555
+ platformsToTry = [resolvedPlatform]
556
+ } else if (Array.isArray(resolvedPlatform)) {
557
+ // Validate that all array elements are strings
558
+ if (!resolvedPlatform.every(p => typeof p === 'string')) {
559
+ throw new Error(`Action "${actionName}": platform array must contain only strings`)
560
+ }
561
+ platformsToTry = resolvedPlatform
562
+ } else {
563
+ throw new Error(`Action "${actionName}": platform must be a string, array of strings, or 'all'`)
564
+ }
565
+
566
+ // Validate that the contract has the necessary information for verification
567
+ if (!contract.sourceName) {
568
+ throw new Error(`Action "${actionName}": Contract is missing sourceName required for verification`)
569
+ }
570
+ if (!contract.contractName) {
571
+ throw new Error(`Action "${actionName}": Contract is missing contractName required for verification`)
572
+ }
573
+ if (!contract.compiler) {
574
+ throw new Error(`Action "${actionName}": Contract is missing compiler information required for verification`)
575
+ }
576
+ if (!contract.buildInfoId) {
577
+ throw new Error(`Action "${actionName}": Contract is missing buildInfoId required for verification`)
578
+ }
579
+
580
+ // Validate constructor arguments if provided
581
+ let constructorArguments: string | undefined
582
+ if (resolvedConstructorArgs !== undefined) {
583
+ constructorArguments = validateHexData(resolvedConstructorArgs, actionName, 'constructorArguments')
584
+ }
585
+
586
+ const network = context.getNetwork()
587
+ const contractName = `${contract.sourceName}:${contract.contractName}`
588
+
589
+ // Handle platform verification
590
+ if (platformsToTry.includes('all')) {
591
+ // Handle "all" platform - try all configured platforms for this network
592
+ const configuredPlatforms = this.verificationRegistry.getConfiguredPlatforms(network)
593
+
594
+ if (configuredPlatforms.length === 0) {
595
+ this.events.emitEvent({
596
+ type: 'action_skipped',
597
+ level: 'warn',
598
+ data: {
599
+ actionName: actionName,
600
+ reason: `No configured verification platforms available for network ${network.name}`
601
+ }
602
+ })
603
+ return
604
+ }
605
+
606
+ // Try verification on all configured platforms
607
+ let anySuccess = false
608
+ for (const platform of configuredPlatforms) {
609
+ try {
610
+ await this.verifyOnSinglePlatform(
611
+ platform,
612
+ contract,
613
+ address,
614
+ constructorArguments,
615
+ network,
616
+ actionName,
617
+ contractName,
618
+ action,
619
+ context,
620
+ hasCustomOutput
621
+ )
622
+ anySuccess = true
623
+ } catch (error) {
624
+ // Log the error but continue with other platforms
625
+ this.events.emitEvent({
626
+ type: 'verification_failed',
627
+ level: 'warn',
628
+ data: {
629
+ actionName: actionName,
630
+ address,
631
+ contractName,
632
+ platform: platform.name,
633
+ error: error instanceof Error ? error.message : String(error)
634
+ }
635
+ })
636
+ }
637
+ }
638
+
639
+ if (!anySuccess) {
640
+ throw new Error(`Verification failed on all configured platforms for network ${network.name}`)
641
+ }
642
+ } else {
643
+ // Handle specific platform(s) verification
644
+ let anySuccess = false
645
+ for (const platformName of platformsToTry) {
646
+ const platform = this.verificationRegistry.get(platformName)
647
+ if (!platform) {
648
+ throw new Error(`Action "${actionName}": Unsupported verification platform "${platformName}"`)
649
+ }
650
+
651
+ try {
652
+ await this.verifyOnSinglePlatform(
653
+ platform,
654
+ contract,
655
+ address,
656
+ constructorArguments,
657
+ network,
658
+ actionName,
659
+ contractName,
660
+ action,
661
+ context,
662
+ hasCustomOutput
663
+ )
664
+ anySuccess = true
665
+ } catch (error) {
666
+ // Log the error but continue with other platforms if multiple specified
667
+ this.events.emitEvent({
668
+ type: 'verification_failed',
669
+ level: platformsToTry.length > 1 ? 'warn' : 'error',
670
+ data: {
671
+ actionName: actionName,
672
+ address,
673
+ contractName,
674
+ platform: platform.name,
675
+ error: error instanceof Error ? error.message : String(error)
676
+ }
677
+ })
678
+
679
+ // If only one platform specified, re-throw the error
680
+ if (platformsToTry.length === 1) {
681
+ throw error
682
+ }
683
+ }
684
+ }
685
+
686
+ if (!anySuccess && platformsToTry.length > 1) {
687
+ throw new Error(`Verification failed on all specified platforms: ${platformsToTry.join(', ')}`)
688
+ }
689
+ }
690
+
691
+ break
692
+ }
693
+ case 'static': {
694
+ const resolvedValue = await this.resolver.resolve(action.arguments.value, context, scope)
695
+
696
+ if (action.name && !hasCustomOutput) {
697
+ context.setOutput(`${action.name}.value`, resolvedValue)
698
+ }
699
+ break
700
+ }
701
+ case 'create-contract': {
702
+ const resolvedData = await this.resolver.resolve(action.arguments.data, context, scope)
703
+ const resolvedValue = action.arguments.value ? await this.resolver.resolve(action.arguments.value, context, scope) : 0
704
+ const resolvedGasMultiplier = action.arguments.gasMultiplier !== undefined ? await this.resolver.resolve(action.arguments.gasMultiplier, context, scope) : undefined
705
+
706
+ // Validate and convert types
707
+ const data = validateHexData(resolvedData, actionName, 'data')
708
+ const value = validateBigNumberish(resolvedValue, actionName, 'value')
709
+
710
+ // Validate gas multiplier if provided
711
+ let gasMultiplier: number | undefined
712
+ if (resolvedGasMultiplier !== undefined) {
713
+ if (typeof resolvedGasMultiplier !== 'number' || resolvedGasMultiplier <= 0) {
714
+ throw new Error(`Action "${actionName}": gasMultiplier must be a positive number, got: ${resolvedGasMultiplier}`)
715
+ }
716
+ gasMultiplier = resolvedGasMultiplier
717
+ }
718
+
719
+ // Prepare transaction parameters for contract creation (to: null)
720
+ const txParams: any = { to: null, data, value }
721
+
722
+ // Handle gas limit with optional multiplier
723
+ const network = context.getNetwork()
724
+ if (network.gasLimit) {
725
+ const baseGasLimit = network.gasLimit
726
+ txParams.gasLimit = gasMultiplier ? Math.floor(baseGasLimit * gasMultiplier) : baseGasLimit
727
+ } else if (gasMultiplier) {
728
+ // If gasMultiplier is specified but no network gasLimit, estimate gas first
729
+ const signer = await context.getResolvedSigner()
730
+ const estimatedGas = await signer.estimateGas({ to: null, data, value })
731
+ txParams.gasLimit = Math.floor(Number(estimatedGas) * gasMultiplier)
732
+ }
733
+
734
+ const signer = await context.getResolvedSigner()
735
+ const tx = await signer.sendTransaction(txParams)
736
+
737
+ this.events.emitEvent({
738
+ type: 'transaction_sent',
739
+ level: 'info',
740
+ data: {
741
+ to: 'contract creation',
742
+ value: value.toString(),
743
+ dataPreview: String(data).substring(0, 42),
744
+ txHash: tx.hash
745
+ }
746
+ })
747
+
748
+ const receipt = await tx.wait()
749
+ if (!receipt || receipt.status !== 1) {
750
+ throw new Error(`Contract creation for action "${actionName}" failed (reverted). Hash: ${tx.hash}`)
751
+ }
752
+
753
+ if (!receipt.contractAddress) {
754
+ throw new Error(`Contract creation for action "${actionName}" did not return a contract address. Hash: ${tx.hash}`)
755
+ }
756
+
757
+ this.events.emitEvent({
758
+ type: 'transaction_confirmed',
759
+ level: 'info',
760
+ data: {
761
+ txHash: tx.hash,
762
+ blockNumber: receipt.blockNumber
763
+ }
764
+ })
765
+
766
+ this.events.emitEvent({
767
+ type: 'contract_created',
768
+ level: 'info',
769
+ data: {
770
+ contractAddress: receipt.contractAddress,
771
+ txHash: tx.hash,
772
+ blockNumber: receipt.blockNumber
773
+ }
774
+ })
775
+
776
+ if (action.name && !hasCustomOutput) {
777
+ context.setOutput(`${action.name}.hash`, tx.hash)
778
+ context.setOutput(`${action.name}.receipt`, receipt)
779
+ context.setOutput(`${action.name}.address`, receipt.contractAddress)
780
+ }
781
+ break
782
+ }
783
+ case 'test-nicks-method': {
784
+ // Default bytecode if none provided
785
+ const defaultBytecode = '0x608060405234801561001057600080fd5b5061013d806100206000396000f3fe60806040526004361061001e5760003560e01c80639c4ae2d014610023575b600080fd5b6100cb6004803603604081101561003957600080fd5b81019060208101813564010000000081111561005457600080fd5b82018360208201111561006657600080fd5b8035906020019184600183028401116401000000008311171561008857600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955050913592506100cd915050565b005b60008183516020850134f56040805173ffffffffffffffffffffffffffffffffffffffff83168152905191925081900360200190a050505056fea264697066735822122033609f614f03931b92d88c309d698449bb77efcd517328d341fa4f923c5d8c7964736f6c63430007060033'
786
+
787
+ // Handle case where arguments is undefined (action takes no arguments)
788
+ const args = action.arguments || {}
789
+ const resolvedBytecode = args.bytecode ? await this.resolver.resolve(args.bytecode, context, scope) : defaultBytecode
790
+ const resolvedGasPrice = args.gasPrice ? await this.resolver.resolve(args.gasPrice, context, scope) : undefined
791
+ const resolvedGasLimit = args.gasLimit ? await this.resolver.resolve(args.gasLimit, context, scope) : undefined
792
+ const resolvedFundingAmount = args.fundingAmount ? await this.resolver.resolve(args.fundingAmount, context, scope) : undefined
793
+
794
+ // Validate inputs
795
+ const bytecode = validateHexData(resolvedBytecode, actionName, 'bytecode')
796
+ const gasPrice = resolvedGasPrice ? validateBigNumberish(resolvedGasPrice, actionName, 'gasPrice') : undefined
797
+ const gasLimit = resolvedGasLimit ? validateBigNumberish(resolvedGasLimit, actionName, 'gasLimit') : undefined
798
+ const fundingAmount = resolvedFundingAmount ? validateBigNumberish(resolvedFundingAmount, actionName, 'fundingAmount') : undefined
799
+
800
+ const success = await this.testNicksMethod(bytecode, context, gasPrice, gasLimit, fundingAmount)
801
+
802
+ if (!success) {
803
+ throw new Error(`Nick's method test failed for action "${actionName}"`)
804
+ }
805
+
806
+ this.events.emitEvent({
807
+ type: 'action_completed',
808
+ level: 'info',
809
+ data: {
810
+ actionName: actionName,
811
+ result: 'Nick\'s method test passed'
812
+ }
813
+ })
814
+
815
+ if (action.name && !hasCustomOutput) {
816
+ context.setOutput(`${action.name}.success`, true)
817
+ }
818
+ break
819
+ }
820
+ case 'json-request': {
821
+ const resolvedUrl = await this.resolver.resolve(action.arguments.url, context, scope)
822
+ const resolvedMethod = action.arguments.method ? await this.resolver.resolve(action.arguments.method, context, scope) : 'GET'
823
+ const resolvedHeaders = action.arguments.headers ? await this.resolver.resolve(action.arguments.headers, context, scope) : {}
824
+ const resolvedBody = action.arguments.body ? await this.resolver.resolve(action.arguments.body, context, scope) : undefined
825
+
826
+ // Validate inputs
827
+ if (typeof resolvedUrl !== 'string') {
828
+ throw new Error(`Action "${actionName}": url must be a string, got: ${typeof resolvedUrl}`)
829
+ }
830
+
831
+ if (typeof resolvedMethod !== 'string') {
832
+ throw new Error(`Action "${actionName}": method must be a string, got: ${typeof resolvedMethod}`)
833
+ }
834
+
835
+ if (resolvedHeaders && typeof resolvedHeaders !== 'object') {
836
+ throw new Error(`Action "${actionName}": headers must be an object, got: ${typeof resolvedHeaders}`)
837
+ }
838
+
839
+ try {
840
+ // Prepare fetch options
841
+ const fetchOptions: RequestInit = {
842
+ method: resolvedMethod.toUpperCase(),
843
+ headers: {
844
+ 'Content-Type': 'application/json',
845
+ ...(resolvedHeaders as Record<string, string>)
846
+ }
847
+ }
848
+
849
+ // Add body for non-GET requests
850
+ if (resolvedBody !== undefined && resolvedMethod.toUpperCase() !== 'GET') {
851
+ fetchOptions.body = JSON.stringify(resolvedBody)
852
+ }
853
+
854
+ this.events.emitEvent({
855
+ type: 'action_started',
856
+ level: 'info',
857
+ data: {
858
+ actionName: actionName,
859
+ message: `Making ${resolvedMethod.toUpperCase()} request to ${resolvedUrl}`
860
+ }
861
+ })
862
+
863
+ // Make the HTTP request
864
+ const response = await fetch(resolvedUrl, fetchOptions)
865
+
866
+ if (!response.ok) {
867
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`)
868
+ }
869
+
870
+ // Parse JSON response
871
+ const responseData = await response.json()
872
+
873
+ this.events.emitEvent({
874
+ type: 'action_completed',
875
+ level: 'info',
876
+ data: {
877
+ actionName: actionName,
878
+ message: `Request completed successfully (${response.status})`
879
+ }
880
+ })
881
+
882
+ // Store outputs
883
+ if (action.name && !hasCustomOutput) {
884
+ context.setOutput(`${action.name}.response`, responseData)
885
+ context.setOutput(`${action.name}.status`, response.status)
886
+ context.setOutput(`${action.name}.statusText`, response.statusText)
887
+ }
888
+ } catch (error) {
889
+ this.events.emitEvent({
890
+ type: 'action_failed',
891
+ level: 'error',
892
+ data: {
893
+ actionName: actionName,
894
+ error: error instanceof Error ? error.message : String(error)
895
+ }
896
+ })
897
+ throw new Error(`Action "${actionName}" failed: ${error instanceof Error ? error.message : String(error)}`)
898
+ }
899
+ break
900
+ }
901
+ default:
902
+ throw new Error(`Unknown or unimplemented primitive action type: ${(action as any).type}`)
903
+ }
904
+ }
905
+
906
+ /**
907
+ * Helper method to verify a contract on a single platform
908
+ * @private
909
+ */
910
+ private async verifyOnSinglePlatform(
911
+ platform: any,
912
+ contract: Contract,
913
+ address: string,
914
+ constructorArguments: string | undefined,
915
+ network: any,
916
+ actionName: string,
917
+ contractName: string,
918
+ action: Action,
919
+ context: ExecutionContext,
920
+ hasCustomOutput: boolean = false
921
+ ): Promise<void> {
922
+ // Check if platform supports this network
923
+ const supportsNetwork = platform.supportsNetwork(network)
924
+ if (!supportsNetwork) {
925
+ this.events.emitEvent({
926
+ type: 'action_skipped',
927
+ level: 'info',
928
+ data: {
929
+ actionName: actionName,
930
+ reason: `Network ${network.name} does not support ${platform.name} verification`
931
+ }
932
+ })
933
+ return
934
+ }
935
+
936
+ // Check if platform is properly configured
937
+ const isConfigured = platform.isConfigured()
938
+ if (!isConfigured) {
939
+ this.events.emitEvent({
940
+ type: 'action_skipped',
941
+ level: 'warn',
942
+ data: {
943
+ actionName: actionName,
944
+ reason: `Verification skipped: ${platform.getConfigurationRequirements()}`
945
+ }
946
+ })
947
+ return
948
+ }
949
+
950
+ // Find and load build info
951
+ let buildInfoPath: string | undefined
952
+ for (const sourcePath of contract._sources) {
953
+ if (sourcePath.includes('/build-info/') && sourcePath.endsWith('.json')) {
954
+ buildInfoPath = sourcePath
955
+ break
956
+ }
957
+ }
958
+
959
+ if (!buildInfoPath) {
960
+ throw new Error(`Action "${actionName}": No build-info file found in contract sources`)
961
+ }
962
+
963
+ const fs = await import('fs/promises')
964
+ let buildInfoContent: string
965
+ try {
966
+ buildInfoContent = await fs.readFile(buildInfoPath, 'utf-8')
967
+ } catch (error) {
968
+ throw new Error(`Action "${actionName}": Failed to read build info file at ${buildInfoPath}: ${error instanceof Error ? error.message : String(error)}`)
969
+ }
970
+
971
+ let buildInfo: BuildInfo
972
+ try {
973
+ buildInfo = JSON.parse(buildInfoContent)
974
+ } catch (error) {
975
+ throw new Error(`Action "${actionName}": Failed to parse build info JSON: ${error instanceof Error ? error.message : String(error)}`)
976
+ }
977
+
978
+ this.events.emitEvent({
979
+ type: 'verification_started',
980
+ level: 'info',
981
+ data: {
982
+ actionName: actionName,
983
+ address,
984
+ contractName,
985
+ platform: platform.name,
986
+ networkName: network.name
987
+ }
988
+ })
989
+
990
+ try {
991
+ // Use the platform to verify the contract
992
+ const verificationResult = await platform.verifyContract({
993
+ contract,
994
+ buildInfo,
995
+ address,
996
+ constructorArguments,
997
+ network
998
+ })
999
+
1000
+ if (!verificationResult.success) {
1001
+ throw new Error(`Verification failed: ${verificationResult.message}`)
1002
+ }
1003
+
1004
+ // Emit appropriate events based on verification result
1005
+ if (verificationResult.isAlreadyVerified) {
1006
+ this.events.emitEvent({
1007
+ type: 'verification_completed',
1008
+ level: 'info',
1009
+ data: {
1010
+ actionName: actionName,
1011
+ address,
1012
+ contractName,
1013
+ platform: platform.name,
1014
+ message: verificationResult.message
1015
+ }
1016
+ })
1017
+ } else {
1018
+ this.events.emitEvent({
1019
+ type: 'verification_submitted',
1020
+ level: 'info',
1021
+ data: {
1022
+ actionName: actionName,
1023
+ platform: platform.name,
1024
+ guid: verificationResult.guid || 'N/A',
1025
+ message: verificationResult.message
1026
+ }
1027
+ })
1028
+
1029
+ this.events.emitEvent({
1030
+ type: 'verification_completed',
1031
+ level: 'info',
1032
+ data: {
1033
+ actionName: actionName,
1034
+ address,
1035
+ contractName,
1036
+ platform: platform.name,
1037
+ message: 'Contract verified successfully'
1038
+ }
1039
+ })
1040
+ }
1041
+
1042
+ // Set outputs (only for successful verifications)
1043
+ if (action.name && !hasCustomOutput) {
1044
+ context.setOutput(`${action.name}.verified`, true)
1045
+ if (verificationResult.guid) {
1046
+ context.setOutput(`${action.name}.guid`, verificationResult.guid)
1047
+ }
1048
+ }
1049
+
1050
+ } catch (error) {
1051
+ this.events.emitEvent({
1052
+ type: 'verification_failed',
1053
+ level: 'error',
1054
+ data: {
1055
+ actionName: actionName,
1056
+ address,
1057
+ contractName,
1058
+ platform: platform.name,
1059
+ error: error instanceof Error ? error.message : String(error)
1060
+ }
1061
+ })
1062
+ throw error
1063
+ }
1064
+ }
1065
+
1066
+ /**
1067
+ * Tests Nick's method for EOA deployment
1068
+ * Generates a valid ECDSA signature and tests if it can deploy the given bytecode
1069
+ * Returns any remaining funds to the original wallet after testing
1070
+ */
1071
+ private async testNicksMethod(
1072
+ bytecode: string,
1073
+ context: ExecutionContext,
1074
+ gasPrice?: ethers.BigNumberish,
1075
+ gasLimit?: ethers.BigNumberish,
1076
+ fundingAmount?: ethers.BigNumberish
1077
+ ): Promise<boolean> {
1078
+ let testResult = false
1079
+ let eoaAddress: string | undefined
1080
+ let wallet: ethers.HDNodeWallet | ethers.Wallet | undefined
1081
+
1082
+ try {
1083
+ // Default values
1084
+ const defaultGasPrice = gasPrice || ethers.parseUnits('100', 'gwei') // 100 gwei
1085
+ const defaultGasLimit = gasLimit || 250000n // Reasonable gas limit for deployment
1086
+ const calculatedCost = BigInt(defaultGasPrice.toString()) * BigInt(defaultGasLimit.toString())
1087
+ const defaultFundingAmount = fundingAmount || calculatedCost
1088
+
1089
+ // Check main signer balance first
1090
+ const signer = await context.getResolvedSigner()
1091
+ const signerAddress = await signer.getAddress()
1092
+ const signerBalance = await context.provider.getBalance(signerAddress)
1093
+
1094
+ if (signerBalance < BigInt(defaultFundingAmount.toString())) {
1095
+ this.events.emitEvent({
1096
+ type: 'action_failed',
1097
+ level: 'error',
1098
+ data: {
1099
+ message: `Insufficient funds: signer has ${ethers.formatEther(signerBalance)} ETH but needs ${ethers.formatEther(defaultFundingAmount)} ETH`
1100
+ }
1101
+ })
1102
+ return false
1103
+ }
1104
+
1105
+ // Generate a valid ECDSA signature using Nick's method approach
1106
+ const result = await this.generateNicksMethodTransaction(bytecode, defaultGasPrice, defaultGasLimit)
1107
+ const rawTx = result.rawTx
1108
+ eoaAddress = result.eoaAddress
1109
+ wallet = result.wallet
1110
+
1111
+ this.events.emitEvent({
1112
+ type: 'debug_info',
1113
+ level: 'debug',
1114
+ data: {
1115
+ message: `Testing Nick's method with EOA: ${eoaAddress}`
1116
+ }
1117
+ })
1118
+
1119
+ // Check if EOA already has sufficient balance
1120
+ const currentBalance = await context.provider.getBalance(eoaAddress)
1121
+ const neededFunding = BigInt(defaultFundingAmount.toString()) - currentBalance
1122
+
1123
+ if (neededFunding > 0) {
1124
+ // Fund the EOA
1125
+ this.events.emitEvent({
1126
+ type: 'transaction_sent',
1127
+ level: 'debug',
1128
+ data: {
1129
+ to: eoaAddress,
1130
+ value: neededFunding.toString(),
1131
+ dataPreview: 'funding EOA for Nick\'s method test',
1132
+ txHash: 'pending'
1133
+ }
1134
+ })
1135
+
1136
+ this.events.emitEvent({
1137
+ type: 'debug_info',
1138
+ level: 'debug',
1139
+ data: {
1140
+ message: `[NICK'S METHOD DEBUG] Sending funding transaction: ${ethers.formatEther(neededFunding)} ETH to ${eoaAddress}`
1141
+ }
1142
+ })
1143
+
1144
+ const signer = await context.getResolvedSigner()
1145
+ const fundingTx = await signer.sendTransaction({
1146
+ to: eoaAddress,
1147
+ value: neededFunding
1148
+ })
1149
+
1150
+ this.events.emitEvent({
1151
+ type: 'debug_info',
1152
+ level: 'debug',
1153
+ data: {
1154
+ message: `[NICK'S METHOD DEBUG] Funding transaction sent: ${fundingTx.hash}, waiting for confirmation...`
1155
+ }
1156
+ })
1157
+
1158
+ const fundingReceipt = await fundingTx.wait()
1159
+
1160
+ this.events.emitEvent({
1161
+ type: 'transaction_confirmed',
1162
+ level: 'debug',
1163
+ data: {
1164
+ txHash: fundingTx.hash,
1165
+ blockNumber: fundingReceipt?.blockNumber || 0
1166
+ }
1167
+ })
1168
+
1169
+ this.events.emitEvent({
1170
+ type: 'debug_info',
1171
+ level: 'debug',
1172
+ data: {
1173
+ message: `[NICK'S METHOD DEBUG] Funded EOA ${eoaAddress} with ${ethers.formatEther(neededFunding)} ETH, receipt status: ${fundingReceipt?.status}`
1174
+ }
1175
+ })
1176
+
1177
+ if (!fundingReceipt || fundingReceipt.status !== 1) {
1178
+ this.events.emitEvent({
1179
+ type: 'action_failed',
1180
+ level: 'error',
1181
+ data: {
1182
+ message: `[NICK'S METHOD DEBUG] Funding transaction failed! Hash: ${fundingTx.hash}, Status: ${fundingReceipt?.status}`
1183
+ }
1184
+ })
1185
+ return false
1186
+ }
1187
+ } else {
1188
+ this.events.emitEvent({
1189
+ type: 'debug_info',
1190
+ level: 'debug',
1191
+ data: {
1192
+ message: `[NICK'S METHOD DEBUG] EOA already has sufficient balance, skipping funding`
1193
+ }
1194
+ })
1195
+ }
1196
+
1197
+ // Try to broadcast the raw transaction
1198
+ this.events.emitEvent({
1199
+ type: 'debug_info',
1200
+ level: 'debug',
1201
+ data: {
1202
+ message: `[NICK'S METHOD DEBUG] Broadcasting Nick's method transaction. RawTx: ${rawTx.substring(0, 100)}...`
1203
+ }
1204
+ })
1205
+
1206
+ const deployTx = await context.provider.broadcastTransaction(rawTx)
1207
+
1208
+ this.events.emitEvent({
1209
+ type: 'debug_info',
1210
+ level: 'debug',
1211
+ data: {
1212
+ message: `[NICK'S METHOD DEBUG] Transaction broadcasted successfully. Hash: ${deployTx.hash}, waiting for confirmation...`
1213
+ }
1214
+ })
1215
+
1216
+ const receipt = await deployTx.wait()
1217
+
1218
+ this.events.emitEvent({
1219
+ type: 'debug_info',
1220
+ level: 'debug',
1221
+ data: {
1222
+ message: `[NICK'S METHOD DEBUG] Transaction receipt received. Status: ${receipt?.status}, ContractAddress: ${receipt?.contractAddress}, BlockNumber: ${receipt?.blockNumber}`
1223
+ }
1224
+ })
1225
+
1226
+ if (receipt && receipt.status === 1) {
1227
+ this.events.emitEvent({
1228
+ type: 'transaction_confirmed',
1229
+ level: 'info',
1230
+ data: {
1231
+ txHash: deployTx.hash,
1232
+ blockNumber: receipt.blockNumber || 0
1233
+ }
1234
+ })
1235
+
1236
+ this.events.emitEvent({
1237
+ type: 'debug_info',
1238
+ level: 'debug',
1239
+ data: {
1240
+ message: `[NICK'S METHOD DEBUG] Nick's method test successful - contract deployed at ${receipt.contractAddress}`
1241
+ }
1242
+ })
1243
+ testResult = true
1244
+ } else {
1245
+ this.events.emitEvent({
1246
+ type: 'action_failed',
1247
+ level: 'error',
1248
+ data: {
1249
+ message: `[NICK'S METHOD DEBUG] Nick's method test failed - transaction reverted or failed. Hash: ${deployTx.hash}, Status: ${receipt?.status}`
1250
+ }
1251
+ })
1252
+ testResult = false
1253
+ }
1254
+ } catch (error) {
1255
+ this.events.emitEvent({
1256
+ type: 'action_failed',
1257
+ level: 'error',
1258
+ data: {
1259
+ message: `[NICK'S METHOD DEBUG] Nick's method test failed with error: ${error instanceof Error ? error.message : String(error)}`
1260
+ }
1261
+ })
1262
+
1263
+ // Log additional error details for debugging
1264
+ if (error instanceof Error && error.stack) {
1265
+ this.events.emitEvent({
1266
+ type: 'action_failed',
1267
+ level: 'debug',
1268
+ data: {
1269
+ message: `[NICK'S METHOD DEBUG] Error stack trace: ${error.stack}`
1270
+ }
1271
+ })
1272
+ }
1273
+
1274
+ testResult = false
1275
+ } finally {
1276
+ // Always try to return remaining funds to the original wallet
1277
+ if (eoaAddress && wallet) {
1278
+ try {
1279
+ await this.returnRemainingFunds(eoaAddress, wallet, context)
1280
+ } catch (error) {
1281
+ // Log the error but don't fail the main test
1282
+ this.events.emitEvent({
1283
+ type: 'action_failed',
1284
+ level: 'warn',
1285
+ data: {
1286
+ message: `Failed to return remaining funds from EOA ${eoaAddress}: ${error instanceof Error ? error.message : String(error)}`
1287
+ }
1288
+ })
1289
+ }
1290
+ }
1291
+ }
1292
+
1293
+ return testResult
1294
+ }
1295
+
1296
+ /**
1297
+ * Generates a raw transaction and EOA address using Nick's method approach
1298
+ */
1299
+ private async generateNicksMethodTransaction(
1300
+ bytecode: string,
1301
+ gasPrice: ethers.BigNumberish,
1302
+ gasLimit: ethers.BigNumberish
1303
+ ): Promise<{ rawTx: string; eoaAddress: string; wallet: ethers.HDNodeWallet }> {
1304
+ // Generate a random private key for the test
1305
+ const wallet = ethers.Wallet.createRandom()
1306
+
1307
+ // Create unsigned transaction
1308
+ const unsignedTx: ethers.TransactionRequest = {
1309
+ type: 0, // Legacy transaction
1310
+ chainId: 0, // Nick's method uses chainId 0
1311
+ nonce: 0,
1312
+ gasPrice: gasPrice,
1313
+ gasLimit: gasLimit,
1314
+ to: null, // Contract creation
1315
+ value: 0,
1316
+ data: bytecode
1317
+ }
1318
+
1319
+ // Sign the transaction
1320
+ const signedTx = await wallet.signTransaction(unsignedTx)
1321
+
1322
+ // Parse the signed transaction to get the EOA address
1323
+ const parsedTx = ethers.Transaction.from(signedTx)
1324
+ const eoaAddress = parsedTx.from!
1325
+
1326
+ return {
1327
+ rawTx: signedTx,
1328
+ eoaAddress: eoaAddress,
1329
+ wallet: wallet
1330
+ }
1331
+ }
1332
+
1333
+ /**
1334
+ * Returns any remaining funds from the test EOA back to the original wallet
1335
+ */
1336
+ private async returnRemainingFunds(
1337
+ eoaAddress: string,
1338
+ wallet: ethers.HDNodeWallet | ethers.Wallet,
1339
+ context: ExecutionContext
1340
+ ): Promise<void> {
1341
+ // Check remaining balance in the test EOA
1342
+ const remainingBalance = await context.provider.getBalance(eoaAddress)
1343
+
1344
+ if (remainingBalance <= 0n) {
1345
+ // No funds to return
1346
+ return
1347
+ }
1348
+
1349
+ // Connect the wallet to the provider to send transactions
1350
+ const connectedWallet = wallet.connect(context.provider)
1351
+
1352
+ // Estimate gas for a simple transfer
1353
+ const gasPrice = await context.provider.getFeeData().then(data => data.gasPrice || ethers.parseUnits('20', 'gwei'))
1354
+ const gasLimit = 21000n // Standard gas limit for ETH transfer
1355
+ const gasCost = BigInt(gasPrice.toString()) * gasLimit
1356
+
1357
+ // Check if we have enough balance to cover gas costs
1358
+ if (remainingBalance <= gasCost) {
1359
+ this.events.emitEvent({
1360
+ type: 'action_info',
1361
+ level: 'debug',
1362
+ data: {
1363
+ message: `Remaining balance ${ethers.formatEther(remainingBalance)} ETH is insufficient to cover gas costs for fund return`
1364
+ }
1365
+ })
1366
+ return
1367
+ }
1368
+
1369
+ // Calculate amount to send (balance minus gas costs)
1370
+ const amountToSend = remainingBalance - gasCost
1371
+
1372
+ this.events.emitEvent({
1373
+ type: 'transaction_sent',
1374
+ level: 'debug',
1375
+ data: {
1376
+ to: await (await context.getResolvedSigner()).getAddress(),
1377
+ value: amountToSend.toString(),
1378
+ dataPreview: 'returning remaining funds from Nick\'s method test',
1379
+ txHash: 'pending'
1380
+ }
1381
+ })
1382
+
1383
+ // Send the remaining funds back to the original signer
1384
+ const returnTx = await connectedWallet.sendTransaction({
1385
+ to: await (await context.getResolvedSigner()).getAddress(),
1386
+ value: amountToSend,
1387
+ gasPrice: gasPrice,
1388
+ gasLimit: gasLimit
1389
+ })
1390
+
1391
+ await returnTx.wait()
1392
+
1393
+ this.events.emitEvent({
1394
+ type: 'transaction_confirmed',
1395
+ level: 'debug',
1396
+ data: {
1397
+ txHash: returnTx.hash,
1398
+ blockNumber: (await returnTx.wait())?.blockNumber || 0
1399
+ }
1400
+ })
1401
+
1402
+ this.events.emitEvent({
1403
+ type: 'debug_info',
1404
+ level: 'debug',
1405
+ data: {
1406
+ message: `Returned ${ethers.formatEther(amountToSend)} ETH from test EOA ${eoaAddress} to original wallet`
1407
+ }
1408
+ })
1409
+ }
1410
+
1411
+ /**
1412
+ * Retries a boolean-producing async check to mitigate transient RPC state lag after transactions.
1413
+ * Returns true on first successful check; otherwise waits delayMs and retries up to retries times.
1414
+ */
1415
+ private async retryBooleanCheck(checkFn: () => Promise<boolean>, retries: number = 3, delayMs: number = 2000): Promise<boolean> {
1416
+ // Throttle debug logging: log first, 25%, 50%, 75%, and final attempt
1417
+ const milestones = new Set<number>()
1418
+ const total = retries + 1
1419
+ milestones.add(1)
1420
+ milestones.add(Math.max(1, Math.floor(total * 0.25)))
1421
+ milestones.add(Math.max(1, Math.floor(total * 0.5)))
1422
+ milestones.add(Math.max(1, Math.floor(total * 0.75)))
1423
+ milestones.add(total)
1424
+
1425
+ for (let attempt = 0; attempt < total; attempt++) {
1426
+ try {
1427
+ const result = await checkFn()
1428
+ if (result) {
1429
+ return true
1430
+ }
1431
+ if (milestones.has(attempt + 1)) {
1432
+ this.events.emitEvent({
1433
+ type: 'debug_info',
1434
+ level: 'debug',
1435
+ data: {
1436
+ message: `Post-execution check returned false (attempt ${attempt + 1}/${total}).`
1437
+ }
1438
+ })
1439
+ }
1440
+ } catch (err) {
1441
+ if (milestones.has(attempt + 1)) {
1442
+ this.events.emitEvent({
1443
+ type: 'debug_info',
1444
+ level: 'debug',
1445
+ data: {
1446
+ message: `Post-execution check threw error (attempt ${attempt + 1}/${total}): ${err instanceof Error ? err.message : String(err)}`
1447
+ }
1448
+ })
1449
+ }
1450
+ }
1451
+ if (attempt < retries) {
1452
+ await new Promise(res => setTimeout(res, delayMs))
1453
+ }
1454
+ }
1455
+ return false
1456
+ }
1457
+
1458
+ /**
1459
+ * Evaluates a list of conditions and returns true if any of them are met.
1460
+ */
1461
+ private async evaluateSkipConditions(
1462
+ conditions: Condition[] | undefined,
1463
+ context: ExecutionContext,
1464
+ scope: ResolutionScope,
1465
+ ): Promise<boolean> {
1466
+ if (!conditions || conditions.length === 0) {
1467
+ return false
1468
+ }
1469
+ for (const condition of conditions) {
1470
+ const shouldSkip = await this.resolver.resolve(condition, context, scope)
1471
+ if (shouldSkip) {
1472
+ return true
1473
+ }
1474
+ }
1475
+ return false
1476
+ }
1477
+
1478
+ /**
1479
+ * Creates a topological sort of actions within a job based on their `depends_on` fields.
1480
+ */
1481
+ private topologicalSortActions(job: Job): string[] {
1482
+ const sorted: string[] = []
1483
+ const graph = new Map<string, Set<string>>()
1484
+ const inDegree = new Map<string, number>()
1485
+ const actionMap = new Map(job.actions.map(a => [a.name, a]))
1486
+
1487
+ // Initialize graph and in-degrees
1488
+ for (const action of job.actions) {
1489
+ graph.set(action.name, new Set(action.depends_on || []))
1490
+ inDegree.set(action.name, 0)
1491
+ }
1492
+
1493
+ // Calculate in-degrees and validate dependencies
1494
+ for (const [actionName, dependencies] of graph.entries()) {
1495
+ for (const depName of dependencies) {
1496
+ if (!actionMap.has(depName)) {
1497
+ throw new Error(`Action "${actionName}" in job "${job.name}" has an invalid dependency on "${depName}", which does not exist.`)
1498
+ }
1499
+ inDegree.set(actionName, (inDegree.get(actionName) ?? 0) + 1)
1500
+ }
1501
+ }
1502
+
1503
+ // Initialize queue with actions having an in-degree of 0
1504
+ const queue = Array.from(inDegree.entries())
1505
+ .filter(([, degree]) => degree === 0)
1506
+ .map(([name]) => name)
1507
+
1508
+ // Process the queue
1509
+ while (queue.length > 0) {
1510
+ const currentName = queue.shift()!
1511
+ sorted.push(currentName)
1512
+
1513
+ // Find all actions that depend on the current one
1514
+ for (const [actionName, dependencies] of graph.entries()) {
1515
+ if (dependencies.has(currentName)) {
1516
+ const newDegree = (inDegree.get(actionName) ?? 1) - 1
1517
+ inDegree.set(actionName, newDegree)
1518
+ if (newDegree === 0) {
1519
+ queue.push(actionName)
1520
+ }
1521
+ }
1522
+ }
1523
+ }
1524
+
1525
+ if (sorted.length !== job.actions.length) {
1526
+ throw new Error(`Circular dependency detected among actions in job "${job.name}".`)
1527
+ }
1528
+
1529
+ return sorted
1530
+ }
1531
+ }