@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.mjs CHANGED
@@ -949,13 +949,30 @@ var AdminApiClient = class {
949
949
  );
950
950
  return { blobs: res.blobs.map((b) => ({ blobId: b.blob_id, size: b.size })) };
951
951
  }
952
+ /**
953
+ * Blob-read query params. `context_id` is the same snake_case param
954
+ * {@link uploadBlob} announces with; building it in one place keeps the read
955
+ * side from drifting from the write side.
956
+ */
957
+ blobPath(blobId, options) {
958
+ if (!options?.contextId) return `/admin-api/blobs/${blobId}`;
959
+ const params = new URLSearchParams({ context_id: options.contextId });
960
+ return `/admin-api/blobs/${blobId}?${params.toString()}`;
961
+ }
952
962
  /**
953
963
  * Download a blob's raw bytes. `GET /admin-api/blobs/:id` streams the blob
954
964
  * content (e.g. `application/gzip`), NOT JSON — so fetch it as an ArrayBuffer.
955
965
  * Use {@link listBlobs} for `{ blobId, size }` metadata.
966
+ *
967
+ * Without `contextId` the read is local-only: the node answers from its own
968
+ * blob store and returns immediately, or 404s. Passing `contextId` opts into
969
+ * network discovery — the node probes that context's peers (availability
970
+ * nodes first) for a holder, then transfers the bytes. Budget for it: core
971
+ * bounds discovery by a ~30s deadline, and the transfer is on top of that, so
972
+ * the worst case is well past what a UI should block on unhinted.
956
973
  */
957
- async getBlob(blobId) {
958
- return this.httpClient.get(`/admin-api/blobs/${blobId}`, {
974
+ async getBlob(blobId, options) {
975
+ return this.httpClient.get(this.blobPath(blobId, options), {
959
976
  parse: "arrayBuffer"
960
977
  });
961
978
  }
@@ -963,15 +980,26 @@ var AdminApiClient = class {
963
980
  * Fetch a blob's metadata without downloading it. `HEAD /admin-api/blobs/:id`
964
981
  * returns the info in response headers (size via `content-length`, plus
965
982
  * `x-blob-id`/`x-blob-hash`/`x-blob-mime-type`).
983
+ *
984
+ * Without `contextId` this is a local-only presence check that returns
985
+ * immediately. Passing `contextId` opts into the same network discovery
986
+ * {@link getBlob} uses — presence and size answered by probing the context's
987
+ * peers, with no transfer — so it inherits core's ~30s discovery deadline as
988
+ * its worst case. A probe only carries `found` and `size`, so a peer-sourced
989
+ * answer has no `hash`/`mimeType`; read `source` to tell the two apart.
966
990
  */
967
- async getBlobInfo(blobId) {
968
- const { headers } = await this.httpClient.head(`/admin-api/blobs/${blobId}`);
991
+ async getBlobInfo(blobId, options) {
992
+ const { headers } = await this.httpClient.head(this.blobPath(blobId, options));
969
993
  const size = Number(headers["content-length"]);
970
994
  return {
971
995
  blobId: headers["x-blob-id"] ?? blobId,
972
996
  size: Number.isFinite(size) ? size : 0,
973
997
  hash: headers["x-blob-hash"],
974
- mimeType: headers["x-blob-mime-type"]
998
+ mimeType: headers["x-blob-mime-type"],
999
+ // Nodes that predate the context-aware HEAD omit this header entirely, so
1000
+ // it stays undefined rather than being defaulted to 'local' — absent means
1001
+ // "the node didn't say", which is not the same claim as "served locally".
1002
+ source: headers["x-blob-source"]
975
1003
  };
976
1004
  }
977
1005
  // ---- Alias Management ----
@@ -3082,8 +3110,10 @@ async function derivePublicKey(secretHex) {
3082
3110
  }
3083
3111
 
3084
3112
  // src/warrant/warrant.ts
3085
- var SIGN_DOMAIN = new TextEncoder().encode("calimero.warrant.v1");
3113
+ var SIGN_DOMAIN = new TextEncoder().encode("calimero.warrant.v2");
3086
3114
  var INTENT_DOMAIN = new TextEncoder().encode("calimero.warrant.intent.v1");
3115
+ var MAX_CITED_HEADS = 64;
3116
+ var UNPINNED_APP = "00".repeat(32);
3087
3117
  async function intentHash(method, argsJson) {
3088
3118
  const args = new TextEncoder().encode(JSON.stringify(argsJson));
3089
3119
  return domainHash(INTENT_DOMAIN, [new TextEncoder().encode(method), args]);
@@ -3092,8 +3122,16 @@ async function signWarrant(input) {
3092
3122
  const context = fromHex(input.context, "context", 32);
3093
3123
  const authorAccount = fromHex(input.authorAccount, "authorAccount", 32);
3094
3124
  const executor = fromHex(input.executor, "executor", 32);
3125
+ const appVersion = fromHex(
3126
+ input.appVersion ?? UNPINNED_APP,
3127
+ "appVersion",
3128
+ 32
3129
+ );
3130
+ const accountHeads = citedHeads(input.accountHeads, "accountHeads");
3131
+ const governanceFloor = citedHeads(input.governanceFloor, "governanceFloor");
3095
3132
  const key = await importSigningKey(input.deviceSecret);
3096
3133
  const publicKey = await derivePublicKey(input.deviceSecret);
3134
+ const method = new TextEncoder().encode(input.method);
3097
3135
  const commitment = await intentHash(input.method, input.argsJson);
3098
3136
  const nonce = u64le(input.nonce);
3099
3137
  const notAfter = u64le(input.notAfter);
@@ -3102,7 +3140,13 @@ async function signWarrant(input) {
3102
3140
  authorAccount,
3103
3141
  publicKey,
3104
3142
  executor,
3143
+ appVersion,
3144
+ method,
3105
3145
  commitment,
3146
+ u64le(accountHeads.length),
3147
+ ...accountHeads,
3148
+ u64le(governanceFloor.length),
3149
+ ...governanceFloor,
3106
3150
  nonce,
3107
3151
  notAfter
3108
3152
  ]);
@@ -3115,13 +3159,29 @@ async function signWarrant(input) {
3115
3159
  authorAccount,
3116
3160
  publicKey,
3117
3161
  executor,
3162
+ appVersion,
3163
+ u32le(method.length),
3164
+ method,
3118
3165
  commitment,
3166
+ u32le(accountHeads.length),
3167
+ ...accountHeads,
3168
+ u32le(governanceFloor.length),
3169
+ ...governanceFloor,
3119
3170
  nonce,
3120
3171
  notAfter,
3121
3172
  signature
3122
3173
  )
3123
3174
  );
3124
3175
  }
3176
+ function citedHeads(heads, label) {
3177
+ const list = heads ?? [];
3178
+ if (list.length > MAX_CITED_HEADS) {
3179
+ throw new Error(
3180
+ `${label} cites ${list.length} heads, over the ${MAX_CITED_HEADS} a node accepts`
3181
+ );
3182
+ }
3183
+ return list.map((head, i) => fromHex(head, `${label}[${i}]`, 32));
3184
+ }
3125
3185
 
3126
3186
  // src/relay/relay-client.ts
3127
3187
  var DEFAULT_TTL_SECONDS = 300;