@reality.eth/reality-eth-lib 3.4.28 → 3.4.29

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
@@ -1,2 +1,36 @@
1
1
  # reality-eth-lib
2
+
2
3
  Useful libraries for Reality.eth
4
+
5
+ ## CJS build
6
+
7
+ Used by the indexer and other Node.js packages.
8
+
9
+ ```
10
+ npm run build
11
+ ```
12
+
13
+ Compiles TypeScript sources under `src/` to `dist/cjs/` via `tsc`.
14
+
15
+ ## Browser bundle
16
+
17
+ Used by the website (`packages/website/webroot/js/vendor/reality-eth-lib.js`).
18
+ Exports all public functions from `browser-entry.ts` as `window.RealityLib`.
19
+
20
+ ```
21
+ npm run build:browser
22
+ ```
23
+
24
+ Built with esbuild (version pinned in `package.json` for reproducibility).
25
+ The output file is committed to the website package. To verify a clean rebuild
26
+ produces the same file:
27
+
28
+ ```
29
+ npm run build:browser
30
+ sha256sum ../website/webroot/js/vendor/reality-eth-lib.js
31
+ ```
32
+
33
+ Expected hash: `6b713caca2d26b7e9b4814e7137179d66d70af185228f4adfd66a1d3b1a159fa`
34
+
35
+ Update this hash in the README after any intentional change to `browser-entry.ts`
36
+ or its dependencies.
@@ -0,0 +1,34 @@
1
+ // Browser IIFE entry: exposes parsing/formatting helpers as window.RealityLib
2
+ export {
3
+ delimiter,
4
+ populatedJSONForTemplate,
5
+ parseQuestionJSON,
6
+ encodeText,
7
+ encodeCustomText,
8
+ answerHash,
9
+ answerToBytes32,
10
+ bytes32ToString,
11
+ getAnswerString,
12
+ contentHash,
13
+ questionID,
14
+ hasInvalidOption,
15
+ hasAnsweredTooSoonOption,
16
+ getInvalidValue,
17
+ getAnsweredTooSoonValue,
18
+ minNumber,
19
+ maxNumber,
20
+ arrayToBitmaskBigNumber,
21
+ padToBytes32,
22
+ convertTsToString,
23
+ secondsTodHms,
24
+ getLanguage,
25
+ commitmentID,
26
+ shortDisplayQuestionID,
27
+ } from './src/formatters/question.js';
28
+
29
+ export {
30
+ preloadedTemplateContents,
31
+ preloadedTemplateContentsV32,
32
+ defaultTemplateIDForType,
33
+ defaultTemplateForType,
34
+ } from './src/formatters/template.js';
@@ -0,0 +1,24 @@
1
+ import { build } from 'esbuild';
2
+ import { resolve, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const outfile = resolve(__dirname, '../frontend/webroot/js/vendor/reality-eth-lib.js');
7
+
8
+ await build({
9
+ entryPoints: [resolve(__dirname, 'browser-entry.ts')],
10
+ bundle: true,
11
+ format: 'iife',
12
+ globalName: 'RealityLib',
13
+ platform: 'browser',
14
+ target: ['es2020'],
15
+ minify: true,
16
+ define: {
17
+ 'process.env.NODE_ENV': '"production"',
18
+ 'global': 'globalThis',
19
+ },
20
+ outfile,
21
+ logLevel: 'info',
22
+ });
23
+
24
+ console.log(`Built ${outfile}`);
@@ -0,0 +1,58 @@
1
+ export interface QuestionJSON {
2
+ title?: string;
3
+ title_text?: string;
4
+ title_html?: string;
5
+ type?: string;
6
+ category?: string;
7
+ lang?: string;
8
+ outcomes?: string[];
9
+ format?: string;
10
+ precision?: string;
11
+ decimals?: number;
12
+ has_invalid?: boolean;
13
+ errors?: Record<string, boolean>;
14
+ [key: string]: unknown;
15
+ }
16
+ export interface TemplatePart {
17
+ part: 'parameter' | 'array_parameter' | 'text';
18
+ part_index?: number;
19
+ label?: string;
20
+ value?: string;
21
+ }
22
+ export interface TemplateFieldDef {
23
+ label: string;
24
+ parts: TemplatePart[];
25
+ }
26
+ export interface TemplateConfig {
27
+ fields: Record<string, TemplateFieldDef>;
28
+ tags: Record<string, string>;
29
+ }
30
+ export declare function delimiter(): string;
31
+ export declare function contentHash(template_id: number | string, opening_ts: number | string, content: string): string;
32
+ export declare function questionID(template_id: number | string, question: string, arbitrator: string, timeout: number | string, opening_ts: number | string, sender: string, nonce: number | string, min_bond?: string, contract?: string, version?: string): string;
33
+ export declare function minNumber(qjson: QuestionJSON): bigint;
34
+ export declare function maxNumber(qjson: QuestionJSON): bigint;
35
+ export declare function arrayToBitmaskBigNumber(selections: boolean[]): bigint;
36
+ export declare function padToBytes32(n: string, raw?: boolean): string;
37
+ export declare function answerToBytes32(answer: any, qjson: QuestionJSON): string;
38
+ export declare function bytes32ToString(bytes32str: string, qjson: QuestionJSON): string;
39
+ export declare function convertTsToString(ts: number | {
40
+ toNumber(): number;
41
+ }): string;
42
+ export declare function secondsTodHms(sec: number | string): string;
43
+ export declare function parseQuestionJSON(data: string, errors_to_title?: boolean, vsprint_errors?: Record<string, boolean> | null): QuestionJSON;
44
+ export declare function populatedJSONForTemplate(template: string, question: string, errors_to_title?: boolean): QuestionJSON;
45
+ export declare function encodeCustomText(params: Record<string, any>): string;
46
+ export declare function guessTemplateConfig(template: string): TemplateConfig;
47
+ export declare function encodeText(qtype: string, txt: string, outcomes?: string[] | null, category?: string, lang?: string): string;
48
+ export declare function getInvalidValue(_question_json?: QuestionJSON): string;
49
+ export declare function getAnsweredTooSoonValue(_question_json?: QuestionJSON): string;
50
+ export declare function getLanguage(question_json: QuestionJSON): string;
51
+ export declare function hasInvalidOption(question_json: QuestionJSON, _contract_version?: string): boolean;
52
+ export declare function hasAnsweredTooSoonOption(_question_json: QuestionJSON, contract_version: string): boolean;
53
+ export declare function getAnswerString(question_json: QuestionJSON, answer: string | null): string;
54
+ export declare function commitmentID(question_id: string, answer_hash: string, bond: string | {
55
+ toString(base: number): string;
56
+ }): string;
57
+ export declare function answerHash(answer_plaintext: string, nonce: string): string;
58
+ export declare function shortDisplayQuestionID(question_id: string): string;
@@ -0,0 +1,537 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.delimiter = delimiter;
7
+ exports.contentHash = contentHash;
8
+ exports.questionID = questionID;
9
+ exports.minNumber = minNumber;
10
+ exports.maxNumber = maxNumber;
11
+ exports.arrayToBitmaskBigNumber = arrayToBitmaskBigNumber;
12
+ exports.padToBytes32 = padToBytes32;
13
+ exports.answerToBytes32 = answerToBytes32;
14
+ exports.bytes32ToString = bytes32ToString;
15
+ exports.convertTsToString = convertTsToString;
16
+ exports.secondsTodHms = secondsTodHms;
17
+ exports.parseQuestionJSON = parseQuestionJSON;
18
+ exports.populatedJSONForTemplate = populatedJSONForTemplate;
19
+ exports.encodeCustomText = encodeCustomText;
20
+ exports.guessTemplateConfig = guessTemplateConfig;
21
+ exports.encodeText = encodeText;
22
+ exports.getInvalidValue = getInvalidValue;
23
+ exports.getAnsweredTooSoonValue = getAnsweredTooSoonValue;
24
+ exports.getLanguage = getLanguage;
25
+ exports.hasInvalidOption = hasInvalidOption;
26
+ exports.hasAnsweredTooSoonOption = hasAnsweredTooSoonOption;
27
+ exports.getAnswerString = getAnswerString;
28
+ exports.commitmentID = commitmentID;
29
+ exports.answerHash = answerHash;
30
+ exports.shortDisplayQuestionID = shortDisplayQuestionID;
31
+ const ethers_1 = require("ethers");
32
+ const marked_1 = require("marked");
33
+ const isomorphic_dompurify_1 = __importDefault(require("isomorphic-dompurify"));
34
+ const QUESTION_MAX_OUTCOMES = 128;
35
+ const TEMPLATE_MAX_PLACEHOLDERS = 128;
36
+ marked_1.marked.setOptions({ headerIds: false });
37
+ // Replace %s placeholders in order from args array
38
+ function vsprintf(template, args) {
39
+ let i = 0;
40
+ return template.replace(/%s/g, () => (i < args.length ? args[i++] : ''));
41
+ }
42
+ // Convert marked.js HTML output to plain text.
43
+ // Handles headings, paragraphs, blockquotes, lists; strips inline tags; skips hr.
44
+ function htmlToText(html) {
45
+ function decodeEntities(str) {
46
+ return str.replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&#39;|&apos;/g, "'");
47
+ }
48
+ function stripInline(h) {
49
+ return decodeEntities(h.replace(/<[^>]+>/g, ''));
50
+ }
51
+ const blocks = [];
52
+ const blockRe = /<(h[1-6]|p|blockquote|ul|ol)([^>]*)>([\s\S]*?)<\/\1>|<hr[^>]*\/?>/gi;
53
+ let match;
54
+ while ((match = blockRe.exec(html)) !== null) {
55
+ const tag = match[1] ? match[1].toLowerCase() : 'hr';
56
+ const content = match[3] || '';
57
+ if (tag === 'hr')
58
+ continue;
59
+ let text = '', leading = 2, trailing = 2;
60
+ if (/^h[1-6]$/.test(tag)) {
61
+ text = stripInline(content).trim();
62
+ leading = 3;
63
+ trailing = 3;
64
+ }
65
+ else if (tag === 'p') {
66
+ text = stripInline(content).trim();
67
+ }
68
+ else if (tag === 'blockquote') {
69
+ const inner = content.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, (_m, pc) => stripInline(pc).trim()).trim();
70
+ text = '> ' + inner;
71
+ }
72
+ else if (tag === 'ul') {
73
+ const items = [];
74
+ content.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_m, item) => {
75
+ items.push(' * ' + stripInline(item).trim());
76
+ return '';
77
+ });
78
+ text = items.join('\n');
79
+ }
80
+ else if (tag === 'ol') {
81
+ const items = [];
82
+ let i = 1;
83
+ content.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_m, item) => {
84
+ items.push(' ' + i + '. ' + stripInline(item).trim());
85
+ i++;
86
+ return '';
87
+ });
88
+ text = items.join('\n');
89
+ }
90
+ if (text !== '')
91
+ blocks.push({ text, leading, trailing });
92
+ }
93
+ if (blocks.length === 0)
94
+ return '';
95
+ let result = blocks[0].text;
96
+ for (let i = 1; i < blocks.length; i++) {
97
+ result += '\n'.repeat(Math.max(blocks[i - 1].trailing, blocks[i].leading)) + blocks[i].text;
98
+ }
99
+ return result;
100
+ }
101
+ function delimiter() {
102
+ return '␟';
103
+ }
104
+ function contentHash(template_id, opening_ts, content) {
105
+ return ethers_1.ethers.solidityPackedKeccak256(['uint256', 'uint32', 'string'], [template_id, opening_ts, content]);
106
+ }
107
+ function questionID(template_id, question, arbitrator, timeout, opening_ts, sender, nonce, min_bond, contract, version) {
108
+ if (typeof version === 'undefined') {
109
+ if (typeof min_bond === 'string' || typeof contract === 'string') {
110
+ throw Error('Version not defined');
111
+ }
112
+ }
113
+ if (!version)
114
+ version = '2.0';
115
+ const vernum = parseInt(version);
116
+ if (isNaN(vernum) || vernum < 2 || vernum > 4) {
117
+ throw Error('Version not recognized');
118
+ }
119
+ if (vernum >= 3) {
120
+ if (typeof min_bond !== 'string') {
121
+ throw Error('min_bond not supplied or invalid. Required in v3. Pass "0x0" for a zero bond');
122
+ }
123
+ }
124
+ const content_hash = contentHash(template_id, opening_ts, question);
125
+ if (vernum < 3) {
126
+ return ethers_1.ethers.solidityPackedKeccak256(['uint256', 'address', 'uint32', 'address', 'uint256'], [content_hash, arbitrator, timeout, sender, nonce]);
127
+ }
128
+ else {
129
+ const contractAddr = ('0x' + BigInt(contract).toString(16).padStart(40, '0'));
130
+ return ethers_1.ethers.solidityPackedKeccak256(['uint256', 'address', 'uint32', 'uint256', 'address', 'address', 'uint256'], [content_hash, arbitrator, timeout, min_bond, contractAddr, sender, nonce]);
131
+ }
132
+ }
133
+ function minNumber(qjson) {
134
+ if (qjson['type'] !== 'int')
135
+ return 0n;
136
+ return -maxNumber(qjson);
137
+ }
138
+ function maxNumber(qjson) {
139
+ const is_signed = qjson['type'] === 'int';
140
+ const uint256_max = (1n << 256n) - 1n;
141
+ let divby = 1n;
142
+ if (qjson['decimals']) {
143
+ divby = 10n ** BigInt(qjson['decimals']);
144
+ }
145
+ if (is_signed)
146
+ divby *= 2n;
147
+ return uint256_max / divby;
148
+ }
149
+ function arrayToBitmaskBigNumber(selections) {
150
+ let bitstr = '';
151
+ for (let i = 0; i < selections.length; i++) {
152
+ bitstr = (selections[i] ? '1' : '0') + bitstr;
153
+ }
154
+ return bitstr ? BigInt('0b' + bitstr) : 0n;
155
+ }
156
+ function padToBytes32(n, raw) {
157
+ while (n.length < 64)
158
+ n = '0' + n;
159
+ return raw ? n : '0x' + n;
160
+ }
161
+ function answerToBytes32(answer, qjson) {
162
+ const qtype = qjson['type'];
163
+ if (qtype === 'hash') {
164
+ return bytes32ToString(answer, qjson);
165
+ }
166
+ if (qtype === 'multiple-select') {
167
+ answer = arrayToBitmaskBigNumber(answer);
168
+ }
169
+ let decimals = (qtype === 'uint') ? parseInt(String(qjson['decimals'])) : 0;
170
+ if (!decimals)
171
+ decimals = 0;
172
+ let ans_hex;
173
+ if (qtype === 'int') {
174
+ const bi = BigInt(String(answer));
175
+ ans_hex = (bi < 0n ? bi + (1n << 256n) : bi).toString(16);
176
+ }
177
+ else if (decimals > 0) {
178
+ ans_hex = ethers_1.ethers.parseUnits(String(answer), decimals).toString(16);
179
+ }
180
+ else {
181
+ ans_hex = BigInt(answer).toString(16);
182
+ }
183
+ return padToBytes32(ans_hex);
184
+ }
185
+ function bytes32ToString(bytes32str, qjson) {
186
+ const qtype = qjson['type'];
187
+ let decimals = parseInt(String(qjson['decimals']));
188
+ if (!decimals)
189
+ decimals = 0;
190
+ bytes32str = bytes32str.replace(/^0x/, '');
191
+ if (qtype === 'hash') {
192
+ return padToBytes32(bytes32str).toLowerCase();
193
+ }
194
+ const bi = BigInt('0x' + bytes32str);
195
+ let ans;
196
+ if (qtype === 'int') {
197
+ const threshold = 1n << 255n;
198
+ ans = (bi >= threshold ? bi - (1n << 256n) : bi).toString();
199
+ }
200
+ else if (qtype === 'uint' || qtype === 'datetime') {
201
+ ans = bi.toString();
202
+ }
203
+ else {
204
+ throw Error('Unrecognized answer type ' + qtype);
205
+ }
206
+ if (decimals > 0) {
207
+ const divisor = 10n ** BigInt(decimals);
208
+ const bigAns = BigInt(ans);
209
+ const whole = bigAns / divisor;
210
+ const frac = bigAns % divisor;
211
+ if (frac === 0n) {
212
+ ans = whole.toString();
213
+ }
214
+ else {
215
+ const fracStr = frac.toString().padStart(decimals, '0').replace(/0+$/, '');
216
+ ans = whole.toString() + '.' + fracStr;
217
+ }
218
+ }
219
+ return ans;
220
+ }
221
+ function convertTsToString(ts) {
222
+ if (typeof ts.toNumber === 'function') {
223
+ ts = ts.toNumber();
224
+ }
225
+ const date = new Date();
226
+ date.setTime(ts * 1000);
227
+ return date.toISOString();
228
+ }
229
+ function secondsTodHms(sec) {
230
+ const s = Number(sec);
231
+ const d = Math.floor(s / (3600 * 24));
232
+ const h = Math.floor(s % (3600 * 24) / 3600);
233
+ const m = Math.floor(s % (3600 * 24) % 3600 / 60);
234
+ const r = Math.floor(s % (3600 * 24) % 3600 % 60);
235
+ const dDisplay = d > 0 ? d + (d === 1 ? ' day ' : ' days ') : '';
236
+ const hDisplay = h > 0 ? h + (h === 1 ? ' hour ' : ' hours ') : '';
237
+ const mDisplay = m > 0 ? m + (m === 1 ? ' minute ' : ' minutes ') : '';
238
+ const sDisplay = r > 0 ? r + (r === 1 ? ' second' : ' seconds') : '';
239
+ return dDisplay + hDisplay + mDisplay + sDisplay;
240
+ }
241
+ function parseQuestionJSON(data, errors_to_title, vsprint_errors) {
242
+ data = data.replace(//g, '');
243
+ let question_json;
244
+ try {
245
+ question_json = JSON.parse(data);
246
+ }
247
+ catch (e) {
248
+ question_json = {
249
+ title: '[Badly formatted question]: ' + data,
250
+ type: 'broken-question',
251
+ errors: { json_parse_failed: true },
252
+ };
253
+ }
254
+ if (question_json['outcomes'] && question_json['outcomes'].length > QUESTION_MAX_OUTCOMES) {
255
+ if (!question_json['errors'])
256
+ question_json['errors'] = {};
257
+ question_json['errors']['too_many_outcomes'] = true;
258
+ }
259
+ if ('type' in question_json && question_json['type'] === 'datetime' && 'precision' in question_json) {
260
+ if (!(['Y', 'm', 'd', 'H', 'i', 's'].includes(String(question_json['precision'])))) {
261
+ if (!question_json['errors'])
262
+ question_json['errors'] = {};
263
+ question_json['errors']['invalid_precision'] = true;
264
+ }
265
+ }
266
+ if (vsprint_errors && vsprint_errors['suspicious_extra_data']) {
267
+ if (!question_json['errors'])
268
+ question_json['errors'] = {};
269
+ question_json['errors']['suspicious_extra_data'] = true;
270
+ }
271
+ if (errors_to_title) {
272
+ if ('errors' in question_json) {
273
+ const prependers = {
274
+ invalid_precision: 'Invalid date format',
275
+ too_many_outcomes: 'Too many outcomes',
276
+ };
277
+ for (const e in question_json['errors']) {
278
+ if (e in prependers) {
279
+ question_json['title'] = '[' + prependers[e] + '] ' + question_json['title'];
280
+ }
281
+ }
282
+ }
283
+ }
284
+ if (!question_json['format'])
285
+ question_json['format'] = 'text/plain';
286
+ if (question_json['format'] === 'text/plain') {
287
+ question_json['title_text'] = question_json['title'];
288
+ }
289
+ else if (question_json['format'] === 'text/markdown') {
290
+ try {
291
+ const safeMarkdown = isomorphic_dompurify_1.default.sanitize(String(question_json['title']), { USE_PROFILES: { html: false } });
292
+ if (safeMarkdown !== question_json['title']) {
293
+ if (!question_json['errors'])
294
+ question_json['errors'] = {};
295
+ question_json['errors']['unsafe_markdown'] = true;
296
+ }
297
+ else {
298
+ question_json['title_html'] = isomorphic_dompurify_1.default.sanitize(marked_1.marked.parse(safeMarkdown).replace(/<img.*src="(.*?)".*alt="(.*?)".*\/?>/, '<a href="$1">$2</a>'));
299
+ question_json['title_text'] = htmlToText(String(question_json['title_html']));
300
+ }
301
+ }
302
+ catch (e) {
303
+ if (!question_json['errors'])
304
+ question_json['errors'] = {};
305
+ question_json['errors']['markdown_parse_failed'] = true;
306
+ }
307
+ }
308
+ else {
309
+ if (!question_json['errors'])
310
+ question_json['errors'] = {};
311
+ question_json['errors']['invalid_format'] = true;
312
+ }
313
+ if (errors_to_title) {
314
+ if ('errors' in question_json) {
315
+ const prependers = {
316
+ invalid_format: 'Invalid format',
317
+ unsafe_markdown: 'Unsafe markdown',
318
+ markdown_parse_failed: 'Bad markdown parse',
319
+ suspicious_extra_data: 'Suspicious Extra Data',
320
+ };
321
+ for (const e in question_json['errors']) {
322
+ if (e in prependers) {
323
+ question_json['title'] = '[' + prependers[e] + '] ' + question_json['title'];
324
+ }
325
+ }
326
+ }
327
+ }
328
+ return question_json;
329
+ }
330
+ function populatedJSONForTemplate(template, question, errors_to_title) {
331
+ const qbits = question.split(delimiter());
332
+ const interpolated = vsprintf(template, qbits);
333
+ let vsprint_errors = null;
334
+ if (question !== '') {
335
+ const tweak_question = question + 'x';
336
+ const qbits2 = tweak_question.split(delimiter());
337
+ const interpolated2 = vsprintf(template, qbits2);
338
+ if (interpolated === interpolated2) {
339
+ vsprint_errors = { suspicious_extra_data: true };
340
+ }
341
+ }
342
+ return parseQuestionJSON(interpolated, errors_to_title, vsprint_errors);
343
+ }
344
+ function encodeCustomText(params) {
345
+ const items = [];
346
+ for (const p in params) {
347
+ let val = params[p];
348
+ if (typeof val === 'string') {
349
+ val = JSON.stringify(val).replace(/^"|"$/g, '');
350
+ }
351
+ else if (typeof val === 'object' && val !== null) {
352
+ val = JSON.stringify(val).replace(/^\[/, '').replace(/\]$/, '');
353
+ }
354
+ else {
355
+ val = null;
356
+ }
357
+ items.push(val);
358
+ }
359
+ return items.join(delimiter());
360
+ }
361
+ function guessTemplateConfig(template) {
362
+ const placeholder = ethers_1.ethers.solidityPackedKeccak256(['string'], [template]);
363
+ const arr_placeholder = ethers_1.ethers.solidityPackedKeccak256(['string'], [template + '_arr']);
364
+ const pl_arr = new Array(TEMPLATE_MAX_PLACEHOLDERS).fill(placeholder);
365
+ let interpolated = vsprintf(template, pl_arr);
366
+ interpolated = interpolated.replaceAll('[' + placeholder + ']', '"' + arr_placeholder + '"');
367
+ const fake_json = parseQuestionJSON(interpolated, false);
368
+ const meta = '__META' in fake_json ? fake_json['__META'] : {};
369
+ const labels = 'labels' in meta ? meta['labels'] : {};
370
+ const fields = {};
371
+ for (const k in fake_json) {
372
+ if (k === 'title_text' || k === 'title_html' || k === '__META')
373
+ continue;
374
+ const fdef = {
375
+ label: k in labels ? labels[k] : k,
376
+ parts: [],
377
+ };
378
+ const regexp = new RegExp('(' + placeholder + '|' + arr_placeholder + ')');
379
+ const bits = String(fake_json[k]).split(regexp);
380
+ let part_i = 0;
381
+ for (const b of bits) {
382
+ if (b === '')
383
+ continue;
384
+ if (b === placeholder || b === arr_placeholder) {
385
+ let part_label = k + '_' + part_i;
386
+ if (part_label in labels)
387
+ part_label = labels[part_label];
388
+ fdef.parts.push(b === placeholder
389
+ ? { part: 'parameter', part_index: part_i, label: part_label }
390
+ : { part: 'array_parameter', part_index: part_i, label: part_label });
391
+ part_i++;
392
+ }
393
+ else {
394
+ fdef.parts.push({ part: 'text', value: b });
395
+ }
396
+ }
397
+ fields[k] = fdef;
398
+ }
399
+ const tags = 'tags' in meta ? meta['tags'] : {};
400
+ return { fields, tags };
401
+ }
402
+ function encodeText(qtype, txt, outcomes, category, lang) {
403
+ const def = { title: txt };
404
+ if (qtype === 'single-select' || qtype === 'multiple-select') {
405
+ def['outcomes'] = outcomes;
406
+ }
407
+ def['category'] = category;
408
+ def['lang'] = lang;
409
+ return encodeCustomText(def);
410
+ }
411
+ function getInvalidValue(_question_json) {
412
+ return '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff';
413
+ }
414
+ function getAnsweredTooSoonValue(_question_json) {
415
+ return '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe';
416
+ }
417
+ function getLanguage(question_json) {
418
+ if (typeof question_json['lang'] === 'undefined' || question_json['lang'] === '') {
419
+ return 'en_US';
420
+ }
421
+ return String(question_json['lang']);
422
+ }
423
+ function hasInvalidOption(question_json, _contract_version) {
424
+ return !('has_invalid' in question_json && !question_json['has_invalid']);
425
+ }
426
+ function hasAnsweredTooSoonOption(_question_json, contract_version) {
427
+ const bits = contract_version.split('.');
428
+ return parseInt(bits[0]) >= 3;
429
+ }
430
+ function getAnswerString(question_json, answer) {
431
+ if (answer === null)
432
+ return 'null';
433
+ if (answer === getInvalidValue(question_json))
434
+ return 'Invalid';
435
+ if (answer === getAnsweredTooSoonValue(question_json))
436
+ return 'Answered too soon';
437
+ let label = '';
438
+ switch (question_json['type']) {
439
+ case 'uint':
440
+ case 'int':
441
+ case 'hash':
442
+ label = bytes32ToString(answer, question_json);
443
+ break;
444
+ case 'bool': {
445
+ const boolVal = BigInt(answer);
446
+ if (boolVal === 1n)
447
+ label = 'Yes';
448
+ else if (boolVal === 0n)
449
+ label = 'No';
450
+ break;
451
+ }
452
+ case 'single-select':
453
+ if (question_json['outcomes'] && question_json['outcomes'].length > 0) {
454
+ const idx = Number(BigInt(answer));
455
+ label = question_json['outcomes'][idx];
456
+ }
457
+ break;
458
+ case 'multiple-select':
459
+ if (question_json['outcomes'] && question_json['outcomes'].length > 0) {
460
+ const answer_bits = BigInt(answer).toString(2);
461
+ const entries = [];
462
+ for (let i = question_json['outcomes'].length - 1; i >= 0; i--) {
463
+ if (answer_bits[i] === '1') {
464
+ const idx = answer_bits.length - 1 - i;
465
+ entries.push(question_json['outcomes'][idx]);
466
+ }
467
+ }
468
+ return entries.join(' / ');
469
+ }
470
+ break;
471
+ case 'datetime': {
472
+ let precision = 'd';
473
+ if ('precision' in question_json && ['Y', 'm', 'd', 'H', 'i', 's'].includes(String(question_json['precision']))) {
474
+ precision = String(question_json['precision']);
475
+ }
476
+ const ts = parseInt(bytes32ToString(answer, question_json));
477
+ const dateObj = new Date(ts * 1000);
478
+ const year = dateObj.getUTCFullYear();
479
+ const month = dateObj.getUTCMonth() + 1;
480
+ const date = dateObj.getUTCDate();
481
+ const hour = dateObj.getUTCHours();
482
+ const min = dateObj.getUTCMinutes();
483
+ const sec = dateObj.getUTCSeconds();
484
+ const needm = precision !== 'Y';
485
+ const needd = needm && precision !== 'm';
486
+ const needH = needd && precision !== 'd';
487
+ const needi = needH && precision !== 'H';
488
+ const needs = needi && precision !== 'i';
489
+ const hass = needs || sec > 0;
490
+ const hasi = needi || hass || min > 0;
491
+ const hasH = needH || hasi || hour > 0;
492
+ const hasd = needd || hasH || date > 1;
493
+ const hasm = needm || hasd || month > 1;
494
+ let invalid = false;
495
+ if (!needm && hasm)
496
+ invalid = true;
497
+ if (!needd && hasd)
498
+ invalid = true;
499
+ if (!needH && hasH)
500
+ invalid = true;
501
+ if (!needi && hasi)
502
+ invalid = true;
503
+ if (!needs && hass)
504
+ invalid = true;
505
+ if (invalid)
506
+ label = '[Invalid datetime]: ';
507
+ function pad2(n) { return ('0' + n).slice(-2); }
508
+ label += year;
509
+ if (hasm)
510
+ label += '-' + pad2(month);
511
+ if (hasd)
512
+ label += '-' + pad2(date);
513
+ if (hasH)
514
+ label += ' ' + pad2(hour);
515
+ if (hasi)
516
+ label += ':' + pad2(min);
517
+ else if (hasH)
518
+ label += 'hr';
519
+ if (hass)
520
+ label += ':' + pad2(sec);
521
+ break;
522
+ }
523
+ }
524
+ return label;
525
+ }
526
+ function commitmentID(question_id, answer_hash, bond) {
527
+ const bond_hex = (typeof bond === 'string') ? bond : ('0x' + bond.toString(16));
528
+ return ethers_1.ethers.solidityPackedKeccak256(['uint256', 'uint256', 'uint256'], [question_id, answer_hash, bond_hex]);
529
+ }
530
+ function answerHash(answer_plaintext, nonce) {
531
+ return ethers_1.ethers.solidityPackedKeccak256(['uint256', 'uint256'], [answer_plaintext, nonce]);
532
+ }
533
+ function shortDisplayQuestionID(question_id) {
534
+ const bits = question_id.split('-');
535
+ const qid = bits[bits.length - 1];
536
+ return qid.substring(2, 9) + '...' + qid.slice(-7);
537
+ }