@opendatalabs/vana-sdk 3.11.0 → 3.13.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/README.md CHANGED
@@ -108,6 +108,33 @@ const result = await storage.upload(myBlob, "report.json");
108
108
  console.log(result.url);
109
109
  ```
110
110
 
111
+ ### Scope Vana storage by chain ID
112
+
113
+ Set `chainId` when writing to Vana Storage for a specific chain (e.g. `1480` for
114
+ Vana mainnet, `14800` for Moksha). Uploads then use chain-scoped routes
115
+ (`/v1/chains/{chainId}/blobs/...`) so data for different chains never collides.
116
+
117
+ ```typescript
118
+ import { createVanaStorageProvider } from "@opendatalabs/vana-sdk/node";
119
+
120
+ const storage = createVanaStorageProvider({
121
+ endpoint: "https://storage.vana.org",
122
+ chainId: 14800,
123
+ signer: {
124
+ address: account.address,
125
+ signMessage: (msg) => account.signMessage({ message: msg }),
126
+ },
127
+ });
128
+
129
+ const result = await storage.upload(
130
+ myBlob,
131
+ "instagram.profile/2026-05-08T20:00:00.000Z",
132
+ );
133
+ ```
134
+
135
+ Chain-configured providers reject legacy blob URLs and URLs scoped to a
136
+ different explicit chain ID.
137
+
111
138
  ## Build a Vana app
112
139
 
113
140
  Request user-approved data, read it from the user's Personal Server, and pay
@@ -262,7 +289,7 @@ in October 2025; the report is in [`audits/`](https://github.com/vana-com/vana-s
262
289
 
263
290
  - [Documentation](https://docs.vana.org/docs/sdk)
264
291
  - [API reference](https://vana-com.github.io/vana-sdk)
265
- - [Discord](https://discord.gg/vanabuilders)
292
+ - [Discord](https://discord.gg/vanaofficial)
266
293
 
267
294
  ## License
268
295
 
@@ -29028,12 +29028,14 @@ async function buildWeb3SignedHeader(params) {
29028
29028
 
29029
29029
  // src/storage/providers/vana-storage.ts
29030
29030
  var DEFAULT_ENDPOINT = "https://storage.vana.org";
29031
- var BLOB_PATH_PREFIX = "/v1/blobs";
29031
+ var LEGACY_BLOB_PATH_PREFIX = "/v1/blobs";
29032
29032
  var DEFAULT_TOKEN_TTL_SECONDS = 300;
29033
29033
  var MAX_UPLOAD_ATTEMPTS = 4;
29034
29034
  var MAX_RATE_LIMIT_DELAY_MS = 3e4;
29035
29035
  var VanaStorage = class {
29036
29036
  endpoint;
29037
+ chainId;
29038
+ blobPathPrefix;
29037
29039
  signer;
29038
29040
  ownerAddress;
29039
29041
  fetchImpl;
@@ -29046,6 +29048,15 @@ var VanaStorage = class {
29046
29048
  );
29047
29049
  }
29048
29050
  this.endpoint = (config.endpoint ?? DEFAULT_ENDPOINT).replace(/\/+$/, "");
29051
+ if (config.chainId !== void 0 && !isValidChainId(config.chainId)) {
29052
+ throw new StorageError(
29053
+ `Unsupported vana-storage chainId '${String(config.chainId)}'`,
29054
+ "INVALID_CHAIN_ID",
29055
+ "vana-storage"
29056
+ );
29057
+ }
29058
+ this.chainId = config.chainId;
29059
+ this.blobPathPrefix = this.chainId !== void 0 ? `/v1/chains/${this.chainId}/blobs` : LEGACY_BLOB_PATH_PREFIX;
29049
29060
  this.signer = config.signer;
29050
29061
  this.ownerAddress = (config.ownerAddress ?? config.signer.address).toLowerCase();
29051
29062
  this.fetchImpl = config.fetchImpl ?? globalThis.fetch.bind(globalThis);
@@ -29066,7 +29077,7 @@ var VanaStorage = class {
29066
29077
  );
29067
29078
  }
29068
29079
  const subpath = encodeRelativePath(filename);
29069
- const path = `${BLOB_PATH_PREFIX}/${this.ownerAddress}/${subpath}`;
29080
+ const path = `${this.blobPathPrefix}/${this.ownerAddress}/${subpath}`;
29070
29081
  const body = new Uint8Array(await file.arrayBuffer());
29071
29082
  const contentType = file.type !== "" ? file.type : "application/octet-stream";
29072
29083
  const header = await this.signRequest("PUT", path, body);
@@ -29234,12 +29245,17 @@ var VanaStorage = class {
29234
29245
  "vana-storage"
29235
29246
  );
29236
29247
  }
29237
- const segments = parsed.pathname.split("/").filter((s) => s.length > 0);
29238
- const isTraversal = (s) => s === "." || s === "..";
29239
- const valid = segments.length === 5 && segments[0] === "v1" && segments[1] === "blobs" && segments[2]?.toLowerCase() === this.ownerAddress && segments[3] !== void 0 && !isTraversal(segments[3]) && segments[4] !== void 0 && !isTraversal(segments[4]);
29240
- if (!valid) {
29248
+ const route = parseBlobPath(parsed.pathname);
29249
+ if (!route || route.owner.toLowerCase() !== this.ownerAddress) {
29250
+ throw new StorageError(
29251
+ `URL path '${parsed.pathname}' must be ${this.blobPathPrefix}/${this.ownerAddress}/{scope}/{collectedAt}`,
29252
+ "INVALID_URL",
29253
+ "vana-storage"
29254
+ );
29255
+ }
29256
+ if (route.chainId !== this.chainId) {
29241
29257
  throw new StorageError(
29242
- `URL path '${parsed.pathname}' must be /v1/blobs/${this.ownerAddress}/{scope}/{collectedAt}`,
29258
+ `URL chainId '${route.chainId ?? "legacy"}' does not match provider chainId '${this.chainId ?? "legacy"}'`,
29243
29259
  "INVALID_URL",
29244
29260
  "vana-storage"
29245
29261
  );
@@ -29247,6 +29263,27 @@ var VanaStorage = class {
29247
29263
  return parsed.pathname;
29248
29264
  }
29249
29265
  };
29266
+ function isValidChainId(value) {
29267
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
29268
+ }
29269
+ function parseBlobPath(pathname) {
29270
+ const segments = pathname.split("/").filter((s) => s.length > 0);
29271
+ const isTraversal = (s) => s === "." || s === "..";
29272
+ if (segments.length === 5 && segments[0] === "v1" && segments[1] === "blobs") {
29273
+ const [, , owner, scope, collectedAt] = segments;
29274
+ if (isTraversal(scope) || isTraversal(collectedAt)) return null;
29275
+ return { owner, scope, collectedAt };
29276
+ }
29277
+ if (segments.length === 7 && segments[0] === "v1" && segments[1] === "chains" && segments[3] === "blobs") {
29278
+ const [, , chainIdSegment, , owner, scope, collectedAt] = segments;
29279
+ if (!/^[0-9]+$/.test(chainIdSegment)) return null;
29280
+ const chainId = Number(chainIdSegment);
29281
+ if (!isValidChainId(chainId)) return null;
29282
+ if (isTraversal(scope) || isTraversal(collectedAt)) return null;
29283
+ return { chainId, owner, scope, collectedAt };
29284
+ }
29285
+ return null;
29286
+ }
29250
29287
  function encodeRelativePath(filename) {
29251
29288
  const parts = filename.split("/");
29252
29289
  if (parts.length !== 2 || parts.some((p) => p.length === 0 || p === "." || p === "..")) {