@0xsequence/catapult 1.4.0 → 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 (163) hide show
  1. package/README.md +27 -0
  2. package/dist/lib/__tests__/network-loader.spec.js.map +1 -1
  3. package/dist/lib/core/__tests__/resolver.spec.js +22 -0
  4. package/dist/lib/core/__tests__/resolver.spec.js.map +1 -1
  5. package/dist/lib/core/__tests__/sign-actions.spec.d.ts +2 -0
  6. package/dist/lib/core/__tests__/sign-actions.spec.d.ts.map +1 -0
  7. package/dist/lib/core/__tests__/sign-actions.spec.js +128 -0
  8. package/dist/lib/core/__tests__/sign-actions.spec.js.map +1 -0
  9. package/dist/lib/core/__tests__/signer.spec.d.ts +2 -0
  10. package/dist/lib/core/__tests__/signer.spec.d.ts.map +1 -0
  11. package/dist/lib/core/__tests__/signer.spec.js +40 -0
  12. package/dist/lib/core/__tests__/signer.spec.js.map +1 -0
  13. package/dist/lib/core/context.d.ts +3 -2
  14. package/dist/lib/core/context.d.ts.map +1 -1
  15. package/dist/lib/core/context.js +3 -2
  16. package/dist/lib/core/context.js.map +1 -1
  17. package/dist/lib/core/engine.d.ts +4 -0
  18. package/dist/lib/core/engine.d.ts.map +1 -1
  19. package/dist/lib/core/engine.js +173 -0
  20. package/dist/lib/core/engine.js.map +1 -1
  21. package/dist/lib/core/signer.d.ts +7 -0
  22. package/dist/lib/core/signer.d.ts.map +1 -0
  23. package/dist/lib/core/signer.js +60 -0
  24. package/dist/lib/core/signer.js.map +1 -0
  25. package/dist/lib/parsers/__tests__/source.spec.js +37 -0
  26. package/dist/lib/parsers/__tests__/source.spec.js.map +1 -1
  27. package/dist/lib/parsers/source.js +1 -1
  28. package/dist/lib/parsers/source.js.map +1 -1
  29. package/dist/lib/provenance.js +51 -2
  30. package/dist/lib/provenance.js.map +1 -1
  31. package/dist/lib/types/actions.d.ts +26 -2
  32. package/dist/lib/types/actions.d.ts.map +1 -1
  33. package/dist/lib/types/actions.js +3 -0
  34. package/dist/lib/types/actions.js.map +1 -1
  35. package/dist/lib/types/source.d.ts +2 -0
  36. package/dist/lib/types/source.d.ts.map +1 -1
  37. package/package.json +4 -1
  38. package/.eslintrc.json +0 -29
  39. package/.github/workflows/ci.yml +0 -181
  40. package/CONCEPT.md +0 -24
  41. package/contracts/checked-call.huff +0 -65
  42. package/eslint.config.js +0 -48
  43. package/examples/jobs/guards-v1.yaml +0 -17
  44. package/examples/jobs/sequence-seq-0001-patch.yaml +0 -59
  45. package/examples/jobs/sequence-v1.yaml +0 -59
  46. package/examples/templates/sequence-factory-v1.yaml +0 -56
  47. package/jest.config.js +0 -25
  48. package/src/cli.ts +0 -18
  49. package/src/commands/common.ts +0 -61
  50. package/src/commands/dry.ts +0 -209
  51. package/src/commands/etherscan.ts +0 -360
  52. package/src/commands/index.ts +0 -6
  53. package/src/commands/list.ts +0 -262
  54. package/src/commands/provenance.ts +0 -120
  55. package/src/commands/run.ts +0 -146
  56. package/src/commands/utils.ts +0 -215
  57. package/src/index.ts +0 -67
  58. package/src/lib/__tests__/deployer-events.spec.ts +0 -338
  59. package/src/lib/__tests__/deployer.spec.ts +0 -2269
  60. package/src/lib/__tests__/network-loader.spec.ts +0 -150
  61. package/src/lib/__tests__/network-selection.spec.ts +0 -41
  62. package/src/lib/__tests__/network-utils.spec.ts +0 -230
  63. package/src/lib/__tests__/provenance.spec.ts +0 -208
  64. package/src/lib/artifacts/__tests__/fixtures/contract1.json +0 -19
  65. package/src/lib/artifacts/__tests__/fixtures/contract2.json +0 -19
  66. package/src/lib/artifacts/__tests__/fixtures/duplicate-name.json +0 -19
  67. package/src/lib/artifacts/__tests__/fixtures/nested/nested-contract.json +0 -18
  68. package/src/lib/artifacts/__tests__/fixtures/not-an-artifact.json +0 -8
  69. package/src/lib/artifacts/__tests__/fixtures/readme.txt +0 -2
  70. package/src/lib/contracts/__tests__/repository.spec.ts +0 -612
  71. package/src/lib/contracts/repository.ts +0 -411
  72. package/src/lib/core/__tests__/assert-action.spec.ts +0 -474
  73. package/src/lib/core/__tests__/context.spec.ts +0 -37
  74. package/src/lib/core/__tests__/engine.spec.ts +0 -2005
  75. package/src/lib/core/__tests__/graph.spec.ts +0 -125
  76. package/src/lib/core/__tests__/json-integration.spec.ts +0 -425
  77. package/src/lib/core/__tests__/loader.spec.ts +0 -367
  78. package/src/lib/core/__tests__/multi-platform-verification.spec.ts +0 -406
  79. package/src/lib/core/__tests__/resolver.spec.ts +0 -2496
  80. package/src/lib/core/__tests__/static-action.spec.ts +0 -172
  81. package/src/lib/core/context.ts +0 -127
  82. package/src/lib/core/engine.ts +0 -1834
  83. package/src/lib/core/graph.ts +0 -252
  84. package/src/lib/core/loader.ts +0 -253
  85. package/src/lib/core/resolver.ts +0 -873
  86. package/src/lib/deployer.ts +0 -1005
  87. package/src/lib/events/__tests__/event-system.spec.ts +0 -392
  88. package/src/lib/events/cli-adapter.ts +0 -369
  89. package/src/lib/events/emitter.ts +0 -62
  90. package/src/lib/events/index.ts +0 -3
  91. package/src/lib/events/types.ts +0 -520
  92. package/src/lib/index.ts +0 -17
  93. package/src/lib/network-loader.ts +0 -90
  94. package/src/lib/network-selection.ts +0 -73
  95. package/src/lib/network-utils.ts +0 -64
  96. package/src/lib/parsers/__tests__/buildinfo.spec.ts +0 -122
  97. package/src/lib/parsers/__tests__/fixtures/buildinfo/invalid-bytecode-buildinfo.json +0 -62
  98. package/src/lib/parsers/__tests__/fixtures/buildinfo/invalid-json.txt +0 -2
  99. package/src/lib/parsers/__tests__/fixtures/buildinfo/multi-contract-buildinfo.json +0 -89
  100. package/src/lib/parsers/__tests__/fixtures/buildinfo/no-contracts-buildinfo.json +0 -17
  101. package/src/lib/parsers/__tests__/fixtures/buildinfo/simple-buildinfo.json +0 -63
  102. package/src/lib/parsers/__tests__/fixtures/buildinfo/wrong-format.json +0 -4
  103. package/src/lib/parsers/__tests__/job.spec.ts +0 -439
  104. package/src/lib/parsers/__tests__/source.spec.ts +0 -134
  105. package/src/lib/parsers/__tests__/template.spec.ts +0 -111
  106. package/src/lib/parsers/artifact/__tests__/artifact.spec.ts +0 -117
  107. package/src/lib/parsers/artifact/__tests__/fixtures/empty-bytecode.json +0 -5
  108. package/src/lib/parsers/artifact/__tests__/fixtures/hardhat-artifact.json +0 -67
  109. package/src/lib/parsers/artifact/__tests__/fixtures/invalid-bytecode.json +0 -5
  110. package/src/lib/parsers/artifact/__tests__/fixtures/invalid-json.txt +0 -11
  111. package/src/lib/parsers/artifact/__tests__/fixtures/minimal-artifact.json +0 -5
  112. package/src/lib/parsers/artifact/__tests__/fixtures/missing-abi.json +0 -4
  113. package/src/lib/parsers/artifact/__tests__/fixtures/missing-bytecode.json +0 -11
  114. package/src/lib/parsers/artifact/__tests__/fixtures/missing-contract-name.json +0 -11
  115. package/src/lib/parsers/artifact/__tests__/fixtures/simple-artifact.json +0 -40
  116. package/src/lib/parsers/artifact/__tests__/fixtures/wrong-types.json +0 -7
  117. package/src/lib/parsers/artifact/foundry-1.2.ts +0 -72
  118. package/src/lib/parsers/artifact/index.ts +0 -27
  119. package/src/lib/parsers/artifact/types.ts +0 -9
  120. package/src/lib/parsers/buildinfo.ts +0 -127
  121. package/src/lib/parsers/constants.ts +0 -56
  122. package/src/lib/parsers/index.ts +0 -6
  123. package/src/lib/parsers/job.ts +0 -160
  124. package/src/lib/parsers/source.ts +0 -129
  125. package/src/lib/parsers/template.ts +0 -135
  126. package/src/lib/provenance.ts +0 -785
  127. package/src/lib/std/templates/arachnid-deterministic-deployment-proxy.yaml +0 -68
  128. package/src/lib/std/templates/assured-deployment.yaml +0 -46
  129. package/src/lib/std/templates/era-evm-predeploy.yaml +0 -35
  130. package/src/lib/std/templates/erc-2470.yaml +0 -70
  131. package/src/lib/std/templates/min-balance.yaml +0 -35
  132. package/src/lib/std/templates/nano-universal-deployer.yaml +0 -61
  133. package/src/lib/std/templates/raw-erc-2470.yaml +0 -62
  134. package/src/lib/std/templates/raw-nano-universal-deployer.yaml +0 -54
  135. package/src/lib/std/templates/raw-sequence-universal-deployer-2.yaml +0 -52
  136. package/src/lib/std/templates/sequence-universal-deployer-2.yaml +0 -61
  137. package/src/lib/types/__tests__/json-request-action.spec.ts +0 -243
  138. package/src/lib/types/__tests__/read-json-value.spec.ts +0 -278
  139. package/src/lib/types/__tests__/resolve-json-value.spec.ts +0 -769
  140. package/src/lib/types/actions.ts +0 -148
  141. package/src/lib/types/artifacts.ts +0 -21
  142. package/src/lib/types/buildinfo.ts +0 -116
  143. package/src/lib/types/conditions.ts +0 -50
  144. package/src/lib/types/contracts.ts +0 -26
  145. package/src/lib/types/definitions.ts +0 -77
  146. package/src/lib/types/index.ts +0 -9
  147. package/src/lib/types/network.ts +0 -33
  148. package/src/lib/types/project.ts +0 -9
  149. package/src/lib/types/source.ts +0 -26
  150. package/src/lib/types/task.ts +0 -9
  151. package/src/lib/types/values.ts +0 -221
  152. package/src/lib/utils/assertion.ts +0 -24
  153. package/src/lib/utils/validation.ts +0 -116
  154. package/src/lib/validation/contract-references.ts +0 -210
  155. package/src/lib/validation/index.ts +0 -1
  156. package/src/lib/verification/__tests__/etherscan.spec.ts +0 -710
  157. package/src/lib/verification/__tests__/sourcify.spec.ts +0 -288
  158. package/src/lib/verification/etherscan.ts +0 -547
  159. package/src/lib/verification/sourcify.ts +0 -248
  160. package/test_validation/artifacts/TestContract.json +0 -9
  161. package/test_validation/jobs/test-missing.yaml +0 -16
  162. package/test_validation/networks.yaml +0 -3
  163. package/tsconfig.json +0 -36
@@ -1,209 +0,0 @@
1
- import { Command } from 'commander'
2
- import chalk from 'chalk'
3
- import { loadProject } from './common'
4
- import { loadNetworks } from '../lib/network-loader'
5
- import { DependencyGraph } from '../lib/core/graph'
6
- import { projectOption, noStdOption, verbosityOption } from './common'
7
- import { validateContractReferences, extractUsedContractReferences } from '../lib/validation/contract-references'
8
- import { setVerbosity } from '../index'
9
- import { resolveSelectedChainIds } from '../lib/network-selection'
10
- import { Template } from '../lib/types'
11
-
12
- interface DryRunOptions {
13
- project: string
14
- std: boolean
15
- network?: string
16
- verbose: number
17
- }
18
-
19
- /**
20
- * Extract only constant-like placeholders from a value, using template metadata when available.
21
- * A placeholder is treated as a constant candidate if:
22
- * - It is a bare identifier (no dot, no parentheses), AND
23
- * - It is NOT declared as a template argument in the current template (when template context provided)
24
- */
25
- function extractConstantRefs(value: unknown, refs: string[], templateCtx?: Template) {
26
- if (typeof value === 'string') {
27
- const m = value.match(/^{{(.*)}}$/)
28
- if (m) {
29
- const expr = m[1].trim()
30
-
31
- // Skip outputs or function-like references
32
- if (expr.includes('.') || expr.includes('(') || expr.includes(')')) return
33
-
34
- // If we have a template context, and the expr matches a declared argument, it's NOT a constant
35
- if (templateCtx?.arguments && Object.prototype.hasOwnProperty.call(templateCtx.arguments, expr)) {
36
- return
37
- }
38
-
39
- // Otherwise, treat as a constant candidate
40
- refs.push(expr)
41
- }
42
- } else if (Array.isArray(value)) {
43
- for (const v of value) extractConstantRefs(v, refs, templateCtx)
44
- } else if (value && typeof value === 'object') {
45
- for (const v of Object.values(value)) extractConstantRefs(v, refs, templateCtx)
46
- }
47
- }
48
-
49
- export function makeDryRunCommand(): Command {
50
- const dryRun = new Command('dry-run')
51
- .description('Validate project configuration and show execution plan without running transactions')
52
- .argument('[jobs...]', 'Specific job names to validate (and their dependencies).')
53
- .option('-n, --network <selectors>', 'Comma-separated network selectors (by chain ID or name).')
54
-
55
- projectOption(dryRun)
56
- noStdOption(dryRun)
57
- verbosityOption(dryRun)
58
-
59
- dryRun.action(async (jobs: string[], options: DryRunOptions) => {
60
- try {
61
- // Set verbosity level for logging
62
- setVerbosity(options.verbose as 0 | 1 | 2 | 3)
63
-
64
- console.log(chalk.bold.inverse(' DRY-RUN MODE '))
65
- const projectRoot = options.project
66
- const loader = await loadProject(projectRoot, {
67
- loadStdTemplates: options.std !== false
68
- })
69
- const allNetworks = await loadNetworks(projectRoot)
70
-
71
- console.log(chalk.blue('\nBuilding dependency graph...'))
72
- const graph = new DependencyGraph(loader.jobs, loader.templates)
73
- const fullOrder = graph.getExecutionOrder()
74
- console.log(chalk.green(' - Dependency graph built successfully.'))
75
-
76
- console.log(chalk.blue('\nContract Repository:'))
77
- console.log(chalk.green(` - Found ${loader.contractRepository.getAll().length} unique contracts.`))
78
-
79
- // Check for ambiguous references that are actually being used
80
- const usedRefs = await extractUsedContractReferences(loader)
81
- const allAmbiguousRefs = loader.contractRepository.getAmbiguousReferences()
82
- const usedRefNames = usedRefs.map(ref => ref.reference)
83
- const usedAmbiguousRefs = allAmbiguousRefs.filter(ref => usedRefNames.includes(ref))
84
-
85
- if (usedAmbiguousRefs.length > 0) {
86
- console.log(chalk.red('\n - Found ambiguous contract references being used:'))
87
- for (const ref of usedAmbiguousRefs) {
88
- console.log(chalk.red(` ✗ "${ref}" could refer to multiple contracts`))
89
- }
90
- throw new Error(`Found ${usedAmbiguousRefs.length} ambiguous contract reference(s) being used. Please use more specific references to resolve ambiguity.`)
91
- }
92
- console.log(chalk.green(' - All used contract references are unambiguous.'))
93
-
94
- // Validate that all Contract() references point to existing contracts
95
- console.log(chalk.blue('\nValidating contract references...'))
96
- const missingRefs = await validateContractReferences(loader)
97
- if (missingRefs.length > 0) {
98
- console.log(chalk.red('\n - Found missing contract references:'))
99
- for (const ref of missingRefs) {
100
- console.log(chalk.red(` ✗ ${ref.reference} in ${ref.location}`))
101
- }
102
- throw new Error(`Found ${missingRefs.length} missing contract reference(s). Please ensure all referenced contracts exist.`)
103
- }
104
- console.log(chalk.green(' - All contract references are valid.'))
105
-
106
- // Validate constant references exist
107
- console.log(chalk.blue('\nValidating constant references...'))
108
- const topLevelConstants = loader.constants
109
- const missingConstantRefs: Array<{ ref: string; location: string }> = []
110
-
111
- // Check jobs (arguments and outputs within templates are resolved at runtime; here we just check expressions)
112
- for (const [jobName, job] of loader.jobs.entries()) {
113
- // Collect refs in job actions
114
- for (let i = 0; i < job.actions.length; i++) {
115
- const action = job.actions[i]
116
- const refs: string[] = []
117
- extractConstantRefs(action.arguments, refs)
118
- const jobConstants = job.constants || {}
119
- for (const r of refs) {
120
- if (!(r in jobConstants) && !topLevelConstants.has(r)) {
121
- missingConstantRefs.push({ ref: r, location: `job '${jobName}', action ${i + 1}${action.name ? ` '${action.name}'` : ''}` })
122
- }
123
- }
124
- }
125
- }
126
-
127
- // Check templates (setup/actions/outputs)
128
- for (const [templateName, template] of loader.templates.entries()) {
129
- // actions
130
- for (let i = 0; i < template.actions.length; i++) {
131
- const action = template.actions[i]
132
- const refs: string[] = []
133
- extractConstantRefs(action.arguments, refs, template)
134
- for (const r of refs) {
135
- if (!topLevelConstants.has(r)) {
136
- missingConstantRefs.push({ ref: r, location: `template '${templateName}', action ${i + 1}${action.name ? ` '${action.name}'` : ''}` })
137
- }
138
- }
139
- }
140
- // setup actions
141
- if (template.setup?.actions) {
142
- for (let i = 0; i < (template.setup.actions?.length || 0); i++) {
143
- const action = template.setup.actions![i]
144
- const refs: string[] = []
145
- extractConstantRefs(action.arguments, refs, template)
146
- for (const r of refs) {
147
- if (!topLevelConstants.has(r)) {
148
- missingConstantRefs.push({ ref: r, location: `template '${templateName}' setup, action ${i + 1}${action.name ? ` '${action.name}'` : ''}` })
149
- }
150
- }
151
- }
152
- }
153
- // outputs
154
- if (template.outputs) {
155
- const refs: string[] = []
156
- extractConstantRefs(template.outputs, refs, template)
157
- for (const r of refs) {
158
- if (!topLevelConstants.has(r)) {
159
- missingConstantRefs.push({ ref: r, location: `template '${templateName}' outputs` })
160
- }
161
- }
162
- }
163
- }
164
-
165
- if (missingConstantRefs.length > 0) {
166
- console.log(chalk.red('\n - Found missing constant references:'))
167
- for (const m of missingConstantRefs) {
168
- console.log(chalk.red(` ✗ ${m.ref} in ${m.location}`))
169
- }
170
- throw new Error(`Found ${missingConstantRefs.length} missing constant reference(s). Ensure they are defined at top-level or in the job's constants.`)
171
- }
172
- console.log(chalk.green(' - All constant references are valid.'))
173
-
174
- const runJobs = jobs.length > 0 ? jobs : undefined
175
- const runOnNetworks = resolveSelectedChainIds(options.network, allNetworks)
176
-
177
- const jobsToRun = new Set<string>()
178
- if (runJobs) {
179
- for (const jobName of runJobs) {
180
- if (!loader.jobs.has(jobName)) {
181
- throw new Error(`Specified job "${jobName}" not found in project.`)
182
- }
183
- jobsToRun.add(jobName)
184
- graph.getDependencies(jobName).forEach(dep => jobsToRun.add(dep))
185
- }
186
- } else {
187
- fullOrder.forEach(j => jobsToRun.add(j))
188
- }
189
-
190
- const jobExecutionPlan = fullOrder.filter(jobName => jobsToRun.has(jobName))
191
- const targetNetworks = runOnNetworks
192
- ? allNetworks.filter(n => runOnNetworks.includes(n.chainId))
193
- : allNetworks
194
-
195
- console.log(chalk.blue('\nExecution Plan:'))
196
- console.log(chalk.gray(` - Target Networks: ${targetNetworks.map(n => `${n.name} (ChainID: ${n.chainId})`).join(', ')}`))
197
- console.log(chalk.gray(` - Job Execution Order: ${jobExecutionPlan.join(' -> ')}`))
198
-
199
- console.log(chalk.green.bold('\n✅ Dry run successful. All job and template definitions appear to be valid.'))
200
-
201
- } catch (error) {
202
- console.error(chalk.red.bold('\n💥 DRY RUN FAILED!'))
203
- console.error(chalk.red(error instanceof Error ? error.message : String(error)))
204
- process.exit(1)
205
- }
206
- })
207
-
208
- return dryRun
209
- }
@@ -1,360 +0,0 @@
1
- import { Command } from 'commander'
2
- import chalk from 'chalk'
3
- import { loadNetworks } from '../lib/network-loader'
4
- import { resolveSingleChainId } from '../lib/network-selection'
5
- import { projectOption, verbosityOption } from './common'
6
- import { setVerbosity } from '../index'
7
- import * as solc from 'solc'
8
- import { createHash } from 'crypto'
9
-
10
- type ApiAction = 'getsourcecode' | 'getabi'
11
-
12
- interface EtherscanCmdBase {
13
- project: string
14
- verbose: number
15
- etherscanApiKey?: string
16
- network?: string
17
- address: string
18
- raw?: boolean
19
- }
20
-
21
- function getEtherscanApiUrl(chainId: number): string {
22
- return `https://api.etherscan.io/v2/api?chainid=${chainId}`
23
- }
24
-
25
- type EtherscanSourceEnvelope = {
26
- rawResult: Record<string, unknown>
27
- parsedSource: unknown // standard-json object or flattened string
28
- }
29
-
30
- async function fetchFromEtherscan(
31
- chainId: number,
32
- apiKey: string,
33
- address: string,
34
- action: ApiAction
35
- ): Promise<unknown | EtherscanSourceEnvelope> {
36
- const apiUrl = getEtherscanApiUrl(chainId)
37
- const params = new URLSearchParams({
38
- module: 'contract',
39
- action,
40
- apikey: apiKey,
41
- address
42
- })
43
- const resp = await fetch(`${apiUrl}&${params.toString()}`, {
44
- method: 'GET',
45
- signal: AbortSignal.timeout(20000)
46
- })
47
- if (!resp.ok) {
48
- throw new Error(`HTTP ${resp.status}: ${resp.statusText}`)
49
- }
50
- const data = await resp.json() as {
51
- status: string
52
- message?: string
53
- result:
54
- | string
55
- | Array<{
56
- SourceCode?: string
57
- ABI?: string
58
- [key: string]: unknown
59
- }>
60
- }
61
-
62
- if (data.status !== '1') {
63
- // Etherscan v2 returns status "0" with message in result
64
- const msg = typeof data.result === 'string' ? data.result : JSON.stringify(data.result)
65
- throw new Error(msg || 'Unknown Etherscan error')
66
- }
67
-
68
- if (action === 'getabi') {
69
- // data.result is a JSON-encoded string for ABI, parse and return as object
70
- if (typeof data.result !== 'string') {
71
- throw new Error('Unexpected ABI result format from Etherscan')
72
- }
73
- try {
74
- return JSON.parse(data.result as string)
75
- } catch (_e) {
76
- throw new Error('Failed to parse ABI JSON returned by Etherscan')
77
- }
78
- }
79
-
80
- if (action === 'getsourcecode') {
81
- // data.result[0].SourceCode is a string; when standard-json it is wrapped with a leading and trailing character
82
- if (!Array.isArray(data.result) || data.result.length === 0) {
83
- throw new Error('Empty result from Etherscan')
84
- }
85
- const first = (data.result as Array<{ SourceCode?: string }>)[0] as Record<string, unknown>
86
- const sourceCodeRaw = first?.SourceCode as string
87
- if (typeof sourceCodeRaw !== 'string' || sourceCodeRaw.length === 0) {
88
- throw new Error('No SourceCode found on Etherscan')
89
- }
90
-
91
- // The SourceCode may be:
92
- // 1) a raw source (flattened) string
93
- // 2) a JSON string that may be double-wrapped like: "{...}" or "{{...}}"
94
- // Try to normalize to a parsed JSON object when possible, otherwise return the raw string.
95
- const trimmed = sourceCodeRaw.trim()
96
- // Heuristic: if it starts with '{{' and ends with '}}', strip one layer
97
- const cleaned = trimmed.startsWith('{{') && trimmed.endsWith('}}')
98
- ? trimmed.slice(1, -1)
99
- : trimmed
100
-
101
- // Try to parse JSON; if it fails, return string
102
- try {
103
- const parsed = JSON.parse(cleaned)
104
- return { rawResult: first, parsedSource: parsed }
105
- } catch {
106
- // Not JSON, return as-is string
107
- return { rawResult: first, parsedSource: sourceCodeRaw }
108
- }
109
- }
110
-
111
- return data.result
112
- }
113
-
114
- export function makeEtherscanCommand(): Command {
115
- const etherscan = new Command('etherscan')
116
- .description('Etherscan helper commands (ABI/source fetch)')
117
-
118
- // Common options builder
119
- const withCommon = (cmd: Command) => {
120
- projectOption(cmd)
121
- verbosityOption(cmd)
122
- cmd
123
- .option('--etherscan-api-key <key>', 'Etherscan API key. Can also be set via ETHERSCAN_API_KEY env var.')
124
- .option('-n, --network <selector>', 'Target network (chain ID or name). When a name matches multiple networks, the first match is used.')
125
- .option('-a, --address <address>', 'Contract address to query', '')
126
- .option('--raw', 'Print raw response (no pretty JSON). Useful for piping.', false)
127
- return cmd
128
- }
129
-
130
- // etherscan abi
131
- const abi = new Command('abi')
132
- .description('Fetch contract ABI from Etherscan and print to stdout')
133
- withCommon(abi)
134
- abi.action(async (options: EtherscanCmdBase) => {
135
- try {
136
- setVerbosity(options.verbose as 0 | 1 | 2 | 3)
137
-
138
- const apiKey = options.etherscanApiKey || process.env.ETHERSCAN_API_KEY
139
- if (!apiKey) {
140
- console.error(chalk.red('Etherscan API key is required. Use --etherscan-api-key or set ETHERSCAN_API_KEY.'))
141
- process.exit(1)
142
- }
143
-
144
- if (!options.address) {
145
- console.error(chalk.red('Missing required --address option'))
146
- process.exit(1)
147
- }
148
-
149
- // Determine chainId
150
- let chainId: number | undefined
151
- const networks = await loadNetworks(options.project)
152
- if (options.network) {
153
- chainId = resolveSingleChainId(options.network, networks)
154
- } else if (networks.length === 1) {
155
- chainId = networks[0].chainId
156
- }
157
- if (!chainId) {
158
- console.error(chalk.red('Please provide --network <selector>. When multiple networks are configured, selection is required.'))
159
- process.exit(1)
160
- }
161
-
162
- const result = await fetchFromEtherscan(chainId!, apiKey, options.address, 'getabi')
163
-
164
- if (options.raw) {
165
- // raw: output minified JSON
166
- process.stdout.write(JSON.stringify(result))
167
- } else {
168
- // pretty
169
- console.log(JSON.stringify(result, null, 2))
170
- }
171
- } catch (error) {
172
- console.error(chalk.red('Error fetching ABI from Etherscan:'), error instanceof Error ? error.message : String(error))
173
- process.exit(1)
174
- }
175
- })
176
-
177
- // etherscan source
178
- const source = new Command('source')
179
- .description('Fetch contract source and emit a self-contained build-info JSON suitable for verification')
180
- withCommon(source)
181
- source.action(async (options: EtherscanCmdBase) => {
182
- try {
183
- setVerbosity(options.verbose as 0 | 1 | 2 | 3)
184
-
185
- const apiKey = options.etherscanApiKey || process.env.ETHERSCAN_API_KEY
186
- if (!apiKey) {
187
- console.error(chalk.red('Etherscan API key is required. Use --etherscan-api-key or set ETHERSCAN_API_KEY.'))
188
- process.exit(1)
189
- }
190
-
191
- if (!options.address) {
192
- console.error(chalk.red('Missing required --address option'))
193
- process.exit(1)
194
- }
195
-
196
- // Determine chainId
197
- let chainId: number | undefined
198
- const networks2 = await loadNetworks(options.project)
199
- if (options.network) {
200
- chainId = resolveSingleChainId(options.network, networks2)
201
- } else if (networks2.length === 1) {
202
- chainId = networks2[0].chainId
203
- }
204
- if (!chainId) {
205
- console.error(chalk.red('Please provide --network <selector>. When multiple networks are configured, selection is required.'))
206
- process.exit(1)
207
- }
208
-
209
- const result = await fetchFromEtherscan(chainId!, apiKey, options.address, 'getsourcecode') as EtherscanSourceEnvelope
210
-
211
- const raw = result.rawResult
212
- const parsed = result.parsedSource
213
-
214
- // Extract compiler version with commit from metadata when available
215
- const compilerVersion = (raw?.CompilerVersion as string | undefined) || ''
216
- const optimizationUsed = (raw?.OptimizationUsed as string | undefined) || ''
217
- const runsStr = (raw?.Runs as string | undefined) || ''
218
- const evmVersionRaw = (raw?.EVMVersion as string | undefined) || ''
219
- const isStandardJson = !!(parsed && typeof parsed === 'object' && 'language' in parsed && 'sources' in parsed)
220
-
221
- // If we have a standard JSON input, use it; otherwise build one from flattened source
222
- type SolcInput = {
223
- language: string
224
- sources: Record<string, { content?: string }>
225
- settings?: {
226
- optimizer?: { enabled?: boolean; runs?: number }
227
- evmVersion?: string
228
- outputSelection?: Record<string, Record<string, string[]>>
229
- viaIR?: boolean
230
- libraries?: Record<string, Record<string, string>>
231
- }
232
- }
233
- let input: SolcInput
234
- if (isStandardJson) {
235
- input = parsed as SolcInput
236
- // Ensure outputSelection includes required entries to get creation bytecode and metadata
237
- const currentSel = (input.settings?.outputSelection ?? {}) as Record<string, Record<string, string[]>>
238
- const mergedSel: Record<string, Record<string, string[]>> = {
239
- '*': {
240
- '*': Array.from(new Set<string>([
241
- ...((currentSel?.['*']?.['*']) || []),
242
- 'abi',
243
- 'evm.bytecode',
244
- 'evm.deployedBytecode',
245
- 'metadata',
246
- 'userdoc',
247
- 'devdoc',
248
- 'evm.methodIdentifiers'
249
- ]))
250
- }
251
- }
252
- input.settings = {
253
- ...(input.settings || {}),
254
- outputSelection: mergedSel
255
- }
256
- } else {
257
- // Build a minimal standard JSON input from flattened source
258
- const flattened = String(parsed || '')
259
- input = {
260
- language: 'Solidity',
261
- sources: {
262
- 'Flattened.sol': { content: flattened }
263
- },
264
- settings: {
265
- optimizer: {
266
- enabled: optimizationUsed === '1',
267
- runs: Number.isFinite(Number(runsStr)) ? Number(runsStr) : 200
268
- },
269
- evmVersion: evmVersionRaw && evmVersionRaw !== 'default' ? evmVersionRaw : undefined,
270
- outputSelection: {
271
- '*': {
272
- '*': [
273
- 'abi',
274
- 'evm.bytecode.object',
275
- 'evm.bytecode.sourceMap',
276
- 'evm.bytecode.linkReferences',
277
- 'evm.deployedBytecode.object',
278
- 'evm.deployedBytecode.sourceMap',
279
- 'evm.deployedBytecode.linkReferences',
280
- 'evm.deployedBytecode.immutableReferences',
281
- 'evm.methodIdentifiers',
282
- 'metadata'
283
- ]
284
- }
285
- }
286
- }
287
- }
288
- }
289
-
290
- // Compile with exact solc version when possible
291
- const solcInput = JSON.stringify(input)
292
- const versionTag = compilerVersion && compilerVersion.startsWith('v') ? compilerVersion : (compilerVersion ? `v${compilerVersion}` : '')
293
- let outputRaw: string
294
- if (versionTag) {
295
- outputRaw = await new Promise<string>((resolve, reject) => {
296
- // @ts-ignore - loadRemoteVersion exists in solc js
297
- solc.loadRemoteVersion(versionTag, (err: unknown, specificSolc: { compile: (input: string) => string } | undefined) => {
298
- if (err || !specificSolc) return reject((err as Error) || new Error('Failed to load solc version'))
299
- try {
300
- resolve(specificSolc.compile(solcInput))
301
- } catch (e) {
302
- reject(e)
303
- }
304
- })
305
- })
306
- } else {
307
- outputRaw = solc.compile(solcInput)
308
- }
309
- const output = JSON.parse(outputRaw)
310
-
311
- // Build build-info id as hex of sha1 of input
312
- const id = createHash('sha1').update(solcInput).digest('hex')
313
-
314
- // Determine solc versions
315
- const solcLongVersion = (output?.compiler?.version as string | undefined) || (compilerVersion ? compilerVersion.replace(/^v/, '') : undefined)
316
- const solcMaybe = solc as unknown as { version?: () => string }
317
- const solcVersion = (solcLongVersion || '').split('+')[0] || (typeof solcMaybe.version === 'function' ? solcMaybe.version() : 'unknown')
318
-
319
- // Augment settings with defaults similar to reference format
320
- const basePath = process.cwd()
321
- const includePaths = [basePath]
322
- const allowPaths = includePaths
323
-
324
- const buildInfo = {
325
- id,
326
- source_id_to_path: Object.fromEntries(Object.keys(input.sources).map((p, i) => [String(i), p])),
327
- language: input.language,
328
- _format: 'ethers-rs-sol-build-info-1',
329
- input: {
330
- version: solcVersion,
331
- language: input.language,
332
- sources: input.sources,
333
- settings: input.settings,
334
- evmVersion: input.settings?.evmVersion || 'cancun',
335
- viaIR: input.settings?.viaIR || false,
336
- libraries: input.settings?.libraries || {}
337
- },
338
- allowPaths,
339
- basePath,
340
- includePaths,
341
- output: {
342
- contracts: output.contracts || {},
343
- sources: output.sources || {}
344
- },
345
- solcLongVersion: solcLongVersion || solcVersion,
346
- solcVersion: solcVersion
347
- }
348
-
349
- // Print build-info JSON
350
- console.log(options.raw ? JSON.stringify(buildInfo) : JSON.stringify(buildInfo, null, 2))
351
- } catch (error) {
352
- console.error(chalk.red('Error fetching source from Etherscan:'), error instanceof Error ? error.message : String(error))
353
- process.exit(1)
354
- }
355
- })
356
-
357
- etherscan.addCommand(abi)
358
- etherscan.addCommand(source)
359
- return etherscan
360
- }
@@ -1,6 +0,0 @@
1
- export * from './run'
2
- export * from './dry'
3
- export * from './list'
4
- export * from './utils'
5
- export * from './etherscan'
6
- export * from './provenance'