@diamondslab/diamonds 1.0.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 (229) hide show
  1. package/README.md +618 -0
  2. package/diamonds/README.md +3 -0
  3. package/dist/core/CallbackManager.d.ts +13 -0
  4. package/dist/core/CallbackManager.d.ts.map +1 -0
  5. package/dist/core/CallbackManager.js +95 -0
  6. package/dist/core/CallbackManager.js.map +1 -0
  7. package/dist/core/DeploymentManager.d.ts +10 -0
  8. package/dist/core/DeploymentManager.d.ts.map +1 -0
  9. package/dist/core/DeploymentManager.js +50 -0
  10. package/dist/core/DeploymentManager.js.map +1 -0
  11. package/dist/core/Diamond.d.ts +58 -0
  12. package/dist/core/Diamond.d.ts.map +1 -0
  13. package/dist/core/Diamond.js +146 -0
  14. package/dist/core/Diamond.js.map +1 -0
  15. package/dist/core/DiamondDeployer.d.ts +10 -0
  16. package/dist/core/DiamondDeployer.d.ts.map +1 -0
  17. package/dist/core/DiamondDeployer.js +33 -0
  18. package/dist/core/DiamondDeployer.js.map +1 -0
  19. package/dist/core/index.d.ts +5 -0
  20. package/dist/core/index.d.ts.map +1 -0
  21. package/dist/core/index.js +12 -0
  22. package/dist/core/index.js.map +1 -0
  23. package/dist/index.d.ts +6 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +22 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/repositories/DBDeploymentRepository.d.ts +1 -0
  28. package/dist/repositories/DBDeploymentRepository.d.ts.map +1 -0
  29. package/dist/repositories/DBDeploymentRepository.js +20 -0
  30. package/dist/repositories/DBDeploymentRepository.js.map +1 -0
  31. package/dist/repositories/DeploymentRepository.d.ts +8 -0
  32. package/dist/repositories/DeploymentRepository.d.ts.map +1 -0
  33. package/dist/repositories/DeploymentRepository.js +7 -0
  34. package/dist/repositories/DeploymentRepository.js.map +1 -0
  35. package/dist/repositories/FileDeploymentRepository.d.ts +18 -0
  36. package/dist/repositories/FileDeploymentRepository.d.ts.map +1 -0
  37. package/dist/repositories/FileDeploymentRepository.js +58 -0
  38. package/dist/repositories/FileDeploymentRepository.js.map +1 -0
  39. package/dist/repositories/databaseHandler.d.ts +1 -0
  40. package/dist/repositories/databaseHandler.d.ts.map +1 -0
  41. package/dist/repositories/databaseHandler.js +13 -0
  42. package/dist/repositories/databaseHandler.js.map +1 -0
  43. package/dist/repositories/index.d.ts +4 -0
  44. package/dist/repositories/index.d.ts.map +1 -0
  45. package/dist/repositories/index.js +20 -0
  46. package/dist/repositories/index.js.map +1 -0
  47. package/dist/repositories/jsonFileHandler.d.ts +81 -0
  48. package/dist/repositories/jsonFileHandler.d.ts.map +1 -0
  49. package/dist/repositories/jsonFileHandler.js +223 -0
  50. package/dist/repositories/jsonFileHandler.js.map +1 -0
  51. package/dist/repositories/prismaDBHandler.d.ts +1 -0
  52. package/dist/repositories/prismaDBHandler.d.ts.map +1 -0
  53. package/dist/repositories/prismaDBHandler.js +11 -0
  54. package/dist/repositories/prismaDBHandler.js.map +1 -0
  55. package/dist/schemas/DeploymentSchema.d.ts +309 -0
  56. package/dist/schemas/DeploymentSchema.d.ts.map +1 -0
  57. package/dist/schemas/DeploymentSchema.js +56 -0
  58. package/dist/schemas/DeploymentSchema.js.map +1 -0
  59. package/dist/schemas/index.d.ts +2 -0
  60. package/dist/schemas/index.d.ts.map +1 -0
  61. package/dist/schemas/index.js +18 -0
  62. package/dist/schemas/index.js.map +1 -0
  63. package/dist/strategies/BaseDeploymentStrategy.d.ts +41 -0
  64. package/dist/strategies/BaseDeploymentStrategy.d.ts.map +1 -0
  65. package/dist/strategies/BaseDeploymentStrategy.js +545 -0
  66. package/dist/strategies/BaseDeploymentStrategy.js.map +1 -0
  67. package/dist/strategies/DeploymentStrategy.d.ts +19 -0
  68. package/dist/strategies/DeploymentStrategy.d.ts.map +1 -0
  69. package/dist/strategies/DeploymentStrategy.js +3 -0
  70. package/dist/strategies/DeploymentStrategy.js.map +1 -0
  71. package/dist/strategies/LocalDeploymentStrategy.d.ts +4 -0
  72. package/dist/strategies/LocalDeploymentStrategy.d.ts.map +1 -0
  73. package/dist/strategies/LocalDeploymentStrategy.js +8 -0
  74. package/dist/strategies/LocalDeploymentStrategy.js.map +1 -0
  75. package/dist/strategies/OZDefenderDeploymentStrategy.d.ts +62 -0
  76. package/dist/strategies/OZDefenderDeploymentStrategy.d.ts.map +1 -0
  77. package/dist/strategies/OZDefenderDeploymentStrategy.js +757 -0
  78. package/dist/strategies/OZDefenderDeploymentStrategy.js.map +1 -0
  79. package/dist/strategies/RPCDeploymentStrategy.d.ts +139 -0
  80. package/dist/strategies/RPCDeploymentStrategy.d.ts.map +1 -0
  81. package/dist/strategies/RPCDeploymentStrategy.js +710 -0
  82. package/dist/strategies/RPCDeploymentStrategy.js.map +1 -0
  83. package/dist/strategies/index.d.ts +6 -0
  84. package/dist/strategies/index.d.ts.map +1 -0
  85. package/dist/strategies/index.js +12 -0
  86. package/dist/strategies/index.js.map +1 -0
  87. package/dist/types/config.d.ts +26 -0
  88. package/dist/types/config.d.ts.map +1 -0
  89. package/dist/types/config.js +3 -0
  90. package/dist/types/config.js.map +1 -0
  91. package/dist/types/defender.d.ts +22 -0
  92. package/dist/types/defender.d.ts.map +1 -0
  93. package/dist/types/defender.js +3 -0
  94. package/dist/types/defender.js.map +1 -0
  95. package/dist/types/deployments.d.ts +71 -0
  96. package/dist/types/deployments.d.ts.map +1 -0
  97. package/dist/types/deployments.js +20 -0
  98. package/dist/types/deployments.js.map +1 -0
  99. package/dist/types/index.d.ts +5 -0
  100. package/dist/types/index.d.ts.map +1 -0
  101. package/dist/types/index.js +21 -0
  102. package/dist/types/index.js.map +1 -0
  103. package/dist/types/rpc.d.ts +35 -0
  104. package/dist/types/rpc.d.ts.map +1 -0
  105. package/dist/types/rpc.js +3 -0
  106. package/dist/types/rpc.js.map +1 -0
  107. package/dist/utils/common.d.ts +20 -0
  108. package/dist/utils/common.d.ts.map +1 -0
  109. package/dist/utils/common.js +45 -0
  110. package/dist/utils/common.js.map +1 -0
  111. package/dist/utils/configurationResolver.d.ts +30 -0
  112. package/dist/utils/configurationResolver.d.ts.map +1 -0
  113. package/dist/utils/configurationResolver.js +151 -0
  114. package/dist/utils/configurationResolver.js.map +1 -0
  115. package/dist/utils/contractMapping.d.ts +29 -0
  116. package/dist/utils/contractMapping.d.ts.map +1 -0
  117. package/dist/utils/contractMapping.js +224 -0
  118. package/dist/utils/contractMapping.js.map +1 -0
  119. package/dist/utils/defenderClients.d.ts +5 -0
  120. package/dist/utils/defenderClients.d.ts.map +1 -0
  121. package/dist/utils/defenderClients.js +21 -0
  122. package/dist/utils/defenderClients.js.map +1 -0
  123. package/dist/utils/defenderStore.d.ts +14 -0
  124. package/dist/utils/defenderStore.d.ts.map +1 -0
  125. package/dist/utils/defenderStore.js +92 -0
  126. package/dist/utils/defenderStore.js.map +1 -0
  127. package/dist/utils/diamondAbiGenerator.d.ts +113 -0
  128. package/dist/utils/diamondAbiGenerator.d.ts.map +1 -0
  129. package/dist/utils/diamondAbiGenerator.js +415 -0
  130. package/dist/utils/diamondAbiGenerator.js.map +1 -0
  131. package/dist/utils/diffDeployedFacets.d.ts +26 -0
  132. package/dist/utils/diffDeployedFacets.d.ts.map +1 -0
  133. package/dist/utils/diffDeployedFacets.js +106 -0
  134. package/dist/utils/diffDeployedFacets.js.map +1 -0
  135. package/dist/utils/index.d.ts +16 -0
  136. package/dist/utils/index.d.ts.map +1 -0
  137. package/dist/utils/index.js +35 -0
  138. package/dist/utils/index.js.map +1 -0
  139. package/dist/utils/loupe.d.ts +44 -0
  140. package/dist/utils/loupe.d.ts.map +1 -0
  141. package/dist/utils/loupe.js +128 -0
  142. package/dist/utils/loupe.js.map +1 -0
  143. package/dist/utils/rpcStore.d.ts +36 -0
  144. package/dist/utils/rpcStore.d.ts.map +1 -0
  145. package/dist/utils/rpcStore.js +166 -0
  146. package/dist/utils/rpcStore.js.map +1 -0
  147. package/dist/utils/signer.d.ts +36 -0
  148. package/dist/utils/signer.d.ts.map +1 -0
  149. package/dist/utils/signer.js +91 -0
  150. package/dist/utils/signer.js.map +1 -0
  151. package/dist/utils/txlogging.d.ts +13 -0
  152. package/dist/utils/txlogging.d.ts.map +1 -0
  153. package/dist/utils/txlogging.js +87 -0
  154. package/dist/utils/txlogging.js.map +1 -0
  155. package/dist/utils/workspaceSetup.d.ts +32 -0
  156. package/dist/utils/workspaceSetup.d.ts.map +1 -0
  157. package/dist/utils/workspaceSetup.js +311 -0
  158. package/dist/utils/workspaceSetup.js.map +1 -0
  159. package/docs/DIAMOND_ABI_CONFIGURATION_SUMMARY.md +40 -0
  160. package/docs/DIAMOND_ABI_GENERATION.md +220 -0
  161. package/docs/DIAMOND_ABI_GENERATOR_EXAMPLES.md +1204 -0
  162. package/docs/DIAMOND_ABI_GENERATOR_IMPLEMENTATION.md +947 -0
  163. package/docs/DIAMOND_ABI_GENERATOR_QUICK_REFERENCE.md +336 -0
  164. package/docs/README-DEFENDER.md +394 -0
  165. package/docs/README_DIAMOND_ABI_GENERATOR.md +303 -0
  166. package/docs/ROADMAP.md +250 -0
  167. package/docs/assets/image.png +0 -0
  168. package/docs/defender-integration.md +451 -0
  169. package/docs/diamond_module-BaseStrategy_design-v2.uxf +247 -0
  170. package/docs/diamond_module-BaseStrategy_design.uxf +272 -0
  171. package/docs/monitoring-troubleshooting.md +556 -0
  172. package/docs/testing-guide.md +713 -0
  173. package/examples/Diamond_Config_and_Deployment_examples/diamonds/ProxyDiamond/callbacks/ERC20ProxyFacet.ts +31 -0
  174. package/examples/Diamond_Config_and_Deployment_examples/diamonds/ProxyDiamond/proxydiamond.config.json +27 -0
  175. package/examples/Local_Hardhat_Deployer_Script_example/LocalDiamondDeployer.ts +180 -0
  176. package/examples/OZ_Defender_Deployer_Script_example/OZDiamondDeployer.ts +107 -0
  177. package/examples/OZ_Defender_Deployer_Script_example/run-oz-deploy.ts +17 -0
  178. package/examples/Test_examples/ProxyDiamondDeployment.test.ts +202 -0
  179. package/examples/defender-deployment/.env.example +35 -0
  180. package/examples/defender-deployment/README.md +415 -0
  181. package/examples/defender-deployment/contracts/ExampleDiamond.sol +41 -0
  182. package/examples/defender-deployment/contracts/ExampleFacet1.sol +84 -0
  183. package/examples/defender-deployment/contracts/ExampleFacet2.sol +104 -0
  184. package/examples/defender-deployment/contracts/UpgradeFacet.sol +92 -0
  185. package/examples/defender-deployment/deploy-script.ts +170 -0
  186. package/examples/defender-deployment/diamond-config.json +36 -0
  187. package/examples/defender-deployment/upgrade-script.ts +237 -0
  188. package/examples/hardhat-diamonds-config.example.ts +41 -0
  189. package/package.json +228 -0
  190. package/src/core/CallbackManager.ts +70 -0
  191. package/src/core/DeploymentManager.ts +64 -0
  192. package/src/core/Diamond.ts +197 -0
  193. package/src/core/DiamondDeployer.ts +36 -0
  194. package/src/core/index.ts +4 -0
  195. package/src/index.ts +5 -0
  196. package/src/repositories/DBDeploymentRepository.ts +22 -0
  197. package/src/repositories/DeploymentRepository.ts +12 -0
  198. package/src/repositories/FileDeploymentRepository.ts +67 -0
  199. package/src/repositories/databaseHandler.ts +14 -0
  200. package/src/repositories/index.ts +4 -0
  201. package/src/repositories/jsonFileHandler.ts +252 -0
  202. package/src/repositories/prismaDBHandler.ts +10 -0
  203. package/src/schemas/DeploymentSchema.ts +71 -0
  204. package/src/schemas/index.ts +1 -0
  205. package/src/strategies/BaseDeploymentStrategy.ts +649 -0
  206. package/src/strategies/DeploymentStrategy.ts +25 -0
  207. package/src/strategies/LocalDeploymentStrategy.ts +5 -0
  208. package/src/strategies/OZDefenderDeploymentStrategy.ts +849 -0
  209. package/src/strategies/RPCDeploymentStrategy.ts +881 -0
  210. package/src/strategies/index.ts +5 -0
  211. package/src/types/config.ts +34 -0
  212. package/src/types/defender.ts +24 -0
  213. package/src/types/deployments.ts +102 -0
  214. package/src/types/index.ts +4 -0
  215. package/src/types/rpc.ts +37 -0
  216. package/src/utils/common.ts +54 -0
  217. package/src/utils/configurationResolver.ts +141 -0
  218. package/src/utils/contractMapping.ts +220 -0
  219. package/src/utils/defenderClients.ts +22 -0
  220. package/src/utils/defenderStore.ts +62 -0
  221. package/src/utils/diamondAbiGenerator.ts +523 -0
  222. package/src/utils/diffDeployedFacets.ts +131 -0
  223. package/src/utils/index.ts +15 -0
  224. package/src/utils/loupe.ts +159 -0
  225. package/src/utils/rpcStore.ts +152 -0
  226. package/src/utils/signer.ts +93 -0
  227. package/src/utils/txlogging.ts +97 -0
  228. package/src/utils/workspaceSetup.ts +315 -0
  229. package/test/README.md +136 -0
@@ -0,0 +1,881 @@
1
+ import chalk from "chalk";
2
+ import { Contract, ContractFactory, ethers, JsonRpcProvider, parseUnits, Signer } from "ethers";
3
+ import { Diamond } from "../core/Diamond";
4
+ import {
5
+ FacetCutAction,
6
+ FacetCuts,
7
+ FunctionSelectorRegistryEntry,
8
+ NewDeployedFacet,
9
+ RegistryFacetCutAction,
10
+ RPCStepRecord, RPCStepStatus
11
+ } from "../types";
12
+ import { getContractArtifact, getContractName, getDiamondContractName, RPCDeploymentStore } from "../utils";
13
+ import { BaseDeploymentStrategy } from "./BaseDeploymentStrategy";
14
+
15
+ /**
16
+ * Error classes for RPC-specific failures
17
+ */
18
+ export class RPCConnectionError extends Error {
19
+ constructor(message: string, public readonly originalError?: Error) {
20
+ super(message);
21
+ this.name = "RPCConnectionError";
22
+ }
23
+ }
24
+
25
+ export class TransactionFailedError extends Error {
26
+ constructor(message: string, public readonly txHash?: string, public readonly originalError?: Error) {
27
+ super(message);
28
+ this.name = "TransactionFailedError";
29
+ }
30
+ }
31
+
32
+ export class GasEstimationError extends Error {
33
+ constructor(message: string, public readonly originalError?: Error) {
34
+ super(message);
35
+ this.name = "GasEstimationError";
36
+ }
37
+ }
38
+
39
+ export class ContractDeploymentError extends Error {
40
+ constructor(message: string, public readonly contractName?: string, public readonly originalError?: Error) {
41
+ super(message);
42
+ this.name = "ContractDeploymentError";
43
+ }
44
+ }
45
+
46
+ /**
47
+ * RPC Deployment Strategy for direct blockchain interaction
48
+ *
49
+ * This strategy enables direct RPC communication with blockchain networks
50
+ * for contract deployment, diamond cuts, and callback execution without
51
+ * relying on Hardhat's deployment abstractions.
52
+ */
53
+ export class RPCDeploymentStrategy extends BaseDeploymentStrategy {
54
+ private provider: JsonRpcProvider;
55
+ private signer: Signer;
56
+ private gasLimitMultiplier: number;
57
+ private maxRetries: number;
58
+ private retryDelayMs: number;
59
+ private store?: RPCDeploymentStore;
60
+
61
+ /**
62
+ * Creates a new RPC Deployment Strategy
63
+ *
64
+ * @param rpcUrl - The RPC endpoint URL
65
+ * @param privateKey - The deployer's private key (0x prefixed)
66
+ * @param gasLimitMultiplier - Multiplier for gas limit estimates (default: 1.2)
67
+ * @param maxRetries - Maximum number of retries for failed operations (default: 3)
68
+ * @param retryDelayMs - Delay between retries in milliseconds (default: 2000)
69
+ * @param verbose - Enable verbose logging (default: false)
70
+ */
71
+ constructor(
72
+ private rpcUrl: string,
73
+ private privateKey: string,
74
+ gasLimitMultiplier: number = 1.2,
75
+ maxRetries: number = 3,
76
+ retryDelayMs: number = 2000,
77
+ verbose: boolean = false
78
+ ) {
79
+ super(verbose);
80
+
81
+ // Validate inputs
82
+ this.validateConstructorInputs(rpcUrl, privateKey, gasLimitMultiplier, maxRetries, retryDelayMs);
83
+
84
+ // Initialize provider and signer
85
+ this.provider = new JsonRpcProvider(rpcUrl);
86
+ this.signer = new ethers.Wallet(privateKey, this.provider);
87
+
88
+ this.gasLimitMultiplier = gasLimitMultiplier;
89
+ this.maxRetries = maxRetries;
90
+ this.retryDelayMs = retryDelayMs;
91
+
92
+ if (this.verbose) {
93
+ console.log(chalk.blue(`🔗 RPC Strategy initialized with endpoint: ${rpcUrl}`));
94
+ console.log(chalk.blue(`👤 Deployer address: ${this.signer.getAddress()}`));
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Initialize step tracking store for deployment
100
+ */
101
+ private async initializeStore(diamond: Diamond): Promise<void> {
102
+ const diamondConfig = diamond.getDiamondConfig();
103
+ const network = await this.provider.getNetwork();
104
+ const deploymentId = `${diamond.diamondName}-${diamondConfig.networkName}-${Number(network.chainId)}`;
105
+
106
+ console.log("🔍 DEBUG: Creating RPCDeploymentStore with:", {
107
+ diamondName: diamond.diamondName,
108
+ deploymentId,
109
+ deploymentsPath: diamondConfig.deploymentsPath
110
+ });
111
+
112
+ this.store = new RPCDeploymentStore(diamond.diamondName, deploymentId, diamondConfig.deploymentsPath);
113
+
114
+ // Initialize deployment metadata
115
+ this.store.initializeDeployment(
116
+ diamondConfig.networkName || 'unknown',
117
+ Number(network.chainId),
118
+ this.rpcUrl,
119
+ await this.signer.getAddress()
120
+ );
121
+
122
+ console.log("🔍 DEBUG: Store created and initialized");
123
+
124
+ if (this.verbose) {
125
+ console.log(chalk.blue(`📊 Step tracking initialized: ${deploymentId}`));
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Save a deployment step with tracking
131
+ */
132
+ private saveStep(stepName: string, description: string, status: RPCStepStatus = 'pending'): void {
133
+ if (!this.store) return;
134
+
135
+ const step: RPCStepRecord = {
136
+ stepName,
137
+ description,
138
+ status,
139
+ timestamp: Date.now()
140
+ };
141
+
142
+ this.store.saveStep(step);
143
+
144
+ if (this.verbose) {
145
+ const statusColor = status === 'completed' ? 'green' : status === 'failed' ? 'red' : 'blue';
146
+ console.log(chalk[statusColor](`📝 Step ${status}: ${stepName} - ${description}`));
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Update step status with transaction details
152
+ */
153
+ private updateStepStatus(
154
+ stepName: string,
155
+ status: RPCStepStatus,
156
+ txHash?: string,
157
+ contractAddress?: string,
158
+ gasUsed?: string,
159
+ error?: string
160
+ ): void {
161
+ if (!this.store) return;
162
+
163
+ this.store.updateStatus(stepName, status, txHash, contractAddress, error);
164
+
165
+ const step = this.store.getStep(stepName);
166
+ if (step && txHash) {
167
+ step.txHash = txHash;
168
+ step.gasUsed = gasUsed;
169
+ this.store.saveStep(step);
170
+ }
171
+
172
+ if (this.verbose) {
173
+ const statusColor = status === 'completed' ? 'green' : status === 'failed' ? 'red' : 'yellow';
174
+ console.log(chalk[statusColor](`🔄 Updated ${stepName}: ${status}${txHash ? ` (${txHash})` : ''}${error ? ` - ${error}` : ''}`));
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Check if a step is already completed
180
+ */
181
+ private isStepCompleted(stepName: string): boolean {
182
+ return this.store?.isStepCompleted(stepName) || false;
183
+ }
184
+
185
+ /**
186
+ * Skip a step that's already completed
187
+ */
188
+ private skipCompletedStep(stepName: string, description: string): boolean {
189
+ if (this.isStepCompleted(stepName)) {
190
+ if (this.verbose) {
191
+ console.log(chalk.gray(`⏭️ Skipping completed step: ${stepName} - ${description}`));
192
+ }
193
+ return true;
194
+ }
195
+ return false;
196
+ }
197
+
198
+ /**
199
+ * Resolve diamond contract name handling multiple artifacts issue
200
+ */
201
+ private async resolveDiamondContractName(diamondName: string, diamond: Diamond): Promise<string> {
202
+ // For GeniusDiamond, specifically use the gnus-ai version to avoid artifact conflicts
203
+ if (diamondName === 'GeniusDiamond') {
204
+ const gnusAiFqn = `contracts/gnus-ai/${diamondName}.sol:${diamondName}`;
205
+ try {
206
+ // Test if this fully qualified name exists by trying to get the artifact
207
+ const { artifacts } = require('hardhat');
208
+ await artifacts.readArtifact(gnusAiFqn);
209
+ return gnusAiFqn;
210
+ } catch (error) {
211
+ if (this.verbose) {
212
+ console.log(chalk.yellow(`⚠️ Could not resolve ${gnusAiFqn}, falling back to simple name`));
213
+ }
214
+ }
215
+ }
216
+
217
+ // For other diamonds or if the specific resolution fails, try the original approach
218
+ try {
219
+ // Try the diamond name first
220
+ const { artifacts } = require('hardhat');
221
+ await artifacts.readArtifact(diamondName);
222
+ return diamondName;
223
+ } catch (error) {
224
+ // If there are multiple artifacts and it's not GeniusDiamond, fall back to original logic
225
+ return await getDiamondContractName(diamondName, diamond);
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Validates constructor inputs
231
+ */
232
+ private validateConstructorInputs(
233
+ rpcUrl: string,
234
+ privateKey: string,
235
+ gasLimitMultiplier: number,
236
+ maxRetries: number,
237
+ retryDelayMs: number
238
+ ): void {
239
+ if (!rpcUrl || typeof rpcUrl !== 'string') {
240
+ throw new Error('Invalid RPC URL provided');
241
+ }
242
+
243
+ if (!privateKey || !privateKey.match(/^0x[a-fA-F0-9]{64}$/)) {
244
+ throw new Error('Invalid private key format. Must be 64 hex characters with 0x prefix');
245
+ }
246
+
247
+ if (gasLimitMultiplier < 1.0 || gasLimitMultiplier > 2.0) {
248
+ throw new Error('Gas limit multiplier must be between 1.0 and 2.0');
249
+ }
250
+
251
+ if (maxRetries < 1 || maxRetries > 10) {
252
+ throw new Error('Max retries must be between 1 and 10');
253
+ }
254
+
255
+ if (retryDelayMs < 100 || retryDelayMs > 30000) {
256
+ throw new Error('Retry delay must be between 100ms and 30000ms');
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Retry wrapper for operations that may fail due to network issues
262
+ */
263
+ private async withRetry<T>(
264
+ operation: () => Promise<T>,
265
+ operationName: string,
266
+ maxRetries: number = this.maxRetries
267
+ ): Promise<T> {
268
+ let lastError: Error;
269
+
270
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
271
+ try {
272
+ if (this.verbose && attempt > 1) {
273
+ console.log(chalk.yellow(`🔄 Retrying ${operationName} (attempt ${attempt}/${maxRetries})`));
274
+ }
275
+
276
+ const result = await operation();
277
+
278
+ if (attempt > 1 && this.verbose) {
279
+ console.log(chalk.green(`✅ ${operationName} succeeded on attempt ${attempt}`));
280
+ }
281
+
282
+ return result;
283
+ } catch (error) {
284
+ lastError = error as Error;
285
+
286
+ if (this.verbose) {
287
+ console.log(chalk.red(`❌ ${operationName} failed on attempt ${attempt}: ${lastError.message}`));
288
+ }
289
+
290
+ if (attempt < maxRetries) {
291
+ const delay = this.retryDelayMs * Math.pow(1.5, attempt - 1); // Exponential backoff
292
+ await new Promise(resolve => setTimeout(resolve, delay));
293
+ }
294
+ }
295
+ }
296
+
297
+ throw new Error(`${operationName} failed after ${maxRetries} attempts. Last error: ${lastError!.message}`);
298
+ }
299
+
300
+ /**
301
+ * Estimates gas for a transaction with safety multiplier
302
+ */
303
+ private async estimateGasWithMultiplier(
304
+ contract: Contract,
305
+ methodName: string,
306
+ args: unknown[] = []
307
+ ): Promise<bigint> {
308
+ try {
309
+ const estimatedGas = await contract[methodName].estimateGas(...args);
310
+ const gasWithMultiplier = BigInt(Math.floor(Number(estimatedGas) * this.gasLimitMultiplier));
311
+
312
+ if (this.verbose) {
313
+ console.log(chalk.gray(`⛽ Gas estimate for ${methodName}: ${estimatedGas.toString()} (with multiplier: ${gasWithMultiplier.toString()})`));
314
+ }
315
+
316
+ return gasWithMultiplier;
317
+ } catch (error) {
318
+ throw new GasEstimationError(
319
+ `Failed to estimate gas for ${methodName}: ${(error as Error).message}`,
320
+ error as Error
321
+ );
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Gets current gas price with optional premium
327
+ */
328
+ private async getGasPrice(): Promise<bigint> {
329
+ try {
330
+ const feeData = await this.provider.getFeeData();
331
+
332
+ if (feeData.gasPrice) {
333
+ if (this.verbose) {
334
+ console.log(chalk.gray(`⛽ Gas price: ${ethers.formatUnits(feeData.gasPrice, "gwei")} gwei`));
335
+ }
336
+ return feeData.gasPrice;
337
+ } else {
338
+ // Fallback for networks that don't support getFeeData
339
+ const gasPrice = await this.provider.send('eth_gasPrice', []);
340
+ const gasPriceBigInt = BigInt(gasPrice);
341
+ if (this.verbose) {
342
+ console.log(chalk.gray(`⛽ Gas price (fallback): ${ethers.formatUnits(gasPriceBigInt, "gwei")} gwei`));
343
+ }
344
+ return gasPriceBigInt;
345
+ }
346
+ } catch (error) {
347
+ throw new GasEstimationError(
348
+ `Failed to get gas price: ${(error as Error).message}`,
349
+ error as Error
350
+ );
351
+ }
352
+ }
353
+
354
+ /**
355
+ * Deploys a contract using RPC
356
+ */
357
+ private async deployContract(
358
+ contractName: string,
359
+ constructorArgs: unknown[] = [],
360
+ diamond: Diamond
361
+ ): Promise<any> {
362
+ return await this.withRetry(async () => {
363
+ try {
364
+ // Get contract artifact using Hardhat's artifact resolution
365
+ // This will find artifacts in all configured paths (contracts-starter, gnus-ai, etc.)
366
+ const artifact = await getContractArtifact(contractName, diamond);
367
+
368
+ // Create contract factory
369
+ const factory = new ContractFactory(artifact.abi, artifact.bytecode, this.signer);
370
+
371
+ // Estimate gas for deployment
372
+ const deployTransaction = await factory.getDeployTransaction(...constructorArgs);
373
+ const estimatedGas = await this.provider.estimateGas({
374
+ data: deployTransaction.data,
375
+ from: await this.signer.getAddress()
376
+ });
377
+ const gasLimit = BigInt(Math.floor(Number(estimatedGas) * this.gasLimitMultiplier));
378
+
379
+ // Get gas price
380
+ const gasPrice = await this.getGasPrice();
381
+
382
+ if (this.verbose) {
383
+ console.log(chalk.blue(`🚀 Deploying ${contractName} with gas limit: ${gasLimit.toString()}`));
384
+ }
385
+
386
+ // Deploy contract
387
+ const contract = await factory.deploy(...constructorArgs, {
388
+ gasLimit,
389
+ gasPrice
390
+ });
391
+
392
+ // Wait for deployment
393
+ const deploymentReceipt = await contract.deploymentTransaction()?.wait();
394
+
395
+ if (!deploymentReceipt) {
396
+ throw new ContractDeploymentError(
397
+ `Deployment transaction failed for ${contractName}`,
398
+ contractName
399
+ );
400
+ }
401
+
402
+ if (this.verbose) {
403
+ console.log(chalk.green(`✅ ${contractName} deployed at: ${await contract.getAddress()}`));
404
+ console.log(chalk.gray(` Transaction hash: ${deploymentReceipt.hash}`));
405
+ console.log(chalk.gray(` Gas used: ${deploymentReceipt.gasUsed.toString()}`));
406
+ }
407
+
408
+ return contract;
409
+ } catch (error) {
410
+ if (error instanceof ContractDeploymentError) {
411
+ throw error;
412
+ }
413
+ throw new ContractDeploymentError(
414
+ `Failed to deploy ${contractName}: ${(error as Error).message}`,
415
+ contractName,
416
+ error as Error
417
+ );
418
+ }
419
+ }, `Deploy ${contractName}`);
420
+ }
421
+
422
+ /**
423
+ * Override deployDiamondTasks to use RPC instead of Hardhat
424
+ */
425
+ protected async deployDiamondTasks(diamond: Diamond): Promise<void> {
426
+ // Initialize step tracking store
427
+ await this.initializeStore(diamond);
428
+
429
+ if (this.verbose) {
430
+ console.log(chalk.blueBright(`🚀 Explicitly deploying DiamondCutFacet and Diamond for ${diamond.diamondName} via RPC`));
431
+ }
432
+
433
+ try {
434
+ // Step 1: Deploy DiamondCutFacet
435
+ const diamondCutStepName = 'deploy-diamondcutfacet';
436
+ if (!this.skipCompletedStep(diamondCutStepName, 'Deploy DiamondCutFacet')) {
437
+ this.saveStep(diamondCutStepName, 'Deploy DiamondCutFacet', 'in_progress');
438
+
439
+ const diamondCutContractName = await getContractName("DiamondCutFacet", diamond);
440
+ const diamondCutFacet = await this.deployContract(diamondCutContractName, [], diamond);
441
+ const diamondCutFacetAddress = await diamondCutFacet.getAddress();
442
+ const diamondCutTxHash = diamondCutFacet.deploymentTransaction()?.hash;
443
+
444
+ this.updateStepStatus(diamondCutStepName, 'completed', diamondCutTxHash, diamondCutFacetAddress);
445
+ }
446
+
447
+ // Step 2: Deploy Diamond contract
448
+ const diamondStepName = 'deploy-diamond';
449
+ if (!this.skipCompletedStep(diamondStepName, 'Deploy Diamond contract')) {
450
+ this.saveStep(diamondStepName, 'Deploy Diamond contract', 'in_progress');
451
+
452
+ // Get DiamondCutFacet address from completed step or deploy
453
+ const diamondCutStep = this.store?.getStep(diamondCutStepName);
454
+ const diamondCutFacetAddress = diamondCutStep?.contractAddress;
455
+
456
+ if (!diamondCutFacetAddress) {
457
+ throw new Error('DiamondCutFacet address not found from previous step');
458
+ }
459
+
460
+ const diamondContractName = await this.resolveDiamondContractName(diamond.diamondName, diamond);
461
+ const diamondContract = await this.deployContract(
462
+ diamondContractName,
463
+ [await this.signer.getAddress(), diamondCutFacetAddress],
464
+ diamond
465
+ );
466
+
467
+ const diamondContractAddress = await diamondContract.getAddress();
468
+ const diamondTxHash = diamondContract.deploymentTransaction()?.hash;
469
+
470
+ this.updateStepStatus(diamondStepName, 'completed', diamondTxHash, diamondContractAddress);
471
+ }
472
+
473
+ // Step 3: Register DiamondCutFacet selectors
474
+ this.saveStep('register-diamondcut-selectors', 'Register DiamondCutFacet function selectors', 'in_progress');
475
+
476
+ // Re-create DiamondCutFacet instance to get selectors
477
+ const diamondCutContractName = await getContractName("DiamondCutFacet", diamond);
478
+ const diamondCutArtifact = await getContractArtifact(diamondCutContractName, diamond);
479
+ const diamondCutFacetAddress = this.store?.getStep(diamondCutStepName)?.contractAddress!;
480
+ const diamondCutFacet = new Contract(diamondCutFacetAddress, diamondCutArtifact.abi, this.signer);
481
+
482
+ // Get function selectors for DiamondCutFacet
483
+ const diamondCutFacetFunctionSelectors: string[] = [];
484
+ diamondCutFacet.interface.forEachFunction((func: ethers.FunctionFragment) => {
485
+ diamondCutFacetFunctionSelectors.push(func.selector);
486
+ });
487
+
488
+ // Register the DiamondCutFacet function selectors
489
+ const diamondCutFacetSelectorsRegistry = diamondCutFacetFunctionSelectors.reduce((acc, selector) => {
490
+ acc[selector] = {
491
+ facetName: "DiamondCutFacet",
492
+ priority: diamond.getFacetsConfig()?.DiamondCutFacet?.priority || 1000,
493
+ address: diamondCutFacetAddress,
494
+ action: RegistryFacetCutAction.Deployed,
495
+ };
496
+ return acc;
497
+ }, {} as Record<string, Omit<FunctionSelectorRegistryEntry, "selector">>);
498
+
499
+ diamond.registerFunctionSelectors(diamondCutFacetSelectorsRegistry);
500
+ this.updateStepStatus('register-diamondcut-selectors', 'completed');
501
+
502
+ // Step 4: Update deployed diamond data
503
+ this.saveStep('update-diamond-data', 'Update deployed diamond data', 'in_progress');
504
+
505
+ const deployedDiamondData = diamond.getDeployedDiamondData();
506
+ const diamondContractAddress = this.store?.getStep('deploy-diamond')?.contractAddress!;
507
+
508
+ deployedDiamondData.DeployerAddress = await this.signer.getAddress();
509
+ deployedDiamondData.DiamondAddress = diamondContractAddress;
510
+ deployedDiamondData.DeployedFacets = deployedDiamondData.DeployedFacets || {};
511
+ deployedDiamondData.DeployedFacets["DiamondCutFacet"] = {
512
+ address: diamondCutFacetAddress,
513
+ tx_hash: this.store?.getStep(diamondCutStepName)?.txHash || "",
514
+ version: 0,
515
+ funcSelectors: diamondCutFacetFunctionSelectors,
516
+ };
517
+
518
+ diamond.updateDeployedDiamondData(deployedDiamondData);
519
+ this.updateStepStatus('update-diamond-data', 'completed');
520
+
521
+ if (this.verbose) {
522
+ console.log(chalk.green(`✅ Diamond deployed at ${diamondContractAddress}, DiamondCutFacet at ${diamondCutFacetAddress}`));
523
+ }
524
+ } catch (error) {
525
+ const errorMessage = (error as Error).message;
526
+ if (this.store) {
527
+ this.store.markDeploymentFailed(errorMessage);
528
+ }
529
+ console.error(chalk.red(`❌ Failed to deploy diamond via RPC: ${errorMessage}`));
530
+ throw error;
531
+ }
532
+ }
533
+
534
+ /**
535
+ * Override deployFacetsTasks to use RPC instead of Hardhat
536
+ */
537
+ protected async deployFacetsTasks(diamond: Diamond): Promise<void> {
538
+ console.log("🔍 DEBUG: RPCDeploymentStrategy.deployFacetsTasks called");
539
+ const deployConfig = diamond.getDeployConfig();
540
+ const facetsConfig = diamond.getDeployConfig().facets;
541
+ const deployedDiamondData = diamond.getDeployedDiamondData();
542
+
543
+ const sortedFacetNames = Object.keys(deployConfig.facets)
544
+ .sort((a, b) => {
545
+ return (deployConfig.facets[a].priority || 1000) - (deployConfig.facets[b].priority || 1000);
546
+ });
547
+
548
+ // Deploy facets sequentially to maintain order
549
+ for (const facetName of sortedFacetNames) {
550
+ const facetConfig = facetsConfig[facetName];
551
+ const deployedVersion = deployedDiamondData.DeployedFacets?.[facetName]?.version ?? -1;
552
+ const availableVersions = Object.keys(facetConfig.versions || {}).map(Number);
553
+ const upgradeVersion = Math.max(...availableVersions);
554
+
555
+ if (upgradeVersion > deployedVersion || deployedVersion === -1) {
556
+ const facetStepName = `deploy-facet-${facetName.toLowerCase()}`;
557
+
558
+ if (this.skipCompletedStep(facetStepName, `Deploy ${facetName} facet`)) {
559
+ continue;
560
+ }
561
+
562
+ this.saveStep(facetStepName, `Deploy ${facetName} facet v${upgradeVersion}`, 'in_progress');
563
+
564
+ if (this.verbose) {
565
+ console.log(chalk.blueBright(`🚀 Deploying facet: ${facetName} to version ${upgradeVersion} via RPC`));
566
+ }
567
+
568
+ try {
569
+ // Deploy the facet contract using RPC
570
+ const facetContractName = await getContractName(facetName, diamond);
571
+ const facetContract = await this.deployContract(facetContractName, [], diamond);
572
+
573
+ const facetAddress = await facetContract.getAddress();
574
+ const facetTxHash = facetContract.deploymentTransaction()?.hash;
575
+
576
+ const facetSelectors: string[] = [];
577
+ facetContract.interface.forEachFunction((func: ethers.FunctionFragment) => {
578
+ facetSelectors.push(func.selector);
579
+ });
580
+
581
+ // Initializer function Registry
582
+ const deployInit = facetConfig.versions?.[upgradeVersion]?.deployInit || "";
583
+ const upgradeInit = facetConfig.versions?.[upgradeVersion]?.upgradeInit || "";
584
+
585
+ const initFn = diamond.newDeployment ? deployInit : upgradeInit;
586
+ if (initFn && facetName !== deployConfig.protocolInitFacet) {
587
+ diamond.initializerRegistry.set(facetName, initFn);
588
+ }
589
+
590
+ const newFacetData: NewDeployedFacet = {
591
+ priority: facetConfig.priority || 1000,
592
+ address: facetAddress,
593
+ tx_hash: facetTxHash || "",
594
+ version: upgradeVersion,
595
+ funcSelectors: facetSelectors,
596
+ deployInclude: facetConfig.versions?.[upgradeVersion]?.deployInclude || [],
597
+ deployExclude: facetConfig.versions?.[upgradeVersion]?.deployExclude || [],
598
+ initFunction: initFn,
599
+ verified: false,
600
+ };
601
+
602
+ diamond.updateNewDeployedFacets(facetName, newFacetData);
603
+
604
+ // Update step status with deployment info
605
+ this.updateStepStatus(facetStepName, 'completed', facetTxHash, facetAddress);
606
+
607
+ console.log(chalk.cyan(`⛵ ${facetName} deployed at ${facetAddress} with ${facetSelectors.length} selectors.`));
608
+
609
+ if (this.verbose) {
610
+ console.log(chalk.gray(` Selectors:`), facetSelectors);
611
+ }
612
+ } catch (error) {
613
+ const errorMessage = (error as Error).message;
614
+ this.updateStepStatus(facetStepName, 'failed', undefined, undefined, undefined, errorMessage);
615
+ console.error(chalk.red(`❌ Failed to deploy facet ${facetName}: ${errorMessage}`));
616
+ throw error;
617
+ }
618
+ }
619
+ }
620
+ }
621
+
622
+ /**
623
+ * Override performDiamondCutTasks to use RPC instead of Hardhat
624
+ */
625
+ protected async performDiamondCutTasks(diamond: Diamond): Promise<void> {
626
+ const deployConfig = diamond.getDeployConfig();
627
+ const deployedDiamondData = diamond.getDeployedDiamondData();
628
+
629
+ const diamondCutStepName = 'perform-diamond-cut';
630
+
631
+ if (this.skipCompletedStep(diamondCutStepName, 'Perform diamond cut')) {
632
+ return;
633
+ }
634
+
635
+ this.saveStep(diamondCutStepName, 'Perform diamond cut to add facets', 'in_progress');
636
+
637
+ try {
638
+ // Get diamond contract using RPC
639
+ const diamondAddress = deployedDiamondData.DiamondAddress!;
640
+
641
+ // Load IDiamondCut ABI using Hardhat artifact resolution
642
+ const diamondCutArtifact = await getContractArtifact("IDiamondCut", diamond);
643
+ const diamondContract = new Contract(diamondAddress, diamondCutArtifact.abi, this.signer);
644
+
645
+ // Setup initCallData with Atomic Protocol Initializer
646
+ const [initCalldata, initAddress] = await this.getInitCalldata(diamond);
647
+
648
+ // Extract facet cuts from the selector registry
649
+ const facetCuts: FacetCuts = await this.getFacetCuts(diamond);
650
+
651
+ // Validate no orphaned selectors
652
+ await this.validateNoOrphanedSelectors(facetCuts);
653
+
654
+ if (this.verbose) {
655
+ console.log(chalk.yellowBright(`\n🪓 Performing DiamondCut with ${facetCuts.length} cut(s) via RPC:`));
656
+ for (const cut of facetCuts) {
657
+ console.log(chalk.bold(`- ${FacetCutAction[cut.action]} for facet ${cut.name} at ${cut.facetAddress}`));
658
+ console.log(chalk.gray(` Selectors:`), cut.functionSelectors);
659
+ }
660
+ if (initAddress !== ethers.ZeroAddress) {
661
+ console.log(chalk.cyan(`Initializing with functionSelector ${initCalldata} on ProtocolInitFacet ${deployConfig.protocolInitFacet} @ ${initAddress}`));
662
+ }
663
+ }
664
+
665
+ // Prepare the diamond cut transaction
666
+ const facetSelectorCutMap = facetCuts.map(fc => ({
667
+ facetAddress: fc.facetAddress,
668
+ action: fc.action,
669
+ functionSelectors: fc.functionSelectors
670
+ }));
671
+
672
+ // Estimate gas for diamond cut
673
+ const gasLimit = await this.estimateGasWithMultiplier(
674
+ diamondContract,
675
+ 'diamondCut',
676
+ [facetSelectorCutMap, initAddress, initCalldata]
677
+ );
678
+
679
+ // Get gas price
680
+ const gasPrice = await this.getGasPrice();
681
+
682
+ // Perform the diamond cut
683
+ const tx = await diamondContract.diamondCut(
684
+ facetSelectorCutMap,
685
+ initAddress,
686
+ initCalldata,
687
+ { gasLimit, gasPrice }
688
+ );
689
+
690
+ console.log(chalk.blueBright(`🔄 Waiting for DiamondCut transaction to be mined...`));
691
+ console.log(chalk.gray(` Transaction hash: ${tx.hash}`));
692
+
693
+ // Wait for transaction confirmation
694
+ const receipt = await tx.wait();
695
+
696
+ if (!receipt) {
697
+ throw new TransactionFailedError("DiamondCut transaction failed", tx.hash);
698
+ }
699
+
700
+ if (this.verbose) {
701
+ console.log(chalk.gray(` Gas used: ${receipt.gasUsed.toString()}`));
702
+ console.log(chalk.gray(` Block number: ${receipt.blockNumber}`));
703
+ }
704
+
705
+ // Update step status with transaction details
706
+ this.updateStepStatus(diamondCutStepName, 'completed', tx.hash, diamondAddress, receipt.gasUsed.toString());
707
+
708
+ // Update the deployed diamond data
709
+ const txHash = tx.hash;
710
+ await this.postDiamondCutDeployedDataUpdate(diamond, txHash);
711
+
712
+ console.log(chalk.green(`✅ DiamondCut executed: ${tx.hash}`));
713
+
714
+ // Execute initializer functions
715
+ await this.executeInitializerFunctions(diamond);
716
+
717
+ // Mark deployment as complete
718
+ if (this.store) {
719
+ this.store.markDeploymentComplete();
720
+ }
721
+
722
+ } catch (error) {
723
+ const errorMessage = (error as Error).message;
724
+ this.updateStepStatus(diamondCutStepName, 'failed', undefined, undefined, undefined, errorMessage);
725
+ if (this.store) {
726
+ this.store.markDeploymentFailed(errorMessage);
727
+ }
728
+ console.error(chalk.red(`❌ Failed to perform diamond cut via RPC: ${(error as Error).message}`));
729
+ throw error;
730
+ }
731
+ }
732
+
733
+ /**
734
+ * Executes initializer functions for deployed facets
735
+ */
736
+ private async executeInitializerFunctions(diamond: Diamond): Promise<void> {
737
+ const deployedDiamondData = diamond.getDeployedDiamondData();
738
+ const diamondAddress = deployedDiamondData.DiamondAddress!;
739
+
740
+ for (const [facetName, initFunction] of diamond.initializerRegistry.entries()) {
741
+ const initStepName = `init-${facetName.toLowerCase()}`;
742
+
743
+ if (this.skipCompletedStep(initStepName, `Initialize ${facetName} facet`)) {
744
+ continue;
745
+ }
746
+
747
+ this.saveStep(initStepName, `Execute ${initFunction} from ${facetName} facet`, 'in_progress');
748
+
749
+ if (this.verbose) {
750
+ console.log(chalk.blueBright(`▶ Running ${initFunction} from the ${facetName} facet via RPC`));
751
+ }
752
+
753
+ try {
754
+ // Get facet contract name and load ABI using artifact resolution
755
+ const facetContractName = await getContractName(facetName, diamond);
756
+ const facetArtifact = await getContractArtifact(facetContractName, diamond);
757
+ const initContract = new Contract(diamondAddress, facetArtifact.abi, this.signer);
758
+
759
+ // Estimate gas for initializer function
760
+ const gasLimit = await this.estimateGasWithMultiplier(initContract, initFunction);
761
+ const gasPrice = await this.getGasPrice();
762
+
763
+ // Execute initializer function
764
+ const tx = await initContract[initFunction]({ gasLimit, gasPrice });
765
+
766
+ console.log(chalk.blueBright(`🔄 Waiting for ${facetName}.${initFunction} to be mined...`));
767
+
768
+ const receipt = await tx.wait();
769
+
770
+ if (!receipt) {
771
+ throw new TransactionFailedError(`${facetName}.${initFunction} transaction failed`, tx.hash);
772
+ }
773
+
774
+ if (this.verbose) {
775
+ console.log(chalk.gray(` Transaction hash: ${tx.hash}`));
776
+ console.log(chalk.gray(` Gas used: ${receipt.gasUsed.toString()}`));
777
+ }
778
+
779
+ // Update step status with transaction details
780
+ this.updateStepStatus(initStepName, 'completed', tx.hash, diamondAddress, receipt.gasUsed.toString());
781
+
782
+ console.log(chalk.green(`✅ ${facetName}.${initFunction} executed`));
783
+ } catch (error) {
784
+ const errorMessage = (error as Error).message;
785
+ this.updateStepStatus(initStepName, 'failed', undefined, undefined, undefined, errorMessage);
786
+ console.error(chalk.red(`❌ Failed to execute ${facetName}.${initFunction}: ${errorMessage}`));
787
+ throw error;
788
+ }
789
+ }
790
+ }
791
+
792
+ /**
793
+ * Checks network connection and validates signer
794
+ */
795
+ async validateConnection(): Promise<void> {
796
+ try {
797
+ await this.withRetry(async () => {
798
+ // Check provider connection
799
+ const network = await this.provider.getNetwork();
800
+ const balance = await this.provider.getBalance(await this.signer.getAddress());
801
+
802
+ if (this.verbose) {
803
+ console.log(chalk.blue(`🌐 Connected to network: ${network.name} (Chain ID: ${network.chainId})`));
804
+ console.log(chalk.blue(`💰 Deployer balance: ${ethers.formatEther(balance)} ETH`));
805
+ }
806
+
807
+ // Verify minimum balance (0.01 ETH)
808
+ if (balance < parseUnits("0.01", 18)) {
809
+ console.warn(chalk.yellow(`⚠️ Low balance detected: ${ethers.formatEther(balance)} ETH`));
810
+ }
811
+ }, "Network connection validation");
812
+ } catch (error) {
813
+ throw new RPCConnectionError(
814
+ `Failed to validate RPC connection: ${(error as Error).message}`,
815
+ error as Error
816
+ );
817
+ }
818
+ }
819
+
820
+ /**
821
+ * Gets the provider instance
822
+ */
823
+ getProvider(): JsonRpcProvider {
824
+ return this.provider;
825
+ }
826
+
827
+ /**
828
+ * Gets the signer instance
829
+ */
830
+ getSigner(): Signer {
831
+ return this.signer;
832
+ }
833
+
834
+ /**
835
+ * Gets deployment strategy configuration
836
+ */
837
+ getConfig() {
838
+ return {
839
+ rpcUrl: this.rpcUrl,
840
+ signerAddress: this.signer.getAddress(),
841
+ gasLimitMultiplier: this.gasLimitMultiplier,
842
+ maxRetries: this.maxRetries,
843
+ retryDelayMs: this.retryDelayMs,
844
+ verbose: this.verbose
845
+ };
846
+ }
847
+
848
+ // Pre-deploy hooks with connection validation
849
+ async preDeployDiamond(diamond: Diamond): Promise<void> {
850
+ await this.validateConnection();
851
+ await super.preDeployDiamond(diamond);
852
+ }
853
+
854
+ protected async preDeployFacetsTasks(diamond: Diamond): Promise<void> {
855
+ console.log("🔍 DEBUG: preDeployFacetsTasks called for", diamond.diamondName);
856
+ // Initialize step tracking store for both new deployments and upgrades
857
+ if (!this.store) {
858
+ console.log("🔍 DEBUG: Initializing store...");
859
+ await this.initializeStore(diamond);
860
+ console.log("🔍 DEBUG: Store initialized:", !!this.store);
861
+ } else {
862
+ console.log("🔍 DEBUG: Store already exists:", !!this.store);
863
+ }
864
+ await this.validateConnection();
865
+ await super.preDeployFacetsTasks(diamond);
866
+ }
867
+
868
+ protected async prePerformDiamondCutTasks(diamond: Diamond): Promise<void> {
869
+ console.log("🔍 DEBUG: prePerformDiamondCutTasks called for", diamond.diamondName);
870
+ await this.validateConnection();
871
+ // Initialize step tracking store for both new deployments and upgrades
872
+ if (!this.store) {
873
+ console.log("🔍 DEBUG: Initializing store...");
874
+ await this.initializeStore(diamond);
875
+ console.log("🔍 DEBUG: Store initialized:", !!this.store);
876
+ } else {
877
+ console.log("🔍 DEBUG: Store already exists:", !!this.store);
878
+ }
879
+ await super.prePerformDiamondCutTasks(diamond);
880
+ }
881
+ }