@ethernity-cloud/sdk-js 1.6.1 → 1.6.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/info.js CHANGED
@@ -1,324 +1,324 @@
1
- #!/usr/bin/env node
2
- /**
3
- * ecld-info: everything a developer needs to read or troubleshoot an enclave.
4
- *
5
- * Read-only and free (eth_call + event logs -- no task, no gas, no private
6
- * key). Reads your project's identity from .env (PROJECT_NAME / IPFS_HASH /
7
- * BLOCKCHAIN_NETWORK), then reports:
8
- *
9
- * NETWORK name, type, chain id, RPC, contract addresses, live block
10
- * TRUSTEDZONE the gatekeeper enclave's Image Registry record
11
- * SECURELOCK the executor enclave's Image Registry record
12
- * ESR registry address, total keys, and -- with --enclave <wallet>
13
- * -- this enclave's recent StateCommitted events
14
- *
15
- * Subcommands (default: the full summary):
16
- * ecld-info full summary
17
- * ecld-info network just the network section
18
- * ecld-info trustedzone just the trustedzone registration
19
- * ecld-info securelock just the securelock registration
20
- * ecld-info esr address|count|state|version|nonce|list ESR inspection
21
- *
22
- * Registration is keyed by the image's IPFS hash: pass --ipfs <hash> for an
23
- * exact record, or --name <n> to resolve the latest registered version of a
24
- * name. With neither, .env IPFS_HASH / PROJECT_NAME are used.
25
- *
26
- * Network: --network NAME, else ESR_NETWORK env, else .env BLOCKCHAIN_NETWORK,
27
- * else BLOXBERG_TESTNET.
28
- */
29
-
30
- 'use strict';
31
-
32
- const fs = require('fs');
33
- const path = require('path');
34
- const { ethers } = require('ethers');
35
- try { require('dotenv').config(); } catch (e) { /* dotenv optional */ }
36
-
37
- // Authoritative per-network values (mirror of the Python SDK's network table).
38
- const NETWORKS = {
39
- BLOXBERG_MAINNET: {
40
- type: 'mainnet', chainId: 8995, rpc: 'https://core.bloxberg.org',
41
- protocol: '0x549A6E06BB2084100148D50F51CF77a3436C3Ae7',
42
- imageRegistry: '0x15D73a742529C3fb11f3FA32EF7f0CC3870ACA31',
43
- esr: '0x4Bf5cDE3BFD73dd10B707f8B123Ba631D2EBEAD2',
44
- },
45
- BLOXBERG_TESTNET: {
46
- type: 'testnet', chainId: 8995, rpc: 'https://core.bloxberg.org',
47
- protocol: '0x02882F03097fE8cD31afbdFbB5D72a498B41112c',
48
- imageRegistry: '0x15D73a742529C3fb11f3FA32EF7f0CC3870ACA31',
49
- esr: '0x0Ea1728EAE108FD3B9340ae91451348E2Cc6b4E4',
50
- },
51
- LITVM_LITEFORGE: {
52
- type: 'testnet', chainId: 0, rpc: 'https://liteforge.rpc.caldera.xyz/infra-partner-http',
53
- protocol: '', imageRegistry: '', esr: '0x709052Fe77Af543d3d9FE2Ac06a15c635c8D4Be5',
54
- },
55
- };
56
-
57
- const IMAGE_REGISTRY_ABI = [
58
- 'function imageDetails(string) view returns (address owner, string ipfsHash, string version, string session, uint256 fee, address rewardAddress, bool validated, bool published, string certPublicKey, string dockerComposeHash, string name)',
59
- 'function trustedZoneImageDetails(string) view returns (address owner, string ipfsHash, string version, string session, uint256 fee, address rewardAddress, bool validated, bool published, string certPublicKey, string dockerComposeHash, string name)',
60
- 'function imageVersions(string, uint256) view returns (string)',
61
- 'function trustedZoneImageVersions(string, uint256) view returns (string)',
62
- ];
63
-
64
- const ESR_ABI = [
65
- 'function getState(address enclave, bytes32 key) view returns (string cid, uint256 version, uint64 updatedAt)',
66
- 'function getVersion(address enclave, bytes32 key) view returns (uint256)',
67
- 'function exists(address enclave, bytes32 key) view returns (bool)',
68
- // The idempotency nonce is PUBLIC on-chain data, recorded next to the
69
- // version and enforced strictly in-order per (enclave, key).
70
- 'function getNonce(address enclave, bytes32 key) view returns (uint256)',
71
- 'function entryCount() view returns (uint256)',
72
- 'function getEntriesFrom(uint256 start, uint256 limit) view returns (address[] enclaves, bytes32[] keys, string[] cids, uint256[] versions, uint64[] updatedAts, uint256 total)',
73
- 'event StateCommitted(address indexed enclave, bytes32 indexed key, string cid, uint256 version, uint256 seq, uint256 nonce)',
74
- ];
75
-
76
- function resolveNetwork(name) {
77
- name = (name || process.env.ESR_NETWORK || process.env.BLOCKCHAIN_NETWORK || 'BLOXBERG_TESTNET')
78
- .toUpperCase();
79
- const net = NETWORKS[name];
80
- if (!net) {
81
- throw new Error(`Unknown network '${name}'. Known: ${Object.keys(NETWORKS).join(', ')}`);
82
- }
83
- return { name, net };
84
- }
85
-
86
- function provider(rpc) {
87
- return new ethers.providers.JsonRpcProvider(rpc);
88
- }
89
-
90
- function keyHash(key) {
91
- return ethers.utils.id(key); // keccak256(utf8(key))
92
- }
93
-
94
- function looksLikeCID(cid) {
95
- return typeof cid === 'string' && (cid.startsWith('Qm') || cid.startsWith('bafk')) && cid.length > 20;
96
- }
97
-
98
- function decodeDetails(d, fallbackName) {
99
- return {
100
- name: d.name || fallbackName,
101
- published: Boolean(d.published),
102
- validated: Boolean(d.validated),
103
- image_ipfs_hash: d.ipfsHash || null,
104
- image_version: d.version || null,
105
- reward_address: d.rewardAddress,
106
- owner: d.owner,
107
- docker_compose_hash: d.dockerComposeHash || null,
108
- attestation_session: Boolean(d.session),
109
- cert_present: Boolean(d.certPublicKey),
110
- };
111
- }
112
-
113
- async function lookupRegistration(reg, kind, ipfs, name) {
114
- const detailsFn = kind === 'trustedzone' ? 'trustedZoneImageDetails' : 'imageDetails';
115
- const versionsFn = kind === 'trustedzone' ? 'trustedZoneImageVersions' : 'imageVersions';
116
- if (ipfs) {
117
- try { return decodeDetails(await reg[detailsFn](ipfs), ipfs); }
118
- catch (e) { return { found: false, error: e.message }; }
119
- }
120
- if (name) {
121
- let latest = null;
122
- for (let i = 0; i < 64; i += 1) {
123
- let v;
124
- try { v = await reg[versionsFn](name, i); } catch (e) { break; }
125
- if (!v) break;
126
- latest = v;
127
- }
128
- if (latest) {
129
- try { return decodeDetails(await reg[detailsFn](latest), name); }
130
- catch (e) { return { found: false, error: e.message }; }
131
- }
132
- return { found: false, note: `no registered version for name '${name}' -- the Image Registry is keyed by image IPFS hash; pass --ipfs <hash>` };
133
- }
134
- return { found: false, note: 'no --ipfs or --name to look up' };
135
- }
136
-
137
- async function sectionNetwork(prov, name, net) {
138
- const out = {
139
- network: name, type: net.type, chain_id: net.chainId, rpc: net.rpc,
140
- protocol_contract: net.protocol || '(n/a)',
141
- image_registry: net.imageRegistry || '(n/a)',
142
- esr_contract: net.esr || '(not deployed)',
143
- };
144
- try {
145
- out.latest_block = await prov.getBlockNumber();
146
- out.connected = true;
147
- } catch (e) {
148
- out.connected = false; out.error = e.message;
149
- }
150
- return out;
151
- }
152
-
153
- async function sectionRegistration(prov, net, kind, ipfs, name) {
154
- if (!net.imageRegistry) return { note: 'no image registry on this network' };
155
- const reg = new ethers.Contract(net.imageRegistry, IMAGE_REGISTRY_ABI, prov);
156
- return lookupRegistration(reg, kind, ipfs, name);
157
- }
158
-
159
- async function sectionEsr(prov, net, enclaveWallet, eventsN) {
160
- if (!net.esr) return { note: 'ESR is not deployed on this network' };
161
- const esr = new ethers.Contract(net.esr, ESR_ABI, prov);
162
- const out = { esr_contract: net.esr };
163
- try { out.total_registry_entries = (await esr.entryCount()).toNumber(); } catch (e) { /* ignore */ }
164
- if (enclaveWallet) {
165
- try {
166
- const latest = await prov.getBlockNumber();
167
- const from = Math.max(0, latest - 500000);
168
- const flt = esr.filters.StateCommitted(ethers.utils.getAddress(enclaveWallet));
169
- let logs = await esr.queryFilter(flt, from, 'latest');
170
- logs = logs.slice(-eventsN);
171
- out.recent_commits = logs.map((l) => ({
172
- key_hash: l.args.key,
173
- version: l.args.version.toNumber(),
174
- cid: l.args.cid,
175
- seq: l.args.seq.toNumber(),
176
- // PUBLIC per-key nonce (advances by 1 on every commit).
177
- nonce: l.args.nonce.toNumber(),
178
- block: l.blockNumber,
179
- }));
180
- } catch (e) {
181
- out.recent_commits_error = e.message;
182
- }
183
- } else {
184
- out.recent_commits_note = 'pass --enclave <ESR wallet> to list this enclave\'s state commits';
185
- }
186
- return out;
187
- }
188
-
189
- async function esrQuery(prov, name, net, args) {
190
- const esrAddr = args.contract || net.esr;
191
- const sub = args.esrCmd || 'address';
192
- if (sub === 'address') return { network: name, esr_contract: esrAddr || '(not deployed)', rpc: net.rpc };
193
- if (!esrAddr) throw new Error(`ESR is not deployed on ${name}.`);
194
- const c = new ethers.Contract(esrAddr, ESR_ABI, prov);
195
- if (sub === 'count') return { network: name, entry_count: (await c.entryCount()).toNumber() };
196
- if (sub === 'version') {
197
- const v = (await c.getVersion(ethers.utils.getAddress(args.enclave), keyHash(args.key))).toNumber();
198
- return { enclave: args.enclave, key: args.key, version: v };
199
- }
200
- if (sub === 'nonce') {
201
- const n = (await c.getNonce(ethers.utils.getAddress(args.enclave), keyHash(args.key))).toNumber();
202
- return {
203
- enclave: args.enclave, key: args.key, nonce: n,
204
- note: 'per-key nonce, PUBLIC data (free eth_call); advances by 1 on '
205
- + 'every commit, 0 = key never committed. For exactly-once, pass '
206
- + 'EXACTLY nonce + 1 to commit(); an omitted nonce is auto-assigned '
207
- + 'the next value by the registry',
208
- };
209
- }
210
- if (sub === 'state') {
211
- const enclave = ethers.utils.getAddress(args.enclave);
212
- const kh = keyHash(args.key);
213
- const [cid, version, updated] = await c.getState(enclave, kh);
214
- const exists = await c.exists(enclave, kh);
215
- return {
216
- network: name, enclave: args.enclave, key: args.key, key_hash: kh,
217
- exists, version: version.toNumber(),
218
- // PUBLIC per-key nonce; 0 = key never committed.
219
- nonce: (await c.getNonce(enclave, kh)).toNumber(),
220
- cid: cid || null, cid_valid: looksLikeCID(cid),
221
- updated_at: updated.toNumber(),
222
- note: 'cid points at ENCRYPTED state; only the enclave can decrypt it',
223
- };
224
- }
225
- if (sub === 'list') {
226
- const start = parseInt(args.start || 0, 10);
227
- const limit = parseInt(args.limit || 50, 10);
228
- const [encs, keys, cids, versions, updated, total] = await c.getEntriesFrom(start, limit);
229
- const entries = encs.map((e, i) => ({
230
- enclave: e, key_hash: keys[i], cid: cids[i] || null,
231
- version: versions[i].toNumber(), updated_at: updated[i].toNumber(),
232
- }));
233
- return { network: name, total: total.toNumber(), start, returned: entries.length, entries };
234
- }
235
- throw new Error(`unknown esr subcommand: ${sub}`);
236
- }
237
-
238
- // ---- arg parsing (tiny, dependency-free) -----------------------------------
239
-
240
- function parseArgs(argv) {
241
- const a = { _: [], json: false };
242
- for (let i = 0; i < argv.length; i += 1) {
243
- const t = argv[i];
244
- if (t === '--json') a.json = true;
245
- else if (t.startsWith('--')) { a[t.slice(2)] = argv[i + 1]; i += 1; }
246
- else a._.push(t);
247
- }
248
- return a;
249
- }
250
-
251
- function emit(obj, asJson) {
252
- if (asJson) { console.log(JSON.stringify(obj, null, 2)); return; }
253
- for (const [k, v] of Object.entries(obj)) {
254
- if (Array.isArray(v)) { console.log(`${k}:`); v.forEach((it) => console.log(' ' + JSON.stringify(it))); }
255
- else console.log(`${k}: ${v}`);
256
- }
257
- }
258
-
259
- function printFull(info) {
260
- const n = info.network;
261
- console.log('NETWORK');
262
- console.log(` ${n.network} (${n.type}, chain ${n.chain_id}) block ${n.latest_block || '?'} ${n.connected ? 'connected' : 'NOT CONNECTED'}`);
263
- console.log(` rpc: ${n.rpc}`);
264
- console.log(` protocol: ${n.protocol_contract}`);
265
- console.log(` image registry: ${n.image_registry}`);
266
- console.log(` esr: ${n.esr_contract}`);
267
- for (const [label, key] of [['TRUSTEDZONE', 'trustedzone'], ['SECURELOCK', 'securelock']]) {
268
- const r = info[key] || {};
269
- console.log(label);
270
- if (r.published || r.validated || r.image_ipfs_hash) {
271
- console.log(` published: ${r.published ? 'yes' : 'no'} validated: ${r.validated ? 'yes' : 'no'}`);
272
- console.log(` image hash: ${r.image_ipfs_hash}`);
273
- console.log(` version: ${r.image_version}`);
274
- console.log(` reward addr: ${r.reward_address}`);
275
- console.log(` attestation: ${r.attestation_session ? 'MRENCLAVE session present' : 'none'}`);
276
- } else {
277
- console.log(` not found (${r.error || r.note || 'not registered'})`);
278
- }
279
- }
280
- const e = info.esr || {};
281
- console.log('ESR');
282
- if (e.note) { console.log(` ${e.note}`); return; }
283
- console.log(` registry: ${e.esr_contract}`);
284
- if ('total_registry_entries' in e) console.log(` total keys: ${e.total_registry_entries} (all enclaves)`);
285
- if (e.recent_commits) {
286
- console.log(` recent state commits for this enclave: ${e.recent_commits.length}`);
287
- e.recent_commits.forEach((c) => console.log(` v${c.version} seq ${c.seq} nonce ${c.nonce} ${c.key_hash} ${c.cid} (block ${c.block})`));
288
- } else if (e.recent_commits_note) console.log(` ${e.recent_commits_note}`);
289
- else if (e.recent_commits_error) console.log(` (could not read commits: ${e.recent_commits_error})`);
290
- }
291
-
292
- async function main() {
293
- const args = parseArgs(process.argv.slice(2));
294
- const section = args._[0]; // network | trustedzone | securelock | esr | undefined
295
- const { name: netName, net } = resolveNetwork(args.network);
296
- const prov = provider(net.rpc);
297
-
298
- const ipfs = args.ipfs || process.env.IPFS_HASH || null;
299
- const projName = args.name || process.env.PROJECT_NAME || null;
300
-
301
- if (section === 'esr') {
302
- args.esrCmd = args._[1];
303
- const obj = await esrQuery(prov, netName, net, args);
304
- emit(obj, args.json);
305
- return;
306
- }
307
- if (section === 'network') { emit(await sectionNetwork(prov, netName, net), args.json); return; }
308
- if (section === 'trustedzone') { emit(await sectionRegistration(prov, net, 'trustedzone', ipfs, projName), args.json); return; }
309
- if (section === 'securelock') { emit(await sectionRegistration(prov, net, 'securelock', ipfs, projName), args.json); return; }
310
-
311
- const info = {
312
- project: { name: projName || '(unknown)', dapp_type: process.env.SERVICE_TYPE || process.env.DAPP_TYPE, version: process.env.VERSION },
313
- network: await sectionNetwork(prov, netName, net),
314
- trustedzone: await sectionRegistration(prov, net, 'trustedzone', ipfs, projName),
315
- securelock: await sectionRegistration(prov, net, 'securelock', ipfs, projName),
316
- esr: await sectionEsr(prov, net, args.enclave || null, parseInt(args.events || 10, 10)),
317
- };
318
- if (args.json) { console.log(JSON.stringify(info, null, 2)); return; }
319
- const pr = info.project;
320
- console.log(`PROJECT ${pr.name}${pr.dapp_type ? ' type ' + pr.dapp_type : ''}${pr.version != null ? ' version ' + pr.version : ''}`);
321
- printFull(info);
322
- }
323
-
324
- main().catch((e) => { console.error(`ecld-info: ${e.message}`); process.exit(1); });
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ecld-info: everything a developer needs to read or troubleshoot an enclave.
4
+ *
5
+ * Read-only and free (eth_call + event logs -- no task, no gas, no private
6
+ * key). Reads your project's identity from .env (PROJECT_NAME / IPFS_HASH /
7
+ * BLOCKCHAIN_NETWORK), then reports:
8
+ *
9
+ * NETWORK name, type, chain id, RPC, contract addresses, live block
10
+ * TRUSTEDZONE the gatekeeper enclave's Image Registry record
11
+ * SECURELOCK the executor enclave's Image Registry record
12
+ * ESR registry address, total keys, and -- with --enclave <wallet>
13
+ * -- this enclave's recent StateCommitted events
14
+ *
15
+ * Subcommands (default: the full summary):
16
+ * ecld-info full summary
17
+ * ecld-info network just the network section
18
+ * ecld-info trustedzone just the trustedzone registration
19
+ * ecld-info securelock just the securelock registration
20
+ * ecld-info esr address|count|state|version|nonce|list ESR inspection
21
+ *
22
+ * Registration is keyed by the image's IPFS hash: pass --ipfs <hash> for an
23
+ * exact record, or --name <n> to resolve the latest registered version of a
24
+ * name. With neither, .env IPFS_HASH / PROJECT_NAME are used.
25
+ *
26
+ * Network: --network NAME, else ESR_NETWORK env, else .env BLOCKCHAIN_NETWORK,
27
+ * else BLOXBERG_TESTNET.
28
+ */
29
+
30
+ 'use strict';
31
+
32
+ const fs = require('fs');
33
+ const path = require('path');
34
+ const { ethers } = require('ethers');
35
+ try { require('dotenv').config(); } catch (e) { /* dotenv optional */ }
36
+
37
+ // Authoritative per-network values (mirror of the Python SDK's network table).
38
+ const NETWORKS = {
39
+ BLOXBERG_MAINNET: {
40
+ type: 'mainnet', chainId: 8995, rpc: 'https://core.bloxberg.org',
41
+ protocol: '0x549A6E06BB2084100148D50F51CF77a3436C3Ae7',
42
+ imageRegistry: '0x15D73a742529C3fb11f3FA32EF7f0CC3870ACA31',
43
+ esr: '0x4Bf5cDE3BFD73dd10B707f8B123Ba631D2EBEAD2',
44
+ },
45
+ BLOXBERG_TESTNET: {
46
+ type: 'testnet', chainId: 8995, rpc: 'https://core.bloxberg.org',
47
+ protocol: '0x02882F03097fE8cD31afbdFbB5D72a498B41112c',
48
+ imageRegistry: '0x99A84C624C028bdf0a855A1E9E3f2fcf7275B3D8',
49
+ esr: '0x0Ea1728EAE108FD3B9340ae91451348E2Cc6b4E4',
50
+ },
51
+ LITVM_LITEFORGE: {
52
+ type: 'testnet', chainId: 0, rpc: 'https://liteforge.rpc.caldera.xyz/infra-partner-http',
53
+ protocol: '', imageRegistry: '', esr: '0x709052Fe77Af543d3d9FE2Ac06a15c635c8D4Be5',
54
+ },
55
+ };
56
+
57
+ const IMAGE_REGISTRY_ABI = [
58
+ 'function imageDetails(string) view returns (address owner, string ipfsHash, string version, string session, uint256 fee, address rewardAddress, bool validated, bool published, string certPublicKey, string dockerComposeHash, string name)',
59
+ 'function trustedZoneImageDetails(string) view returns (address owner, string ipfsHash, string version, string session, uint256 fee, address rewardAddress, bool validated, bool published, string certPublicKey, string dockerComposeHash, string name)',
60
+ 'function imageVersions(string, uint256) view returns (string)',
61
+ 'function trustedZoneImageVersions(string, uint256) view returns (string)',
62
+ ];
63
+
64
+ const ESR_ABI = [
65
+ 'function getState(address enclave, bytes32 key) view returns (string cid, uint256 version, uint64 updatedAt)',
66
+ 'function getVersion(address enclave, bytes32 key) view returns (uint256)',
67
+ 'function exists(address enclave, bytes32 key) view returns (bool)',
68
+ // The idempotency nonce is PUBLIC on-chain data, recorded next to the
69
+ // version and enforced strictly in-order per (enclave, key).
70
+ 'function getNonce(address enclave, bytes32 key) view returns (uint256)',
71
+ 'function entryCount() view returns (uint256)',
72
+ 'function getEntriesFrom(uint256 start, uint256 limit) view returns (address[] enclaves, bytes32[] keys, string[] cids, uint256[] versions, uint64[] updatedAts, uint256 total)',
73
+ 'event StateCommitted(address indexed enclave, bytes32 indexed key, string cid, uint256 version, uint256 seq, uint256 nonce)',
74
+ ];
75
+
76
+ function resolveNetwork(name) {
77
+ name = (name || process.env.ESR_NETWORK || process.env.BLOCKCHAIN_NETWORK || 'BLOXBERG_TESTNET')
78
+ .toUpperCase();
79
+ const net = NETWORKS[name];
80
+ if (!net) {
81
+ throw new Error(`Unknown network '${name}'. Known: ${Object.keys(NETWORKS).join(', ')}`);
82
+ }
83
+ return { name, net };
84
+ }
85
+
86
+ function provider(rpc) {
87
+ return new ethers.providers.JsonRpcProvider(rpc);
88
+ }
89
+
90
+ function keyHash(key) {
91
+ return ethers.utils.id(key); // keccak256(utf8(key))
92
+ }
93
+
94
+ function looksLikeCID(cid) {
95
+ return typeof cid === 'string' && (cid.startsWith('Qm') || cid.startsWith('bafk')) && cid.length > 20;
96
+ }
97
+
98
+ function decodeDetails(d, fallbackName) {
99
+ return {
100
+ name: d.name || fallbackName,
101
+ published: Boolean(d.published),
102
+ validated: Boolean(d.validated),
103
+ image_ipfs_hash: d.ipfsHash || null,
104
+ image_version: d.version || null,
105
+ reward_address: d.rewardAddress,
106
+ owner: d.owner,
107
+ docker_compose_hash: d.dockerComposeHash || null,
108
+ attestation_session: Boolean(d.session),
109
+ cert_present: Boolean(d.certPublicKey),
110
+ };
111
+ }
112
+
113
+ async function lookupRegistration(reg, kind, ipfs, name) {
114
+ const detailsFn = kind === 'trustedzone' ? 'trustedZoneImageDetails' : 'imageDetails';
115
+ const versionsFn = kind === 'trustedzone' ? 'trustedZoneImageVersions' : 'imageVersions';
116
+ if (ipfs) {
117
+ try { return decodeDetails(await reg[detailsFn](ipfs), ipfs); }
118
+ catch (e) { return { found: false, error: e.message }; }
119
+ }
120
+ if (name) {
121
+ let latest = null;
122
+ for (let i = 0; i < 64; i += 1) {
123
+ let v;
124
+ try { v = await reg[versionsFn](name, i); } catch (e) { break; }
125
+ if (!v) break;
126
+ latest = v;
127
+ }
128
+ if (latest) {
129
+ try { return decodeDetails(await reg[detailsFn](latest), name); }
130
+ catch (e) { return { found: false, error: e.message }; }
131
+ }
132
+ return { found: false, note: `no registered version for name '${name}' -- the Image Registry is keyed by image IPFS hash; pass --ipfs <hash>` };
133
+ }
134
+ return { found: false, note: 'no --ipfs or --name to look up' };
135
+ }
136
+
137
+ async function sectionNetwork(prov, name, net) {
138
+ const out = {
139
+ network: name, type: net.type, chain_id: net.chainId, rpc: net.rpc,
140
+ protocol_contract: net.protocol || '(n/a)',
141
+ image_registry: net.imageRegistry || '(n/a)',
142
+ esr_contract: net.esr || '(not deployed)',
143
+ };
144
+ try {
145
+ out.latest_block = await prov.getBlockNumber();
146
+ out.connected = true;
147
+ } catch (e) {
148
+ out.connected = false; out.error = e.message;
149
+ }
150
+ return out;
151
+ }
152
+
153
+ async function sectionRegistration(prov, net, kind, ipfs, name) {
154
+ if (!net.imageRegistry) return { note: 'no image registry on this network' };
155
+ const reg = new ethers.Contract(net.imageRegistry, IMAGE_REGISTRY_ABI, prov);
156
+ return lookupRegistration(reg, kind, ipfs, name);
157
+ }
158
+
159
+ async function sectionEsr(prov, net, enclaveWallet, eventsN) {
160
+ if (!net.esr) return { note: 'ESR is not deployed on this network' };
161
+ const esr = new ethers.Contract(net.esr, ESR_ABI, prov);
162
+ const out = { esr_contract: net.esr };
163
+ try { out.total_registry_entries = (await esr.entryCount()).toNumber(); } catch (e) { /* ignore */ }
164
+ if (enclaveWallet) {
165
+ try {
166
+ const latest = await prov.getBlockNumber();
167
+ const from = Math.max(0, latest - 500000);
168
+ const flt = esr.filters.StateCommitted(ethers.utils.getAddress(enclaveWallet));
169
+ let logs = await esr.queryFilter(flt, from, 'latest');
170
+ logs = logs.slice(-eventsN);
171
+ out.recent_commits = logs.map((l) => ({
172
+ key_hash: l.args.key,
173
+ version: l.args.version.toNumber(),
174
+ cid: l.args.cid,
175
+ seq: l.args.seq.toNumber(),
176
+ // PUBLIC per-key nonce (advances by 1 on every commit).
177
+ nonce: l.args.nonce.toNumber(),
178
+ block: l.blockNumber,
179
+ }));
180
+ } catch (e) {
181
+ out.recent_commits_error = e.message;
182
+ }
183
+ } else {
184
+ out.recent_commits_note = 'pass --enclave <ESR wallet> to list this enclave\'s state commits';
185
+ }
186
+ return out;
187
+ }
188
+
189
+ async function esrQuery(prov, name, net, args) {
190
+ const esrAddr = args.contract || net.esr;
191
+ const sub = args.esrCmd || 'address';
192
+ if (sub === 'address') return { network: name, esr_contract: esrAddr || '(not deployed)', rpc: net.rpc };
193
+ if (!esrAddr) throw new Error(`ESR is not deployed on ${name}.`);
194
+ const c = new ethers.Contract(esrAddr, ESR_ABI, prov);
195
+ if (sub === 'count') return { network: name, entry_count: (await c.entryCount()).toNumber() };
196
+ if (sub === 'version') {
197
+ const v = (await c.getVersion(ethers.utils.getAddress(args.enclave), keyHash(args.key))).toNumber();
198
+ return { enclave: args.enclave, key: args.key, version: v };
199
+ }
200
+ if (sub === 'nonce') {
201
+ const n = (await c.getNonce(ethers.utils.getAddress(args.enclave), keyHash(args.key))).toNumber();
202
+ return {
203
+ enclave: args.enclave, key: args.key, nonce: n,
204
+ note: 'per-key nonce, PUBLIC data (free eth_call); advances by 1 on '
205
+ + 'every commit, 0 = key never committed. For exactly-once, pass '
206
+ + 'EXACTLY nonce + 1 to commit(); an omitted nonce is auto-assigned '
207
+ + 'the next value by the registry',
208
+ };
209
+ }
210
+ if (sub === 'state') {
211
+ const enclave = ethers.utils.getAddress(args.enclave);
212
+ const kh = keyHash(args.key);
213
+ const [cid, version, updated] = await c.getState(enclave, kh);
214
+ const exists = await c.exists(enclave, kh);
215
+ return {
216
+ network: name, enclave: args.enclave, key: args.key, key_hash: kh,
217
+ exists, version: version.toNumber(),
218
+ // PUBLIC per-key nonce; 0 = key never committed.
219
+ nonce: (await c.getNonce(enclave, kh)).toNumber(),
220
+ cid: cid || null, cid_valid: looksLikeCID(cid),
221
+ updated_at: updated.toNumber(),
222
+ note: 'cid points at ENCRYPTED state; only the enclave can decrypt it',
223
+ };
224
+ }
225
+ if (sub === 'list') {
226
+ const start = parseInt(args.start || 0, 10);
227
+ const limit = parseInt(args.limit || 50, 10);
228
+ const [encs, keys, cids, versions, updated, total] = await c.getEntriesFrom(start, limit);
229
+ const entries = encs.map((e, i) => ({
230
+ enclave: e, key_hash: keys[i], cid: cids[i] || null,
231
+ version: versions[i].toNumber(), updated_at: updated[i].toNumber(),
232
+ }));
233
+ return { network: name, total: total.toNumber(), start, returned: entries.length, entries };
234
+ }
235
+ throw new Error(`unknown esr subcommand: ${sub}`);
236
+ }
237
+
238
+ // ---- arg parsing (tiny, dependency-free) -----------------------------------
239
+
240
+ function parseArgs(argv) {
241
+ const a = { _: [], json: false };
242
+ for (let i = 0; i < argv.length; i += 1) {
243
+ const t = argv[i];
244
+ if (t === '--json') a.json = true;
245
+ else if (t.startsWith('--')) { a[t.slice(2)] = argv[i + 1]; i += 1; }
246
+ else a._.push(t);
247
+ }
248
+ return a;
249
+ }
250
+
251
+ function emit(obj, asJson) {
252
+ if (asJson) { console.log(JSON.stringify(obj, null, 2)); return; }
253
+ for (const [k, v] of Object.entries(obj)) {
254
+ if (Array.isArray(v)) { console.log(`${k}:`); v.forEach((it) => console.log(' ' + JSON.stringify(it))); }
255
+ else console.log(`${k}: ${v}`);
256
+ }
257
+ }
258
+
259
+ function printFull(info) {
260
+ const n = info.network;
261
+ console.log('NETWORK');
262
+ console.log(` ${n.network} (${n.type}, chain ${n.chain_id}) block ${n.latest_block || '?'} ${n.connected ? 'connected' : 'NOT CONNECTED'}`);
263
+ console.log(` rpc: ${n.rpc}`);
264
+ console.log(` protocol: ${n.protocol_contract}`);
265
+ console.log(` image registry: ${n.image_registry}`);
266
+ console.log(` esr: ${n.esr_contract}`);
267
+ for (const [label, key] of [['TRUSTEDZONE', 'trustedzone'], ['SECURELOCK', 'securelock']]) {
268
+ const r = info[key] || {};
269
+ console.log(label);
270
+ if (r.published || r.validated || r.image_ipfs_hash) {
271
+ console.log(` published: ${r.published ? 'yes' : 'no'} validated: ${r.validated ? 'yes' : 'no'}`);
272
+ console.log(` image hash: ${r.image_ipfs_hash}`);
273
+ console.log(` version: ${r.image_version}`);
274
+ console.log(` reward addr: ${r.reward_address}`);
275
+ console.log(` attestation: ${r.attestation_session ? 'MRENCLAVE session present' : 'none'}`);
276
+ } else {
277
+ console.log(` not found (${r.error || r.note || 'not registered'})`);
278
+ }
279
+ }
280
+ const e = info.esr || {};
281
+ console.log('ESR');
282
+ if (e.note) { console.log(` ${e.note}`); return; }
283
+ console.log(` registry: ${e.esr_contract}`);
284
+ if ('total_registry_entries' in e) console.log(` total keys: ${e.total_registry_entries} (all enclaves)`);
285
+ if (e.recent_commits) {
286
+ console.log(` recent state commits for this enclave: ${e.recent_commits.length}`);
287
+ e.recent_commits.forEach((c) => console.log(` v${c.version} seq ${c.seq} nonce ${c.nonce} ${c.key_hash} ${c.cid} (block ${c.block})`));
288
+ } else if (e.recent_commits_note) console.log(` ${e.recent_commits_note}`);
289
+ else if (e.recent_commits_error) console.log(` (could not read commits: ${e.recent_commits_error})`);
290
+ }
291
+
292
+ async function main() {
293
+ const args = parseArgs(process.argv.slice(2));
294
+ const section = args._[0]; // network | trustedzone | securelock | esr | undefined
295
+ const { name: netName, net } = resolveNetwork(args.network);
296
+ const prov = provider(net.rpc);
297
+
298
+ const ipfs = args.ipfs || process.env.IPFS_HASH || null;
299
+ const projName = args.name || process.env.PROJECT_NAME || null;
300
+
301
+ if (section === 'esr') {
302
+ args.esrCmd = args._[1];
303
+ const obj = await esrQuery(prov, netName, net, args);
304
+ emit(obj, args.json);
305
+ return;
306
+ }
307
+ if (section === 'network') { emit(await sectionNetwork(prov, netName, net), args.json); return; }
308
+ if (section === 'trustedzone') { emit(await sectionRegistration(prov, net, 'trustedzone', ipfs, projName), args.json); return; }
309
+ if (section === 'securelock') { emit(await sectionRegistration(prov, net, 'securelock', ipfs, projName), args.json); return; }
310
+
311
+ const info = {
312
+ project: { name: projName || '(unknown)', dapp_type: process.env.SERVICE_TYPE || process.env.DAPP_TYPE, version: process.env.VERSION },
313
+ network: await sectionNetwork(prov, netName, net),
314
+ trustedzone: await sectionRegistration(prov, net, 'trustedzone', ipfs, projName),
315
+ securelock: await sectionRegistration(prov, net, 'securelock', ipfs, projName),
316
+ esr: await sectionEsr(prov, net, args.enclave || null, parseInt(args.events || 10, 10)),
317
+ };
318
+ if (args.json) { console.log(JSON.stringify(info, null, 2)); return; }
319
+ const pr = info.project;
320
+ console.log(`PROJECT ${pr.name}${pr.dapp_type ? ' type ' + pr.dapp_type : ''}${pr.version != null ? ' version ' + pr.version : ''}`);
321
+ printFull(info);
322
+ }
323
+
324
+ main().catch((e) => { console.error(`ecld-info: ${e.message}`); process.exit(1); });