@ethernity-cloud/sdk-js 1.5.9 → 1.5.12

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/run.js ADDED
@@ -0,0 +1,245 @@
1
+ #!/usr/bin/env node
2
+ /* ecld-run — submit a payload to the Ethernity Cloud network and print the result.
3
+ *
4
+ * The network-side sibling of `ecld-test`: same payload flags (a code string,
5
+ * --file, --input/--input-text), but instead of executing locally it drives the
6
+ * real runner (@ethernity-cloud/runner's EthernityCloudRunner) end to end —
7
+ * encrypt the payload, push to IPFS, place the on-chain DO request, wait for a
8
+ * node to process it, download and decrypt the result.
9
+ *
10
+ * Where ecld-test answers "does my CALL work?", ecld-run answers "does it work
11
+ * on the network, on the enclave I published?" — it costs gas and needs a
12
+ * funded key.
13
+ *
14
+ * Network and enclave come from the project's .env (written by ecld-init /
15
+ * ecld-publish), matching how ethernity_task drives a run:
16
+ *
17
+ * BLOCKCHAIN_NETWORK e.g. "Bloxberg_Testnet" -> the network token address
18
+ * PROJECT_NAME the securelock enclave to run
19
+ * TRUSTED_ZONE_IMAGE the trustedzone enclave
20
+ * PRIVATE_KEY a funded 0x key — it pays for the order
21
+ *
22
+ * Usage, from the project root:
23
+ *
24
+ * npx ecld-run 'hello("World")'
25
+ * npx ecld-run --file payload.js
26
+ * npx ecld-run --input data.json 'processData(___etny_data_set___)'
27
+ * npx ecld-run --network BLOXBERG_TESTNET --task-price 3 'esrIncrement()'
28
+ * npx ecld-run --json 'hello("World")' # machine-readable result
29
+ *
30
+ * The runner is ESM; this CJS entry loads it with dynamic import().
31
+ *
32
+ * Exit code 0 on a SUCCESS task result, 1 otherwise.
33
+ */
34
+
35
+ 'use strict';
36
+
37
+ const fs = require('fs');
38
+ try { require('dotenv').config(); } catch (e) { /* dotenv optional */ }
39
+
40
+ const DEFAULT_IPFS = 'https://ipfs.ethernity.cloud/api/v0';
41
+
42
+ /* Map a BLOCKCHAIN_NETWORK token (as ecld-init writes it, spaces -> underscores)
43
+ * to the runner's network token ADDRESS. Built from the runner's own ECAddress
44
+ * so it can't drift. Accepts a few spellings per network. */
45
+ function networkAddress(ECAddress, raw) {
46
+ const key = String(raw || '').trim().toUpperCase().replace(/\s+/g, '_');
47
+ const table = {
48
+ BLOXBERG_TESTNET: ECAddress.BLOXBERG.TESTNET_ADDRESS,
49
+ BLOXBERG_MAINNET: ECAddress.BLOXBERG.MAINNET_ADDRESS,
50
+ POLYGON_MAINNET: ECAddress.POLYGON.MAINNET_ADDRESS,
51
+ POLYGON_AMOY: ECAddress.POLYGON.TESTNET_ADDRESS,
52
+ POLYGON_AMOY_TESTNET: ECAddress.POLYGON.TESTNET_ADDRESS,
53
+ IOTEX_TESTNET: ECAddress.IOTEX && ECAddress.IOTEX.TESTNET_ADDRESS,
54
+ ETHEREUM_SEPOLIA: ECAddress.SEPOLIA && ECAddress.SEPOLIA.TESTNET_ADDRESS,
55
+ SEPOLIA: ECAddress.SEPOLIA && ECAddress.SEPOLIA.TESTNET_ADDRESS,
56
+ LITVM_LITEFORGE: ECAddress.LITVM && ECAddress.LITVM.TESTNET_ADDRESS,
57
+ LITVM: ECAddress.LITVM && ECAddress.LITVM.TESTNET_ADDRESS,
58
+ };
59
+ return { key, address: table[key] || null, known: Object.keys(table) };
60
+ }
61
+
62
+ function parseArgs(argv) {
63
+ const opts = { ipfs: DEFAULT_IPFS, timeout: 600, taskPrice: 3, cpu: 1, memory: 1, storage: 10, bandwidth: 1, duration: 1, validators: 1, node: '' };
64
+ const positional = [];
65
+ for (let i = 0; i < argv.length; i++) {
66
+ const a = argv[i];
67
+ if (a === '--file' || a === '-f') opts.file = argv[++i];
68
+ else if (a === '--input' || a === '-i') opts.inputFile = argv[++i];
69
+ else if (a === '--input-text') opts.inputText = argv[++i];
70
+ else if (a === '--network') opts.network = argv[++i];
71
+ else if (a === '--securelock') opts.securelock = argv[++i];
72
+ else if (a === '--trustedzone') opts.trustedzone = argv[++i];
73
+ else if (a === '--node') opts.node = argv[++i];
74
+ else if (a === '--ipfs') opts.ipfs = argv[++i];
75
+ else if (a === '--timeout') opts.timeout = parseInt(argv[++i], 10);
76
+ else if (a === '--task-price') opts.taskPrice = parseInt(argv[++i], 10);
77
+ else if (a === '--cpu') opts.cpu = parseInt(argv[++i], 10);
78
+ else if (a === '--memory') opts.memory = parseInt(argv[++i], 10);
79
+ else if (a === '--storage') opts.storage = parseInt(argv[++i], 10);
80
+ else if (a === '--bandwidth') opts.bandwidth = parseInt(argv[++i], 10);
81
+ else if (a === '--duration') opts.duration = parseInt(argv[++i], 10);
82
+ else if (a === '--validators') opts.validators = parseInt(argv[++i], 10);
83
+ else if (a === '--json') opts.json = true;
84
+ else if (a === '--help' || a === '-h') opts.help = true;
85
+ else positional.push(a);
86
+ }
87
+ return { opts, positional };
88
+ }
89
+
90
+ const HELP = `ecld-run — submit a payload to the Ethernity Cloud network (costs gas; needs a funded key).
91
+ Local sibling: ecld-test.
92
+
93
+ usage: ecld-run 'hello("World")' | ecld-run --file payload.js | ecld-run --input data.json 'fn(___etny_data_set___)'
94
+
95
+ payload: a code string, or --file <path>; input via --input <file> / --input-text <str>
96
+ network: --network BLOXBERG_TESTNET (default: BLOCKCHAIN_NETWORK from .env)
97
+ enclaves: --securelock <name> (default PROJECT_NAME), --trustedzone <name> (default TRUSTED_ZONE_IMAGE)
98
+ key: PRIVATE_KEY (a funded 0x key) from .env / env, or ECLD_PRIVATE_KEY
99
+ resources: --task-price 3 --cpu 1 --memory 1 --storage 10 --bandwidth 1 --duration 1 --validators 1
100
+ other: --node <addr> --ipfs <url> --timeout <sec> --json`;
101
+
102
+ async function main() {
103
+ const { opts, positional } = parseArgs(process.argv.slice(2));
104
+ if (opts.help) { console.log(HELP); process.exit(0); }
105
+
106
+ // ---- payload ----
107
+ let code;
108
+ if (opts.file) code = fs.readFileSync(opts.file, 'utf8');
109
+ else if (positional.length) code = positional.join(' ');
110
+ else if ((process.env.ECLD_TEST_CODE || '').trim()) code = process.env.ECLD_TEST_CODE.trim();
111
+ else { console.error("ecld-run: provide a payload string, --file, or ECLD_TEST_CODE\n\n" + HELP); process.exit(2); }
112
+
113
+ if (opts.inputFile !== undefined && opts.inputText !== undefined) {
114
+ console.error('ecld-run: --input and --input-text are mutually exclusive'); process.exit(2);
115
+ }
116
+ let input = null;
117
+ if (opts.inputFile !== undefined) input = fs.readFileSync(opts.inputFile, 'utf8');
118
+ else if (opts.inputText !== undefined) input = opts.inputText;
119
+ // The enclave executor exposes the input as ___etny_data_set___; the runner
120
+ // passes it as part of the code's scope, so a payload references it by name.
121
+ // ecld-run mirrors ecld-test: input is delivered via the payload call itself.
122
+
123
+ // ---- runner (ESM) ----
124
+ let EthernityCloudRunner, ECStatus, ECEvent, ECAddress, ECOrderTaskStatus;
125
+ try {
126
+ const runnerMod = await import('@ethernity-cloud/runner');
127
+ EthernityCloudRunner = runnerMod.default || runnerMod.EthernityCloudRunner;
128
+ const enums = await import('@ethernity-cloud/runner/enums.js');
129
+ ({ ECStatus, ECEvent, ECAddress, ECOrderTaskStatus } = enums);
130
+ } catch (e) {
131
+ console.error('ecld-run: the runner package is not installed or is too old '
132
+ + '(need @ethernity-cloud/runner >= 0.4.4): ' + e.message);
133
+ process.exit(1);
134
+ }
135
+
136
+ // ---- network / enclaves / key ----
137
+ const rawNetwork = opts.network || process.env.BLOCKCHAIN_NETWORK || 'BLOXBERG_TESTNET';
138
+ const { key: netKey, address, known } = networkAddress(ECAddress, rawNetwork);
139
+ if (!address) {
140
+ console.error(`ecld-run: unknown network '${rawNetwork}'. Known: ${known.join(', ')}`);
141
+ process.exit(1);
142
+ }
143
+ const securelock = opts.securelock || process.env.PROJECT_NAME;
144
+ const trustedzone = opts.trustedzone || process.env.TRUSTED_ZONE_IMAGE; // may be undefined -> run() default
145
+ if (!securelock) {
146
+ console.error('ecld-run: no securelock enclave: set PROJECT_NAME in .env '
147
+ + '(run ecld-init/ecld-publish) or pass --securelock.');
148
+ process.exit(1);
149
+ }
150
+
151
+ let privateKey = (process.env.PRIVATE_KEY || process.env.ECLD_PRIVATE_KEY || '').trim();
152
+ if (!privateKey) {
153
+ console.error('ecld-run: no signing key. Set PRIVATE_KEY (a funded 0x key) in .env '
154
+ + '(ecld-publish stores it) or ECLD_PRIVATE_KEY.');
155
+ process.exit(1);
156
+ }
157
+ if (!privateKey.startsWith('0x')) privateKey = '0x' + privateKey;
158
+
159
+ console.log(`network : ${netKey} (${address})`);
160
+ console.log(`securelock : ${securelock}`);
161
+ console.log(`trustedzone: ${trustedzone || '(runner default)'}`);
162
+ console.log(`payload : ${JSON.stringify(code)}`);
163
+ console.log(`input : ${input === null ? '<empty>' : JSON.stringify(input.slice(0, 80))}`);
164
+
165
+ // ---- drive the runner ----
166
+ let runner;
167
+ try {
168
+ runner = new EthernityCloudRunner(address, { privateKey });
169
+ } catch (e) {
170
+ console.error('ecld-run: could not initialise the runner: ' + e.message);
171
+ process.exit(1);
172
+ }
173
+ runner.initializeStorage(opts.ipfs);
174
+
175
+ let lastPhase = null;
176
+ const onProgress = (e) => {
177
+ const d = (e && e.detail) || {};
178
+ if (d.progress !== lastPhase) { lastPhase = d.progress; console.log(`[${d.progress}] ${d.message}`); }
179
+ else if (d.message) console.log(` ${d.message}`);
180
+ };
181
+ runner.addEventListener(ECStatus.DEFAULT, onProgress);
182
+
183
+ const resources = {
184
+ taskPrice: opts.taskPrice, cpu: opts.cpu, memory: opts.memory,
185
+ storage: opts.storage, bandwidth: opts.bandwidth, duration: opts.duration, validators: opts.validators,
186
+ };
187
+ console.log(`resources : ${JSON.stringify(resources)}`);
188
+ console.log('submitting order... (this places an on-chain request and costs gas)');
189
+
190
+ const done = new Promise((resolve) => {
191
+ runner.addEventListener(ECStatus.SUCCESS, () => resolve('SUCCESS'));
192
+ runner.addEventListener(ECStatus.ERROR, (e) => {
193
+ const msg = (e && e.detail && e.detail.message) || 'unknown error';
194
+ resolve({ error: msg });
195
+ });
196
+ });
197
+
198
+ // run() resolves when the task terminates; the events resolve `done`. Race a
199
+ // timeout so a stuck order doesn't hang the CLI forever.
200
+ const timeoutMs = (opts.timeout || 600) * 1000;
201
+ let timer;
202
+ const timeout = new Promise((resolve) => { timer = setTimeout(() => resolve('TIMEOUT'), timeoutMs); });
203
+
204
+ try {
205
+ const runArgs = [resources, securelock, code, opts.node];
206
+ if (trustedzone) runArgs.push(trustedzone);
207
+ // Fire the run; its own promise also settles on completion.
208
+ const runPromise = runner.run(...runArgs).catch((e) => ({ error: e.message }));
209
+ const outcome = await Promise.race([done, timeout, runPromise]);
210
+ clearTimeout(timer);
211
+
212
+ if (outcome === 'TIMEOUT') {
213
+ console.error(`\necld-run: no result after ${opts.timeout}s; the order may still process on-chain.`);
214
+ process.exit(1);
215
+ }
216
+ if (outcome && outcome.error) {
217
+ console.error(`\nstatus : ERROR`);
218
+ console.error(`error : ${outcome.error}`);
219
+ process.exit(1);
220
+ }
221
+
222
+ // SUCCESS
223
+ const result = await runner.getResult();
224
+ const typed = (runner.getStructuredResult && runner.getStructuredResult()) || null;
225
+ const taskCode = runner.resultTaskCode;
226
+ const taskName = runner.resultTaskCodeName
227
+ || (ECOrderTaskStatus && taskCode !== undefined ? ECOrderTaskStatus[taskCode] : undefined);
228
+
229
+ if (opts.json) {
230
+ console.log(JSON.stringify({ task_code: taskCode, task_code_name: taskName, value: result, structured: typed }, null, 2));
231
+ } else {
232
+ if (taskName && taskName !== 'SUCCESS') console.log(`task code : ${taskCode} (${taskName})`);
233
+ console.log('result :');
234
+ console.log(result);
235
+ }
236
+ // A non-zero enclave task code is a failed run even though the order landed.
237
+ process.exit(taskCode === undefined || taskCode === 0 ? 0 : 1);
238
+ } catch (e) {
239
+ clearTimeout(timer);
240
+ console.error('ecld-run: run failed: ' + (e && e.stack ? e.stack : e));
241
+ process.exit(1);
242
+ }
243
+ }
244
+
245
+ main().catch((e) => { console.error(e && e.stack ? e.stack : String(e)); process.exit(1); });