@0xsequence/catapult 1.3.3 → 1.3.5

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 (45) hide show
  1. package/README.md +16 -0
  2. package/dist/commands/run.d.ts.map +1 -1
  3. package/dist/commands/run.js +4 -2
  4. package/dist/commands/run.js.map +1 -1
  5. package/dist/lib/__tests__/deployer.spec.js +71 -1
  6. package/dist/lib/__tests__/deployer.spec.js.map +1 -1
  7. package/dist/lib/core/__tests__/engine.spec.js +270 -0
  8. package/dist/lib/core/__tests__/engine.spec.js.map +1 -1
  9. package/dist/lib/core/__tests__/resolver.spec.js +199 -2
  10. package/dist/lib/core/__tests__/resolver.spec.js.map +1 -1
  11. package/dist/lib/core/engine.d.ts +13 -0
  12. package/dist/lib/core/engine.d.ts.map +1 -1
  13. package/dist/lib/core/engine.js +59 -5
  14. package/dist/lib/core/engine.js.map +1 -1
  15. package/dist/lib/core/resolver.d.ts +1 -0
  16. package/dist/lib/core/resolver.d.ts.map +1 -1
  17. package/dist/lib/core/resolver.js +38 -8
  18. package/dist/lib/core/resolver.js.map +1 -1
  19. package/dist/lib/deployer.d.ts +2 -0
  20. package/dist/lib/deployer.d.ts.map +1 -1
  21. package/dist/lib/deployer.js +18 -1
  22. package/dist/lib/deployer.js.map +1 -1
  23. package/dist/lib/events/cli-adapter.d.ts.map +1 -1
  24. package/dist/lib/events/cli-adapter.js +20 -0
  25. package/dist/lib/events/cli-adapter.js.map +1 -1
  26. package/dist/lib/events/types.d.ts +25 -1
  27. package/dist/lib/events/types.d.ts.map +1 -1
  28. package/dist/lib/types/values.d.ts +8 -1
  29. package/dist/lib/types/values.d.ts.map +1 -1
  30. package/dist/lib/utils/assertion.d.ts +4 -0
  31. package/dist/lib/utils/assertion.d.ts.map +1 -0
  32. package/dist/lib/utils/assertion.js +27 -0
  33. package/dist/lib/utils/assertion.js.map +1 -0
  34. package/package.json +1 -1
  35. package/src/commands/run.ts +4 -1
  36. package/src/lib/__tests__/deployer.spec.ts +108 -1
  37. package/src/lib/core/__tests__/engine.spec.ts +321 -0
  38. package/src/lib/core/__tests__/resolver.spec.ts +231 -3
  39. package/src/lib/core/engine.ts +92 -6
  40. package/src/lib/core/resolver.ts +46 -9
  41. package/src/lib/deployer.ts +28 -1
  42. package/src/lib/events/cli-adapter.ts +24 -0
  43. package/src/lib/events/types.ts +29 -1
  44. package/src/lib/types/values.ts +10 -1
  45. package/src/lib/utils/assertion.ts +24 -0
@@ -1,7 +1,7 @@
1
1
  import { ethers } from 'ethers'
2
2
  import { ValueResolver } from '../resolver'
3
3
  import { ExecutionContext } from '../context'
4
- import { BasicArithmeticValue, Network, ReadBalanceValue, ComputeCreate2Value, ConstructorEncodeValue, AbiEncodeValue, AbiPackValue, CallValue, ContractExistsValue } from '../../types'
4
+ import { BasicArithmeticValue, Network, ReadBalanceValue, ComputeCreate2Value, ConstructorEncodeValue, AbiEncodeValue, AbiPackValue, CallValue, ContractExistsValue, ComputeCreateValue } from '../../types'
5
5
  import { ContractRepository } from '../../contracts/repository'
6
6
 
7
7
  describe('ValueResolver', () => {
@@ -15,7 +15,14 @@ describe('ValueResolver', () => {
15
15
  mockRegistry = new ContractRepository()
16
16
  // Allow configuring RPC URL via environment variable for CI
17
17
  const rpcUrl = process.env.RPC_URL || 'http://127.0.0.1:8545'
18
- mockNetwork = { name: 'testnet', chainId: 999, rpcUrl }
18
+ mockNetwork = {
19
+ name: 'testnet',
20
+ chainId: 999,
21
+ rpcUrl,
22
+ supports: ["sourcify", "etherscan_v2"],
23
+ gasLimit: 10000000,
24
+ evmVersion: 'cancun',
25
+ }
19
26
  // A dummy private key is fine as these tests don't send transactions
20
27
  const mockPrivateKey = '0x0000000000000000000000000000000000000000000000000000000000000001'
21
28
  context = new ExecutionContext(mockNetwork, mockPrivateKey, mockRegistry)
@@ -494,6 +501,155 @@ describe('ValueResolver', () => {
494
501
  })
495
502
  })
496
503
 
504
+ describe('compute-create', () => {
505
+ it('should compute CREATE address with hardcoded test case 1', async () => {
506
+ const value: ComputeCreateValue = {
507
+ type: 'compute-create',
508
+ arguments: {
509
+ deployerAddress: '0x0000000000000000000000000000000000000000',
510
+ nonce: '0',
511
+ },
512
+ }
513
+ const result = await resolver.resolve(value, context)
514
+ expect(result).toBe('0xBd770416a3345F91E4B34576cb804a576fa48EB1')
515
+ })
516
+
517
+ it('should compute CREATE address with hardcoded test case 2', async () => {
518
+ const value: ComputeCreateValue = {
519
+ type: 'compute-create',
520
+ arguments: {
521
+ deployerAddress: '0xC6064FfBaDB0687Da29721C8EC02ACa71e735a3e',
522
+ nonce: '1',
523
+ },
524
+ }
525
+ const result = await resolver.resolve(value, context)
526
+ expect(result).toBe('0x6d2E686984620c01Af3cd125F9E1A2E23a972FFc')
527
+ })
528
+
529
+ it('should compute CREATE address with hardcoded test case 3', async () => {
530
+ const value: ComputeCreateValue = {
531
+ type: 'compute-create',
532
+ arguments: {
533
+ deployerAddress: '0xC6064FfBaDB0687Da29721C8EC02ACa71e735a3e',
534
+ nonce: '2',
535
+ },
536
+ }
537
+ const result = await resolver.resolve(value, context)
538
+ expect(result).toBe('0xBA6CfaFc33eD8229D2Af9a5a7BC22e8834cE0873')
539
+ })
540
+
541
+ it('should compute CREATE address with hardcoded test case ERC-2470', async () => {
542
+ const value: ComputeCreateValue = {
543
+ type: 'compute-create',
544
+ arguments: {
545
+ deployerAddress: '0xBb6e024b9cFFACB947A71991E386681B1Cd1477D',
546
+ nonce: '0',
547
+ },
548
+ }
549
+ const result = await resolver.resolve(value, context)
550
+ expect(result).toBe('0xce0042B868300000d44A59004Da54A005ffdcf9f')
551
+ })
552
+
553
+ it('should compute CREATE address with hardcoded test case Universal Deployer', async () => {
554
+ const value: ComputeCreateValue = {
555
+ type: 'compute-create',
556
+ arguments: {
557
+ deployerAddress: '0x9c5a87452d4FAC0cbd53BDCA580b20A45526B3AB',
558
+ nonce: '0',
559
+ },
560
+ }
561
+ const result = await resolver.resolve(value, context)
562
+ expect(result).toBe('0x1B926fBB24A9F78DCDd3272f2d86F5D0660E59c0')
563
+ })
564
+
565
+ it('should resolve values from context before computing CREATE address', async () => {
566
+ context.setOutput('myDeployer', '0x0000000000000000000000000000000000000000')
567
+ context.setOutput('myNonce', '0')
568
+
569
+ const value: ComputeCreateValue = {
570
+ type: 'compute-create',
571
+ arguments: {
572
+ deployerAddress: '{{myDeployer}}',
573
+ nonce: '{{myNonce}}',
574
+ },
575
+ }
576
+ const result = await resolver.resolve(value, context)
577
+ expect(result).toBe('0xBd770416a3345F91E4B34576cb804a576fa48EB1')
578
+ })
579
+
580
+ it('should throw error for invalid deployer address', async () => {
581
+ const value: ComputeCreateValue = {
582
+ type: 'compute-create',
583
+ arguments: {
584
+ deployerAddress: 'invalid-address',
585
+ nonce: '0',
586
+ },
587
+ }
588
+
589
+ await expect(resolver.resolve(value, context)).rejects.toThrow('Invalid deployer address: invalid-address')
590
+ })
591
+
592
+ it('should throw error for invalid nonce', async () => {
593
+ const value: ComputeCreateValue = {
594
+ type: 'compute-create',
595
+ arguments: {
596
+ deployerAddress: '0x0000000000000000000000000000000000000000',
597
+ nonce: 'invalid-nonce',
598
+ },
599
+ }
600
+
601
+ await expect(resolver.resolve(value, context)).rejects.toThrow('Invalid nonce: invalid-nonce')
602
+ })
603
+
604
+ it('should throw error for null deployer address', async () => {
605
+ const value: ComputeCreateValue = {
606
+ type: 'compute-create',
607
+ arguments: {
608
+ deployerAddress: null as any,
609
+ nonce: '0',
610
+ },
611
+ }
612
+
613
+ await expect(resolver.resolve(value, context)).rejects.toThrow('Invalid deployer address: null')
614
+ })
615
+
616
+ it('should throw error for undefined nonce', async () => {
617
+ const value: ComputeCreateValue = {
618
+ type: 'compute-create',
619
+ arguments: {
620
+ deployerAddress: '0x0000000000000000000000000000000000000000',
621
+ nonce: undefined as any,
622
+ },
623
+ }
624
+
625
+ await expect(resolver.resolve(value, context)).rejects.toThrow('Invalid nonce: undefined')
626
+ })
627
+
628
+ it('should handle checksummed addresses', async () => {
629
+ const value: ComputeCreateValue = {
630
+ type: 'compute-create',
631
+ arguments: {
632
+ deployerAddress: '0xdEADBEeF00000000000000000000000000000000',
633
+ nonce: '0',
634
+ },
635
+ }
636
+ const result = await resolver.resolve(value, context)
637
+ expect(result).toBe('0xf2048C36a5536FeA3Bc71d49ed59f2c65C546EEA')
638
+ })
639
+
640
+ it('should handle number nonce', async () => {
641
+ const value: ComputeCreateValue = {
642
+ type: 'compute-create',
643
+ arguments: {
644
+ deployerAddress: '0x0000000000000000000000000000000000000000',
645
+ nonce: 69420,
646
+ },
647
+ }
648
+ const result = await resolver.resolve(value, context)
649
+ expect(result).toBe('0x7Bd7F19787DA009bD75b849c92Db10CE11916487')
650
+ })
651
+ })
652
+
497
653
  describe('constructor-encode', () => {
498
654
  it('should encode creation code with no constructor arguments', async () => {
499
655
  const value = {
@@ -1057,7 +1213,7 @@ describe('ValueResolver', () => {
1057
1213
  })
1058
1214
  })
1059
1215
 
1060
- describe('artifact function expressions', () => {
1216
+ describe('contract artifact function expressions', () => {
1061
1217
  beforeEach(() => {
1062
1218
  // Add test artifacts to the registry
1063
1219
  const testArtifact1 = {
@@ -1341,6 +1497,78 @@ describe('ValueResolver', () => {
1341
1497
  })
1342
1498
  })
1343
1499
 
1500
+ describe('network function expressions', () => {
1501
+ describe('Network().property', () => {
1502
+ it('should return name for valid network', async () => {
1503
+ const result = await resolver.resolve('{{Network().name}}', context)
1504
+ expect(result).toBe(mockNetwork.name)
1505
+ })
1506
+
1507
+ it('should return handle whitespace after expression', async () => {
1508
+ const result = await resolver.resolve('{{Network().name }}', context)
1509
+ expect(result).toBe(mockNetwork.name)
1510
+ })
1511
+
1512
+ it('should return handle whitespace before expression', async () => {
1513
+ const result = await resolver.resolve('{{ Network().name}}', context)
1514
+ expect(result).toBe(mockNetwork.name)
1515
+ })
1516
+
1517
+ it('should return handle whitespace around expression', async () => {
1518
+ const result = await resolver.resolve('{{ Network().name }}', context)
1519
+ expect(result).toBe(mockNetwork.name)
1520
+ })
1521
+
1522
+ it('should return chainId for valid network', async () => {
1523
+ const result = await resolver.resolve('{{Network().chainId}}', context)
1524
+ expect(result).toBe(mockNetwork.chainId)
1525
+ })
1526
+
1527
+ it('should return rpcUrl for valid network', async () => {
1528
+ const result = await resolver.resolve('{{Network().rpcUrl}}', context)
1529
+ expect(result).toBe(mockNetwork.rpcUrl)
1530
+ })
1531
+
1532
+ it('should return supports for valid network', async () => {
1533
+ const result = await resolver.resolve('{{Network().supports}}', context)
1534
+ expect(result).toBe(mockNetwork.supports)
1535
+ })
1536
+
1537
+ it('should return gasLimit for valid network', async () => {
1538
+ const result = await resolver.resolve('{{Network().gasLimit}}', context)
1539
+ expect(result).toBe(mockNetwork.gasLimit)
1540
+ })
1541
+
1542
+ it('should return testnet for valid network', async () => {
1543
+ // Note: Expect true because testnet is not set
1544
+ const result = await resolver.resolve('{{Network().testnet}}', context)
1545
+ expect(result).toBe(false)
1546
+ })
1547
+
1548
+ it('should return evmVersion for valid network', async () => {
1549
+ const result = await resolver.resolve('{{Network().evmVersion}}', context)
1550
+ expect(result).toBe(mockNetwork.evmVersion)
1551
+ })
1552
+ })
1553
+
1554
+ describe('invalid Network expressions', () => {
1555
+ it('should fail for invalid property', async () => {
1556
+ await expect(resolver.resolve('{{Network().invalid}}', context))
1557
+ .rejects.toThrow('Property "invalid" does not exist on network')
1558
+ })
1559
+
1560
+ it('should fail for undefined property', async () => {
1561
+ await expect(resolver.resolve('{{Network().undefined}}', context))
1562
+ .rejects.toThrow('Property "undefined" does not exist on network')
1563
+ })
1564
+
1565
+ it('should fail for network with reference', async () => {
1566
+ await expect(resolver.resolve('{{Network(testnet).name}}', context))
1567
+ .rejects.toThrow('Failed to resolve expression \"{{Network(testnet).name}}\". It is not a valid Contract(...) or Network() reference, local scope variable, constant, or a known output.')
1568
+ })
1569
+ })
1570
+ })
1571
+
1344
1572
  describe('call', () => {
1345
1573
  let testContractAddress: string
1346
1574
  let anvilProvider: ethers.JsonRpcProvider
@@ -13,6 +13,7 @@ export type EngineOptions = {
13
13
  verificationRegistry?: VerificationPlatformRegistry
14
14
  noPostCheckConditions?: boolean
15
15
  allowMultipleNicksMethodTests?: boolean
16
+ ignoreVerifyErrors?: boolean
16
17
  }
17
18
 
18
19
  /**
@@ -27,7 +28,17 @@ export class ExecutionEngine {
27
28
  private readonly verificationRegistry: VerificationPlatformRegistry
28
29
  private readonly noPostCheckConditions: boolean
29
30
  private readonly allowMultipleNicksMethodTests: boolean
31
+ private readonly ignoreVerifyErrors: boolean
30
32
  private nicksMethodTested: boolean = false
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
+ }> = []
31
42
 
32
43
  constructor(templates: Map<string, Template>, options?: EngineOptions) {
33
44
  this.resolver = new ValueResolver()
@@ -36,6 +47,7 @@ export class ExecutionEngine {
36
47
  this.verificationRegistry = options?.verificationRegistry || createDefaultVerificationRegistry()
37
48
  this.noPostCheckConditions = options?.noPostCheckConditions ?? false
38
49
  this.allowMultipleNicksMethodTests = options?.allowMultipleNicksMethodTests ?? false
50
+ this.ignoreVerifyErrors = options?.ignoreVerifyErrors ?? false
39
51
  }
40
52
 
41
53
  /**
@@ -631,6 +643,20 @@ export class ExecutionEngine {
631
643
  )
632
644
  anySuccess = true
633
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
+
634
660
  // Log the error but continue with other platforms
635
661
  this.events.emitEvent({
636
662
  type: 'verification_failed',
@@ -640,14 +666,26 @@ export class ExecutionEngine {
640
666
  address,
641
667
  contractName,
642
668
  platform: platform.name,
643
- error: error instanceof Error ? error.message : String(error)
669
+ error: errorMessage
644
670
  }
645
671
  })
646
672
  }
647
673
  }
648
674
 
649
675
  if (!anySuccess) {
650
- throw new Error(`Verification failed on all configured platforms for network ${network.name}`)
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
+ }
651
689
  }
652
690
  } else {
653
691
  // Handle specific platform(s) verification
@@ -673,6 +711,20 @@ export class ExecutionEngine {
673
711
  )
674
712
  anySuccess = true
675
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
+
676
728
  // Log the error but continue with other platforms if multiple specified
677
729
  this.events.emitEvent({
678
730
  type: 'verification_failed',
@@ -682,19 +734,31 @@ export class ExecutionEngine {
682
734
  address,
683
735
  contractName,
684
736
  platform: platform.name,
685
- error: error instanceof Error ? error.message : String(error)
737
+ error: errorMessage
686
738
  }
687
739
  })
688
740
 
689
- // If only one platform specified, re-throw the error
690
- if (platformsToTry.length === 1) {
741
+ // If only one platform specified, re-throw the error unless ignoreVerifyErrors is enabled
742
+ if (platformsToTry.length === 1 && !this.ignoreVerifyErrors) {
691
743
  throw error
692
744
  }
693
745
  }
694
746
  }
695
747
 
696
748
  if (!anySuccess && platformsToTry.length > 1) {
697
- throw new Error(`Verification failed on all specified platforms: ${platformsToTry.join(', ')}`)
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
+ }
698
762
  }
699
763
  }
700
764
 
@@ -1635,4 +1699,26 @@ export class ExecutionEngine {
1635
1699
 
1636
1700
  return sorted
1637
1701
  }
1702
+
1703
+ /**
1704
+ * Get all verification warnings that were collected when ignoreVerifyErrors is enabled
1705
+ */
1706
+ public getVerificationWarnings(): Array<{
1707
+ actionName: string
1708
+ address: string
1709
+ contractName: string
1710
+ platform: string
1711
+ error: string
1712
+ jobName?: string
1713
+ networkName?: string
1714
+ }> {
1715
+ return [...this.verificationWarnings]
1716
+ }
1717
+
1718
+ /**
1719
+ * Clear verification warnings (useful for testing)
1720
+ */
1721
+ public clearVerificationWarnings(): void {
1722
+ this.verificationWarnings = []
1723
+ }
1638
1724
  }
@@ -5,16 +5,17 @@ import {
5
5
  AbiEncodeValue,
6
6
  AbiPackValue,
7
7
  ConstructorEncodeValue,
8
+ ComputeCreateValue,
8
9
  ComputeCreate2Value,
9
10
  ReadBalanceValue,
10
11
  BasicArithmeticValue,
11
12
  CallValue,
12
- ContractExistsCondition,
13
13
  ContractExistsValue,
14
14
  JobCompletedValue,
15
15
  ReadJsonValue,
16
16
  } from '../types'
17
17
  import { ExecutionContext } from './context'
18
+ import { isAddress, isBigNumberish, isBytesLike } from '../utils/assertion'
18
19
 
19
20
  /**
20
21
  * A scope for resolving local variables, such as template arguments.
@@ -115,6 +116,22 @@ export class ValueResolver {
115
116
  return value
116
117
  }
117
118
 
119
+ // Check for Network(...) property access
120
+ const networkMatch = expression.match(/^Network\(\)\.(.\w+)$/)
121
+ if (networkMatch) {
122
+ const [, property] = networkMatch
123
+ const network = context.getNetwork()
124
+ if (property === 'testnet') {
125
+ // Special case for testnet property. Default to false if not set.
126
+ return !!network.testnet
127
+ }
128
+ const value = (network as any)[property]
129
+ if (value === undefined) {
130
+ throw new Error(`Property "${property}" does not exist on network`)
131
+ }
132
+ return value
133
+ }
134
+
118
135
  // Check scope for local variables (template arguments) first
119
136
  if (scope.has(expression)) {
120
137
  return scope.get(expression)
@@ -131,7 +148,7 @@ export class ValueResolver {
131
148
  return context.getOutput(expression)
132
149
  } catch (e) {
133
150
  // Provide a more helpful error if an unresolved reference is found
134
- throw new Error(`Failed to resolve expression "{{${expression}}}". It is not a valid Contract(...) reference, local scope variable, constant, or a known output.`)
151
+ throw new Error(`Failed to resolve expression "{{${expression}}}". It is not a valid Contract(...) or Network() reference, local scope variable, constant, or a known output.`)
135
152
  }
136
153
  }
137
154
 
@@ -154,6 +171,8 @@ export class ValueResolver {
154
171
  return this.resolveAbiPack(resolvedArgs as AbiPackValue['arguments'])
155
172
  case 'constructor-encode':
156
173
  return this.resolveConstructorEncode(resolvedArgs as ConstructorEncodeValue['arguments'])
174
+ case 'compute-create':
175
+ return this.resolveComputeCreate(resolvedArgs as ComputeCreateValue['arguments'])
157
176
  case 'compute-create2':
158
177
  return this.resolveComputeCreate2(resolvedArgs as ComputeCreate2Value['arguments'])
159
178
  case 'read-balance':
@@ -260,7 +279,7 @@ export class ValueResolver {
260
279
  }
261
280
 
262
281
  // Validate that creation code is valid bytecode
263
- if (!ethers.isBytesLike(creationCode)) {
282
+ if (!isBytesLike(creationCode)) {
264
283
  throw new Error(`Invalid creation code: ${creationCode}`)
265
284
  }
266
285
 
@@ -280,18 +299,36 @@ export class ValueResolver {
280
299
  return '0x' + cleanCreationCode + cleanEncodedArgs
281
300
  }
282
301
 
302
+ private resolveComputeCreate(args: ComputeCreateValue['arguments']): string {
303
+ const { deployerAddress, nonce } = args
304
+ // Check if the deployer address is a valid address
305
+ if (!isAddress(deployerAddress)) {
306
+ throw new Error(`Invalid deployer address: ${deployerAddress}`)
307
+ }
308
+ // Check if the nonce is a valid value
309
+ if (!isBigNumberish(nonce)) {
310
+ throw new Error(`Invalid nonce: ${nonce}`)
311
+ }
312
+ const bnNonce = ethers.toBigInt(nonce)
313
+ // Create the create address
314
+ return ethers.getCreateAddress({
315
+ from: deployerAddress,
316
+ nonce: bnNonce,
317
+ })
318
+ }
319
+
283
320
  private resolveComputeCreate2(args: ComputeCreate2Value['arguments']): string {
284
321
  const { deployerAddress, salt, initCode } = args
285
322
  // Check if the deployer address is a valid address
286
- if (!ethers.isAddress(deployerAddress)) {
323
+ if (!isAddress(deployerAddress)) {
287
324
  throw new Error(`Invalid deployer address: ${deployerAddress}`)
288
325
  }
289
326
  // Check if the salt is a valid bytes value
290
- if (!ethers.isBytesLike(salt)) {
327
+ if (!isBytesLike(salt)) {
291
328
  throw new Error(`Invalid salt: ${salt}`)
292
329
  }
293
330
  // Check if the init code is a valid bytes value
294
- if (!ethers.isBytesLike(initCode)) {
331
+ if (!isBytesLike(initCode)) {
295
332
  throw new Error(`Invalid init code: ${initCode}`)
296
333
  }
297
334
  // Hash the init code using Keccak256
@@ -304,7 +341,7 @@ export class ValueResolver {
304
341
  // Check if the address is a valid address
305
342
  const addressValue = args.address as any
306
343
 
307
- if (!ethers.isAddress(addressValue)) {
344
+ if (!isAddress(addressValue)) {
308
345
  throw new Error(`Invalid address: ${addressValue}`)
309
346
  }
310
347
 
@@ -349,7 +386,7 @@ export class ValueResolver {
349
386
  }
350
387
 
351
388
  // Validate that the target address is a valid Ethereum address
352
- if (!ethers.isAddress(to)) {
389
+ if (!isAddress(to)) {
353
390
  throw new Error(`call: invalid target address: ${to}`)
354
391
  }
355
392
 
@@ -407,7 +444,7 @@ export class ValueResolver {
407
444
  private async resolveContractExists(args: ContractExistsValue['arguments'], context: ExecutionContext): Promise<boolean> {
408
445
  const { address } = args
409
446
 
410
- if (!ethers.isAddress(address)) {
447
+ if (!isAddress(address)) {
411
448
  throw new Error(`contract-exists: invalid address: ${address}`)
412
449
  }
413
450
 
@@ -52,6 +52,9 @@ export interface DeployerOptions {
52
52
 
53
53
  /** Optional: Show end-of-run summary (default: true). */
54
54
  showSummary?: boolean
55
+
56
+ /** Optional: Convert verification errors to warnings instead of failing (default: false). */
57
+ ignoreVerifyErrors?: boolean
55
58
  }
56
59
 
57
60
  /**
@@ -157,7 +160,8 @@ export class Deployer {
157
160
  const engine = new ExecutionEngine(this.loader.templates, {
158
161
  eventEmitter: this.events,
159
162
  verificationRegistry,
160
- noPostCheckConditions: this.noPostCheckConditions
163
+ noPostCheckConditions: this.noPostCheckConditions,
164
+ ignoreVerifyErrors: this.options.ignoreVerifyErrors ?? false
161
165
  })
162
166
 
163
167
  // Track if any jobs have failed
@@ -312,6 +316,11 @@ export class Deployer {
312
316
  // 5. Write results to output files.
313
317
  await this.writeOutputFiles()
314
318
 
319
+ // Show verification warnings report if ignoreVerifyErrors is enabled
320
+ if (this.options.ignoreVerifyErrors) {
321
+ this.emitVerificationWarningsReport(engine)
322
+ }
323
+
315
324
  // Emit end-of-run summary before final status
316
325
  if (this.showSummary) {
317
326
  this.emitRunSummary(hasFailures)
@@ -423,6 +432,24 @@ export class Deployer {
423
432
  this.events.emitEvent(summaryEvent)
424
433
  }
425
434
 
435
+ /**
436
+ * Emit verification warnings report when ignoreVerifyErrors is enabled
437
+ */
438
+ private emitVerificationWarningsReport(engine: ExecutionEngine): void {
439
+ const warnings = engine.getVerificationWarnings()
440
+
441
+ if (warnings.length > 0) {
442
+ this.events.emitEvent({
443
+ type: 'verification_warnings_report',
444
+ level: 'warn',
445
+ data: {
446
+ totalWarnings: warnings.length,
447
+ warnings: warnings
448
+ }
449
+ })
450
+ }
451
+ }
452
+
426
453
  /**
427
454
  * Determines the final, ordered list of jobs to execute based on user input.
428
455
  * If a user requests specific jobs, this ensures all their dependencies are also included.
@@ -266,6 +266,30 @@ export class CLIEventAdapter {
266
266
  // Keep short to reduce noise
267
267
  console.log(chalk.gray(` Verification retry ${event.data.attempt}/${event.data.maxRetries}: ${event.data.error}`))
268
268
  break
269
+
270
+ case 'verification_skipped':
271
+ console.log(chalk.yellow(` ⚠️ ${event.data.reason}`))
272
+ break
273
+
274
+ case 'verification_warnings_report':
275
+ // Display detailed verification warnings report
276
+ console.log(chalk.yellow('\n📋 Verification Warnings Report'))
277
+ console.log(chalk.yellow(` Total warnings: ${event.data.totalWarnings}`))
278
+ console.log('')
279
+
280
+ if (event.data.warnings && event.data.warnings.length > 0) {
281
+ for (const warning of event.data.warnings) {
282
+ console.log(chalk.red(` ❌ ${warning.actionName} (${warning.contractName})`))
283
+ console.log(chalk.gray(` Address: ${warning.address}`))
284
+ console.log(chalk.gray(` Platform: ${warning.platform}`))
285
+ if (warning.networkName) {
286
+ console.log(chalk.gray(` Network: ${warning.networkName}`))
287
+ }
288
+ console.log(chalk.gray(` Error: ${warning.error}`))
289
+ console.log('')
290
+ }
291
+ }
292
+ break
269
293
  case 'contract_created':
270
294
  console.log(chalk.gray(` contract: ${event.data.contractAddress}`))
271
295
  break
@@ -429,6 +429,32 @@ export interface VerificationRetryEvent extends BaseEvent {
429
429
  }
430
430
  }
431
431
 
432
+ export interface VerificationSkippedEvent extends BaseEvent {
433
+ type: 'verification_skipped'
434
+ level: 'warn'
435
+ data: {
436
+ actionName: string
437
+ reason: string
438
+ }
439
+ }
440
+
441
+ export interface VerificationWarningsReportEvent extends BaseEvent {
442
+ type: 'verification_warnings_report'
443
+ level: 'warn'
444
+ data: {
445
+ totalWarnings: number
446
+ warnings: Array<{
447
+ actionName: string
448
+ address: string
449
+ contractName: string
450
+ platform: string
451
+ error: string
452
+ jobName?: string
453
+ networkName?: string
454
+ }>
455
+ }
456
+ }
457
+
432
458
  // End-of-run summary
433
459
  export interface RunSummaryEvent extends BaseEvent {
434
460
  type: 'run_summary'
@@ -488,5 +514,7 @@ export type DeploymentEvent =
488
514
  | VerificationSubmittedEvent
489
515
  | VerificationCompletedEvent
490
516
  | VerificationFailedEvent
491
- | VerificationRetryEvent
517
+ | VerificationRetryEvent
518
+ | VerificationSkippedEvent
519
+ | VerificationWarningsReportEvent
492
520
  | RunSummaryEvent