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