@calimero-network/mero-js 24.1.3 → 24.3.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.mjs CHANGED
@@ -1484,16 +1484,18 @@ var AdminApiClient = class {
1484
1484
  * Ask what this node can do for a member in `contextId`, before they sign
1485
1485
  * anything.
1486
1486
  *
1487
- * The two facts {@link performIntent} needs and a client cannot derive: whose
1488
- * account goes in the warrant's `executor`, and whether this node holds
1489
- * `CAN_AUTHOR_ON_BEHALF` on the owning group. `canAuthorOnBehalf: false` comes
1490
- * back as an answer rather than an error — it is the default state of every
1491
- * context — so a caller can say "an admin of `groupId` must grant it to
1492
- * `executorAccount`" instead of presenting a warrant that will be refused
1493
- * after the author has spent a nonce on it.
1494
- *
1495
- * `404` means the context belongs to no group, or this node holds no account
1496
- * yet; either way it can be named as no warrant's executor.
1487
+ * The facts {@link performIntent} needs and a client cannot derive: whose
1488
+ * account and key go in the warrant's `executor` and `executor_key`, which
1489
+ * release it pins, and whether this node holds `CAN_AUTHOR_ON_BEHALF` on the
1490
+ * owning group.
1491
+ * `canAuthorOnBehalf: false` comes back as an answer rather than an error, as
1492
+ * it is the default state of every context, so a caller can say "an admin of
1493
+ * `groupId` must grant it to `executorAccount`" instead of presenting a
1494
+ * warrant that will be refused after the author has spent a nonce on it.
1495
+ *
1496
+ * `404` means the context belongs to no group, this node holds no account or
1497
+ * identity in it yet, or its group names no release yet; no warrant can be
1498
+ * spent here until that changes.
1497
1499
  *
1498
1500
  * For a caller with no credential on the node at all — a browser tab holding
1499
1501
  * one signing key — use `RelayClient`, which reads this same route on a node
@@ -7190,7 +7192,7 @@ function splitVersion2(version) {
7190
7192
  var SIGN_DOMAIN2 = new TextEncoder().encode("calimero.warrant.v2");
7191
7193
  var INTENT_DOMAIN = new TextEncoder().encode("calimero.warrant.intent.v1");
7192
7194
  var MAX_CITED_HEADS = 64;
7193
- var UNPINNED_APP = "00".repeat(32);
7195
+ var MAX_RELEASE_VERSION_LEN = 256;
7194
7196
  async function intentHash(method, argsJson) {
7195
7197
  const args = new TextEncoder().encode(JSON.stringify(argsJson));
7196
7198
  return domainHash(INTENT_DOMAIN, [new TextEncoder().encode(method), args]);
@@ -7199,11 +7201,9 @@ async function signWarrant(input) {
7199
7201
  const context = fromHex(input.context, "context", 32);
7200
7202
  const authorAccount = fromHex(input.authorAccount, "authorAccount", 32);
7201
7203
  const executor = fromHex(input.executor, "executor", 32);
7202
- const appVersion = fromHex(
7203
- input.appVersion ?? UNPINNED_APP,
7204
- "appVersion",
7205
- 32
7206
- );
7204
+ const executorKey = fromHex(input.executorKey, "executorKey", 32);
7205
+ const releaseBytecodeId = fromHex(input.releaseBytecodeId, "releaseBytecodeId", 32);
7206
+ const releaseVersion2 = releaseVersionBytes(input.releaseVersion ?? "");
7207
7207
  const accountHeads = citedHeads(input.accountHeads, "accountHeads");
7208
7208
  const governanceFloor = citedHeads(input.governanceFloor, "governanceFloor");
7209
7209
  const signer = await resolveSigner(input.deviceSecret, input.signer, "deviceSecret");
@@ -7217,7 +7217,9 @@ async function signWarrant(input) {
7217
7217
  authorAccount,
7218
7218
  publicKey,
7219
7219
  executor,
7220
- appVersion,
7220
+ executorKey,
7221
+ releaseBytecodeId,
7222
+ releaseVersion2,
7221
7223
  method,
7222
7224
  commitment,
7223
7225
  u64le(accountHeads.length),
@@ -7234,7 +7236,10 @@ async function signWarrant(input) {
7234
7236
  authorAccount,
7235
7237
  publicKey,
7236
7238
  executor,
7237
- appVersion,
7239
+ executorKey,
7240
+ releaseBytecodeId,
7241
+ u32le(releaseVersion2.length),
7242
+ releaseVersion2,
7238
7243
  u32le(method.length),
7239
7244
  method,
7240
7245
  commitment,
@@ -7255,6 +7260,18 @@ function checkedSignature(signature) {
7255
7260
  }
7256
7261
  return signature;
7257
7262
  }
7263
+ function releaseVersionBytes(version) {
7264
+ const bytes = new TextEncoder().encode(version);
7265
+ checkReleaseVersionLength(bytes.length);
7266
+ return bytes;
7267
+ }
7268
+ function checkReleaseVersionLength(len) {
7269
+ if (len > MAX_RELEASE_VERSION_LEN) {
7270
+ throw new Error(
7271
+ `releaseVersion is ${len} bytes, over the ${MAX_RELEASE_VERSION_LEN} a node accepts`
7272
+ );
7273
+ }
7274
+ }
7258
7275
  function citedHeads(heads, label2) {
7259
7276
  const list = heads ?? [];
7260
7277
  if (list.length > MAX_CITED_HEADS) {
@@ -7280,6 +7297,7 @@ async function creationWarrantPreimage(p) {
7280
7297
  p.authorAccount,
7281
7298
  p.deviceKey,
7282
7299
  p.executor,
7300
+ p.executorKey,
7283
7301
  p.applicationId,
7284
7302
  new Uint8Array([p.serviceName === null ? 0 : 1]),
7285
7303
  p.serviceName ?? new Uint8Array(0),
@@ -7299,6 +7317,7 @@ async function signCreationWarrant(input) {
7299
7317
  const seed = input.seed === void 0 ? crypto.getRandomValues(new Uint8Array(32)) : fromHex(input.seed, "seed", 32);
7300
7318
  const authorAccount = fromHex(input.authorAccount, "authorAccount", 32);
7301
7319
  const executor = fromHex(input.executor, "executor", 32);
7320
+ const executorKey = fromHex(input.executorKey, "executorKey", 32);
7302
7321
  const applicationId = fromHex(input.applicationId, "applicationId", 32);
7303
7322
  const serviceName = label(input.serviceName, "serviceName");
7304
7323
  const name = label(input.name, "name");
@@ -7313,6 +7332,7 @@ async function signCreationWarrant(input) {
7313
7332
  authorAccount,
7314
7333
  deviceKey,
7315
7334
  executor,
7335
+ executorKey,
7316
7336
  applicationId,
7317
7337
  serviceName,
7318
7338
  name,
@@ -7330,6 +7350,7 @@ async function signCreationWarrant(input) {
7330
7350
  authorAccount,
7331
7351
  deviceKey,
7332
7352
  executor,
7353
+ executorKey,
7333
7354
  applicationId,
7334
7355
  borshOption(serviceName),
7335
7356
  borshOption(name),
@@ -7389,6 +7410,7 @@ function parseCreationWarrant(warrant) {
7389
7410
  authorAccount: hex(take(32, "authorAccount")),
7390
7411
  deviceKey: hex(take(32, "deviceKey")),
7391
7412
  executor: hex(take(32, "executor")),
7413
+ executorKey: hex(take(32, "executorKey")),
7392
7414
  applicationId: hex(take(32, "applicationId")),
7393
7415
  serviceName: option("serviceName"),
7394
7416
  name: option("name"),
@@ -7435,6 +7457,7 @@ async function governanceWarrantPreimage(p) {
7435
7457
  p.authorAccount,
7436
7458
  p.deviceKey,
7437
7459
  p.executor,
7460
+ p.executorKey,
7438
7461
  p.opHash,
7439
7462
  u64le(p.accountHeads.length),
7440
7463
  ...p.accountHeads,
@@ -7448,6 +7471,7 @@ async function signGovernanceWarrant(input) {
7448
7471
  const scope = fromHex(input.scope, "scope", 32);
7449
7472
  const authorAccount = fromHex(input.authorAccount, "authorAccount", 32);
7450
7473
  const executor = fromHex(input.executor, "executor", 32);
7474
+ const executorKey = fromHex(input.executorKey, "executorKey", 32);
7451
7475
  const accountHeads = citedHeads(input.accountHeads, "accountHeads");
7452
7476
  const governanceFloor = citedHeads(input.governanceFloor, "governanceFloor");
7453
7477
  const kind = kindByte(input.op.kind);
@@ -7460,6 +7484,7 @@ async function signGovernanceWarrant(input) {
7460
7484
  authorAccount,
7461
7485
  deviceKey,
7462
7486
  executor,
7487
+ executorKey,
7463
7488
  opHash,
7464
7489
  accountHeads,
7465
7490
  governanceFloor,
@@ -7474,6 +7499,7 @@ async function signGovernanceWarrant(input) {
7474
7499
  authorAccount,
7475
7500
  deviceKey,
7476
7501
  executor,
7502
+ executorKey,
7477
7503
  opHash,
7478
7504
  u32le(accountHeads.length),
7479
7505
  ...accountHeads,
@@ -7525,6 +7551,7 @@ function parseGovernanceWarrant(warrant) {
7525
7551
  authorAccount: hex(take(32, "authorAccount")),
7526
7552
  deviceKey: hex(take(32, "deviceKey")),
7527
7553
  executor: hex(take(32, "executor")),
7554
+ executorKey: hex(take(32, "executorKey")),
7528
7555
  opHash: hex(take(32, "opHash")),
7529
7556
  accountHeads: heads("accountHeads"),
7530
7557
  governanceFloor: heads("governanceFloor"),
@@ -7829,7 +7856,6 @@ var RelayClient = class {
7829
7856
  constructor(config) {
7830
7857
  this.config = config;
7831
7858
  this.baseUrl = config.relayUrl.replace(/\/+$/, "");
7832
- this.executorAccount = config.executorAccount;
7833
7859
  const injected = config.fetch;
7834
7860
  this.fetchImpl = injected ? (input, init) => injected(input, init) : (input, init) => globalThis.fetch(input, init);
7835
7861
  this.timeoutMs = config.timeoutMs ?? 1e4;
@@ -7879,8 +7905,10 @@ var RelayClient = class {
7879
7905
  * as diagnostics after a failure.
7880
7906
  */
7881
7907
  async describe(contextId) {
7882
- const body = await this.json("GET", `/admin-api/contexts/${encodeURIComponent(contextId)}/intents`);
7883
- this.executorAccount = body.data.executorAccount;
7908
+ const body = await this.json(
7909
+ "GET",
7910
+ `/admin-api/contexts/${encodeURIComponent(contextId)}/intents`
7911
+ );
7884
7912
  return body.data;
7885
7913
  }
7886
7914
  /**
@@ -7891,14 +7919,23 @@ var RelayClient = class {
7891
7919
  * apply. Nothing accumulates and nothing is reusable.
7892
7920
  */
7893
7921
  async execute(contextId, method, argsJson = {}) {
7894
- const executor = this.executorAccount ?? (await this.describe(contextId)).executorAccount;
7922
+ const described = await this.describe(contextId);
7923
+ const executor = this.checkedExecutor(described);
7924
+ const releaseBytecodeId = hex(
7925
+ fromHex(described.releaseBytecodeId ?? "", "the relay's releaseBytecodeId", 32)
7926
+ );
7927
+ const releaseVersion2 = described.releaseVersion ?? "";
7928
+ releaseVersionBytes(releaseVersion2);
7895
7929
  const signer = await this.authorSigner();
7896
7930
  const nonce2 = await this.config.nonces.next();
7897
7931
  const ttl = this.config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
7898
7932
  const warrant = await signWarrant({
7899
7933
  context: contextId,
7900
7934
  authorAccount: this.config.authorAccount,
7901
- executor,
7935
+ executor: executor.executorAccount,
7936
+ executorKey: executor.executorKey,
7937
+ releaseBytecodeId,
7938
+ releaseVersion: releaseVersion2,
7902
7939
  method,
7903
7940
  argsJson,
7904
7941
  nonce: nonce2,
@@ -7978,7 +8015,7 @@ var RelayClient = class {
7978
8015
  403
7979
8016
  );
7980
8017
  }
7981
- const executor = this.checkedExecutor(described.executorAccount);
8018
+ const executor = this.checkedExecutor(described);
7982
8019
  const initArgs = input.initArgs ?? {};
7983
8020
  const signer = await this.authorSigner();
7984
8021
  const nonce2 = await this.config.nonces.next();
@@ -7987,7 +8024,8 @@ var RelayClient = class {
7987
8024
  group: input.groupId,
7988
8025
  seed: input.seed,
7989
8026
  authorAccount: this.config.authorAccount,
7990
- executor,
8027
+ executor: executor.executorAccount,
8028
+ executorKey: executor.executorKey,
7991
8029
  applicationId: input.applicationId,
7992
8030
  serviceName: input.serviceName,
7993
8031
  name: input.name,
@@ -8009,7 +8047,7 @@ var RelayClient = class {
8009
8047
  }
8010
8048
  /**
8011
8049
  * Ask the relay whether it may publish governance ops for members of
8012
- * `groupId`, and which account a warrant must name.
8050
+ * `groupId`, and which account and key a warrant must name.
8013
8051
  */
8014
8052
  async describeGovernance(groupId) {
8015
8053
  const body = await this.json(
@@ -8043,7 +8081,7 @@ var RelayClient = class {
8043
8081
  403
8044
8082
  );
8045
8083
  }
8046
- const executor = this.checkedExecutor(described.executorAccount);
8084
+ const executor = this.checkedExecutor(described);
8047
8085
  const signer = await this.authorSigner();
8048
8086
  const nonce2 = await this.config.nonces.next();
8049
8087
  const ttl = this.config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
@@ -8051,7 +8089,8 @@ var RelayClient = class {
8051
8089
  scope: input.groupId,
8052
8090
  op: input.op,
8053
8091
  authorAccount: this.config.authorAccount,
8054
- executor,
8092
+ executor: executor.executorAccount,
8093
+ executorKey: executor.executorKey,
8055
8094
  nonce: nonce2,
8056
8095
  notAfter: BigInt(Math.floor(Date.now() / 1e3)) + BigInt(ttl),
8057
8096
  signer
@@ -8077,9 +8116,7 @@ var RelayClient = class {
8077
8116
  *
8078
8117
  * The executor cannot be learned from the relay first: the namespace does not
8079
8118
  * exist, so `describeGovernance` has nothing to answer about. It comes from
8080
- * `input.executorAccount`, the configured one, or one an earlier call learned
8081
- * (`describe`, `createContext`, `govern`), and without any of them this
8082
- * throws before taking a nonce.
8119
+ * `input.executor` only, and without it this throws before taking a nonce.
8083
8120
  * The nonce is spent in the new namespace's ledger.
8084
8121
  *
8085
8122
  * With `defaultCapabilities`, the founder then sets the namespace's default
@@ -8090,13 +8127,13 @@ var RelayClient = class {
8090
8127
  * Refusals (`400`/`403`) come back as {@link IntentRefusedError}; a salt
8091
8128
  * that names an existing namespace is a `409`, an `HTTPError`.
8092
8129
  */
8093
- async foundNamespace(input = {}) {
8094
- const executor = input.executorAccount ? this.checkedExecutor(input.executorAccount) : this.executorAccount;
8095
- if (!executor) {
8130
+ async foundNamespace(input) {
8131
+ if (!input?.executor) {
8096
8132
  throw new Error(
8097
- "foundNamespace needs the relay's account: pass executorAccount or configure it (checked before signing, no nonce was spent)"
8133
+ "foundNamespace needs the relay's account and signing key: pass executor (checked before signing, no nonce was spent)"
8098
8134
  );
8099
8135
  }
8136
+ const executor = this.checkedExecutor(input.executor);
8100
8137
  const salt = input.salt ? hex(fromHex(input.salt, "salt", 32)) : hex(crypto.getRandomValues(new Uint8Array(32)));
8101
8138
  const namespaceId = await foundedNamespaceId(this.config.authorAccount, salt);
8102
8139
  const op = namespaceCreatedOp({
@@ -8113,7 +8150,8 @@ var RelayClient = class {
8113
8150
  scope: namespaceId,
8114
8151
  op,
8115
8152
  authorAccount: this.config.authorAccount,
8116
- executor,
8153
+ executor: executor.executorAccount,
8154
+ executorKey: executor.executorKey,
8117
8155
  nonce: nonce2,
8118
8156
  notAfter: BigInt(Math.floor(Date.now() / 1e3)) + BigInt(ttl),
8119
8157
  signer
@@ -8154,18 +8192,21 @@ var RelayClient = class {
8154
8192
  return body.data;
8155
8193
  }
8156
8194
  /**
8157
- * The executor a warrant must name: the one the relay answered with, which a
8158
- * configured account must agree with. Remembered for later calls.
8195
+ * The relay's answer, checked against a configured account.
8196
+ * Decoded here so a relay naming no key is refused before a nonce is taken.
8159
8197
  */
8160
8198
  checkedExecutor(answered) {
8199
+ const executor = {
8200
+ executorAccount: hex(fromHex(answered.executorAccount ?? "", "the relay's executorAccount", 32)),
8201
+ executorKey: hex(fromHex(answered.executorKey ?? "", "the relay's executorKey", 32))
8202
+ };
8161
8203
  const configured = this.config.executorAccount;
8162
- if (configured && configured.toLowerCase() !== answered.toLowerCase()) {
8204
+ if (configured && configured.toLowerCase() !== executor.executorAccount) {
8163
8205
  throw new Error(
8164
- `configured executorAccount ${configured} is not the relay's account ${answered}; a warrant naming it would be unspendable`
8206
+ `configured executorAccount ${configured} is not the relay's account ${executor.executorAccount}; a warrant naming it would be unspendable`
8165
8207
  );
8166
8208
  }
8167
- this.executorAccount = answered;
8168
- return answered;
8209
+ return executor;
8169
8210
  }
8170
8211
  /**
8171
8212
  * One request, waiting out a rate limit rather than failing the caller.
@@ -10507,19 +10548,10 @@ async function foundDelegatedNamespace(s, req = {}, deps = {}) {
10507
10548
  const relays = knownRelays(s).map((u) => u.replace(/\/+$/, ""));
10508
10549
  const relay = relays[0];
10509
10550
  if (!relay) throw new Error("no relay is known for this account, so there is nowhere to found a namespace");
10510
- const known = Object.entries(readRelayMap(s.account).namespaces).find(
10511
- ([, url]) => url.replace(/\/+$/, "") === relay
10512
- )?.[0];
10513
- const named = s.executorAccount && s.relayUrl && s.relayUrl.replace(/\/+$/, "") === relay ? s.executorAccount : null;
10514
- if (!named && !known) {
10515
- throw new Error(
10516
- "the executor account of this relay is not known: join a namespace on it first, or connect with the relay's executor account (the cloud shows it beside the relay)"
10517
- );
10518
- }
10519
- const executorAccount = named ?? (await client(s, relay, governanceNonce(relay, known), deps.fetch).describeGovernance(known)).executorAccount;
10551
+ const executor = await foundingExecutor(s, relay, deps.fetch);
10520
10552
  let next = BigInt(Date.now());
10521
10553
  const founded = await client(s, relay, { next: async () => next++ }, deps.fetch).foundNamespace({
10522
- executorAccount,
10554
+ executor,
10523
10555
  ...req.defaultCapabilities !== void 0 ? { defaultCapabilities: req.defaultCapabilities } : {},
10524
10556
  ...req.application ? { application: req.application } : {}
10525
10557
  });
@@ -10539,6 +10571,33 @@ async function foundDelegatedNamespace(s, req = {}, deps = {}) {
10539
10571
  };
10540
10572
  return { namespaceId: founded.namespaceId, teeEnabled: founded.teeEnabled, ...ha };
10541
10573
  }
10574
+ async function foundingExecutor(s, relay, fetch2) {
10575
+ const named = s.executorAccount && s.relayUrl?.replace(/\/+$/, "") === relay ? s.executorAccount : null;
10576
+ if (named) return { executorAccount: named, executorKey: await attestedNodeKey(relay) };
10577
+ const known = Object.entries(readRelayMap(s.account).namespaces).find(
10578
+ ([, url]) => url.replace(/\/+$/, "") === relay
10579
+ )?.[0];
10580
+ if (!known) {
10581
+ throw new Error(
10582
+ "the executor account of this relay is not known: join a namespace on it first, or connect with the relay's executor account (the cloud shows it beside the relay)"
10583
+ );
10584
+ }
10585
+ const executorKey = await attestedNodeKey(relay);
10586
+ const described = await client(s, relay, governanceNonce(relay, known), fetch2).describeGovernance(known);
10587
+ if (described.executorKey?.toLowerCase() !== executorKey) {
10588
+ throw new Error(
10589
+ `the relay's discovery names signing key ${described.executorKey}, not the relay's node key ${executorKey}: refusing to found through it`
10590
+ );
10591
+ }
10592
+ return { executorAccount: described.executorAccount, executorKey };
10593
+ }
10594
+ async function attestedNodeKey(relay) {
10595
+ const key = await attemptRelayNodeKey(relay);
10596
+ if (key.kind === "learned") return key.nodeKey;
10597
+ throw new Error(
10598
+ key.kind === "unavailable" ? "the relay's signing key could not be learned: its attestation did not answer, try again" : "the relay's attestation was refused, so founding through it is not possible (only a key from its operator, pinned with pinRelayNodeKey, can stand in)"
10599
+ );
10600
+ }
10542
10601
  var HA_ACCOUNT_NOT_LINKED_MESSAGE = "link this account to your cloud user in the wallet so invitees can find this namespace";
10543
10602
  var HA_REFUSAL_MESSAGES = {
10544
10603
  account_not_linked: HA_ACCOUNT_NOT_LINKED_MESSAGE,