@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.
@@ -251,65 +251,9 @@ var init_browser = __esm({
251
251
  try {
252
252
  const eccrypto2 = await import("eccrypto-js");
253
253
  const uncompressedKey = processWalletPublicKey(publicKey);
254
- const iv = Buffer.from([
255
- 1,
256
- 2,
257
- 3,
258
- 4,
259
- 5,
260
- 6,
261
- 7,
262
- 8,
263
- 9,
264
- 10,
265
- 11,
266
- 12,
267
- 13,
268
- 14,
269
- 15,
270
- 16
271
- ]);
272
- const ephemeralKey = Buffer.from([
273
- 17,
274
- 34,
275
- 51,
276
- 68,
277
- 85,
278
- 102,
279
- 119,
280
- 136,
281
- 153,
282
- 170,
283
- 187,
284
- 204,
285
- 221,
286
- 238,
287
- 255,
288
- 0,
289
- 16,
290
- 32,
291
- 48,
292
- 64,
293
- 80,
294
- 96,
295
- 112,
296
- 128,
297
- 144,
298
- 160,
299
- 176,
300
- 192,
301
- 208,
302
- 224,
303
- 240,
304
- 0
305
- ]);
306
254
  const encryptedBuffer = await eccrypto2.encrypt(
307
255
  uncompressedKey,
308
- Buffer.from(data),
309
- {
310
- iv,
311
- ephemPrivateKey: ephemeralKey
312
- }
256
+ Buffer.from(data)
313
257
  );
314
258
  const result = Buffer.concat([
315
259
  encryptedBuffer.iv,
@@ -678,6 +622,9 @@ function isWalletConfig(config) {
678
622
  function isChainConfig(config) {
679
623
  return "chainId" in config && !("walletClient" in config);
680
624
  }
625
+ function hasStorageConfig(config) {
626
+ return config.storage?.providers !== void 0 && Object.keys(config.storage.providers).length > 0;
627
+ }
681
628
 
682
629
  // src/types/chains.ts
683
630
  function isVanaChainId(chainId) {
@@ -973,55 +920,6 @@ function isReplicateAPIResponse(value) {
973
920
  obj.status
974
921
  );
975
922
  }
976
- function isIdentityServerOutput(value) {
977
- console.debug("\u{1F50D} Type Guard: Checking value:", value);
978
- console.debug("\u{1F50D} Type Guard: Value type:", typeof value);
979
- if (typeof value !== "object" || value === null) {
980
- console.debug("\u{1F50D} Type Guard: Failed - not object or null");
981
- return false;
982
- }
983
- const obj = value;
984
- console.debug("\u{1F50D} Type Guard: Object keys:", Object.keys(obj));
985
- console.debug(
986
- "\u{1F50D} Type Guard: user_address:",
987
- obj.user_address,
988
- typeof obj.user_address
989
- );
990
- if (typeof obj.user_address !== "string") {
991
- console.debug("\u{1F50D} Type Guard: Failed - user_address not string");
992
- return false;
993
- }
994
- console.debug(
995
- "\u{1F50D} Type Guard: personal_server:",
996
- obj.personal_server,
997
- typeof obj.personal_server
998
- );
999
- if (typeof obj.personal_server !== "object" || obj.personal_server === null) {
1000
- console.debug("\u{1F50D} Type Guard: Failed - personal_server not object or null");
1001
- return false;
1002
- }
1003
- const personalServer = obj.personal_server;
1004
- console.debug(
1005
- "\u{1F50D} Type Guard: Personal server keys:",
1006
- Object.keys(personalServer)
1007
- );
1008
- console.debug("\u{1F50D} Type Guard: address:", personalServer.address);
1009
- console.debug("\u{1F50D} Type Guard: public_key:", personalServer.public_key);
1010
- const hasAddress = "address" in personalServer;
1011
- const hasPublicKey = "public_key" in personalServer;
1012
- console.debug(
1013
- "\u{1F50D} Type Guard: Has address:",
1014
- hasAddress,
1015
- "Has public_key:",
1016
- hasPublicKey
1017
- );
1018
- return hasAddress && hasPublicKey;
1019
- }
1020
- function isPersonalServerOutput(value) {
1021
- if (typeof value !== "object" || value === null) return false;
1022
- const obj = value;
1023
- return "user_address" in obj && "identity" in obj && typeof obj.user_address === "string" && typeof obj.identity === "object";
1024
- }
1025
923
  function isAPIResponse(value) {
1026
924
  if (typeof value !== "object" || value === null) return false;
1027
925
  const obj = value;
@@ -33614,7 +33512,7 @@ function getAbi(contract) {
33614
33512
  import { keccak256, toHex } from "viem";
33615
33513
  function createGrantFile(params) {
33616
33514
  const grantFile = {
33617
- grantee: params.to,
33515
+ grantee: params.grantee,
33618
33516
  operation: params.operation,
33619
33517
  parameters: params.parameters
33620
33518
  };
@@ -34034,7 +33932,7 @@ var PermissionsController = class {
34034
33932
  * @example
34035
33933
  * ```typescript
34036
33934
  * const txHash = await vana.permissions.grant({
34037
- * to: "0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36",
33935
+ * grantee: "0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36",
34038
33936
  * operation: "llm_inference",
34039
33937
  * parameters: {
34040
33938
  * model: "gpt-4",
@@ -34063,7 +33961,7 @@ var PermissionsController = class {
34063
33961
  * @example
34064
33962
  * ```typescript
34065
33963
  * const { preview, confirm } = await vana.permissions.prepareGrant({
34066
- * to: "0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36",
33964
+ * grantee: "0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36",
34067
33965
  * operation: "llm_inference",
34068
33966
  * files: [1, 2, 3],
34069
33967
  * parameters: { model: "gpt-4", prompt: "Analyze my social media data" }
@@ -34112,9 +34010,13 @@ var PermissionsController = class {
34112
34010
  console.debug("\u{1F50D} Debug - Grant URL from params:", grantUrl);
34113
34011
  if (!grantUrl) {
34114
34012
  if (!this.context.relayerCallbacks?.storeGrantFile && !this.context.storageManager) {
34115
- throw new Error(
34116
- "No storage available. Provide a grantUrl, configure relayerCallbacks.storeGrantFile, or storageManager."
34117
- );
34013
+ if (this.context.validateStorageRequired) {
34014
+ this.context.validateStorageRequired();
34015
+ } else {
34016
+ throw new Error(
34017
+ "No storage available. Provide a grantUrl, configure relayerCallbacks.storeGrantFile, or storageManager."
34018
+ );
34019
+ }
34118
34020
  }
34119
34021
  if (this.context.relayerCallbacks?.storeGrantFile) {
34120
34022
  grantUrl = await this.context.relayerCallbacks.storeGrantFile(grantFile);
@@ -34138,7 +34040,7 @@ var PermissionsController = class {
34138
34040
  grantUrl
34139
34041
  );
34140
34042
  const typedData = await this.composePermissionGrantMessage({
34141
- to: params.to,
34043
+ grantee: params.grantee,
34142
34044
  operation: params.operation,
34143
34045
  // Placeholder - real data is in IPFS
34144
34046
  files: params.files,
@@ -34185,7 +34087,7 @@ var PermissionsController = class {
34185
34087
  * @example
34186
34088
  * ```typescript
34187
34089
  * const { typedData, signature } = await vana.permissions.createAndSign({
34188
- * to: "0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36",
34090
+ * grantee: "0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36",
34189
34091
  * operation: "data_analysis",
34190
34092
  * parameters: { analysisType: "sentiment" },
34191
34093
  * });
@@ -34201,9 +34103,13 @@ var PermissionsController = class {
34201
34103
  console.debug("\u{1F50D} Debug - Grant URL from params:", grantUrl);
34202
34104
  if (!grantUrl) {
34203
34105
  if (!this.context.relayerCallbacks?.storeGrantFile && !this.context.storageManager) {
34204
- throw new Error(
34205
- "No storage available. Provide a grantUrl, configure relayerCallbacks.storeGrantFile, or storageManager."
34206
- );
34106
+ if (this.context.validateStorageRequired) {
34107
+ this.context.validateStorageRequired();
34108
+ } else {
34109
+ throw new Error(
34110
+ "No storage available. Provide a grantUrl, configure relayerCallbacks.storeGrantFile, or storageManager."
34111
+ );
34112
+ }
34207
34113
  }
34208
34114
  if (this.context.relayerCallbacks?.storeGrantFile) {
34209
34115
  grantUrl = await this.context.relayerCallbacks.storeGrantFile(grantFile);
@@ -34227,7 +34133,7 @@ var PermissionsController = class {
34227
34133
  grantUrl
34228
34134
  );
34229
34135
  const typedData = await this.composePermissionGrantMessage({
34230
- to: params.to,
34136
+ grantee: params.grantee,
34231
34137
  operation: params.operation,
34232
34138
  // Placeholder - real data is in IPFS
34233
34139
  files: params.files,
@@ -34578,7 +34484,7 @@ var PermissionsController = class {
34578
34484
  * Composes the EIP-712 typed data for PermissionGrant (new simplified format).
34579
34485
  *
34580
34486
  * @param params - The parameters for composing the permission grant message
34581
- * @param params.to - The recipient address for the permission grant
34487
+ * @param params.grantee - The recipient address for the permission grant
34582
34488
  * @param params.operation - The type of operation being granted permission for
34583
34489
  * @param params.files - Array of file IDs that the permission applies to
34584
34490
  * @param params.grantUrl - URL where the grant details are stored
@@ -34671,36 +34577,45 @@ var PermissionsController = class {
34671
34577
  return addresses[0];
34672
34578
  }
34673
34579
  /**
34674
- * Retrieves all permissions granted by the current user using subgraph queries.
34580
+ * Gets on-chain permission grant data without expensive off-chain resolution.
34675
34581
  *
34676
34582
  * @remarks
34677
- * This method queries the Vana subgraph to find permissions directly granted by the user
34678
- * using the Permission entity. It efficiently handles millions of permissions by leveraging
34679
- * indexed subgraph data instead of scanning contract logs. The method fetches complete
34680
- * grant files from IPFS to provide detailed permission information including operation
34681
- * parameters and grantee details.
34682
- * @param params - Optional query parameters
34683
- * @param params.limit - Maximum number of permissions to return (default: 50)
34684
- * @param params.subgraphUrl - Optional subgraph URL to override the default endpoint
34685
- * @returns A Promise that resolves to an array of `GrantedPermission` objects
34686
- * @throws {BlockchainError} When subgraph is unavailable or returns invalid data
34583
+ * This method provides a fast, performance-focused way to retrieve permission grants
34584
+ * by querying only the subgraph without making expensive IPFS or individual contract calls.
34585
+ * It eliminates the N+1 query problem of the legacy `getUserPermissions()` method.
34586
+ *
34587
+ * The returned data contains all on-chain information but does NOT include resolved
34588
+ * operation details, parameters, or file IDs. Use `retrieveGrantFile()` separately
34589
+ * for specific grants when detailed data is needed.
34590
+ *
34591
+ * **Performance**: Completes in ~100-500ms regardless of permission count.
34592
+ * **Reliability**: Single point of failure (subgraph) with clear RPC fallback path.
34593
+ *
34594
+ * @param options - Options for retrieving permissions (limit, subgraph URL)
34595
+ * @returns A Promise that resolves to an array of `OnChainPermissionGrant` objects
34596
+ * @throws {BlockchainError} When subgraph query fails
34597
+ * @throws {NetworkError} When network requests fail
34687
34598
  * @example
34688
34599
  * ```typescript
34689
- * // Get all permissions granted by current user
34690
- * const permissions = await vana.permissions.getUserPermissions();
34600
+ * // Fast: Get all on-chain permission data
34601
+ * const grants = await vana.permissions.getUserPermissionGrantsOnChain({ limit: 20 });
34691
34602
  *
34692
- * permissions.forEach(permission => {
34693
- * console.log(`Granted ${permission.operation} to ${permission.grantee}`);
34603
+ * // Display in UI immediately
34604
+ * grants.forEach(grant => {
34605
+ * console.log(`Permission ${grant.id}: ${grant.grantUrl}`);
34694
34606
  * });
34695
34607
  *
34696
- * // Limit results
34697
- * const recent = await vana.permissions.getUserPermissions({ limit: 10 });
34608
+ * // Lazy load detailed data for specific permission when user clicks
34609
+ * const grantFile = await retrieveGrantFile(grants[0].grantUrl);
34610
+ * console.log(`Operation: ${grantFile.operation}`);
34611
+ * console.log(`Parameters:`, grantFile.parameters);
34698
34612
  * ```
34699
34613
  */
34700
- async getUserPermissions(params) {
34614
+ async getUserPermissionGrantsOnChain(options = {}) {
34615
+ const { limit = 50, subgraphUrl } = options;
34701
34616
  try {
34702
34617
  const userAddress = await this.getUserAddress();
34703
- const graphqlEndpoint = params?.subgraphUrl || this.context.subgraphUrl;
34618
+ const graphqlEndpoint = subgraphUrl || this.context.subgraphUrl;
34704
34619
  if (!graphqlEndpoint) {
34705
34620
  throw new BlockchainError(
34706
34621
  "subgraphUrl is required. Please provide a valid subgraph endpoint or configure it in Vana constructor."
@@ -34722,16 +34637,6 @@ var PermissionsController = class {
34722
34637
  }
34723
34638
  }
34724
34639
  `;
34725
- console.info("Query:", query);
34726
- console.info(
34727
- "Body:",
34728
- JSON.stringify({
34729
- query,
34730
- variables: {
34731
- userId: userAddress.toLowerCase()
34732
- }
34733
- })
34734
- );
34735
34640
  const response = await fetch(graphqlEndpoint, {
34736
34641
  method: "POST",
34737
34642
  headers: {
@@ -34750,7 +34655,6 @@ var PermissionsController = class {
34750
34655
  );
34751
34656
  }
34752
34657
  const result = await response.json();
34753
- console.info("Result:", result);
34754
34658
  if (result.errors) {
34755
34659
  throw new BlockchainError(
34756
34660
  `Subgraph errors: ${result.errors.map((e) => e.message).join(", ")}`
@@ -34758,64 +34662,32 @@ var PermissionsController = class {
34758
34662
  }
34759
34663
  const userData = result.data?.user;
34760
34664
  if (!userData || !userData.permissions?.length) {
34761
- console.warn("No permissions found for user:", userAddress);
34762
34665
  return [];
34763
34666
  }
34764
- const userPermissions = [];
34765
- const limit = params?.limit || 50;
34766
- const permissionsToProcess = userData.permissions.slice(0, limit);
34767
- for (const permission of permissionsToProcess) {
34768
- try {
34769
- let operation;
34770
- let files = [];
34771
- let parameters;
34772
- let granteeAddress;
34773
- try {
34774
- const grantFile = await retrieveGrantFile(permission.grant);
34775
- operation = grantFile.operation;
34776
- parameters = grantFile.parameters;
34777
- granteeAddress = grantFile.grantee;
34778
- } catch {
34779
- }
34780
- try {
34781
- const fileIds = await this.getPermissionFileIds(
34782
- BigInt(permission.id)
34783
- );
34784
- files = fileIds.map((id) => Number(id));
34785
- } catch {
34786
- }
34787
- userPermissions.push({
34788
- id: BigInt(permission.id),
34789
- files,
34790
- operation: operation || "",
34791
- parameters: parameters || {},
34792
- grant: permission.grant,
34793
- grantor: userAddress.toLowerCase(),
34794
- // Current user is the grantor
34795
- grantee: granteeAddress || userAddress,
34796
- // Application that received permission
34797
- active: true,
34798
- // Default to active if not specified
34799
- grantedAt: Number(permission.addedAtBlock),
34800
- nonce: Number(permission.nonce)
34801
- });
34802
- } catch (error) {
34803
- console.error(
34804
- "SDK Error: Failed to process permission:",
34805
- permission.id,
34806
- error
34807
- );
34808
- }
34809
- }
34810
- return userPermissions.sort((a, b) => {
34667
+ const onChainGrants = userData.permissions.slice(0, limit).map((permission) => ({
34668
+ id: BigInt(permission.id),
34669
+ grantUrl: permission.grant,
34670
+ grantSignature: permission.grantSignature,
34671
+ grantHash: permission.grantHash,
34672
+ nonce: BigInt(permission.nonce),
34673
+ addedAtBlock: BigInt(permission.addedAtBlock),
34674
+ addedAtTimestamp: BigInt(permission.addedAtTimestamp || "0"),
34675
+ transactionHash: permission.transactionHash || "",
34676
+ grantor: userAddress,
34677
+ active: true
34678
+ // TODO: Add revocation status from subgraph when available
34679
+ }));
34680
+ return onChainGrants.sort((a, b) => {
34811
34681
  if (a.id < b.id) return 1;
34812
34682
  if (a.id > b.id) return -1;
34813
34683
  return 0;
34814
34684
  });
34815
34685
  } catch (error) {
34816
- console.error("Failed to fetch user permissions:", error);
34686
+ if (error instanceof BlockchainError || error instanceof NetworkError) {
34687
+ throw error;
34688
+ }
34817
34689
  throw new BlockchainError(
34818
- `Failed to fetch user permissions: ${error instanceof Error ? error.message : "Unknown error"}`
34690
+ `Failed to fetch user permission grants: ${error instanceof Error ? error.message : "Unknown error"}`
34819
34691
  );
34820
34692
  }
34821
34693
  }
@@ -35577,32 +35449,19 @@ async function decryptWithPrivateKey(encryptedData, privateKey, platformAdapter)
35577
35449
  throw new Error(`Failed to decrypt with private key: ${error}`);
35578
35450
  }
35579
35451
  }
35580
- async function encryptUserData(data, walletSignature, platformAdapter) {
35452
+ async function encryptBlobWithSignedKey(data, key, platformAdapter) {
35581
35453
  try {
35582
35454
  const dataBuffer = data instanceof Blob ? await data.arrayBuffer() : new TextEncoder().encode(data);
35583
35455
  const dataArray = new Uint8Array(dataBuffer);
35584
35456
  const encrypted = await platformAdapter.crypto.encryptWithPassword(
35585
35457
  dataArray,
35586
- walletSignature
35458
+ key
35587
35459
  );
35588
35460
  return new Blob([encrypted], {
35589
35461
  type: "application/octet-stream"
35590
35462
  });
35591
35463
  } catch (error) {
35592
- throw new Error(`Failed to encrypt user data: ${error}`);
35593
- }
35594
- }
35595
- async function decryptUserData(encryptedData, walletSignature, platformAdapter) {
35596
- try {
35597
- const encryptedBuffer = encryptedData instanceof Blob ? await encryptedData.arrayBuffer() : new TextEncoder().encode(encryptedData);
35598
- const encryptedArray = new Uint8Array(encryptedBuffer);
35599
- const decrypted = await platformAdapter.crypto.decryptWithPassword(
35600
- encryptedArray,
35601
- walletSignature
35602
- );
35603
- return new Blob([decrypted], { type: "text/plain" });
35604
- } catch (error) {
35605
- throw new Error(`Failed to decrypt user data: ${error}`);
35464
+ throw new Error(`Failed to encrypt data: ${error}`);
35606
35465
  }
35607
35466
  }
35608
35467
  async function generateEncryptionKeyPair(platformAdapter) {
@@ -35619,60 +35478,25 @@ async function generatePGPKeyPair(platformAdapter, options) {
35619
35478
  throw new Error(`Failed to generate PGP key pair: ${error}`);
35620
35479
  }
35621
35480
  }
35481
+ async function decryptBlobWithSignedKey(encryptedData, key, platformAdapter) {
35482
+ try {
35483
+ const encryptedBuffer = encryptedData instanceof Blob ? await encryptedData.arrayBuffer() : new TextEncoder().encode(encryptedData);
35484
+ const encryptedArray = new Uint8Array(encryptedBuffer);
35485
+ const decrypted = await platformAdapter.crypto.decryptWithPassword(
35486
+ encryptedArray,
35487
+ key
35488
+ );
35489
+ return new Blob([decrypted], { type: "text/plain" });
35490
+ } catch (error) {
35491
+ throw new Error(`Failed to decrypt data: ${error}`);
35492
+ }
35493
+ }
35622
35494
 
35623
35495
  // src/controllers/data.ts
35624
35496
  var DataController = class {
35625
35497
  constructor(context) {
35626
35498
  this.context = context;
35627
35499
  }
35628
- /**
35629
- * Uploads user data with automatic encryption and blockchain registration.
35630
- *
35631
- * @remarks
35632
- * This is the primary method for uploading user data to the Vana network. It handles
35633
- * the complete workflow including content normalization, schema validation, encryption,
35634
- * storage upload, permission granting, and blockchain registration.
35635
- *
35636
- * The method automatically:
35637
- * - Normalizes input content to a Blob
35638
- * - Validates data against schema if provided
35639
- * - Generates encryption keys and encrypts the data
35640
- * - Uploads to the configured storage provider
35641
- * - Grants permissions to specified applications
35642
- * - Registers the file on the blockchain
35643
- *
35644
- * @param params - Upload parameters including content, filename, schema, and permissions
35645
- * @returns Promise resolving to upload results with file ID and transaction hash
35646
- * @throws {Error} When wallet is not connected or storage is not configured
35647
- * @throws {SchemaValidationError} When schema validation fails
35648
- * @throws {Error} When upload or blockchain registration fails
35649
- * @example
35650
- * ```typescript
35651
- * // Basic file upload
35652
- * const result = await vana.data.upload({
35653
- * content: "My personal data",
35654
- * filename: "diary.txt"
35655
- * });
35656
- *
35657
- * // Upload with schema validation
35658
- * const result = await vana.data.upload({
35659
- * content: { name: "John", age: 30 },
35660
- * filename: "profile.json",
35661
- * schemaId: 1
35662
- * });
35663
- *
35664
- * // Upload with permissions
35665
- * const result = await vana.data.upload({
35666
- * content: "Data for AI analysis",
35667
- * filename: "analysis.txt",
35668
- * permissions: [{
35669
- * to: "0x1234...",
35670
- * operation: "llm_inference",
35671
- * parameters: { model: "gpt-4" }
35672
- * }]
35673
- * });
35674
- * ```
35675
- */
35676
35500
  async upload(params) {
35677
35501
  const {
35678
35502
  content,
@@ -35680,7 +35504,8 @@ var DataController = class {
35680
35504
  schemaId,
35681
35505
  permissions = [],
35682
35506
  encrypt: encrypt3 = true,
35683
- providerName
35507
+ providerName,
35508
+ owner
35684
35509
  } = params;
35685
35510
  try {
35686
35511
  let blob;
@@ -35737,23 +35562,28 @@ var DataController = class {
35737
35562
  this.context.walletClient,
35738
35563
  DEFAULT_ENCRYPTION_SEED
35739
35564
  );
35740
- finalBlob = await encryptUserData(
35565
+ finalBlob = await encryptBlobWithSignedKey(
35741
35566
  blob,
35742
35567
  encryptionKey,
35743
35568
  this.context.platform
35744
35569
  );
35745
35570
  }
35746
35571
  if (!this.context.storageManager) {
35747
- throw new Error(
35748
- "Storage manager not configured. Please provide storage providers in VanaConfig."
35749
- );
35572
+ if (this.context.validateStorageRequired) {
35573
+ this.context.validateStorageRequired();
35574
+ throw new Error("Storage validation failed");
35575
+ } else {
35576
+ throw new Error(
35577
+ "Storage manager not configured. Please provide storage providers in VanaConfig."
35578
+ );
35579
+ }
35750
35580
  }
35751
35581
  const uploadResult = await this.context.storageManager.upload(
35752
35582
  finalBlob,
35753
35583
  filename,
35754
35584
  providerName
35755
35585
  );
35756
- const userAddress = await this.getUserAddress();
35586
+ const userAddress = owner || await this.getUserAddress();
35757
35587
  let encryptedPermissions = [];
35758
35588
  if (permissions.length > 0 && encrypt3) {
35759
35589
  const userEncryptionKey = await generateEncryptionKey(
@@ -35765,7 +35595,7 @@ var DataController = class {
35765
35595
  const publicKey = permission.publicKey;
35766
35596
  if (!publicKey) {
35767
35597
  throw new Error(
35768
- `Public key required for permission to ${permission.to}`
35598
+ `Public key required for permission to ${permission.grantee} when encryption is enabled`
35769
35599
  );
35770
35600
  }
35771
35601
  const encryptedKey = await encryptWithWalletPublicKey(
@@ -35774,7 +35604,7 @@ var DataController = class {
35774
35604
  this.context.platform
35775
35605
  );
35776
35606
  return {
35777
- account: permission.to,
35607
+ account: permission.grantee,
35778
35608
  key: encryptedKey
35779
35609
  };
35780
35610
  })
@@ -35787,7 +35617,8 @@ var DataController = class {
35787
35617
  url: uploadResult.url,
35788
35618
  userAddress,
35789
35619
  permissions: encryptedPermissions,
35790
- schemaId: schemaId || 0
35620
+ schemaId: schemaId || 0,
35621
+ ownerAddress: owner
35791
35622
  }
35792
35623
  );
35793
35624
  } else if (this.context.relayerCallbacks?.submitFileAddition) {
@@ -35823,6 +35654,142 @@ var DataController = class {
35823
35654
  );
35824
35655
  }
35825
35656
  }
35657
+ /**
35658
+ * Decrypts a file owned by the user using their wallet signature.
35659
+ *
35660
+ * @remarks
35661
+ * This is the high-level convenience method for decrypting user files, serving as the
35662
+ * symmetrical counterpart to the `upload` method. It handles the complete decryption
35663
+ * workflow including key generation, URL protocol detection, content fetching, and
35664
+ * decryption.
35665
+ *
35666
+ * The method automatically:
35667
+ * - Generates the decryption key from the user's wallet signature
35668
+ * - Determines the appropriate fetch method based on the file URL protocol
35669
+ * - Fetches the encrypted content from IPFS or standard HTTP URLs
35670
+ * - Decrypts the content using the generated key
35671
+ *
35672
+ * For IPFS URLs, the method uses gateway fallback for improved reliability. For
35673
+ * standard HTTP URLs, it uses a simple fetch. If you need custom authentication
35674
+ * headers or specific gateway configurations, use the low-level primitives directly.
35675
+ *
35676
+ * @param file - The user file to decrypt (typically from getUserFiles)
35677
+ * @param encryptionSeed - Optional custom encryption seed (defaults to Vana standard)
35678
+ * @returns Promise resolving to the decrypted file content as a Blob
35679
+ * @throws {Error} When the wallet is not connected
35680
+ * @throws {Error} When fetching the encrypted content fails
35681
+ * @throws {Error} When decryption fails (wrong key or corrupted data)
35682
+ * @example
35683
+ * ```typescript
35684
+ * // Basic file decryption
35685
+ * const files = await vana.data.getUserFiles({ owner: userAddress });
35686
+ * const decryptedBlob = await vana.data.decryptFile(files[0]);
35687
+ *
35688
+ * // Convert to text
35689
+ * const text = await decryptedBlob.text();
35690
+ * console.log('Decrypted content:', text);
35691
+ *
35692
+ * // Convert to JSON
35693
+ * const json = JSON.parse(await decryptedBlob.text());
35694
+ * console.log('Decrypted data:', json);
35695
+ *
35696
+ * // With custom encryption seed
35697
+ * const decryptedBlob = await vana.data.decryptFile(
35698
+ * files[0],
35699
+ * "My custom encryption seed"
35700
+ * );
35701
+ *
35702
+ * // Save to file (in Node.js)
35703
+ * const buffer = await decryptedBlob.arrayBuffer();
35704
+ * fs.writeFileSync('decrypted-file.txt', Buffer.from(buffer));
35705
+ * ```
35706
+ */
35707
+ async decryptFile(file, encryptionSeed) {
35708
+ try {
35709
+ const encryptionKey = await generateEncryptionKey(
35710
+ this.context.walletClient,
35711
+ encryptionSeed || DEFAULT_ENCRYPTION_SEED
35712
+ );
35713
+ let encryptedBlob;
35714
+ try {
35715
+ if (file.url.startsWith("ipfs://")) {
35716
+ encryptedBlob = await this.fetchFromIPFS(file.url);
35717
+ } else {
35718
+ encryptedBlob = await this.fetch(file.url);
35719
+ }
35720
+ } catch (fetchError) {
35721
+ const errorMessage = fetchError instanceof Error ? fetchError.message : "Unknown error";
35722
+ if (errorMessage.includes("Failed to fetch IPFS content") && errorMessage.includes("from all gateways")) {
35723
+ throw new Error(
35724
+ "Network error: Cannot access the file URL. The file may be stored on a server that's not accessible or has CORS restrictions."
35725
+ );
35726
+ } else if (errorMessage.includes("Empty response")) {
35727
+ throw new Error("File is empty or could not be retrieved");
35728
+ } else if (errorMessage.includes("Network error:") || errorMessage.includes("Failed to fetch")) {
35729
+ throw new Error(
35730
+ "Network error: Cannot access the file URL. The file may be stored on a server that's not accessible or has CORS restrictions."
35731
+ );
35732
+ } else if (errorMessage.includes("HTTP error!")) {
35733
+ const statusMatch = errorMessage.match(/status: (\d+)/);
35734
+ const status = statusMatch ? statusMatch[1] : "unknown";
35735
+ if (status === "500") {
35736
+ throw new Error(
35737
+ "Network error: Cannot access the file URL. The file may be stored on a server that's not accessible or has CORS restrictions."
35738
+ );
35739
+ } else if (status === "403") {
35740
+ throw new Error(
35741
+ "Access denied. You may not have permission to access this file"
35742
+ );
35743
+ } else if (status === "404") {
35744
+ throw new Error(
35745
+ "File not found: The encrypted file is no longer available at the stored URL."
35746
+ );
35747
+ } else {
35748
+ throw new Error(
35749
+ "Network error: Cannot access the file URL. The file may be stored on a server that's not accessible or has CORS restrictions."
35750
+ );
35751
+ }
35752
+ }
35753
+ throw fetchError;
35754
+ }
35755
+ if (encryptedBlob.size === 0) {
35756
+ throw new Error("File is empty or could not be retrieved");
35757
+ }
35758
+ let decryptedBlob;
35759
+ try {
35760
+ decryptedBlob = await decryptBlobWithSignedKey(
35761
+ encryptedBlob,
35762
+ encryptionKey,
35763
+ this.context.platform
35764
+ );
35765
+ } catch (decryptError) {
35766
+ const errorMessage = decryptError instanceof Error ? decryptError.message : "Unknown error";
35767
+ if (errorMessage.includes("not a valid OpenPGP message")) {
35768
+ throw new Error(
35769
+ "Invalid file format: This file doesn't appear to be encrypted with the Vana protocol"
35770
+ );
35771
+ } else if (errorMessage.includes("Session key decryption failed")) {
35772
+ throw new Error("Wrong encryption key");
35773
+ } else if (errorMessage.includes("Error decrypting message")) {
35774
+ throw new Error("Wrong encryption key");
35775
+ } else if (errorMessage.includes("File not found")) {
35776
+ throw new Error(
35777
+ "File not found: The encrypted file is no longer available"
35778
+ );
35779
+ } else {
35780
+ throw decryptError;
35781
+ }
35782
+ }
35783
+ return decryptedBlob;
35784
+ } catch (error) {
35785
+ 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"))) {
35786
+ throw error;
35787
+ }
35788
+ throw new Error(
35789
+ `Failed to decrypt file: ${error instanceof Error ? error.message : "Unknown error"}`
35790
+ );
35791
+ }
35792
+ }
35826
35793
  /**
35827
35794
  * Retrieves all data files owned by a specific user address.
35828
35795
  *
@@ -36573,81 +36540,6 @@ var DataController = class {
36573
36540
  );
36574
36541
  }
36575
36542
  }
36576
- /**
36577
- * Decrypts a file that was encrypted using the Vana protocol.
36578
- *
36579
- * @param file - The UserFile object containing the file URL and metadata
36580
- * @param encryptionSeed - Optional custom encryption seed (defaults to Vana standard)
36581
- * @returns Promise resolving to the decrypted file as a Blob
36582
- *
36583
- * This method handles the complete flow of:
36584
- * 1. Generating the encryption key from the user's wallet signature
36585
- * 2. Fetching the encrypted file from the stored URL
36586
- * 3. Decrypting the file using the canonical Vana decryption method
36587
- */
36588
- async decryptFile(file, encryptionSeed = DEFAULT_ENCRYPTION_SEED) {
36589
- try {
36590
- const encryptionKey = await generateEncryptionKey(
36591
- this.context.walletClient,
36592
- encryptionSeed
36593
- );
36594
- const fetchUrl = this.convertToDownloadUrl(file.url);
36595
- console.debug(
36596
- `Fetching encrypted file from URL: ${fetchUrl} (original: ${file.url})`
36597
- );
36598
- const response = await fetch(fetchUrl);
36599
- if (!response.ok) {
36600
- if (response.status === 404) {
36601
- throw new Error(
36602
- "File not found. The encrypted file may have been moved or deleted."
36603
- );
36604
- } else if (response.status === 403) {
36605
- throw new Error(
36606
- "Access denied. You may not have permission to access this file."
36607
- );
36608
- } else {
36609
- throw new Error(
36610
- `Network error: ${response.status} ${response.statusText}`
36611
- );
36612
- }
36613
- }
36614
- const encryptedBlob = await response.blob();
36615
- console.debug(
36616
- `Retrieved blob of size: ${encryptedBlob.size} bytes, type: ${encryptedBlob.type}`
36617
- );
36618
- if (encryptedBlob.size === 0) {
36619
- throw new Error("File is empty or could not be retrieved.");
36620
- }
36621
- const decryptedBlob = await decryptUserData(
36622
- encryptedBlob,
36623
- encryptionKey,
36624
- this.context.platform
36625
- );
36626
- return decryptedBlob;
36627
- } catch (error) {
36628
- console.error("Failed to decrypt file:", error);
36629
- if (error instanceof Error) {
36630
- if (error.message.includes("Session key decryption failed") || error.message.includes("Error decrypting message")) {
36631
- throw new Error(
36632
- "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."
36633
- );
36634
- } else if (error.message.includes("Failed to fetch") || error.message.includes("Network error")) {
36635
- throw new Error(
36636
- "Network error: Cannot access the file URL. The file may be stored on a server that's not accessible or has CORS restrictions."
36637
- );
36638
- } else if (error.message.includes("File not found")) {
36639
- throw new Error(
36640
- "File not found: The encrypted file is no longer available at the stored URL."
36641
- );
36642
- } else if (error.message.includes("not a valid OpenPGP message") || error.message.includes("does not conform to a valid OpenPGP format")) {
36643
- throw new Error(
36644
- "Invalid file format: This file doesn't appear to be encrypted with the Vana protocol."
36645
- );
36646
- }
36647
- }
36648
- throw error;
36649
- }
36650
- }
36651
36543
  /**
36652
36544
  * Registers a file URL directly on the blockchain with a schema ID.
36653
36545
  *
@@ -36708,26 +36600,6 @@ var DataController = class {
36708
36600
  );
36709
36601
  }
36710
36602
  }
36711
- /**
36712
- * Converts IPFS and Google Drive URLs to direct download URLs for fetching.
36713
- *
36714
- * @param url - The URL to convert to a direct download URL
36715
- * @returns The converted direct download URL or the original URL if not a special URL
36716
- */
36717
- convertToDownloadUrl(url) {
36718
- if (url.startsWith("ipfs://")) {
36719
- const hash = url.replace("ipfs://", "");
36720
- return `https://ipfs.io/ipfs/${hash}`;
36721
- }
36722
- if (url.includes("drive.google.com/file/d/")) {
36723
- const fileIdMatch = url.match(/\/file\/d\/([a-zA-Z0-9-_]+)/);
36724
- if (fileIdMatch) {
36725
- const fileId = fileIdMatch[1];
36726
- return `https://drive.google.com/uc?id=${fileId}&export=download`;
36727
- }
36728
- }
36729
- return url;
36730
- }
36731
36603
  /**
36732
36604
  * Gets the user's address from the wallet client.
36733
36605
  *
@@ -37221,7 +37093,7 @@ var DataController = class {
37221
37093
  this.context.walletClient,
37222
37094
  DEFAULT_ENCRYPTION_SEED
37223
37095
  );
37224
- const encryptedData = await encryptUserData(
37096
+ const encryptedData = await encryptBlobWithSignedKey(
37225
37097
  data,
37226
37098
  userEncryptionKey,
37227
37099
  this.context.platform
@@ -37385,12 +37257,13 @@ var DataController = class {
37385
37257
  privateKey,
37386
37258
  this.context.platform
37387
37259
  );
37388
- const response = await fetch(this.convertToDownloadUrl(file.url));
37389
- if (!response.ok) {
37390
- throw new Error(`Failed to download file: ${response.statusText}`);
37260
+ let encryptedData;
37261
+ if (file.url.startsWith("ipfs://")) {
37262
+ encryptedData = await this.fetchFromIPFS(file.url);
37263
+ } else {
37264
+ encryptedData = await this.fetch(file.url);
37391
37265
  }
37392
- const encryptedData = await response.blob();
37393
- const decryptedData = await decryptUserData(
37266
+ const decryptedData = await decryptBlobWithSignedKey(
37394
37267
  encryptedData,
37395
37268
  userEncryptionKey,
37396
37269
  this.context.platform
@@ -37403,6 +37276,162 @@ var DataController = class {
37403
37276
  );
37404
37277
  }
37405
37278
  }
37279
+ /**
37280
+ * Simple network-agnostic fetch utility for retrieving file content.
37281
+ *
37282
+ * @remarks
37283
+ * This is a thin wrapper around the global fetch API that returns the response as a Blob.
37284
+ * It provides a consistent interface for fetching encrypted content before decryption.
37285
+ * For IPFS URLs, consider using fetchFromIPFS for better reliability.
37286
+ *
37287
+ * @param url - The URL to fetch content from
37288
+ * @returns Promise resolving to the fetched content as a Blob
37289
+ * @throws {Error} When the fetch fails or returns a non-ok response
37290
+ *
37291
+ * @example
37292
+ * ```typescript
37293
+ * // Fetch and decrypt a file
37294
+ * const encryptionKey = await generateEncryptionKey(walletClient);
37295
+ * const encryptedBlob = await vana.data.fetch(file.url);
37296
+ * const decryptedBlob = await decryptBlob(encryptedBlob, encryptionKey, platform);
37297
+ *
37298
+ * // With custom headers for authentication
37299
+ * const response = await fetch(file.url, {
37300
+ * headers: { 'Authorization': 'Bearer token' }
37301
+ * });
37302
+ * const encryptedBlob = await response.blob();
37303
+ * ```
37304
+ */
37305
+ async fetch(url) {
37306
+ try {
37307
+ const response = await fetch(url);
37308
+ if (!response.ok) {
37309
+ throw new Error(
37310
+ `HTTP error! status: ${response.status} ${response.statusText}`
37311
+ );
37312
+ }
37313
+ const blob = await response.blob();
37314
+ if (blob.size === 0) {
37315
+ throw new Error("Empty response");
37316
+ }
37317
+ return blob;
37318
+ } catch (error) {
37319
+ if (error instanceof TypeError && error.message.includes("fetch")) {
37320
+ throw new Error(
37321
+ `Network error: Failed to fetch from ${url}. The URL may be invalid or the server may not be accessible.`
37322
+ );
37323
+ }
37324
+ throw error;
37325
+ }
37326
+ }
37327
+ /**
37328
+ * Specialized IPFS fetcher with gateway fallback mechanism.
37329
+ *
37330
+ * @remarks
37331
+ * This method provides robust IPFS content fetching by trying multiple gateways
37332
+ * in sequence until one succeeds. It supports both ipfs:// URLs and raw CIDs.
37333
+ *
37334
+ * The default gateway list includes public gateways, but you should provide
37335
+ * your own gateways for production use to ensure reliability and privacy.
37336
+ *
37337
+ * @param url - The IPFS URL (ipfs://...) or CID to fetch
37338
+ * @param options - Optional configuration
37339
+ * @param options.gateways - Array of IPFS gateway URLs to try (must end with /)
37340
+ * @returns Promise resolving to the fetched content as a Blob
37341
+ * @throws {Error} When all gateways fail to fetch the content
37342
+ *
37343
+ * @example
37344
+ * ```typescript
37345
+ * // Fetch from IPFS with custom gateways
37346
+ * const encryptedBlob = await vana.data.fetchFromIPFS(file.url, {
37347
+ * gateways: [
37348
+ * 'https://my-private-gateway.com/ipfs/',
37349
+ * 'https://dweb.link/ipfs/',
37350
+ * 'https://ipfs.io/ipfs/'
37351
+ * ]
37352
+ * });
37353
+ *
37354
+ * // Decrypt the fetched content
37355
+ * const encryptionKey = await generateEncryptionKey(walletClient);
37356
+ * const decryptedBlob = await decryptBlob(encryptedBlob, encryptionKey, platform);
37357
+ *
37358
+ * // With raw CID
37359
+ * const blob = await vana.data.fetchFromIPFS('QmXxx...', {
37360
+ * gateways: ['https://ipfs.io/ipfs/']
37361
+ * });
37362
+ * ```
37363
+ */
37364
+ async fetchFromIPFS(url, options) {
37365
+ const defaultGateways = [
37366
+ "https://dweb.link/ipfs/",
37367
+ "https://ipfs.io/ipfs/"
37368
+ ];
37369
+ const gateways = options?.gateways || this.context.ipfsGateways || defaultGateways;
37370
+ let cid;
37371
+ if (url.startsWith("ipfs://")) {
37372
+ cid = url.replace("ipfs://", "");
37373
+ } else if (url.startsWith("Qm") || url.startsWith("bafy")) {
37374
+ cid = url;
37375
+ } else {
37376
+ throw new Error(
37377
+ `Invalid IPFS URL format. Expected ipfs://... or a raw CID, got: ${url}`
37378
+ );
37379
+ }
37380
+ const errors = [];
37381
+ for (let i = 0; i < gateways.length; i++) {
37382
+ const gateway = gateways[i];
37383
+ const isLastGateway = i === gateways.length - 1;
37384
+ const gatewayUrl = gateway.endsWith("/") ? `${gateway}${cid}` : `${gateway}/${cid}`;
37385
+ try {
37386
+ console.debug(`Trying IPFS gateway: ${gatewayUrl}`);
37387
+ const response = await fetch(gatewayUrl);
37388
+ if (response.ok) {
37389
+ const blob = await response.blob();
37390
+ if (blob.size > 0) {
37391
+ console.debug(`Successfully fetched from gateway: ${gateway}`);
37392
+ return blob;
37393
+ } else {
37394
+ if (isLastGateway) {
37395
+ throw new Error("Empty response");
37396
+ }
37397
+ errors.push({
37398
+ gateway,
37399
+ error: "Empty response"
37400
+ });
37401
+ }
37402
+ } else {
37403
+ if (isLastGateway) {
37404
+ if (response.status === 403) {
37405
+ throw new Error(`HTTP error! status: 403 ${response.statusText}`);
37406
+ } else if (response.status === 404) {
37407
+ throw new Error(`HTTP error! status: 404 ${response.statusText}`);
37408
+ } else {
37409
+ throw new Error(
37410
+ `HTTP error! status: ${response.status} ${response.statusText}`
37411
+ );
37412
+ }
37413
+ }
37414
+ errors.push({
37415
+ gateway,
37416
+ error: `HTTP ${response.status} ${response.statusText}`
37417
+ });
37418
+ }
37419
+ } catch (error) {
37420
+ if (isLastGateway && error instanceof Error && (error.message.includes("Empty response") || error.message.includes("HTTP error!"))) {
37421
+ throw error;
37422
+ }
37423
+ errors.push({
37424
+ gateway,
37425
+ error: error instanceof Error ? error.message : "Unknown error"
37426
+ });
37427
+ }
37428
+ }
37429
+ const errorDetails = errors.map((e) => `${e.gateway}: ${e.error}`).join("\n ");
37430
+ throw new Error(
37431
+ `Failed to fetch IPFS content ${cid} from all gateways:
37432
+ ${errorDetails}`
37433
+ );
37434
+ }
37406
37435
  /**
37407
37436
  * Validates a data schema against the Vana meta-schema.
37408
37437
  *
@@ -37591,9 +37620,14 @@ var SchemaController = class {
37591
37620
  };
37592
37621
  validateDataSchema(dataSchema);
37593
37622
  if (!this.context.storageManager) {
37594
- throw new Error(
37595
- "Storage manager not configured. Please provide storage providers in VanaConfig."
37596
- );
37623
+ if (this.context.validateStorageRequired) {
37624
+ this.context.validateStorageRequired();
37625
+ throw new Error("Storage validation failed");
37626
+ } else {
37627
+ throw new Error(
37628
+ "Storage manager not configured. Please provide storage providers in VanaConfig."
37629
+ );
37630
+ }
37597
37631
  }
37598
37632
  const schemaBlob = new Blob([JSON.stringify(schemaDefinition)], {
37599
37633
  type: "application/json"
@@ -37865,6 +37899,7 @@ var ServerController = class {
37865
37899
  }
37866
37900
  const serverResponse = await response.json();
37867
37901
  return {
37902
+ kind: serverResponse.personal_server.kind,
37868
37903
  address: serverResponse.personal_server.address,
37869
37904
  public_key: serverResponse.personal_server.public_key,
37870
37905
  base_url: this.PERSONAL_SERVER_BASE_URL || "",
@@ -39478,175 +39513,151 @@ var PinataStorage = class {
39478
39513
  }
39479
39514
  };
39480
39515
 
39481
- // src/storage/providers/server-proxy.ts
39482
- var ServerProxyStorage = class {
39483
- constructor(config) {
39484
- this.config = config;
39485
- if (!config.uploadUrl) {
39486
- throw new StorageError(
39487
- "Upload URL is required",
39488
- "MISSING_UPLOAD_URL",
39489
- "server-proxy"
39490
- );
39491
- }
39492
- if (!config.downloadUrl) {
39493
- throw new StorageError(
39494
- "Download URL is required",
39495
- "MISSING_DOWNLOAD_URL",
39496
- "server-proxy"
39516
+ // src/storage/providers/callback-storage.ts
39517
+ var CallbackStorage = class {
39518
+ constructor(callbacks) {
39519
+ this.callbacks = callbacks;
39520
+ if (!callbacks.upload || !callbacks.download) {
39521
+ throw new Error(
39522
+ "CallbackStorage requires both upload and download callbacks"
39497
39523
  );
39498
39524
  }
39499
39525
  }
39500
39526
  /**
39501
- * Uploads a file through your server endpoint
39527
+ * Upload a file using the provided callback
39502
39528
  *
39503
- * @remarks
39504
- * This method sends the file to your configured upload endpoint via FormData.
39505
- * Your server is responsible for handling the actual storage implementation
39506
- * and must return a JSON response with `success: true` and an `identifier` field.
39507
- *
39508
- * @param file - The file to upload
39509
- * @param filename - Optional custom filename
39510
- * @returns Promise that resolves to the server-provided identifier
39511
- * @throws {StorageError} When the upload fails or server returns an error
39512
- *
39513
- * @example
39514
- * ```typescript
39515
- * const identifier = await serverStorage.upload(fileBlob, { name: "report.pdf" });
39516
- * console.log("File uploaded with identifier:", identifier);
39517
- * ```
39529
+ * @param file - The blob to upload
39530
+ * @param filename - Optional filename for the upload
39531
+ * @returns The upload result with URL and metadata
39518
39532
  */
39519
39533
  async upload(file, filename) {
39520
39534
  try {
39521
- const formData = new FormData();
39522
- formData.append("file", file);
39523
- if (filename) {
39524
- formData.append("name", filename);
39525
- }
39526
- const response = await fetch(this.config.uploadUrl, {
39527
- method: "POST",
39528
- body: formData
39529
- });
39530
- if (!response.ok) {
39531
- const _errorText = await response.text();
39532
- throw new StorageError(
39533
- `Server upload failed: ${response.status} ${response.statusText}`,
39534
- "UPLOAD_FAILED",
39535
- "server-proxy"
39536
- );
39537
- }
39538
- const result = await response.json();
39539
- if (!result.success) {
39535
+ const result = await this.callbacks.upload(file, filename);
39536
+ if (!result.url || result.url.trim() === "") {
39540
39537
  throw new StorageError(
39541
- `Upload failed: ${result.error || "Unknown server error"}`,
39542
- "UPLOAD_FAILED",
39543
- "server-proxy"
39538
+ "Upload callback returned invalid result: missing or empty url",
39539
+ "INVALID_UPLOAD_RESULT",
39540
+ "callback-storage"
39544
39541
  );
39545
39542
  }
39546
- if (!result.identifier) {
39547
- throw new StorageError(
39548
- "Server upload succeeded but no identifier returned",
39549
- "NO_IDENTIFIER_RETURNED",
39550
- "server-proxy"
39551
- );
39552
- }
39553
- return {
39554
- url: result.url || result.identifier,
39555
- size: file.size,
39556
- contentType: file.type || "application/octet-stream"
39557
- };
39543
+ return result;
39558
39544
  } catch (error) {
39559
39545
  if (error instanceof StorageError) {
39560
39546
  throw error;
39561
39547
  }
39562
39548
  throw new StorageError(
39563
- `Server proxy upload error: ${error instanceof Error ? error.message : "Unknown error"}`,
39549
+ `Upload failed: ${error instanceof Error ? error.message : String(error)}`,
39564
39550
  "UPLOAD_ERROR",
39565
- "server-proxy"
39551
+ "callback-storage",
39552
+ { cause: error instanceof Error ? error : void 0 }
39566
39553
  );
39567
39554
  }
39568
39555
  }
39569
39556
  /**
39570
- * Downloads a file through your server endpoint
39557
+ * Download a file using the provided callback
39571
39558
  *
39572
- * @remarks
39573
- * This method sends the identifier to your configured download endpoint via POST request.
39574
- * Your server is responsible for retrieving the file from your storage backend
39575
- * and returning the file content as a blob response.
39576
- *
39577
- * @param url - The server-provided URL or identifier from upload
39578
- * @returns Promise that resolves to the downloaded file content
39579
- * @throws {StorageError} When the download fails or file is not found
39580
- *
39581
- * @example
39582
- * ```typescript
39583
- * const fileBlob = await serverStorage.download("file-123");
39584
- * const url = URL.createObjectURL(fileBlob);
39585
- * ```
39559
+ * @param url - The URL or identifier to download
39560
+ * @returns The downloaded blob
39586
39561
  */
39587
39562
  async download(url) {
39588
39563
  try {
39589
- const identifier = this.extractIdentifierFromUrl(url);
39590
- const response = await fetch(this.config.downloadUrl, {
39591
- method: "POST",
39592
- headers: {
39593
- "Content-Type": "application/json"
39594
- },
39595
- body: JSON.stringify({ identifier })
39596
- });
39597
- if (!response.ok) {
39598
- const _errorText = await response.text();
39564
+ const identifier = this.callbacks.extractIdentifier ? this.callbacks.extractIdentifier(url) : url;
39565
+ const blob = await this.callbacks.download(identifier);
39566
+ if (!(blob instanceof Blob)) {
39599
39567
  throw new StorageError(
39600
- `Server download failed: ${response.status} ${response.statusText}`,
39601
- "DOWNLOAD_FAILED",
39602
- "server-proxy"
39568
+ "Download callback returned invalid result: expected Blob",
39569
+ "INVALID_DOWNLOAD_RESULT",
39570
+ "callback-storage"
39603
39571
  );
39604
39572
  }
39605
- return await response.blob();
39573
+ return blob;
39606
39574
  } catch (error) {
39607
39575
  if (error instanceof StorageError) {
39608
39576
  throw error;
39609
39577
  }
39610
39578
  throw new StorageError(
39611
- `Server proxy download error: ${error instanceof Error ? error.message : "Unknown error"}`,
39579
+ `Download failed: ${error instanceof Error ? error.message : String(error)}`,
39612
39580
  "DOWNLOAD_ERROR",
39613
- "server-proxy"
39581
+ "callback-storage",
39582
+ { cause: error instanceof Error ? error : void 0 }
39614
39583
  );
39615
39584
  }
39616
39585
  }
39617
- async list(_options) {
39618
- throw new StorageError(
39619
- "List operation is not supported by server proxy storage",
39620
- "LIST_NOT_SUPPORTED",
39621
- "server-proxy"
39622
- );
39623
- }
39624
- async delete(_url) {
39625
- throw new StorageError(
39626
- "Delete operation is not supported by server proxy storage",
39627
- "DELETE_NOT_SUPPORTED",
39628
- "server-proxy"
39629
- );
39586
+ /**
39587
+ * List files using the provided callback (if available)
39588
+ *
39589
+ * @param options - Optional list options including filters and pagination
39590
+ * @returns Array of storage files
39591
+ */
39592
+ async list(options) {
39593
+ if (!this.callbacks.list) {
39594
+ throw new StorageError(
39595
+ "List operation not supported - no list callback provided",
39596
+ "NOT_SUPPORTED",
39597
+ "callback-storage"
39598
+ );
39599
+ }
39600
+ try {
39601
+ const result = await this.callbacks.list(options?.namePattern, options);
39602
+ return result.items.map((item, index) => ({
39603
+ id: item.identifier,
39604
+ name: item.identifier.split("/").pop() || `file-${index}`,
39605
+ url: item.identifier,
39606
+ size: item.size || 0,
39607
+ contentType: "application/octet-stream",
39608
+ createdAt: item.lastModified || /* @__PURE__ */ new Date(),
39609
+ metadata: item.metadata
39610
+ }));
39611
+ } catch (error) {
39612
+ throw new StorageError(
39613
+ `List failed: ${error instanceof Error ? error.message : String(error)}`,
39614
+ "LIST_ERROR",
39615
+ "callback-storage",
39616
+ { cause: error instanceof Error ? error : void 0 }
39617
+ );
39618
+ }
39630
39619
  }
39631
39620
  /**
39632
- * Extract identifier from URL or return as-is
39621
+ * Delete a file using the provided callback (if available)
39633
39622
  *
39634
- * @param url - URL or identifier string
39635
- * @returns identifier string
39623
+ * @param url - The URL or identifier to delete
39624
+ * @returns True if deletion succeeded
39636
39625
  */
39637
- extractIdentifierFromUrl(url) {
39638
- return url;
39626
+ async delete(url) {
39627
+ if (!this.callbacks.delete) {
39628
+ throw new StorageError(
39629
+ "Delete operation not supported - no delete callback provided",
39630
+ "NOT_SUPPORTED",
39631
+ "callback-storage"
39632
+ );
39633
+ }
39634
+ try {
39635
+ const identifier = this.callbacks.extractIdentifier ? this.callbacks.extractIdentifier(url) : url;
39636
+ return await this.callbacks.delete(identifier);
39637
+ } catch (error) {
39638
+ throw new StorageError(
39639
+ `Delete failed: ${error instanceof Error ? error.message : String(error)}`,
39640
+ "DELETE_ERROR",
39641
+ "callback-storage",
39642
+ { cause: error instanceof Error ? error : void 0 }
39643
+ );
39644
+ }
39639
39645
  }
39646
+ /**
39647
+ * Get provider configuration
39648
+ *
39649
+ * @returns Provider configuration metadata
39650
+ */
39640
39651
  getConfig() {
39641
39652
  return {
39642
- name: "Server Proxy",
39643
- type: "server-proxy",
39653
+ name: "callback-storage",
39654
+ type: "callback",
39644
39655
  requiresAuth: false,
39645
39656
  features: {
39646
39657
  upload: true,
39647
39658
  download: true,
39648
- list: false,
39649
- delete: false
39659
+ list: !!this.callbacks.list,
39660
+ delete: !!this.callbacks.delete
39650
39661
  }
39651
39662
  };
39652
39663
  }
@@ -39876,6 +39887,47 @@ function getAllChains() {
39876
39887
  }
39877
39888
 
39878
39889
  // src/core.ts
39890
+ var VanaCoreFactory = class {
39891
+ /**
39892
+ * Creates a VanaCore instance that enforces storage requirements at compile time.
39893
+ * Use this factory when you know you'll need storage-dependent operations.
39894
+ *
39895
+ * @param platform - The platform adapter for environment-specific operations
39896
+ * @param config - Configuration that includes required storage providers
39897
+ * @returns VanaCore instance with storage validation
39898
+ * @example
39899
+ * ```typescript
39900
+ * const vanaCore = VanaCoreFactory.createWithStorage(platformAdapter, {
39901
+ * walletClient: myWalletClient,
39902
+ * storage: {
39903
+ * providers: { ipfs: new IPFSStorage() },
39904
+ * defaultProvider: 'ipfs'
39905
+ * }
39906
+ * });
39907
+ * ```
39908
+ */
39909
+ static createWithStorage(platform, config) {
39910
+ const core = new VanaCore(platform, config);
39911
+ return core;
39912
+ }
39913
+ /**
39914
+ * Creates a VanaCore instance without storage requirements.
39915
+ * Storage-dependent operations will fail at runtime if not configured.
39916
+ *
39917
+ * @param platform - The platform adapter for environment-specific operations
39918
+ * @param config - Basic configuration without required storage
39919
+ * @returns VanaCore instance
39920
+ * @example
39921
+ * ```typescript
39922
+ * const vanaCore = VanaCoreFactory.create(platformAdapter, {
39923
+ * walletClient: myWalletClient
39924
+ * });
39925
+ * ```
39926
+ */
39927
+ static create(platform, config) {
39928
+ return new VanaCore(platform, config);
39929
+ }
39930
+ };
39879
39931
  var VanaCore = class {
39880
39932
  /** Manages gasless data access permissions and trusted server registry. */
39881
39933
  permissions;
@@ -39891,12 +39943,18 @@ var VanaCore = class {
39891
39943
  platform;
39892
39944
  relayerCallbacks;
39893
39945
  storageManager;
39946
+ hasRequiredStorage;
39947
+ ipfsGateways;
39894
39948
  /**
39895
39949
  * Initializes a new VanaCore client instance with the provided configuration.
39896
39950
  *
39897
39951
  * @remarks
39898
39952
  * The constructor validates the configuration, initializes storage providers if configured,
39899
39953
  * creates wallet and public clients, and sets up all SDK controllers with shared context.
39954
+ *
39955
+ * IMPORTANT: This constructor will validate storage requirements at runtime to fail fast.
39956
+ * Methods that require storage will throw runtime errors if storage is not configured.
39957
+ *
39900
39958
  * @param platform - The platform adapter for environment-specific operations
39901
39959
  * @param config - The configuration object specifying wallet or chain settings
39902
39960
  * @throws {InvalidConfigurationError} When the configuration is invalid or incomplete
@@ -39912,6 +39970,8 @@ var VanaCore = class {
39912
39970
  this.platform = platform;
39913
39971
  this.validateConfig(config);
39914
39972
  this.relayerCallbacks = config.relayerCallbacks;
39973
+ this.ipfsGateways = config.ipfsGateways;
39974
+ this.hasRequiredStorage = hasStorageConfig(config);
39915
39975
  if (config.storage?.providers) {
39916
39976
  this.storageManager = new StorageManager();
39917
39977
  for (const [name, provider] of Object.entries(config.storage.providers)) {
@@ -39966,8 +40026,11 @@ var VanaCore = class {
39966
40026
  relayerCallbacks: this.relayerCallbacks,
39967
40027
  storageManager: this.storageManager,
39968
40028
  subgraphUrl,
39969
- platform: this.platform
40029
+ platform: this.platform,
39970
40030
  // Pass the platform adapter to controllers
40031
+ validateStorageRequired: this.validateStorageRequired.bind(this),
40032
+ hasStorage: this.hasStorage.bind(this),
40033
+ ipfsGateways: this.ipfsGateways
39971
40034
  };
39972
40035
  this.permissions = new PermissionsController(sharedContext);
39973
40036
  this.data = new DataController(sharedContext);
@@ -39975,6 +40038,58 @@ var VanaCore = class {
39975
40038
  this.server = new ServerController(sharedContext);
39976
40039
  this.protocol = new ProtocolController(sharedContext);
39977
40040
  }
40041
+ /**
40042
+ * Validates that storage is available for storage-dependent operations.
40043
+ * This method enforces the fail-fast principle by checking storage availability
40044
+ * at method call time rather than during expensive operations.
40045
+ *
40046
+ * @throws {InvalidConfigurationError} When storage is required but not configured
40047
+ * @example
40048
+ * ```typescript
40049
+ * // This will throw if storage is not configured
40050
+ * vana.validateStorageRequired();
40051
+ * await vana.data.uploadFile(file); // Safe to proceed
40052
+ * ```
40053
+ */
40054
+ validateStorageRequired() {
40055
+ if (!this.hasRequiredStorage) {
40056
+ throw new InvalidConfigurationError(
40057
+ "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."
40058
+ );
40059
+ }
40060
+ }
40061
+ /**
40062
+ * Checks whether storage is configured without throwing an error.
40063
+ *
40064
+ * @returns True if storage is properly configured
40065
+ * @example
40066
+ * ```typescript
40067
+ * if (vana.hasStorage()) {
40068
+ * await vana.data.uploadFile(file);
40069
+ * } else {
40070
+ * console.warn('Storage not configured - using pre-stored URLs only');
40071
+ * }
40072
+ * ```
40073
+ */
40074
+ hasStorage() {
40075
+ return this.hasRequiredStorage;
40076
+ }
40077
+ /**
40078
+ * Type guard to check if this instance has storage enabled at compile time.
40079
+ * Use this when you need TypeScript to understand that storage is available.
40080
+ *
40081
+ * @returns True if storage is configured, with type narrowing
40082
+ * @example
40083
+ * ```typescript
40084
+ * if (vana.isStorageEnabled()) {
40085
+ * // TypeScript knows storage is available here
40086
+ * await vana.data.uploadFile(file);
40087
+ * }
40088
+ * ```
40089
+ */
40090
+ isStorageEnabled() {
40091
+ return this.hasRequiredStorage;
40092
+ }
39978
40093
  /**
39979
40094
  * Validates the provided configuration object against all requirements.
39980
40095
  *
@@ -40163,23 +40278,23 @@ var VanaCore = class {
40163
40278
  return this.platform;
40164
40279
  }
40165
40280
  /**
40166
- * Encrypts user data using the Vana protocol standard encryption.
40281
+ * Encrypts data using the Vana protocol standard encryption.
40167
40282
  * This method automatically uses the correct platform adapter for the current environment.
40168
40283
  *
40169
40284
  * @param data The data to encrypt (string or Blob)
40170
- * @param walletSignature The wallet signature to use as encryption key
40285
+ * @param key The key to use as encryption key
40171
40286
  * @returns The encrypted data as Blob
40172
40287
  * @example
40173
40288
  * ```typescript
40174
40289
  * const encryptionKey = await generateEncryptionKey(walletClient);
40175
- * const encrypted = await vana.encryptUserData("sensitive data", encryptionKey);
40290
+ * const encrypted = await vana.encryptBlob("sensitive data", encryptionKey);
40176
40291
  * ```
40177
40292
  */
40178
- async encryptUserData(data, walletSignature) {
40179
- return encryptUserData(data, walletSignature, this.platform);
40293
+ async encryptBlob(data, key) {
40294
+ return encryptBlobWithSignedKey(data, key, this.platform);
40180
40295
  }
40181
40296
  /**
40182
- * Decrypts user data using the Vana protocol standard decryption.
40297
+ * Decrypts data that was encrypted using the Vana protocol.
40183
40298
  * This method automatically uses the correct platform adapter for the current environment.
40184
40299
  *
40185
40300
  * @param encryptedData The encrypted data (string or Blob)
@@ -40188,12 +40303,16 @@ var VanaCore = class {
40188
40303
  * @example
40189
40304
  * ```typescript
40190
40305
  * const encryptionKey = await generateEncryptionKey(walletClient);
40191
- * const decrypted = await vana.decryptUserData(encryptedData, encryptionKey);
40306
+ * const decrypted = await vana.decryptBlob(encryptedData, encryptionKey);
40192
40307
  * const text = await decrypted.text();
40193
40308
  * ```
40194
40309
  */
40195
- async decryptUserData(encryptedData, walletSignature) {
40196
- return decryptUserData(encryptedData, walletSignature, this.platform);
40310
+ async decryptBlob(encryptedData, walletSignature) {
40311
+ return decryptBlobWithSignedKey(
40312
+ encryptedData,
40313
+ walletSignature,
40314
+ this.platform
40315
+ );
40197
40316
  }
40198
40317
  };
40199
40318
 
@@ -40224,7 +40343,7 @@ function createValidatedGrant(params) {
40224
40343
  try {
40225
40344
  validateGrant(grantFile, {
40226
40345
  schema: true,
40227
- grantee: params.to,
40346
+ grantee: params.grantee,
40228
40347
  operation: params.operation
40229
40348
  });
40230
40349
  } catch (error) {
@@ -41087,31 +41206,22 @@ var ApiClient = class {
41087
41206
  };
41088
41207
 
41089
41208
  // src/index.node.ts
41090
- var VanaNode = class extends VanaCore {
41091
- /**
41092
- * Creates a Vana SDK instance configured for Node.js environments.
41093
- *
41094
- * @param config - SDK configuration object (wallet client or chain config)
41095
- * @example
41096
- * ```typescript
41097
- * // With wallet client
41098
- * const vana = new Vana({ walletClient });
41099
- *
41100
- * // With chain configuration
41101
- * const vana = new Vana({ chainId: 14800, account });
41102
- * ```
41103
- */
41209
+ var VanaNodeImpl = class extends VanaCore {
41104
41210
  constructor(config) {
41105
41211
  super(new NodePlatformAdapter(), config);
41106
41212
  }
41107
41213
  };
41108
- var index_node_default = VanaNode;
41214
+ function Vana(config) {
41215
+ return new VanaNodeImpl(config);
41216
+ }
41217
+ var index_node_default = Vana;
41109
41218
  export {
41110
41219
  ApiClient,
41111
41220
  AsyncQueue,
41112
41221
  BaseController,
41113
41222
  BlockchainError,
41114
41223
  BrowserPlatformAdapter,
41224
+ CallbackStorage,
41115
41225
  CircuitBreaker,
41116
41226
  ContractFactory,
41117
41227
  ContractNotFoundError,
@@ -41146,16 +41256,16 @@ export {
41146
41256
  SchemaValidator,
41147
41257
  SerializationError,
41148
41258
  ServerController,
41149
- ServerProxyStorage,
41150
41259
  ServerUrlMismatchError,
41151
41260
  SignatureError,
41152
41261
  StorageError,
41153
41262
  StorageManager,
41154
41263
  UserRejectedRequestError,
41155
- VanaNode as Vana,
41264
+ Vana,
41156
41265
  VanaCore,
41266
+ VanaCoreFactory,
41157
41267
  VanaError,
41158
- VanaNode,
41268
+ VanaNodeImpl,
41159
41269
  __contractCache,
41160
41270
  chains,
41161
41271
  checkGrantAccess,
@@ -41170,13 +41280,13 @@ export {
41170
41280
  createPlatformAdapterFor,
41171
41281
  createPlatformAdapterSafe,
41172
41282
  createValidatedGrant,
41173
- decryptUserData,
41283
+ decryptBlobWithSignedKey,
41174
41284
  decryptWithPrivateKey,
41175
41285
  decryptWithWalletPrivateKey,
41176
41286
  index_node_default as default,
41177
41287
  detectPlatform,
41288
+ encryptBlobWithSignedKey,
41178
41289
  encryptFileKey,
41179
- encryptUserData,
41180
41290
  encryptWithWalletPublicKey,
41181
41291
  extractIpfsHash,
41182
41292
  fetchAndValidateSchema,
@@ -41201,9 +41311,7 @@ export {
41201
41311
  handleRelayerRequest,
41202
41312
  isAPIResponse,
41203
41313
  isGrantExpired,
41204
- isIdentityServerOutput,
41205
41314
  isIpfsUrl,
41206
- isPersonalServerOutput,
41207
41315
  isPlatformSupported,
41208
41316
  isReplicateAPIResponse,
41209
41317
  moksha,