universa-core2 2.0.0-alpha.1
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/LICENSE +8 -0
- package/LICENSE.BSD-3-Clause +30 -0
- package/LICENSE.GPL-2.0 +339 -0
- package/README.md +718 -0
- package/dist/.gitkeep +0 -0
- package/dist/index.cjs +2140 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +611 -0
- package/dist/index.mjs +80 -0
- package/package.json +67 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2140 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
Capsule: () => Capsule,
|
|
24
|
+
ChangeNumberPermission: () => ChangeNumberPermission,
|
|
25
|
+
ChangeOwnerPermission: () => ChangeOwnerPermission,
|
|
26
|
+
Compound: () => Compound,
|
|
27
|
+
Contract: () => Contract,
|
|
28
|
+
HashId: () => HashId,
|
|
29
|
+
KeyRecord: () => KeyRecord,
|
|
30
|
+
ModifyDataPermission: () => ModifyDataPermission,
|
|
31
|
+
Network: () => Network,
|
|
32
|
+
Node: () => Node,
|
|
33
|
+
Parcel: () => Parcel,
|
|
34
|
+
Reference: () => Reference,
|
|
35
|
+
RevokePermission: () => RevokePermission,
|
|
36
|
+
RoleLink: () => RoleLink,
|
|
37
|
+
RoleList: () => RoleList,
|
|
38
|
+
RoleSimple: () => RoleSimple,
|
|
39
|
+
SplitJoinPermission: () => SplitJoinPermission,
|
|
40
|
+
Topology: () => Topology,
|
|
41
|
+
TransactionPack: () => TransactionPack,
|
|
42
|
+
UniversaContract: () => UniversaContract
|
|
43
|
+
});
|
|
44
|
+
module.exports = __toCommonJS(index_exports);
|
|
45
|
+
__reExport(index_exports, require("unicrypto"), module.exports);
|
|
46
|
+
|
|
47
|
+
// src/models/universa_contract.ts
|
|
48
|
+
var import_unicrypto2 = require("unicrypto");
|
|
49
|
+
|
|
50
|
+
// src/boss.ts
|
|
51
|
+
var import_unicrypto = require("unicrypto");
|
|
52
|
+
var BossSingleton = class _BossSingleton {
|
|
53
|
+
constructor() {
|
|
54
|
+
}
|
|
55
|
+
static getInstance() {
|
|
56
|
+
if (!_BossSingleton.instance) {
|
|
57
|
+
_BossSingleton.instance = new _BossSingleton();
|
|
58
|
+
}
|
|
59
|
+
return _BossSingleton.instance;
|
|
60
|
+
}
|
|
61
|
+
register(alias, clz) {
|
|
62
|
+
return import_unicrypto.Boss.register(alias, clz);
|
|
63
|
+
}
|
|
64
|
+
dump(data) {
|
|
65
|
+
return import_unicrypto.Boss.dump(data);
|
|
66
|
+
}
|
|
67
|
+
load(packed) {
|
|
68
|
+
return import_unicrypto.Boss.load(packed);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
var boss_default = BossSingleton;
|
|
72
|
+
|
|
73
|
+
// src/models/roles/role_link.ts
|
|
74
|
+
var boss = boss_default.getInstance();
|
|
75
|
+
var RoleLink = class _RoleLink {
|
|
76
|
+
constructor(name, targetName) {
|
|
77
|
+
this.name = name;
|
|
78
|
+
this.targetName = targetName;
|
|
79
|
+
}
|
|
80
|
+
resolve(roles = {}, nestedLevel = 0) {
|
|
81
|
+
if (nestedLevel >= 100) throw new Error(`Role "${this.name}" is circular`);
|
|
82
|
+
const target = roles[this.targetName];
|
|
83
|
+
if (!target) throw new Error(`Target role "${this.targetName}" is not found`);
|
|
84
|
+
return target.resolve(roles, nestedLevel + 1);
|
|
85
|
+
}
|
|
86
|
+
async getSimpleAddress(roles = {}, ignoreRefs = true) {
|
|
87
|
+
const target = this.resolve(roles);
|
|
88
|
+
return target.getSimpleAddress(roles, ignoreRefs);
|
|
89
|
+
}
|
|
90
|
+
async availableFor(options) {
|
|
91
|
+
const finalTarget = this.resolve(options.roles);
|
|
92
|
+
return finalTarget.availableFor(options);
|
|
93
|
+
}
|
|
94
|
+
static {
|
|
95
|
+
// BOSS serialization definitions
|
|
96
|
+
this.className = "RoleLink";
|
|
97
|
+
}
|
|
98
|
+
serializeToBOSS() {
|
|
99
|
+
return {
|
|
100
|
+
"name": this.name,
|
|
101
|
+
"target_name": this.targetName
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
static deserializeFromBOSS(serialized) {
|
|
105
|
+
return new _RoleLink(serialized.name, serialized["target_name"]);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
boss.register("RoleLink", RoleLink);
|
|
109
|
+
|
|
110
|
+
// src/models/reference.ts
|
|
111
|
+
var boss2 = boss_default.getInstance();
|
|
112
|
+
var Reference = class _Reference {
|
|
113
|
+
constructor(name, type, where) {
|
|
114
|
+
this.fields = [];
|
|
115
|
+
this.roles = [];
|
|
116
|
+
this.signedBy = [];
|
|
117
|
+
this.transactionalId = "";
|
|
118
|
+
this.required = true;
|
|
119
|
+
this.name = name;
|
|
120
|
+
this.type = type;
|
|
121
|
+
this.where = where;
|
|
122
|
+
}
|
|
123
|
+
static {
|
|
124
|
+
this.className = "Reference";
|
|
125
|
+
}
|
|
126
|
+
static {
|
|
127
|
+
this.TYPE_TRANSACTIONAL = 1 /* TYPE_TRANSACTIONAL */;
|
|
128
|
+
}
|
|
129
|
+
static {
|
|
130
|
+
this.TYPE_EXISTING_DEFINITION = 2 /* TYPE_EXISTING_DEFINITION */;
|
|
131
|
+
}
|
|
132
|
+
static {
|
|
133
|
+
this.TYPE_EXISTING_STATE = 3 /* TYPE_EXISTING_STATE */;
|
|
134
|
+
}
|
|
135
|
+
serializeToBOSS() {
|
|
136
|
+
return {
|
|
137
|
+
"fields": this.fields,
|
|
138
|
+
"roles": this.roles,
|
|
139
|
+
"signed_by": this.signedBy,
|
|
140
|
+
"transactional_id": this.transactionalId,
|
|
141
|
+
"required": this.required,
|
|
142
|
+
"name": this.name,
|
|
143
|
+
"type": this.type,
|
|
144
|
+
"where": this.where
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
static deserializeFromBOSS(serialized) {
|
|
148
|
+
const ref = new _Reference(
|
|
149
|
+
serialized["name"],
|
|
150
|
+
serialized["type"],
|
|
151
|
+
serialized["where"]
|
|
152
|
+
);
|
|
153
|
+
ref.required = serialized.required;
|
|
154
|
+
ref.transactionalId = serialized["transactional_id"];
|
|
155
|
+
return ref;
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
boss2.register("Reference", Reference);
|
|
159
|
+
|
|
160
|
+
// src/models/permissions/permission.ts
|
|
161
|
+
var Permission = class _Permission {
|
|
162
|
+
constructor(role, params, name = _Permission.DefaultName) {
|
|
163
|
+
this.name = name;
|
|
164
|
+
this.role = role;
|
|
165
|
+
this.params = params;
|
|
166
|
+
}
|
|
167
|
+
static makeLink(name, roleName) {
|
|
168
|
+
return new RoleLink(`@${name}`, roleName);
|
|
169
|
+
}
|
|
170
|
+
availableFor(options) {
|
|
171
|
+
return this.role.availableFor(options);
|
|
172
|
+
}
|
|
173
|
+
serializeToBOSS() {
|
|
174
|
+
return Object.assign({
|
|
175
|
+
"name": this.name,
|
|
176
|
+
"role": this.role
|
|
177
|
+
}, this.params);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// src/models/permissions/revoke_permission.ts
|
|
182
|
+
var boss3 = boss_default.getInstance();
|
|
183
|
+
var RevokePermission = class _RevokePermission extends Permission {
|
|
184
|
+
static {
|
|
185
|
+
this.DefaultName = "revoke";
|
|
186
|
+
}
|
|
187
|
+
static {
|
|
188
|
+
this.className = "RevokePermission";
|
|
189
|
+
}
|
|
190
|
+
constructor(role, name = _RevokePermission.DefaultName) {
|
|
191
|
+
super(role, {}, name);
|
|
192
|
+
}
|
|
193
|
+
static create(roleName, name = _RevokePermission.DefaultName) {
|
|
194
|
+
return new _RevokePermission(this.makeLink(name, roleName), name);
|
|
195
|
+
}
|
|
196
|
+
static deserializeFromBOSS(serialized) {
|
|
197
|
+
return new _RevokePermission(serialized.role, serialized.name);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
boss3.register("RevokePermission", RevokePermission);
|
|
201
|
+
|
|
202
|
+
// src/models/universa_contract.ts
|
|
203
|
+
var boss4 = boss_default.getInstance();
|
|
204
|
+
var DEFAULT_EXPIRES_AT = 5;
|
|
205
|
+
var DEFAULT_API_LEVEL = 4;
|
|
206
|
+
function uniqueShortId(existing) {
|
|
207
|
+
const maxIterations = 1e5;
|
|
208
|
+
let found = false;
|
|
209
|
+
let id = "";
|
|
210
|
+
let i = 0;
|
|
211
|
+
while (i < maxIterations && !found) {
|
|
212
|
+
id = (0, import_unicrypto2.shortId)();
|
|
213
|
+
if (existing.indexOf(id) !== -1) found = true;
|
|
214
|
+
i++;
|
|
215
|
+
}
|
|
216
|
+
return id;
|
|
217
|
+
}
|
|
218
|
+
var UniversaContract = class _UniversaContract {
|
|
219
|
+
constructor(apiLevel, definition, state, transactional) {
|
|
220
|
+
this.apiLevel = apiLevel;
|
|
221
|
+
this.definition = definition;
|
|
222
|
+
this.state = state;
|
|
223
|
+
this.transactional = transactional;
|
|
224
|
+
}
|
|
225
|
+
static create(issuer, options) {
|
|
226
|
+
const opts = options || {};
|
|
227
|
+
const expiresAtOpt = opts.expiresAt;
|
|
228
|
+
const createdAt = opts.createdAt || /* @__PURE__ */ new Date();
|
|
229
|
+
let expiresAt = new Date(createdAt.getTime());
|
|
230
|
+
if (!expiresAtOpt) {
|
|
231
|
+
expiresAt.setFullYear(expiresAt.getFullYear() + DEFAULT_EXPIRES_AT);
|
|
232
|
+
} else if (typeof expiresAtOpt === "string") {
|
|
233
|
+
const tpe = expiresAtOpt.slice(-1);
|
|
234
|
+
const amount = parseInt(expiresAtOpt.slice(0, expiresAtOpt.length - 1));
|
|
235
|
+
if (tpe === "d") expiresAt.setDate(expiresAt.getDate() + amount);
|
|
236
|
+
if (tpe === "m") expiresAt.setMonth(expiresAt.getMonth() + amount);
|
|
237
|
+
if (tpe === "y") expiresAt.setFullYear(expiresAt.getFullYear() + amount);
|
|
238
|
+
} else {
|
|
239
|
+
expiresAt = expiresAtOpt;
|
|
240
|
+
}
|
|
241
|
+
const revokePermission = RevokePermission.create("owner");
|
|
242
|
+
const revokeId = (0, import_unicrypto2.shortId)();
|
|
243
|
+
const definition = {
|
|
244
|
+
createdAt,
|
|
245
|
+
issuer,
|
|
246
|
+
permissions: { [revokeId]: revokePermission },
|
|
247
|
+
data: {},
|
|
248
|
+
references: []
|
|
249
|
+
};
|
|
250
|
+
const state = {
|
|
251
|
+
createdAt,
|
|
252
|
+
creator: new RoleLink("creator", "issuer"),
|
|
253
|
+
revision: 1,
|
|
254
|
+
owner: new RoleLink("owner", "issuer"),
|
|
255
|
+
expiresAt,
|
|
256
|
+
data: {},
|
|
257
|
+
parent: null,
|
|
258
|
+
origin: null,
|
|
259
|
+
branchId: null,
|
|
260
|
+
roles: {},
|
|
261
|
+
references: []
|
|
262
|
+
};
|
|
263
|
+
return new _UniversaContract(DEFAULT_API_LEVEL, definition, state, null);
|
|
264
|
+
}
|
|
265
|
+
get issuer() {
|
|
266
|
+
return this.definition.issuer;
|
|
267
|
+
}
|
|
268
|
+
get owner() {
|
|
269
|
+
return this.state.owner;
|
|
270
|
+
}
|
|
271
|
+
set owner(role) {
|
|
272
|
+
this.state.owner = role;
|
|
273
|
+
}
|
|
274
|
+
get creator() {
|
|
275
|
+
return this.state.creator;
|
|
276
|
+
}
|
|
277
|
+
set creator(role) {
|
|
278
|
+
this.state.creator = role;
|
|
279
|
+
}
|
|
280
|
+
get parent() {
|
|
281
|
+
return this.state.parent;
|
|
282
|
+
}
|
|
283
|
+
get origin() {
|
|
284
|
+
return this.state.origin;
|
|
285
|
+
}
|
|
286
|
+
addPermission(permission) {
|
|
287
|
+
const id = uniqueShortId(Object.keys(this.definition.permissions));
|
|
288
|
+
this.definition.permissions[id] = permission;
|
|
289
|
+
}
|
|
290
|
+
removePermission(permissionId) {
|
|
291
|
+
delete this.definition.permissions[permissionId];
|
|
292
|
+
}
|
|
293
|
+
setParent(id) {
|
|
294
|
+
this.state.parent = id;
|
|
295
|
+
}
|
|
296
|
+
setOrigin(id) {
|
|
297
|
+
this.state.origin = id;
|
|
298
|
+
}
|
|
299
|
+
setCreatorTo(roleName) {
|
|
300
|
+
this.state.creator = new RoleLink("creator", roleName);
|
|
301
|
+
}
|
|
302
|
+
incrementRevision() {
|
|
303
|
+
this.state.revision += 1;
|
|
304
|
+
}
|
|
305
|
+
static {
|
|
306
|
+
this.className = "UniversaContract";
|
|
307
|
+
}
|
|
308
|
+
serializeToBOSS() {
|
|
309
|
+
const d = this.definition;
|
|
310
|
+
const s = this.state;
|
|
311
|
+
const definitionSerialized = {
|
|
312
|
+
"issuer": d.issuer,
|
|
313
|
+
"permissions": d.permissions,
|
|
314
|
+
"created_at": d.createdAt,
|
|
315
|
+
"data": d.data,
|
|
316
|
+
"references": d.references
|
|
317
|
+
};
|
|
318
|
+
const stateSerialized = {
|
|
319
|
+
"created_at": s.createdAt,
|
|
320
|
+
"expires_at": s.expiresAt,
|
|
321
|
+
"owner": s.owner,
|
|
322
|
+
"created_by": s.creator,
|
|
323
|
+
"data": s.data,
|
|
324
|
+
"revision": s.revision,
|
|
325
|
+
"roles": s.roles,
|
|
326
|
+
"parent": s.parent,
|
|
327
|
+
"origin": s.origin,
|
|
328
|
+
"branch_id": s.branchId,
|
|
329
|
+
"references": s.references
|
|
330
|
+
};
|
|
331
|
+
const serialized = {
|
|
332
|
+
api_level: this.apiLevel,
|
|
333
|
+
definition: definitionSerialized,
|
|
334
|
+
state: stateSerialized
|
|
335
|
+
};
|
|
336
|
+
if (this.transactional || this.transactional === null)
|
|
337
|
+
serialized.transactional = this.transactional;
|
|
338
|
+
return serialized;
|
|
339
|
+
}
|
|
340
|
+
static deserializeFromBOSS(serialized) {
|
|
341
|
+
const apiLevel = serialized["api_level"];
|
|
342
|
+
const rawDefinition = serialized.definition;
|
|
343
|
+
const rawState = serialized.state;
|
|
344
|
+
let stateRoles = rawState.roles || {};
|
|
345
|
+
if (stateRoles instanceof Array) stateRoles = {};
|
|
346
|
+
const definition = {
|
|
347
|
+
issuer: rawDefinition.issuer,
|
|
348
|
+
permissions: rawDefinition.permissions,
|
|
349
|
+
createdAt: rawDefinition["created_at"],
|
|
350
|
+
data: rawDefinition.data,
|
|
351
|
+
references: rawDefinition.references || []
|
|
352
|
+
};
|
|
353
|
+
const state = {
|
|
354
|
+
createdAt: rawState["created_at"],
|
|
355
|
+
expiresAt: rawState["expires_at"],
|
|
356
|
+
owner: rawState.owner,
|
|
357
|
+
creator: rawState["created_by"],
|
|
358
|
+
data: rawState.data,
|
|
359
|
+
revision: rawState.revision,
|
|
360
|
+
roles: stateRoles,
|
|
361
|
+
parent: rawState.parent,
|
|
362
|
+
origin: rawState.origin,
|
|
363
|
+
branchId: rawState["branch_id"],
|
|
364
|
+
references: rawState.references || []
|
|
365
|
+
};
|
|
366
|
+
return new _UniversaContract(apiLevel, definition, state, serialized.transactional);
|
|
367
|
+
}
|
|
368
|
+
resetTransactional() {
|
|
369
|
+
this.transactional = null;
|
|
370
|
+
}
|
|
371
|
+
createTransactional(id) {
|
|
372
|
+
this.transactional = { id };
|
|
373
|
+
}
|
|
374
|
+
addReference(ref) {
|
|
375
|
+
if (ref.type === Reference.TYPE_TRANSACTIONAL && !this.transactional)
|
|
376
|
+
throw new Error("Can't add transactional reference: transaction is empty");
|
|
377
|
+
if (ref.type === Reference.TYPE_TRANSACTIONAL) {
|
|
378
|
+
this.transactional.references = this.transactional.references || [];
|
|
379
|
+
this.transactional.references.push(ref);
|
|
380
|
+
}
|
|
381
|
+
if (ref.type === Reference.TYPE_EXISTING_DEFINITION) {
|
|
382
|
+
this.definition.references = this.definition.references || [];
|
|
383
|
+
this.definition.references.push(ref);
|
|
384
|
+
}
|
|
385
|
+
if (ref.type === Reference.TYPE_EXISTING_STATE) {
|
|
386
|
+
this.state.references = this.state.references || [];
|
|
387
|
+
this.state.references.push(ref);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
boss4.register("UniversaContract", UniversaContract);
|
|
392
|
+
|
|
393
|
+
// src/models/capsule.ts
|
|
394
|
+
var boss5 = boss_default.getInstance();
|
|
395
|
+
var Capsule = class _Capsule {
|
|
396
|
+
constructor(contract, options) {
|
|
397
|
+
this.new = [];
|
|
398
|
+
this.revoking = [];
|
|
399
|
+
this.contract = contract;
|
|
400
|
+
if (options) {
|
|
401
|
+
this.new = options.new || [];
|
|
402
|
+
this.revoking = options.revoking || [];
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
static unpack(bin) {
|
|
406
|
+
const raw = boss5.load(bin);
|
|
407
|
+
return new _Capsule(raw.contract, { new: raw.new, revoking: raw.revoking });
|
|
408
|
+
}
|
|
409
|
+
static create(issuer, options) {
|
|
410
|
+
const opts = options || {};
|
|
411
|
+
const contract = UniversaContract.create(issuer, {
|
|
412
|
+
createdAt: opts.createdAt,
|
|
413
|
+
expiresAt: opts.expiresAt
|
|
414
|
+
});
|
|
415
|
+
if (opts.definitionData) contract.definition.data = opts.definitionData;
|
|
416
|
+
if (opts.stateData) contract.state.data = opts.stateData;
|
|
417
|
+
if (opts.permissions)
|
|
418
|
+
opts.permissions.forEach((p) => contract.addPermission(p));
|
|
419
|
+
if (opts.owner) contract.owner = opts.owner;
|
|
420
|
+
return new _Capsule(contract);
|
|
421
|
+
}
|
|
422
|
+
get issuer() {
|
|
423
|
+
return this.contract.issuer;
|
|
424
|
+
}
|
|
425
|
+
get owner() {
|
|
426
|
+
return this.contract.owner;
|
|
427
|
+
}
|
|
428
|
+
set owner(role) {
|
|
429
|
+
this.contract.owner = role;
|
|
430
|
+
}
|
|
431
|
+
get creator() {
|
|
432
|
+
return this.contract.creator;
|
|
433
|
+
}
|
|
434
|
+
set creator(role) {
|
|
435
|
+
this.contract.creator = role;
|
|
436
|
+
}
|
|
437
|
+
get parent() {
|
|
438
|
+
return this.contract.parent;
|
|
439
|
+
}
|
|
440
|
+
get origin() {
|
|
441
|
+
return this.contract.origin;
|
|
442
|
+
}
|
|
443
|
+
get definition() {
|
|
444
|
+
return this.contract.definition;
|
|
445
|
+
}
|
|
446
|
+
get state() {
|
|
447
|
+
return this.contract.state;
|
|
448
|
+
}
|
|
449
|
+
get transactional() {
|
|
450
|
+
return this.contract.transactional;
|
|
451
|
+
}
|
|
452
|
+
pack() {
|
|
453
|
+
return boss5.dump({
|
|
454
|
+
contract: this.contract,
|
|
455
|
+
new: this.new,
|
|
456
|
+
revoking: this.revoking
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
// src/models/contract.ts
|
|
462
|
+
var import_unicrypto4 = require("unicrypto");
|
|
463
|
+
|
|
464
|
+
// src/models/hash_id.ts
|
|
465
|
+
var import_unicrypto3 = require("unicrypto");
|
|
466
|
+
var boss6 = boss_default.getInstance();
|
|
467
|
+
var HashId = class _HashId {
|
|
468
|
+
constructor(composite3) {
|
|
469
|
+
this.composite3 = composite3;
|
|
470
|
+
}
|
|
471
|
+
static async calculate(bin) {
|
|
472
|
+
return new _HashId(await (0, import_unicrypto3.hashId)(bin));
|
|
473
|
+
}
|
|
474
|
+
get base64() {
|
|
475
|
+
return (0, import_unicrypto3.encode64)(this.composite3);
|
|
476
|
+
}
|
|
477
|
+
equals(hashId2) {
|
|
478
|
+
return this.base64 === hashId2.base64;
|
|
479
|
+
}
|
|
480
|
+
static {
|
|
481
|
+
this.className = "HashId";
|
|
482
|
+
}
|
|
483
|
+
serializeToBOSS() {
|
|
484
|
+
return { composite3: this.composite3 };
|
|
485
|
+
}
|
|
486
|
+
static deserializeFromBOSS(serialized) {
|
|
487
|
+
return new _HashId(serialized.composite3);
|
|
488
|
+
}
|
|
489
|
+
static unpack(bin) {
|
|
490
|
+
return boss6.load(bin);
|
|
491
|
+
}
|
|
492
|
+
pack() {
|
|
493
|
+
return boss6.dump(this);
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
boss6.register("HashId", HashId);
|
|
497
|
+
|
|
498
|
+
// src/models/contract.ts
|
|
499
|
+
var boss7 = boss_default.getInstance();
|
|
500
|
+
var Contract = class _Contract {
|
|
501
|
+
constructor(data, signatures, options) {
|
|
502
|
+
this.binary = null;
|
|
503
|
+
this.type = "unicapsule";
|
|
504
|
+
this.version = 4;
|
|
505
|
+
this.data = data;
|
|
506
|
+
this.capsule = Capsule.unpack(data);
|
|
507
|
+
this.signatures = signatures || [];
|
|
508
|
+
if (options) {
|
|
509
|
+
this.type = options.type || this.type;
|
|
510
|
+
this.version = options.version || this.version;
|
|
511
|
+
this.binary = options.binary || this.binary;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
get issuer() {
|
|
515
|
+
return this.capsule.issuer;
|
|
516
|
+
}
|
|
517
|
+
get owner() {
|
|
518
|
+
return this.capsule.owner;
|
|
519
|
+
}
|
|
520
|
+
set owner(role) {
|
|
521
|
+
this.capsule.owner = role;
|
|
522
|
+
}
|
|
523
|
+
get creator() {
|
|
524
|
+
return this.capsule.creator;
|
|
525
|
+
}
|
|
526
|
+
set creator(role) {
|
|
527
|
+
this.capsule.creator = role;
|
|
528
|
+
}
|
|
529
|
+
setCreatorTo(roleName) {
|
|
530
|
+
this.capsule.contract.setCreatorTo(roleName);
|
|
531
|
+
}
|
|
532
|
+
get parent() {
|
|
533
|
+
return this.capsule.parent;
|
|
534
|
+
}
|
|
535
|
+
get origin() {
|
|
536
|
+
return this.capsule.origin;
|
|
537
|
+
}
|
|
538
|
+
get definition() {
|
|
539
|
+
return this.capsule.definition;
|
|
540
|
+
}
|
|
541
|
+
get state() {
|
|
542
|
+
return this.capsule.state;
|
|
543
|
+
}
|
|
544
|
+
get transactional() {
|
|
545
|
+
return this.capsule.transactional;
|
|
546
|
+
}
|
|
547
|
+
hashId() {
|
|
548
|
+
if (this.binary) return HashId.calculate(this.binary);
|
|
549
|
+
else throw new Error("contract is not packed yet");
|
|
550
|
+
}
|
|
551
|
+
async getSignatureKeys() {
|
|
552
|
+
const keys = [];
|
|
553
|
+
const signaturesTotal = this.signatures.length;
|
|
554
|
+
let i = 0;
|
|
555
|
+
const query = this.signatures.map(async (signature) => {
|
|
556
|
+
const { exts } = boss7.load(signature);
|
|
557
|
+
const targetSignature = boss7.load(exts);
|
|
558
|
+
return await import_unicrypto4.PublicKey.unpack(targetSignature["pub_key"]);
|
|
559
|
+
});
|
|
560
|
+
return await Promise.all(query);
|
|
561
|
+
}
|
|
562
|
+
async isSignedBy(options) {
|
|
563
|
+
const { publicKey, address } = options;
|
|
564
|
+
let signed = false;
|
|
565
|
+
let i = 0;
|
|
566
|
+
const signaturesTotal = this.signatures.length;
|
|
567
|
+
const data = this.data;
|
|
568
|
+
const self = this;
|
|
569
|
+
if (publicKey == null && address == null) throw new Error("No address or key was given");
|
|
570
|
+
async function verify(index) {
|
|
571
|
+
if (index >= signaturesTotal) return false;
|
|
572
|
+
const signature = self.signatures[index];
|
|
573
|
+
let pub = publicKey;
|
|
574
|
+
if (pub != null) {
|
|
575
|
+
const verified = await pub.verifyExtended(signature, data);
|
|
576
|
+
if (verified) return true;
|
|
577
|
+
} else {
|
|
578
|
+
const { exts } = boss7.load(signature);
|
|
579
|
+
const targetSignature = boss7.load(exts);
|
|
580
|
+
const unpackedKey = await import_unicrypto4.PublicKey.unpack(targetSignature["pub_key"]);
|
|
581
|
+
if (address?.isMatchingKey(unpackedKey)) return true;
|
|
582
|
+
}
|
|
583
|
+
return await verify(index + 1);
|
|
584
|
+
}
|
|
585
|
+
return await verify(0);
|
|
586
|
+
}
|
|
587
|
+
async sign(privateKey) {
|
|
588
|
+
const isSigned = await this.isSignedBy({ publicKey: privateKey.publicKey });
|
|
589
|
+
if (isSigned) return;
|
|
590
|
+
const signature = await privateKey.signExtended(this.data);
|
|
591
|
+
this.signatures.push(signature);
|
|
592
|
+
}
|
|
593
|
+
static create(issuer, options) {
|
|
594
|
+
const capsule = Capsule.create(issuer, options);
|
|
595
|
+
return new _Contract(capsule.pack());
|
|
596
|
+
}
|
|
597
|
+
packData() {
|
|
598
|
+
this.data = this.capsule.pack();
|
|
599
|
+
this.signatures = [];
|
|
600
|
+
}
|
|
601
|
+
pack() {
|
|
602
|
+
const packed = boss7.dump({
|
|
603
|
+
data: this.data,
|
|
604
|
+
signatures: this.signatures,
|
|
605
|
+
type: this.type,
|
|
606
|
+
version: this.version
|
|
607
|
+
});
|
|
608
|
+
this.binary = packed;
|
|
609
|
+
return packed;
|
|
610
|
+
}
|
|
611
|
+
async createRevision(createdAt) {
|
|
612
|
+
const parentId = await this.hashId();
|
|
613
|
+
if (!parentId || !this.binary)
|
|
614
|
+
throw new Error("Can't create revision of draft contract");
|
|
615
|
+
const newRevision = _Contract.unpack(this.binary);
|
|
616
|
+
const capsule = newRevision.capsule;
|
|
617
|
+
const unicontract = capsule.contract;
|
|
618
|
+
unicontract.incrementRevision();
|
|
619
|
+
if (!unicontract.origin) unicontract.setOrigin(parentId);
|
|
620
|
+
unicontract.setParent(parentId);
|
|
621
|
+
unicontract.state.createdAt = createdAt || /* @__PURE__ */ new Date();
|
|
622
|
+
capsule.new = [];
|
|
623
|
+
capsule.revoking = [parentId];
|
|
624
|
+
return newRevision;
|
|
625
|
+
}
|
|
626
|
+
static unpack(binary) {
|
|
627
|
+
const raw = boss7.load(binary);
|
|
628
|
+
return new _Contract(raw.data, raw.signatures, {
|
|
629
|
+
type: raw.type,
|
|
630
|
+
version: raw.version,
|
|
631
|
+
binary
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
resetTransactional() {
|
|
635
|
+
this.capsule.contract.resetTransactional();
|
|
636
|
+
}
|
|
637
|
+
createTransactional(id) {
|
|
638
|
+
this.capsule.contract.createTransactional(id);
|
|
639
|
+
}
|
|
640
|
+
addReference(ref) {
|
|
641
|
+
this.capsule.contract.addReference(ref);
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
// src/models/compound.ts
|
|
646
|
+
var import_unicrypto5 = require("unicrypto");
|
|
647
|
+
|
|
648
|
+
// src/models/transaction_pack.ts
|
|
649
|
+
var boss8 = boss_default.getInstance();
|
|
650
|
+
var TAG_PREFIX_RESERVED = "universa:";
|
|
651
|
+
var TransactionPack = class _TransactionPack {
|
|
652
|
+
constructor(contractPacked, options) {
|
|
653
|
+
this.subItems = {};
|
|
654
|
+
this.referencedItems = {};
|
|
655
|
+
this.taggedItems = {};
|
|
656
|
+
const self = this;
|
|
657
|
+
const opts = options || {};
|
|
658
|
+
const subItemsPacked = opts.subItems || [];
|
|
659
|
+
const referencedItemsPacked = opts.referencedItems || [];
|
|
660
|
+
const tags = opts.tags || {};
|
|
661
|
+
this.contract = Contract.unpack(contractPacked);
|
|
662
|
+
this.ready = new Promise((resolve, reject) => {
|
|
663
|
+
const subLoaders = subItemsPacked.map(async (item) => {
|
|
664
|
+
const contract = Contract.unpack(item);
|
|
665
|
+
const hashId2 = await contract.hashId();
|
|
666
|
+
self.subItems[hashId2.base64] = contract;
|
|
667
|
+
});
|
|
668
|
+
const refLoaders = referencedItemsPacked.map(async (item) => {
|
|
669
|
+
const contract = Contract.unpack(item);
|
|
670
|
+
const hashId2 = await contract.hashId();
|
|
671
|
+
self.referencedItems[hashId2.base64] = contract;
|
|
672
|
+
});
|
|
673
|
+
const tagLoaders = [];
|
|
674
|
+
async function loadTag(tag) {
|
|
675
|
+
const hashId2 = tags[tag];
|
|
676
|
+
const hashId64 = hashId2.base64;
|
|
677
|
+
const contractId = await self.contract.hashId();
|
|
678
|
+
if (contractId.base64 === hashId64) self.taggedItems[tag] = {
|
|
679
|
+
contract: self.contract,
|
|
680
|
+
hashId: hashId2
|
|
681
|
+
};
|
|
682
|
+
const item = self.subItems[hashId64] || self.referencedItems[hashId64];
|
|
683
|
+
if (item) self.taggedItems[tag] = { hashId: hashId2, contract: item };
|
|
684
|
+
}
|
|
685
|
+
for (let tag in tags) tagLoaders.push(loadTag(tag));
|
|
686
|
+
const loaders = subLoaders.concat(refLoaders).concat(tagLoaders);
|
|
687
|
+
Promise.all(loaders).then(() => resolve());
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
async getItem(hashId2) {
|
|
691
|
+
await this.ready;
|
|
692
|
+
const hashId64 = hashId2.base64;
|
|
693
|
+
const contractId = await this.contract.hashId();
|
|
694
|
+
if (contractId.base64 === hashId64) return this.contract;
|
|
695
|
+
return this.subItems[hashId64] || this.referencedItems[hashId64];
|
|
696
|
+
}
|
|
697
|
+
async getTag(tag) {
|
|
698
|
+
await this.ready;
|
|
699
|
+
return this.taggedItems[tag];
|
|
700
|
+
}
|
|
701
|
+
async addTag(tag, hashId2) {
|
|
702
|
+
if (tag.startsWith(TAG_PREFIX_RESERVED))
|
|
703
|
+
throw new Error("tag can't start with reserved words");
|
|
704
|
+
await this.ready;
|
|
705
|
+
const contract = await this.getItem(hashId2);
|
|
706
|
+
if (!contract) throw new Error("Can't add tag for non-existing item");
|
|
707
|
+
this.taggedItems[tag] = {
|
|
708
|
+
hashId: hashId2,
|
|
709
|
+
contract
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
async addSubItem(itemPacked) {
|
|
713
|
+
const item = Contract.unpack(itemPacked);
|
|
714
|
+
const hashId2 = await item.hashId();
|
|
715
|
+
this.subItems[hashId2.base64] = item;
|
|
716
|
+
}
|
|
717
|
+
async addReferencedItem(itemPacked) {
|
|
718
|
+
const item = Contract.unpack(itemPacked);
|
|
719
|
+
const hashId2 = await item.hashId();
|
|
720
|
+
this.referencedItems[hashId2.base64] = item;
|
|
721
|
+
}
|
|
722
|
+
async pack() {
|
|
723
|
+
await this.ready;
|
|
724
|
+
return boss8.dump(this);
|
|
725
|
+
}
|
|
726
|
+
static unpack(packed) {
|
|
727
|
+
return boss8.load(packed);
|
|
728
|
+
}
|
|
729
|
+
static {
|
|
730
|
+
this.className = "TransactionPack";
|
|
731
|
+
}
|
|
732
|
+
serializeToBOSS() {
|
|
733
|
+
const subItems = [];
|
|
734
|
+
const referencedItems = [];
|
|
735
|
+
const tags = {};
|
|
736
|
+
for (let hashId64 in this.subItems) {
|
|
737
|
+
subItems.push(this.subItems[hashId64].binary);
|
|
738
|
+
}
|
|
739
|
+
for (let hashId64 in this.referencedItems) {
|
|
740
|
+
referencedItems.push(this.referencedItems[hashId64].binary);
|
|
741
|
+
}
|
|
742
|
+
for (let tag in this.taggedItems) {
|
|
743
|
+
tags[tag] = this.taggedItems[tag].hashId;
|
|
744
|
+
}
|
|
745
|
+
return {
|
|
746
|
+
contract: this.contract.binary,
|
|
747
|
+
subItems,
|
|
748
|
+
referencedItems,
|
|
749
|
+
tags
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
static deserializeFromBOSS(serialized) {
|
|
753
|
+
return new _TransactionPack(serialized.contract, {
|
|
754
|
+
subItems: serialized.subItems || [],
|
|
755
|
+
referencedItems: serialized.referencedItems || [],
|
|
756
|
+
tags: serialized.tags || {}
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
async sign(key) {
|
|
760
|
+
await this.contract.sign(key);
|
|
761
|
+
this.contract.pack();
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
boss8.register("TransactionPack", TransactionPack);
|
|
765
|
+
|
|
766
|
+
// src/models/compound.ts
|
|
767
|
+
var Compound = class _Compound {
|
|
768
|
+
constructor(tpack) {
|
|
769
|
+
this.tpack = tpack;
|
|
770
|
+
}
|
|
771
|
+
static unpack(bin) {
|
|
772
|
+
return new _Compound(TransactionPack.unpack(bin));
|
|
773
|
+
}
|
|
774
|
+
pack() {
|
|
775
|
+
return this.tpack.pack();
|
|
776
|
+
}
|
|
777
|
+
getDefinitionContracts() {
|
|
778
|
+
return this.tpack.contract.definition.data.contracts;
|
|
779
|
+
}
|
|
780
|
+
// create(expiresAt: Date) {
|
|
781
|
+
// const contract =
|
|
782
|
+
// }
|
|
783
|
+
getTags() {
|
|
784
|
+
return Object.keys(this.getDefinitionContracts() || {});
|
|
785
|
+
}
|
|
786
|
+
sign(privateKey) {
|
|
787
|
+
return this.tpack.sign(privateKey);
|
|
788
|
+
}
|
|
789
|
+
async getTag(tag) {
|
|
790
|
+
const contracts = this.getDefinitionContracts() || {};
|
|
791
|
+
const contractInfo = contracts[tag];
|
|
792
|
+
if (!contractInfo) return null;
|
|
793
|
+
const mainId = new HashId((0, import_unicrypto5.decode64)(contractInfo.id));
|
|
794
|
+
const mainContract = await this.tpack.getItem(mainId);
|
|
795
|
+
const tpack = new TransactionPack(mainContract.binary);
|
|
796
|
+
const newItemIds = mainContract.capsule.new;
|
|
797
|
+
const revokingItemIds = mainContract.capsule.revoking;
|
|
798
|
+
const subItemIds = newItemIds.concat(revokingItemIds);
|
|
799
|
+
for (let i = 0; i < subItemIds.length; i++) {
|
|
800
|
+
let id = subItemIds[i];
|
|
801
|
+
let contract = await this.tpack.getItem(id);
|
|
802
|
+
await tpack.addSubItem(contract.binary);
|
|
803
|
+
}
|
|
804
|
+
const refs = contractInfo.refs || [];
|
|
805
|
+
for (let i = 0; i < refs.length; i++) {
|
|
806
|
+
let id = new HashId((0, import_unicrypto5.decode64)(refs[i]));
|
|
807
|
+
let contract = await this.tpack.getItem(id);
|
|
808
|
+
await tpack.addReferencedItem(contract.binary);
|
|
809
|
+
}
|
|
810
|
+
const tags = contractInfo.tags || {};
|
|
811
|
+
for (let tag2 in tags) {
|
|
812
|
+
let id = new HashId((0, import_unicrypto5.decode64)(tags[tag2]));
|
|
813
|
+
await tpack.addTag(tag2, id);
|
|
814
|
+
}
|
|
815
|
+
return tpack;
|
|
816
|
+
}
|
|
817
|
+
getItem(hashId2) {
|
|
818
|
+
return this.tpack.getItem(hashId2);
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
// src/models/parcel.ts
|
|
823
|
+
var boss9 = boss_default.getInstance();
|
|
824
|
+
var Parcel = class _Parcel {
|
|
825
|
+
constructor(payment, payload, hashId2) {
|
|
826
|
+
this.paymentBin = payment;
|
|
827
|
+
this.payloadBin = payload;
|
|
828
|
+
this.payment = boss9.load(payment);
|
|
829
|
+
this.payload = boss9.load(payload);
|
|
830
|
+
this.hashId = hashId2;
|
|
831
|
+
}
|
|
832
|
+
static {
|
|
833
|
+
this.className = "Parcel";
|
|
834
|
+
}
|
|
835
|
+
serializeToBOSS() {
|
|
836
|
+
return {
|
|
837
|
+
payment: this.paymentBin,
|
|
838
|
+
payload: this.payloadBin,
|
|
839
|
+
hashId: this.hashId
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
static deserializeFromBOSS(serialized) {
|
|
843
|
+
return new _Parcel(serialized.payment, serialized.payload, serialized.hashId);
|
|
844
|
+
}
|
|
845
|
+
static async create(payment, payload) {
|
|
846
|
+
const paymentId = (await HashId.calculate(payment)).composite3;
|
|
847
|
+
const payloadId = (await HashId.calculate(payload)).composite3;
|
|
848
|
+
const composite = new Uint8Array(paymentId.length + payloadId.length);
|
|
849
|
+
composite.set(paymentId);
|
|
850
|
+
composite.set(payloadId, paymentId.length);
|
|
851
|
+
const id = await HashId.calculate(composite);
|
|
852
|
+
return new _Parcel(payment, payload, id);
|
|
853
|
+
}
|
|
854
|
+
static async createPayment(amount, upack, options = {}) {
|
|
855
|
+
let key = "transaction_units";
|
|
856
|
+
if (options.isTestnet) key = "test_transaction_units";
|
|
857
|
+
const uOld = upack.contract;
|
|
858
|
+
if (!uOld.binary)
|
|
859
|
+
throw new Error("Can't create payment with draft u pack");
|
|
860
|
+
const balance = uOld.state.data[key];
|
|
861
|
+
if (amount > balance) throw new Error("not enough U");
|
|
862
|
+
const balanceNew = balance - amount;
|
|
863
|
+
const uNew = await uOld.createRevision(options.createdAt);
|
|
864
|
+
uNew.state.data[key] = balanceNew;
|
|
865
|
+
uNew.capsule.contract.setCreatorTo("owner");
|
|
866
|
+
uNew.packData();
|
|
867
|
+
return new TransactionPack(uNew.pack(), { subItems: [uOld.binary] });
|
|
868
|
+
}
|
|
869
|
+
pack() {
|
|
870
|
+
return boss9.dump(this);
|
|
871
|
+
}
|
|
872
|
+
static unpack(bin) {
|
|
873
|
+
return boss9.load(bin);
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
boss9.register("Parcel", Parcel);
|
|
877
|
+
|
|
878
|
+
// src/models/key_record.ts
|
|
879
|
+
var import_unicrypto6 = require("unicrypto");
|
|
880
|
+
|
|
881
|
+
// src/utils.ts
|
|
882
|
+
var fs = require("fs");
|
|
883
|
+
async function retry(fn, options) {
|
|
884
|
+
const opts = options || {};
|
|
885
|
+
let attempts = 5;
|
|
886
|
+
if (typeof opts.attempts === "number") attempts = opts.attempts;
|
|
887
|
+
const interval = opts.interval || 1e3;
|
|
888
|
+
const exponential = opts.exponential || false;
|
|
889
|
+
const onError = opts.onError;
|
|
890
|
+
try {
|
|
891
|
+
const val = await fn();
|
|
892
|
+
return val;
|
|
893
|
+
} catch (error) {
|
|
894
|
+
if (error instanceof Canceled) throw error;
|
|
895
|
+
if (onError) onError(error);
|
|
896
|
+
if (attempts) {
|
|
897
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
898
|
+
return retry(fn, {
|
|
899
|
+
attempts: attempts - 1,
|
|
900
|
+
interval: exponential ? interval * 2 : interval,
|
|
901
|
+
exponential,
|
|
902
|
+
onError
|
|
903
|
+
});
|
|
904
|
+
} else throw new Error("Maximum attempts reached");
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
function abortable(responsePromise, request) {
|
|
908
|
+
return cancelable(responsePromise, () => {
|
|
909
|
+
const activeRequest = typeof request === "function" ? request() : request;
|
|
910
|
+
if (typeof activeRequest?.cancel === "function") activeRequest.cancel();
|
|
911
|
+
else if (typeof activeRequest?.abort === "function") activeRequest.abort();
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
var Canceled = class _Canceled extends Error {
|
|
915
|
+
constructor(reason = "") {
|
|
916
|
+
super(reason);
|
|
917
|
+
Object.setPrototypeOf(this, _Canceled.prototype);
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
function cancelable(promise, onCancel) {
|
|
921
|
+
let cancel = null;
|
|
922
|
+
let cancelable2;
|
|
923
|
+
cancelable2 = new Promise((resolve, reject) => {
|
|
924
|
+
cancel = () => {
|
|
925
|
+
try {
|
|
926
|
+
if (onCancel) onCancel();
|
|
927
|
+
} catch (e) {
|
|
928
|
+
reject(e);
|
|
929
|
+
}
|
|
930
|
+
reject(new Canceled());
|
|
931
|
+
return cancelable2;
|
|
932
|
+
};
|
|
933
|
+
promise.then(resolve, reject);
|
|
934
|
+
});
|
|
935
|
+
if (cancel) cancelable2.cancel = cancel;
|
|
936
|
+
return cancelable2;
|
|
937
|
+
}
|
|
938
|
+
function readJSON(path) {
|
|
939
|
+
return new Promise((resolve, reject) => {
|
|
940
|
+
fs.readFile(path, (err, data) => {
|
|
941
|
+
if (err) reject(err);
|
|
942
|
+
else resolve(JSON.parse(data));
|
|
943
|
+
});
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
function isSuperset(set, subset) {
|
|
947
|
+
for (var elem of Array.from(subset.values())) {
|
|
948
|
+
if (!set.has(elem)) {
|
|
949
|
+
return false;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
return true;
|
|
953
|
+
}
|
|
954
|
+
function createAddressSet(keys = [], addresses = []) {
|
|
955
|
+
const addrs = /* @__PURE__ */ new Set();
|
|
956
|
+
keys.forEach((key) => {
|
|
957
|
+
addrs.add(key.shortAddress.base58);
|
|
958
|
+
addrs.add(key.longAddress.base58);
|
|
959
|
+
});
|
|
960
|
+
addresses.forEach((addr) => addrs.add(addr.base58));
|
|
961
|
+
return addrs;
|
|
962
|
+
}
|
|
963
|
+
function omitBOSS(serialized, fields) {
|
|
964
|
+
if (typeof serialized !== "object" || !serialized) return serialized;
|
|
965
|
+
const cleaned = Object.assign({}, serialized);
|
|
966
|
+
delete cleaned["__t"];
|
|
967
|
+
delete cleaned["__type"];
|
|
968
|
+
fields.forEach((field) => delete cleaned[field]);
|
|
969
|
+
return cleaned;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// src/models/key_record.ts
|
|
973
|
+
var boss10 = boss_default.getInstance();
|
|
974
|
+
var KeyRecord = class _KeyRecord {
|
|
975
|
+
constructor(keyPacked, extra, key) {
|
|
976
|
+
this.key = null;
|
|
977
|
+
this.extra = {};
|
|
978
|
+
this.keyPacked = keyPacked;
|
|
979
|
+
if (key) {
|
|
980
|
+
this.key = key;
|
|
981
|
+
this.keyLoader = new Promise((resolve) => resolve(key));
|
|
982
|
+
} else this.keyLoader = import_unicrypto6.PublicKey.unpack(keyPacked);
|
|
983
|
+
if (extra) this.extra = extra;
|
|
984
|
+
}
|
|
985
|
+
async getKey() {
|
|
986
|
+
if (this.key) return this.key;
|
|
987
|
+
return await this.keyLoader;
|
|
988
|
+
}
|
|
989
|
+
setExtra(extra) {
|
|
990
|
+
this.extra = extra;
|
|
991
|
+
}
|
|
992
|
+
static create(key, extra = {}) {
|
|
993
|
+
return new _KeyRecord(key.packed, extra, key);
|
|
994
|
+
}
|
|
995
|
+
static {
|
|
996
|
+
this.className = "KeyRecord";
|
|
997
|
+
}
|
|
998
|
+
serializeToBOSS() {
|
|
999
|
+
const key = {
|
|
1000
|
+
"__t": "RSAPublicKey",
|
|
1001
|
+
"packed": this.keyPacked
|
|
1002
|
+
};
|
|
1003
|
+
return Object.assign({ key }, this.extra);
|
|
1004
|
+
}
|
|
1005
|
+
static deserializeFromBOSS(serialized) {
|
|
1006
|
+
return new _KeyRecord(serialized.key.packed, omitBOSS(serialized, ["key"]));
|
|
1007
|
+
}
|
|
1008
|
+
pack() {
|
|
1009
|
+
return boss10.dump(this);
|
|
1010
|
+
}
|
|
1011
|
+
static unpack(bin) {
|
|
1012
|
+
return boss10.load(bin);
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
boss10.register("KeyRecord", KeyRecord);
|
|
1016
|
+
|
|
1017
|
+
// src/models/roles/role_simple.ts
|
|
1018
|
+
var boss11 = boss_default.getInstance();
|
|
1019
|
+
var RoleSimple = class _RoleSimple {
|
|
1020
|
+
constructor(name, options) {
|
|
1021
|
+
this.keyRecords = [];
|
|
1022
|
+
this.addresses = [];
|
|
1023
|
+
this.anonIds = [];
|
|
1024
|
+
this.name = name;
|
|
1025
|
+
if (options.addresses) this.addresses = options.addresses;
|
|
1026
|
+
if (options.keyRecords) this.keyRecords = options.keyRecords;
|
|
1027
|
+
if (options.anonIds) this.anonIds = options.anonIds;
|
|
1028
|
+
if (options.keys)
|
|
1029
|
+
this.keyRecords = options.keys.map((key) => KeyRecord.create(key));
|
|
1030
|
+
}
|
|
1031
|
+
resolve(roles, nestedLevel = 0) {
|
|
1032
|
+
return this;
|
|
1033
|
+
}
|
|
1034
|
+
async getSimpleAddress(roles = {}, ignoreRefs = true) {
|
|
1035
|
+
if (this.keyRecords.length + this.addresses.length > 1) return null;
|
|
1036
|
+
if (this.keyRecords.length === 1) {
|
|
1037
|
+
const key = await this.keyRecords[0].getKey();
|
|
1038
|
+
return key.shortAddress;
|
|
1039
|
+
} else {
|
|
1040
|
+
return this.addresses[0];
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
async availableFor(options) {
|
|
1044
|
+
const keyLoaders = this.keyRecords.map((record) => record.getKey());
|
|
1045
|
+
const roleKeys = await Promise.all(keyLoaders);
|
|
1046
|
+
const roleAddresses = createAddressSet(roleKeys, this.addresses);
|
|
1047
|
+
const providedAddresses = createAddressSet(options.keys, options.addresses);
|
|
1048
|
+
return isSuperset(providedAddresses, roleAddresses);
|
|
1049
|
+
}
|
|
1050
|
+
static {
|
|
1051
|
+
this.className = "RoleSimple";
|
|
1052
|
+
}
|
|
1053
|
+
serializeToBOSS() {
|
|
1054
|
+
return {
|
|
1055
|
+
name: this.name,
|
|
1056
|
+
keys: this.keyRecords,
|
|
1057
|
+
addresses: this.addresses,
|
|
1058
|
+
anonIds: this.anonIds
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
static deserializeFromBOSS(serialized) {
|
|
1062
|
+
const records = serialized.keys || [];
|
|
1063
|
+
const addresses = serialized.addresses || [];
|
|
1064
|
+
return new _RoleSimple(serialized.name, {
|
|
1065
|
+
keyRecords: records,
|
|
1066
|
+
addresses,
|
|
1067
|
+
anonIds: serialized.anonIds || []
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
1071
|
+
boss11.register("SimpleRole", RoleSimple);
|
|
1072
|
+
|
|
1073
|
+
// src/models/roles/role_list.ts
|
|
1074
|
+
var boss12 = boss_default.getInstance();
|
|
1075
|
+
var MODES = /* @__PURE__ */ ((MODES2) => {
|
|
1076
|
+
MODES2["ANY"] = "ANY";
|
|
1077
|
+
MODES2["ALL"] = "ALL";
|
|
1078
|
+
MODES2["QUORUM"] = "QUORUM";
|
|
1079
|
+
return MODES2;
|
|
1080
|
+
})(MODES || {});
|
|
1081
|
+
var RoleList = class _RoleList {
|
|
1082
|
+
constructor(name, options) {
|
|
1083
|
+
this.roles = [];
|
|
1084
|
+
this.quorumSize = 1;
|
|
1085
|
+
const { mode, roleNames, roles, quorumSize } = options;
|
|
1086
|
+
if (!roleNames && !roles)
|
|
1087
|
+
throw new Error("Array of role names or roles must be provided");
|
|
1088
|
+
this.name = name;
|
|
1089
|
+
this.mode = mode;
|
|
1090
|
+
const createLink = (roleName) => new RoleLink(`__${name}-${roleName}`, roleName);
|
|
1091
|
+
if (roles) this.roles = roles;
|
|
1092
|
+
if (roleNames) this.roles = roleNames.map(createLink);
|
|
1093
|
+
if (quorumSize) this.quorumSize = quorumSize;
|
|
1094
|
+
}
|
|
1095
|
+
static {
|
|
1096
|
+
this.MODES = MODES;
|
|
1097
|
+
}
|
|
1098
|
+
resolve(roles = {}, nestedLevel = 0) {
|
|
1099
|
+
if (nestedLevel >= 100) throw new Error(`Role "${this.name}" is circular`);
|
|
1100
|
+
return new _RoleList(this.name, {
|
|
1101
|
+
mode: this.mode,
|
|
1102
|
+
roles: this.roles.map((role) => role.resolve(roles, nestedLevel + 1)),
|
|
1103
|
+
quorumSize: this.quorumSize
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
async getSimpleAddress(roles = {}, ignoreRefs = true) {
|
|
1107
|
+
if (this.roles.length === 1 && (this.mode !== "QUORUM" /* QUORUM */ || this.quorumSize === 1))
|
|
1108
|
+
return this.roles[0].getSimpleAddress(roles);
|
|
1109
|
+
else
|
|
1110
|
+
return null;
|
|
1111
|
+
}
|
|
1112
|
+
async availableFor(options) {
|
|
1113
|
+
const finalTarget = this.resolve(options.roles);
|
|
1114
|
+
const totalRoles = finalTarget.roles.length;
|
|
1115
|
+
async function atLeastNPositive(N) {
|
|
1116
|
+
let counter = 0;
|
|
1117
|
+
let i = 0;
|
|
1118
|
+
while (counter < N && i < totalRoles) {
|
|
1119
|
+
const vote = await finalTarget.roles[i].availableFor(options);
|
|
1120
|
+
if (vote) counter++;
|
|
1121
|
+
i++;
|
|
1122
|
+
}
|
|
1123
|
+
return counter >= N;
|
|
1124
|
+
}
|
|
1125
|
+
switch (this.mode) {
|
|
1126
|
+
case "ANY" /* ANY */:
|
|
1127
|
+
return await atLeastNPositive(1);
|
|
1128
|
+
case "QUORUM" /* QUORUM */:
|
|
1129
|
+
return await atLeastNPositive(this.quorumSize);
|
|
1130
|
+
case "ALL" /* ALL */:
|
|
1131
|
+
return await atLeastNPositive(totalRoles);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
static {
|
|
1135
|
+
this.className = "RoleList";
|
|
1136
|
+
}
|
|
1137
|
+
serializeToBOSS() {
|
|
1138
|
+
return {
|
|
1139
|
+
"name": this.name,
|
|
1140
|
+
"mode": this.mode,
|
|
1141
|
+
"roles": this.roles,
|
|
1142
|
+
"quorumSize": this.quorumSize
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
static deserializeFromBOSS(serialized) {
|
|
1146
|
+
return new _RoleList(serialized.name, {
|
|
1147
|
+
mode: serialized.mode,
|
|
1148
|
+
roles: serialized.roles,
|
|
1149
|
+
quorumSize: serialized.quorumSize
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
boss12.register("RoleList", RoleList);
|
|
1154
|
+
|
|
1155
|
+
// src/models/permissions/change_owner_permission.ts
|
|
1156
|
+
var boss13 = boss_default.getInstance();
|
|
1157
|
+
var ChangeOwnerPermission = class _ChangeOwnerPermission extends Permission {
|
|
1158
|
+
static {
|
|
1159
|
+
this.DefaultName = "change_owner";
|
|
1160
|
+
}
|
|
1161
|
+
static {
|
|
1162
|
+
this.className = "ChangeOwnerPermission";
|
|
1163
|
+
}
|
|
1164
|
+
constructor(role, name = _ChangeOwnerPermission.DefaultName) {
|
|
1165
|
+
super(role, {}, name);
|
|
1166
|
+
}
|
|
1167
|
+
static create(roleName, name = _ChangeOwnerPermission.DefaultName) {
|
|
1168
|
+
return new _ChangeOwnerPermission(this.makeLink(name, roleName), name);
|
|
1169
|
+
}
|
|
1170
|
+
static deserializeFromBOSS(serialized) {
|
|
1171
|
+
return new _ChangeOwnerPermission(serialized.role, serialized.name);
|
|
1172
|
+
}
|
|
1173
|
+
};
|
|
1174
|
+
boss13.register("ChangeOwnerPermission", ChangeOwnerPermission);
|
|
1175
|
+
|
|
1176
|
+
// src/models/permissions/modify_data_permission.ts
|
|
1177
|
+
var boss14 = boss_default.getInstance();
|
|
1178
|
+
var ModifyDataPermission = class _ModifyDataPermission extends Permission {
|
|
1179
|
+
static {
|
|
1180
|
+
this.className = "ModifyDataPermission";
|
|
1181
|
+
}
|
|
1182
|
+
static {
|
|
1183
|
+
this.DefaultName = "modify_data";
|
|
1184
|
+
}
|
|
1185
|
+
constructor(role, params, name = _ModifyDataPermission.DefaultName) {
|
|
1186
|
+
super(role, params, name);
|
|
1187
|
+
this.params = params;
|
|
1188
|
+
}
|
|
1189
|
+
static create(roleName, params, name = _ModifyDataPermission.DefaultName) {
|
|
1190
|
+
return new _ModifyDataPermission(this.makeLink(name, roleName), params, name);
|
|
1191
|
+
}
|
|
1192
|
+
static deserializeFromBOSS(serialized) {
|
|
1193
|
+
const params = omitBOSS(serialized, ["role", "name"]);
|
|
1194
|
+
return new _ModifyDataPermission(serialized.role, params, serialized.name);
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
boss14.register("ModifyDataPermission", ModifyDataPermission);
|
|
1198
|
+
|
|
1199
|
+
// src/models/permissions/change_number_permission.ts
|
|
1200
|
+
var boss15 = boss_default.getInstance();
|
|
1201
|
+
var ChangeNumberPermission = class _ChangeNumberPermission extends Permission {
|
|
1202
|
+
static {
|
|
1203
|
+
this.className = "ChangeNumberPermission";
|
|
1204
|
+
}
|
|
1205
|
+
static {
|
|
1206
|
+
this.DefaultName = "decrement_permission";
|
|
1207
|
+
}
|
|
1208
|
+
constructor(role, params, name = _ChangeNumberPermission.DefaultName) {
|
|
1209
|
+
super(role, params, name);
|
|
1210
|
+
this.params = params;
|
|
1211
|
+
}
|
|
1212
|
+
static create(roleName, params, name = _ChangeNumberPermission.DefaultName) {
|
|
1213
|
+
return new _ChangeNumberPermission(this.makeLink(name, roleName), params, name);
|
|
1214
|
+
}
|
|
1215
|
+
static deserializeFromBOSS(serialized) {
|
|
1216
|
+
const params = omitBOSS(serialized, ["role", "name"]);
|
|
1217
|
+
return new _ChangeNumberPermission(serialized.role, params, serialized.name);
|
|
1218
|
+
}
|
|
1219
|
+
};
|
|
1220
|
+
boss15.register("ChangeNumberPermission", ChangeNumberPermission);
|
|
1221
|
+
|
|
1222
|
+
// src/models/permissions/split_join_permission.ts
|
|
1223
|
+
var boss16 = boss_default.getInstance();
|
|
1224
|
+
var SplitJoinPermission = class _SplitJoinPermission extends Permission {
|
|
1225
|
+
static {
|
|
1226
|
+
this.className = "SplitJoinPermission";
|
|
1227
|
+
}
|
|
1228
|
+
static {
|
|
1229
|
+
this.DefaultName = "split_join";
|
|
1230
|
+
}
|
|
1231
|
+
constructor(role, params, name = _SplitJoinPermission.DefaultName) {
|
|
1232
|
+
super(role, params, name);
|
|
1233
|
+
this.params = params;
|
|
1234
|
+
}
|
|
1235
|
+
static create(roleName, params, name = _SplitJoinPermission.DefaultName) {
|
|
1236
|
+
return new _SplitJoinPermission(this.makeLink(name, roleName), params, name);
|
|
1237
|
+
}
|
|
1238
|
+
static deserializeFromBOSS(serialized) {
|
|
1239
|
+
const params = omitBOSS(serialized, ["role", "name"]);
|
|
1240
|
+
return new _SplitJoinPermission(serialized.role, params, serialized.name);
|
|
1241
|
+
}
|
|
1242
|
+
};
|
|
1243
|
+
boss16.register("SplitJoinPermission", SplitJoinPermission);
|
|
1244
|
+
|
|
1245
|
+
// src/network/node_connection.ts
|
|
1246
|
+
var import_unicrypto7 = require("unicrypto");
|
|
1247
|
+
var boss17 = boss_default.getInstance();
|
|
1248
|
+
var CONNECTION_TIMEOUT = 5e3;
|
|
1249
|
+
var CLIENT_VERSION = 3;
|
|
1250
|
+
var NodeConnection = class _NodeConnection {
|
|
1251
|
+
constructor(node, authKey, directConnection) {
|
|
1252
|
+
this.node = node;
|
|
1253
|
+
this.authKey = authKey;
|
|
1254
|
+
this.nodeURL = node.https;
|
|
1255
|
+
if (directConnection) this.nodeURL = node.http;
|
|
1256
|
+
}
|
|
1257
|
+
async connect() {
|
|
1258
|
+
const clientNonce = (0, import_unicrypto7.randomBytes)(47);
|
|
1259
|
+
const signatureOpts = { pssHash: "sha512" };
|
|
1260
|
+
console.log(`setting up protected connection to ${this.nodeURL}`);
|
|
1261
|
+
const clientKey = await this.authKey.publicKey.pack();
|
|
1262
|
+
const connectionData = await this.request("connect", {
|
|
1263
|
+
client_key: clientKey,
|
|
1264
|
+
client_version: CLIENT_VERSION
|
|
1265
|
+
}, { timeout: CONNECTION_TIMEOUT });
|
|
1266
|
+
this.sessionId = connectionData["session_id"];
|
|
1267
|
+
const serverVersion = connectionData["server_version"] || 1;
|
|
1268
|
+
this.version = Math.min(serverVersion, CLIENT_VERSION);
|
|
1269
|
+
const authData = boss17.dump({
|
|
1270
|
+
client_nonce: clientNonce,
|
|
1271
|
+
server_nonce: connectionData["server_nonce"],
|
|
1272
|
+
client_version: CLIENT_VERSION,
|
|
1273
|
+
server_version: serverVersion
|
|
1274
|
+
});
|
|
1275
|
+
const token = await this.request("get_token", {
|
|
1276
|
+
data: authData,
|
|
1277
|
+
signature: await this.authKey.sign(authData, signatureOpts),
|
|
1278
|
+
session_id: this.sessionId
|
|
1279
|
+
});
|
|
1280
|
+
const tokenData = token.data;
|
|
1281
|
+
const nodeKey = await this.node.getPublicKey();
|
|
1282
|
+
if (!nodeKey) throw new Error("Node key is undefined");
|
|
1283
|
+
const isVerified = await nodeKey.verify(
|
|
1284
|
+
tokenData,
|
|
1285
|
+
token.signature,
|
|
1286
|
+
signatureOpts
|
|
1287
|
+
);
|
|
1288
|
+
if (!isVerified) throw new Error("bad node signature");
|
|
1289
|
+
const params = boss17.load(tokenData);
|
|
1290
|
+
if ((0, import_unicrypto7.encode64)(clientNonce) !== (0, import_unicrypto7.encode64)(params["client_nonce"]))
|
|
1291
|
+
throw new Error("nonce mismatch, authentication failed");
|
|
1292
|
+
const decryptedTokenBin = await this.authKey.decrypt(params["encrypted_token"]);
|
|
1293
|
+
const decryptedToken = boss17.load(decryptedTokenBin);
|
|
1294
|
+
this.sessionKey = new import_unicrypto7.SymmetricKey({ keyBytes: decryptedToken.sk });
|
|
1295
|
+
const response = await this.command("hello");
|
|
1296
|
+
if (response.status !== "OK")
|
|
1297
|
+
throw new Error(`wrong status ${response.status}, authentication failed`);
|
|
1298
|
+
console.log(`connected, system says: ${response.message}`);
|
|
1299
|
+
return this;
|
|
1300
|
+
}
|
|
1301
|
+
async command(name, params = {}, requestOptions = {}) {
|
|
1302
|
+
if (!this.sessionKey || !this.version) throw new Error("not in session");
|
|
1303
|
+
const version = this.version;
|
|
1304
|
+
const sk = this.sessionKey;
|
|
1305
|
+
const data = boss17.dump({ command: name, params });
|
|
1306
|
+
let encryptedParams;
|
|
1307
|
+
if (version >= 2) encryptedParams = await sk.etaEncrypt(data);
|
|
1308
|
+
else encryptedParams = await sk.encrypt(data);
|
|
1309
|
+
const req = this.request("command", {
|
|
1310
|
+
command: "command",
|
|
1311
|
+
params: encryptedParams,
|
|
1312
|
+
session_id: this.sessionId
|
|
1313
|
+
}, requestOptions);
|
|
1314
|
+
return abortable(new Promise((resolve, reject) => {
|
|
1315
|
+
req.then(async (response) => {
|
|
1316
|
+
let decrypted;
|
|
1317
|
+
if (version >= 2) decrypted = await sk.etaDecrypt(response.result);
|
|
1318
|
+
else decrypted = await sk.decrypt(response.result);
|
|
1319
|
+
const result = boss17.load(decrypted);
|
|
1320
|
+
if (result.error) reject(result.error);
|
|
1321
|
+
else resolve(result.result);
|
|
1322
|
+
}).catch(reject);
|
|
1323
|
+
}), req);
|
|
1324
|
+
}
|
|
1325
|
+
request(path, params = {}, requestOptions = {}) {
|
|
1326
|
+
const url = `${this.nodeURL}/${path}`;
|
|
1327
|
+
const data = { requestData64: (0, import_unicrypto7.encode64)(boss17.dump(params)) };
|
|
1328
|
+
return _NodeConnection.request("POST", url, { data, ...requestOptions });
|
|
1329
|
+
}
|
|
1330
|
+
static request(method, url, options = {}) {
|
|
1331
|
+
const controller = new AbortController();
|
|
1332
|
+
let timeout;
|
|
1333
|
+
const promise = new Promise((resolve, reject) => {
|
|
1334
|
+
let headers = options.headers || {};
|
|
1335
|
+
if (method === "POST") headers = Object.assign({}, headers, {
|
|
1336
|
+
"Content-Type": "application/json"
|
|
1337
|
+
});
|
|
1338
|
+
let opts = {
|
|
1339
|
+
method,
|
|
1340
|
+
headers,
|
|
1341
|
+
signal: controller.signal
|
|
1342
|
+
};
|
|
1343
|
+
if (method === "POST") opts.body = JSON.stringify(options.data);
|
|
1344
|
+
const fetch = globalThis.fetch;
|
|
1345
|
+
if (typeof fetch !== "function") {
|
|
1346
|
+
reject(new Error("Fetch API is not available in this environment"));
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
fetch(url, opts).then((res) => {
|
|
1350
|
+
if (!res.ok) {
|
|
1351
|
+
throw new Error(`HTTP ${res.status}${res.statusText ? ` ${res.statusText}` : ""}`);
|
|
1352
|
+
}
|
|
1353
|
+
return res.blob();
|
|
1354
|
+
}).then(function(blob) {
|
|
1355
|
+
return blob.arrayBuffer();
|
|
1356
|
+
}).then(function(ab) {
|
|
1357
|
+
const response = new Uint8Array(ab);
|
|
1358
|
+
const answer = boss17.load(response);
|
|
1359
|
+
if (answer && answer.result === "ok") resolve(answer.response);
|
|
1360
|
+
else reject(answer);
|
|
1361
|
+
}).catch((err) => reject(err)).finally(() => {
|
|
1362
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
1363
|
+
});
|
|
1364
|
+
if (options.timeout) {
|
|
1365
|
+
timeout = setTimeout(() => {
|
|
1366
|
+
controller.abort();
|
|
1367
|
+
reject(new Error("Connection timed out"));
|
|
1368
|
+
}, options.timeout);
|
|
1369
|
+
}
|
|
1370
|
+
});
|
|
1371
|
+
return abortable(promise, controller);
|
|
1372
|
+
}
|
|
1373
|
+
static xchangeRequest(method, url, options = {}) {
|
|
1374
|
+
const controller = new AbortController();
|
|
1375
|
+
let timeout;
|
|
1376
|
+
const promise = new Promise((resolve, reject) => {
|
|
1377
|
+
let headers = options.headers || {};
|
|
1378
|
+
if (method === "POST") headers = Object.assign({}, headers, {
|
|
1379
|
+
"Content-Type": "application/json"
|
|
1380
|
+
});
|
|
1381
|
+
let opts = {
|
|
1382
|
+
method,
|
|
1383
|
+
headers,
|
|
1384
|
+
signal: controller.signal
|
|
1385
|
+
};
|
|
1386
|
+
if (method === "POST") opts.body = JSON.stringify(options.data);
|
|
1387
|
+
const fetch = globalThis.fetch;
|
|
1388
|
+
if (typeof fetch !== "function") {
|
|
1389
|
+
reject(new Error("Fetch API is not available in this environment"));
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
fetch(url, opts).then((res) => {
|
|
1393
|
+
if (!res.ok) {
|
|
1394
|
+
throw new Error(`HTTP ${res.status}${res.statusText ? ` ${res.statusText}` : ""}`);
|
|
1395
|
+
}
|
|
1396
|
+
return res.json();
|
|
1397
|
+
}).then(resolve).catch((err) => reject(err)).finally(() => {
|
|
1398
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
1399
|
+
});
|
|
1400
|
+
if (options.timeout) {
|
|
1401
|
+
timeout = setTimeout(() => {
|
|
1402
|
+
controller.abort();
|
|
1403
|
+
reject(new Error("Connection timed out"));
|
|
1404
|
+
}, options.timeout);
|
|
1405
|
+
}
|
|
1406
|
+
});
|
|
1407
|
+
return abortable(promise, controller);
|
|
1408
|
+
}
|
|
1409
|
+
};
|
|
1410
|
+
|
|
1411
|
+
// src/network/node.ts
|
|
1412
|
+
var import_unicrypto8 = require("unicrypto");
|
|
1413
|
+
var boss18 = boss_default.getInstance();
|
|
1414
|
+
function rewriteProtocol(value, protocol) {
|
|
1415
|
+
const url = new URL(value);
|
|
1416
|
+
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
1417
|
+
throw new Error(`unsupported node URL protocol: ${url.protocol}`);
|
|
1418
|
+
const oldPort = url.port;
|
|
1419
|
+
url.protocol = protocol;
|
|
1420
|
+
if (protocol === "https:" && oldPort === "8080") url.port = "443";
|
|
1421
|
+
if (protocol === "http:" && oldPort === "443") url.port = "8080";
|
|
1422
|
+
return url.toString().replace(/\/$/, "");
|
|
1423
|
+
}
|
|
1424
|
+
function validateNodeURL(value, nodeName, field) {
|
|
1425
|
+
try {
|
|
1426
|
+
const url = new URL(value);
|
|
1427
|
+
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
1428
|
+
throw new Error(`unsupported protocol ${url.protocol}`);
|
|
1429
|
+
} catch (error) {
|
|
1430
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1431
|
+
throw new Error(`invalid node ${nodeName}: invalid ${field} URL ${value} (${reason})`);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
var forceHTTPS = (url) => rewriteProtocol(url, "https:");
|
|
1435
|
+
var forceHTTP = (url) => rewriteProtocol(url, "http:");
|
|
1436
|
+
var GET_TOPOLOGY_TIMEOUT = 1e3;
|
|
1437
|
+
function difference(setA, setB) {
|
|
1438
|
+
let _difference = new Set(setA);
|
|
1439
|
+
setB.forEach((elem) => _difference.delete(elem));
|
|
1440
|
+
return _difference;
|
|
1441
|
+
}
|
|
1442
|
+
function isEqual(setA, setB) {
|
|
1443
|
+
if (setA.size !== setB.size) return false;
|
|
1444
|
+
const diff = difference(setA, setB);
|
|
1445
|
+
return diff.size === 0;
|
|
1446
|
+
}
|
|
1447
|
+
var Node = class {
|
|
1448
|
+
constructor(info) {
|
|
1449
|
+
if (!info || typeof info !== "object") throw new Error("invalid node: expected an object");
|
|
1450
|
+
if (typeof info.name !== "string" || info.name.length === 0)
|
|
1451
|
+
throw new Error("invalid node: name must be a non-empty string");
|
|
1452
|
+
if (!Number.isInteger(info.number) || info.number < 0)
|
|
1453
|
+
throw new Error(`invalid node ${info.name}: number must be a non-negative integer`);
|
|
1454
|
+
if (!Array.isArray(info.domain_urls) || info.domain_urls.length === 0 || info.domain_urls.some((url) => typeof url !== "string" || url.length === 0))
|
|
1455
|
+
throw new Error(`invalid node ${info.name}: domain_urls must be a non-empty string array`);
|
|
1456
|
+
if (!Array.isArray(info.direct_urls) || info.direct_urls.some((url) => typeof url !== "string" || url.length === 0))
|
|
1457
|
+
throw new Error(`invalid node ${info.name}: direct_urls must be a string array`);
|
|
1458
|
+
if (!(typeof info.key === "string" && info.key.length > 0) && !(info.key instanceof Uint8Array))
|
|
1459
|
+
throw new Error(`invalid node ${info.name}: key must be base64 or Uint8Array`);
|
|
1460
|
+
info.domain_urls.forEach((url) => validateNodeURL(url, info.name, "domain_urls"));
|
|
1461
|
+
info.direct_urls.forEach((url) => validateNodeURL(url, info.name, "direct_urls"));
|
|
1462
|
+
this.name = info.name;
|
|
1463
|
+
this.number = info.number;
|
|
1464
|
+
this.domainURLs = new Set(info.domain_urls);
|
|
1465
|
+
this.directURLs = new Set(info.direct_urls);
|
|
1466
|
+
let keyBIN = info.key;
|
|
1467
|
+
if (typeof keyBIN === "string") keyBIN = (0, import_unicrypto8.decode64)(keyBIN);
|
|
1468
|
+
this.keyBIN = keyBIN;
|
|
1469
|
+
const self = this;
|
|
1470
|
+
this.ready = import_unicrypto8.PublicKey.unpack(this.keyBIN).then((key) => {
|
|
1471
|
+
self.key = key;
|
|
1472
|
+
self.id = (0, import_unicrypto8.encode64)(key.fingerprint);
|
|
1473
|
+
});
|
|
1474
|
+
const domainURL = this.domainURLs.values().next().value;
|
|
1475
|
+
if (this.directURLs.size)
|
|
1476
|
+
this.http = this.directURLs.values().next().value;
|
|
1477
|
+
else
|
|
1478
|
+
this.http = forceHTTP(domainURL);
|
|
1479
|
+
this.https = forceHTTPS(domainURL);
|
|
1480
|
+
}
|
|
1481
|
+
async getId() {
|
|
1482
|
+
await this.ready;
|
|
1483
|
+
return this.id;
|
|
1484
|
+
}
|
|
1485
|
+
async getPublicKey() {
|
|
1486
|
+
await this.ready;
|
|
1487
|
+
return this.key;
|
|
1488
|
+
}
|
|
1489
|
+
async equals(node) {
|
|
1490
|
+
await this.ready;
|
|
1491
|
+
if (node.name !== this.name) return false;
|
|
1492
|
+
if (node.number !== this.number) return false;
|
|
1493
|
+
if (await node.getId() !== await this.getId()) return false;
|
|
1494
|
+
if (!isEqual(this.domainURLs, node.domainURLs)) return false;
|
|
1495
|
+
if (!isEqual(this.directURLs, node.directURLs)) return false;
|
|
1496
|
+
return true;
|
|
1497
|
+
}
|
|
1498
|
+
info() {
|
|
1499
|
+
return {
|
|
1500
|
+
name: this.name,
|
|
1501
|
+
number: this.number,
|
|
1502
|
+
domain_urls: Array.from(this.domainURLs),
|
|
1503
|
+
direct_urls: Array.from(this.directURLs),
|
|
1504
|
+
key: (0, import_unicrypto8.encode64)(this.keyBIN)
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
async getTopology(directConnection) {
|
|
1508
|
+
await this.ready;
|
|
1509
|
+
let url = this.https;
|
|
1510
|
+
if (directConnection) url = this.http;
|
|
1511
|
+
const resp = await NodeConnection.request("GET", `${url}/topology`, {
|
|
1512
|
+
timeout: GET_TOPOLOGY_TIMEOUT
|
|
1513
|
+
});
|
|
1514
|
+
const { signature, packed_data: packed } = resp;
|
|
1515
|
+
if (!this.key) throw new Error("node initialization failed (key is undefined)");
|
|
1516
|
+
const isVerified = await this.key.verifyExtended(signature, packed);
|
|
1517
|
+
if (!isVerified) throw new Error("node signature mismatch");
|
|
1518
|
+
return boss18.load(packed);
|
|
1519
|
+
}
|
|
1520
|
+
};
|
|
1521
|
+
|
|
1522
|
+
// src/network/topology.ts
|
|
1523
|
+
var randomIndex = (arrayLength) => ~~(arrayLength * Math.random());
|
|
1524
|
+
var Topology = class _Topology {
|
|
1525
|
+
constructor(nodes, updatedAt) {
|
|
1526
|
+
this.nodes = nodes;
|
|
1527
|
+
this.updatedAt = updatedAt;
|
|
1528
|
+
}
|
|
1529
|
+
async update(directConnection) {
|
|
1530
|
+
const trustLevel = 0.4;
|
|
1531
|
+
const self = this;
|
|
1532
|
+
const confirmed = {};
|
|
1533
|
+
const failed = {};
|
|
1534
|
+
;
|
|
1535
|
+
const stats = {};
|
|
1536
|
+
async function addStats(nodes) {
|
|
1537
|
+
const queue = nodes.map(async (node) => {
|
|
1538
|
+
const nodeId = await node.getId();
|
|
1539
|
+
if (!nodeId) return;
|
|
1540
|
+
if (!stats[nodeId]) stats[nodeId] = [];
|
|
1541
|
+
let found = false;
|
|
1542
|
+
const queue2 = stats[nodeId].map(async (stat, i) => {
|
|
1543
|
+
if (await stat.node.equals(node)) {
|
|
1544
|
+
found = true;
|
|
1545
|
+
stats[nodeId][i].count++;
|
|
1546
|
+
}
|
|
1547
|
+
});
|
|
1548
|
+
return Promise.all(queue2).then(() => {
|
|
1549
|
+
if (!found) stats[nodeId].push({ count: 1, node });
|
|
1550
|
+
});
|
|
1551
|
+
});
|
|
1552
|
+
return Promise.all(queue);
|
|
1553
|
+
}
|
|
1554
|
+
function buildFinal(trustLevel2) {
|
|
1555
|
+
const nodes = {};
|
|
1556
|
+
const confirmedCount = Object.keys(confirmed).length;
|
|
1557
|
+
for (var id2 in stats) {
|
|
1558
|
+
const list = stats[id2];
|
|
1559
|
+
let trusted;
|
|
1560
|
+
if (trustLevel2 !== 0)
|
|
1561
|
+
trusted = list.find((n) => confirmedCount > 0 && n.count / confirmedCount >= trustLevel2);
|
|
1562
|
+
else {
|
|
1563
|
+
const maximum = Math.max.apply(null, list.map((n) => n.count));
|
|
1564
|
+
trusted = list.find((n) => n.count === maximum);
|
|
1565
|
+
}
|
|
1566
|
+
if (trusted) nodes[id2] = trusted.node;
|
|
1567
|
+
}
|
|
1568
|
+
return nodes;
|
|
1569
|
+
}
|
|
1570
|
+
function colloquium(trustLevel2, nodesToAsk) {
|
|
1571
|
+
return new Promise((resolve, reject) => {
|
|
1572
|
+
let Nt = Math.ceil(self.size() * trustLevel2);
|
|
1573
|
+
if (Nt < 1) Nt = 1;
|
|
1574
|
+
let resultFound = false;
|
|
1575
|
+
const source = nodesToAsk || self.nodes;
|
|
1576
|
+
const ids = Object.keys(source);
|
|
1577
|
+
function success() {
|
|
1578
|
+
if (resultFound) return;
|
|
1579
|
+
resultFound = true;
|
|
1580
|
+
resolve({ failed, confirmed });
|
|
1581
|
+
}
|
|
1582
|
+
function failure(err) {
|
|
1583
|
+
if (resultFound) return;
|
|
1584
|
+
resultFound = true;
|
|
1585
|
+
reject(err);
|
|
1586
|
+
}
|
|
1587
|
+
function processNext() {
|
|
1588
|
+
if (resultFound) return;
|
|
1589
|
+
const id2 = ids.pop();
|
|
1590
|
+
id2 && processNode(id2);
|
|
1591
|
+
}
|
|
1592
|
+
async function processNode(id2) {
|
|
1593
|
+
if (resultFound) return;
|
|
1594
|
+
try {
|
|
1595
|
+
const response = await source[id2].getTopology(directConnection);
|
|
1596
|
+
await processResponse(id2, response);
|
|
1597
|
+
} catch (err) {
|
|
1598
|
+
failed[id2] = true;
|
|
1599
|
+
if (ids.length > 0) processNext();
|
|
1600
|
+
else failure(new Error("confirmed responses < 40%"));
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
async function processResponse(id2, resp) {
|
|
1604
|
+
if (resultFound) return;
|
|
1605
|
+
const responseNodes = resp.nodes;
|
|
1606
|
+
const responseTopology = await _Topology.load({
|
|
1607
|
+
list: responseNodes,
|
|
1608
|
+
updated: Math.floor(Date.now() / 1e3)
|
|
1609
|
+
});
|
|
1610
|
+
await addStats(Object.values(responseTopology.nodes));
|
|
1611
|
+
confirmed[id2] = resp;
|
|
1612
|
+
delete failed[id2];
|
|
1613
|
+
if (Object.keys(confirmed).length >= Nt) success();
|
|
1614
|
+
}
|
|
1615
|
+
for (var i = 0; i < Nt; i++) {
|
|
1616
|
+
const idToProcess = ids.pop();
|
|
1617
|
+
idToProcess && processNode(idToProcess);
|
|
1618
|
+
}
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
try {
|
|
1622
|
+
await colloquium(0.4);
|
|
1623
|
+
} catch (err) {
|
|
1624
|
+
const tempResult = buildFinal(0);
|
|
1625
|
+
for (var id in tempResult) if (!failed[id]) delete tempResult[id];
|
|
1626
|
+
await colloquium(0.4, tempResult);
|
|
1627
|
+
}
|
|
1628
|
+
this.nodes = buildFinal(0.9);
|
|
1629
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
1630
|
+
}
|
|
1631
|
+
size() {
|
|
1632
|
+
return Object.keys(this.nodes).length;
|
|
1633
|
+
}
|
|
1634
|
+
pack() {
|
|
1635
|
+
const list = [];
|
|
1636
|
+
for (var id in this.nodes) list.push(this.nodes[id].info());
|
|
1637
|
+
const updatedSec = (this.updatedAt.getTime() / 1e3).toString();
|
|
1638
|
+
return {
|
|
1639
|
+
list,
|
|
1640
|
+
updated: parseInt(updatedSec)
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
getRandomNode() {
|
|
1644
|
+
return this.getNode(this.getRandomNodeId());
|
|
1645
|
+
}
|
|
1646
|
+
getRandomNodeId() {
|
|
1647
|
+
const ids = Object.keys(this.nodes);
|
|
1648
|
+
return ids[randomIndex(ids.length)];
|
|
1649
|
+
}
|
|
1650
|
+
getNode(id) {
|
|
1651
|
+
return this.nodes[id];
|
|
1652
|
+
}
|
|
1653
|
+
static async load(input) {
|
|
1654
|
+
const packed = input;
|
|
1655
|
+
const isRawList = Array.isArray(packed);
|
|
1656
|
+
if (!isRawList && (!packed || typeof packed !== "object" || !Array.isArray(packed.list)))
|
|
1657
|
+
throw new Error("invalid topology: expected a node array or an object with a list array");
|
|
1658
|
+
const list = isRawList ? packed : packed.list;
|
|
1659
|
+
if (list.length === 0) throw new Error("invalid topology: node list is empty");
|
|
1660
|
+
let updatedAt;
|
|
1661
|
+
if (isRawList) {
|
|
1662
|
+
updatedAt = /* @__PURE__ */ new Date();
|
|
1663
|
+
} else {
|
|
1664
|
+
if (typeof packed.updated !== "number" || !Number.isFinite(packed.updated) || packed.updated < 0)
|
|
1665
|
+
throw new Error("invalid topology: updated must be a non-negative Unix timestamp");
|
|
1666
|
+
updatedAt = new Date(packed.updated * 1e3);
|
|
1667
|
+
}
|
|
1668
|
+
const nodes = {};
|
|
1669
|
+
const loaded = await Promise.all(list.map(async (info) => {
|
|
1670
|
+
const node = new Node(info);
|
|
1671
|
+
const nodeId = await node.getId();
|
|
1672
|
+
if (!nodeId) throw new Error("invalid node");
|
|
1673
|
+
return { node, nodeId };
|
|
1674
|
+
}));
|
|
1675
|
+
const names = /* @__PURE__ */ new Set();
|
|
1676
|
+
const numbers = /* @__PURE__ */ new Set();
|
|
1677
|
+
for (const { node, nodeId } of loaded) {
|
|
1678
|
+
if (nodes[nodeId]) throw new Error(`invalid topology: duplicate node key (${node.name})`);
|
|
1679
|
+
if (names.has(node.name)) throw new Error(`invalid topology: duplicate node name ${node.name}`);
|
|
1680
|
+
if (numbers.has(node.number)) throw new Error(`invalid topology: duplicate node number ${node.number}`);
|
|
1681
|
+
nodes[nodeId] = node;
|
|
1682
|
+
names.add(node.name);
|
|
1683
|
+
numbers.add(node.number);
|
|
1684
|
+
}
|
|
1685
|
+
return new _Topology(nodes, updatedAt);
|
|
1686
|
+
}
|
|
1687
|
+
};
|
|
1688
|
+
|
|
1689
|
+
// src/network/estimator.ts
|
|
1690
|
+
var Estimator = class {
|
|
1691
|
+
#sx;
|
|
1692
|
+
#sx2;
|
|
1693
|
+
#n;
|
|
1694
|
+
constructor() {
|
|
1695
|
+
this.#sx = 0;
|
|
1696
|
+
this.#sx2 = 0;
|
|
1697
|
+
this.#n = 0;
|
|
1698
|
+
}
|
|
1699
|
+
/**
|
|
1700
|
+
* Current number of samples
|
|
1701
|
+
*/
|
|
1702
|
+
get count() {
|
|
1703
|
+
return this.#n;
|
|
1704
|
+
}
|
|
1705
|
+
/**
|
|
1706
|
+
* current mean value
|
|
1707
|
+
*/
|
|
1708
|
+
get mean() {
|
|
1709
|
+
return this.#sx / this.#n;
|
|
1710
|
+
}
|
|
1711
|
+
/**
|
|
1712
|
+
* Current Sample Variance estimation, e.g. 𝛔²(n-1)
|
|
1713
|
+
*/
|
|
1714
|
+
get s2() {
|
|
1715
|
+
const n = this.#n;
|
|
1716
|
+
const m = this.mean;
|
|
1717
|
+
return (this.#sx2 - n * m * m) / (n - 1);
|
|
1718
|
+
}
|
|
1719
|
+
/**
|
|
1720
|
+
* Estimated standard deviation, √𝛔² (square root of sample variance estimation)
|
|
1721
|
+
*/
|
|
1722
|
+
get stdev() {
|
|
1723
|
+
return Math.sqrt(this.s2);
|
|
1724
|
+
}
|
|
1725
|
+
/**
|
|
1726
|
+
* Add next sample to series.
|
|
1727
|
+
*
|
|
1728
|
+
* @param x value to add.
|
|
1729
|
+
*/
|
|
1730
|
+
addSample(x) {
|
|
1731
|
+
this.#sx += x;
|
|
1732
|
+
this.#sx2 += x * x;
|
|
1733
|
+
this.#n++;
|
|
1734
|
+
}
|
|
1735
|
+
/**
|
|
1736
|
+
* clear current series so it can restart calulation
|
|
1737
|
+
*/
|
|
1738
|
+
clear() {
|
|
1739
|
+
this.#sx = this.#sx2 = this.#n = 0;
|
|
1740
|
+
}
|
|
1741
|
+
toString() {
|
|
1742
|
+
return `(${this.mean} \xB1 ${this.stdev.toPrecision(3)})`;
|
|
1743
|
+
}
|
|
1744
|
+
};
|
|
1745
|
+
|
|
1746
|
+
// src/network/index.ts
|
|
1747
|
+
var import_unicrypto9 = require("unicrypto");
|
|
1748
|
+
var boss19 = boss_default.getInstance();
|
|
1749
|
+
var CHECK_CONTRACT_TIMEOUT = 2e3;
|
|
1750
|
+
function createHashId(id) {
|
|
1751
|
+
return {
|
|
1752
|
+
__type: "HashId",
|
|
1753
|
+
composite3: id
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
function sleep(ms) {
|
|
1757
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1758
|
+
}
|
|
1759
|
+
var Network = class {
|
|
1760
|
+
constructor(privateKey, options) {
|
|
1761
|
+
this.options = options || {};
|
|
1762
|
+
this.connections = {};
|
|
1763
|
+
this.topologyKey = this.options.topologyKey || "__universa_topology";
|
|
1764
|
+
this.directConnection = this.options.directConnection || false;
|
|
1765
|
+
this.readyState = "pending";
|
|
1766
|
+
this.connecting = null;
|
|
1767
|
+
this.resetReady();
|
|
1768
|
+
this.authKey = privateKey;
|
|
1769
|
+
this.timeOffset = null;
|
|
1770
|
+
this.timeUpdatedAt = null;
|
|
1771
|
+
}
|
|
1772
|
+
resetReady() {
|
|
1773
|
+
this.readyState = "pending";
|
|
1774
|
+
this.ready = new Promise((resolve, reject) => {
|
|
1775
|
+
this.setReady = resolve;
|
|
1776
|
+
this.rejectReady = reject;
|
|
1777
|
+
});
|
|
1778
|
+
this.ready.catch(() => void 0);
|
|
1779
|
+
}
|
|
1780
|
+
size() {
|
|
1781
|
+
if (this.topology) return this.topology.size();
|
|
1782
|
+
throw new Error("You need to connect before accessing size");
|
|
1783
|
+
}
|
|
1784
|
+
async getLastTopology() {
|
|
1785
|
+
if (typeof window !== "undefined") {
|
|
1786
|
+
const bin = localStorage.getItem(this.topologyKey);
|
|
1787
|
+
if (bin) {
|
|
1788
|
+
return Topology.load(boss19.load((0, import_unicrypto9.decode64)(bin)));
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
if (this.options.topology) return this.options.topology;
|
|
1792
|
+
if (this.options.topologyFile) {
|
|
1793
|
+
const packed = await readJSON(this.options.topologyFile);
|
|
1794
|
+
return Topology.load(packed);
|
|
1795
|
+
}
|
|
1796
|
+
throw new Error(
|
|
1797
|
+
"No default topology is bundled in this alpha; provide options.topology or options.topologyFile"
|
|
1798
|
+
);
|
|
1799
|
+
}
|
|
1800
|
+
saveNewTopology() {
|
|
1801
|
+
if (typeof window === "undefined") return;
|
|
1802
|
+
if (!this.topology) throw new Error("Can't save undefined topology");
|
|
1803
|
+
const packed = this.topology.pack();
|
|
1804
|
+
localStorage.setItem(this.topologyKey, (0, import_unicrypto9.encode64)(boss19.dump(packed)));
|
|
1805
|
+
}
|
|
1806
|
+
connect() {
|
|
1807
|
+
if (this.connecting) return this.connecting;
|
|
1808
|
+
if (this.readyState !== "pending") this.resetReady();
|
|
1809
|
+
this.connecting = this.performConnect();
|
|
1810
|
+
return this.connecting;
|
|
1811
|
+
}
|
|
1812
|
+
async performConnect() {
|
|
1813
|
+
try {
|
|
1814
|
+
this.topology = await this.getLastTopology();
|
|
1815
|
+
await this.topology.update(this.directConnection);
|
|
1816
|
+
this.saveNewTopology();
|
|
1817
|
+
if (this.timeOffset === null) {
|
|
1818
|
+
try {
|
|
1819
|
+
await this.loadNetworkTime();
|
|
1820
|
+
} catch (err) {
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
this.readyState = "fulfilled";
|
|
1824
|
+
this.setReady();
|
|
1825
|
+
return this;
|
|
1826
|
+
} catch (err) {
|
|
1827
|
+
this.readyState = "rejected";
|
|
1828
|
+
this.rejectReady(err);
|
|
1829
|
+
throw err;
|
|
1830
|
+
} finally {
|
|
1831
|
+
this.connecting = null;
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
async nodeConnection(nodeId) {
|
|
1835
|
+
if (!this.topology)
|
|
1836
|
+
throw new Error("can't establish connection without topology");
|
|
1837
|
+
const node = this.topology.getNode(nodeId);
|
|
1838
|
+
if (this.connections[nodeId]) return this.connections[nodeId];
|
|
1839
|
+
await this.ready;
|
|
1840
|
+
const connection = new NodeConnection(node, this.authKey, this.directConnection);
|
|
1841
|
+
await connection.connect();
|
|
1842
|
+
this.connections[nodeId] = connection;
|
|
1843
|
+
return connection;
|
|
1844
|
+
}
|
|
1845
|
+
async getRandomConnection() {
|
|
1846
|
+
await this.ready;
|
|
1847
|
+
if (!this.topology)
|
|
1848
|
+
throw new Error("can't establish connection without topology");
|
|
1849
|
+
return this.nodeConnection(this.topology.getRandomNodeId());
|
|
1850
|
+
}
|
|
1851
|
+
command(name, options, connection, requestOptions) {
|
|
1852
|
+
let req, conn;
|
|
1853
|
+
const run = async () => {
|
|
1854
|
+
conn = connection || await this.getRandomConnection();
|
|
1855
|
+
try {
|
|
1856
|
+
req = conn.command(name, options, requestOptions);
|
|
1857
|
+
return await req;
|
|
1858
|
+
} catch (error) {
|
|
1859
|
+
if (!connection) {
|
|
1860
|
+
const nodeId = await conn.node.getId();
|
|
1861
|
+
if (nodeId) delete this.connections[nodeId];
|
|
1862
|
+
}
|
|
1863
|
+
throw error;
|
|
1864
|
+
}
|
|
1865
|
+
};
|
|
1866
|
+
return abortable(retry(run, {
|
|
1867
|
+
attempts: 5,
|
|
1868
|
+
interval: 1e3,
|
|
1869
|
+
onError: (e) => console.log(e, ` send command again`)
|
|
1870
|
+
}), () => req);
|
|
1871
|
+
}
|
|
1872
|
+
getState(id, connection, requestOptions) {
|
|
1873
|
+
let itemId;
|
|
1874
|
+
if (typeof id === "string") itemId = (0, import_unicrypto9.decode64)(id);
|
|
1875
|
+
else if (id.constructor.name === "HashId") itemId = id.composite3;
|
|
1876
|
+
else itemId = id;
|
|
1877
|
+
const hashId2 = createHashId(itemId);
|
|
1878
|
+
return this.command("getState", { itemId: hashId2 }, connection, requestOptions || {});
|
|
1879
|
+
}
|
|
1880
|
+
checkParcel(id) {
|
|
1881
|
+
let itemId;
|
|
1882
|
+
if (typeof id === "string") itemId = (0, import_unicrypto9.decode64)(id);
|
|
1883
|
+
else itemId = id;
|
|
1884
|
+
const hashId2 = createHashId(itemId);
|
|
1885
|
+
return this.command("getParcelProcessingState", { parcelId: hashId2 });
|
|
1886
|
+
}
|
|
1887
|
+
checkContract(id, trustLevel) {
|
|
1888
|
+
const self = this;
|
|
1889
|
+
let itemId;
|
|
1890
|
+
if (typeof id === "string") itemId = (0, import_unicrypto9.decode64)(id);
|
|
1891
|
+
else if (id.constructor.name === "HashId") itemId = id.composite3;
|
|
1892
|
+
else itemId = id;
|
|
1893
|
+
return new Promise((resolve, reject) => {
|
|
1894
|
+
let tLevel = trustLevel;
|
|
1895
|
+
if (tLevel > 0.9) tLevel = 0.9;
|
|
1896
|
+
const size = self.size();
|
|
1897
|
+
if (!size) throw new Error("missing topology");
|
|
1898
|
+
let Nt = Math.ceil(size * tLevel);
|
|
1899
|
+
if (Nt < 1) Nt = 1;
|
|
1900
|
+
const N10 = Math.floor(size * 0.1) + 1;
|
|
1901
|
+
const Nn = Math.max(Nt + 1, N10);
|
|
1902
|
+
let resultFound = false;
|
|
1903
|
+
let createdAtList = [];
|
|
1904
|
+
let positive = 0;
|
|
1905
|
+
let negative = 0;
|
|
1906
|
+
let states = {};
|
|
1907
|
+
let isTestnet;
|
|
1908
|
+
const requests = [];
|
|
1909
|
+
if (!self.topology) throw new Error("missing topology");
|
|
1910
|
+
const ids = Object.keys(self.topology.nodes);
|
|
1911
|
+
const isPending = (state) => state.indexOf("PENDING") === 0 || state.indexOf("LOCKED") === 0;
|
|
1912
|
+
function success(status) {
|
|
1913
|
+
if (resultFound) return;
|
|
1914
|
+
resultFound = true;
|
|
1915
|
+
if (!status) return resolve(null);
|
|
1916
|
+
const createdAtMs = createdAtList.map((d) => d.getTime());
|
|
1917
|
+
const meanT = createdAtMs.reduce((p, n) => p + n) / createdAtMs.length;
|
|
1918
|
+
const meanTime = new Date(meanT);
|
|
1919
|
+
const estimator = new Estimator();
|
|
1920
|
+
createdAtMs.forEach((c) => estimator.addSample(c / 1e3));
|
|
1921
|
+
const result = {
|
|
1922
|
+
id: itemId,
|
|
1923
|
+
meanTime,
|
|
1924
|
+
stdevSeconds: estimator.stdev,
|
|
1925
|
+
trustLevel: Nn / size
|
|
1926
|
+
};
|
|
1927
|
+
resolve(result);
|
|
1928
|
+
}
|
|
1929
|
+
function failure(err) {
|
|
1930
|
+
if (resultFound) return;
|
|
1931
|
+
resultFound = true;
|
|
1932
|
+
reject(err);
|
|
1933
|
+
}
|
|
1934
|
+
function processNext() {
|
|
1935
|
+
if (!resultFound && ids.length > 0) {
|
|
1936
|
+
const id2 = ids.pop();
|
|
1937
|
+
id2 && processNode(id2);
|
|
1938
|
+
} else failure(new Error("not enough responses to find consensus"));
|
|
1939
|
+
}
|
|
1940
|
+
function processVote(itemResult, nodeId) {
|
|
1941
|
+
if (resultFound) return;
|
|
1942
|
+
const { state } = itemResult;
|
|
1943
|
+
if (isPending(state)) return ids.unshift(nodeId);
|
|
1944
|
+
if (!states[state]) states[state] = 1;
|
|
1945
|
+
else states[state] += 1;
|
|
1946
|
+
if (state === "APPROVED") {
|
|
1947
|
+
positive++;
|
|
1948
|
+
if (itemResult.createdAt) createdAtList.push(itemResult.createdAt);
|
|
1949
|
+
if (positive >= Nt) return success(true);
|
|
1950
|
+
} else {
|
|
1951
|
+
negative++;
|
|
1952
|
+
if (negative >= N10) return success(false);
|
|
1953
|
+
}
|
|
1954
|
+
if (positive + negative >= Nt) processNext();
|
|
1955
|
+
}
|
|
1956
|
+
async function processNode(nodeId) {
|
|
1957
|
+
if (resultFound) return;
|
|
1958
|
+
try {
|
|
1959
|
+
const conn = await self.nodeConnection(nodeId);
|
|
1960
|
+
if (resultFound) return;
|
|
1961
|
+
const req = self.getState(id, conn, {
|
|
1962
|
+
timeout: CHECK_CONTRACT_TIMEOUT
|
|
1963
|
+
});
|
|
1964
|
+
requests.push(req);
|
|
1965
|
+
const response = await req;
|
|
1966
|
+
const { itemResult } = response;
|
|
1967
|
+
isTestnet = itemResult.isTestnet;
|
|
1968
|
+
processVote(itemResult, nodeId);
|
|
1969
|
+
} catch (err) {
|
|
1970
|
+
console.log("On check contract: ", err);
|
|
1971
|
+
if (ids.length > 0) processNext();
|
|
1972
|
+
else failure(new Error("not enough responses"));
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
for (let i = 0; i < Nt; i++) processNext();
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1978
|
+
isApprovedExtended(id, trustLevel, onNodeResponse) {
|
|
1979
|
+
const self = this;
|
|
1980
|
+
return new Promise((resolve, reject) => {
|
|
1981
|
+
let tLevel = trustLevel;
|
|
1982
|
+
if (tLevel > 0.9) tLevel = 0.9;
|
|
1983
|
+
const size = self.size();
|
|
1984
|
+
if (!size) throw new Error("missing topology");
|
|
1985
|
+
let Nt = Math.ceil(size * tLevel);
|
|
1986
|
+
if (Nt < 1) Nt = 1;
|
|
1987
|
+
const N10 = Math.floor(size * 0.1) + 1;
|
|
1988
|
+
const Nn = Math.max(Nt + 1, N10);
|
|
1989
|
+
let resultFound = false;
|
|
1990
|
+
let positive = 0;
|
|
1991
|
+
let negative = 0;
|
|
1992
|
+
let states = {};
|
|
1993
|
+
let isTestnet;
|
|
1994
|
+
const requests = [];
|
|
1995
|
+
if (!self.topology) throw new Error("missing topology");
|
|
1996
|
+
const ids = Object.keys(self.topology.nodes);
|
|
1997
|
+
const isPending = (state) => state.indexOf("PENDING") === 0 || state.indexOf("LOCKED") === 0;
|
|
1998
|
+
function success(status) {
|
|
1999
|
+
if (resultFound) return;
|
|
2000
|
+
resultFound = true;
|
|
2001
|
+
resolve({
|
|
2002
|
+
isApproved: status,
|
|
2003
|
+
isTestnet,
|
|
2004
|
+
states
|
|
2005
|
+
});
|
|
2006
|
+
}
|
|
2007
|
+
function failure(err) {
|
|
2008
|
+
if (resultFound) return;
|
|
2009
|
+
resultFound = true;
|
|
2010
|
+
reject(err);
|
|
2011
|
+
}
|
|
2012
|
+
function processNext() {
|
|
2013
|
+
if (!resultFound && ids.length > 0) {
|
|
2014
|
+
const id2 = ids.pop();
|
|
2015
|
+
id2 && processNode(id2);
|
|
2016
|
+
} else failure(new Error("not enough responses to find consensus"));
|
|
2017
|
+
}
|
|
2018
|
+
function processVote(state, nodeId) {
|
|
2019
|
+
if (resultFound) return;
|
|
2020
|
+
if (isPending(state)) return ids.unshift(nodeId);
|
|
2021
|
+
if (!states[state]) states[state] = 1;
|
|
2022
|
+
else states[state] += 1;
|
|
2023
|
+
if (state === "APPROVED") {
|
|
2024
|
+
positive++;
|
|
2025
|
+
if (positive >= Nt) return success(true);
|
|
2026
|
+
} else {
|
|
2027
|
+
negative++;
|
|
2028
|
+
if (negative >= N10) return success(false);
|
|
2029
|
+
}
|
|
2030
|
+
if (positive + negative >= Nt) processNext();
|
|
2031
|
+
}
|
|
2032
|
+
async function processNode(nodeId) {
|
|
2033
|
+
if (resultFound) return;
|
|
2034
|
+
try {
|
|
2035
|
+
const conn = await self.nodeConnection(nodeId);
|
|
2036
|
+
if (resultFound) return;
|
|
2037
|
+
const req = self.getState(id, conn, {
|
|
2038
|
+
timeout: CHECK_CONTRACT_TIMEOUT
|
|
2039
|
+
});
|
|
2040
|
+
requests.push(req);
|
|
2041
|
+
const response = await req;
|
|
2042
|
+
const { itemResult } = response;
|
|
2043
|
+
if (onNodeResponse) onNodeResponse({ nodeId, itemResult });
|
|
2044
|
+
isTestnet = itemResult.isTestnet;
|
|
2045
|
+
processVote(itemResult.state, nodeId);
|
|
2046
|
+
} catch (err) {
|
|
2047
|
+
console.log("On check contract: ", err);
|
|
2048
|
+
if (ids.length > 0) processNext();
|
|
2049
|
+
else failure(new Error("not enough responses"));
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
for (let i = 0; i < Nt; i++) processNext();
|
|
2053
|
+
});
|
|
2054
|
+
}
|
|
2055
|
+
isApproved(id, trustLevel) {
|
|
2056
|
+
return this.isApprovedExtended(id, trustLevel).then((result) => result.isApproved);
|
|
2057
|
+
}
|
|
2058
|
+
now() {
|
|
2059
|
+
if (this.timeOffset === null)
|
|
2060
|
+
throw new Error("you should load network time before");
|
|
2061
|
+
const localTime = Date.now();
|
|
2062
|
+
return new Date(localTime + this.timeOffset);
|
|
2063
|
+
}
|
|
2064
|
+
async loadNetworkTime() {
|
|
2065
|
+
const url = "https://xchange.mainnetwork.io/api/v1/utc";
|
|
2066
|
+
const response = await NodeConnection.xchangeRequest("GET", url, { timeout: 3e3 });
|
|
2067
|
+
const uTime = response.currentEpochSecond * 1e3;
|
|
2068
|
+
const localTime = Date.now();
|
|
2069
|
+
this.timeOffset = uTime - localTime;
|
|
2070
|
+
this.timeUpdatedAt = localTime;
|
|
2071
|
+
}
|
|
2072
|
+
static async getCost(tpack) {
|
|
2073
|
+
const url = "https://xchange.mainnetwork.io/api/v1/contracts/cost";
|
|
2074
|
+
const binary = await tpack.pack();
|
|
2075
|
+
const data = { packedContract: (0, import_unicrypto9.encode64)(binary) };
|
|
2076
|
+
return NodeConnection.xchangeRequest("POST", url, { data });
|
|
2077
|
+
}
|
|
2078
|
+
async registerParcel(parcel) {
|
|
2079
|
+
const self = this;
|
|
2080
|
+
const packedItem = boss19.dump(parcel);
|
|
2081
|
+
const connection = await this.getRandomConnection();
|
|
2082
|
+
const paymentId = await parcel.payment.contract.hashId();
|
|
2083
|
+
const payloadId = await parcel.payload.contract.hashId();
|
|
2084
|
+
if (!paymentId || !payloadId) throw new Error("payment or payload wasn't saved!");
|
|
2085
|
+
let response, error;
|
|
2086
|
+
try {
|
|
2087
|
+
response = await this.command(
|
|
2088
|
+
"approveParcel",
|
|
2089
|
+
{ packedItem },
|
|
2090
|
+
connection,
|
|
2091
|
+
{ timeout: 3e3 }
|
|
2092
|
+
);
|
|
2093
|
+
} catch (err) {
|
|
2094
|
+
error = err;
|
|
2095
|
+
return this.registerParcel(parcel);
|
|
2096
|
+
}
|
|
2097
|
+
const paymentState = await finalState(paymentId);
|
|
2098
|
+
const payloadState = await finalState(payloadId);
|
|
2099
|
+
return {
|
|
2100
|
+
payment: paymentState,
|
|
2101
|
+
payload: payloadState
|
|
2102
|
+
};
|
|
2103
|
+
async function finalState(id) {
|
|
2104
|
+
async function check(response3) {
|
|
2105
|
+
const { itemResult } = response3;
|
|
2106
|
+
const { state } = itemResult;
|
|
2107
|
+
if (!state.startsWith("PENDING")) return itemResult;
|
|
2108
|
+
await sleep(100);
|
|
2109
|
+
return await finalState(id);
|
|
2110
|
+
}
|
|
2111
|
+
const response2 = await self.getState(id, connection);
|
|
2112
|
+
return await check(response2);
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
};
|
|
2116
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2117
|
+
0 && (module.exports = {
|
|
2118
|
+
Capsule,
|
|
2119
|
+
ChangeNumberPermission,
|
|
2120
|
+
ChangeOwnerPermission,
|
|
2121
|
+
Compound,
|
|
2122
|
+
Contract,
|
|
2123
|
+
HashId,
|
|
2124
|
+
KeyRecord,
|
|
2125
|
+
ModifyDataPermission,
|
|
2126
|
+
Network,
|
|
2127
|
+
Node,
|
|
2128
|
+
Parcel,
|
|
2129
|
+
Reference,
|
|
2130
|
+
RevokePermission,
|
|
2131
|
+
RoleLink,
|
|
2132
|
+
RoleList,
|
|
2133
|
+
RoleSimple,
|
|
2134
|
+
SplitJoinPermission,
|
|
2135
|
+
Topology,
|
|
2136
|
+
TransactionPack,
|
|
2137
|
+
UniversaContract,
|
|
2138
|
+
...require("unicrypto")
|
|
2139
|
+
});
|
|
2140
|
+
//# sourceMappingURL=index.cjs.map
|