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 +26 -0
- package/cli/commands/account/index.js +55 -20
- package/cli/commands/wallet/index.js +9 -8
- package/cli/index.js +20 -4
- package/cli/utils/auth.js +18 -15
- package/cli/utils/constants.js +1 -1
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
188
|
+
// Get account details from first available network
|
|
189
|
+
let owners = [];
|
|
190
|
+
let required = 0;
|
|
191
|
+
let primaryNetwork = null;
|
|
187
192
|
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
20
|
-
* 3.
|
|
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:
|
|
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),
|
|
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
|
|
package/cli/utils/constants.js
CHANGED
|
@@ -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 = '
|
|
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';
|