@calimero-network/mero-js 18.3.0 → 19.1.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
@@ -1031,13 +1031,30 @@ var AdminApiClient = class {
1031
1031
  );
1032
1032
  return { blobs: res.blobs.map((b) => ({ blobId: b.blob_id, size: b.size })) };
1033
1033
  }
1034
+ /**
1035
+ * Blob-read query params. `context_id` is the same snake_case param
1036
+ * {@link uploadBlob} announces with; building it in one place keeps the read
1037
+ * side from drifting from the write side.
1038
+ */
1039
+ blobPath(blobId, options) {
1040
+ if (!options?.contextId) return `/admin-api/blobs/${blobId}`;
1041
+ const params = new URLSearchParams({ context_id: options.contextId });
1042
+ return `/admin-api/blobs/${blobId}?${params.toString()}`;
1043
+ }
1034
1044
  /**
1035
1045
  * Download a blob's raw bytes. `GET /admin-api/blobs/:id` streams the blob
1036
1046
  * content (e.g. `application/gzip`), NOT JSON — so fetch it as an ArrayBuffer.
1037
1047
  * Use {@link listBlobs} for `{ blobId, size }` metadata.
1048
+ *
1049
+ * Without `contextId` the read is local-only: the node answers from its own
1050
+ * blob store and returns immediately, or 404s. Passing `contextId` opts into
1051
+ * network discovery — the node probes that context's peers (availability
1052
+ * nodes first) for a holder, then transfers the bytes. Budget for it: core
1053
+ * bounds discovery by a ~30s deadline, and the transfer is on top of that, so
1054
+ * the worst case is well past what a UI should block on unhinted.
1038
1055
  */
1039
- async getBlob(blobId) {
1040
- return this.httpClient.get(`/admin-api/blobs/${blobId}`, {
1056
+ async getBlob(blobId, options) {
1057
+ return this.httpClient.get(this.blobPath(blobId, options), {
1041
1058
  parse: "arrayBuffer"
1042
1059
  });
1043
1060
  }
@@ -1045,15 +1062,26 @@ var AdminApiClient = class {
1045
1062
  * Fetch a blob's metadata without downloading it. `HEAD /admin-api/blobs/:id`
1046
1063
  * returns the info in response headers (size via `content-length`, plus
1047
1064
  * `x-blob-id`/`x-blob-hash`/`x-blob-mime-type`).
1065
+ *
1066
+ * Without `contextId` this is a local-only presence check that returns
1067
+ * immediately. Passing `contextId` opts into the same network discovery
1068
+ * {@link getBlob} uses — presence and size answered by probing the context's
1069
+ * peers, with no transfer — so it inherits core's ~30s discovery deadline as
1070
+ * its worst case. A probe only carries `found` and `size`, so a peer-sourced
1071
+ * answer has no `hash`/`mimeType`; read `source` to tell the two apart.
1048
1072
  */
1049
- async getBlobInfo(blobId) {
1050
- const { headers } = await this.httpClient.head(`/admin-api/blobs/${blobId}`);
1073
+ async getBlobInfo(blobId, options) {
1074
+ const { headers } = await this.httpClient.head(this.blobPath(blobId, options));
1051
1075
  const size = Number(headers["content-length"]);
1052
1076
  return {
1053
1077
  blobId: headers["x-blob-id"] ?? blobId,
1054
1078
  size: Number.isFinite(size) ? size : 0,
1055
1079
  hash: headers["x-blob-hash"],
1056
- mimeType: headers["x-blob-mime-type"]
1080
+ mimeType: headers["x-blob-mime-type"],
1081
+ // Nodes that predate the context-aware HEAD omit this header entirely, so
1082
+ // it stays undefined rather than being defaulted to 'local' — absent means
1083
+ // "the node didn't say", which is not the same claim as "served locally".
1084
+ source: headers["x-blob-source"]
1057
1085
  };
1058
1086
  }
1059
1087
  // ---- Alias Management ----
@@ -3164,8 +3192,10 @@ async function derivePublicKey(secretHex) {
3164
3192
  }
3165
3193
 
3166
3194
  // src/warrant/warrant.ts
3167
- var SIGN_DOMAIN = new TextEncoder().encode("calimero.warrant.v1");
3195
+ var SIGN_DOMAIN = new TextEncoder().encode("calimero.warrant.v2");
3168
3196
  var INTENT_DOMAIN = new TextEncoder().encode("calimero.warrant.intent.v1");
3197
+ var MAX_CITED_HEADS = 64;
3198
+ var UNPINNED_APP = "00".repeat(32);
3169
3199
  async function intentHash(method, argsJson) {
3170
3200
  const args = new TextEncoder().encode(JSON.stringify(argsJson));
3171
3201
  return domainHash(INTENT_DOMAIN, [new TextEncoder().encode(method), args]);
@@ -3174,8 +3204,16 @@ async function signWarrant(input) {
3174
3204
  const context = fromHex(input.context, "context", 32);
3175
3205
  const authorAccount = fromHex(input.authorAccount, "authorAccount", 32);
3176
3206
  const executor = fromHex(input.executor, "executor", 32);
3207
+ const appVersion = fromHex(
3208
+ input.appVersion ?? UNPINNED_APP,
3209
+ "appVersion",
3210
+ 32
3211
+ );
3212
+ const accountHeads = citedHeads(input.accountHeads, "accountHeads");
3213
+ const governanceFloor = citedHeads(input.governanceFloor, "governanceFloor");
3177
3214
  const key = await importSigningKey(input.deviceSecret);
3178
3215
  const publicKey = await derivePublicKey(input.deviceSecret);
3216
+ const method = new TextEncoder().encode(input.method);
3179
3217
  const commitment = await intentHash(input.method, input.argsJson);
3180
3218
  const nonce = u64le(input.nonce);
3181
3219
  const notAfter = u64le(input.notAfter);
@@ -3184,7 +3222,13 @@ async function signWarrant(input) {
3184
3222
  authorAccount,
3185
3223
  publicKey,
3186
3224
  executor,
3225
+ appVersion,
3226
+ method,
3187
3227
  commitment,
3228
+ u64le(accountHeads.length),
3229
+ ...accountHeads,
3230
+ u64le(governanceFloor.length),
3231
+ ...governanceFloor,
3188
3232
  nonce,
3189
3233
  notAfter
3190
3234
  ]);
@@ -3197,13 +3241,29 @@ async function signWarrant(input) {
3197
3241
  authorAccount,
3198
3242
  publicKey,
3199
3243
  executor,
3244
+ appVersion,
3245
+ u32le(method.length),
3246
+ method,
3200
3247
  commitment,
3248
+ u32le(accountHeads.length),
3249
+ ...accountHeads,
3250
+ u32le(governanceFloor.length),
3251
+ ...governanceFloor,
3201
3252
  nonce,
3202
3253
  notAfter,
3203
3254
  signature
3204
3255
  )
3205
3256
  );
3206
3257
  }
3258
+ function citedHeads(heads, label) {
3259
+ const list = heads ?? [];
3260
+ if (list.length > MAX_CITED_HEADS) {
3261
+ throw new Error(
3262
+ `${label} cites ${list.length} heads, over the ${MAX_CITED_HEADS} a node accepts`
3263
+ );
3264
+ }
3265
+ return list.map((head, i) => fromHex(head, `${label}[${i}]`, 32));
3266
+ }
3207
3267
 
3208
3268
  // src/relay/relay-client.ts
3209
3269
  var DEFAULT_TTL_SECONDS = 300;