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

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.
@@ -413,7 +413,6 @@ __export(index_node_exports, {
413
413
  BaseController: () => BaseController,
414
414
  BlockchainError: () => BlockchainError,
415
415
  BrowserPlatformAdapter: () => BrowserPlatformAdapter,
416
- CallbackStorage: () => CallbackStorage,
417
416
  CircuitBreaker: () => CircuitBreaker,
418
417
  ContractFactory: () => ContractFactory,
419
418
  ContractNotFoundError: () => ContractNotFoundError,
@@ -448,6 +447,7 @@ __export(index_node_exports, {
448
447
  SchemaValidator: () => SchemaValidator,
449
448
  SerializationError: () => SerializationError,
450
449
  ServerController: () => ServerController,
450
+ ServerProxyStorage: () => ServerProxyStorage,
451
451
  ServerUrlMismatchError: () => ServerUrlMismatchError,
452
452
  SignatureError: () => SignatureError,
453
453
  StorageError: () => StorageError,
@@ -34507,6 +34507,8 @@ var PermissionsController = class {
34507
34507
  * Revokes a previously granted permission.
34508
34508
  *
34509
34509
  * @param params - Parameters for revoking the permission
34510
+ * @param params.permissionId - Permission identifier as bigint for contract compatibility.
34511
+ * Obtain from permission grants via `getUserPermissionGrantsOnChain()`.
34510
34512
  * @returns Promise resolving to transaction hash
34511
34513
  * @example
34512
34514
  * ```typescript
@@ -35648,8 +35650,7 @@ var DataController = class {
35648
35650
  schemaId,
35649
35651
  permissions = [],
35650
35652
  encrypt: encrypt3 = true,
35651
- providerName,
35652
- owner
35653
+ providerName
35653
35654
  } = params;
35654
35655
  try {
35655
35656
  let blob;
@@ -35727,7 +35728,7 @@ var DataController = class {
35727
35728
  filename,
35728
35729
  providerName
35729
35730
  );
35730
- const userAddress = owner || await this.getUserAddress();
35731
+ const userAddress = await this.getUserAddress();
35731
35732
  let encryptedPermissions = [];
35732
35733
  if (permissions.length > 0 && encrypt3) {
35733
35734
  const userEncryptionKey = await generateEncryptionKey(
@@ -35761,8 +35762,7 @@ var DataController = class {
35761
35762
  url: uploadResult.url,
35762
35763
  userAddress,
35763
35764
  permissions: encryptedPermissions,
35764
- schemaId: schemaId || 0,
35765
- ownerAddress: owner
35765
+ schemaId: schemaId || 0
35766
35766
  }
35767
35767
  );
35768
35768
  } else if (this.context.relayerCallbacks?.submitFileAddition) {
@@ -38023,6 +38023,39 @@ var ServerController = class {
38023
38023
  this.context = context;
38024
38024
  }
38025
38025
  PERSONAL_SERVER_BASE_URL = process.env.NEXT_PUBLIC_PERSONAL_SERVER_BASE_URL;
38026
+ /**
38027
+ * Retrieves the cryptographic identity of a personal server.
38028
+ *
38029
+ * @remarks
38030
+ * This method fetches the public key and metadata for a personal server,
38031
+ * which is required for encrypting data before sharing with the server.
38032
+ * The identity includes the server's public key, address, and operational
38033
+ * details needed for secure communication. This information is cached
38034
+ * by identity servers to enable offline key retrieval.
38035
+ *
38036
+ * @param request - Parameters containing the user address
38037
+ * @param request.userAddress - The wallet address associated with the personal server
38038
+ * @returns Promise resolving to the server's identity information
38039
+ * @throws {NetworkError} When the identity service is unavailable or returns invalid data
38040
+ * @throws {PersonalServerError} When server identity cannot be retrieved
38041
+ * @example
38042
+ * ```typescript
38043
+ * // Get server identity for data encryption
38044
+ * const identity = await vana.server.getIdentity({
38045
+ * userAddress: "0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36"
38046
+ * });
38047
+ *
38048
+ * console.log(`Server: ${identity.name}`);
38049
+ * console.log(`Address: ${identity.address}`);
38050
+ * console.log(`Public Key: ${identity.public_key}`);
38051
+ *
38052
+ * // Use the public key for encrypting data to share with this server
38053
+ * const encryptedData = await encryptWithWalletPublicKey(
38054
+ * userData,
38055
+ * identity.public_key
38056
+ * );
38057
+ * ```
38058
+ */
38026
38059
  async getIdentity(request) {
38027
38060
  try {
38028
38061
  const response = await fetch(
@@ -38065,10 +38098,13 @@ var ServerController = class {
38065
38098
  * This method submits a computation request to the personal server API.
38066
38099
  * The response includes the operation ID.
38067
38100
  * @param params - The request parameters object
38068
- * @param params.permissionId - The permission ID authorizing this operation
38101
+ * @param params.permissionId - The permission ID authorizing this operation.
38102
+ * Obtain from granted permissions via `vana.permissions.getUserPermissionGrantsOnChain()`.
38069
38103
  * @returns A Promise that resolves to an operation response with status and control URLs
38070
- * @throws {PersonalServerError} When server request fails or parameters are invalid
38071
- * @throws {NetworkError} When personal server API communication fails
38104
+ * @throws {PersonalServerError} When server request fails or parameters are invalid.
38105
+ * Verify permissionId exists and is active for the target server.
38106
+ * @throws {NetworkError} When personal server API communication fails.
38107
+ * Check server URL configuration and network connectivity.
38072
38108
  * @example
38073
38109
  * ```typescript
38074
38110
  * const response = await vana.server.createOperation({
@@ -38170,6 +38206,50 @@ var ServerController = class {
38170
38206
  );
38171
38207
  }
38172
38208
  }
38209
+ /**
38210
+ * Cancels a running operation on the personal server.
38211
+ *
38212
+ * @remarks
38213
+ * This method attempts to cancel an operation that is currently processing
38214
+ * on the personal server. The operation must be in a cancellable state
38215
+ * (typically `starting` or `processing`). Not all operations support
38216
+ * cancellation, and cancellation may not be immediate. The server will
38217
+ * attempt to stop the operation and update its status to `canceled`.
38218
+ *
38219
+ * **Cancellation Behavior:**
38220
+ * - Operations in `succeeded` or `failed` states cannot be canceled
38221
+ * - Some long-running operations may take time to respond to cancellation
38222
+ * - Always verify cancellation by polling the operation status afterward
38223
+ *
38224
+ * @param operationId - The unique identifier of the operation to cancel,
38225
+ * obtained from `createOperation()` response
38226
+ * @returns Promise that resolves when the cancellation request is accepted
38227
+ * @throws {PersonalServerError} When the operation cannot be canceled or doesn't exist.
38228
+ * Check operation status - it may already be completed or failed.
38229
+ * @throws {NetworkError} When unable to reach the personal server API.
38230
+ * Verify server URL and network connectivity.
38231
+ * @example
38232
+ * ```typescript
38233
+ * // Start a long-running operation
38234
+ * const operation = await vana.server.createOperation({
38235
+ * permissionId: 123
38236
+ * });
38237
+ *
38238
+ * // Cancel if needed
38239
+ * try {
38240
+ * await vana.server.cancelOperation(operation.id);
38241
+ * console.log("Cancellation requested");
38242
+ *
38243
+ * // Verify cancellation
38244
+ * const status = await vana.server.getOperation(operation.id);
38245
+ * if (status.status === "canceled") {
38246
+ * console.log("Operation successfully canceled");
38247
+ * }
38248
+ * } catch (error) {
38249
+ * console.error("Failed to cancel:", error);
38250
+ * }
38251
+ * ```
38252
+ */
38173
38253
  async cancelOperation(operationId) {
38174
38254
  try {
38175
38255
  const response = await fetch(
@@ -38467,7 +38547,8 @@ var ProtocolController = class {
38467
38547
  * are actually deployed on the current network.
38468
38548
  * @param contractName - The name of the Vana contract to retrieve (use const assertion for full typing)
38469
38549
  * @returns An object containing the contract's address and fully typed ABI
38470
- * @throws {ContractNotFoundError} When the contract is not deployed on the current chain
38550
+ * @throws {ContractNotFoundError} When the contract is not deployed on the current chain.
38551
+ * Verify contract name spelling and check current network with `getChainId()`.
38471
38552
  * @example
38472
38553
  * ```typescript
38473
38554
  * // Get contract info with full type inference
@@ -39651,151 +39732,175 @@ var PinataStorage = class {
39651
39732
  }
39652
39733
  };
39653
39734
 
39654
- // src/storage/providers/callback-storage.ts
39655
- var CallbackStorage = class {
39656
- constructor(callbacks) {
39657
- this.callbacks = callbacks;
39658
- if (!callbacks.upload || !callbacks.download) {
39659
- throw new Error(
39660
- "CallbackStorage requires both upload and download callbacks"
39735
+ // src/storage/providers/server-proxy.ts
39736
+ var ServerProxyStorage = class {
39737
+ constructor(config) {
39738
+ this.config = config;
39739
+ if (!config.uploadUrl) {
39740
+ throw new StorageError(
39741
+ "Upload URL is required",
39742
+ "MISSING_UPLOAD_URL",
39743
+ "server-proxy"
39744
+ );
39745
+ }
39746
+ if (!config.downloadUrl) {
39747
+ throw new StorageError(
39748
+ "Download URL is required",
39749
+ "MISSING_DOWNLOAD_URL",
39750
+ "server-proxy"
39661
39751
  );
39662
39752
  }
39663
39753
  }
39664
39754
  /**
39665
- * Upload a file using the provided callback
39755
+ * Uploads a file through your server endpoint
39666
39756
  *
39667
- * @param file - The blob to upload
39668
- * @param filename - Optional filename for the upload
39669
- * @returns The upload result with URL and metadata
39757
+ * @remarks
39758
+ * This method sends the file to your configured upload endpoint via FormData.
39759
+ * Your server is responsible for handling the actual storage implementation
39760
+ * and must return a JSON response with `success: true` and an `identifier` field.
39761
+ *
39762
+ * @param file - The file to upload
39763
+ * @param filename - Optional custom filename
39764
+ * @returns Promise that resolves to the server-provided identifier
39765
+ * @throws {StorageError} When the upload fails or server returns an error
39766
+ *
39767
+ * @example
39768
+ * ```typescript
39769
+ * const identifier = await serverStorage.upload(fileBlob, { name: "report.pdf" });
39770
+ * console.log("File uploaded with identifier:", identifier);
39771
+ * ```
39670
39772
  */
39671
39773
  async upload(file, filename) {
39672
39774
  try {
39673
- const result = await this.callbacks.upload(file, filename);
39674
- if (!result.url || result.url.trim() === "") {
39775
+ const formData = new FormData();
39776
+ formData.append("file", file);
39777
+ if (filename) {
39778
+ formData.append("name", filename);
39779
+ }
39780
+ const response = await fetch(this.config.uploadUrl, {
39781
+ method: "POST",
39782
+ body: formData
39783
+ });
39784
+ if (!response.ok) {
39785
+ const _errorText = await response.text();
39675
39786
  throw new StorageError(
39676
- "Upload callback returned invalid result: missing or empty url",
39677
- "INVALID_UPLOAD_RESULT",
39678
- "callback-storage"
39787
+ `Server upload failed: ${response.status} ${response.statusText}`,
39788
+ "UPLOAD_FAILED",
39789
+ "server-proxy"
39679
39790
  );
39680
39791
  }
39681
- return result;
39792
+ const result = await response.json();
39793
+ if (!result.success) {
39794
+ throw new StorageError(
39795
+ `Upload failed: ${result.error || "Unknown server error"}`,
39796
+ "UPLOAD_FAILED",
39797
+ "server-proxy"
39798
+ );
39799
+ }
39800
+ if (!result.identifier) {
39801
+ throw new StorageError(
39802
+ "Server upload succeeded but no identifier returned",
39803
+ "NO_IDENTIFIER_RETURNED",
39804
+ "server-proxy"
39805
+ );
39806
+ }
39807
+ return {
39808
+ url: result.url || result.identifier,
39809
+ size: file.size,
39810
+ contentType: file.type || "application/octet-stream"
39811
+ };
39682
39812
  } catch (error) {
39683
39813
  if (error instanceof StorageError) {
39684
39814
  throw error;
39685
39815
  }
39686
39816
  throw new StorageError(
39687
- `Upload failed: ${error instanceof Error ? error.message : String(error)}`,
39817
+ `Server proxy upload error: ${error instanceof Error ? error.message : "Unknown error"}`,
39688
39818
  "UPLOAD_ERROR",
39689
- "callback-storage",
39690
- { cause: error instanceof Error ? error : void 0 }
39819
+ "server-proxy"
39691
39820
  );
39692
39821
  }
39693
39822
  }
39694
39823
  /**
39695
- * Download a file using the provided callback
39824
+ * Downloads a file through your server endpoint
39696
39825
  *
39697
- * @param url - The URL or identifier to download
39698
- * @returns The downloaded blob
39826
+ * @remarks
39827
+ * This method sends the identifier to your configured download endpoint via POST request.
39828
+ * Your server is responsible for retrieving the file from your storage backend
39829
+ * and returning the file content as a blob response.
39830
+ *
39831
+ * @param url - The server-provided URL or identifier from upload
39832
+ * @returns Promise that resolves to the downloaded file content
39833
+ * @throws {StorageError} When the download fails or file is not found
39834
+ *
39835
+ * @example
39836
+ * ```typescript
39837
+ * const fileBlob = await serverStorage.download("file-123");
39838
+ * const url = URL.createObjectURL(fileBlob);
39839
+ * ```
39699
39840
  */
39700
39841
  async download(url) {
39701
39842
  try {
39702
- const identifier = this.callbacks.extractIdentifier ? this.callbacks.extractIdentifier(url) : url;
39703
- const blob = await this.callbacks.download(identifier);
39704
- if (!(blob instanceof Blob)) {
39843
+ const identifier = this.extractIdentifierFromUrl(url);
39844
+ const response = await fetch(this.config.downloadUrl, {
39845
+ method: "POST",
39846
+ headers: {
39847
+ "Content-Type": "application/json"
39848
+ },
39849
+ body: JSON.stringify({ identifier })
39850
+ });
39851
+ if (!response.ok) {
39852
+ const _errorText = await response.text();
39705
39853
  throw new StorageError(
39706
- "Download callback returned invalid result: expected Blob",
39707
- "INVALID_DOWNLOAD_RESULT",
39708
- "callback-storage"
39854
+ `Server download failed: ${response.status} ${response.statusText}`,
39855
+ "DOWNLOAD_FAILED",
39856
+ "server-proxy"
39709
39857
  );
39710
39858
  }
39711
- return blob;
39859
+ return await response.blob();
39712
39860
  } catch (error) {
39713
39861
  if (error instanceof StorageError) {
39714
39862
  throw error;
39715
39863
  }
39716
39864
  throw new StorageError(
39717
- `Download failed: ${error instanceof Error ? error.message : String(error)}`,
39865
+ `Server proxy download error: ${error instanceof Error ? error.message : "Unknown error"}`,
39718
39866
  "DOWNLOAD_ERROR",
39719
- "callback-storage",
39720
- { cause: error instanceof Error ? error : void 0 }
39867
+ "server-proxy"
39721
39868
  );
39722
39869
  }
39723
39870
  }
39724
- /**
39725
- * List files using the provided callback (if available)
39726
- *
39727
- * @param options - Optional list options including filters and pagination
39728
- * @returns Array of storage files
39729
- */
39730
- async list(options) {
39731
- if (!this.callbacks.list) {
39732
- throw new StorageError(
39733
- "List operation not supported - no list callback provided",
39734
- "NOT_SUPPORTED",
39735
- "callback-storage"
39736
- );
39737
- }
39738
- try {
39739
- const result = await this.callbacks.list(options?.namePattern, options);
39740
- return result.items.map((item, index) => ({
39741
- id: item.identifier,
39742
- name: item.identifier.split("/").pop() || `file-${index}`,
39743
- url: item.identifier,
39744
- size: item.size || 0,
39745
- contentType: "application/octet-stream",
39746
- createdAt: item.lastModified || /* @__PURE__ */ new Date(),
39747
- metadata: item.metadata
39748
- }));
39749
- } catch (error) {
39750
- throw new StorageError(
39751
- `List failed: ${error instanceof Error ? error.message : String(error)}`,
39752
- "LIST_ERROR",
39753
- "callback-storage",
39754
- { cause: error instanceof Error ? error : void 0 }
39755
- );
39756
- }
39871
+ async list(_options) {
39872
+ throw new StorageError(
39873
+ "List operation is not supported by server proxy storage",
39874
+ "LIST_NOT_SUPPORTED",
39875
+ "server-proxy"
39876
+ );
39757
39877
  }
39758
- /**
39759
- * Delete a file using the provided callback (if available)
39760
- *
39761
- * @param url - The URL or identifier to delete
39762
- * @returns True if deletion succeeded
39763
- */
39764
- async delete(url) {
39765
- if (!this.callbacks.delete) {
39766
- throw new StorageError(
39767
- "Delete operation not supported - no delete callback provided",
39768
- "NOT_SUPPORTED",
39769
- "callback-storage"
39770
- );
39771
- }
39772
- try {
39773
- const identifier = this.callbacks.extractIdentifier ? this.callbacks.extractIdentifier(url) : url;
39774
- return await this.callbacks.delete(identifier);
39775
- } catch (error) {
39776
- throw new StorageError(
39777
- `Delete failed: ${error instanceof Error ? error.message : String(error)}`,
39778
- "DELETE_ERROR",
39779
- "callback-storage",
39780
- { cause: error instanceof Error ? error : void 0 }
39781
- );
39782
- }
39878
+ async delete(_url) {
39879
+ throw new StorageError(
39880
+ "Delete operation is not supported by server proxy storage",
39881
+ "DELETE_NOT_SUPPORTED",
39882
+ "server-proxy"
39883
+ );
39783
39884
  }
39784
39885
  /**
39785
- * Get provider configuration
39886
+ * Extract identifier from URL or return as-is
39786
39887
  *
39787
- * @returns Provider configuration metadata
39888
+ * @param url - URL or identifier string
39889
+ * @returns identifier string
39788
39890
  */
39891
+ extractIdentifierFromUrl(url) {
39892
+ return url;
39893
+ }
39789
39894
  getConfig() {
39790
39895
  return {
39791
- name: "callback-storage",
39792
- type: "callback",
39896
+ name: "Server Proxy",
39897
+ type: "server-proxy",
39793
39898
  requiresAuth: false,
39794
39899
  features: {
39795
39900
  upload: true,
39796
39901
  download: true,
39797
- list: !!this.callbacks.list,
39798
- delete: !!this.callbacks.delete
39902
+ list: false,
39903
+ delete: false
39799
39904
  }
39800
39905
  };
39801
39906
  }
@@ -40417,15 +40522,35 @@ var VanaCore = class {
40417
40522
  }
40418
40523
  /**
40419
40524
  * Encrypts data using the Vana protocol standard encryption.
40420
- * This method automatically uses the correct platform adapter for the current environment.
40525
+ *
40526
+ * @remarks
40527
+ * This method implements the Vana network's standard encryption protocol using
40528
+ * platform-appropriate cryptographic libraries. It automatically handles different
40529
+ * input types (string or Blob) and produces encrypted output suitable for secure
40530
+ * storage or transmission. The encryption is compatible with the network's
40531
+ * decryption protocols and can be decrypted by authorized parties.
40421
40532
  *
40422
- * @param data The data to encrypt (string or Blob)
40423
- * @param key The key to use as encryption key
40424
- * @returns The encrypted data as Blob
40533
+ * @param data - The data to encrypt (string or Blob)
40534
+ * @param key - The encryption key (typically generated via `generateEncryptionKey`)
40535
+ * @returns The encrypted data as a Blob
40536
+ * @throws {Error} When encryption fails due to invalid key or data format
40425
40537
  * @example
40426
40538
  * ```typescript
40427
- * const encryptionKey = await generateEncryptionKey(walletClient);
40428
- * const encrypted = await vana.encryptBlob("sensitive data", encryptionKey);
40539
+ * import { generateEncryptionKey } from '@opendatalabs/vana-sdk/node';
40540
+ *
40541
+ * // Generate encryption key from wallet signature
40542
+ * const encryptionKey = await generateEncryptionKey(vana.walletClient);
40543
+ *
40544
+ * // Encrypt string data
40545
+ * const sensitiveData = "User's private information";
40546
+ * const encrypted = await vana.encryptBlob(sensitiveData, encryptionKey);
40547
+ *
40548
+ * // Encrypt file data
40549
+ * const fileBlob = new Blob([fileContent], { type: 'application/json' });
40550
+ * const encryptedFile = await vana.encryptBlob(fileBlob, encryptionKey);
40551
+ *
40552
+ * // Store encrypted data safely
40553
+ * await storageProvider.upload(encrypted, 'encrypted-data.bin');
40429
40554
  * ```
40430
40555
  */
40431
40556
  async encryptBlob(data, key) {
@@ -40433,16 +40558,52 @@ var VanaCore = class {
40433
40558
  }
40434
40559
  /**
40435
40560
  * Decrypts data that was encrypted using the Vana protocol.
40436
- * This method automatically uses the correct platform adapter for the current environment.
40561
+ *
40562
+ * @remarks
40563
+ * This method decrypts data that was previously encrypted using the Vana network's
40564
+ * standard encryption protocol. It requires the same wallet signature that was used
40565
+ * for encryption and automatically uses the appropriate platform adapter for
40566
+ * cryptographic operations. The decrypted output maintains the original data format.
40437
40567
  *
40438
- * @param encryptedData The encrypted data (string or Blob)
40439
- * @param walletSignature The wallet signature to use as decryption key
40440
- * @returns The decrypted data as Blob
40568
+ * @param encryptedData - The encrypted data (string or Blob)
40569
+ * @param walletSignature - The wallet signature used as decryption key
40570
+ * @returns The decrypted data as a Blob
40571
+ * @throws {Error} When decryption fails due to invalid signature or corrupted data
40441
40572
  * @example
40442
40573
  * ```typescript
40443
- * const encryptionKey = await generateEncryptionKey(walletClient);
40444
- * const decrypted = await vana.decryptBlob(encryptedData, encryptionKey);
40445
- * const text = await decrypted.text();
40574
+ * import { generateEncryptionKey } from '@opendatalabs/vana-sdk/node';
40575
+ *
40576
+ * // Retrieve encrypted data from storage
40577
+ * const encryptedBlob = await storageProvider.download('encrypted-data.bin');
40578
+ *
40579
+ * // Generate the same key used for encryption
40580
+ * const decryptionKey = await generateEncryptionKey(vana.walletClient);
40581
+ *
40582
+ * // Decrypt the data
40583
+ * const decrypted = await vana.decryptBlob(encryptedBlob, decryptionKey);
40584
+ *
40585
+ * // Convert back to original format
40586
+ * const originalText = await decrypted.text();
40587
+ * const originalJson = JSON.parse(originalText);
40588
+ *
40589
+ * console.log('Decrypted data:', originalJson);
40590
+ * ```
40591
+ *
40592
+ * @example
40593
+ * ```typescript
40594
+ * // Decrypt file downloaded from Vana network
40595
+ * const userFiles = await vana.data.getUserFiles();
40596
+ * const file = userFiles[0];
40597
+ *
40598
+ * // Download encrypted content
40599
+ * const encrypted = await fetch(file.url).then(r => r.blob());
40600
+ *
40601
+ * // Decrypt with user's key
40602
+ * const decryptionKey = await generateEncryptionKey(vana.walletClient);
40603
+ * const decrypted = await vana.decryptBlob(encrypted, decryptionKey);
40604
+ *
40605
+ * // Process original data
40606
+ * const fileContent = await decrypted.arrayBuffer();
40446
40607
  * ```
40447
40608
  */
40448
40609
  async decryptBlob(encryptedData, walletSignature) {
@@ -41360,7 +41521,6 @@ var index_node_default = Vana;
41360
41521
  BaseController,
41361
41522
  BlockchainError,
41362
41523
  BrowserPlatformAdapter,
41363
- CallbackStorage,
41364
41524
  CircuitBreaker,
41365
41525
  ContractFactory,
41366
41526
  ContractNotFoundError,
@@ -41395,6 +41555,7 @@ var index_node_default = Vana;
41395
41555
  SchemaValidator,
41396
41556
  SerializationError,
41397
41557
  ServerController,
41558
+ ServerProxyStorage,
41398
41559
  ServerUrlMismatchError,
41399
41560
  SignatureError,
41400
41561
  StorageError,