@opendatalabs/vana-sdk 0.1.0-alpha.761813a → 0.1.0-alpha.8eb4e46

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.
@@ -141,65 +141,9 @@ var init_browser = __esm({
141
141
  try {
142
142
  const eccrypto = await import("eccrypto-js");
143
143
  const uncompressedKey = processWalletPublicKey(publicKey);
144
- const iv = Buffer.from([
145
- 1,
146
- 2,
147
- 3,
148
- 4,
149
- 5,
150
- 6,
151
- 7,
152
- 8,
153
- 9,
154
- 10,
155
- 11,
156
- 12,
157
- 13,
158
- 14,
159
- 15,
160
- 16
161
- ]);
162
- const ephemeralKey = Buffer.from([
163
- 17,
164
- 34,
165
- 51,
166
- 68,
167
- 85,
168
- 102,
169
- 119,
170
- 136,
171
- 153,
172
- 170,
173
- 187,
174
- 204,
175
- 221,
176
- 238,
177
- 255,
178
- 0,
179
- 16,
180
- 32,
181
- 48,
182
- 64,
183
- 80,
184
- 96,
185
- 112,
186
- 128,
187
- 144,
188
- 160,
189
- 176,
190
- 192,
191
- 208,
192
- 224,
193
- 240,
194
- 0
195
- ]);
196
144
  const encryptedBuffer = await eccrypto.encrypt(
197
145
  uncompressedKey,
198
- Buffer.from(data),
199
- {
200
- iv,
201
- ephemPrivateKey: ephemeralKey
202
- }
146
+ Buffer.from(data)
203
147
  );
204
148
  const result = Buffer.concat([
205
149
  encryptedBuffer.iv,
@@ -446,6 +390,9 @@ function isWalletConfig(config) {
446
390
  function isChainConfig(config) {
447
391
  return "chainId" in config && !("walletClient" in config);
448
392
  }
393
+ function hasStorageConfig(config) {
394
+ return config.storage?.providers !== void 0 && Object.keys(config.storage.providers).length > 0;
395
+ }
449
396
 
450
397
  // src/types/chains.ts
451
398
  function isVanaChainId(chainId) {
@@ -741,55 +688,6 @@ function isReplicateAPIResponse(value) {
741
688
  obj.status
742
689
  );
743
690
  }
744
- function isIdentityServerOutput(value) {
745
- console.debug("\u{1F50D} Type Guard: Checking value:", value);
746
- console.debug("\u{1F50D} Type Guard: Value type:", typeof value);
747
- if (typeof value !== "object" || value === null) {
748
- console.debug("\u{1F50D} Type Guard: Failed - not object or null");
749
- return false;
750
- }
751
- const obj = value;
752
- console.debug("\u{1F50D} Type Guard: Object keys:", Object.keys(obj));
753
- console.debug(
754
- "\u{1F50D} Type Guard: user_address:",
755
- obj.user_address,
756
- typeof obj.user_address
757
- );
758
- if (typeof obj.user_address !== "string") {
759
- console.debug("\u{1F50D} Type Guard: Failed - user_address not string");
760
- return false;
761
- }
762
- console.debug(
763
- "\u{1F50D} Type Guard: personal_server:",
764
- obj.personal_server,
765
- typeof obj.personal_server
766
- );
767
- if (typeof obj.personal_server !== "object" || obj.personal_server === null) {
768
- console.debug("\u{1F50D} Type Guard: Failed - personal_server not object or null");
769
- return false;
770
- }
771
- const personalServer = obj.personal_server;
772
- console.debug(
773
- "\u{1F50D} Type Guard: Personal server keys:",
774
- Object.keys(personalServer)
775
- );
776
- console.debug("\u{1F50D} Type Guard: address:", personalServer.address);
777
- console.debug("\u{1F50D} Type Guard: public_key:", personalServer.public_key);
778
- const hasAddress = "address" in personalServer;
779
- const hasPublicKey = "public_key" in personalServer;
780
- console.debug(
781
- "\u{1F50D} Type Guard: Has address:",
782
- hasAddress,
783
- "Has public_key:",
784
- hasPublicKey
785
- );
786
- return hasAddress && hasPublicKey;
787
- }
788
- function isPersonalServerOutput(value) {
789
- if (typeof value !== "object" || value === null) return false;
790
- const obj = value;
791
- return "user_address" in obj && "identity" in obj && typeof obj.user_address === "string" && typeof obj.identity === "object";
792
- }
793
691
  function isAPIResponse(value) {
794
692
  if (typeof value !== "object" || value === null) return false;
795
693
  const obj = value;
@@ -33382,7 +33280,7 @@ function getAbi(contract) {
33382
33280
  import { keccak256, toHex } from "viem";
33383
33281
  function createGrantFile(params) {
33384
33282
  const grantFile = {
33385
- grantee: params.to,
33283
+ grantee: params.grantee,
33386
33284
  operation: params.operation,
33387
33285
  parameters: params.parameters
33388
33286
  };
@@ -33802,7 +33700,7 @@ var PermissionsController = class {
33802
33700
  * @example
33803
33701
  * ```typescript
33804
33702
  * const txHash = await vana.permissions.grant({
33805
- * to: "0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36",
33703
+ * grantee: "0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36",
33806
33704
  * operation: "llm_inference",
33807
33705
  * parameters: {
33808
33706
  * model: "gpt-4",
@@ -33831,7 +33729,7 @@ var PermissionsController = class {
33831
33729
  * @example
33832
33730
  * ```typescript
33833
33731
  * const { preview, confirm } = await vana.permissions.prepareGrant({
33834
- * to: "0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36",
33732
+ * grantee: "0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36",
33835
33733
  * operation: "llm_inference",
33836
33734
  * files: [1, 2, 3],
33837
33735
  * parameters: { model: "gpt-4", prompt: "Analyze my social media data" }
@@ -33880,9 +33778,13 @@ var PermissionsController = class {
33880
33778
  console.debug("\u{1F50D} Debug - Grant URL from params:", grantUrl);
33881
33779
  if (!grantUrl) {
33882
33780
  if (!this.context.relayerCallbacks?.storeGrantFile && !this.context.storageManager) {
33883
- throw new Error(
33884
- "No storage available. Provide a grantUrl, configure relayerCallbacks.storeGrantFile, or storageManager."
33885
- );
33781
+ if (this.context.validateStorageRequired) {
33782
+ this.context.validateStorageRequired();
33783
+ } else {
33784
+ throw new Error(
33785
+ "No storage available. Provide a grantUrl, configure relayerCallbacks.storeGrantFile, or storageManager."
33786
+ );
33787
+ }
33886
33788
  }
33887
33789
  if (this.context.relayerCallbacks?.storeGrantFile) {
33888
33790
  grantUrl = await this.context.relayerCallbacks.storeGrantFile(grantFile);
@@ -33906,7 +33808,7 @@ var PermissionsController = class {
33906
33808
  grantUrl
33907
33809
  );
33908
33810
  const typedData = await this.composePermissionGrantMessage({
33909
- to: params.to,
33811
+ grantee: params.grantee,
33910
33812
  operation: params.operation,
33911
33813
  // Placeholder - real data is in IPFS
33912
33814
  files: params.files,
@@ -33953,7 +33855,7 @@ var PermissionsController = class {
33953
33855
  * @example
33954
33856
  * ```typescript
33955
33857
  * const { typedData, signature } = await vana.permissions.createAndSign({
33956
- * to: "0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36",
33858
+ * grantee: "0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36",
33957
33859
  * operation: "data_analysis",
33958
33860
  * parameters: { analysisType: "sentiment" },
33959
33861
  * });
@@ -33969,9 +33871,13 @@ var PermissionsController = class {
33969
33871
  console.debug("\u{1F50D} Debug - Grant URL from params:", grantUrl);
33970
33872
  if (!grantUrl) {
33971
33873
  if (!this.context.relayerCallbacks?.storeGrantFile && !this.context.storageManager) {
33972
- throw new Error(
33973
- "No storage available. Provide a grantUrl, configure relayerCallbacks.storeGrantFile, or storageManager."
33974
- );
33874
+ if (this.context.validateStorageRequired) {
33875
+ this.context.validateStorageRequired();
33876
+ } else {
33877
+ throw new Error(
33878
+ "No storage available. Provide a grantUrl, configure relayerCallbacks.storeGrantFile, or storageManager."
33879
+ );
33880
+ }
33975
33881
  }
33976
33882
  if (this.context.relayerCallbacks?.storeGrantFile) {
33977
33883
  grantUrl = await this.context.relayerCallbacks.storeGrantFile(grantFile);
@@ -33995,7 +33901,7 @@ var PermissionsController = class {
33995
33901
  grantUrl
33996
33902
  );
33997
33903
  const typedData = await this.composePermissionGrantMessage({
33998
- to: params.to,
33904
+ grantee: params.grantee,
33999
33905
  operation: params.operation,
34000
33906
  // Placeholder - real data is in IPFS
34001
33907
  files: params.files,
@@ -34346,7 +34252,7 @@ var PermissionsController = class {
34346
34252
  * Composes the EIP-712 typed data for PermissionGrant (new simplified format).
34347
34253
  *
34348
34254
  * @param params - The parameters for composing the permission grant message
34349
- * @param params.to - The recipient address for the permission grant
34255
+ * @param params.grantee - The recipient address for the permission grant
34350
34256
  * @param params.operation - The type of operation being granted permission for
34351
34257
  * @param params.files - Array of file IDs that the permission applies to
34352
34258
  * @param params.grantUrl - URL where the grant details are stored
@@ -34439,36 +34345,45 @@ var PermissionsController = class {
34439
34345
  return addresses[0];
34440
34346
  }
34441
34347
  /**
34442
- * Retrieves all permissions granted by the current user using subgraph queries.
34348
+ * Gets on-chain permission grant data without expensive off-chain resolution.
34443
34349
  *
34444
34350
  * @remarks
34445
- * This method queries the Vana subgraph to find permissions directly granted by the user
34446
- * using the Permission entity. It efficiently handles millions of permissions by leveraging
34447
- * indexed subgraph data instead of scanning contract logs. The method fetches complete
34448
- * grant files from IPFS to provide detailed permission information including operation
34449
- * parameters and grantee details.
34450
- * @param params - Optional query parameters
34451
- * @param params.limit - Maximum number of permissions to return (default: 50)
34452
- * @param params.subgraphUrl - Optional subgraph URL to override the default endpoint
34453
- * @returns A Promise that resolves to an array of `GrantedPermission` objects
34454
- * @throws {BlockchainError} When subgraph is unavailable or returns invalid data
34351
+ * This method provides a fast, performance-focused way to retrieve permission grants
34352
+ * by querying only the subgraph without making expensive IPFS or individual contract calls.
34353
+ * It eliminates the N+1 query problem of the legacy `getUserPermissions()` method.
34354
+ *
34355
+ * The returned data contains all on-chain information but does NOT include resolved
34356
+ * operation details, parameters, or file IDs. Use `retrieveGrantFile()` separately
34357
+ * for specific grants when detailed data is needed.
34358
+ *
34359
+ * **Performance**: Completes in ~100-500ms regardless of permission count.
34360
+ * **Reliability**: Single point of failure (subgraph) with clear RPC fallback path.
34361
+ *
34362
+ * @param options - Options for retrieving permissions (limit, subgraph URL)
34363
+ * @returns A Promise that resolves to an array of `OnChainPermissionGrant` objects
34364
+ * @throws {BlockchainError} When subgraph query fails
34365
+ * @throws {NetworkError} When network requests fail
34455
34366
  * @example
34456
34367
  * ```typescript
34457
- * // Get all permissions granted by current user
34458
- * const permissions = await vana.permissions.getUserPermissions();
34368
+ * // Fast: Get all on-chain permission data
34369
+ * const grants = await vana.permissions.getUserPermissionGrantsOnChain({ limit: 20 });
34459
34370
  *
34460
- * permissions.forEach(permission => {
34461
- * console.log(`Granted ${permission.operation} to ${permission.grantee}`);
34371
+ * // Display in UI immediately
34372
+ * grants.forEach(grant => {
34373
+ * console.log(`Permission ${grant.id}: ${grant.grantUrl}`);
34462
34374
  * });
34463
34375
  *
34464
- * // Limit results
34465
- * const recent = await vana.permissions.getUserPermissions({ limit: 10 });
34376
+ * // Lazy load detailed data for specific permission when user clicks
34377
+ * const grantFile = await retrieveGrantFile(grants[0].grantUrl);
34378
+ * console.log(`Operation: ${grantFile.operation}`);
34379
+ * console.log(`Parameters:`, grantFile.parameters);
34466
34380
  * ```
34467
34381
  */
34468
- async getUserPermissions(params) {
34382
+ async getUserPermissionGrantsOnChain(options = {}) {
34383
+ const { limit = 50, subgraphUrl } = options;
34469
34384
  try {
34470
34385
  const userAddress = await this.getUserAddress();
34471
- const graphqlEndpoint = params?.subgraphUrl || this.context.subgraphUrl;
34386
+ const graphqlEndpoint = subgraphUrl || this.context.subgraphUrl;
34472
34387
  if (!graphqlEndpoint) {
34473
34388
  throw new BlockchainError(
34474
34389
  "subgraphUrl is required. Please provide a valid subgraph endpoint or configure it in Vana constructor."
@@ -34490,16 +34405,6 @@ var PermissionsController = class {
34490
34405
  }
34491
34406
  }
34492
34407
  `;
34493
- console.info("Query:", query);
34494
- console.info(
34495
- "Body:",
34496
- JSON.stringify({
34497
- query,
34498
- variables: {
34499
- userId: userAddress.toLowerCase()
34500
- }
34501
- })
34502
- );
34503
34408
  const response = await fetch(graphqlEndpoint, {
34504
34409
  method: "POST",
34505
34410
  headers: {
@@ -34518,7 +34423,6 @@ var PermissionsController = class {
34518
34423
  );
34519
34424
  }
34520
34425
  const result = await response.json();
34521
- console.info("Result:", result);
34522
34426
  if (result.errors) {
34523
34427
  throw new BlockchainError(
34524
34428
  `Subgraph errors: ${result.errors.map((e) => e.message).join(", ")}`
@@ -34526,64 +34430,32 @@ var PermissionsController = class {
34526
34430
  }
34527
34431
  const userData = result.data?.user;
34528
34432
  if (!userData || !userData.permissions?.length) {
34529
- console.warn("No permissions found for user:", userAddress);
34530
34433
  return [];
34531
34434
  }
34532
- const userPermissions = [];
34533
- const limit = params?.limit || 50;
34534
- const permissionsToProcess = userData.permissions.slice(0, limit);
34535
- for (const permission of permissionsToProcess) {
34536
- try {
34537
- let operation;
34538
- let files = [];
34539
- let parameters;
34540
- let granteeAddress;
34541
- try {
34542
- const grantFile = await retrieveGrantFile(permission.grant);
34543
- operation = grantFile.operation;
34544
- parameters = grantFile.parameters;
34545
- granteeAddress = grantFile.grantee;
34546
- } catch {
34547
- }
34548
- try {
34549
- const fileIds = await this.getPermissionFileIds(
34550
- BigInt(permission.id)
34551
- );
34552
- files = fileIds.map((id) => Number(id));
34553
- } catch {
34554
- }
34555
- userPermissions.push({
34556
- id: BigInt(permission.id),
34557
- files,
34558
- operation: operation || "",
34559
- parameters: parameters || {},
34560
- grant: permission.grant,
34561
- grantor: userAddress.toLowerCase(),
34562
- // Current user is the grantor
34563
- grantee: granteeAddress || userAddress,
34564
- // Application that received permission
34565
- active: true,
34566
- // Default to active if not specified
34567
- grantedAt: Number(permission.addedAtBlock),
34568
- nonce: Number(permission.nonce)
34569
- });
34570
- } catch (error) {
34571
- console.error(
34572
- "SDK Error: Failed to process permission:",
34573
- permission.id,
34574
- error
34575
- );
34576
- }
34577
- }
34578
- return userPermissions.sort((a, b) => {
34435
+ const onChainGrants = userData.permissions.slice(0, limit).map((permission) => ({
34436
+ id: BigInt(permission.id),
34437
+ grantUrl: permission.grant,
34438
+ grantSignature: permission.grantSignature,
34439
+ grantHash: permission.grantHash,
34440
+ nonce: BigInt(permission.nonce),
34441
+ addedAtBlock: BigInt(permission.addedAtBlock),
34442
+ addedAtTimestamp: BigInt(permission.addedAtTimestamp || "0"),
34443
+ transactionHash: permission.transactionHash || "",
34444
+ grantor: userAddress,
34445
+ active: true
34446
+ // TODO: Add revocation status from subgraph when available
34447
+ }));
34448
+ return onChainGrants.sort((a, b) => {
34579
34449
  if (a.id < b.id) return 1;
34580
34450
  if (a.id > b.id) return -1;
34581
34451
  return 0;
34582
34452
  });
34583
34453
  } catch (error) {
34584
- console.error("Failed to fetch user permissions:", error);
34454
+ if (error instanceof BlockchainError || error instanceof NetworkError) {
34455
+ throw error;
34456
+ }
34585
34457
  throw new BlockchainError(
34586
- `Failed to fetch user permissions: ${error instanceof Error ? error.message : "Unknown error"}`
34458
+ `Failed to fetch user permission grants: ${error instanceof Error ? error.message : "Unknown error"}`
34587
34459
  );
34588
34460
  }
34589
34461
  }
@@ -35345,32 +35217,19 @@ async function decryptWithPrivateKey(encryptedData, privateKey, platformAdapter)
35345
35217
  throw new Error(`Failed to decrypt with private key: ${error}`);
35346
35218
  }
35347
35219
  }
35348
- async function encryptUserData(data, walletSignature, platformAdapter) {
35220
+ async function encryptBlobWithSignedKey(data, key, platformAdapter) {
35349
35221
  try {
35350
35222
  const dataBuffer = data instanceof Blob ? await data.arrayBuffer() : new TextEncoder().encode(data);
35351
35223
  const dataArray = new Uint8Array(dataBuffer);
35352
35224
  const encrypted = await platformAdapter.crypto.encryptWithPassword(
35353
35225
  dataArray,
35354
- walletSignature
35226
+ key
35355
35227
  );
35356
35228
  return new Blob([encrypted], {
35357
35229
  type: "application/octet-stream"
35358
35230
  });
35359
35231
  } catch (error) {
35360
- throw new Error(`Failed to encrypt user data: ${error}`);
35361
- }
35362
- }
35363
- async function decryptUserData(encryptedData, walletSignature, platformAdapter) {
35364
- try {
35365
- const encryptedBuffer = encryptedData instanceof Blob ? await encryptedData.arrayBuffer() : new TextEncoder().encode(encryptedData);
35366
- const encryptedArray = new Uint8Array(encryptedBuffer);
35367
- const decrypted = await platformAdapter.crypto.decryptWithPassword(
35368
- encryptedArray,
35369
- walletSignature
35370
- );
35371
- return new Blob([decrypted], { type: "text/plain" });
35372
- } catch (error) {
35373
- throw new Error(`Failed to decrypt user data: ${error}`);
35232
+ throw new Error(`Failed to encrypt data: ${error}`);
35374
35233
  }
35375
35234
  }
35376
35235
  async function generateEncryptionKeyPair(platformAdapter) {
@@ -35387,60 +35246,25 @@ async function generatePGPKeyPair(platformAdapter, options) {
35387
35246
  throw new Error(`Failed to generate PGP key pair: ${error}`);
35388
35247
  }
35389
35248
  }
35249
+ async function decryptBlobWithSignedKey(encryptedData, key, platformAdapter) {
35250
+ try {
35251
+ const encryptedBuffer = encryptedData instanceof Blob ? await encryptedData.arrayBuffer() : new TextEncoder().encode(encryptedData);
35252
+ const encryptedArray = new Uint8Array(encryptedBuffer);
35253
+ const decrypted = await platformAdapter.crypto.decryptWithPassword(
35254
+ encryptedArray,
35255
+ key
35256
+ );
35257
+ return new Blob([decrypted], { type: "text/plain" });
35258
+ } catch (error) {
35259
+ throw new Error(`Failed to decrypt data: ${error}`);
35260
+ }
35261
+ }
35390
35262
 
35391
35263
  // src/controllers/data.ts
35392
35264
  var DataController = class {
35393
35265
  constructor(context) {
35394
35266
  this.context = context;
35395
35267
  }
35396
- /**
35397
- * Uploads user data with automatic encryption and blockchain registration.
35398
- *
35399
- * @remarks
35400
- * This is the primary method for uploading user data to the Vana network. It handles
35401
- * the complete workflow including content normalization, schema validation, encryption,
35402
- * storage upload, permission granting, and blockchain registration.
35403
- *
35404
- * The method automatically:
35405
- * - Normalizes input content to a Blob
35406
- * - Validates data against schema if provided
35407
- * - Generates encryption keys and encrypts the data
35408
- * - Uploads to the configured storage provider
35409
- * - Grants permissions to specified applications
35410
- * - Registers the file on the blockchain
35411
- *
35412
- * @param params - Upload parameters including content, filename, schema, and permissions
35413
- * @returns Promise resolving to upload results with file ID and transaction hash
35414
- * @throws {Error} When wallet is not connected or storage is not configured
35415
- * @throws {SchemaValidationError} When schema validation fails
35416
- * @throws {Error} When upload or blockchain registration fails
35417
- * @example
35418
- * ```typescript
35419
- * // Basic file upload
35420
- * const result = await vana.data.upload({
35421
- * content: "My personal data",
35422
- * filename: "diary.txt"
35423
- * });
35424
- *
35425
- * // Upload with schema validation
35426
- * const result = await vana.data.upload({
35427
- * content: { name: "John", age: 30 },
35428
- * filename: "profile.json",
35429
- * schemaId: 1
35430
- * });
35431
- *
35432
- * // Upload with permissions
35433
- * const result = await vana.data.upload({
35434
- * content: "Data for AI analysis",
35435
- * filename: "analysis.txt",
35436
- * permissions: [{
35437
- * to: "0x1234...",
35438
- * operation: "llm_inference",
35439
- * parameters: { model: "gpt-4" }
35440
- * }]
35441
- * });
35442
- * ```
35443
- */
35444
35268
  async upload(params) {
35445
35269
  const {
35446
35270
  content,
@@ -35448,7 +35272,8 @@ var DataController = class {
35448
35272
  schemaId,
35449
35273
  permissions = [],
35450
35274
  encrypt: encrypt2 = true,
35451
- providerName
35275
+ providerName,
35276
+ owner
35452
35277
  } = params;
35453
35278
  try {
35454
35279
  let blob;
@@ -35505,23 +35330,28 @@ var DataController = class {
35505
35330
  this.context.walletClient,
35506
35331
  DEFAULT_ENCRYPTION_SEED
35507
35332
  );
35508
- finalBlob = await encryptUserData(
35333
+ finalBlob = await encryptBlobWithSignedKey(
35509
35334
  blob,
35510
35335
  encryptionKey,
35511
35336
  this.context.platform
35512
35337
  );
35513
35338
  }
35514
35339
  if (!this.context.storageManager) {
35515
- throw new Error(
35516
- "Storage manager not configured. Please provide storage providers in VanaConfig."
35517
- );
35340
+ if (this.context.validateStorageRequired) {
35341
+ this.context.validateStorageRequired();
35342
+ throw new Error("Storage validation failed");
35343
+ } else {
35344
+ throw new Error(
35345
+ "Storage manager not configured. Please provide storage providers in VanaConfig."
35346
+ );
35347
+ }
35518
35348
  }
35519
35349
  const uploadResult = await this.context.storageManager.upload(
35520
35350
  finalBlob,
35521
35351
  filename,
35522
35352
  providerName
35523
35353
  );
35524
- const userAddress = await this.getUserAddress();
35354
+ const userAddress = owner || await this.getUserAddress();
35525
35355
  let encryptedPermissions = [];
35526
35356
  if (permissions.length > 0 && encrypt2) {
35527
35357
  const userEncryptionKey = await generateEncryptionKey(
@@ -35533,7 +35363,7 @@ var DataController = class {
35533
35363
  const publicKey = permission.publicKey;
35534
35364
  if (!publicKey) {
35535
35365
  throw new Error(
35536
- `Public key required for permission to ${permission.to}`
35366
+ `Public key required for permission to ${permission.grantee} when encryption is enabled`
35537
35367
  );
35538
35368
  }
35539
35369
  const encryptedKey = await encryptWithWalletPublicKey(
@@ -35542,7 +35372,7 @@ var DataController = class {
35542
35372
  this.context.platform
35543
35373
  );
35544
35374
  return {
35545
- account: permission.to,
35375
+ account: permission.grantee,
35546
35376
  key: encryptedKey
35547
35377
  };
35548
35378
  })
@@ -35555,7 +35385,8 @@ var DataController = class {
35555
35385
  url: uploadResult.url,
35556
35386
  userAddress,
35557
35387
  permissions: encryptedPermissions,
35558
- schemaId: schemaId || 0
35388
+ schemaId: schemaId || 0,
35389
+ ownerAddress: owner
35559
35390
  }
35560
35391
  );
35561
35392
  } else if (this.context.relayerCallbacks?.submitFileAddition) {
@@ -35591,6 +35422,142 @@ var DataController = class {
35591
35422
  );
35592
35423
  }
35593
35424
  }
35425
+ /**
35426
+ * Decrypts a file owned by the user using their wallet signature.
35427
+ *
35428
+ * @remarks
35429
+ * This is the high-level convenience method for decrypting user files, serving as the
35430
+ * symmetrical counterpart to the `upload` method. It handles the complete decryption
35431
+ * workflow including key generation, URL protocol detection, content fetching, and
35432
+ * decryption.
35433
+ *
35434
+ * The method automatically:
35435
+ * - Generates the decryption key from the user's wallet signature
35436
+ * - Determines the appropriate fetch method based on the file URL protocol
35437
+ * - Fetches the encrypted content from IPFS or standard HTTP URLs
35438
+ * - Decrypts the content using the generated key
35439
+ *
35440
+ * For IPFS URLs, the method uses gateway fallback for improved reliability. For
35441
+ * standard HTTP URLs, it uses a simple fetch. If you need custom authentication
35442
+ * headers or specific gateway configurations, use the low-level primitives directly.
35443
+ *
35444
+ * @param file - The user file to decrypt (typically from getUserFiles)
35445
+ * @param encryptionSeed - Optional custom encryption seed (defaults to Vana standard)
35446
+ * @returns Promise resolving to the decrypted file content as a Blob
35447
+ * @throws {Error} When the wallet is not connected
35448
+ * @throws {Error} When fetching the encrypted content fails
35449
+ * @throws {Error} When decryption fails (wrong key or corrupted data)
35450
+ * @example
35451
+ * ```typescript
35452
+ * // Basic file decryption
35453
+ * const files = await vana.data.getUserFiles({ owner: userAddress });
35454
+ * const decryptedBlob = await vana.data.decryptFile(files[0]);
35455
+ *
35456
+ * // Convert to text
35457
+ * const text = await decryptedBlob.text();
35458
+ * console.log('Decrypted content:', text);
35459
+ *
35460
+ * // Convert to JSON
35461
+ * const json = JSON.parse(await decryptedBlob.text());
35462
+ * console.log('Decrypted data:', json);
35463
+ *
35464
+ * // With custom encryption seed
35465
+ * const decryptedBlob = await vana.data.decryptFile(
35466
+ * files[0],
35467
+ * "My custom encryption seed"
35468
+ * );
35469
+ *
35470
+ * // Save to file (in Node.js)
35471
+ * const buffer = await decryptedBlob.arrayBuffer();
35472
+ * fs.writeFileSync('decrypted-file.txt', Buffer.from(buffer));
35473
+ * ```
35474
+ */
35475
+ async decryptFile(file, encryptionSeed) {
35476
+ try {
35477
+ const encryptionKey = await generateEncryptionKey(
35478
+ this.context.walletClient,
35479
+ encryptionSeed || DEFAULT_ENCRYPTION_SEED
35480
+ );
35481
+ let encryptedBlob;
35482
+ try {
35483
+ if (file.url.startsWith("ipfs://")) {
35484
+ encryptedBlob = await this.fetchFromIPFS(file.url);
35485
+ } else {
35486
+ encryptedBlob = await this.fetch(file.url);
35487
+ }
35488
+ } catch (fetchError) {
35489
+ const errorMessage = fetchError instanceof Error ? fetchError.message : "Unknown error";
35490
+ if (errorMessage.includes("Failed to fetch IPFS content") && errorMessage.includes("from all gateways")) {
35491
+ throw new Error(
35492
+ "Network error: Cannot access the file URL. The file may be stored on a server that's not accessible or has CORS restrictions."
35493
+ );
35494
+ } else if (errorMessage.includes("Empty response")) {
35495
+ throw new Error("File is empty or could not be retrieved");
35496
+ } else if (errorMessage.includes("Network error:") || errorMessage.includes("Failed to fetch")) {
35497
+ throw new Error(
35498
+ "Network error: Cannot access the file URL. The file may be stored on a server that's not accessible or has CORS restrictions."
35499
+ );
35500
+ } else if (errorMessage.includes("HTTP error!")) {
35501
+ const statusMatch = errorMessage.match(/status: (\d+)/);
35502
+ const status = statusMatch ? statusMatch[1] : "unknown";
35503
+ if (status === "500") {
35504
+ throw new Error(
35505
+ "Network error: Cannot access the file URL. The file may be stored on a server that's not accessible or has CORS restrictions."
35506
+ );
35507
+ } else if (status === "403") {
35508
+ throw new Error(
35509
+ "Access denied. You may not have permission to access this file"
35510
+ );
35511
+ } else if (status === "404") {
35512
+ throw new Error(
35513
+ "File not found: The encrypted file is no longer available at the stored URL."
35514
+ );
35515
+ } else {
35516
+ throw new Error(
35517
+ "Network error: Cannot access the file URL. The file may be stored on a server that's not accessible or has CORS restrictions."
35518
+ );
35519
+ }
35520
+ }
35521
+ throw fetchError;
35522
+ }
35523
+ if (encryptedBlob.size === 0) {
35524
+ throw new Error("File is empty or could not be retrieved");
35525
+ }
35526
+ let decryptedBlob;
35527
+ try {
35528
+ decryptedBlob = await decryptBlobWithSignedKey(
35529
+ encryptedBlob,
35530
+ encryptionKey,
35531
+ this.context.platform
35532
+ );
35533
+ } catch (decryptError) {
35534
+ const errorMessage = decryptError instanceof Error ? decryptError.message : "Unknown error";
35535
+ if (errorMessage.includes("not a valid OpenPGP message")) {
35536
+ throw new Error(
35537
+ "Invalid file format: This file doesn't appear to be encrypted with the Vana protocol"
35538
+ );
35539
+ } else if (errorMessage.includes("Session key decryption failed")) {
35540
+ throw new Error("Wrong encryption key");
35541
+ } else if (errorMessage.includes("Error decrypting message")) {
35542
+ throw new Error("Wrong encryption key");
35543
+ } else if (errorMessage.includes("File not found")) {
35544
+ throw new Error(
35545
+ "File not found: The encrypted file is no longer available"
35546
+ );
35547
+ } else {
35548
+ throw decryptError;
35549
+ }
35550
+ }
35551
+ return decryptedBlob;
35552
+ } catch (error) {
35553
+ if (error instanceof Error && (error.message.includes("Network error:") || error.message.includes("Invalid file format:") || error.message.includes("Wrong encryption key") || error.message.includes("Access denied") || error.message.includes("File not found:") || error.message.includes("File is empty"))) {
35554
+ throw error;
35555
+ }
35556
+ throw new Error(
35557
+ `Failed to decrypt file: ${error instanceof Error ? error.message : "Unknown error"}`
35558
+ );
35559
+ }
35560
+ }
35594
35561
  /**
35595
35562
  * Retrieves all data files owned by a specific user address.
35596
35563
  *
@@ -36341,81 +36308,6 @@ var DataController = class {
36341
36308
  );
36342
36309
  }
36343
36310
  }
36344
- /**
36345
- * Decrypts a file that was encrypted using the Vana protocol.
36346
- *
36347
- * @param file - The UserFile object containing the file URL and metadata
36348
- * @param encryptionSeed - Optional custom encryption seed (defaults to Vana standard)
36349
- * @returns Promise resolving to the decrypted file as a Blob
36350
- *
36351
- * This method handles the complete flow of:
36352
- * 1. Generating the encryption key from the user's wallet signature
36353
- * 2. Fetching the encrypted file from the stored URL
36354
- * 3. Decrypting the file using the canonical Vana decryption method
36355
- */
36356
- async decryptFile(file, encryptionSeed = DEFAULT_ENCRYPTION_SEED) {
36357
- try {
36358
- const encryptionKey = await generateEncryptionKey(
36359
- this.context.walletClient,
36360
- encryptionSeed
36361
- );
36362
- const fetchUrl = this.convertToDownloadUrl(file.url);
36363
- console.debug(
36364
- `Fetching encrypted file from URL: ${fetchUrl} (original: ${file.url})`
36365
- );
36366
- const response = await fetch(fetchUrl);
36367
- if (!response.ok) {
36368
- if (response.status === 404) {
36369
- throw new Error(
36370
- "File not found. The encrypted file may have been moved or deleted."
36371
- );
36372
- } else if (response.status === 403) {
36373
- throw new Error(
36374
- "Access denied. You may not have permission to access this file."
36375
- );
36376
- } else {
36377
- throw new Error(
36378
- `Network error: ${response.status} ${response.statusText}`
36379
- );
36380
- }
36381
- }
36382
- const encryptedBlob = await response.blob();
36383
- console.debug(
36384
- `Retrieved blob of size: ${encryptedBlob.size} bytes, type: ${encryptedBlob.type}`
36385
- );
36386
- if (encryptedBlob.size === 0) {
36387
- throw new Error("File is empty or could not be retrieved.");
36388
- }
36389
- const decryptedBlob = await decryptUserData(
36390
- encryptedBlob,
36391
- encryptionKey,
36392
- this.context.platform
36393
- );
36394
- return decryptedBlob;
36395
- } catch (error) {
36396
- console.error("Failed to decrypt file:", error);
36397
- if (error instanceof Error) {
36398
- if (error.message.includes("Session key decryption failed") || error.message.includes("Error decrypting message")) {
36399
- throw new Error(
36400
- "Wrong encryption key. This file may have been encrypted with a different wallet or encryption seed. Try using the same wallet that originally encrypted this file."
36401
- );
36402
- } else if (error.message.includes("Failed to fetch") || error.message.includes("Network error")) {
36403
- throw new Error(
36404
- "Network error: Cannot access the file URL. The file may be stored on a server that's not accessible or has CORS restrictions."
36405
- );
36406
- } else if (error.message.includes("File not found")) {
36407
- throw new Error(
36408
- "File not found: The encrypted file is no longer available at the stored URL."
36409
- );
36410
- } else if (error.message.includes("not a valid OpenPGP message") || error.message.includes("does not conform to a valid OpenPGP format")) {
36411
- throw new Error(
36412
- "Invalid file format: This file doesn't appear to be encrypted with the Vana protocol."
36413
- );
36414
- }
36415
- }
36416
- throw error;
36417
- }
36418
- }
36419
36311
  /**
36420
36312
  * Registers a file URL directly on the blockchain with a schema ID.
36421
36313
  *
@@ -36476,26 +36368,6 @@ var DataController = class {
36476
36368
  );
36477
36369
  }
36478
36370
  }
36479
- /**
36480
- * Converts IPFS and Google Drive URLs to direct download URLs for fetching.
36481
- *
36482
- * @param url - The URL to convert to a direct download URL
36483
- * @returns The converted direct download URL or the original URL if not a special URL
36484
- */
36485
- convertToDownloadUrl(url) {
36486
- if (url.startsWith("ipfs://")) {
36487
- const hash = url.replace("ipfs://", "");
36488
- return `https://ipfs.io/ipfs/${hash}`;
36489
- }
36490
- if (url.includes("drive.google.com/file/d/")) {
36491
- const fileIdMatch = url.match(/\/file\/d\/([a-zA-Z0-9-_]+)/);
36492
- if (fileIdMatch) {
36493
- const fileId = fileIdMatch[1];
36494
- return `https://drive.google.com/uc?id=${fileId}&export=download`;
36495
- }
36496
- }
36497
- return url;
36498
- }
36499
36371
  /**
36500
36372
  * Gets the user's address from the wallet client.
36501
36373
  *
@@ -36989,7 +36861,7 @@ var DataController = class {
36989
36861
  this.context.walletClient,
36990
36862
  DEFAULT_ENCRYPTION_SEED
36991
36863
  );
36992
- const encryptedData = await encryptUserData(
36864
+ const encryptedData = await encryptBlobWithSignedKey(
36993
36865
  data,
36994
36866
  userEncryptionKey,
36995
36867
  this.context.platform
@@ -37153,12 +37025,13 @@ var DataController = class {
37153
37025
  privateKey,
37154
37026
  this.context.platform
37155
37027
  );
37156
- const response = await fetch(this.convertToDownloadUrl(file.url));
37157
- if (!response.ok) {
37158
- throw new Error(`Failed to download file: ${response.statusText}`);
37028
+ let encryptedData;
37029
+ if (file.url.startsWith("ipfs://")) {
37030
+ encryptedData = await this.fetchFromIPFS(file.url);
37031
+ } else {
37032
+ encryptedData = await this.fetch(file.url);
37159
37033
  }
37160
- const encryptedData = await response.blob();
37161
- const decryptedData = await decryptUserData(
37034
+ const decryptedData = await decryptBlobWithSignedKey(
37162
37035
  encryptedData,
37163
37036
  userEncryptionKey,
37164
37037
  this.context.platform
@@ -37171,6 +37044,162 @@ var DataController = class {
37171
37044
  );
37172
37045
  }
37173
37046
  }
37047
+ /**
37048
+ * Simple network-agnostic fetch utility for retrieving file content.
37049
+ *
37050
+ * @remarks
37051
+ * This is a thin wrapper around the global fetch API that returns the response as a Blob.
37052
+ * It provides a consistent interface for fetching encrypted content before decryption.
37053
+ * For IPFS URLs, consider using fetchFromIPFS for better reliability.
37054
+ *
37055
+ * @param url - The URL to fetch content from
37056
+ * @returns Promise resolving to the fetched content as a Blob
37057
+ * @throws {Error} When the fetch fails or returns a non-ok response
37058
+ *
37059
+ * @example
37060
+ * ```typescript
37061
+ * // Fetch and decrypt a file
37062
+ * const encryptionKey = await generateEncryptionKey(walletClient);
37063
+ * const encryptedBlob = await vana.data.fetch(file.url);
37064
+ * const decryptedBlob = await decryptBlob(encryptedBlob, encryptionKey, platform);
37065
+ *
37066
+ * // With custom headers for authentication
37067
+ * const response = await fetch(file.url, {
37068
+ * headers: { 'Authorization': 'Bearer token' }
37069
+ * });
37070
+ * const encryptedBlob = await response.blob();
37071
+ * ```
37072
+ */
37073
+ async fetch(url) {
37074
+ try {
37075
+ const response = await fetch(url);
37076
+ if (!response.ok) {
37077
+ throw new Error(
37078
+ `HTTP error! status: ${response.status} ${response.statusText}`
37079
+ );
37080
+ }
37081
+ const blob = await response.blob();
37082
+ if (blob.size === 0) {
37083
+ throw new Error("Empty response");
37084
+ }
37085
+ return blob;
37086
+ } catch (error) {
37087
+ if (error instanceof TypeError && error.message.includes("fetch")) {
37088
+ throw new Error(
37089
+ `Network error: Failed to fetch from ${url}. The URL may be invalid or the server may not be accessible.`
37090
+ );
37091
+ }
37092
+ throw error;
37093
+ }
37094
+ }
37095
+ /**
37096
+ * Specialized IPFS fetcher with gateway fallback mechanism.
37097
+ *
37098
+ * @remarks
37099
+ * This method provides robust IPFS content fetching by trying multiple gateways
37100
+ * in sequence until one succeeds. It supports both ipfs:// URLs and raw CIDs.
37101
+ *
37102
+ * The default gateway list includes public gateways, but you should provide
37103
+ * your own gateways for production use to ensure reliability and privacy.
37104
+ *
37105
+ * @param url - The IPFS URL (ipfs://...) or CID to fetch
37106
+ * @param options - Optional configuration
37107
+ * @param options.gateways - Array of IPFS gateway URLs to try (must end with /)
37108
+ * @returns Promise resolving to the fetched content as a Blob
37109
+ * @throws {Error} When all gateways fail to fetch the content
37110
+ *
37111
+ * @example
37112
+ * ```typescript
37113
+ * // Fetch from IPFS with custom gateways
37114
+ * const encryptedBlob = await vana.data.fetchFromIPFS(file.url, {
37115
+ * gateways: [
37116
+ * 'https://my-private-gateway.com/ipfs/',
37117
+ * 'https://dweb.link/ipfs/',
37118
+ * 'https://ipfs.io/ipfs/'
37119
+ * ]
37120
+ * });
37121
+ *
37122
+ * // Decrypt the fetched content
37123
+ * const encryptionKey = await generateEncryptionKey(walletClient);
37124
+ * const decryptedBlob = await decryptBlob(encryptedBlob, encryptionKey, platform);
37125
+ *
37126
+ * // With raw CID
37127
+ * const blob = await vana.data.fetchFromIPFS('QmXxx...', {
37128
+ * gateways: ['https://ipfs.io/ipfs/']
37129
+ * });
37130
+ * ```
37131
+ */
37132
+ async fetchFromIPFS(url, options) {
37133
+ const defaultGateways = [
37134
+ "https://dweb.link/ipfs/",
37135
+ "https://ipfs.io/ipfs/"
37136
+ ];
37137
+ const gateways = options?.gateways || this.context.ipfsGateways || defaultGateways;
37138
+ let cid;
37139
+ if (url.startsWith("ipfs://")) {
37140
+ cid = url.replace("ipfs://", "");
37141
+ } else if (url.startsWith("Qm") || url.startsWith("bafy")) {
37142
+ cid = url;
37143
+ } else {
37144
+ throw new Error(
37145
+ `Invalid IPFS URL format. Expected ipfs://... or a raw CID, got: ${url}`
37146
+ );
37147
+ }
37148
+ const errors = [];
37149
+ for (let i = 0; i < gateways.length; i++) {
37150
+ const gateway = gateways[i];
37151
+ const isLastGateway = i === gateways.length - 1;
37152
+ const gatewayUrl = gateway.endsWith("/") ? `${gateway}${cid}` : `${gateway}/${cid}`;
37153
+ try {
37154
+ console.debug(`Trying IPFS gateway: ${gatewayUrl}`);
37155
+ const response = await fetch(gatewayUrl);
37156
+ if (response.ok) {
37157
+ const blob = await response.blob();
37158
+ if (blob.size > 0) {
37159
+ console.debug(`Successfully fetched from gateway: ${gateway}`);
37160
+ return blob;
37161
+ } else {
37162
+ if (isLastGateway) {
37163
+ throw new Error("Empty response");
37164
+ }
37165
+ errors.push({
37166
+ gateway,
37167
+ error: "Empty response"
37168
+ });
37169
+ }
37170
+ } else {
37171
+ if (isLastGateway) {
37172
+ if (response.status === 403) {
37173
+ throw new Error(`HTTP error! status: 403 ${response.statusText}`);
37174
+ } else if (response.status === 404) {
37175
+ throw new Error(`HTTP error! status: 404 ${response.statusText}`);
37176
+ } else {
37177
+ throw new Error(
37178
+ `HTTP error! status: ${response.status} ${response.statusText}`
37179
+ );
37180
+ }
37181
+ }
37182
+ errors.push({
37183
+ gateway,
37184
+ error: `HTTP ${response.status} ${response.statusText}`
37185
+ });
37186
+ }
37187
+ } catch (error) {
37188
+ if (isLastGateway && error instanceof Error && (error.message.includes("Empty response") || error.message.includes("HTTP error!"))) {
37189
+ throw error;
37190
+ }
37191
+ errors.push({
37192
+ gateway,
37193
+ error: error instanceof Error ? error.message : "Unknown error"
37194
+ });
37195
+ }
37196
+ }
37197
+ const errorDetails = errors.map((e) => `${e.gateway}: ${e.error}`).join("\n ");
37198
+ throw new Error(
37199
+ `Failed to fetch IPFS content ${cid} from all gateways:
37200
+ ${errorDetails}`
37201
+ );
37202
+ }
37174
37203
  /**
37175
37204
  * Validates a data schema against the Vana meta-schema.
37176
37205
  *
@@ -37359,9 +37388,14 @@ var SchemaController = class {
37359
37388
  };
37360
37389
  validateDataSchema(dataSchema);
37361
37390
  if (!this.context.storageManager) {
37362
- throw new Error(
37363
- "Storage manager not configured. Please provide storage providers in VanaConfig."
37364
- );
37391
+ if (this.context.validateStorageRequired) {
37392
+ this.context.validateStorageRequired();
37393
+ throw new Error("Storage validation failed");
37394
+ } else {
37395
+ throw new Error(
37396
+ "Storage manager not configured. Please provide storage providers in VanaConfig."
37397
+ );
37398
+ }
37365
37399
  }
37366
37400
  const schemaBlob = new Blob([JSON.stringify(schemaDefinition)], {
37367
37401
  type: "application/json"
@@ -37633,6 +37667,7 @@ var ServerController = class {
37633
37667
  }
37634
37668
  const serverResponse = await response.json();
37635
37669
  return {
37670
+ kind: serverResponse.personal_server.kind,
37636
37671
  address: serverResponse.personal_server.address,
37637
37672
  public_key: serverResponse.personal_server.public_key,
37638
37673
  base_url: this.PERSONAL_SERVER_BASE_URL || "",
@@ -39246,175 +39281,151 @@ var PinataStorage = class {
39246
39281
  }
39247
39282
  };
39248
39283
 
39249
- // src/storage/providers/server-proxy.ts
39250
- var ServerProxyStorage = class {
39251
- constructor(config) {
39252
- this.config = config;
39253
- if (!config.uploadUrl) {
39254
- throw new StorageError(
39255
- "Upload URL is required",
39256
- "MISSING_UPLOAD_URL",
39257
- "server-proxy"
39258
- );
39259
- }
39260
- if (!config.downloadUrl) {
39261
- throw new StorageError(
39262
- "Download URL is required",
39263
- "MISSING_DOWNLOAD_URL",
39264
- "server-proxy"
39284
+ // src/storage/providers/callback-storage.ts
39285
+ var CallbackStorage = class {
39286
+ constructor(callbacks) {
39287
+ this.callbacks = callbacks;
39288
+ if (!callbacks.upload || !callbacks.download) {
39289
+ throw new Error(
39290
+ "CallbackStorage requires both upload and download callbacks"
39265
39291
  );
39266
39292
  }
39267
39293
  }
39268
39294
  /**
39269
- * Uploads a file through your server endpoint
39295
+ * Upload a file using the provided callback
39270
39296
  *
39271
- * @remarks
39272
- * This method sends the file to your configured upload endpoint via FormData.
39273
- * Your server is responsible for handling the actual storage implementation
39274
- * and must return a JSON response with `success: true` and an `identifier` field.
39275
- *
39276
- * @param file - The file to upload
39277
- * @param filename - Optional custom filename
39278
- * @returns Promise that resolves to the server-provided identifier
39279
- * @throws {StorageError} When the upload fails or server returns an error
39280
- *
39281
- * @example
39282
- * ```typescript
39283
- * const identifier = await serverStorage.upload(fileBlob, { name: "report.pdf" });
39284
- * console.log("File uploaded with identifier:", identifier);
39285
- * ```
39297
+ * @param file - The blob to upload
39298
+ * @param filename - Optional filename for the upload
39299
+ * @returns The upload result with URL and metadata
39286
39300
  */
39287
39301
  async upload(file, filename) {
39288
39302
  try {
39289
- const formData = new FormData();
39290
- formData.append("file", file);
39291
- if (filename) {
39292
- formData.append("name", filename);
39293
- }
39294
- const response = await fetch(this.config.uploadUrl, {
39295
- method: "POST",
39296
- body: formData
39297
- });
39298
- if (!response.ok) {
39299
- const _errorText = await response.text();
39300
- throw new StorageError(
39301
- `Server upload failed: ${response.status} ${response.statusText}`,
39302
- "UPLOAD_FAILED",
39303
- "server-proxy"
39304
- );
39305
- }
39306
- const result = await response.json();
39307
- if (!result.success) {
39303
+ const result = await this.callbacks.upload(file, filename);
39304
+ if (!result.url || result.url.trim() === "") {
39308
39305
  throw new StorageError(
39309
- `Upload failed: ${result.error || "Unknown server error"}`,
39310
- "UPLOAD_FAILED",
39311
- "server-proxy"
39306
+ "Upload callback returned invalid result: missing or empty url",
39307
+ "INVALID_UPLOAD_RESULT",
39308
+ "callback-storage"
39312
39309
  );
39313
39310
  }
39314
- if (!result.identifier) {
39315
- throw new StorageError(
39316
- "Server upload succeeded but no identifier returned",
39317
- "NO_IDENTIFIER_RETURNED",
39318
- "server-proxy"
39319
- );
39320
- }
39321
- return {
39322
- url: result.url || result.identifier,
39323
- size: file.size,
39324
- contentType: file.type || "application/octet-stream"
39325
- };
39311
+ return result;
39326
39312
  } catch (error) {
39327
39313
  if (error instanceof StorageError) {
39328
39314
  throw error;
39329
39315
  }
39330
39316
  throw new StorageError(
39331
- `Server proxy upload error: ${error instanceof Error ? error.message : "Unknown error"}`,
39317
+ `Upload failed: ${error instanceof Error ? error.message : String(error)}`,
39332
39318
  "UPLOAD_ERROR",
39333
- "server-proxy"
39319
+ "callback-storage",
39320
+ { cause: error instanceof Error ? error : void 0 }
39334
39321
  );
39335
39322
  }
39336
39323
  }
39337
39324
  /**
39338
- * Downloads a file through your server endpoint
39325
+ * Download a file using the provided callback
39339
39326
  *
39340
- * @remarks
39341
- * This method sends the identifier to your configured download endpoint via POST request.
39342
- * Your server is responsible for retrieving the file from your storage backend
39343
- * and returning the file content as a blob response.
39344
- *
39345
- * @param url - The server-provided URL or identifier from upload
39346
- * @returns Promise that resolves to the downloaded file content
39347
- * @throws {StorageError} When the download fails or file is not found
39348
- *
39349
- * @example
39350
- * ```typescript
39351
- * const fileBlob = await serverStorage.download("file-123");
39352
- * const url = URL.createObjectURL(fileBlob);
39353
- * ```
39327
+ * @param url - The URL or identifier to download
39328
+ * @returns The downloaded blob
39354
39329
  */
39355
39330
  async download(url) {
39356
39331
  try {
39357
- const identifier = this.extractIdentifierFromUrl(url);
39358
- const response = await fetch(this.config.downloadUrl, {
39359
- method: "POST",
39360
- headers: {
39361
- "Content-Type": "application/json"
39362
- },
39363
- body: JSON.stringify({ identifier })
39364
- });
39365
- if (!response.ok) {
39366
- const _errorText = await response.text();
39332
+ const identifier = this.callbacks.extractIdentifier ? this.callbacks.extractIdentifier(url) : url;
39333
+ const blob = await this.callbacks.download(identifier);
39334
+ if (!(blob instanceof Blob)) {
39367
39335
  throw new StorageError(
39368
- `Server download failed: ${response.status} ${response.statusText}`,
39369
- "DOWNLOAD_FAILED",
39370
- "server-proxy"
39336
+ "Download callback returned invalid result: expected Blob",
39337
+ "INVALID_DOWNLOAD_RESULT",
39338
+ "callback-storage"
39371
39339
  );
39372
39340
  }
39373
- return await response.blob();
39341
+ return blob;
39374
39342
  } catch (error) {
39375
39343
  if (error instanceof StorageError) {
39376
39344
  throw error;
39377
39345
  }
39378
39346
  throw new StorageError(
39379
- `Server proxy download error: ${error instanceof Error ? error.message : "Unknown error"}`,
39347
+ `Download failed: ${error instanceof Error ? error.message : String(error)}`,
39380
39348
  "DOWNLOAD_ERROR",
39381
- "server-proxy"
39349
+ "callback-storage",
39350
+ { cause: error instanceof Error ? error : void 0 }
39382
39351
  );
39383
39352
  }
39384
39353
  }
39385
- async list(_options) {
39386
- throw new StorageError(
39387
- "List operation is not supported by server proxy storage",
39388
- "LIST_NOT_SUPPORTED",
39389
- "server-proxy"
39390
- );
39391
- }
39392
- async delete(_url) {
39393
- throw new StorageError(
39394
- "Delete operation is not supported by server proxy storage",
39395
- "DELETE_NOT_SUPPORTED",
39396
- "server-proxy"
39397
- );
39354
+ /**
39355
+ * List files using the provided callback (if available)
39356
+ *
39357
+ * @param options - Optional list options including filters and pagination
39358
+ * @returns Array of storage files
39359
+ */
39360
+ async list(options) {
39361
+ if (!this.callbacks.list) {
39362
+ throw new StorageError(
39363
+ "List operation not supported - no list callback provided",
39364
+ "NOT_SUPPORTED",
39365
+ "callback-storage"
39366
+ );
39367
+ }
39368
+ try {
39369
+ const result = await this.callbacks.list(options?.namePattern, options);
39370
+ return result.items.map((item, index) => ({
39371
+ id: item.identifier,
39372
+ name: item.identifier.split("/").pop() || `file-${index}`,
39373
+ url: item.identifier,
39374
+ size: item.size || 0,
39375
+ contentType: "application/octet-stream",
39376
+ createdAt: item.lastModified || /* @__PURE__ */ new Date(),
39377
+ metadata: item.metadata
39378
+ }));
39379
+ } catch (error) {
39380
+ throw new StorageError(
39381
+ `List failed: ${error instanceof Error ? error.message : String(error)}`,
39382
+ "LIST_ERROR",
39383
+ "callback-storage",
39384
+ { cause: error instanceof Error ? error : void 0 }
39385
+ );
39386
+ }
39398
39387
  }
39399
39388
  /**
39400
- * Extract identifier from URL or return as-is
39389
+ * Delete a file using the provided callback (if available)
39401
39390
  *
39402
- * @param url - URL or identifier string
39403
- * @returns identifier string
39391
+ * @param url - The URL or identifier to delete
39392
+ * @returns True if deletion succeeded
39404
39393
  */
39405
- extractIdentifierFromUrl(url) {
39406
- return url;
39394
+ async delete(url) {
39395
+ if (!this.callbacks.delete) {
39396
+ throw new StorageError(
39397
+ "Delete operation not supported - no delete callback provided",
39398
+ "NOT_SUPPORTED",
39399
+ "callback-storage"
39400
+ );
39401
+ }
39402
+ try {
39403
+ const identifier = this.callbacks.extractIdentifier ? this.callbacks.extractIdentifier(url) : url;
39404
+ return await this.callbacks.delete(identifier);
39405
+ } catch (error) {
39406
+ throw new StorageError(
39407
+ `Delete failed: ${error instanceof Error ? error.message : String(error)}`,
39408
+ "DELETE_ERROR",
39409
+ "callback-storage",
39410
+ { cause: error instanceof Error ? error : void 0 }
39411
+ );
39412
+ }
39407
39413
  }
39414
+ /**
39415
+ * Get provider configuration
39416
+ *
39417
+ * @returns Provider configuration metadata
39418
+ */
39408
39419
  getConfig() {
39409
39420
  return {
39410
- name: "Server Proxy",
39411
- type: "server-proxy",
39421
+ name: "callback-storage",
39422
+ type: "callback",
39412
39423
  requiresAuth: false,
39413
39424
  features: {
39414
39425
  upload: true,
39415
39426
  download: true,
39416
- list: false,
39417
- delete: false
39427
+ list: !!this.callbacks.list,
39428
+ delete: !!this.callbacks.delete
39418
39429
  }
39419
39430
  };
39420
39431
  }
@@ -39646,6 +39657,47 @@ function getAllChains() {
39646
39657
  }
39647
39658
 
39648
39659
  // src/core.ts
39660
+ var VanaCoreFactory = class {
39661
+ /**
39662
+ * Creates a VanaCore instance that enforces storage requirements at compile time.
39663
+ * Use this factory when you know you'll need storage-dependent operations.
39664
+ *
39665
+ * @param platform - The platform adapter for environment-specific operations
39666
+ * @param config - Configuration that includes required storage providers
39667
+ * @returns VanaCore instance with storage validation
39668
+ * @example
39669
+ * ```typescript
39670
+ * const vanaCore = VanaCoreFactory.createWithStorage(platformAdapter, {
39671
+ * walletClient: myWalletClient,
39672
+ * storage: {
39673
+ * providers: { ipfs: new IPFSStorage() },
39674
+ * defaultProvider: 'ipfs'
39675
+ * }
39676
+ * });
39677
+ * ```
39678
+ */
39679
+ static createWithStorage(platform, config) {
39680
+ const core = new VanaCore(platform, config);
39681
+ return core;
39682
+ }
39683
+ /**
39684
+ * Creates a VanaCore instance without storage requirements.
39685
+ * Storage-dependent operations will fail at runtime if not configured.
39686
+ *
39687
+ * @param platform - The platform adapter for environment-specific operations
39688
+ * @param config - Basic configuration without required storage
39689
+ * @returns VanaCore instance
39690
+ * @example
39691
+ * ```typescript
39692
+ * const vanaCore = VanaCoreFactory.create(platformAdapter, {
39693
+ * walletClient: myWalletClient
39694
+ * });
39695
+ * ```
39696
+ */
39697
+ static create(platform, config) {
39698
+ return new VanaCore(platform, config);
39699
+ }
39700
+ };
39649
39701
  var VanaCore = class {
39650
39702
  /**
39651
39703
  * Initializes a new VanaCore client instance with the provided configuration.
@@ -39653,6 +39705,10 @@ var VanaCore = class {
39653
39705
  * @remarks
39654
39706
  * The constructor validates the configuration, initializes storage providers if configured,
39655
39707
  * creates wallet and public clients, and sets up all SDK controllers with shared context.
39708
+ *
39709
+ * IMPORTANT: This constructor will validate storage requirements at runtime to fail fast.
39710
+ * Methods that require storage will throw runtime errors if storage is not configured.
39711
+ *
39656
39712
  * @param platform - The platform adapter for environment-specific operations
39657
39713
  * @param config - The configuration object specifying wallet or chain settings
39658
39714
  * @throws {InvalidConfigurationError} When the configuration is invalid or incomplete
@@ -39679,9 +39735,13 @@ var VanaCore = class {
39679
39735
  __publicField(this, "platform");
39680
39736
  __publicField(this, "relayerCallbacks");
39681
39737
  __publicField(this, "storageManager");
39738
+ __publicField(this, "hasRequiredStorage");
39739
+ __publicField(this, "ipfsGateways");
39682
39740
  this.platform = platform;
39683
39741
  this.validateConfig(config);
39684
39742
  this.relayerCallbacks = config.relayerCallbacks;
39743
+ this.ipfsGateways = config.ipfsGateways;
39744
+ this.hasRequiredStorage = hasStorageConfig(config);
39685
39745
  if (config.storage?.providers) {
39686
39746
  this.storageManager = new StorageManager();
39687
39747
  for (const [name, provider] of Object.entries(config.storage.providers)) {
@@ -39736,8 +39796,11 @@ var VanaCore = class {
39736
39796
  relayerCallbacks: this.relayerCallbacks,
39737
39797
  storageManager: this.storageManager,
39738
39798
  subgraphUrl,
39739
- platform: this.platform
39799
+ platform: this.platform,
39740
39800
  // Pass the platform adapter to controllers
39801
+ validateStorageRequired: this.validateStorageRequired.bind(this),
39802
+ hasStorage: this.hasStorage.bind(this),
39803
+ ipfsGateways: this.ipfsGateways
39741
39804
  };
39742
39805
  this.permissions = new PermissionsController(sharedContext);
39743
39806
  this.data = new DataController(sharedContext);
@@ -39745,6 +39808,58 @@ var VanaCore = class {
39745
39808
  this.server = new ServerController(sharedContext);
39746
39809
  this.protocol = new ProtocolController(sharedContext);
39747
39810
  }
39811
+ /**
39812
+ * Validates that storage is available for storage-dependent operations.
39813
+ * This method enforces the fail-fast principle by checking storage availability
39814
+ * at method call time rather than during expensive operations.
39815
+ *
39816
+ * @throws {InvalidConfigurationError} When storage is required but not configured
39817
+ * @example
39818
+ * ```typescript
39819
+ * // This will throw if storage is not configured
39820
+ * vana.validateStorageRequired();
39821
+ * await vana.data.uploadFile(file); // Safe to proceed
39822
+ * ```
39823
+ */
39824
+ validateStorageRequired() {
39825
+ if (!this.hasRequiredStorage) {
39826
+ throw new InvalidConfigurationError(
39827
+ "Storage configuration is required for this operation. Please configure storage providers in VanaConfig.storage, provide a relayerCallbacks.storeGrantFile implementation, or pass pre-stored URLs to avoid this dependency. \n\nFor better type safety, consider using VanaCoreFactory.createWithStorage() with VanaConfigWithStorage to catch this error at compile time."
39828
+ );
39829
+ }
39830
+ }
39831
+ /**
39832
+ * Checks whether storage is configured without throwing an error.
39833
+ *
39834
+ * @returns True if storage is properly configured
39835
+ * @example
39836
+ * ```typescript
39837
+ * if (vana.hasStorage()) {
39838
+ * await vana.data.uploadFile(file);
39839
+ * } else {
39840
+ * console.warn('Storage not configured - using pre-stored URLs only');
39841
+ * }
39842
+ * ```
39843
+ */
39844
+ hasStorage() {
39845
+ return this.hasRequiredStorage;
39846
+ }
39847
+ /**
39848
+ * Type guard to check if this instance has storage enabled at compile time.
39849
+ * Use this when you need TypeScript to understand that storage is available.
39850
+ *
39851
+ * @returns True if storage is configured, with type narrowing
39852
+ * @example
39853
+ * ```typescript
39854
+ * if (vana.isStorageEnabled()) {
39855
+ * // TypeScript knows storage is available here
39856
+ * await vana.data.uploadFile(file);
39857
+ * }
39858
+ * ```
39859
+ */
39860
+ isStorageEnabled() {
39861
+ return this.hasRequiredStorage;
39862
+ }
39748
39863
  /**
39749
39864
  * Validates the provided configuration object against all requirements.
39750
39865
  *
@@ -39933,23 +40048,23 @@ var VanaCore = class {
39933
40048
  return this.platform;
39934
40049
  }
39935
40050
  /**
39936
- * Encrypts user data using the Vana protocol standard encryption.
40051
+ * Encrypts data using the Vana protocol standard encryption.
39937
40052
  * This method automatically uses the correct platform adapter for the current environment.
39938
40053
  *
39939
40054
  * @param data The data to encrypt (string or Blob)
39940
- * @param walletSignature The wallet signature to use as encryption key
40055
+ * @param key The key to use as encryption key
39941
40056
  * @returns The encrypted data as Blob
39942
40057
  * @example
39943
40058
  * ```typescript
39944
40059
  * const encryptionKey = await generateEncryptionKey(walletClient);
39945
- * const encrypted = await vana.encryptUserData("sensitive data", encryptionKey);
40060
+ * const encrypted = await vana.encryptBlob("sensitive data", encryptionKey);
39946
40061
  * ```
39947
40062
  */
39948
- async encryptUserData(data, walletSignature) {
39949
- return encryptUserData(data, walletSignature, this.platform);
40063
+ async encryptBlob(data, key) {
40064
+ return encryptBlobWithSignedKey(data, key, this.platform);
39950
40065
  }
39951
40066
  /**
39952
- * Decrypts user data using the Vana protocol standard decryption.
40067
+ * Decrypts data that was encrypted using the Vana protocol.
39953
40068
  * This method automatically uses the correct platform adapter for the current environment.
39954
40069
  *
39955
40070
  * @param encryptedData The encrypted data (string or Blob)
@@ -39958,12 +40073,16 @@ var VanaCore = class {
39958
40073
  * @example
39959
40074
  * ```typescript
39960
40075
  * const encryptionKey = await generateEncryptionKey(walletClient);
39961
- * const decrypted = await vana.decryptUserData(encryptedData, encryptionKey);
40076
+ * const decrypted = await vana.decryptBlob(encryptedData, encryptionKey);
39962
40077
  * const text = await decrypted.text();
39963
40078
  * ```
39964
40079
  */
39965
- async decryptUserData(encryptedData, walletSignature) {
39966
- return decryptUserData(encryptedData, walletSignature, this.platform);
40080
+ async decryptBlob(encryptedData, walletSignature) {
40081
+ return decryptBlobWithSignedKey(
40082
+ encryptedData,
40083
+ walletSignature,
40084
+ this.platform
40085
+ );
39967
40086
  }
39968
40087
  };
39969
40088
 
@@ -39994,7 +40113,7 @@ function createValidatedGrant(params) {
39994
40113
  try {
39995
40114
  validateGrant(grantFile, {
39996
40115
  schema: true,
39997
- grantee: params.to,
40116
+ grantee: params.grantee,
39998
40117
  operation: params.operation
39999
40118
  });
40000
40119
  } catch (error) {
@@ -40751,31 +40870,22 @@ var ApiClient = class {
40751
40870
  };
40752
40871
 
40753
40872
  // src/index.browser.ts
40754
- var VanaBrowser = class extends VanaCore {
40755
- /**
40756
- * Creates a Vana SDK instance configured for browser environments.
40757
- *
40758
- * @param config - SDK configuration object (wallet client or chain config)
40759
- * @example
40760
- * ```typescript
40761
- * // With wallet client
40762
- * const vana = new Vana({ walletClient });
40763
- *
40764
- * // With chain configuration
40765
- * const vana = new Vana({ chainId: 14800, account });
40766
- * ```
40767
- */
40873
+ var VanaBrowserImpl = class extends VanaCore {
40768
40874
  constructor(config) {
40769
40875
  super(new BrowserPlatformAdapter(), config);
40770
40876
  }
40771
40877
  };
40772
- var index_browser_default = VanaBrowser;
40878
+ function Vana(config) {
40879
+ return new VanaBrowserImpl(config);
40880
+ }
40881
+ var index_browser_default = Vana;
40773
40882
  export {
40774
40883
  ApiClient,
40775
40884
  AsyncQueue,
40776
40885
  BaseController,
40777
40886
  BlockchainError,
40778
40887
  BrowserPlatformAdapter,
40888
+ CallbackStorage,
40779
40889
  CircuitBreaker,
40780
40890
  ContractFactory,
40781
40891
  ContractNotFoundError,
@@ -40808,14 +40918,15 @@ export {
40808
40918
  SchemaValidator,
40809
40919
  SerializationError,
40810
40920
  ServerController,
40811
- ServerProxyStorage,
40812
40921
  ServerUrlMismatchError,
40813
40922
  SignatureError,
40814
40923
  StorageError,
40815
40924
  StorageManager,
40816
40925
  UserRejectedRequestError,
40817
- VanaBrowser as Vana,
40818
- VanaBrowser,
40926
+ Vana,
40927
+ VanaBrowserImpl,
40928
+ VanaCore,
40929
+ VanaCoreFactory,
40819
40930
  VanaError,
40820
40931
  __contractCache,
40821
40932
  chains,
@@ -40828,13 +40939,13 @@ export {
40828
40939
  createGrantFile,
40829
40940
  createPlatformAdapterSafe,
40830
40941
  createValidatedGrant,
40831
- decryptUserData,
40942
+ decryptBlobWithSignedKey,
40832
40943
  decryptWithPrivateKey,
40833
40944
  decryptWithWalletPrivateKey,
40834
40945
  index_browser_default as default,
40835
40946
  detectPlatform,
40947
+ encryptBlobWithSignedKey,
40836
40948
  encryptFileKey,
40837
- encryptUserData,
40838
40949
  encryptWithWalletPublicKey,
40839
40950
  extractIpfsHash,
40840
40951
  fetchAndValidateSchema,
@@ -40858,9 +40969,7 @@ export {
40858
40969
  getPlatformCapabilities,
40859
40970
  isAPIResponse,
40860
40971
  isGrantExpired,
40861
- isIdentityServerOutput,
40862
40972
  isIpfsUrl,
40863
- isPersonalServerOutput,
40864
40973
  isPlatformSupported,
40865
40974
  isReplicateAPIResponse,
40866
40975
  moksha,