@witnet/sdk 1.2.2 → 1.2.3

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.
Files changed (53) hide show
  1. package/.env_witnet +2 -1
  2. package/dist/package.json +4 -3
  3. package/dist/src/bin/helpers.js +1 -1
  4. package/dist/src/index.js +1 -1
  5. package/dist/src/lib/crypto/account.js +1 -1
  6. package/dist/src/lib/crypto/coinbase.js +1 -1
  7. package/dist/src/lib/crypto/index.js +1 -1
  8. package/dist/src/lib/crypto/interfaces.js +1 -1
  9. package/dist/src/lib/crypto/payloads/DataRequestPayload.js +1 -1
  10. package/dist/src/lib/crypto/payloads/StakePayload.js +1 -1
  11. package/dist/src/lib/crypto/payloads/UnstakePayload.js +1 -1
  12. package/dist/src/lib/crypto/payloads/ValueTransferPayload.js +1 -1
  13. package/dist/src/lib/crypto/payloads.js +1 -1
  14. package/dist/src/lib/crypto/signer.js +1 -1
  15. package/dist/src/lib/crypto/transmitters/DataRequests.js +1 -1
  16. package/dist/src/lib/crypto/transmitters/StakeDeposits.js +1 -1
  17. package/dist/src/lib/crypto/transmitters/StakeWithdrawals.js +1 -1
  18. package/dist/src/lib/crypto/transmitters/ValueTransfers.js +1 -1
  19. package/dist/src/lib/crypto/transmitters.js +1 -1
  20. package/dist/src/lib/crypto/types.js +1 -1
  21. package/dist/src/lib/crypto/utils.js +1 -1
  22. package/dist/src/lib/crypto/wallet.js +1 -1
  23. package/dist/src/lib/index.js +1 -1
  24. package/dist/src/lib/radon/ccdr/eth.js +1 -1
  25. package/dist/src/lib/radon/ccdr/index.js +1 -1
  26. package/dist/src/lib/radon/ccdr/wit.js +1 -1
  27. package/dist/src/lib/radon/filters.js +1 -1
  28. package/dist/src/lib/radon/index.js +1 -1
  29. package/dist/src/lib/radon/reducers.js +1 -1
  30. package/dist/src/lib/radon/types.js +1 -1
  31. package/dist/src/lib/radon/utils.d.ts.map +1 -1
  32. package/dist/src/lib/radon/utils.js +1 -1
  33. package/dist/src/lib/rest/kermit.d.ts.map +1 -1
  34. package/dist/src/lib/rest/kermit.js +2 -4
  35. package/dist/src/lib/rest/types.js +1 -1
  36. package/dist/src/lib/rpc/index.js +1 -1
  37. package/dist/src/lib/rpc/nodes.js +1 -1
  38. package/dist/src/lib/rpc/provider.js +1 -1
  39. package/dist/src/lib/rpc/types.js +1 -1
  40. package/dist/src/lib/types.js +1 -1
  41. package/dist/src/lib/utils.js +1 -1
  42. package/package.json +4 -3
  43. package/scripts/clean.cjs +21 -21
  44. package/scripts/postinstall.cjs +9 -9
  45. package/src/bin/bots/watcher.cjs +365 -354
  46. package/src/bin/cli/history.cjs +31 -31
  47. package/src/bin/cli/inspect.js +581 -581
  48. package/src/bin/cli/network.js +695 -695
  49. package/src/bin/cli/nodes.js +424 -424
  50. package/src/bin/cli/radon.js +1124 -1122
  51. package/src/bin/cli/wallet.js +1362 -1362
  52. package/src/bin/helpers.js +974 -974
  53. package/src/bin/index.js +328 -328
@@ -1,354 +1,365 @@
1
- const cron = require("node-cron");
2
- require("dotenv").config({ quiet: true, path: _spliceFromArgs(process.argv, `--config-path`) });
3
- const { Command } = require("commander");
4
- const moment = require("moment")
5
- const program = new Command();
6
-
7
- const { utils, Witnet } = require("../../../dist/src/index.js");
8
- const { version } = require("../../../package.json");
9
-
10
- const CHECK_BALANCE_SCHEDULE =
11
- process.env.WITNET_SDK_WATCHER_BALANCE_SCHEDULE || "*/15 * * * *"; // every 15 minutes
12
- const DRY_RUN_SCHEDULE = process.env.WITNET_SDK_WATCHER_DRY_RUN_SCHEDULE || "* * * * *" // every minute
13
- const WIT_WALLET_MASTER_KEY = process.env.WITNET_SDK_WALLET_MASTER_KEY;
14
-
15
- const lastUpdate = {}
16
-
17
- main();
18
-
19
- async function main() {
20
- const headline = `WITNET WATCHER BOT v${version}`;
21
- console.info("=".repeat(120));
22
- console.info(headline);
23
-
24
- program
25
- .name("npx --package @witnet/sdk watcher")
26
- .description("Watcher bot for polling and notarizing data feed updates in Witnet.")
27
- .version(version);
28
-
29
- program
30
- .option(
31
- "--cooldown <secs>",
32
- "Min. amount of seconds that must elapse before notarizing the next data update in Witnet.",
33
- process.env.WITNET_SDK_WATCHER_WIT_COOLDOWN_SECS || 900
34
- )
35
- .option(
36
- "--deviation <percentage>",
37
- "Deviation percentage threshold that will force notarizing a data update in Witnet (numeric data feeds only).",
38
- process.env.WITNET_SDK_WATCHER_WIT_DEVIATION_PERCENTAGE
39
- )
40
- .option(
41
- "--heartbeat <secs>",
42
- "If set, max. amount of seconds between data feed updates in Witnet.",
43
- process.env.WITNET_SDK_WATCHER_WIT_HEARTBEAT_SECS
44
- )
45
- .option(
46
- "--min-balance <wits>",
47
- "Min. balance threshold",
48
- process.env.WITNET_SDK_WATCHER_WIT_MIN_BALANCE || 1000.0,
49
- )
50
- .option(
51
- "--network <mainnet|testnet|url>",
52
- "The name of the Witnet network, or the WIT/RPC provider URL, to connect to.",
53
- process.env.WITNET_SDK_WATCHER_WIT_NETWORK
54
- || process.env.WITNET_SDK_PROVIDER_URL
55
- || "mainnet"
56
- )
57
- .option(
58
- "--priority <priority>",
59
- "Network priority when notarizing data updates in Witnet.",
60
- process.env.WITNET_SDK_WATCHER_WIT_NETWORK_PRIORITY ||
61
- Witnet.TransactionPriority.Medium,
62
- )
63
- .option(
64
- "--notarize-errors",
65
- "If set, eventual data retrieving errors will also get notarized."
66
- )
67
- .option(
68
- "--signer <wit_pkh>",
69
- "Witnet public key hash in charge of notarizing data updates.",
70
- process.env.WITNET_SDK_WATCHER_WIT_SIGNER,
71
- )
72
- .option(
73
- "--target <hex>",
74
- "Either the RAD hash or the actual bytecode of the Radon Request used for detecting data updates.",
75
- process.env.WITNET_SDK_WATCHER_WIT_RADON_REQUEST
76
- )
77
- .option(
78
- "--witnesses <number>",
79
- "Size of the witnessing committee when notarizing data updates in Witnet.",
80
- process.env.WITNET_SDK_WATCHER_WIT_WITNESSES || undefined,
81
- )
82
-
83
- program.parse();
84
-
85
- let { deviation, minBalance } = program.opts();
86
- const {
87
- debug,
88
- target,
89
- cooldown,
90
- heartbeat,
91
- network,
92
- notarizeErrors,
93
- priority,
94
- signer,
95
- witnesses
96
- } = program.opts();
97
-
98
- if (!debug) console.debug = () => {};
99
-
100
- if (!WIT_WALLET_MASTER_KEY) {
101
- console.error(
102
- `❌ Fatal: a Witnet wallet's master key is not settled on this environment.`,
103
- );
104
- process.exit(0);
105
- }
106
-
107
- const provider = network === "mainnet" ? "https://rpc-01.witnet.io" : (network === "testnet" ? "https://rpc-testnet.witnet.io" : network)
108
- const wallet = await Witnet.Wallet.fromXprv(WIT_WALLET_MASTER_KEY, {
109
- limit: 1,
110
- provider: await Witnet.JsonRpcProvider.fromURL(provider),
111
- });
112
- const ledger = wallet.getSigner(signer || wallet.coinbase.pkh);
113
- if (!ledger) {
114
- console.error(
115
- `❌ Fatal: hot wallet address ${signer} not found in wallet!`,
116
- );
117
- process.exit(0);
118
- }
119
-
120
- console.info(`Wit/RPC provider: ${provider}`);
121
- console.info(
122
- `Witnet network: WITNET:${wallet.provider.network.toUpperCase()} (${wallet.provider.networkId.toString(16)})`,
123
- );
124
- console.info(`Witnet hot wallet: ${ledger.pkh}`);
125
- console.info(`Network priority: ${priority.toUpperCase()}`);
126
- console.info(
127
- `Balance threshold: ${Witnet.Coins.fromWits(minBalance).toString(2)}`,
128
- );
129
-
130
- if (!target || !utils.isHexString(target)) {
131
- console.error(
132
- `❌ Fatal: a valid hex string must be provided as --target.`,
133
- );
134
- process.exit(0);
135
- }
136
-
137
- let request
138
- try {
139
- request = Witnet.Radon.RadonRequest.fromBytecode(target)
140
- } catch {
141
- const txs = await wallet.provider.searchDataRequests(target, { limit: 1, mode: "ethereal" })
142
- if (txs.length > 0 && txs[0]?.query) {
143
- const bytecode = txs[0].query.rad_bytecode
144
- request = Witnet.Radon.RadonRequest.fromBytecode(bytecode)
145
-
146
- } else {
147
- console.error(
148
- `❌ Fatal: provided --target is not a valid Radon Request bytecode nor a known Radon Request hash.`
149
- )
150
- process.exit(0)
151
- }
152
- }
153
-
154
- const dataType = request.dataType
155
- const authorities = request.sources.map(source => source.authority.split(".").slice(-2)[0].toUpperCase())
156
- console.info(`Radon bytecode: ${request.toBytecode()}`)
157
- console.info(`Radon RAD hash: ${request.radHash}`)
158
- console.info(`Radon data type: ${dataType}`)
159
- console.info(`Data authorities: ${authorities}`)
160
- if (heartbeat) console.info(`Update heartbeat: ${_commas(heartbeat)} "`);
161
- if (cooldown) console.info(`Update cool-down: ${_commas(cooldown)} "`);
162
-
163
- // validate deviation parameter, only on integer or float data feeds
164
- if (["RadonInteger", "RadonFloat"].includes(request.dataType)) {
165
- deviation = parseFloat(deviation || 0.0)
166
- console.info(`Update deviation: ${deviation.toFixed(2)} %`)
167
- }
168
-
169
- // create DR transaction factory
170
- const DRs = Witnet.DataRequests.from(ledger, request);
171
-
172
- // schedule signer's balance check
173
- let balance = Witnet.Coins.fromPedros((await ledger.getBalance()).unlocked);
174
- minBalance = Witnet.Coins.fromWits(minBalance)
175
- console.info(
176
- `Initial balance: ${balance.toString(2)}`,
177
- );
178
- if (balance.pedros < minBalance.pedros) {
179
- console.error(
180
- `❌ Fatal: hot wallet must be funded with at least ${minBalance.toString(2)}.`,
181
- );
182
- process.exit(0);
183
- } else {
184
- if (!cron.validate(CHECK_BALANCE_SCHEDULE)) {
185
- console.error(
186
- `❌ Fatal: invalid check balance schedule: ${CHECK_BALANCE_SCHEDULE}`,
187
- );
188
- process.exit(0);
189
- }
190
- console.info(`Checking balance schedule: ${CHECK_BALANCE_SCHEDULE}`);
191
- cron.schedule(CHECK_BALANCE_SCHEDULE, async () => checkWitnetBalance());
192
- }
193
-
194
- // schedule data feeding dry runs
195
- if (!cron.validate(DRY_RUN_SCHEDULE)) {
196
- console.error(
197
- `❌ Fatal: invalid dry-run schedule: ${DRY_RUN_SCHEDULE}`,
198
- )
199
- process.exit(0)
200
- }
201
- console.info(`Dry running schedule: ${DRY_RUN_SCHEDULE}`)
202
- cron.schedule(DRY_RUN_SCHEDULE, async () => dryRunRadonRequest(), { noOverlap: true })
203
-
204
- // ----------------------------------------------------------------------------------------------------------------
205
- async function dryRunRadonRequest() {
206
- const tag = `[@witnet/sdk/watcher][witnet:${wallet.provider.network}]`
207
- try {
208
- let notarize = false
209
- let dryrun = JSON.parse(await request.execDryRun())
210
- let result
211
- if (Object.keys(dryrun).includes("RadonError")) {
212
- if (notarizeErrors) {
213
- notarize = true
214
- result = dryrun.RadonError
215
- } else {
216
- throw `Unexpected dry run error: ${JSON.stringify(result)}`
217
- }
218
-
219
- } else if (["RadonInteger", "RadonFloat"].includes(dataType) && Object.keys(dryrun).includes(`${dataType}`)) {
220
- result = parseFloat(dryrun[`${dataType}`])
221
- if (deviation && lastUpdate?.value) {
222
- notarize = (Math.abs(result - lastUpdate.value) / lastUpdate.value) >= deviation
223
- } else {
224
- notarize = true
225
- }
226
-
227
- } else if (!lastUpdate?.value || lastUpdate.value !== result) {
228
- notarize = true
229
- }
230
- console.info(`${tag} Dry run result => ${JSON.stringify(dryrun)}`)
231
- const clock = Math.floor(Date.now() / 1000)
232
- const elapsed = clock - (lastUpdate?.timestamp || (clock - cooldown - 1))
233
- if (!notarize && heartbeat && elapsed >= heartbeat) {
234
- console.info(`${tag} Notarizing data due to heartbeat after ${elapsed} secs ...`)
235
- notarize = true
236
- } else if (notarize) {
237
- if (!cooldown || elapsed >= cooldown) {
238
- console.info(`${tag} Notarizing possible data update as provided by ${authorities} ...`)
239
- } else {
240
- throw `Postponing possible data update as only ${elapsed} out of ${cooldown} secs elapsed since the last notarized update.`
241
- }
242
- }
243
- if (notarize) {
244
- lastUpdate.timestamp = clock
245
- lastUpdate.value = result
246
- return notarizeRadonRequest(tag)
247
- }
248
- } catch (err) {
249
- console.warn(`${tag} ${err}`)
250
- }
251
- }
252
-
253
- // ----------------------------------------------------------------------------------------------------------------
254
- async function notarizeRadonRequest(tag) {
255
- try {
256
- // create, sign and send new data request transaction
257
- let tx = await DRs.sendTransaction({ witnesses, fees: priority })
258
- console.info(`${tag} RAD hash =>`, tx.radHash);
259
- console.info(`${tag} DRT hash =>`, tx.hash);
260
- console.info(`${tag} DRT weight =>`, _commas(tx.weight));
261
- console.info(`${tag} DRT wtnsss =>`, tx.witnesses);
262
- console.debug(
263
- `${tag} DRT inputs =>`,
264
- tx.tx?.DataRequest?.signatures.length,
265
- );
266
- console.info(
267
- `${tag} DRT cost =>`,
268
- Witnet.Coins.fromNanowits(
269
- tx.fees.nanowits + tx.value?.nanowits,
270
- ).toString(2),
271
- );
272
-
273
- // await inclusion in Witnet
274
- tx = await DRs.confirmTransaction(tx.hash, {
275
- onStatusChange: () => console.info(`${tag} DRT status =>`, tx.status),
276
- }).catch((err) => {
277
- throw err;
278
- });
279
-
280
- console.debug(
281
- `${tag} Cache info after confirmation =>`,
282
- ledger.cacheInfo,
283
- );
284
-
285
- // await resolution in Witnet
286
- let status = tx.status;
287
- do {
288
- const report = await ledger.provider.getDataRequest(
289
- tx.hash,
290
- "ethereal",
291
- );
292
- if (report.status !== status) {
293
- status = report.status;
294
- console.info(`${tag} DRT status =>`, report.status);
295
- }
296
- if (report.status === "solved" && report?.result) {
297
- lastUpdate.cborBytes = report.result.cbor_bytes
298
- const result = utils.cbor.decode(
299
- utils.fromHexString(report.result.cbor_bytes),
300
- );
301
- console.info(`${tag} DRT result =>`, result);
302
- console.info(`${tag} DRT tmstmp =>`, moment.unix(report.result.timestamp));
303
- lastUpdate.value = result
304
- break;
305
- }
306
- const delay = (ms) =>
307
- new Promise((_resolve) => setTimeout(_resolve, ms));
308
- await delay(5000);
309
- } while (status !== "solved");
310
- } catch (err) {
311
- console.warn(`${tag} ${err}`);
312
- }
313
- }
314
-
315
- // ----------------------------------------------------------------------------------------------------------------
316
- async function checkWitnetBalance() {
317
- const tag = `[@witnet/sdk/watcher][witnet:${wallet.provider.network}:${ledger.pkh}]`
318
- try {
319
- balance = Witnet.Coins.fromPedros(
320
- (await ledger.getBalance()).unlocked,
321
- );
322
- } catch (err) {
323
- console.error(
324
- `${tag} Cannot check balance: ${err}`,
325
- );
326
- }
327
- console.info(
328
- `${tag} Balance: ${balance.toString(2)}`,
329
- );
330
- if (balance.pedros < minBalance.pedros)
331
- console.warn(
332
- `${tag} Low funds !!!`,
333
- );
334
- return balance;
335
- }
336
- }
337
-
338
- const _commas = (number) => {
339
- const parts = number.toString().split(".");
340
- const result =
341
- parts.length <= 1
342
- ? `${parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`
343
- : `${parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",")}.${parts[1]}`;
344
- return result;
345
- };
346
-
347
- function _spliceFromArgs(args, flag) {
348
- const argIndex = args.indexOf(flag)
349
- if (argIndex >= 0 && args.length > argIndex + 1) {
350
- const value = args[argIndex + 1]
351
- args.splice(argIndex, 2)
352
- return value
353
- }
354
- }
1
+ const cron = require("node-cron");
2
+ require("dotenv").config({
3
+ quiet: true,
4
+ path: _spliceFromArgs(process.argv, `--config-path`),
5
+ });
6
+ const { Command } = require("commander");
7
+ const moment = require("moment");
8
+ const program = new Command();
9
+
10
+ const { utils, Witnet } = require("../../../dist/src/index.js");
11
+ const { colors, traceHeader } = require("../helpers.js");
12
+ const { version } = require("../../../package.json");
13
+
14
+ const CHECK_BALANCE_SCHEDULE =
15
+ process.env.WITNET_SDK_WATCHER_BALANCE_SCHEDULE || "*/15 * * * *"; // every 15 minutes
16
+ const DRY_RUN_SCHEDULE =
17
+ process.env.WITNET_SDK_WATCHER_DRY_RUN_SCHEDULE || "* * * * *"; // every minute
18
+ const WIT_WALLET_MASTER_KEY = process.env.WITNET_SDK_WALLET_MASTER_KEY;
19
+
20
+ const lastUpdate = {};
21
+
22
+ traceHeader(`@WITNET/SDK WATCHER BOT v${version}`, colors.white);
23
+
24
+ main();
25
+
26
+ async function main() {
27
+ program
28
+ .name("npx --package @witnet/sdk watcher")
29
+ .description(
30
+ "Watcher bot for detecting and notarizing real-world data updates in Witnet.",
31
+ )
32
+ .version(version);
33
+
34
+ program
35
+ .option(
36
+ "--cooldown <secs>",
37
+ "Min. amount of seconds that must elapse before notarizing the next data update in Witnet.",
38
+ process.env.WITNET_SDK_WATCHER_WIT_COOLDOWN_SECS || 900,
39
+ )
40
+ .option(
41
+ "--deviation <percentage>",
42
+ "Deviation percentage threshold that will force notarizing a data update in Witnet (numeric data feeds only).",
43
+ process.env.WITNET_SDK_WATCHER_WIT_DEVIATION_PERCENTAGE,
44
+ )
45
+ .option(
46
+ "--heartbeat <secs>",
47
+ "If set, max. amount of seconds between data feed updates in Witnet.",
48
+ process.env.WITNET_SDK_WATCHER_WIT_HEARTBEAT_SECS,
49
+ )
50
+ .option(
51
+ "--min-balance <wits>",
52
+ "Min. balance threshold",
53
+ process.env.WITNET_SDK_WATCHER_WIT_MIN_BALANCE || 1000.0,
54
+ )
55
+ .option(
56
+ "--priority <priority>",
57
+ "Network priority when notarizing data updates in Witnet.",
58
+ process.env.WITNET_SDK_WATCHER_WIT_NETWORK_PRIORITY ||
59
+ Witnet.TransactionPriority.Medium,
60
+ )
61
+ .option(
62
+ "--notarize-errors",
63
+ "If set, eventual data retrieving errors will also get notarized.",
64
+ )
65
+ .option(
66
+ "--signer <wit_pkh>",
67
+ "Witnet public key hash in charge of notarizing data updates.",
68
+ process.env.WITNET_SDK_WATCHER_WIT_SIGNER,
69
+ )
70
+ .option(
71
+ "--target <hex>",
72
+ "Either an existing Radon hash in Witnet, or the Radon Request bytecode to use for detecting data updates.",
73
+ process.env.WITNET_SDK_WATCHER_WIT_RADON_REQUEST,
74
+ )
75
+ .option(
76
+ "--witnesses <number>",
77
+ "Size of the witnessing committee when notarizing data updates in Witnet.",
78
+ process.env.WITNET_SDK_WATCHER_WIT_WITNESSES || undefined,
79
+ )
80
+ .option(
81
+ '--witnet <"mainnet" | "testnet" | url>',
82
+ "The name of the Witnet network, or the URL of the WIT/RPC provider to connect to.",
83
+ process.env.WITNET_SDK_WATCHER_WIT_NETWORK ||
84
+ process.env.WITNET_SDK_PROVIDER_URL ||
85
+ "mainnet",
86
+ );
87
+
88
+ program.parse();
89
+
90
+ let { deviation, minBalance } = program.opts();
91
+ const {
92
+ debug,
93
+ target,
94
+ cooldown,
95
+ heartbeat,
96
+ notarizeErrors,
97
+ priority,
98
+ signer,
99
+ witnesses,
100
+ witnet,
101
+ } = program.opts();
102
+
103
+ if (!debug) console.debug = () => {};
104
+
105
+ if (!WIT_WALLET_MASTER_KEY) {
106
+ console.error(
107
+ `❌ Fatal: a Witnet wallet's master key is not settled on this environment.`,
108
+ );
109
+ process.exit(0);
110
+ }
111
+
112
+ const provider =
113
+ witnet === "mainnet"
114
+ ? "https://rpc-01.witnet.io"
115
+ : witnet === "testnet"
116
+ ? "https://rpc-testnet.witnet.io"
117
+ : witnet;
118
+ const wallet = await Witnet.Wallet.fromXprv(WIT_WALLET_MASTER_KEY, {
119
+ limit: 1,
120
+ provider: await Witnet.JsonRpcProvider.fromURL(provider),
121
+ });
122
+ const ledger = wallet.getSigner(signer || wallet.coinbase.pkh);
123
+ if (!ledger) {
124
+ console.error(
125
+ `❌ Fatal: hot wallet address ${signer} not found in wallet!`,
126
+ );
127
+ process.exit(0);
128
+ }
129
+
130
+ console.info(`Wit/RPC provider: ${provider}`);
131
+ console.info(
132
+ `Witnet network: WITNET:${wallet.provider.network.toUpperCase()} (${wallet.provider.networkId.toString(16)})`,
133
+ );
134
+ console.info(`Witnet hot wallet: ${ledger.pkh}`);
135
+ console.info(`Network priority: ${priority.toUpperCase()}`);
136
+ console.info(
137
+ `Balance threshold: ${Witnet.Coins.fromWits(minBalance).toString(2)}`,
138
+ );
139
+
140
+ if (!target || !utils.isHexString(target)) {
141
+ console.error(`❌ Fatal: a valid hex string must be provided as --target.`);
142
+ process.exit(0);
143
+ }
144
+
145
+ let request;
146
+ try {
147
+ request = Witnet.Radon.RadonRequest.fromBytecode(target);
148
+ } catch {
149
+ const txs = await wallet.provider.searchDataRequests(target, {
150
+ limit: 1,
151
+ mode: "ethereal",
152
+ });
153
+ if (txs.length > 0 && txs[0]?.query) {
154
+ const bytecode = txs[0].query.rad_bytecode;
155
+ request = Witnet.Radon.RadonRequest.fromBytecode(bytecode);
156
+ } else {
157
+ console.error(
158
+ `❌ Fatal: provided --target is not a valid Radon Request bytecode nor a known Radon Request hash.`,
159
+ );
160
+ process.exit(0);
161
+ }
162
+ }
163
+
164
+ const dataType = request.dataType;
165
+ const authorities = request.sources.map((source) =>
166
+ source.authority.split(".").slice(-2)[0].toUpperCase(),
167
+ );
168
+ console.info(`Radon bytecode: ${request.toBytecode()}`);
169
+ console.info(`Radon RAD hash: ${request.radHash}`);
170
+ console.info(`Radon data type: ${dataType}`);
171
+ console.info(`Data authorities: ${authorities}`);
172
+ if (heartbeat) console.info(`Update heartbeat: ${_commas(heartbeat)} "`);
173
+ if (cooldown) console.info(`Update cool-down: ${_commas(cooldown)} "`);
174
+
175
+ // validate deviation parameter, only on integer or float data feeds
176
+ if (["RadonInteger", "RadonFloat"].includes(request.dataType)) {
177
+ deviation = parseFloat(deviation || 0.0);
178
+ console.info(`Update deviation: ${deviation.toFixed(2)} %`);
179
+ }
180
+
181
+ // create DR transaction factory
182
+ const DRs = Witnet.DataRequests.from(ledger, request);
183
+
184
+ // schedule signer's balance check
185
+ let balance = Witnet.Coins.fromPedros((await ledger.getBalance()).unlocked);
186
+ minBalance = Witnet.Coins.fromWits(minBalance);
187
+ console.info(`Initial balance: ${balance.toString(2)}`);
188
+ if (balance.pedros < minBalance.pedros) {
189
+ console.error(
190
+ `❌ Fatal: hot wallet must be funded with at least ${minBalance.toString(2)}.`,
191
+ );
192
+ process.exit(0);
193
+ } else {
194
+ if (!cron.validate(CHECK_BALANCE_SCHEDULE)) {
195
+ console.error(
196
+ `❌ Fatal: invalid check balance schedule: ${CHECK_BALANCE_SCHEDULE}`,
197
+ );
198
+ process.exit(0);
199
+ }
200
+ console.info(`Checking balance schedule: ${CHECK_BALANCE_SCHEDULE}`);
201
+ cron.schedule(CHECK_BALANCE_SCHEDULE, async () => checkWitnetBalance());
202
+ }
203
+
204
+ // schedule data feeding dry runs
205
+ if (!cron.validate(DRY_RUN_SCHEDULE)) {
206
+ console.error(`❌ Fatal: invalid dry-run schedule: ${DRY_RUN_SCHEDULE}`);
207
+ process.exit(0);
208
+ }
209
+ console.info(`Dry running schedule: ${DRY_RUN_SCHEDULE}`);
210
+ cron.schedule(DRY_RUN_SCHEDULE, async () => dryRunRadonRequest(), {
211
+ noOverlap: true,
212
+ });
213
+
214
+ // ----------------------------------------------------------------------------------------------------------------
215
+ async function dryRunRadonRequest() {
216
+ const tag = `[@witnet/sdk/watcher][witnet:${wallet.provider.network}]`;
217
+ try {
218
+ let notarize = false;
219
+ const dryrun = JSON.parse(await request.execDryRun());
220
+ let result;
221
+ if (Object.keys(dryrun).includes("RadonError")) {
222
+ if (notarizeErrors) {
223
+ notarize = true;
224
+ result = dryrun.RadonError;
225
+ } else {
226
+ throw `Unexpected dry run error: ${JSON.stringify(result)}`;
227
+ }
228
+ } else if (
229
+ ["RadonInteger", "RadonFloat"].includes(dataType) &&
230
+ Object.keys(dryrun).includes(`${dataType}`)
231
+ ) {
232
+ result = parseFloat(dryrun[`${dataType}`]);
233
+ if (deviation && lastUpdate?.value) {
234
+ notarize =
235
+ Math.abs(result - lastUpdate.value) / lastUpdate.value >= deviation;
236
+ } else {
237
+ notarize = true;
238
+ }
239
+ } else if (!lastUpdate?.value || lastUpdate.value !== result) {
240
+ notarize = true;
241
+ }
242
+ console.info(`${tag} Dry run result => ${JSON.stringify(dryrun)}`);
243
+ const clock = Math.floor(Date.now() / 1000);
244
+ const elapsed = clock - (lastUpdate?.timestamp || clock - cooldown - 1);
245
+ if (!notarize && heartbeat && elapsed >= heartbeat) {
246
+ console.info(
247
+ `${tag} Notarizing data due to heartbeat after ${elapsed} secs ...`,
248
+ );
249
+ notarize = true;
250
+ } else if (notarize) {
251
+ if (!cooldown || elapsed >= cooldown) {
252
+ console.info(
253
+ `${tag} Notarizing possible data update as provided by ${authorities} ...`,
254
+ );
255
+ } else {
256
+ throw `Postponing possible data update as only ${elapsed} out of ${cooldown} secs elapsed since the last notarized update.`;
257
+ }
258
+ }
259
+ if (notarize) {
260
+ lastUpdate.timestamp = clock;
261
+ lastUpdate.value = result;
262
+ return notarizeRadonRequest(tag);
263
+ }
264
+ } catch (err) {
265
+ console.warn(`${tag} ${err}`);
266
+ }
267
+ }
268
+
269
+ // ----------------------------------------------------------------------------------------------------------------
270
+ async function notarizeRadonRequest(tag) {
271
+ try {
272
+ // create, sign and send new data request transaction
273
+ let tx = await DRs.sendTransaction({ witnesses, fees: priority });
274
+ console.info(`${tag} RAD hash =>`, tx.radHash);
275
+ console.info(`${tag} DRT hash =>`, tx.hash);
276
+ console.info(`${tag} DRT weight =>`, _commas(tx.weight));
277
+ console.info(`${tag} DRT wtnsss =>`, tx.witnesses);
278
+ console.debug(
279
+ `${tag} DRT inputs =>`,
280
+ tx.tx?.DataRequest?.signatures.length,
281
+ );
282
+ console.info(
283
+ `${tag} DRT cost =>`,
284
+ Witnet.Coins.fromNanowits(
285
+ tx.fees.nanowits + tx.value?.nanowits,
286
+ ).toString(2),
287
+ );
288
+
289
+ // await inclusion in Witnet
290
+ tx = await DRs.confirmTransaction(tx.hash, {
291
+ onStatusChange: () => console.info(`${tag} DRT status =>`, tx.status),
292
+ }).catch((err) => {
293
+ throw err;
294
+ });
295
+
296
+ console.debug(
297
+ `${tag} Cache info after confirmation =>`,
298
+ ledger.cacheInfo,
299
+ );
300
+
301
+ // await resolution in Witnet
302
+ let status = tx.status;
303
+ do {
304
+ const report = await ledger.provider.getDataRequest(
305
+ tx.hash,
306
+ "ethereal",
307
+ );
308
+ if (report.status !== status) {
309
+ status = report.status;
310
+ console.info(`${tag} DRT status =>`, report.status);
311
+ }
312
+ if (report.status === "solved" && report?.result) {
313
+ lastUpdate.cborBytes = report.result.cbor_bytes;
314
+ const result = utils.cbor.decode(
315
+ utils.fromHexString(report.result.cbor_bytes),
316
+ );
317
+ console.info(`${tag} DRT result =>`, result);
318
+ console.info(
319
+ `${tag} DRT tmstmp =>`,
320
+ moment.unix(report.result.timestamp),
321
+ );
322
+ lastUpdate.value = result;
323
+ break;
324
+ }
325
+ const delay = (ms) =>
326
+ new Promise((_resolve) => setTimeout(_resolve, ms));
327
+ await delay(5000);
328
+ } while (status !== "solved");
329
+ } catch (err) {
330
+ console.warn(`${tag} ${err}`);
331
+ }
332
+ }
333
+
334
+ // ----------------------------------------------------------------------------------------------------------------
335
+ async function checkWitnetBalance() {
336
+ const tag = `[@witnet/sdk/watcher][witnet:${wallet.provider.network}:${ledger.pkh}]`;
337
+ try {
338
+ balance = Witnet.Coins.fromPedros((await ledger.getBalance()).unlocked);
339
+ } catch (err) {
340
+ console.error(`${tag} Cannot check balance: ${err}`);
341
+ }
342
+ console.info(`${tag} Balance: ${balance.toString(2)}`);
343
+ if (balance.pedros < minBalance.pedros)
344
+ console.warn(`${tag} Low funds !!!`);
345
+ return balance;
346
+ }
347
+ }
348
+
349
+ const _commas = (number) => {
350
+ const parts = number.toString().split(".");
351
+ const result =
352
+ parts.length <= 1
353
+ ? `${parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`
354
+ : `${parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",")}.${parts[1]}`;
355
+ return result;
356
+ };
357
+
358
+ function _spliceFromArgs(args, flag) {
359
+ const argIndex = args.indexOf(flag);
360
+ if (argIndex >= 0 && args.length > argIndex + 1) {
361
+ const value = args[argIndex + 1];
362
+ args.splice(argIndex, 2);
363
+ return value;
364
+ }
365
+ }