ethershell 0.1.0-alpha.9 → 0.1.0-beta.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.
@@ -7,6 +7,15 @@
7
7
 
8
8
  import { allAccounts, accounts, hdAccounts } from '../services/wallet.js';
9
9
  import { provider } from '../services/network.js';
10
+ import fs from 'fs';
11
+ import { configFile, configPath } from '../services/build.js';
12
+ import { serializeBigInts } from './serialize.js';
13
+
14
+ /**
15
+ * The path which in wallets json file will be saved.
16
+ * @type {String}
17
+ */
18
+ const walletJSONPath = './localStorage/wallets.json';
10
19
 
11
20
  /**
12
21
  * Delete account(s) by index
@@ -22,6 +31,8 @@ export function deleteByIndex(index) {
22
31
  deleteByIndexArr(index);
23
32
  } else if (typeof index === 'number') {
24
33
  _deleteBySingIndex(index);
34
+ } else if (index === null || index === undefined) {
35
+ _deleteAll();
25
36
  }
26
37
  }
27
38
 
@@ -82,6 +93,47 @@ export function detectDupWallet(privKeyArr) {
82
93
  }
83
94
  }
84
95
 
96
+ /**
97
+ * Writes/Updates wallets json file
98
+ * @param {Array<string>} walletArr - Account array
99
+ * @example
100
+ * updateWalletJSON([{
101
+ index: allAccounts.length,
102
+ address: newAccount.address,
103
+ privateKey: privKeyArr,
104
+ type: 'user-imported',
105
+ contracts: []
106
+ }]);
107
+ */
108
+ export function updateWalletJSON(walletArr) {
109
+ const walletObj = serializeBigInts(walletArr);
110
+ fs.writeFileSync(walletJSONPath, JSON.stringify(walletObj, null, 2));
111
+ }
112
+
113
+ /**
114
+ * Returns wallets' object from saved json file
115
+ * @returns {Object}
116
+ */
117
+ export function getWalletJSON() {
118
+ return JSON.parse(fs.readFileSync(walletJSONPath));
119
+ }
120
+
121
+ export function updateAccountMemory(allAccArr) {
122
+ let memAccArr = [];
123
+ let memHDAccArr = [];
124
+ for(let i = 0; i < allAccArr.length; i++) {
125
+ if(allAccArr[i].phrase) {
126
+ memHDAccArr.push(allAccArr[i])
127
+ } else {
128
+ memAccArr.push(allAccArr[i]);
129
+ }
130
+ }
131
+ return {
132
+ memAccArr,
133
+ memHDAccArr
134
+ }
135
+ }
136
+
85
137
  /**
86
138
  * Get information for multiple accounts (internal)
87
139
  * @private
@@ -127,13 +179,6 @@ async function _getAccountInfo(_index) {
127
179
  * @returns {void}
128
180
  */
129
181
  function _deleteBySingIndex(_index) {
130
- if (_index === null || _index === undefined) {
131
- // Clear all arrays
132
- allAccounts.splice(0);
133
- accounts.splice(0);
134
- hdAccounts.splice(0);
135
- return;
136
- }
137
182
 
138
183
  // Find and remove from allAccounts
139
184
  const accountIndex = allAccounts.findIndex(acc => acc.index === _index);
@@ -144,6 +189,9 @@ function _deleteBySingIndex(_index) {
144
189
  for (let i = accountIndex; i < allAccounts.length; i++) {
145
190
  allAccounts[i].index = i;
146
191
  }
192
+
193
+ // Update wallet json file
194
+ fs.writeFileSync(walletJSONPath, JSON.stringify(allAccounts, null, 2));
147
195
 
148
196
  // Remove from accounts array if it exists there
149
197
  const regularIndex = accounts.findIndex(acc => acc.index === _index);
@@ -210,4 +258,26 @@ function _findDupWalletByArr(privKeyArr) {
210
258
  return {
211
259
  status: false
212
260
  }
261
+ }
262
+
263
+ /**
264
+ * Removes all accounts from storage and memory
265
+ * @private
266
+ * @returns {null}
267
+ */
268
+ function _deleteAll() {
269
+ allAccounts.splice(0);
270
+ accounts.splice(0);
271
+ hdAccounts.splice(0);
272
+ fs.writeFileSync(walletJSONPath, JSON.stringify([], null, 2));
273
+ return;
274
+ }
275
+
276
+ /**
277
+ * Updates default account in config file
278
+ * @param {Object} account - // The given account to set as default account
279
+ */
280
+ export function setDefaultAccount(account) {
281
+ configFile.defaultWallet = serializeBigInts(account);
282
+ fs.writeFileSync(configPath, JSON.stringify(configFile, null, 2));
213
283
  }
@@ -167,3 +167,18 @@ export function build(fullPath, selectedContracts, buildPath){
167
167
  });
168
168
  }
169
169
 
170
+ /**
171
+ * Extract loadable version format from full version string
172
+ * @param {string} fullVersion - Full version string (e.g., "0.8.20+commit.a1b79de6.Emscripten.clang")
173
+ * @returns {string} Loadable version format (e.g., "v0.8.20+commit.a1b79de6")
174
+ * @example
175
+ * extractLoadableVersion("0.8.20+commit.a1b79de6.Emscripten.clang"); // Returns: "v0.8.20+commit.a1b79de6"
176
+ */
177
+ export function extractLoadableVersion(fullVersion) {
178
+ // Match version number and commit hash from full version string
179
+ const match = fullVersion.match(/(\d+\.\d+\.\d+)\+commit\.([a-f0-9]+)/);
180
+ if (!match) {
181
+ throw new Error(`Unable to extract version from: ${fullVersion}`);
182
+ }
183
+ return `v${match[1]}+commit.${match[2]}`;
184
+ }
@@ -0,0 +1,7 @@
1
+ import { configFile, configPath } from '../services/build.js';
2
+ import fs from 'fs';
3
+
4
+ export function changeProvider(url) {
5
+ configFile.providerEndpoint = url;
6
+ fs.writeFileSync(configPath, JSON.stringify(configFile, null, 2));
7
+ }
@@ -0,0 +1,162 @@
1
+ // src/utils/contractProxy.js
2
+ import { ethers } from 'ethers';
3
+
4
+ /**
5
+ * Creates a proxy wrapper for ethers.js Contract objects
6
+ * Allows dynamic sender changes and comprehensive transaction options
7
+ *
8
+ * @param {ethers.Contract} contract - The ethers.js contract instance
9
+ * @param {ethers.JsonRpcProvider} provider - The blockchain provider
10
+ * @param {Array} allAccounts - Array of available accounts with privateKey property
11
+ * @returns {Proxy} Proxied contract object
12
+ *
13
+ * @example
14
+ * // Call with comprehensive options
15
+ * await Payment.spend(amount, {
16
+ * from: '0x...',
17
+ * value: ethers.parseEther('1'),
18
+ * gasLimit: 500000,
19
+ * maxFeePerGas: ethers.parseUnits('100', 'gwei'),
20
+ * maxPriorityFeePerGas: ethers.parseUnits('2', 'gwei'),
21
+ * nonce: 42,
22
+ * chainId: 1
23
+ * })
24
+ */
25
+ export function createContractProxy(contract, provider, allAccounts) {
26
+ return new Proxy(contract, {
27
+ get(target, prop) {
28
+ // Pass through non-function properties
29
+ if (typeof target[prop] !== 'function') {
30
+ return target[prop];
31
+ }
32
+
33
+ // Return a wrapper for contract methods
34
+ return async function(...args) {
35
+ // Extract options from last argument if it's an object with custom properties
36
+ const lastArg = args[args.length - 1];
37
+
38
+ // List of all valid transaction option keys (except 'data', 'to', 'from' which are handled internally)
39
+ const validTxOptions = [
40
+ 'value',
41
+ 'nonce',
42
+ 'gasLimit',
43
+ 'gas', // Maps to gasLimit
44
+ 'gasPrice',
45
+ 'maxFeePerGas',
46
+ 'maxPriorityFeePerGas',
47
+ 'chainId',
48
+ 'accessList',
49
+ 'type',
50
+ 'customData',
51
+ 'from' // Special handling for signer switching
52
+ ];
53
+
54
+ const hasOptions =
55
+ lastArg &&
56
+ typeof lastArg === 'object' &&
57
+ !Array.isArray(lastArg) &&
58
+ Object.keys(lastArg).some(key => validTxOptions.includes(key));
59
+
60
+ // Warn if user tries to pass 'data'
61
+ if (lastArg && typeof lastArg === 'object' && lastArg.data) {
62
+ console.warn(
63
+ "Warning: 'data' option is ignored. " +
64
+ "Function calldata is automatically encoded by ethers.js. " +
65
+ "Use .connect() to change the signer if needed."
66
+ );
67
+ }
68
+
69
+ const options = hasOptions ? args.pop() : null;
70
+
71
+ let method = target[prop];
72
+ let txOptions = {};
73
+
74
+ // Handle 'from' option - switch signer if specified
75
+ if (options && options.from) {
76
+ const account = allAccounts.find(
77
+ acc => acc.address?.toLowerCase() === options.from.toLowerCase()
78
+ );
79
+
80
+ if (!account) {
81
+ throw new Error(
82
+ `Account ${options.from} not found in registered accounts`
83
+ );
84
+ }
85
+
86
+ if (!account.privateKey) {
87
+ throw new Error(
88
+ `Account ${options.from} is a node-managed account and cannot be used with {from}`
89
+ );
90
+ }
91
+
92
+ // Create new signer with the specified account
93
+ const newSigner = new ethers.Wallet(account.privateKey, provider);
94
+ const connectedContract = target.connect(newSigner);
95
+ method = connectedContract[prop];
96
+ }
97
+
98
+ // Build transaction options object with all supported ethers.js v6 options
99
+ if (options) {
100
+ // Value (for payable functions)
101
+ if (options.value !== undefined) {
102
+ txOptions.value = options.value;
103
+ }
104
+
105
+ // Gas limit (handle both 'gas' and 'gasLimit')
106
+ if (options.gasLimit !== undefined) {
107
+ txOptions.gasLimit = options.gasLimit;
108
+ } else if (options.gas !== undefined) {
109
+ txOptions.gasLimit = options.gas; // Map 'gas' to 'gasLimit'
110
+ }
111
+
112
+ // Legacy gas price
113
+ if (options.gasPrice !== undefined) {
114
+ txOptions.gasPrice = options.gasPrice;
115
+ }
116
+
117
+ // EIP-1559 options
118
+ if (options.maxFeePerGas !== undefined) {
119
+ txOptions.maxFeePerGas = options.maxFeePerGas;
120
+ }
121
+
122
+ if (options.maxPriorityFeePerGas !== undefined) {
123
+ txOptions.maxPriorityFeePerGas = options.maxPriorityFeePerGas;
124
+ }
125
+
126
+ // Nonce for transaction ordering
127
+ if (options.nonce !== undefined) {
128
+ txOptions.nonce = options.nonce;
129
+ }
130
+
131
+ // Chain ID
132
+ if (options.chainId !== undefined) {
133
+ txOptions.chainId = options.chainId;
134
+ }
135
+
136
+ // EIP-2930 access list
137
+ if (options.accessList !== undefined) {
138
+ txOptions.accessList = options.accessList;
139
+ }
140
+
141
+ // Transaction type
142
+ if (options.type !== undefined) {
143
+ txOptions.type = options.type;
144
+ }
145
+
146
+ // Custom data (for special networks like zkSync)
147
+ if (options.customData !== undefined) {
148
+ txOptions.customData = options.customData;
149
+ }
150
+ }
151
+
152
+ // If there are transaction options, pass them as the last argument
153
+ if (Object.keys(txOptions).length > 0) {
154
+ args.push(txOptions);
155
+ }
156
+
157
+ // Call the method with remaining args and tx options
158
+ return method.apply(method, args);
159
+ };
160
+ }
161
+ });
162
+ }
@@ -0,0 +1,17 @@
1
+ export function serializeBigInts(obj) {
2
+ try{
3
+ if (Array.isArray(obj)) {
4
+ return obj.map(serializeBigInts);
5
+ } else if (obj && typeof obj === 'object') {
6
+ return Object.fromEntries(
7
+ Object.entries(obj).map(([key, value]) => [
8
+ key,
9
+ typeof value === 'bigint' ? value.toString() : serializeBigInts(value)
10
+ ])
11
+ );
12
+ }
13
+ return obj;
14
+ }catch(err){
15
+ console.error(err);
16
+ }
17
+ }
@@ -0,0 +1,247 @@
1
+ /**
2
+ * @fileoverview TypeScript type generation from Solidity ABIs
3
+ * @description Generates type-safe TypeScript interfaces and classes
4
+ * from compiled Solidity contracts for use in TypeScript projects
5
+ * @module typeGenerator
6
+ */
7
+
8
+ import path from 'path';
9
+ import fs from 'fs';
10
+
11
+ /**
12
+ * Generate TypeScript types from contract ABI
13
+ * @param {string} contractName - Name of the contract
14
+ * @param {Array} abi - Contract ABI array
15
+ * @param {string} outputPath - Output directory for types
16
+ * @returns {string} Path to generated type file
17
+ */
18
+ export function generateContractTypes(contractName, abi, outputPath) {
19
+ try {
20
+ // Ensure output directory exists
21
+ if (!fs.existsSync(outputPath)) {
22
+ fs.mkdirSync(outputPath, { recursive: true });
23
+ }
24
+
25
+ // Generate TypeScript interfaces
26
+ const interfaces = generateInterfaces(contractName, abi);
27
+ const typePath = path.join(outputPath, `${contractName}.ts`);
28
+
29
+ fs.writeFileSync(typePath, interfaces);
30
+
31
+ return typePath;
32
+ } catch (error) {
33
+ console.error(`Error generating types for ${contractName}:`, error.message);
34
+ throw error;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Generate all TypeScript types from build artifacts
40
+ * @param {string} buildPath - Path to build directory with ABIs
41
+ * @param {string} typesOutputPath - Output directory for TypeScript types
42
+ * @returns {Array} Array of generated type file paths
43
+ */
44
+ export function generateAllTypes(buildPath, typesOutputPath = './src/types') {
45
+ try {
46
+ const abisPath = path.join(buildPath, 'abis');
47
+
48
+ if (!fs.existsSync(abisPath)) {
49
+ throw new Error(`ABIs directory not found at ${abisPath}`);
50
+ }
51
+
52
+ const abiFiles = fs.readdirSync(abisPath).filter(f => f.endsWith('.abi.json'));
53
+ const generatedFiles = [];
54
+
55
+ abiFiles.forEach(file => {
56
+ const contractName = file.replace('.abi.json', '');
57
+ const abiPath = path.join(abisPath, file);
58
+ const abi = JSON.parse(fs.readFileSync(abiPath, 'utf8'));
59
+
60
+ const typePath = generateContractTypes(contractName, abi, typesOutputPath);
61
+ generatedFiles.push(typePath);
62
+ });
63
+
64
+ // Generate index.ts for barrel export
65
+ generateIndexFile(typesOutputPath, abiFiles);
66
+
67
+ return generatedFiles;
68
+ } catch (error) {
69
+ console.error('Error generating types:', error.message);
70
+ throw error;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Generate TypeScript interface from ABI
76
+ * @private
77
+ */
78
+ function generateInterfaces(contractName, abi) {
79
+ const functions = abi.filter(item => item.type === 'function');
80
+ const events = abi.filter(item => item.type === 'event');
81
+ const structs = extractStructTypes(abi);
82
+
83
+ let typescript = `/**
84
+ * Auto-generated types for ${contractName} contract
85
+ * Generated from ABI
86
+ */
87
+
88
+ // ============= TYPES =============
89
+ ${generateStructTypes(structs)}
90
+
91
+ // ============= FUNCTION INPUTS =============
92
+ ${generateFunctionTypes(functions, 'input')}
93
+
94
+ // ============= FUNCTION OUTPUTS =============
95
+ ${generateFunctionTypes(functions, 'output')}
96
+
97
+ // ============= EVENT TYPES =============
98
+ ${generateEventTypes(events)}
99
+
100
+ // ============= CONTRACT INTERFACE =============
101
+ export interface I${contractName} {
102
+ ${generateContractMethods(functions)}
103
+ }
104
+
105
+ // ============= ABI EXPORT =============
106
+ export const ${contractName}ABI = ${JSON.stringify(abi, null, 2)} as const;
107
+ `;
108
+
109
+ return typescript;
110
+ }
111
+
112
+ /**
113
+ * Generate TypeScript types for function inputs/outputs
114
+ * @private
115
+ */
116
+ function generateFunctionTypes(functions, direction) {
117
+ const lines = [];
118
+
119
+ functions.forEach(func => {
120
+ const params = direction === 'input' ? func.inputs : func.outputs;
121
+ if (!params || params.length === 0) return;
122
+
123
+ const funcName = func.name;
124
+ const typeName = `${funcName.charAt(0).toUpperCase() + funcName.slice(1)}${direction === 'input' ? 'Params' : 'Result'}`;
125
+
126
+ lines.push(`export interface ${typeName} {`);
127
+ params.forEach((param, idx) => {
128
+ const name = param.name || `param${idx}`;
129
+ const type = solToTsType(param.type);
130
+ lines.push(` ${name}: ${type};`);
131
+ });
132
+ lines.push('}\n');
133
+ });
134
+
135
+ return lines.join('\n');
136
+ }
137
+
138
+ /**
139
+ * Generate TypeScript types for events
140
+ * @private
141
+ */
142
+ function generateEventTypes(events) {
143
+ const lines = [];
144
+
145
+ events.forEach(event => {
146
+ const typeName = `${event.name}Event`;
147
+ lines.push(`export interface ${typeName} {`);
148
+
149
+ event.inputs.forEach((input, idx) => {
150
+ const name = input.name || `param${idx}`;
151
+ const type = solToTsType(input.type);
152
+ const indexed = input.indexed ? ' // indexed' : '';
153
+ lines.push(` ${name}: ${type};${indexed}`);
154
+ });
155
+
156
+ lines.push('}\n');
157
+ });
158
+
159
+ return lines.join('\n');
160
+ }
161
+
162
+ /**
163
+ * Generate contract method signatures
164
+ * @private
165
+ */
166
+ function generateContractMethods(functions) {
167
+ const lines = [];
168
+
169
+ functions.forEach(func => {
170
+ const inputs = func.inputs.map(inp => {
171
+ const type = solToTsType(inp.type);
172
+ return `${inp.name || 'param'}: ${type}`;
173
+ }).join(', ');
174
+
175
+ let returnType = 'Promise<void>';
176
+ if (func.outputs && func.outputs.length > 0) {
177
+ if (func.outputs.length === 1) {
178
+ returnType = `Promise<${solToTsType(func.outputs[0].type)}>`;
179
+ } else {
180
+ returnType = `Promise<[${func.outputs.map(o => solToTsType(o.type)).join(', ')}]>`;
181
+ }
182
+ }
183
+
184
+ const stateMutability = func.stateMutability || 'nonpayable';
185
+ lines.push(` ${func.name}(${inputs}): ${returnType}; // ${stateMutability}`);
186
+ });
187
+
188
+ return lines.join('\n');
189
+ }
190
+
191
+ /**
192
+ * Convert Solidity type to TypeScript type
193
+ * @private
194
+ */
195
+ function solToTsType(solidityType) {
196
+ // Handle arrays
197
+ if (solidityType.endsWith(']')) {
198
+ const baseType = solidityType.replace(/\[\d*\]/g, '');
199
+ return `${solToTsType(baseType)}[]`;
200
+ }
201
+
202
+ // Handle base types
203
+ if (solidityType.startsWith('uint')) return 'bigint';
204
+ if (solidityType.startsWith('int')) return 'bigint';
205
+ if (solidityType.startsWith('bytes')) return 'string | Uint8Array';
206
+ if (solidityType === 'bool') return 'boolean';
207
+ if (solidityType === 'address') return 'string'; // EVM address as checksum string
208
+ if (solidityType === 'string') return 'string';
209
+
210
+ // Fallback for custom types
211
+ return 'any';
212
+ }
213
+
214
+ /**
215
+ * Extract struct types from ABI
216
+ * @private
217
+ */
218
+ function extractStructTypes(abi) {
219
+ // This would require more advanced parsing for tuple types
220
+ return [];
221
+ }
222
+
223
+ /**
224
+ * Generate struct type definitions
225
+ * @private
226
+ */
227
+ function generateStructTypes(structs) {
228
+ if (structs.length === 0) return '// No custom structs\n';
229
+
230
+ return structs.map(struct => `export interface ${struct.name} {\n // struct fields\n}\n`).join('\n');
231
+ }
232
+
233
+ /**
234
+ * Generate barrel export index.ts
235
+ * @private
236
+ */
237
+ function generateIndexFile(typesPath, abiFiles) {
238
+ const exports = abiFiles
239
+ .map(file => {
240
+ const name = file.replace('.abi.json', '');
241
+ return `export * from './${name}';`;
242
+ })
243
+ .join('\n');
244
+
245
+ const indexPath = path.join(typesPath, 'index.ts');
246
+ fs.writeFileSync(indexPath, `// Auto-generated barrel export\n${exports}\n`);
247
+ }