ethnotary 1.0.1 → 1.0.2

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.
package/README.md CHANGED
@@ -61,6 +61,7 @@ npm --version # Should show 8.x.x or higher
61
61
  ```bash
62
62
  # Install globally from npm
63
63
  npm install -g ethnotary
64
+ ```
64
65
 
65
66
  ## Quick Start
66
67
 
@@ -110,6 +111,31 @@ ethnotary wallet import # Import existing key/mnemonic
110
111
  ethnotary wallet show # Display wallet address
111
112
  ```
112
113
 
114
+ ## Wallet Security
115
+
116
+ Your private keys **never leave your machine**. Ethnotary stores wallet credentials locally in an encrypted keystore file, protected by a password you choose.
117
+
118
+ **Wallet Priority:**
119
+ 1. `--private-key` flag – Explicit override for single commands
120
+ 2. **Encrypted keystore** – Default, password-protected (recommended)
121
+ 3. `PRIVATE_KEY` env var – Fallback for scripts/automation
122
+
123
+ ```bash
124
+ # Create a new encrypted wallet (stored locally)
125
+ ethnotary wallet init
126
+
127
+ # View your wallet address
128
+ ethnotary wallet show
129
+ ```
130
+
131
+ **What stays on your machine:**
132
+ - Private keys (encrypted in keystore)
133
+ - Wallet passwords (never stored, prompted each time)
134
+ - RPC URLs and API keys (in `~/.ethnotary/config.json`)
135
+
136
+ **What goes on-chain:**
137
+ - Only signed transactions you explicitly submit
138
+ - Your public address (visible in transactions)
113
139
  ### Account Management
114
140
 
115
141
  Account management commands apply changes across ALL networks the contract is deployed on.
@@ -168,40 +168,75 @@ account
168
168
 
169
169
  try {
170
170
  const address = resolveAddress(options.address);
171
- // Get first network from contract's networks, or use specified network
172
171
  const contractNetworks = getContractNetworks(options.address);
173
- const networkKey = globalOpts.network || contractNetworks[0] || 'sepolia';
174
- const network = await getNetwork(networkKey);
175
- const provider = new ethers.JsonRpcProvider(network.rpc);
172
+ const networksToQuery = contractNetworks.length > 0 ? contractNetworks : ['sepolia'];
176
173
 
177
- // Check if contract exists
178
- const code = await provider.getCode(address);
179
- if (code === '0x') {
180
- out.error(`No contract found at ${address} on ${network.name}`);
181
- return;
182
- }
174
+ out.startSpinner('Fetching account info across all networks...');
183
175
 
184
- const multisig = new ethers.Contract(address, MULTISIG_ABI, provider);
176
+ // Fetch balances from all networks in parallel
177
+ const balancePromises = networksToQuery.map(async (networkKey) => {
178
+ try {
179
+ const network = await getNetwork(networkKey);
180
+ const provider = new ethers.JsonRpcProvider(network.rpc);
181
+ const balance = await provider.getBalance(address);
182
+ return { network: networkKey, balance: ethers.formatEther(balance), raw: balance };
183
+ } catch (e) {
184
+ return { network: networkKey, balance: 'unavailable', error: e.message };
185
+ }
186
+ });
185
187
 
186
- out.startSpinner('Fetching account info...');
188
+ // Get account details from first available network
189
+ let owners = [];
190
+ let required = 0;
191
+ let primaryNetwork = null;
187
192
 
188
- const [owners, required, balance] = await Promise.all([
189
- multisig.getOwners(),
190
- multisig.required(),
191
- provider.getBalance(address)
192
- ]);
193
+ for (const networkKey of networksToQuery) {
194
+ try {
195
+ const network = await getNetwork(networkKey);
196
+ const provider = new ethers.JsonRpcProvider(network.rpc);
197
+ const code = await provider.getCode(address);
198
+ if (code !== '0x') {
199
+ const multisig = new ethers.Contract(address, MULTISIG_ABI, provider);
200
+ [owners, required] = await Promise.all([
201
+ multisig.getOwners(),
202
+ multisig.required()
203
+ ]);
204
+ primaryNetwork = networkKey;
205
+ break;
206
+ }
207
+ } catch (e) {
208
+ continue;
209
+ }
210
+ }
211
+
212
+ if (!primaryNetwork) {
213
+ out.failSpinner('Contract not found on any network');
214
+ return;
215
+ }
216
+
217
+ const balances = await Promise.all(balancePromises);
218
+ const totalBalance = balances.reduce((sum, b) => {
219
+ if (b.raw) return sum + b.raw;
220
+ return sum;
221
+ }, 0n);
193
222
 
194
223
  out.succeedSpinner('Account info retrieved');
195
224
 
225
+ // Format balances object
226
+ const balancesByNetwork = {};
227
+ for (const b of balances) {
228
+ balancesByNetwork[b.network] = b.error ? b.balance : `${b.balance} ETH`;
229
+ }
230
+
196
231
  out.print({
197
232
  address,
198
- network: networkKey,
199
- networks: contractNetworks,
233
+ networks: networksToQuery,
200
234
  owners: owners.map(o => o),
201
235
  required: Number(required),
202
236
  ownerCount: owners.length,
203
237
  confirmationsNeeded: `${required} of ${owners.length}`,
204
- balance: ethers.formatEther(balance) + ' ETH'
238
+ balances: balancesByNetwork,
239
+ totalBalance: ethers.formatEther(totalBalance) + ' ETH'
205
240
  });
206
241
 
207
242
  } catch (error) {
@@ -151,26 +151,27 @@ wallet
151
151
  const out = createOutput(globalOpts);
152
152
 
153
153
  try {
154
- // Check for private key in options or env
154
+ // Priority 1: --private-key flag (explicit override)
155
155
  if (globalOpts.privateKey) {
156
156
  const wallet = new ethers.Wallet(globalOpts.privateKey);
157
157
  out.print({ address: wallet.address, source: 'private-key-flag' });
158
158
  return;
159
159
  }
160
160
 
161
- if (process.env.PRIVATE_KEY) {
162
- const wallet = new ethers.Wallet(process.env.PRIVATE_KEY);
163
- out.print({ address: wallet.address, source: 'environment' });
164
- return;
165
- }
166
-
167
- // Check keystore
161
+ // Priority 2: Keystore (default if exists)
168
162
  if (keystoreExists()) {
169
163
  const address = getKeystoreAddress();
170
164
  out.print({ address: address, source: 'keystore' });
171
165
  return;
172
166
  }
173
167
 
168
+ // Priority 3: Environment variable (fallback)
169
+ if (process.env.PRIVATE_KEY) {
170
+ const wallet = new ethers.Wallet(process.env.PRIVATE_KEY);
171
+ out.print({ address: wallet.address, source: 'environment' });
172
+ return;
173
+ }
174
+
174
175
  out.error('No wallet configured. Use --private-key, set PRIVATE_KEY env var, or run "ethnotary wallet init"');
175
176
 
176
177
  } catch (error) {
package/cli/index.js CHANGED
@@ -558,18 +558,34 @@ async function checkRpcConfig() {
558
558
  const receipt = await tx.wait();
559
559
 
560
560
  // Get deployed address from event or use predicted
561
- let actualAddress = predictedAddress;
561
+ let actualAddress = null;
562
562
  for (const log of receipt.logs) {
563
563
  try {
564
564
  const parsed = factory.interface.parseLog(log);
565
565
  if (parsed && parsed.name === 'NewMSACreated') {
566
- actualAddress = parsed.args.msa;
566
+ // The event has a single indexed address parameter
567
+ actualAddress = parsed.args.msaAddress || parsed.args[0];
567
568
  break;
568
569
  }
569
- } catch (e) {}
570
+ } catch (e) {
571
+ // Log might be from a different contract, try parsing the topic directly
572
+ // NewMSACreated(address indexed msaAddress) - topic[1] contains the address
573
+ if (log.topics && log.topics.length >= 2) {
574
+ const eventSig = ethers.id('NewMSACreated(address)');
575
+ if (log.topics[0] === eventSig) {
576
+ actualAddress = ethers.getAddress('0x' + log.topics[1].slice(26));
577
+ break;
578
+ }
579
+ }
580
+ }
581
+ }
582
+
583
+ // Fallback to predicted address if event parsing failed
584
+ if (!actualAddress) {
585
+ actualAddress = predictedAddress;
570
586
  }
571
587
 
572
- if (!deployedAddress) {
588
+ if (!deployedAddress && actualAddress) {
573
589
  deployedAddress = actualAddress;
574
590
  }
575
591
 
package/cli/utils/auth.js CHANGED
@@ -15,12 +15,12 @@ function ensureDir() {
15
15
 
16
16
  /**
17
17
  * Get wallet from various sources (priority order):
18
- * 1. --private-key flag
19
- * 2. PRIVATE_KEY env var
20
- * 3. Encrypted keystore (prompts for password)
18
+ * 1. --private-key flag (explicit override)
19
+ * 2. Encrypted keystore (default if exists)
20
+ * 3. PRIVATE_KEY env var (fallback for scripts/automation)
21
21
  */
22
22
  async function getWallet(options = {}) {
23
- // Priority 1: Direct private key from CLI flag
23
+ // Priority 1: Direct private key from CLI flag (explicit override)
24
24
  if (options.privateKey) {
25
25
  try {
26
26
  return new ethers.Wallet(options.privateKey);
@@ -29,20 +29,14 @@ async function getWallet(options = {}) {
29
29
  }
30
30
  }
31
31
 
32
- // Priority 2: Environment variable
33
- if (process.env.PRIVATE_KEY) {
34
- try {
35
- return new ethers.Wallet(process.env.PRIVATE_KEY);
36
- } catch (e) {
37
- throw new Error(`Invalid PRIVATE_KEY in environment: ${e.message}`);
38
- }
39
- }
40
-
41
- // Priority 3: Encrypted keystore
32
+ // Priority 2: Encrypted keystore (default if exists)
42
33
  if (keystoreExists()) {
43
34
  if (!options.password) {
44
- // In non-interactive mode (--json), we can't prompt
35
+ // In non-interactive mode (--json), fall back to env var
45
36
  if (options.json) {
37
+ if (process.env.PRIVATE_KEY) {
38
+ return new ethers.Wallet(process.env.PRIVATE_KEY);
39
+ }
46
40
  throw new Error('No private key provided. Use --private-key or set PRIVATE_KEY env var.');
47
41
  }
48
42
  // Interactive mode - prompt for password
@@ -58,6 +52,15 @@ async function getWallet(options = {}) {
58
52
  return await loadKeystore(options.password);
59
53
  }
60
54
 
55
+ // Priority 3: Environment variable (fallback for scripts/automation)
56
+ if (process.env.PRIVATE_KEY) {
57
+ try {
58
+ return new ethers.Wallet(process.env.PRIVATE_KEY);
59
+ } catch (e) {
60
+ throw new Error(`Invalid PRIVATE_KEY in environment: ${e.message}`);
61
+ }
62
+ }
63
+
61
64
  throw new Error('No wallet configured. Use --private-key, set PRIVATE_KEY env var, or run "ethnotary wallet init"');
62
65
  }
63
66
 
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  // Default MSA Factory address - same across all EVM networks via CREATE2
8
- const DEFAULT_MSA_FACTORY = '0x25115564780D3Da99623EaeB9f4eFE88eD801261';
8
+ const DEFAULT_MSA_FACTORY = '0x3DEB514B2ac536b8048f5b37182196cf9d5dDD45';
9
9
 
10
10
  // Default PIN Verifier (Groth16Verifier) address - same across all EVM networks via CREATE2
11
11
  const DEFAULT_PIN_VERIFIER = '0x65ee46C4d21405f4a4C8e9d0F8a3832c1B885ab4';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ethnotary",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "CLI for managing MultiSig accounts, transactions, and data queries across EVM networks",
5
5
  "main": "cli/index.js",
6
6
  "bin": {