@calimero-network/mero-js 22.6.0 → 22.7.0

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/dist/index.cjs CHANGED
@@ -23,6 +23,7 @@ __export(index_exports, {
23
23
  AuthApiClient: () => AuthApiClient,
24
24
  AuthRevokedError: () => AuthRevokedError,
25
25
  CAPABILITIES: () => CAPABILITIES,
26
+ CAPABILITY_PRESETS: () => CAPABILITY_PRESETS,
26
27
  CloudClient: () => CloudClient,
27
28
  DEFAULT_LOCAL_NODE_PORTS: () => DEFAULT_LOCAL_NODE_PORTS,
28
29
  DEFAULT_RELEASE_MIRROR: () => DEFAULT_RELEASE_MIRROR,
@@ -51,6 +52,8 @@ __export(index_exports, {
51
52
  admitterAddrsFromNetworkStatus: () => admitterAddrsFromNetworkStatus,
52
53
  buildAuthLoginUrl: () => buildAuthLoginUrl,
53
54
  callerProof: () => callerProof,
55
+ capMask: () => capMask,
56
+ capNames: () => capNames,
54
57
  classifyError: () => classifyError,
55
58
  cloudNodeReleaseUrl: () => cloudNodeReleaseUrl,
56
59
  combineSignals: () => combineSignals,
@@ -80,12 +83,14 @@ __export(index_exports, {
80
83
  createTimeoutSignal: () => createTimeoutSignal,
81
84
  createUniversalHttpClient: () => createUniversalHttpClient,
82
85
  creationInitHash: () => creationInitHash,
86
+ defaultCapabilitiesSetOp: () => defaultCapabilitiesSetOp,
83
87
  deviceCertPayload: () => deviceCertPayload,
84
88
  discoverLocalNodes: () => discoverLocalNodes,
85
89
  encodeSignedInvitation: () => encodeSignedInvitation,
86
90
  fetchAttestedTransportKey: () => fetchAttestedTransportKey,
87
91
  fetchNodeRelease: () => fetchNodeRelease,
88
92
  fetchNodeReleaseVersion: () => fetchNodeReleaseVersion,
93
+ foundedNamespaceId: () => foundedNamespaceId,
89
94
  generateAccountRoot: () => generateAccountRoot,
90
95
  generateSessionKey: () => generateSessionKey,
91
96
  governanceOpHash: () => governanceOpHash,
@@ -104,6 +109,7 @@ __export(index_exports, {
104
109
  memberRemovedOp: () => memberRemovedOp,
105
110
  memberRoleSetOp: () => memberRoleSetOp,
106
111
  mintDeviceId: () => mintDeviceId,
112
+ namespaceCreatedOp: () => namespaceCreatedOp,
107
113
  nodeEndpoint: () => nodeEndpoint,
108
114
  nodeReleaseVersionFromImage: () => nodeReleaseVersionFromImage,
109
115
  parseAuthCallback: () => parseAuthCallback,
@@ -883,6 +889,41 @@ var CAPABILITIES = {
883
889
  */
884
890
  CAN_AUTHOR_ON_BEHALF: 1 << 9
885
891
  };
892
+ var CAPABILITY_PRESETS = {
893
+ /** No capabilities. */
894
+ NONE: 0,
895
+ /** Join Open subgroups only: what a namespace starts with. */
896
+ JOIN_ONLY: CAPABILITIES.CAN_JOIN_OPEN_SUBGROUPS,
897
+ /**
898
+ * Create contexts, invite members, join Open subgroups (= 7). A member who
899
+ * can work in the namespace but not reshape it.
900
+ */
901
+ CONTRIBUTOR: CAPABILITIES.CAN_CREATE_CONTEXT | CAPABILITIES.CAN_INVITE_MEMBERS | CAPABILITIES.CAN_JOIN_OPEN_SUBGROUPS,
902
+ /**
903
+ * `CONTRIBUTOR` plus create and delete subgroups and manage their visibility
904
+ * (= 231). What mero-chat gives members, so they can start and close their
905
+ * own channels.
906
+ */
907
+ COLLABORATOR: CAPABILITIES.CAN_CREATE_CONTEXT | CAPABILITIES.CAN_INVITE_MEMBERS | CAPABILITIES.CAN_JOIN_OPEN_SUBGROUPS | CAPABILITIES.CAN_CREATE_SUBGROUP | CAPABILITIES.CAN_DELETE_SUBGROUP | CAPABILITIES.CAN_MANAGE_VISIBILITY
908
+ };
909
+ function capMask(...caps) {
910
+ let mask = 0;
911
+ for (const cap of caps) {
912
+ if (typeof cap === "number") {
913
+ mask |= cap;
914
+ } else if (Object.prototype.hasOwnProperty.call(CAPABILITIES, cap)) {
915
+ mask |= CAPABILITIES[cap];
916
+ } else {
917
+ throw new Error(`unknown capability: ${String(cap)}`);
918
+ }
919
+ }
920
+ return mask >>> 0;
921
+ }
922
+ function capNames(mask) {
923
+ return Object.keys(CAPABILITIES).filter(
924
+ (name) => hasCap(mask, CAPABILITIES[name])
925
+ );
926
+ }
886
927
  function hasCap(mask, cap) {
887
928
  const capU32 = cap >>> 0;
888
929
  return (mask & capU32) >>> 0 === capU32;
@@ -7059,6 +7100,138 @@ function kindByte(kind) {
7059
7100
  return byte;
7060
7101
  }
7061
7102
 
7103
+ // src/warrant/governance-op.ts
7104
+ var ROLE = {
7105
+ Admin: 0,
7106
+ Member: 1,
7107
+ ReadOnly: 2
7108
+ };
7109
+ var GROUP_OP = {
7110
+ MemberAdded: 1,
7111
+ MemberRemoved: 2,
7112
+ MemberLeft: 3,
7113
+ MemberRoleSet: 4,
7114
+ DefaultCapabilitiesSet: 6
7115
+ };
7116
+ var ROOT_OP = {
7117
+ GroupCreated: 0,
7118
+ GroupReparented: 1,
7119
+ GroupDeleted: 2,
7120
+ NamespaceCreatedV2: 9
7121
+ };
7122
+ var NAMESPACE_ID_DOMAIN = new TextEncoder().encode("calimero.namespace.id.v1");
7123
+ var CLEARED_HASH = new Uint8Array(32);
7124
+ var EMPTY_VEC = u32le(0);
7125
+ function role(value) {
7126
+ const byte = ROLE[value];
7127
+ if (byte === void 0) {
7128
+ throw new Error(`role must be Admin, Member or ReadOnly, got ${String(value)}`);
7129
+ }
7130
+ return new Uint8Array([byte]);
7131
+ }
7132
+ function tag(byte) {
7133
+ return new Uint8Array([byte]);
7134
+ }
7135
+ function memberAddedOp(member, memberRole) {
7136
+ return {
7137
+ kind: "group",
7138
+ bytes: concat(tag(GROUP_OP.MemberAdded), fromHex(member, "member", 32), role(memberRole))
7139
+ };
7140
+ }
7141
+ function memberRemovedOp(member) {
7142
+ return {
7143
+ kind: "group",
7144
+ bytes: concat(
7145
+ tag(GROUP_OP.MemberRemoved),
7146
+ fromHex(member, "member", 32),
7147
+ CLEARED_HASH,
7148
+ EMPTY_VEC
7149
+ )
7150
+ };
7151
+ }
7152
+ function memberLeftOp(member) {
7153
+ return {
7154
+ kind: "group",
7155
+ bytes: concat(
7156
+ tag(GROUP_OP.MemberLeft),
7157
+ fromHex(member, "member", 32),
7158
+ CLEARED_HASH,
7159
+ EMPTY_VEC
7160
+ )
7161
+ };
7162
+ }
7163
+ function memberRoleSetOp(member, memberRole) {
7164
+ return {
7165
+ kind: "group",
7166
+ bytes: concat(tag(GROUP_OP.MemberRoleSet), fromHex(member, "member", 32), role(memberRole))
7167
+ };
7168
+ }
7169
+ function defaultCapabilitiesSetOp(capabilities) {
7170
+ if (!Number.isInteger(capabilities) || capabilities < 0 || capabilities > 4294967295) {
7171
+ throw new Error(`capabilities must be a u32 bit mask, got ${String(capabilities)}`);
7172
+ }
7173
+ if (hasCap(capabilities, CAPABILITIES.CAN_AUTHOR_ON_BEHALF)) {
7174
+ throw new Error(
7175
+ "capabilities may not include CAN_AUTHOR_ON_BEHALF (512): a relay never carries a change to it"
7176
+ );
7177
+ }
7178
+ return {
7179
+ kind: "group",
7180
+ bytes: concat(tag(GROUP_OP.DefaultCapabilitiesSet), u32le(capabilities))
7181
+ };
7182
+ }
7183
+ function groupCreatedOp(input) {
7184
+ return {
7185
+ kind: "root",
7186
+ bytes: concat(
7187
+ tag(ROOT_OP.GroupCreated),
7188
+ fromHex(input.groupId, "groupId", 32),
7189
+ fromHex(input.parentId, "parentId", 32),
7190
+ tag(input.restricted ? 1 : 0),
7191
+ fromHex(input.admin, "admin", 32)
7192
+ )
7193
+ };
7194
+ }
7195
+ function groupReparentedOp(childGroupId, newParentId) {
7196
+ return {
7197
+ kind: "root",
7198
+ bytes: concat(
7199
+ tag(ROOT_OP.GroupReparented),
7200
+ fromHex(childGroupId, "childGroupId", 32),
7201
+ fromHex(newParentId, "newParentId", 32)
7202
+ )
7203
+ };
7204
+ }
7205
+ function groupDeletedOp(rootGroupId) {
7206
+ return {
7207
+ kind: "root",
7208
+ bytes: concat(tag(ROOT_OP.GroupDeleted), fromHex(rootGroupId, "rootGroupId", 32), EMPTY_VEC, EMPTY_VEC)
7209
+ };
7210
+ }
7211
+ async function foundedNamespaceId(founder, salt) {
7212
+ return hex(
7213
+ await domainHash(NAMESPACE_ID_DOMAIN, [
7214
+ fromHex(founder, "founder", 32),
7215
+ fromHex(salt, "salt", 32)
7216
+ ])
7217
+ );
7218
+ }
7219
+ function namespaceCreatedOp(input) {
7220
+ const credential = fromHexUnsized(input.credential, "credential");
7221
+ if (credential.length === 0) {
7222
+ throw new Error("credential must be the founder's AccountProof<DeviceCert>, got nothing");
7223
+ }
7224
+ return {
7225
+ kind: "root",
7226
+ bytes: concat(
7227
+ tag(ROOT_OP.NamespaceCreatedV2),
7228
+ fromHex(input.founder, "founder", 32),
7229
+ credential,
7230
+ fromHex(input.salt, "salt", 32)
7231
+ )
7232
+ };
7233
+ }
7234
+
7062
7235
  // src/relay/relay-client.ts
7063
7236
  var DEFAULT_TTL_SECONDS = 300;
7064
7237
  var IntentRefusedError = class extends Error {
@@ -7259,12 +7432,93 @@ var RelayClient = class {
7259
7432
  notAfter: BigInt(Math.floor(Date.now() / 1e3)) + BigInt(ttl),
7260
7433
  signer
7261
7434
  });
7262
- const body = await this.json(
7263
- "POST",
7264
- `/admin-api/groups/${encodeURIComponent(input.groupId)}/governance-intents`,
7265
- { warrant, authorProof: this.config.authorProof, op: hex(input.op.bytes) }
7266
- );
7267
- return { groupId: body.data.groupId };
7435
+ const data = await this.postGovernance(input.groupId, input.op, warrant);
7436
+ return { groupId: data.groupId };
7437
+ }
7438
+ /**
7439
+ * Found a namespace through the relay, with the author as its founder, owner
7440
+ * and admin. For an account with no node, this is how it gets a namespace at
7441
+ * all.
7442
+ *
7443
+ * The author signs the exact genesis (`NamespaceCreatedV2`, carrying the
7444
+ * author's own credential and the salt) under a root-plane governance
7445
+ * warrant scoped to the new namespace's id, which is derived from the
7446
+ * author's account and the salt, so the relay can neither choose another id
7447
+ * nor found it for anyone else. The relay takes an identity in the new
7448
+ * namespace, mints its key, publishes the genesis, and is seated as the
7449
+ * founding relay (a `Member` holding `CAN_AUTHOR_ON_BEHALF`), so later
7450
+ * `govern`, `createContext` and `execute` calls in the namespace work
7451
+ * through it straight away. A TEE relay then admits itself as the
7452
+ * namespace's first TEE; `teeEnabled` reports whether it did.
7453
+ *
7454
+ * The executor cannot be learned from the relay first: the namespace does not
7455
+ * exist, so `describeGovernance` has nothing to answer about. It comes from
7456
+ * `input.executorAccount`, the configured one, or one an earlier call learned
7457
+ * (`describe`, `createContext`, `govern`), and without any of them this
7458
+ * throws before taking a nonce.
7459
+ * The nonce is spent in the new namespace's ledger.
7460
+ *
7461
+ * With `defaultCapabilities`, the founder then sets the namespace's default
7462
+ * capability mask through the same relay (a second warrant, a second nonce)
7463
+ * and `defaultCapabilitiesSet` reports whether it landed. A failure there
7464
+ * does not throw, since the namespace is already founded.
7465
+ *
7466
+ * Refusals (`400`/`403`) come back as {@link IntentRefusedError}; a salt
7467
+ * that names an existing namespace is a `409`, an `HTTPError`.
7468
+ */
7469
+ async foundNamespace(input = {}) {
7470
+ const executor = input.executorAccount ? this.checkedExecutor(input.executorAccount) : this.executorAccount;
7471
+ if (!executor) {
7472
+ throw new Error(
7473
+ "foundNamespace needs the relay's account: pass executorAccount or configure it (checked before signing, no nonce was spent)"
7474
+ );
7475
+ }
7476
+ const salt = input.salt ? hex(fromHex(input.salt, "salt", 32)) : hex(crypto.getRandomValues(new Uint8Array(32)));
7477
+ const namespaceId = await foundedNamespaceId(this.config.authorAccount, salt);
7478
+ const op = namespaceCreatedOp({
7479
+ founder: this.config.authorAccount,
7480
+ credential: this.config.authorProof,
7481
+ salt
7482
+ });
7483
+ const capabilitiesOp = input.defaultCapabilities === void 0 ? void 0 : defaultCapabilitiesSetOp(input.defaultCapabilities);
7484
+ const signer = await this.authorSigner();
7485
+ const nonce2 = await this.config.nonces.next();
7486
+ const ttl = this.config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
7487
+ const warrant = await signGovernanceWarrant({
7488
+ scope: namespaceId,
7489
+ op,
7490
+ authorAccount: this.config.authorAccount,
7491
+ executor,
7492
+ nonce: nonce2,
7493
+ notAfter: BigInt(Math.floor(Date.now() / 1e3)) + BigInt(ttl),
7494
+ signer
7495
+ });
7496
+ const data = await this.postGovernance(namespaceId, op, warrant);
7497
+ const founded = {
7498
+ namespaceId: data.groupId,
7499
+ salt,
7500
+ teeEnabled: data.teeEnabled ?? false,
7501
+ ...data.teeError ? { teeError: data.teeError } : {}
7502
+ };
7503
+ if (!capabilitiesOp) return founded;
7504
+ try {
7505
+ await this.govern({ groupId: founded.namespaceId, op: capabilitiesOp });
7506
+ return { ...founded, defaultCapabilitiesSet: true };
7507
+ } catch (err) {
7508
+ return {
7509
+ ...founded,
7510
+ defaultCapabilitiesSet: false,
7511
+ defaultCapabilitiesError: err instanceof Error ? err.message : String(err)
7512
+ };
7513
+ }
7514
+ }
7515
+ async postGovernance(groupId, op, warrant) {
7516
+ const body = await this.json("POST", `/admin-api/groups/${encodeURIComponent(groupId)}/governance-intents`, {
7517
+ warrant,
7518
+ authorProof: this.config.authorProof,
7519
+ op: hex(op.bytes)
7520
+ });
7521
+ return body.data;
7268
7522
  }
7269
7523
  /**
7270
7524
  * The executor a warrant must name: the one the relay answered with, which a
@@ -8658,98 +8912,6 @@ function splitVersion2(version) {
8658
8912
  return [[Number(match[1]), Number(match[2]), Number(match[3])], match[4]];
8659
8913
  }
8660
8914
 
8661
- // src/warrant/governance-op.ts
8662
- var ROLE = {
8663
- Admin: 0,
8664
- Member: 1,
8665
- ReadOnly: 2
8666
- };
8667
- var GROUP_OP = {
8668
- MemberAdded: 1,
8669
- MemberRemoved: 2,
8670
- MemberLeft: 3,
8671
- MemberRoleSet: 4
8672
- };
8673
- var ROOT_OP = {
8674
- GroupCreated: 0,
8675
- GroupReparented: 1,
8676
- GroupDeleted: 2
8677
- };
8678
- var CLEARED_HASH = new Uint8Array(32);
8679
- var EMPTY_VEC = u32le(0);
8680
- function role(value) {
8681
- const byte = ROLE[value];
8682
- if (byte === void 0) {
8683
- throw new Error(`role must be Admin, Member or ReadOnly, got ${String(value)}`);
8684
- }
8685
- return new Uint8Array([byte]);
8686
- }
8687
- function tag(byte) {
8688
- return new Uint8Array([byte]);
8689
- }
8690
- function memberAddedOp(member, memberRole) {
8691
- return {
8692
- kind: "group",
8693
- bytes: concat(tag(GROUP_OP.MemberAdded), fromHex(member, "member", 32), role(memberRole))
8694
- };
8695
- }
8696
- function memberRemovedOp(member) {
8697
- return {
8698
- kind: "group",
8699
- bytes: concat(
8700
- tag(GROUP_OP.MemberRemoved),
8701
- fromHex(member, "member", 32),
8702
- CLEARED_HASH,
8703
- EMPTY_VEC
8704
- )
8705
- };
8706
- }
8707
- function memberLeftOp(member) {
8708
- return {
8709
- kind: "group",
8710
- bytes: concat(
8711
- tag(GROUP_OP.MemberLeft),
8712
- fromHex(member, "member", 32),
8713
- CLEARED_HASH,
8714
- EMPTY_VEC
8715
- )
8716
- };
8717
- }
8718
- function memberRoleSetOp(member, memberRole) {
8719
- return {
8720
- kind: "group",
8721
- bytes: concat(tag(GROUP_OP.MemberRoleSet), fromHex(member, "member", 32), role(memberRole))
8722
- };
8723
- }
8724
- function groupCreatedOp(input) {
8725
- return {
8726
- kind: "root",
8727
- bytes: concat(
8728
- tag(ROOT_OP.GroupCreated),
8729
- fromHex(input.groupId, "groupId", 32),
8730
- fromHex(input.parentId, "parentId", 32),
8731
- tag(input.restricted ? 1 : 0),
8732
- fromHex(input.admin, "admin", 32)
8733
- )
8734
- };
8735
- }
8736
- function groupReparentedOp(childGroupId, newParentId) {
8737
- return {
8738
- kind: "root",
8739
- bytes: concat(
8740
- tag(ROOT_OP.GroupReparented),
8741
- fromHex(childGroupId, "childGroupId", 32),
8742
- fromHex(newParentId, "newParentId", 32)
8743
- )
8744
- };
8745
- }
8746
- function groupDeletedOp(rootGroupId) {
8747
- return {
8748
- kind: "root",
8749
- bytes: concat(tag(ROOT_OP.GroupDeleted), fromHex(rootGroupId, "rootGroupId", 32), EMPTY_VEC, EMPTY_VEC)
8750
- };
8751
- }
8752
-
8753
8915
  // src/namespace-op/namespace-op.ts
8754
8916
  var SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 16;
8755
8917
  var NAMESPACE_SIGN_DOMAIN = new TextEncoder().encode("calimero.namespace.v1");