@dstorage-tech/dstorage-sdk 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -14430,7 +14430,6 @@ __export(index_publish_exports, {
14430
14430
  METADATA_AAD: () => METADATA_AAD,
14431
14431
  METADATA_ONLY_OWNER_SECRET_SALT: () => METADATA_ONLY_OWNER_SECRET_SALT,
14432
14432
  ManagedPaymentAdapter: () => ManagedPaymentAdapter,
14433
- MetaTx: () => MetaTx,
14434
14433
  MidnightChainAdapter: () => MidnightChainAdapter,
14435
14434
  MidnightSimulatorChainAdapter: () => MidnightSimulatorChainAdapter,
14436
14435
  MnemonicEncryptionAdapter: () => MnemonicEncryptionAdapter,
@@ -14453,7 +14452,6 @@ __export(index_publish_exports, {
14453
14452
  computeBlake3Hex: () => computeBlake3Hex,
14454
14453
  decryptStorageIdXChaCha: () => decryptStorageIdXChaCha,
14455
14454
  decryptXChaCha: () => decryptXChaCha,
14456
- defaultChainToken: () => defaultChainToken,
14457
14455
  deriveKek: () => deriveKek,
14458
14456
  deriveOwnerSecret: () => deriveOwnerSecret,
14459
14457
  deriveSymmetricKeyFromSeed: () => deriveSymmetricKeyFromSeed,
@@ -14535,9 +14533,6 @@ var CoreErrorCode = {
14535
14533
  // removeReference — 10070-10079
14536
14534
  REMOVE_REF_REQUIRES_CHAIN: 10070,
14537
14535
  REMOVE_REF_NOT_SUPPORTED: 10071,
14538
- // executeMetaTransaction / prepareUploadCrypto — 10080-10089
14539
- META_TX_REQUIRES_ENCRYPTION: 10080,
14540
- PREPARE_UPLOAD_NO_PROVIDERS: 10081,
14541
14536
  // manifest / chunking — 10090-10099
14542
14537
  MANIFEST_INVALID_STRUCTURE: 10090,
14543
14538
  MANIFEST_CHUNK_COUNT_MISMATCH: 10091,
@@ -14547,8 +14542,6 @@ var CoreErrorCode = {
14547
14542
  REASSEMBLY_SIZE_MISMATCH: 10095,
14548
14543
  // init state — 10100-10109
14549
14544
  NOT_INITIALIZED: 10100,
14550
- // MetaTx — 10200-10209
14551
- META_TX_UNKNOWN_STEP: 10200,
14552
14545
  // generic catch-all — 10999
14553
14546
  UNKNOWN_ERROR: 10999
14554
14547
  };
@@ -14569,7 +14562,7 @@ function isStorePartialError(err) {
14569
14562
  // package.json
14570
14563
  var package_default = {
14571
14564
  name: "@dstorage-tech/dstorage-sdk",
14572
- version: "0.0.5",
14565
+ version: "0.0.6",
14573
14566
  description: "Privacy-first decentralized storage SDK with pluggable adapters",
14574
14567
  license: "MIT",
14575
14568
  homepage: "https://dstorage.pro",
@@ -14656,2397 +14649,2046 @@ var package_default = {
14656
14649
  }
14657
14650
  };
14658
14651
 
14659
- // src/metaTx/utils.ts
14660
- function defaultChainToken(chainProvider) {
14661
- switch (chainProvider) {
14662
- case "midnight":
14663
- return "DUST";
14664
- default:
14665
- return "MOCK";
14666
- }
14667
- }
14668
- function buildInitialSteps() {
14669
- return [
14670
- {
14671
- id: "estimate",
14672
- label: "Estimate Costs",
14673
- description: "Calculate storage fee + chain transaction fee",
14674
- status: "pending"
14675
- },
14676
- {
14677
- id: "encrypt",
14678
- label: "Encrypt File",
14679
- description: "Client side encryption using your wallet key",
14680
- status: "pending"
14681
- },
14682
- {
14683
- id: "payment_storage",
14684
- label: "Storage Payment",
14685
- description: "Pay storage network fee",
14686
- status: "pending"
14687
- },
14688
- {
14689
- id: "upload",
14690
- label: "Upload to Storage",
14691
- description: "Send encrypted file to decentralised network",
14692
- status: "pending"
14693
- },
14694
- {
14695
- id: "chain_reference",
14696
- label: "Write On-Chain Reference",
14697
- description: "Store file pointer + metadata on blockchain",
14698
- status: "pending"
14699
- },
14700
- {
14701
- id: "complete",
14702
- label: "Complete",
14703
- description: "Meta-transaction finalised",
14704
- status: "pending"
14705
- }
14706
- ];
14707
- }
14708
- function truncateId(id) {
14709
- return id.length > 20 ? `${id.slice(0, 10)}\u2026${id.slice(-6)}` : id;
14710
- }
14711
- async function readFileAsBytes(file) {
14712
- return new Promise((resolve, reject) => {
14713
- const reader = new FileReader();
14714
- reader.onload = () => resolve(new Uint8Array(reader.result));
14715
- reader.onerror = () => reject(new Error("Failed to read file."));
14716
- reader.readAsArrayBuffer(file);
14717
- });
14652
+ // src/chunked.ts
14653
+ var CHUNK_SIZE = 10 * 1024 * 1024;
14654
+ var MAX_CHUNK_COUNT = 1e4;
14655
+ var MAX_REASSEMBLY_BYTES = MAX_CHUNK_COUNT * CHUNK_SIZE;
14656
+ function isManifest(payload) {
14657
+ if (typeof payload !== "object" || payload === null) return false;
14658
+ const m = payload;
14659
+ if (m["type"] !== "dstorage-chunked-manifest") return false;
14660
+ return Number.isInteger(m["totalSize"]) && m["totalSize"] >= 0 && m["totalSize"] <= MAX_REASSEMBLY_BYTES && Number.isInteger(m["chunkSize"]) && m["chunkSize"] > 0 && Number.isInteger(m["chunkCount"]) && m["chunkCount"] >= 0 && m["chunkCount"] <= MAX_CHUNK_COUNT;
14718
14661
  }
14719
14662
 
14720
- // src/metaTx/MetaTx.ts
14721
- init_dist();
14722
- var MetaTx = class {
14723
- constructor(sdk) {
14724
- this.progressCallback = null;
14725
- this.steps = buildInitialSteps();
14726
- this.startedAt = 0;
14727
- this.sdk = sdk;
14728
- }
14729
- /** Register a progress listener. Called after every step state change. */
14730
- onProgress(cb) {
14731
- this.progressCallback = cb;
14732
- return this;
14733
- }
14734
- // ── Public: execute the full meta-transaction ─────────────────────────────
14735
- async execute(file, config = {}) {
14736
- this.startedAt = Date.now();
14737
- this.steps = buildInitialSteps();
14738
- this.emit({ overallStatus: "running" });
14663
+ // src/dStorage.ts
14664
+ var TAG_SDK_VERSION = "dStorage-Version";
14665
+ var TAG_CONTENT_TYPE = "Content-Type";
14666
+ var TAG_CONTENT_TYPE_OCTET_STREAM = "application/octet-stream";
14667
+ var TAG_CONTENT_TYPE_JSON = "application/json";
14668
+ var TAG_CHUNK_INDEX = "dStorage-Chunk-Index";
14669
+ var TAG_CHUNK_TOTAL = "dStorage-Chunk-Total";
14670
+ var TAG_MANIFEST_UPLOAD = "dStorage-Manifest-Upload";
14671
+ function wrapError(err) {
14672
+ if (err instanceof DStorageError || isDStorageError(err)) throw err;
14673
+ throw new DStorageError(
14674
+ CoreErrorCode.UNKNOWN_ERROR,
14675
+ `[dStorage] Unexpected error: ${err instanceof Error ? err.message : String(err)}`,
14676
+ { cause: err }
14677
+ );
14678
+ }
14679
+ var DStorage = class {
14680
+ constructor(config) {
14681
+ this.wallet = null;
14682
+ /** Encryption adapters — each wraps/unwraps per-upload DEKs using its own algorithm. */
14683
+ this._encryptionProviders = [];
14684
+ this._initialized = false;
14685
+ this.config = config;
14686
+ this.logger = config.logger;
14687
+ }
14688
+ // ── Public accessors ─────────────────────────────────────────────────────
14689
+ get storageAdapter() {
14690
+ return this.config.storageAdapter;
14691
+ }
14692
+ get chainAdapter() {
14693
+ return this.config.chainAdapter;
14694
+ }
14695
+ // ── Lifecycle ─────────────────────────────────────────────────────────────
14696
+ /**
14697
+ * Initialize the SDK:
14698
+ * 1. Initialize the chain adapter wallet (if supported), and
14699
+ * deploy a new DataRegistry contract, or connect to an existing one.
14700
+ * 2. Derive an encryption key from the chain adapter's credentials (if supported).
14701
+ *
14702
+ * Returns the address of the DataRegistry contract in use.
14703
+ * Call this instead of connect() when using MidnightChainAdapter directly.
14704
+ */
14705
+ async init() {
14739
14706
  try {
14740
- this.startStep("estimate", "Analysing file and fetching price feeds\u2026");
14741
- const estimate = await this.sdk.estimateCost(file.size);
14742
- this.completeStep(
14743
- "estimate",
14744
- estimate.chainCost ? `${estimate.storageCost.amount} ${estimate.storageCost.token} + ${estimate.chainCost.amount} ${estimate.chainCost.token}` : `${estimate.storageCost.amount} ${estimate.storageCost.token} (storage only)`
14745
- );
14746
- this.emit({ overallStatus: "running", estimate });
14747
- this.startStep("encrypt", "Reading file bytes\u2026");
14748
- const fileBytes = await readFileAsBytes(file);
14749
- this.updateStepDetail("encrypt", "Encrypting with XChaCha20-Poly1305\u2026");
14750
- const uploadCrypto = await this.sdk.prepareUploadCrypto();
14751
- const uploadData = uploadCrypto.uploadScheme ? await uploadCrypto.uploadScheme.encryptPayload(fileBytes, PAYLOAD_AAD) : fileBytes;
14752
- const fileSizeMB = file.size / (1024 * 1024);
14753
- const fileSizeDisplay = fileSizeMB < 1.1 ? `${(file.size / 1024).toFixed(2)} KB` : `${fileSizeMB.toFixed(2)} MB`;
14754
- this.completeStep(
14755
- "encrypt",
14756
- `${fileSizeDisplay} encrypted (${(uploadData.byteLength / 1024).toFixed(2)} KB ciphertext)`
14757
- );
14758
- const uploadMetadata = {
14759
- "dstorage-version": "0.1.0",
14760
- "dstorage-filename": file.name,
14761
- "dstorage-filetype": file.type,
14762
- "dstorage-filesize": String(file.size),
14763
- ...config.metadata ?? {}
14764
- };
14765
- if (this.sdk.storageAdapter.paymentAdapter.provideUploadData) {
14766
- this.sdk.storageAdapter.paymentAdapter.provideUploadData(
14767
- uploadData,
14768
- uploadMetadata
14707
+ if (this.config.chainAdapter?.init) {
14708
+ await this.config.chainAdapter.init();
14709
+ }
14710
+ this._encryptionProviders = this.config.encryptionAdapters ?? [];
14711
+ this._initialized = true;
14712
+ return this.config.chainAdapter?.getContractAddress();
14713
+ } catch (err) {
14714
+ wrapError(err);
14715
+ }
14716
+ }
14717
+ /** Tear down active state and clear all in-memory key material. */
14718
+ destroy() {
14719
+ this.wallet = null;
14720
+ this._encryptionProviders = [];
14721
+ this._initialized = false;
14722
+ this.logger?.log(
14723
+ "dStorage",
14724
+ "Destroyed \u2014 encryption key cleared from memory."
14725
+ );
14726
+ }
14727
+ get isConnected() {
14728
+ return this._initialized || this.wallet !== null;
14729
+ }
14730
+ get connectedAddress() {
14731
+ return this.wallet?.address ?? null;
14732
+ }
14733
+ // ── Core operations ───────────────────────────────────────────────────────
14734
+ /**
14735
+ * Encrypt content client-side, store to decentralised storage,
14736
+ * and write a cryptographic reference on-chain.
14737
+ *
14738
+ * Files larger than CHUNK_SIZE (10 MB) are automatically split into chunks,
14739
+ * each encrypted and stored independently. A manifest file ties them together.
14740
+ * Only the manifest's storageId is written on-chain.
14741
+ *
14742
+ * @param bytes Uint8Array to store
14743
+ * @param options Optional StoreOptions (metadata, onProgress, isPublic, refId)
14744
+ * @returns StoreResult containing the chainRefId/storageId needed to retrieve later
14745
+ */
14746
+ async store(bytes, options) {
14747
+ try {
14748
+ this.assertConnected();
14749
+ const {
14750
+ tags = {},
14751
+ metadata: rawMeta,
14752
+ onProgress,
14753
+ isPublic = false,
14754
+ refId
14755
+ } = options ?? {};
14756
+ if (rawMeta !== void 0 && isPublic) {
14757
+ throw new DStorageError(
14758
+ CoreErrorCode.METADATA_WITH_PUBLIC_UPLOAD,
14759
+ "[dStorage] metadata cannot be used with isPublic uploads \u2014 there is no DEK to encrypt it with."
14769
14760
  );
14770
14761
  }
14771
- this.startStep(
14772
- "payment_storage",
14773
- `Preparing ${estimate.storageCost.amount} ${estimate.storageCost.token} payment\u2026`
14774
- );
14775
- const storageReceipt = await this.sdk.storageAdapter.paymentAdapter.pay(
14776
- {
14777
- network: this.sdk.storageAdapter.name,
14778
- dataSizeBytes: uploadData.byteLength,
14779
- expiresAt: Date.now() + 6e5
14780
- },
14781
- (msg) => this.updateStepDetail("payment_storage", msg)
14782
- );
14783
- this.completeStep(
14784
- "payment_storage",
14785
- `${storageReceipt.amount} ${storageReceipt.token}`
14786
- );
14787
- this.startStep("upload", `Uploading to ${this.sdk.storageAdapter.name}\u2026`);
14788
- const uploadResult = await this.sdk.storageAdapter.store(
14789
- uploadData,
14790
- uploadMetadata
14791
- );
14792
- this.completeStep(
14793
- "upload",
14794
- `Stored on ${uploadResult.provider} \u2192 ${truncateId(uploadResult.id)}`
14795
- );
14796
- let chainRefId;
14797
- let chainPayment;
14798
- let chainToken;
14799
- if (this.sdk.chainAdapter) {
14800
- this.startStep(
14801
- "chain_reference",
14802
- `Fee payment and writing reference to ${this.sdk.chainAdapter.name}\u2026`
14762
+ if (bytes.length > CHUNK_SIZE) {
14763
+ return this._uploadChunked(
14764
+ bytes,
14765
+ tags,
14766
+ onProgress,
14767
+ isPublic,
14768
+ rawMeta,
14769
+ refId
14803
14770
  );
14804
- const onChainStorageId = uploadCrypto.uploadScheme ? await uploadCrypto.uploadScheme.encryptStorageId(uploadResult.id) : uploadResult.id;
14805
- const { deriveOwnerSecret: deriveOwnerSecret2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
14806
- let ownerSecret;
14807
- if (uploadCrypto.dek) {
14808
- ownerSecret = await deriveOwnerSecret2(
14809
- uploadCrypto.dek,
14810
- new TextEncoder().encode(uploadResult.id)
14771
+ }
14772
+ let uploadData;
14773
+ let onChainStorageId;
14774
+ const encryptionScheme = isPublic ? "" : "xchacha20poly1305-v1";
14775
+ let uploadScheme = null;
14776
+ let keyEnvelope = "";
14777
+ let dek = null;
14778
+ let ownerSecret = new Uint8Array(0);
14779
+ if (isPublic) {
14780
+ uploadData = bytes;
14781
+ } else {
14782
+ if (this._encryptionProviders.length === 0) {
14783
+ throw new DStorageError(
14784
+ CoreErrorCode.UPLOAD_NO_PROVIDERS,
14785
+ "[dStorage] No encryption providers available. Call init() before uploading."
14811
14786
  );
14812
- } else {
14813
- ownerSecret = await deriveOwnerSecret2(
14814
- uploadCrypto.ownerSeed,
14815
- new TextEncoder().encode(uploadResult.id),
14787
+ }
14788
+ dek = generateDek();
14789
+ uploadScheme = XChaChaScheme.fromKey(dek);
14790
+ this.logger?.log(
14791
+ "dStorage",
14792
+ `Encrypting content using scheme: ${encryptionScheme}`
14793
+ );
14794
+ uploadData = await uploadScheme.encryptPayload(bytes, PAYLOAD_AAD);
14795
+ const wrapperEntries = await Promise.all(
14796
+ this._encryptionProviders.map(async (provider) => ({
14797
+ wrapKeyName: provider.name,
14798
+ ...await provider.wrapDek(dek)
14799
+ }))
14800
+ );
14801
+ keyEnvelope = serializeKeyEnvelope(buildKeyEnvelope(wrapperEntries));
14802
+ }
14803
+ const sdkVersion = package_default.version ?? "unknown";
14804
+ const storageResult = await this.config.storageAdapter.store(uploadData, {
14805
+ ...tags,
14806
+ [TAG_SDK_VERSION]: sdkVersion,
14807
+ [TAG_CONTENT_TYPE]: TAG_CONTENT_TYPE_OCTET_STREAM
14808
+ });
14809
+ if (isPublic) {
14810
+ if (this.config.chainAdapter) {
14811
+ if (this._encryptionProviders.length === 0) {
14812
+ throw new DStorageError(
14813
+ CoreErrorCode.PUBLIC_UPLOAD_REQUIRES_SEED_PROVIDER,
14814
+ "[dStorage] Cannot store public data without at least one encryption seed provider. Without a provider there is no owner secret, so the reference could never be removed or updated."
14815
+ );
14816
+ }
14817
+ const ownerSeed = generateDek();
14818
+ const ownerSeedWrappers = await Promise.all(
14819
+ this._encryptionProviders.map(async (provider) => ({
14820
+ wrapKeyName: provider.name,
14821
+ ...await provider.wrapDek(ownerSeed)
14822
+ }))
14823
+ );
14824
+ keyEnvelope = serializeKeyEnvelope(
14825
+ buildKeyEnvelope(ownerSeedWrappers)
14826
+ );
14827
+ ownerSecret = await deriveOwnerSecret(
14828
+ ownerSeed,
14829
+ new TextEncoder().encode(storageResult.id),
14816
14830
  "dstorage:owner-secret:public:v1"
14817
14831
  );
14818
14832
  }
14819
- const chainResult = await this.sdk.chainAdapter.writeReference({
14820
- storageId: onChainStorageId,
14821
- encryptionScheme: uploadCrypto.encryptionScheme,
14822
- storageProvider: uploadResult.provider,
14823
- writtenAt: Date.now(),
14824
- keyEnvelope: uploadCrypto.keyEnvelope,
14825
- ownerSecret
14826
- });
14827
- const finalChainReceipt = chainResult.paymentReceipt ?? {
14828
- amount: "<unknown>",
14829
- token: estimate.chainCost.token,
14830
- txHash: "",
14831
- type: "chain",
14832
- paidAt: 0,
14833
- serviceFee: "0"
14834
- };
14835
- chainRefId = chainResult.refId;
14836
- chainPayment = finalChainReceipt;
14837
- chainToken = finalChainReceipt.token;
14838
- this.completeStep(
14839
- "chain_reference",
14840
- `Reference written \u2192 ${finalChainReceipt.amount} ${finalChainReceipt.token} \u2192 ${truncateId(chainResult.refId)}`
14841
- );
14833
+ onChainStorageId = storageResult.id;
14842
14834
  } else {
14843
- this.completeStep(
14844
- "chain_reference",
14845
- "Skipped \u2014 no chain adapter configured"
14835
+ onChainStorageId = await uploadScheme.encryptStorageId(
14836
+ storageResult.id
14837
+ );
14838
+ ownerSecret = await deriveOwnerSecret(
14839
+ dek,
14840
+ new TextEncoder().encode(storageResult.id)
14846
14841
  );
14847
14842
  }
14848
- const dataId = chainRefId ? `${uploadResult.id}::${chainRefId}` : uploadResult.id;
14849
- const totalDurationMs = Date.now() - this.startedAt;
14850
- this.completeStep(
14851
- "complete",
14852
- `Completed in ${(totalDurationMs / 1e3).toFixed(1)}s`
14853
- );
14854
- const receipt = {
14855
- dataId,
14856
- storageId: uploadResult.id,
14857
- ...chainRefId ? { chainRefId } : {},
14858
- file: {
14859
- name: file.name,
14860
- type: file.type,
14861
- sizeBytes: file.size
14862
- },
14863
- storagePayment: storageReceipt,
14864
- ...chainPayment ? { chainPayment, chainToken } : {},
14865
- storageToken: storageReceipt.token,
14866
- ...config.contractAddress ? {
14867
- contract: {
14868
- address: config.contractAddress,
14869
- name: config.contractName ?? config.contractAddress
14870
- }
14871
- } : {},
14872
- completedAt: Date.now(),
14873
- totalDurationMs
14843
+ const storageCost = {
14844
+ amount: storageResult.cost?.amount ?? "unknown",
14845
+ token: storageResult.cost?.token ?? "AR"
14874
14846
  };
14875
- this.emit({ overallStatus: "complete", estimate, receipt });
14876
- return receipt;
14877
- } catch (err) {
14878
- const message = err instanceof Error ? err.message : String(err);
14879
- const runningStep = this.steps.find((s) => s.status === "running");
14880
- if (runningStep) {
14881
- runningStep.status = "error";
14882
- runningStep.detail = message;
14883
- }
14884
- this.emit({ overallStatus: "error", error: message });
14885
- throw err;
14886
- }
14887
- }
14888
- // ── Private step helpers ──────────────────────────────────────────────────
14889
- startStep(id, detail) {
14890
- const step = this.getStep(id);
14891
- step.status = "running";
14892
- step.detail = detail;
14893
- step.startedAt = Date.now();
14894
- this.emit({ overallStatus: "running", currentStepId: id });
14895
- }
14896
- updateStepDetail(id, detail) {
14897
- const step = this.getStep(id);
14898
- step.detail = detail;
14899
- this.emit({ overallStatus: "running", currentStepId: id });
14900
- }
14901
- completeStep(id, detail) {
14902
- const step = this.getStep(id);
14903
- step.status = "done";
14904
- step.detail = detail;
14905
- if (step.startedAt) step.durationMs = Date.now() - step.startedAt;
14906
- this.emit({ overallStatus: "running", currentStepId: id });
14907
- }
14908
- getStep(id) {
14909
- const step = this.steps.find((s) => s.id === id);
14910
- if (!step)
14911
- throw new DStorageError(
14912
- CoreErrorCode.META_TX_UNKNOWN_STEP,
14913
- `[dStorage] Unknown meta-transaction step: ${id}`
14914
- );
14915
- return step;
14916
- }
14917
- emit(overrides) {
14918
- if (!this.progressCallback) return;
14919
- const currentRunning = this.steps.find((s) => s.status === "running");
14920
- this.progressCallback({
14921
- steps: [...this.steps.map((s) => ({ ...s }))],
14922
- currentStepId: currentRunning?.id ?? overrides.currentStepId ?? null,
14923
- overallStatus: "running",
14924
- ...overrides
14925
- });
14926
- }
14927
- };
14928
-
14929
- // ../payment/dist/chunk-IVL4WQ5Y.mjs
14930
- var TOKENS = {
14931
- AR: { symbol: "AR", name: "Arweave", decimals: 12 },
14932
- DUST: { symbol: "DUST", name: "Midnight", decimals: 6 },
14933
- MOCK: {
14934
- symbol: "MOCK",
14935
- name: "Mock Token",
14936
- decimals: 6
14937
- }
14938
- };
14939
-
14940
- // ../payment/dist/chunk-R6FT3AV4.mjs
14941
- var MockPaymentAdapter = class {
14942
- constructor(config) {
14943
- this.network = "mock";
14944
- this.token = config.token ?? "MOCK";
14945
- this.type = config.type;
14946
- this.quoteValidityMs = config.quoteValidityMs ?? 6e5;
14947
- this.logger = config.logger;
14948
- }
14949
- async estimateCost(dataSizeBytes) {
14950
- const kb = dataSizeBytes / 1024;
14951
- const amount = Math.max(1e-4, kb * 1e-3).toFixed(6);
14952
- this.logger?.log(
14953
- "dStorage/mock-payment",
14954
- `Estimated cost: ${amount} ${this.token}`
14955
- );
14956
- return {
14957
- token: this.token,
14958
- amount,
14959
- network: this.network,
14960
- type: this.type,
14961
- dataSizeBytes,
14962
- expiresAt: Date.now() + this.quoteValidityMs
14963
- };
14964
- }
14965
- async pay(instruction, onStatus) {
14966
- const tokenName = TOKENS[this.token]?.name ?? this.token;
14967
- onStatus?.(`Requesting ${tokenName} wallet signature\u2026`);
14968
- onStatus?.(`Broadcasting ${this.type} payment transaction\u2026`);
14969
- onStatus?.("Awaiting confirmation\u2026");
14970
- const txHash = generateSimulatedTxHash();
14971
- const { amount } = await this.estimateCost(instruction.dataSizeBytes);
14972
- this.logger?.log(
14973
- "dStorage/mock-payment",
14974
- `Simulated ${this.type} payment \u2192 txHash: ${txHash}`
14975
- );
14976
- return {
14977
- txHash,
14978
- token: this.token,
14979
- amount,
14980
- type: this.type,
14981
- paidAt: Date.now(),
14982
- serviceFee: "0"
14983
- };
14984
- }
14985
- };
14986
- function generateSimulatedTxHash() {
14987
- const hex = Array.from(
14988
- { length: 64 },
14989
- () => Math.floor(Math.random() * 16).toString(16)
14990
- ).join("");
14991
- return `0x${hex}`;
14992
- }
14993
-
14994
- // ../payment/dist/index.mjs
14995
- var import_blake32 = require("@noble/hashes/blake3.js");
14996
- init_dist();
14997
- init_dist();
14998
- var PaymentErrorCode = {
14999
- // adapters/arweave.ts (14000–14099)
15000
- ARWEAVE_NOT_INSTALLED: 14001,
15001
- ARWEAVE_WALLET_KEY_REQUIRED: 14002,
15002
- ARWEAVE_CONNECT_NOT_FOUND: 14003,
15003
- ARWEAVE_QUOTE_EXPIRED: 14004,
15004
- // adapters/managed.ts (14100–14299)
15005
- MANAGED_NON_OBJECT_RESPONSE: 14100,
15006
- MANAGED_SERVER_FAILURE: 14101,
15007
- MANAGED_MISSING_FIELD: 14102,
15008
- MANAGED_PUBLIC_KEY_MISMATCH: 14103,
15009
- MANAGED_TOKEN_REQUIRED: 14104,
15010
- MANAGED_QUOTE_EXPIRED: 14105,
15011
- MANAGED_UNBALANCED_TX: 14106,
15012
- MANAGED_SERVER_UNREACHABLE: 14107,
15013
- MANAGED_KEY_CHANGED: 14108,
15014
- MANAGED_SIGN_HTTP_ERROR: 14109,
15015
- MANAGED_NON_JSON_RESPONSE: 14110
15016
- };
15017
- var MAX_ERROR_BODY_LENGTH = 200;
15018
- function sanitiseErrorBody(raw) {
15019
- return raw.replace(/[\x00-\x1f\x7f]/g, " ").replace(/<[^>]*>/g, "").trim().slice(0, MAX_ERROR_BODY_LENGTH);
15020
- }
15021
- var DEFAULT_QUOTE_VALIDITY_MS = 12e4;
15022
- var CONTENT_HASH_TAG = "X-Content-Hash";
15023
- var ArweavePaymentAdapter = class {
15024
- constructor(config = {}) {
15025
- this.network = "arweave";
15026
- this.token = "AR";
15027
- this.type = "storage";
15028
- this.arweaveClient = null;
15029
- this.pendingUpload = null;
15030
- this.signedUpload = null;
15031
- this.gateway = config.gateway ?? {
15032
- host: "arweave.net",
15033
- port: 443,
15034
- protocol: "https"
15035
- };
15036
- this.walletKey = config.walletKey;
15037
- this.quoteValidityMs = config.quoteValidityMs ?? DEFAULT_QUOTE_VALIDITY_MS;
15038
- this.logger = config.logger;
15039
- }
15040
- // ── Protected: lazy Arweave client ───────────────────────────────────────────
15041
- async getClient() {
15042
- if (this.arweaveClient) return this.arweaveClient;
15043
- try {
15044
- const _m = await import("arweave");
15045
- const raw = _m.default;
15046
- const ctor = typeof raw.init === "function" ? raw : raw.default ?? raw;
15047
- this.arweaveClient = ctor.init(this.gateway ?? {});
15048
- return this.arweaveClient;
15049
- } catch {
15050
- throw new DStorageError(
15051
- PaymentErrorCode.ARWEAVE_NOT_INSTALLED,
15052
- "[dStorage/arweave-payment] arweave package not installed. Run: npm install arweave"
15053
- );
15054
- }
15055
- }
15056
- // ── Protected: wallet resolution ─────────────────────────────────────────────
15057
- resolveWallet() {
15058
- if (this.walletKey && Object.keys(this.walletKey).length > 0) {
15059
- return this.walletKey;
15060
- }
15061
- if (typeof window === "undefined") {
15062
- throw new DStorageError(
15063
- PaymentErrorCode.ARWEAVE_WALLET_KEY_REQUIRED,
15064
- "[dStorage/arweave-payment] Node.js usage requires a walletKey in the config."
15065
- );
15066
- }
15067
- if (typeof window.arweaveWallet === "undefined") {
15068
- throw new DStorageError(
15069
- PaymentErrorCode.ARWEAVE_CONNECT_NOT_FOUND,
15070
- "[dStorage/arweave-payment] ArConnect extension not found. Install it from https://arconnect.io"
14847
+ if (this.config.chainAdapter) {
14848
+ const contentHash = await computeBlake3Hex(uploadData);
14849
+ const storeRecovery = {
14850
+ storageId: storageResult.id,
14851
+ storageProvider: storageResult.provider,
14852
+ keyEnvelope: isPublic ? void 0 : keyEnvelope,
14853
+ contentHash
14854
+ };
14855
+ onProgress?.({
14856
+ phase: "stored",
14857
+ chunksUploaded: 1,
14858
+ totalChunks: 1,
14859
+ bytesUploaded: bytes.length,
14860
+ totalBytes: bytes.length,
14861
+ recovery: storeRecovery
14862
+ });
14863
+ const encryptedMetadata = rawMeta ? bytesToBase64url(
14864
+ await uploadScheme.encryptPayload(
14865
+ new TextEncoder().encode(JSON.stringify(rawMeta)),
14866
+ METADATA_AAD
14867
+ )
14868
+ ) : void 0;
14869
+ try {
14870
+ const chainResult = await this.config.chainAdapter.writeReference({
14871
+ storageId: onChainStorageId,
14872
+ encryptionScheme,
14873
+ storageProvider: storageResult.provider,
14874
+ ...refId !== void 0 ? { refId } : {},
14875
+ ...encryptedMetadata !== void 0 ? { encryptedMetadata } : {},
14876
+ writtenAt: Date.now(),
14877
+ keyEnvelope,
14878
+ contentHash,
14879
+ ownerSecret
14880
+ });
14881
+ const resultRefId = chainResult.refId;
14882
+ this.logger?.log(
14883
+ "dStorage",
14884
+ `Upload complete \u2192 refId: ${resultRefId}`
14885
+ );
14886
+ return {
14887
+ chainRefId: resultRefId,
14888
+ storageId: storageResult.id,
14889
+ storageProvider: storageResult.provider,
14890
+ chainProvider: chainResult.provider,
14891
+ uploadedAt: storageResult.uploadedAt,
14892
+ encryptionScheme,
14893
+ costEstimate: {
14894
+ storageCost,
14895
+ chainCost: {
14896
+ amount: chainResult.paymentReceipt?.amount ?? "unknown",
14897
+ token: chainResult.paymentReceipt?.token ?? "DUST"
14898
+ },
14899
+ fileSizeBytes: uploadData.length
14900
+ }
14901
+ };
14902
+ } catch {
14903
+ throw new StorePartialError(storeRecovery);
14904
+ }
14905
+ }
14906
+ this.logger?.log(
14907
+ "dStorage",
14908
+ `Upload complete (storage-only) \u2192 storageId: ${storageResult.id}`
15071
14909
  );
14910
+ return {
14911
+ storageId: storageResult.id,
14912
+ storageProvider: storageResult.provider,
14913
+ uploadedAt: storageResult.uploadedAt,
14914
+ encryptionScheme,
14915
+ ...uploadScheme ? { cryptoScheme: uploadScheme } : {},
14916
+ costEstimate: { storageCost, fileSizeBytes: uploadData.length }
14917
+ };
14918
+ } catch (err) {
14919
+ wrapError(err);
15072
14920
  }
15073
- return "use_wallet";
15074
- }
15075
- // ── PaymentAdapter: provideUploadData ─────────────────────────────────────────
15076
- /**
15077
- * Store the encrypted upload payload and metadata tags so that pay() can
15078
- * build and sign the complete Arweave transaction in one step.
15079
- * Called by MetaTransaction after the encrypt step, before pay().
15080
- */
15081
- provideUploadData(data, metadata = {}) {
15082
- const hashHex = Array.from((0, import_blake32.blake3)(data)).map((b) => b.toString(16).padStart(2, "0")).join("");
15083
- this.pendingUpload = {
15084
- data,
15085
- metadata: { [CONTENT_HASH_TAG]: hashHex, ...metadata }
15086
- };
15087
- this.logger?.log(
15088
- "dStorage/arweave-payment",
15089
- `Upload data staged: ${data.byteLength} bytes`
15090
- );
15091
- }
15092
- // ── PaymentAdapter: estimateCost ─────────────────────────────────────────────
15093
- /**
15094
- * Query the Arweave fee oracle for the real upload cost.
15095
- */
15096
- async estimateCost(dataSizeBytes) {
15097
- const arweave = await this.getClient();
15098
- const winstons = await arweave.transactions.getPrice(
15099
- dataSizeBytes
15100
- );
15101
- const amount = arweave.ar.winstonToAr(winstons);
15102
- this.logger?.log(
15103
- "dStorage/arweave-payment",
15104
- `Estimated: ${amount} AR (${winstons} Winstons)`
15105
- );
15106
- return {
15107
- token: this.token,
15108
- amount,
15109
- network: this.network,
15110
- type: this.type,
15111
- dataSizeBytes,
15112
- expiresAt: Date.now() + this.quoteValidityMs
15113
- };
15114
14921
  }
15115
- // ── PaymentAdapter: pay ───────────────────────────────────────────────────────
15116
14922
  /**
15117
- * Sign-then-store flow (when provideUploadData was called):
15118
- * Creates the Arweave transaction with data + tags, prompts the wallet
15119
- * for approval (ArConnect popup or JWK signing), and stores the signed
15120
- * transaction for ArweaveStorageAdapter.store() to post.
14923
+ * Register a pre-existing storageId as a new on-chain reference.
15121
14924
  *
15122
- * Pass-through fallback (when provideUploadData was NOT called):
15123
- * Returns a receipt acknowledging that payment is bundled with the upload.
15124
- * ArweaveStorageAdapter.store() performs the full create sign post flow.
14925
+ * Use this when data has already been uploaded to the storage network by
14926
+ * other means (e.g. a third-party tool or a separate pipeline). The content
14927
+ * is not touched only the chain reference is created.
14928
+ *
14929
+ * A fresh DEK is generated solely to encrypt the storageId and derive the
14930
+ * ownerSecret. The resulting reference round-trips through retrieveByRefId()
14931
+ * exactly like one created by store().
14932
+ *
14933
+ * Note: chunked manifests are not handled here. For large pre-existing
14934
+ * uploads already split into chunks, register the manifest storageId.
15125
14935
  */
15126
- async pay(instruction, onStatus) {
15127
- if (Date.now() > instruction.expiresAt) {
15128
- throw new DStorageError(
15129
- PaymentErrorCode.ARWEAVE_QUOTE_EXPIRED,
15130
- `[dStorage/payment] Quote expired at ${new Date(instruction.expiresAt).toISOString()}. Request a fresh quote via estimateCost() before calling pay().`
15131
- );
15132
- }
15133
- if (!this.pendingUpload) {
15134
- onStatus?.("AR fee will be deducted as part of the upload transaction.");
15135
- return {
15136
- txHash: "arweave-bundled-with-upload",
15137
- token: this.token,
15138
- amount: "0",
15139
- type: this.type,
15140
- paidAt: Date.now(),
15141
- serviceFee: "0"
15142
- };
15143
- }
15144
- const { data, metadata } = this.pendingUpload;
15145
- this.pendingUpload = null;
15146
- const arweave = await this.getClient();
15147
- const wallet = this.resolveWallet();
15148
- onStatus?.("Creating Arweave transaction\u2026");
15149
- const tx = await arweave.createTransaction({ data }, wallet);
15150
- for (const [key, value] of Object.entries(metadata)) {
15151
- tx.addTag(key, value);
15152
- }
15153
- onStatus?.("Requesting AR wallet approval\u2026");
15154
- await arweave.transactions.sign(tx, wallet);
15155
- const winstons = await arweave.transactions.getPrice(
15156
- data.byteLength
15157
- );
15158
- const arCost = arweave.ar.winstonToAr(winstons);
15159
- this.signedUpload = { tx, arCost };
15160
- this.logger?.log(
15161
- "dStorage/arweave-payment",
15162
- `Transaction signed \u2192 txId: ${tx.id} cost: ~${arCost} AR`
15163
- );
15164
- return {
15165
- txHash: tx.id,
15166
- token: this.token,
15167
- amount: arCost,
15168
- type: this.type,
15169
- paidAt: Date.now(),
15170
- serviceFee: "0"
15171
- };
15172
- }
15173
- // ── Consumed by ArweaveStorageAdapter ─────────────────────────────────────────
15174
- /**
15175
- * Returns the pre-signed transaction produced by pay(), then clears it.
15176
- * Called by ArweaveStorageAdapter.store() to post the already-signed tx.
15177
- * Returns null if pay() did not sign a transaction (pass-through mode).
15178
- */
15179
- consumeSignedUpload() {
15180
- const signed = this.signedUpload;
15181
- this.signedUpload = null;
15182
- return signed;
15183
- }
15184
- };
15185
- var POST_SIGN_TX_API_PATH = "/api/service/sign-tx";
15186
- function uint8ArrayToHex(bytes) {
15187
- return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
15188
- }
15189
- function parseSignedTxBytes(signedTx) {
15190
- if (signedTx.startsWith("midnight:")) {
15191
- const colonIdx = signedTx.lastIndexOf(":");
15192
- return hexToBytes(signedTx.slice(colonIdx + 1), "signedTx");
15193
- }
15194
- return hexToBytes(signedTx, "signedTx");
15195
- }
15196
- function parseSignTxResponse(raw) {
15197
- if (typeof raw !== "object" || raw === null) {
15198
- throw new DStorageError(
15199
- PaymentErrorCode.MANAGED_NON_OBJECT_RESPONSE,
15200
- "[dStorage/managed-payment] Signing server returned a non-object response."
15201
- );
15202
- }
15203
- const r = raw;
15204
- if (r["success"] !== true) {
15205
- throw new DStorageError(
15206
- PaymentErrorCode.MANAGED_SERVER_FAILURE,
15207
- `[dStorage/managed-payment] Signing server reported success:false \u2014 ${sanitiseErrorBody(JSON.stringify(r))}`
15208
- );
15209
- }
15210
- for (const field of [
15211
- "txId",
15212
- "signature",
15213
- "publicKey",
15214
- "txAmount",
15215
- "serviceFee"
15216
- ]) {
15217
- if (typeof r[field] !== "string") {
15218
- throw new DStorageError(
15219
- PaymentErrorCode.MANAGED_MISSING_FIELD,
15220
- `[dStorage/managed-payment] Signing server response missing or invalid field: ${field}`
15221
- );
15222
- }
15223
- }
15224
- return r;
15225
- }
15226
- function assertOwnerKeyMatch(responseKey, pinnedKey) {
15227
- if (pinnedKey && responseKey !== pinnedKey) {
15228
- throw new DStorageError(
15229
- PaymentErrorCode.MANAGED_PUBLIC_KEY_MISMATCH,
15230
- "[dStorage/managed-payment] Signing server returned a public key that does not match the pinned key from the auth token \u2014 possible server compromise or MITM."
15231
- );
15232
- }
15233
- }
15234
- var ManagedPaymentAdapter = class _ManagedPaymentAdapter extends ArweavePaymentAdapter {
15235
- /**
15236
- * Strip a RSA-4096 modulus suffix from a compound auth token
15237
- * (<credential>.<base64url_683_chars>), returning only the credential.
15238
- * A plain token (no '.' or wrong suffix length) is returned unchanged.
15239
- * This lets ArweaveBundlerStorageAdapter compound tokens be passed directly
15240
- * to any adapter that uses ManagedPaymentAdapter without breaking authentication.
15241
- *
15242
- * lastIndexOf is intentional: the credential part may itself contain dots
15243
- * (e.g. a JWT), so we always split at the *last* dot to reach the modulus.
15244
- */
15245
- static _parseAuthToken(token) {
15246
- const dot = token.lastIndexOf(".");
15247
- if (dot !== -1) {
15248
- const suffix = token.slice(dot + 1);
15249
- if (suffix.length === 683 && /^[A-Za-z0-9_-]+$/.test(suffix)) {
15250
- return { credential: token.slice(0, dot), ownerPublicKey: suffix };
14936
+ async registerReference(options = {}) {
14937
+ try {
14938
+ this.assertConnected();
14939
+ if (!this.config.chainAdapter) {
14940
+ throw new DStorageError(
14941
+ CoreErrorCode.REGISTER_REF_REQUIRES_CHAIN,
14942
+ "[dStorage] registerReference requires a chain adapter."
14943
+ );
15251
14944
  }
14945
+ if (this._encryptionProviders.length === 0) {
14946
+ throw new DStorageError(
14947
+ CoreErrorCode.REGISTER_REF_REQUIRES_ENCRYPTION,
14948
+ "[dStorage] registerReference requires at least one encryption adapter."
14949
+ );
14950
+ }
14951
+ let dek;
14952
+ const recoveryEnvelope = options.keyEnvelope;
14953
+ if (recoveryEnvelope) {
14954
+ dek = await this._unwrapDek(recoveryEnvelope);
14955
+ } else {
14956
+ dek = generateDek();
14957
+ }
14958
+ try {
14959
+ const scheme = XChaChaScheme.fromKey(dek);
14960
+ const { metadata: rawMeta } = options;
14961
+ const encryptedMetadata = rawMeta ? bytesToBase64url(
14962
+ await scheme.encryptPayload(
14963
+ new TextEncoder().encode(JSON.stringify(rawMeta)),
14964
+ METADATA_AAD
14965
+ )
14966
+ ) : void 0;
14967
+ const onChainStorageId = options.storageId !== void 0 ? await scheme.encryptStorageId(options.storageId) : void 0;
14968
+ let keyEnvelope;
14969
+ if (recoveryEnvelope !== void 0) {
14970
+ keyEnvelope = recoveryEnvelope;
14971
+ } else {
14972
+ const wrapperEntries = await Promise.all(
14973
+ this._encryptionProviders.map(async (p) => ({
14974
+ wrapKeyName: p.name,
14975
+ ...await p.wrapDek(dek)
14976
+ }))
14977
+ );
14978
+ keyEnvelope = serializeKeyEnvelope(buildKeyEnvelope(wrapperEntries));
14979
+ }
14980
+ const ownerSecret = await deriveOwnerSecret(
14981
+ dek,
14982
+ options.storageId !== void 0 ? new TextEncoder().encode(options.storageId) : METADATA_ONLY_OWNER_SECRET_SALT
14983
+ );
14984
+ const chainResult = await this.config.chainAdapter.writeReference({
14985
+ ...onChainStorageId !== void 0 ? { storageId: onChainStorageId } : {},
14986
+ encryptionScheme: "xchacha20poly1305-v1",
14987
+ storageProvider: options.storageProvider ?? this.config.storageAdapter.name,
14988
+ ...options.refId !== void 0 ? { refId: options.refId } : {},
14989
+ ...encryptedMetadata !== void 0 ? { encryptedMetadata } : {},
14990
+ writtenAt: Date.now(),
14991
+ keyEnvelope,
14992
+ ...options.contentHash !== void 0 ? { contentHash: options.contentHash } : {},
14993
+ ownerSecret
14994
+ });
14995
+ return { chainRefId: chainResult.refId };
14996
+ } finally {
14997
+ dek.fill(0);
14998
+ }
14999
+ } catch (err) {
15000
+ wrapError(err);
15252
15001
  }
15253
- return { credential: token, ownerPublicKey: void 0 };
15254
- }
15255
- constructor(config) {
15256
- super({
15257
- ...config.gateway !== void 0 ? { gateway: config.gateway } : {},
15258
- ...config.quoteValidityMs !== void 0 ? { quoteValidityMs: config.quoteValidityMs } : {},
15259
- ...config.logger !== void 0 ? { logger: config.logger } : {}
15260
- });
15261
- this.signingServerUrl = config.signingServerUrl.replace(/\/$/, "");
15262
- if (!config.authToken || config.authToken.trim() === "") {
15263
- throw new DStorageError(
15264
- PaymentErrorCode.MANAGED_TOKEN_REQUIRED,
15265
- "[dStorage/managed-payment] authToken is required and must not be empty or whitespace."
15266
- );
15267
- }
15268
- const { credential, ownerPublicKey } = _ManagedPaymentAdapter._parseAuthToken(config.authToken);
15269
- this.authToken = credential;
15270
- this.ownerPublicKey = ownerPublicKey;
15271
- this.requestTimeoutMs = config.requestTimeoutMs ?? 3e4;
15272
- this.network = config.network;
15273
- this.type = config.type;
15274
- }
15275
- // ── PaymentAdapter: estimateCost ─────────────────────────────────────────────
15276
- /**
15277
- * Query the Arweave fee oracle for the real upload cost.
15278
- */
15279
- async estimateCost(dataSizeBytes) {
15280
- if (this.network === "arweave") {
15281
- return await super.estimateCost(dataSizeBytes);
15282
- }
15283
- const amount = "0.0012345";
15284
- this.logger?.log(
15285
- "dStorage/managed-payment",
15286
- `Estimated: ${amount} ${this.token}`
15287
- );
15288
- return {
15289
- token: this.token,
15290
- // FIXME: should be probably obtained from the signing service
15291
- amount,
15292
- network: this.network,
15293
- type: this.type,
15294
- dataSizeBytes,
15295
- txData: "---",
15296
- expiresAt: Date.now() + this.quoteValidityMs
15297
- };
15298
15002
  }
15299
- // ── PaymentAdapter: pay ───────────────────────────────────────────────────────
15300
15003
  /**
15301
- * Routes payment to the correct network-specific handler based on quote.network.
15004
+ * Store new content and update an existing on-chain reference in place.
15302
15005
  *
15303
- * The Arweave path falls back to the base-class pass-through if
15304
- * provideUploadData() was not called. An unrecognised network throws.
15006
+ * Ownership is proved by re-deriving the ownerSecret from the existing
15007
+ * reference the same mechanism used by removeReference(). A fresh DEK is
15008
+ * generated for the new content; the previous version is not modified and remains
15009
+ * independently decryptable if the caller retained the old storageId.
15010
+ *
15011
+ * Note: chunking is not applied here. For large files use store() to get a
15012
+ * new chunked reference, then removeReference() on the old one.
15013
+ *
15014
+ * @param refId The chain reference to update (returned by a prior store/registerReference).
15015
+ * @param bytes New content to encrypt and store.
15016
+ * @param options Optional StoreOptions. isPublic is ignored.
15305
15017
  */
15306
- async pay(instruction, onStatus) {
15307
- if (Date.now() > instruction.expiresAt) {
15308
- throw new DStorageError(
15309
- PaymentErrorCode.MANAGED_QUOTE_EXPIRED,
15310
- `[dStorage/payment] Quote expired at ${new Date(instruction.expiresAt).toISOString()}. Request a fresh quote via estimateCost() before calling pay().`
15311
- );
15312
- }
15313
- if (this.network === "managedmock") {
15314
- return this._payManagedMock(instruction, onStatus);
15315
- }
15316
- if (instruction.network === "midnight") {
15317
- return this._payMidnight(instruction, onStatus);
15318
- } else if (instruction.network === "arweave") {
15319
- return this._payArweave(instruction, onStatus);
15320
- } else if (instruction.network === "arweave_bundler") {
15321
- return this._payArweaveBundler(instruction, onStatus);
15018
+ async update(refId, bytes, options) {
15019
+ try {
15020
+ this.assertConnected();
15021
+ if (!this.config.chainAdapter) {
15022
+ throw new DStorageError(
15023
+ CoreErrorCode.UPDATE_UPLOAD_REQUIRES_CHAIN,
15024
+ "[dStorage] update requires a chain adapter."
15025
+ );
15026
+ }
15027
+ if (typeof this.config.chainAdapter.updateReference !== "function") {
15028
+ throw new DStorageError(
15029
+ CoreErrorCode.UPDATE_UPLOAD_NOT_SUPPORTED,
15030
+ "[dStorage] update is not supported by this chain adapter."
15031
+ );
15032
+ }
15033
+ if (this._encryptionProviders.length === 0) {
15034
+ throw new DStorageError(
15035
+ CoreErrorCode.UPDATE_UPLOAD_REQUIRES_ENCRYPTION,
15036
+ "[dStorage] update requires at least one encryption adapter."
15037
+ );
15038
+ }
15039
+ const { tags = {}, metadata: rawMeta } = options ?? {};
15040
+ const ownerSecret = await this._computeOwnerSecret(refId);
15041
+ const dek = generateDek();
15042
+ try {
15043
+ const scheme = XChaChaScheme.fromKey(dek);
15044
+ const uploadData = await scheme.encryptPayload(bytes, PAYLOAD_AAD);
15045
+ const storageResult = await this.config.storageAdapter.store(
15046
+ uploadData,
15047
+ {
15048
+ ...tags,
15049
+ [TAG_CONTENT_TYPE]: TAG_CONTENT_TYPE_OCTET_STREAM
15050
+ }
15051
+ );
15052
+ const onChainStorageId = await scheme.encryptStorageId(
15053
+ storageResult.id
15054
+ );
15055
+ const wrapperEntries = await Promise.all(
15056
+ this._encryptionProviders.map(async (p) => ({
15057
+ wrapKeyName: p.name,
15058
+ ...await p.wrapDek(dek)
15059
+ }))
15060
+ );
15061
+ const keyEnvelope = serializeKeyEnvelope(
15062
+ buildKeyEnvelope(wrapperEntries)
15063
+ );
15064
+ const encryptedMetadata = rawMeta ? bytesToBase64url(
15065
+ await scheme.encryptPayload(
15066
+ new TextEncoder().encode(JSON.stringify(rawMeta)),
15067
+ METADATA_AAD
15068
+ )
15069
+ ) : void 0;
15070
+ const contentHash = await computeBlake3Hex(uploadData);
15071
+ const rawNewStorageId = new TextEncoder().encode(storageResult.id);
15072
+ const newOwnerSecret = await deriveOwnerSecret(dek, rawNewStorageId);
15073
+ const storeRecovery = {
15074
+ storageId: storageResult.id,
15075
+ storageProvider: storageResult.provider,
15076
+ keyEnvelope,
15077
+ contentHash
15078
+ };
15079
+ options?.onProgress?.({
15080
+ phase: "stored",
15081
+ chunksUploaded: 1,
15082
+ totalChunks: 1,
15083
+ bytesUploaded: bytes.length,
15084
+ totalBytes: bytes.length,
15085
+ recovery: storeRecovery
15086
+ });
15087
+ try {
15088
+ await this.config.chainAdapter.updateReference(
15089
+ refId,
15090
+ {
15091
+ storageId: onChainStorageId,
15092
+ encryptionScheme: "xchacha20poly1305-v1",
15093
+ storageProvider: storageResult.provider,
15094
+ ...encryptedMetadata !== void 0 ? { encryptedMetadata } : {},
15095
+ writtenAt: Date.now(),
15096
+ keyEnvelope,
15097
+ contentHash
15098
+ },
15099
+ ownerSecret,
15100
+ newOwnerSecret
15101
+ );
15102
+ } catch {
15103
+ throw new StorePartialError(storeRecovery);
15104
+ }
15105
+ return { chainRefId: refId, storageId: storageResult.id };
15106
+ } finally {
15107
+ dek.fill(0);
15108
+ }
15109
+ } catch (err) {
15110
+ wrapError(err);
15322
15111
  }
15323
- throw Error(`Unsupported network: ${instruction.network}`);
15324
15112
  }
15325
- // ── Network-specific wallet provider factories ────────────────────────────────
15326
15113
  /**
15327
- * Returns a WalletProvider-compatible object for the Midnight chain whose
15328
- * `balanceTx()` is fulfilled by the managed signing server instead of a
15329
- * local wallet. The caller supplies the public keys (from whichever source
15330
- * they have — HD derivation, a server endpoint, etc.); the SDK only handles
15331
- * the signing-server round-trip.
15114
+ * Retry only the on-chain reference write after a failed `update()` call.
15332
15115
  *
15333
- * The returned object satisfies the @midnight-ntwrk/midnight-js-types
15334
- * WalletProvider interface via structural typing no Midnight package
15335
- * import is required from the caller's side.
15116
+ * Use this when `update()` throws a `StorePartialError` — meaning the new
15117
+ * content was stored successfully but the chain write failed. Call with the
15118
+ * `refId` you passed to `update()` and the `err.recovery` from the caught
15119
+ * error (or the `recovery` emitted via `onProgress` at phase `"stored"`).
15336
15120
  *
15337
- * @param coinPublicKey Midnight coin public key (hex) for the ZK fee circuit.
15338
- * @param encryptionPublicKey Midnight encryption public key (hex).
15339
- * @param onReceipt Optional callback invoked with the PaymentReceipt
15340
- * after each successful balanceTx call. Used internally
15341
- * by MidnightChainAdapter to capture receipt metadata;
15342
- * external callers can omit it.
15121
+ * The new content is already in storage; this method re-derives all chain
15122
+ * write parameters from the recovery envelope without re-uploading.
15343
15123
  */
15344
- createMidnightWalletProvider(coinPublicKey, encryptionPublicKey, onReceipt) {
15345
- return {
15346
- getCoinPublicKey: () => coinPublicKey,
15347
- getEncryptionPublicKey: () => encryptionPublicKey,
15348
- balanceTx: async (tx, _ttl) => {
15349
- const txData = uint8ArrayToHex(tx.serialize());
15350
- const receipt = await this.pay({
15351
- network: "midnight",
15352
- txData,
15353
- dataSizeBytes: txData.length,
15354
- expiresAt: Date.now() + 6e4
15355
- });
15356
- onReceipt?.(receipt);
15357
- if (!receipt.signedTx) {
15358
- throw new DStorageError(
15359
- PaymentErrorCode.MANAGED_UNBALANCED_TX,
15360
- "[dStorage/payment] Midnight managed payment server did not return a balanced transaction."
15361
- );
15362
- }
15363
- const { Transaction } = await import("@midnight-ntwrk/midnight-js-protocol/ledger");
15364
- return Transaction.deserialize(
15365
- "signature",
15366
- "proof",
15367
- "binding",
15368
- parseSignedTxBytes(receipt.signedTx)
15124
+ async retryUpdate(refId, recovery) {
15125
+ try {
15126
+ this.assertConnected();
15127
+ if (!this.config.chainAdapter) {
15128
+ throw new DStorageError(
15129
+ CoreErrorCode.UPDATE_UPLOAD_REQUIRES_CHAIN,
15130
+ "[dStorage] retryUpdate requires a chain adapter."
15369
15131
  );
15370
15132
  }
15371
- };
15133
+ if (typeof this.config.chainAdapter.updateReference !== "function") {
15134
+ throw new DStorageError(
15135
+ CoreErrorCode.UPDATE_UPLOAD_NOT_SUPPORTED,
15136
+ "[dStorage] retryUpdate is not supported by this chain adapter."
15137
+ );
15138
+ }
15139
+ if (this._encryptionProviders.length === 0) {
15140
+ throw new DStorageError(
15141
+ CoreErrorCode.UPDATE_UPLOAD_REQUIRES_ENCRYPTION,
15142
+ "[dStorage] retryUpdate requires at least one encryption adapter."
15143
+ );
15144
+ }
15145
+ if (!recovery.keyEnvelope) {
15146
+ throw new DStorageError(
15147
+ CoreErrorCode.UNWRAP_DEK_ENVELOPE_EMPTY,
15148
+ "[dStorage] retryUpdate requires a key envelope in the recovery object."
15149
+ );
15150
+ }
15151
+ const dek = await this._unwrapDek(recovery.keyEnvelope);
15152
+ try {
15153
+ const scheme = XChaChaScheme.fromKey(dek);
15154
+ const ownerSecret = await this._computeOwnerSecret(refId);
15155
+ const onChainStorageId = await scheme.encryptStorageId(
15156
+ recovery.storageId
15157
+ );
15158
+ const newOwnerSecret = await deriveOwnerSecret(
15159
+ dek,
15160
+ new TextEncoder().encode(recovery.storageId)
15161
+ );
15162
+ await this.config.chainAdapter.updateReference(
15163
+ refId,
15164
+ {
15165
+ storageId: onChainStorageId,
15166
+ encryptionScheme: "xchacha20poly1305-v1",
15167
+ storageProvider: recovery.storageProvider,
15168
+ writtenAt: Date.now(),
15169
+ keyEnvelope: recovery.keyEnvelope,
15170
+ ...recovery.contentHash !== void 0 ? { contentHash: recovery.contentHash } : {}
15171
+ },
15172
+ ownerSecret,
15173
+ newOwnerSecret
15174
+ );
15175
+ return { chainRefId: refId, storageId: recovery.storageId };
15176
+ } finally {
15177
+ dek.fill(0);
15178
+ }
15179
+ } catch (err) {
15180
+ wrapError(err);
15181
+ }
15372
15182
  }
15373
- // ── Network-specific payment handlers ────────────────────────────────────────
15374
15183
  /**
15375
- * Arweave remote-signing flow:
15376
- * 1. Creates the Arweave transaction locally (data + tags, no local signing).
15377
- * 2. Calls prepareChunks() to compute data_root (Merkle root) locally.
15378
- * 3. Strips the raw data from the transaction before serialising — only the
15379
- * data_root and data_size (not the actual bytes) are sent to the server.
15380
- * 4. POSTs the stripped transaction JSON to the signing server.
15381
- * 5. Applies the returned id, signature, and owner to the transaction.
15382
- * 6. Restores the raw data so the chunked uploader can post it directly to
15383
- * the Arweave gateway — the server never sees the file content.
15184
+ * Rotate the encryption adapters protecting an existing reference without
15185
+ * re-uploading the content.
15384
15186
  *
15385
- * Falls back to the base-class pass-through if provideUploadData() was not called.
15187
+ * Useful when you want to:
15188
+ * - Change a password: configure [newPassword, mnemonic] and call rotateKeys().
15189
+ * The mnemonic unwraps the old DEK, the new envelope is wrapped under both
15190
+ * new adapters, and the old password wrapper is dropped.
15191
+ * - Add or remove adapters: any single adapter that matches a wrapper in the
15192
+ * existing key envelope can authorise the rotation. The resulting envelope
15193
+ * is wrapped under exactly the adapters currently configured on this SDK.
15194
+ *
15195
+ * The content in storage is not touched — only the on-chain key envelope is
15196
+ * updated. writtenAt is preserved to reflect the original upload time.
15386
15197
  */
15387
- async _payArweave(instruction, onStatus) {
15388
- if (!this.pendingUpload) {
15389
- return super.pay(instruction, onStatus);
15390
- }
15391
- const { data, metadata } = this.pendingUpload;
15392
- this.pendingUpload = null;
15393
- const arweave = await this.getClient();
15394
- onStatus?.("Creating Arweave transaction\u2026");
15395
- const tx = await arweave.createTransaction({ data });
15396
- if (this.ownerPublicKey) {
15397
- tx.owner = this.ownerPublicKey;
15398
- }
15399
- for (const [key, value] of Object.entries(metadata)) {
15400
- tx.addTag(key, value);
15401
- }
15402
- await tx.prepareChunks(tx.data);
15403
- const rawData = tx.data;
15404
- tx.data = new Uint8Array(0);
15405
- const body = JSON.stringify({
15406
- txData: tx.toJSON(),
15407
- network: instruction.network
15408
- });
15409
- const {
15410
- txId,
15411
- signature,
15412
- publicKey,
15413
- txAmount: arCost,
15414
- serviceFee
15415
- } = await this._sendSignTxRequest(body, instruction.network, onStatus);
15416
- assertOwnerKeyMatch(publicKey, this.ownerPublicKey);
15417
- onStatus?.("Applying remote signature\u2026");
15418
- tx.setSignature({ id: txId, signature, owner: publicKey });
15419
- tx.data = rawData;
15420
- this.signedUpload = { tx, arCost };
15421
- this.logger?.log(
15422
- "dStorage/managed-payment",
15423
- `Transaction signed remotely (${this.signingServerUrl} API service) \u2192 txId: ${txId} cost: ~${arCost} ${this.token} (service fee: ${serviceFee})`
15424
- );
15425
- return {
15426
- txHash: txId,
15427
- token: this.token,
15428
- amount: arCost,
15429
- type: this.type,
15430
- paidAt: Date.now(),
15431
- serviceFee
15432
- };
15433
- }
15434
- /**
15435
- * Midnight payment flow:
15436
- * POSTs the fee amount to the dStorage API service, which holds a funded
15437
- * Midnight account and submits the chain transaction on behalf of the user.
15438
- *
15439
- * POST {baseUrl}{POST_SIGN_TX_API_PATH}
15440
- * → { txData: string (hex-serialized UnboundTransaction), network: string, userId: string }
15441
- * ← { txId: string, signature: string (hex-serialized FinalizedTransaction), ... }
15442
- *
15443
- * The hex-serialized FinalizedTransaction is returned in PaymentReceipt.signedTx
15444
- * for the chain adapter to deserialize and pass to submitTx().
15445
- */
15446
- async _payMidnight(instruction, onStatus) {
15447
- const body = JSON.stringify({
15448
- txData: instruction.txData ?? null,
15449
- network: instruction.network
15450
- });
15451
- const {
15452
- txId,
15453
- signature,
15454
- txAmount: dustCost,
15455
- serviceFee
15456
- } = await this._sendSignTxRequest(body, instruction.network, onStatus);
15457
- this.logger?.log(
15458
- "dStorage/managed-payment",
15459
- `Midnight Tx signed remotely (${this.signingServerUrl} API service) \u2192 txId: ${txId} cost: ~${dustCost} ${this.token} (service fee: ${serviceFee})`
15460
- );
15461
- return {
15462
- txHash: txId,
15463
- token: "DUST",
15464
- amount: dustCost,
15465
- type: this.type,
15466
- signedTx: signature,
15467
- paidAt: Date.now(),
15468
- serviceFee
15469
- };
15470
- }
15471
- /**
15472
- * Arweave Bundler
15473
- */
15474
- async _payArweaveBundler(instruction, onStatus) {
15475
- const body = JSON.stringify({
15476
- txData: instruction.txData ?? null,
15477
- network: instruction.network,
15478
- ...instruction.ownerKey !== void 0 ? { ownerKey: instruction.ownerKey } : {}
15479
- });
15480
- const {
15481
- txId,
15482
- signature,
15483
- txAmount: arCost,
15484
- serviceFee
15485
- } = await this._sendSignTxRequest(body, instruction.network, onStatus);
15486
- this.logger?.log(
15487
- "dStorage/managed-payment",
15488
- `Arweave bundler Tx signed remotely (${this.signingServerUrl} API service) \u2192 txId: ${txId} cost: ~${arCost} ${this.token} (service fee: ${serviceFee})`
15489
- );
15490
- return {
15491
- txHash: txId,
15492
- token: "AR",
15493
- amount: arCost,
15494
- type: this.type,
15495
- signedTx: signature,
15496
- paidAt: Date.now(),
15497
- serviceFee
15498
- };
15499
- }
15500
- /**
15501
- * Managed mock flow:
15502
- * Sends a minimal request to the signing server with the literal network
15503
- * identifier "TEST" — the server treats this as a sandbox/test flow with no
15504
- * real funds involved. The SDK-side network name is "managedmock" to follow
15505
- * the lowercase naming convention; the server-side string is always "TEST".
15506
- */
15507
- async _payManagedMock(instruction, onStatus) {
15508
- const body = JSON.stringify({
15509
- txData: instruction.txData || "mock",
15510
- network: "TEST"
15511
- });
15512
- const { txId, txAmount, serviceFee } = await this._sendSignTxRequest(
15513
- body,
15514
- "TEST",
15515
- onStatus
15516
- );
15517
- this.logger?.log(
15518
- "dStorage/managed-payment",
15519
- `ManagedMock Tx signed remotely (${this.signingServerUrl}) \u2192 txId: ${txId} cost: ~${txAmount} MOCK (service fee: ${serviceFee})`
15520
- );
15521
- return {
15522
- txHash: txId,
15523
- token: "MOCK",
15524
- amount: txAmount,
15525
- type: this.type,
15526
- paidAt: Date.now(),
15527
- serviceFee
15528
- };
15198
+ async rotateKeys(refId) {
15199
+ try {
15200
+ this.assertConnected();
15201
+ if (!this.config.chainAdapter) {
15202
+ throw new DStorageError(
15203
+ CoreErrorCode.ROTATE_KEYS_REQUIRES_CHAIN,
15204
+ "[dStorage] rotateKeys requires a chain adapter."
15205
+ );
15206
+ }
15207
+ if (typeof this.config.chainAdapter.updateReference !== "function") {
15208
+ throw new DStorageError(
15209
+ CoreErrorCode.ROTATE_KEYS_NOT_SUPPORTED,
15210
+ "[dStorage] rotateKeys is not supported by this chain adapter."
15211
+ );
15212
+ }
15213
+ if (this._encryptionProviders.length === 0) {
15214
+ throw new DStorageError(
15215
+ CoreErrorCode.ROTATE_KEYS_REQUIRES_ENCRYPTION,
15216
+ "[dStorage] rotateKeys requires at least one encryption adapter."
15217
+ );
15218
+ }
15219
+ const ref = await this.config.chainAdapter.readReference(refId);
15220
+ const ownerSecret = await this._computeOwnerSecret(refId);
15221
+ const seed = await this._unwrapDek(ref.keyEnvelope);
15222
+ try {
15223
+ const wrapperEntries = await Promise.all(
15224
+ this._encryptionProviders.map(async (p) => ({
15225
+ wrapKeyName: p.name,
15226
+ ...await p.wrapDek(seed)
15227
+ }))
15228
+ );
15229
+ const newKeyEnvelope = serializeKeyEnvelope(
15230
+ buildKeyEnvelope(wrapperEntries)
15231
+ );
15232
+ await this.config.chainAdapter.updateReference(
15233
+ refId,
15234
+ {
15235
+ storageId: ref.storageId ?? "",
15236
+ encryptionScheme: ref.encryptionScheme ?? "",
15237
+ storageProvider: ref.storageProvider,
15238
+ writtenAt: ref.writtenAt,
15239
+ keyEnvelope: newKeyEnvelope,
15240
+ ...ref.encryptedMetadata !== void 0 ? { encryptedMetadata: ref.encryptedMetadata } : {},
15241
+ ...ref.contentHash !== void 0 ? { contentHash: ref.contentHash } : {}
15242
+ },
15243
+ ownerSecret,
15244
+ ownerSecret
15245
+ );
15246
+ } finally {
15247
+ seed.fill(0);
15248
+ }
15249
+ } catch (err) {
15250
+ wrapError(err);
15251
+ }
15529
15252
  }
15530
15253
  /**
15531
- * Shared HTTP helper: POSTs a transaction payload to the signing server and
15532
- * returns the parsed SignTxResponse.
15254
+ * Retrieve and decrypt data by its storageId.
15255
+ * Decryption happens locally — the SDK never sends plaintext anywhere.
15533
15256
  *
15534
- * - Arweave: txJson is the Arweave tx JSON object (from tx.toJSON()); the server
15535
- * signs it and returns the signature + owner in the response fields.
15536
- * - Midnight: txJson is the hex-serialized UnboundTransaction; the server balances
15537
- * and signs it, returning the hex-serialized FinalizedTransaction in the
15538
- * `signature` response field.
15257
+ * Automatically detects chunked manifests and reassembles the original file.
15539
15258
  *
15540
- * Throws on network errors or non-2xx HTTP responses.
15259
+ * @param storageId The storageId returned from store()
15541
15260
  */
15542
- async _sendSignTxRequest(body, network, onStatus) {
15543
- onStatus?.(`Sending ${network} transaction to signing server\u2026`);
15544
- let signResp;
15261
+ async retrieveByStorageId(storageId, dekScheme, contentHash) {
15545
15262
  try {
15546
- this.logger?.log(
15547
- "dStorage/managed-payment",
15548
- `${network}: delegating to managed payment service (serialized req length=${body.length})`
15549
- );
15550
- const signal = this.requestTimeoutMs > 0 ? AbortSignal.timeout(this.requestTimeoutMs) : void 0;
15551
- signResp = await fetch(
15552
- `${this.signingServerUrl}${POST_SIGN_TX_API_PATH}`,
15553
- {
15554
- method: "POST",
15555
- headers: {
15556
- "Content-Type": "application/json",
15557
- Authorization: `Bearer ${this.authToken}`
15558
- },
15559
- body,
15560
- ...signal !== void 0 ? { signal } : {}
15263
+ this.assertConnected();
15264
+ const { bytes: rawBytes, tags } = await this.config.storageAdapter.retrieve(storageId);
15265
+ if (contentHash) {
15266
+ const computed = await computeBlake3Hex(rawBytes);
15267
+ if (computed !== contentHash) {
15268
+ throw new DStorageError(
15269
+ CoreErrorCode.CONTENT_HASH_MISMATCH,
15270
+ `[dStorage] Content hash mismatch for storageId ${storageId}. Expected ${contentHash}, got ${computed}. The retrieved bytes do not match the hash committed on-chain \u2014 the storage content may have been tampered with.`
15271
+ );
15561
15272
  }
15562
- );
15273
+ }
15274
+ return this._decryptAndReassemble(rawBytes, storageId, tags, dekScheme);
15563
15275
  } catch (err) {
15276
+ wrapError(err);
15277
+ }
15278
+ }
15279
+ /**
15280
+ * Unwrap the data DEK from a key envelope and return an XChaChaScheme built from it.
15281
+ * Throws if the KEK is not available or the envelope cannot be parsed.
15282
+ * Only valid for private uploads (envelope.wrappers must be present).
15283
+ */
15284
+ async _getDekScheme(keyEnvelope) {
15285
+ if (this._encryptionProviders.length === 0) {
15564
15286
  throw new DStorageError(
15565
- PaymentErrorCode.MANAGED_SERVER_UNREACHABLE,
15566
- `[dStorage/managed-payment] Could not reach signing server: ${err}`,
15567
- { cause: err }
15287
+ CoreErrorCode.DECRYPT_NO_PROVIDERS,
15288
+ "[dStorage] Cannot decrypt: no encryption providers. Call init() before retrieving private data."
15568
15289
  );
15569
15290
  }
15570
- if (signResp.status === 409) {
15291
+ if (!keyEnvelope) {
15571
15292
  throw new DStorageError(
15572
- PaymentErrorCode.MANAGED_KEY_CHANGED,
15573
- "[dStorage/managed-payment] Signing server key has changed \u2014 re-issue your API token to obtain the updated public key."
15293
+ CoreErrorCode.DECRYPT_ENVELOPE_EMPTY,
15294
+ "[dStorage] Cannot decrypt: key envelope is empty.",
15295
+ { cause: { code: "envelope_empty" } }
15574
15296
  );
15575
15297
  }
15576
- if (!signResp.ok) {
15577
- const rawBody = await signResp.text().catch(() => "");
15578
- const body2 = sanitiseErrorBody(rawBody);
15298
+ const envelope = parseKeyEnvelope(keyEnvelope);
15299
+ if (!envelope) {
15579
15300
  throw new DStorageError(
15580
- PaymentErrorCode.MANAGED_SIGN_HTTP_ERROR,
15581
- `[dStorage/managed-payment] POST ${POST_SIGN_TX_API_PATH} failed: HTTP ${signResp.status}${body2 ? ` \u2014 ${body2}` : ""}`
15301
+ CoreErrorCode.DECRYPT_ENVELOPE_MALFORMED,
15302
+ "[dStorage] Cannot decrypt: key envelope is malformed or has an invalid structure. The stored reference may be corrupted or tampered with.",
15303
+ { cause: { code: "envelope_malformed" } }
15582
15304
  );
15583
15305
  }
15584
- let parsed;
15585
- try {
15586
- parsed = await signResp.json();
15587
- } catch {
15588
- throw new DStorageError(
15589
- PaymentErrorCode.MANAGED_NON_JSON_RESPONSE,
15590
- `[dStorage/managed-payment] Managed payment service returned a non-JSON response \u2014 check that the service URL is correct and reachable: ${this.signingServerUrl}${POST_SIGN_TX_API_PATH}`
15306
+ for (const wrapper of envelope.wrappers) {
15307
+ const provider = this._encryptionProviders.find(
15308
+ (p) => p.name === wrapper.wrapKeyName
15591
15309
  );
15310
+ if (!provider) continue;
15311
+ try {
15312
+ const dek = await provider.unwrapDek(wrapper);
15313
+ return XChaChaScheme.fromKey(dek);
15314
+ } catch {
15315
+ }
15592
15316
  }
15593
- return parseSignTxResponse(parsed);
15594
- }
15595
- };
15596
-
15597
- // src/chunked.ts
15598
- var CHUNK_SIZE = 10 * 1024 * 1024;
15599
- var MAX_CHUNK_COUNT = 1e4;
15600
- var MAX_REASSEMBLY_BYTES = MAX_CHUNK_COUNT * CHUNK_SIZE;
15601
- function isManifest(payload) {
15602
- if (typeof payload !== "object" || payload === null) return false;
15603
- const m = payload;
15604
- if (m["type"] !== "dstorage-chunked-manifest") return false;
15605
- return Number.isInteger(m["totalSize"]) && m["totalSize"] >= 0 && m["totalSize"] <= MAX_REASSEMBLY_BYTES && Number.isInteger(m["chunkSize"]) && m["chunkSize"] > 0 && Number.isInteger(m["chunkCount"]) && m["chunkCount"] >= 0 && m["chunkCount"] <= MAX_CHUNK_COUNT;
15606
- }
15607
-
15608
- // src/dStorage.ts
15609
- var TAG_SDK_VERSION = "dStorage-Version";
15610
- var TAG_CONTENT_TYPE = "Content-Type";
15611
- var TAG_CONTENT_TYPE_OCTET_STREAM = "application/octet-stream";
15612
- var TAG_CONTENT_TYPE_JSON = "application/json";
15613
- var TAG_CHUNK_INDEX = "dStorage-Chunk-Index";
15614
- var TAG_CHUNK_TOTAL = "dStorage-Chunk-Total";
15615
- var TAG_MANIFEST_UPLOAD = "dStorage-Manifest-Upload";
15616
- function wrapError(err) {
15617
- if (err instanceof DStorageError || isDStorageError(err)) throw err;
15618
- throw new DStorageError(
15619
- CoreErrorCode.UNKNOWN_ERROR,
15620
- `[dStorage] Unexpected error: ${err instanceof Error ? err.message : String(err)}`,
15621
- { cause: err }
15622
- );
15623
- }
15624
- var DStorage = class {
15625
- constructor(config) {
15626
- this.wallet = null;
15627
- /** Encryption adapters — each wraps/unwraps per-upload DEKs using its own algorithm. */
15628
- this._encryptionProviders = [];
15629
- this._initialized = false;
15630
- this.config = config;
15631
- this.logger = config.logger;
15632
- }
15633
- // ── DStorageContext interface — public accessors ───────────────────────────
15634
- get storageAdapter() {
15635
- return this.config.storageAdapter;
15317
+ throw new DStorageError(
15318
+ CoreErrorCode.DECRYPT_NO_WRAPPER_MATCHED,
15319
+ "[dStorage] Cannot decrypt: this reference was not encrypted with any of your configured keys. Check that you are using the correct encryption adapter, password, seed phrase, or keypair.",
15320
+ { cause: { code: "no_wrapper_matched" } }
15321
+ );
15636
15322
  }
15637
- get chainAdapter() {
15638
- return this.config.chainAdapter;
15323
+ /**
15324
+ * Unwrap the raw DEK bytes from a key envelope using the available KEKs.
15325
+ * The ownerSecret for the reference can be re-derived as HKDF(dek, rawStorageId).
15326
+ * Throws if no wrapper matches any available KEK.
15327
+ */
15328
+ async _unwrapDek(keyEnvelope) {
15329
+ if (this._encryptionProviders.length === 0) {
15330
+ throw new DStorageError(
15331
+ CoreErrorCode.UNWRAP_DEK_NO_PROVIDERS,
15332
+ "[dStorage] Cannot unwrap DEK: no encryption providers. Call init() first."
15333
+ );
15334
+ }
15335
+ if (!keyEnvelope) {
15336
+ throw new DStorageError(
15337
+ CoreErrorCode.UNWRAP_DEK_ENVELOPE_EMPTY,
15338
+ "[dStorage] Cannot unwrap DEK: key envelope is empty.",
15339
+ { cause: { code: "envelope_empty" } }
15340
+ );
15341
+ }
15342
+ const envelope = parseKeyEnvelope(keyEnvelope);
15343
+ if (!envelope) {
15344
+ throw new DStorageError(
15345
+ CoreErrorCode.UNWRAP_DEK_ENVELOPE_MALFORMED,
15346
+ "[dStorage] Cannot unwrap DEK: key envelope is malformed or has an invalid structure. The stored reference may be corrupted or tampered with.",
15347
+ { cause: { code: "envelope_malformed" } }
15348
+ );
15349
+ }
15350
+ for (const wrapper of envelope.wrappers) {
15351
+ const provider = this._encryptionProviders.find(
15352
+ (p) => p.name === wrapper.wrapKeyName
15353
+ );
15354
+ if (!provider) continue;
15355
+ try {
15356
+ return await provider.unwrapDek(wrapper);
15357
+ } catch {
15358
+ }
15359
+ }
15360
+ throw new DStorageError(
15361
+ CoreErrorCode.UNWRAP_DEK_NO_WRAPPER_MATCHED,
15362
+ "[dStorage] Cannot unwrap DEK: this reference was not encrypted with any of your configured keys. Check that you are using the correct encryption adapter, password, seed phrase, or keypair.",
15363
+ { cause: { code: "no_wrapper_matched" } }
15364
+ );
15639
15365
  }
15640
- // ── Lifecycle ─────────────────────────────────────────────────────────────
15641
15366
  /**
15642
- * Initialize the SDK:
15643
- * 1. Initialize the chain adapter wallet (if supported), and
15644
- * deploy a new DataRegistry contract, or connect to an existing one.
15645
- * 2. Derive an encryption key from the chain adapter's credentials (if supported).
15367
+ * Compute the ownerSecret for an existing on-chain reference.
15368
+ * Used by removeReference/updateReference to derive the ZK witness secret.
15646
15369
  *
15647
- * Returns the address of the DataRegistry contract in use.
15648
- * Call this instead of connect() when using MidnightChainAdapter directly.
15370
+ * Flow:
15371
+ * 1. Read the reference from chain to get keyEnvelope + storageId
15372
+ * 2. Unwrap the DEK from keyEnvelope using available KEKs
15373
+ * 3. Recover raw storageId (decrypt with DEK if private, plaintext if public)
15374
+ * 4. Return HKDF-SHA256(dek, rawStorageId) for private, or HKDF-SHA256(ownerSeed, rawStorageId) for public
15649
15375
  */
15650
- async init() {
15376
+ async _computeOwnerSecret(refId) {
15377
+ const ref = await this.config.chainAdapter.readReference(refId);
15378
+ const isPublic = !ref.encryptionScheme;
15379
+ if (isPublic) {
15380
+ const ownerSeed = await this._unwrapDek(ref.keyEnvelope);
15381
+ return deriveOwnerSecret(
15382
+ ownerSeed,
15383
+ new TextEncoder().encode(ref.storageId),
15384
+ "dstorage:owner-secret:public:v1"
15385
+ );
15386
+ }
15387
+ const dek = await this._unwrapDek(ref.keyEnvelope);
15388
+ const dekScheme = XChaChaScheme.fromKey(dek);
15389
+ const rawStorageIdBytes = ref.storageId ? new TextEncoder().encode(
15390
+ await dekScheme.decryptStorageId(ref.storageId)
15391
+ ) : METADATA_ONLY_OWNER_SECRET_SALT;
15392
+ return deriveOwnerSecret(dek, rawStorageIdBytes);
15393
+ }
15394
+ // Accepts an optional pre-built CryptoScheme (unwrapped from a key envelope).
15395
+ // If no dekScheme is provided, content is treated as public (no decryption).
15396
+ async _decryptAndReassemble(rawBytes, storageId, tags, dekScheme) {
15397
+ const scheme = dekScheme ?? null;
15398
+ if (scheme) {
15399
+ let manifestBytes = null;
15400
+ try {
15401
+ manifestBytes = await scheme.decryptPayload(rawBytes, MANIFEST_AAD);
15402
+ } catch {
15403
+ }
15404
+ if (manifestBytes !== null) {
15405
+ const parsed = JSON.parse(
15406
+ new TextDecoder().decode(manifestBytes)
15407
+ );
15408
+ if (!isManifest(parsed)) {
15409
+ throw new DStorageError(
15410
+ CoreErrorCode.MANIFEST_INVALID_STRUCTURE,
15411
+ "[dStorage] Payload authenticated as manifest but has invalid structure"
15412
+ );
15413
+ }
15414
+ this.logger?.log(
15415
+ "dStorage",
15416
+ `Manifest detected: reassembling ${parsed.chunkCount} chunks`
15417
+ );
15418
+ return this._retrieveChunked(parsed, scheme, tags);
15419
+ }
15420
+ let decryptedBytes;
15421
+ try {
15422
+ decryptedBytes = await scheme.decryptPayload(rawBytes, PAYLOAD_AAD);
15423
+ } catch (err) {
15424
+ throw new DStorageError(
15425
+ CoreErrorCode.DECRYPT_PAYLOAD_FAILED,
15426
+ `[dStorage] Failed to decrypt payload: ${err}`,
15427
+ { cause: err }
15428
+ );
15429
+ }
15430
+ this.logger?.log(
15431
+ "dStorage",
15432
+ `Retrieved by refId \u2192 storageId: ${storageId}`
15433
+ );
15434
+ return { bytes: decryptedBytes, metadata: {}, tags };
15435
+ }
15651
15436
  try {
15652
- if (this.config.chainAdapter?.init) {
15653
- await this.config.chainAdapter.init();
15437
+ const parsed = JSON.parse(new TextDecoder().decode(rawBytes));
15438
+ if (isManifest(parsed)) {
15439
+ this.logger?.log(
15440
+ "dStorage",
15441
+ `Manifest detected: reassembling ${parsed.chunkCount} chunks`
15442
+ );
15443
+ return this._retrieveChunked(parsed, null, tags);
15654
15444
  }
15655
- this._encryptionProviders = this.config.encryptionAdapters ?? [];
15656
- this._initialized = true;
15657
- return this.config.chainAdapter?.getContractAddress();
15658
- } catch (err) {
15659
- wrapError(err);
15445
+ } catch {
15660
15446
  }
15661
- }
15662
- /** Tear down active state and clear all in-memory key material. */
15663
- destroy() {
15664
- this.wallet = null;
15665
- this._encryptionProviders = [];
15666
- this._initialized = false;
15667
15447
  this.logger?.log(
15668
15448
  "dStorage",
15669
- "Destroyed \u2014 encryption key cleared from memory."
15449
+ `Retrieved by refId \u2192 storageId: ${storageId}`
15670
15450
  );
15451
+ return {
15452
+ bytes: rawBytes,
15453
+ metadata: {},
15454
+ tags
15455
+ };
15671
15456
  }
15672
- get isConnected() {
15673
- return this._initialized || this.wallet !== null;
15674
- }
15675
- get connectedAddress() {
15676
- return this.wallet?.address ?? null;
15677
- }
15678
- // ── Core operations ───────────────────────────────────────────────────────
15679
15457
  /**
15680
- * Encrypt content client-side, store to decentralised storage,
15681
- * and write a cryptographic reference on-chain.
15458
+ * Retrieve and decrypt data using only its on-chain reference ID.
15682
15459
  *
15683
- * Files larger than CHUNK_SIZE (10 MB) are automatically split into chunks,
15684
- * each encrypted and stored independently. A manifest file ties them together.
15685
- * Only the manifest's storageId is written on-chain.
15460
+ * Flow:
15461
+ * 1. Look up the reference on-chain by refId to obtain the storage network ID.
15462
+ * 2. Fetch the blob from the storage network.
15463
+ * 3. Decrypt locally if an encryption key is available.
15686
15464
  *
15687
- * @param bytes Uint8Array to store
15688
- * @param options Optional StoreOptions (metadata, onProgress, isPublic, refId)
15689
- * @returns StoreResult containing the chainRefId/storageId needed to retrieve later
15465
+ * Requires the chain adapter to implement readReference() (part of the
15466
+ * ChainAdapter interface shared by MidnightChainAdapter, MockChainAdapter, etc.).
15467
+ *
15468
+ * @param refId The chainRefId returned from store()
15690
15469
  */
15691
- async store(bytes, options) {
15470
+ async retrieveByRefId(refId) {
15692
15471
  try {
15693
15472
  this.assertConnected();
15694
- const {
15695
- tags = {},
15696
- metadata: rawMeta,
15697
- onProgress,
15698
- isPublic = false,
15699
- refId
15700
- } = options ?? {};
15701
- if (rawMeta !== void 0 && isPublic) {
15473
+ if (!this.config.chainAdapter) {
15702
15474
  throw new DStorageError(
15703
- CoreErrorCode.METADATA_WITH_PUBLIC_UPLOAD,
15704
- "[dStorage] metadata cannot be used with isPublic uploads \u2014 there is no DEK to encrypt it with."
15475
+ CoreErrorCode.RETRIEVE_BY_REF_ID_REQUIRES_CHAIN,
15476
+ "[dStorage] retrieveByRefId() requires a chain adapter. Configure one in DStorageConfig or use retrieveByStorageId() instead."
15705
15477
  );
15706
15478
  }
15707
- if (bytes.length > CHUNK_SIZE) {
15708
- return this._uploadChunked(
15709
- bytes,
15710
- tags,
15711
- onProgress,
15712
- isPublic,
15713
- rawMeta,
15714
- refId
15479
+ const ref = await this.config.chainAdapter.readReference(refId);
15480
+ if (!ref)
15481
+ throw new DStorageError(
15482
+ CoreErrorCode.REF_NOT_FOUND,
15483
+ `[dStorage] No on-chain reference found for refId: ${refId}`
15484
+ );
15485
+ if (ref.storageProvider && ref.storageProvider !== this.config.storageAdapter.name) {
15486
+ throw new DStorageError(
15487
+ CoreErrorCode.STORAGE_ADAPTER_MISMATCH,
15488
+ `[dStorage] Storage adapter mismatch: reference "${refId}" was stored on "${ref.storageProvider}" but the configured adapter is "${this.config.storageAdapter.name}". Configure a "${ref.storageProvider}" storage adapter to retrieve this reference.`
15715
15489
  );
15716
15490
  }
15717
- let uploadData;
15718
- let onChainStorageId;
15719
- const encryptionScheme = isPublic ? "" : "xchacha20poly1305-v1";
15720
- let uploadScheme = null;
15721
- let keyEnvelope = "";
15722
- let dek = null;
15723
- let ownerSecret = new Uint8Array(0);
15724
- if (isPublic) {
15725
- uploadData = bytes;
15491
+ const isPublic = !ref.encryptionScheme;
15492
+ let storageId;
15493
+ let dekScheme;
15494
+ if (!ref.storageId) {
15495
+ if (!isPublic && ref.keyEnvelope) {
15496
+ dekScheme = await this._getDekScheme(ref.keyEnvelope);
15497
+ }
15498
+ } else if (isPublic) {
15499
+ storageId = ref.storageId;
15726
15500
  } else {
15727
- if (this._encryptionProviders.length === 0) {
15501
+ if (!ref.keyEnvelope) {
15728
15502
  throw new DStorageError(
15729
- CoreErrorCode.UPLOAD_NO_PROVIDERS,
15730
- "[dStorage] No encryption providers available. Call init() before uploading."
15503
+ CoreErrorCode.RETRIEVE_BY_REF_ID_ENVELOPE_EMPTY,
15504
+ `[dStorage] Cannot decrypt refId ${refId}: key envelope is empty.`,
15505
+ { cause: { code: "envelope_empty" } }
15731
15506
  );
15732
15507
  }
15733
- dek = generateDek();
15734
- uploadScheme = XChaChaScheme.fromKey(dek);
15735
- this.logger?.log(
15736
- "dStorage",
15737
- `Encrypting content using scheme: ${encryptionScheme}`
15738
- );
15739
- uploadData = await uploadScheme.encryptPayload(bytes, PAYLOAD_AAD);
15740
- const wrapperEntries = await Promise.all(
15741
- this._encryptionProviders.map(async (provider) => ({
15742
- wrapKeyName: provider.name,
15743
- ...await provider.wrapDek(dek)
15744
- }))
15745
- );
15746
- keyEnvelope = serializeKeyEnvelope(buildKeyEnvelope(wrapperEntries));
15508
+ dekScheme = await this._getDekScheme(ref.keyEnvelope);
15509
+ storageId = await dekScheme.decryptStorageId(ref.storageId);
15747
15510
  }
15748
- const sdkVersion = package_default.version ?? "unknown";
15749
- const storageResult = await this.config.storageAdapter.store(uploadData, {
15750
- ...tags,
15751
- [TAG_SDK_VERSION]: sdkVersion,
15752
- [TAG_CONTENT_TYPE]: TAG_CONTENT_TYPE_OCTET_STREAM
15753
- });
15754
- if (isPublic) {
15755
- if (this.config.chainAdapter) {
15756
- if (this._encryptionProviders.length === 0) {
15757
- throw new DStorageError(
15758
- CoreErrorCode.PUBLIC_UPLOAD_REQUIRES_SEED_PROVIDER,
15759
- "[dStorage] Cannot store public data without at least one encryption seed provider. Without a provider there is no owner secret, so the reference could never be removed or updated."
15760
- );
15761
- }
15762
- const ownerSeed = generateDek();
15763
- const ownerSeedWrappers = await Promise.all(
15764
- this._encryptionProviders.map(async (provider) => ({
15765
- wrapKeyName: provider.name,
15766
- ...await provider.wrapDek(ownerSeed)
15767
- }))
15768
- );
15769
- keyEnvelope = serializeKeyEnvelope(
15770
- buildKeyEnvelope(ownerSeedWrappers)
15771
- );
15772
- ownerSecret = await deriveOwnerSecret(
15773
- ownerSeed,
15774
- new TextEncoder().encode(storageResult.id),
15775
- "dstorage:owner-secret:public:v1"
15776
- );
15777
- }
15778
- onChainStorageId = storageResult.id;
15779
- } else {
15780
- onChainStorageId = await uploadScheme.encryptStorageId(
15781
- storageResult.id
15782
- );
15783
- ownerSecret = await deriveOwnerSecret(
15784
- dek,
15785
- new TextEncoder().encode(storageResult.id)
15511
+ let contentBytes;
15512
+ let contentTags;
15513
+ if (storageId !== void 0) {
15514
+ const r = await this.retrieveByStorageId(
15515
+ storageId,
15516
+ dekScheme,
15517
+ ref.contentHash
15786
15518
  );
15519
+ contentBytes = r.bytes;
15520
+ contentTags = r.tags;
15521
+ } else {
15522
+ contentBytes = new Uint8Array(0);
15523
+ contentTags = {};
15787
15524
  }
15788
- const storageCost = {
15789
- amount: storageResult.cost?.amount ?? "unknown",
15790
- token: storageResult.cost?.token ?? "AR"
15791
- };
15792
- if (this.config.chainAdapter) {
15793
- const contentHash = await computeBlake3Hex(uploadData);
15794
- const storeRecovery = {
15795
- storageId: storageResult.id,
15796
- storageProvider: storageResult.provider,
15797
- keyEnvelope: isPublic ? void 0 : keyEnvelope,
15798
- contentHash
15799
- };
15800
- onProgress?.({
15801
- phase: "stored",
15802
- chunksUploaded: 1,
15803
- totalChunks: 1,
15804
- bytesUploaded: bytes.length,
15805
- totalBytes: bytes.length,
15806
- recovery: storeRecovery
15807
- });
15808
- const encryptedMetadata = rawMeta ? bytesToBase64url(
15809
- await uploadScheme.encryptPayload(
15810
- new TextEncoder().encode(JSON.stringify(rawMeta)),
15811
- METADATA_AAD
15812
- )
15813
- ) : void 0;
15814
- try {
15815
- const chainResult = await this.config.chainAdapter.writeReference({
15816
- storageId: onChainStorageId,
15817
- encryptionScheme,
15818
- storageProvider: storageResult.provider,
15819
- ...refId !== void 0 ? { refId } : {},
15820
- ...encryptedMetadata !== void 0 ? { encryptedMetadata } : {},
15821
- writtenAt: Date.now(),
15822
- keyEnvelope,
15823
- contentHash,
15824
- ownerSecret
15825
- });
15826
- const resultRefId = chainResult.refId;
15827
- this.logger?.log(
15828
- "dStorage",
15829
- `Upload complete \u2192 refId: ${resultRefId}`
15830
- );
15831
- return {
15832
- chainRefId: resultRefId,
15833
- storageId: storageResult.id,
15834
- storageProvider: storageResult.provider,
15835
- chainProvider: chainResult.provider,
15836
- uploadedAt: storageResult.uploadedAt,
15837
- encryptionScheme,
15838
- costEstimate: {
15839
- storageCost,
15840
- chainCost: {
15841
- amount: chainResult.paymentReceipt?.amount ?? "unknown",
15842
- token: chainResult.paymentReceipt?.token ?? "DUST"
15843
- },
15844
- fileSizeBytes: uploadData.length
15845
- }
15846
- };
15847
- } catch {
15848
- throw new StorePartialError(storeRecovery);
15849
- }
15525
+ let metadata = {};
15526
+ if (ref.encryptedMetadata && dekScheme) {
15527
+ const raw = await dekScheme.decryptPayload(
15528
+ base64urlToBytes(ref.encryptedMetadata),
15529
+ METADATA_AAD
15530
+ );
15531
+ metadata = JSON.parse(new TextDecoder().decode(raw));
15850
15532
  }
15851
- this.logger?.log(
15852
- "dStorage",
15853
- `Upload complete (storage-only) \u2192 storageId: ${storageResult.id}`
15854
- );
15855
- return {
15856
- storageId: storageResult.id,
15857
- storageProvider: storageResult.provider,
15858
- uploadedAt: storageResult.uploadedAt,
15859
- encryptionScheme,
15860
- ...uploadScheme ? { cryptoScheme: uploadScheme } : {},
15861
- costEstimate: { storageCost, fileSizeBytes: uploadData.length }
15862
- };
15533
+ return { bytes: contentBytes, tags: contentTags, metadata };
15863
15534
  } catch (err) {
15864
15535
  wrapError(err);
15865
15536
  }
15866
15537
  }
15867
15538
  /**
15868
- * Register a pre-existing storageId as a new on-chain reference.
15869
- *
15870
- * Use this when data has already been uploaded to the storage network by
15871
- * other means (e.g. a third-party tool or a separate pipeline). The content
15872
- * is not touched — only the chain reference is created.
15873
- *
15874
- * A fresh DEK is generated solely to encrypt the storageId and derive the
15875
- * ownerSecret. The resulting reference round-trips through retrieveByRefId()
15876
- * exactly like one created by store().
15877
- *
15878
- * Note: chunked manifests are not handled here. For large pre-existing
15879
- * uploads already split into chunks, register the manifest storageId.
15539
+ * List references currently available from the configured chain adapter.
15880
15540
  */
15881
- async registerReference(options = {}) {
15541
+ async listReferences(options) {
15882
15542
  try {
15883
- this.assertConnected();
15884
15543
  if (!this.config.chainAdapter) {
15885
15544
  throw new DStorageError(
15886
- CoreErrorCode.REGISTER_REF_REQUIRES_CHAIN,
15887
- "[dStorage] registerReference requires a chain adapter."
15545
+ CoreErrorCode.LIST_REFS_REQUIRES_CHAIN,
15546
+ "[dStorage] listReferences() requires a chain adapter."
15888
15547
  );
15889
15548
  }
15890
- if (this._encryptionProviders.length === 0) {
15891
- throw new DStorageError(
15892
- CoreErrorCode.REGISTER_REF_REQUIRES_ENCRYPTION,
15893
- "[dStorage] registerReference requires at least one encryption adapter."
15549
+ let rawItems;
15550
+ if (options?.refIds && options.refIds.length > 0) {
15551
+ rawItems = await Promise.all(
15552
+ options.refIds.map(async (refId) => {
15553
+ const ref = await this.config.chainAdapter.readReference(refId);
15554
+ return {
15555
+ refId,
15556
+ storageId: ref.storageId ?? null,
15557
+ encryptionScheme: ref.encryptionScheme ?? null,
15558
+ keyEnvelope: ref.keyEnvelope,
15559
+ storageProvider: ref.storageProvider,
15560
+ ownerCommitment: "",
15561
+ ...ref.encryptedMetadata !== void 0 ? { encryptedMetadata: ref.encryptedMetadata } : {}
15562
+ };
15563
+ })
15894
15564
  );
15895
- }
15896
- let dek;
15897
- const recoveryEnvelope = options.keyEnvelope;
15898
- if (recoveryEnvelope) {
15899
- dek = await this._unwrapDek(recoveryEnvelope);
15900
15565
  } else {
15901
- dek = generateDek();
15902
- }
15903
- try {
15904
- const scheme = XChaChaScheme.fromKey(dek);
15905
- const { metadata: rawMeta } = options;
15906
- const encryptedMetadata = rawMeta ? bytesToBase64url(
15907
- await scheme.encryptPayload(
15908
- new TextEncoder().encode(JSON.stringify(rawMeta)),
15909
- METADATA_AAD
15910
- )
15911
- ) : void 0;
15912
- const onChainStorageId = options.storageId !== void 0 ? await scheme.encryptStorageId(options.storageId) : void 0;
15913
- let keyEnvelope;
15914
- if (recoveryEnvelope !== void 0) {
15915
- keyEnvelope = recoveryEnvelope;
15916
- } else {
15917
- const wrapperEntries = await Promise.all(
15918
- this._encryptionProviders.map(async (p) => ({
15919
- wrapKeyName: p.name,
15920
- ...await p.wrapDek(dek)
15921
- }))
15566
+ if (typeof this.config.chainAdapter.listReferences !== "function") {
15567
+ throw new DStorageError(
15568
+ CoreErrorCode.LIST_REFS_NOT_SUPPORTED,
15569
+ "[dStorage] listReferences() is not supported by this chain adapter."
15922
15570
  );
15923
- keyEnvelope = serializeKeyEnvelope(buildKeyEnvelope(wrapperEntries));
15924
15571
  }
15925
- const ownerSecret = await deriveOwnerSecret(
15926
- dek,
15927
- options.storageId !== void 0 ? new TextEncoder().encode(options.storageId) : METADATA_ONLY_OWNER_SECRET_SALT
15928
- );
15929
- const chainResult = await this.config.chainAdapter.writeReference({
15930
- ...onChainStorageId !== void 0 ? { storageId: onChainStorageId } : {},
15931
- encryptionScheme: "xchacha20poly1305-v1",
15932
- storageProvider: options.storageProvider ?? this.config.storageAdapter.name,
15933
- ...options.refId !== void 0 ? { refId: options.refId } : {},
15934
- ...encryptedMetadata !== void 0 ? { encryptedMetadata } : {},
15935
- writtenAt: Date.now(),
15936
- keyEnvelope,
15937
- ...options.contentHash !== void 0 ? { contentHash: options.contentHash } : {},
15938
- ownerSecret
15939
- });
15940
- return { chainRefId: chainResult.refId };
15941
- } finally {
15942
- dek.fill(0);
15572
+ rawItems = await this.config.chainAdapter.listReferences();
15943
15573
  }
15944
- } catch (err) {
15945
- wrapError(err);
15574
+ return Promise.all(
15575
+ rawItems.map(async (item) => {
15576
+ let storageId = null;
15577
+ let decryptionError;
15578
+ const isPublic = !item.encryptionScheme;
15579
+ let dekScheme;
15580
+ if (item.storageId) {
15581
+ if (isPublic) {
15582
+ storageId = item.storageId;
15583
+ } else {
15584
+ try {
15585
+ dekScheme = await this._getDekScheme(item.keyEnvelope);
15586
+ storageId = await dekScheme.decryptStorageId(item.storageId);
15587
+ } catch (err) {
15588
+ decryptionError = err instanceof Error ? err.message : String(err);
15589
+ }
15590
+ }
15591
+ }
15592
+ if (!decryptionError) {
15593
+ if (isPublic) {
15594
+ try {
15595
+ await this._unwrapDek(item.keyEnvelope);
15596
+ } catch (err) {
15597
+ decryptionError = err instanceof Error ? err.message : String(err);
15598
+ }
15599
+ }
15600
+ }
15601
+ let metadata;
15602
+ if (item.encryptedMetadata && dekScheme) {
15603
+ try {
15604
+ const raw = await dekScheme.decryptPayload(
15605
+ base64urlToBytes(item.encryptedMetadata),
15606
+ METADATA_AAD
15607
+ );
15608
+ metadata = JSON.parse(new TextDecoder().decode(raw));
15609
+ } catch {
15610
+ }
15611
+ }
15612
+ return {
15613
+ refId: item.refId,
15614
+ storageId,
15615
+ storageProvider: item.storageProvider,
15616
+ encryptionScheme: item.encryptionScheme ?? null,
15617
+ keyEnvelope: item.keyEnvelope,
15618
+ ownerCommitment: item.ownerCommitment,
15619
+ ...decryptionError !== void 0 ? { decryptionError } : {},
15620
+ ...metadata !== void 0 ? { metadata } : {}
15621
+ };
15622
+ })
15623
+ );
15624
+ } catch (err) {
15625
+ wrapError(err);
15946
15626
  }
15947
15627
  }
15948
15628
  /**
15949
- * Store new content and update an existing on-chain reference in place.
15950
- *
15951
- * Ownership is proved by re-deriving the ownerSecret from the existing
15952
- * reference — the same mechanism used by removeReference(). A fresh DEK is
15953
- * generated for the new content; the previous version is not modified and remains
15954
- * independently decryptable if the caller retained the old storageId.
15955
- *
15956
- * Note: chunking is not applied here. For large files use store() to get a
15957
- * new chunked reference, then removeReference() on the old one.
15958
- *
15959
- * @param refId The chain reference to update (returned by a prior store/registerReference).
15960
- * @param bytes New content to encrypt and store.
15961
- * @param options Optional StoreOptions. isPublic is ignored.
15629
+ * Remove a reference from the on-chain registry.
15630
+ * Only the owner of the reference can successfully call this — the chain
15631
+ * enforces ownership via ZK proof using the witness-derived ownerSecret.
15962
15632
  */
15963
- async update(refId, bytes, options) {
15633
+ async removeReference(refId) {
15964
15634
  try {
15965
- this.assertConnected();
15966
15635
  if (!this.config.chainAdapter) {
15967
15636
  throw new DStorageError(
15968
- CoreErrorCode.UPDATE_UPLOAD_REQUIRES_CHAIN,
15969
- "[dStorage] update requires a chain adapter."
15970
- );
15971
- }
15972
- if (typeof this.config.chainAdapter.updateReference !== "function") {
15973
- throw new DStorageError(
15974
- CoreErrorCode.UPDATE_UPLOAD_NOT_SUPPORTED,
15975
- "[dStorage] update is not supported by this chain adapter."
15637
+ CoreErrorCode.REMOVE_REF_REQUIRES_CHAIN,
15638
+ "[dStorage] removeReference() requires a chain adapter."
15976
15639
  );
15977
15640
  }
15978
- if (this._encryptionProviders.length === 0) {
15641
+ if (typeof this.config.chainAdapter.removeReference !== "function") {
15979
15642
  throw new DStorageError(
15980
- CoreErrorCode.UPDATE_UPLOAD_REQUIRES_ENCRYPTION,
15981
- "[dStorage] update requires at least one encryption adapter."
15643
+ CoreErrorCode.REMOVE_REF_NOT_SUPPORTED,
15644
+ "[dStorage] removeReference() is not supported by this chain adapter."
15982
15645
  );
15983
15646
  }
15984
- const { tags = {}, metadata: rawMeta } = options ?? {};
15985
15647
  const ownerSecret = await this._computeOwnerSecret(refId);
15986
- const dek = generateDek();
15987
- try {
15988
- const scheme = XChaChaScheme.fromKey(dek);
15989
- const uploadData = await scheme.encryptPayload(bytes, PAYLOAD_AAD);
15990
- const storageResult = await this.config.storageAdapter.store(
15991
- uploadData,
15992
- {
15993
- ...tags,
15994
- [TAG_CONTENT_TYPE]: TAG_CONTENT_TYPE_OCTET_STREAM
15995
- }
15996
- );
15997
- const onChainStorageId = await scheme.encryptStorageId(
15998
- storageResult.id
15999
- );
16000
- const wrapperEntries = await Promise.all(
16001
- this._encryptionProviders.map(async (p) => ({
16002
- wrapKeyName: p.name,
16003
- ...await p.wrapDek(dek)
16004
- }))
16005
- );
16006
- const keyEnvelope = serializeKeyEnvelope(
16007
- buildKeyEnvelope(wrapperEntries)
16008
- );
16009
- const encryptedMetadata = rawMeta ? bytesToBase64url(
16010
- await scheme.encryptPayload(
16011
- new TextEncoder().encode(JSON.stringify(rawMeta)),
16012
- METADATA_AAD
16013
- )
16014
- ) : void 0;
16015
- const contentHash = await computeBlake3Hex(uploadData);
16016
- const rawNewStorageId = new TextEncoder().encode(storageResult.id);
16017
- const newOwnerSecret = await deriveOwnerSecret(dek, rawNewStorageId);
16018
- const storeRecovery = {
16019
- storageId: storageResult.id,
16020
- storageProvider: storageResult.provider,
16021
- keyEnvelope,
16022
- contentHash
16023
- };
16024
- options?.onProgress?.({
16025
- phase: "stored",
16026
- chunksUploaded: 1,
16027
- totalChunks: 1,
16028
- bytesUploaded: bytes.length,
16029
- totalBytes: bytes.length,
16030
- recovery: storeRecovery
16031
- });
16032
- try {
16033
- await this.config.chainAdapter.updateReference(
16034
- refId,
16035
- {
16036
- storageId: onChainStorageId,
16037
- encryptionScheme: "xchacha20poly1305-v1",
16038
- storageProvider: storageResult.provider,
16039
- ...encryptedMetadata !== void 0 ? { encryptedMetadata } : {},
16040
- writtenAt: Date.now(),
16041
- keyEnvelope,
16042
- contentHash
16043
- },
16044
- ownerSecret,
16045
- newOwnerSecret
16046
- );
16047
- } catch {
16048
- throw new StorePartialError(storeRecovery);
16049
- }
16050
- return { chainRefId: refId, storageId: storageResult.id };
16051
- } finally {
16052
- dek.fill(0);
16053
- }
15648
+ await this.config.chainAdapter.removeReference(refId, ownerSecret);
16054
15649
  } catch (err) {
16055
15650
  wrapError(err);
16056
15651
  }
16057
15652
  }
16058
15653
  /**
16059
- * Retry only the on-chain reference write after a failed `update()` call.
16060
- *
16061
- * Use this when `update()` throws a `StorePartialError` — meaning the new
16062
- * content was stored successfully but the chain write failed. Call with the
16063
- * `refId` you passed to `update()` and the `err.recovery` from the caught
16064
- * error (or the `recovery` emitted via `onProgress` at phase `"stored"`).
16065
- *
16066
- * The new content is already in storage; this method re-derives all chain
16067
- * write parameters from the recovery envelope without re-uploading.
15654
+ * Estimate the full cost (storage + chain) for a given payload size before committing.
16068
15655
  */
16069
- async retryUpdate(refId, recovery) {
15656
+ async estimateCost(sizeBytes) {
16070
15657
  try {
16071
- this.assertConnected();
16072
- if (!this.config.chainAdapter) {
16073
- throw new DStorageError(
16074
- CoreErrorCode.UPDATE_UPLOAD_REQUIRES_CHAIN,
16075
- "[dStorage] retryUpdate requires a chain adapter."
16076
- );
16077
- }
16078
- if (typeof this.config.chainAdapter.updateReference !== "function") {
16079
- throw new DStorageError(
16080
- CoreErrorCode.UPDATE_UPLOAD_NOT_SUPPORTED,
16081
- "[dStorage] retryUpdate is not supported by this chain adapter."
16082
- );
16083
- }
16084
- if (this._encryptionProviders.length === 0) {
16085
- throw new DStorageError(
16086
- CoreErrorCode.UPDATE_UPLOAD_REQUIRES_ENCRYPTION,
16087
- "[dStorage] retryUpdate requires at least one encryption adapter."
16088
- );
16089
- }
16090
- if (!recovery.keyEnvelope) {
16091
- throw new DStorageError(
16092
- CoreErrorCode.UNWRAP_DEK_ENVELOPE_EMPTY,
16093
- "[dStorage] retryUpdate requires a key envelope in the recovery object."
16094
- );
16095
- }
16096
- const dek = await this._unwrapDek(recovery.keyEnvelope);
16097
- try {
16098
- const scheme = XChaChaScheme.fromKey(dek);
16099
- const ownerSecret = await this._computeOwnerSecret(refId);
16100
- const onChainStorageId = await scheme.encryptStorageId(
16101
- recovery.storageId
16102
- );
16103
- const newOwnerSecret = await deriveOwnerSecret(
16104
- dek,
16105
- new TextEncoder().encode(recovery.storageId)
16106
- );
16107
- await this.config.chainAdapter.updateReference(
16108
- refId,
16109
- {
16110
- storageId: onChainStorageId,
16111
- encryptionScheme: "xchacha20poly1305-v1",
16112
- storageProvider: recovery.storageProvider,
16113
- writtenAt: Date.now(),
16114
- keyEnvelope: recovery.keyEnvelope,
16115
- ...recovery.contentHash !== void 0 ? { contentHash: recovery.contentHash } : {}
16116
- },
16117
- ownerSecret,
16118
- newOwnerSecret
16119
- );
16120
- return { chainRefId: refId, storageId: recovery.storageId };
16121
- } finally {
16122
- dek.fill(0);
16123
- }
15658
+ const [storageQuote, chainQuote] = await Promise.all([
15659
+ this.config.storageAdapter.estimateCost(sizeBytes),
15660
+ this.config.chainAdapter?.estimateCost()
15661
+ ]);
15662
+ return {
15663
+ storageCost: { amount: storageQuote.amount, token: storageQuote.token },
15664
+ ...chainQuote ? {
15665
+ chainCost: { amount: chainQuote.amount, token: chainQuote.token }
15666
+ } : {},
15667
+ fileSizeBytes: sizeBytes
15668
+ };
16124
15669
  } catch (err) {
16125
15670
  wrapError(err);
16126
15671
  }
16127
15672
  }
15673
+ // ── Private helpers ────────────────────────────────────────────────────────
15674
+ /**
15675
+ * Upload large content by splitting into CHUNK_SIZE pieces, encrypting each
15676
+ * independently, and storing a manifest that ties them together.
15677
+ * Only the manifest's storageId is written on-chain.
15678
+ */
15679
+ async _uploadChunked(bytes, tags, onProgress, isPublic = false, rawMeta, refId) {
15680
+ if (this._encryptionProviders.length === 0) {
15681
+ throw new DStorageError(
15682
+ CoreErrorCode.UPLOAD_NO_PROVIDERS,
15683
+ "[dStorage] No encryption providers available. Call init() before uploading."
15684
+ );
15685
+ }
15686
+ const totalChunks = Math.ceil(bytes.length / CHUNK_SIZE);
15687
+ const sdkVersion = package_default.version ?? "unknown";
15688
+ const encryptionScheme = isPublic ? "" : "xchacha20poly1305-v1";
15689
+ this.logger?.log(
15690
+ "dStorage",
15691
+ `Chunked upload: ${bytes.length} bytes \u2192 ${totalChunks} chunks${isPublic ? "" : `, encryption scheme: ${encryptionScheme}`}`
15692
+ );
15693
+ let uploadScheme = null;
15694
+ let keyEnvelope;
15695
+ let chunkDek;
15696
+ let ownerSeed;
15697
+ if (!isPublic) {
15698
+ chunkDek = generateDek();
15699
+ uploadScheme = XChaChaScheme.fromKey(chunkDek);
15700
+ const wrapperEntries = await Promise.all(
15701
+ this._encryptionProviders.map(async (provider) => ({
15702
+ wrapKeyName: provider.name,
15703
+ ...await provider.wrapDek(chunkDek)
15704
+ }))
15705
+ );
15706
+ keyEnvelope = serializeKeyEnvelope(buildKeyEnvelope(wrapperEntries));
15707
+ } else {
15708
+ ownerSeed = generateDek();
15709
+ const ownerSeedWrappers = await Promise.all(
15710
+ this._encryptionProviders.map(async (provider) => ({
15711
+ wrapKeyName: provider.name,
15712
+ ...await provider.wrapDek(ownerSeed)
15713
+ }))
15714
+ );
15715
+ keyEnvelope = serializeKeyEnvelope(buildKeyEnvelope(ownerSeedWrappers));
15716
+ }
15717
+ const chunkEntries = [];
15718
+ let bytesUploaded = 0;
15719
+ for (let i = 0; i < totalChunks; i++) {
15720
+ const start = i * CHUNK_SIZE;
15721
+ const chunk = bytes.subarray(start, start + CHUNK_SIZE);
15722
+ onProgress?.({
15723
+ phase: "encrypting",
15724
+ chunksUploaded: i,
15725
+ totalChunks,
15726
+ bytesUploaded,
15727
+ totalBytes: bytes.length
15728
+ });
15729
+ let chunkData;
15730
+ if (isPublic) {
15731
+ chunkData = chunk;
15732
+ } else {
15733
+ chunkData = await uploadScheme.encryptPayload(
15734
+ chunk,
15735
+ chunkAad(i, totalChunks)
15736
+ );
15737
+ }
15738
+ onProgress?.({
15739
+ phase: "uploading",
15740
+ chunksUploaded: i,
15741
+ totalChunks,
15742
+ bytesUploaded,
15743
+ totalBytes: bytes.length
15744
+ });
15745
+ const chunkResult = await this.config.storageAdapter.store(chunkData, {
15746
+ ...tags,
15747
+ [TAG_SDK_VERSION]: sdkVersion,
15748
+ [TAG_CONTENT_TYPE]: TAG_CONTENT_TYPE_OCTET_STREAM,
15749
+ [TAG_CHUNK_INDEX]: String(i),
15750
+ [TAG_CHUNK_TOTAL]: String(totalChunks)
15751
+ });
15752
+ chunkEntries.push({
15753
+ index: i,
15754
+ storageId: chunkResult.id,
15755
+ size: chunk.length
15756
+ });
15757
+ bytesUploaded += chunk.length;
15758
+ onProgress?.({
15759
+ phase: "uploading",
15760
+ chunksUploaded: i + 1,
15761
+ totalChunks,
15762
+ bytesUploaded,
15763
+ totalBytes: bytes.length
15764
+ });
15765
+ }
15766
+ onProgress?.({
15767
+ phase: "finalizing",
15768
+ chunksUploaded: totalChunks,
15769
+ totalChunks,
15770
+ bytesUploaded,
15771
+ totalBytes: bytes.length
15772
+ });
15773
+ const manifest = {
15774
+ v: 1,
15775
+ type: "dstorage-chunked-manifest",
15776
+ totalSize: bytes.length,
15777
+ chunkSize: CHUNK_SIZE,
15778
+ chunkCount: totalChunks,
15779
+ provider: this.config.storageAdapter.name,
15780
+ chunks: chunkEntries
15781
+ };
15782
+ const manifestBytes = new TextEncoder().encode(JSON.stringify(manifest));
15783
+ let manifestData;
15784
+ if (isPublic) {
15785
+ manifestData = manifestBytes;
15786
+ } else {
15787
+ manifestData = await uploadScheme.encryptPayload(
15788
+ manifestBytes,
15789
+ MANIFEST_AAD
15790
+ );
15791
+ }
15792
+ const manifestResult = await this.config.storageAdapter.store(
15793
+ manifestData,
15794
+ {
15795
+ [TAG_SDK_VERSION]: sdkVersion,
15796
+ [TAG_CONTENT_TYPE]: isPublic ? TAG_CONTENT_TYPE_JSON : TAG_CONTENT_TYPE_OCTET_STREAM,
15797
+ [TAG_MANIFEST_UPLOAD]: "true"
15798
+ }
15799
+ );
15800
+ let onChainManifestId;
15801
+ if (isPublic) {
15802
+ onChainManifestId = manifestResult.id;
15803
+ } else {
15804
+ onChainManifestId = await uploadScheme.encryptStorageId(
15805
+ manifestResult.id
15806
+ );
15807
+ }
15808
+ const ownerSecret = chunkDek ? await deriveOwnerSecret(
15809
+ chunkDek,
15810
+ new TextEncoder().encode(manifestResult.id)
15811
+ ) : await deriveOwnerSecret(
15812
+ ownerSeed,
15813
+ new TextEncoder().encode(manifestResult.id),
15814
+ "dstorage:owner-secret:public:v1"
15815
+ );
15816
+ if (this.config.chainAdapter) {
15817
+ const contentHash = await computeBlake3Hex(manifestData);
15818
+ const storeRecovery = {
15819
+ storageId: manifestResult.id,
15820
+ storageProvider: manifestResult.provider,
15821
+ keyEnvelope: isPublic ? void 0 : keyEnvelope,
15822
+ contentHash
15823
+ };
15824
+ onProgress?.({
15825
+ phase: "stored",
15826
+ chunksUploaded: totalChunks,
15827
+ totalChunks,
15828
+ bytesUploaded: bytes.length,
15829
+ totalBytes: bytes.length,
15830
+ recovery: storeRecovery
15831
+ });
15832
+ const encryptedMetadata = rawMeta && uploadScheme ? bytesToBase64url(
15833
+ await uploadScheme.encryptPayload(
15834
+ new TextEncoder().encode(JSON.stringify(rawMeta)),
15835
+ METADATA_AAD
15836
+ )
15837
+ ) : void 0;
15838
+ try {
15839
+ const chainResult = await this.config.chainAdapter.writeReference({
15840
+ storageId: onChainManifestId,
15841
+ encryptionScheme,
15842
+ storageProvider: manifestResult.provider,
15843
+ ...refId !== void 0 ? { refId } : {},
15844
+ ...encryptedMetadata !== void 0 ? { encryptedMetadata } : {},
15845
+ writtenAt: Date.now(),
15846
+ keyEnvelope,
15847
+ contentHash,
15848
+ ownerSecret
15849
+ });
15850
+ const resultRefId = chainResult.refId;
15851
+ this.logger?.log(
15852
+ "dStorage",
15853
+ `Chunked upload complete \u2192 ${totalChunks} chunks + manifest, refId: ${resultRefId}`
15854
+ );
15855
+ return {
15856
+ chainRefId: resultRefId,
15857
+ storageId: manifestResult.id,
15858
+ storageProvider: manifestResult.provider,
15859
+ chainProvider: chainResult.provider,
15860
+ uploadedAt: manifestResult.uploadedAt,
15861
+ encryptionScheme,
15862
+ costEstimate: {
15863
+ storageCost: {
15864
+ amount: manifestResult.cost?.amount ?? "unknown",
15865
+ token: manifestResult.cost?.token ?? "AR"
15866
+ },
15867
+ chainCost: {
15868
+ amount: chainResult.paymentReceipt?.amount ?? "unknown",
15869
+ token: chainResult.paymentReceipt?.token ?? "DUST"
15870
+ },
15871
+ fileSizeBytes: bytes.length
15872
+ }
15873
+ };
15874
+ } catch {
15875
+ throw new StorePartialError(storeRecovery);
15876
+ }
15877
+ }
15878
+ this.logger?.log(
15879
+ "dStorage",
15880
+ `Chunked upload complete (storage-only) \u2192 ${totalChunks} chunks + manifest, storageId: ${manifestResult.id}`
15881
+ );
15882
+ return {
15883
+ storageId: manifestResult.id,
15884
+ storageProvider: manifestResult.provider,
15885
+ uploadedAt: manifestResult.uploadedAt,
15886
+ encryptionScheme,
15887
+ costEstimate: {
15888
+ storageCost: {
15889
+ amount: manifestResult.cost?.amount ?? "unknown",
15890
+ token: manifestResult.cost?.token ?? "AR"
15891
+ },
15892
+ fileSizeBytes: bytes.length
15893
+ }
15894
+ };
15895
+ }
15896
+ /**
15897
+ * Reassemble a chunked file from its manifest.
15898
+ * Fetches and decrypts each chunk sequentially to bound memory usage.
15899
+ */
15900
+ async _retrieveChunked(manifest, scheme = null, tags = {}) {
15901
+ if (manifest.chunks.length !== manifest.chunkCount) {
15902
+ throw new DStorageError(
15903
+ CoreErrorCode.MANIFEST_CHUNK_COUNT_MISMATCH,
15904
+ `[dStorage] Manifest declares ${manifest.chunkCount} chunks but contains ${manifest.chunks.length} entries`
15905
+ );
15906
+ }
15907
+ const sorted = [...manifest.chunks].sort((a, b) => a.index - b.index);
15908
+ for (let i = 0; i < sorted.length; i++) {
15909
+ if (sorted[i].index !== i) {
15910
+ throw new DStorageError(
15911
+ CoreErrorCode.MANIFEST_NOT_CONTIGUOUS,
15912
+ `[dStorage] Manifest chunk list is not contiguous: expected index ${i}, got ${sorted[i].index}`
15913
+ );
15914
+ }
15915
+ }
15916
+ const result = new Uint8Array(manifest.totalSize);
15917
+ let offset = 0;
15918
+ for (const entry of sorted) {
15919
+ const { bytes: rawChunkBytes } = await this.config.storageAdapter.retrieve(entry.storageId);
15920
+ let chunkBytes = rawChunkBytes;
15921
+ if (scheme) {
15922
+ try {
15923
+ chunkBytes = await scheme.decryptPayload(
15924
+ rawChunkBytes,
15925
+ chunkAad(entry.index, manifest.chunkCount)
15926
+ );
15927
+ } catch (err) {
15928
+ throw new DStorageError(
15929
+ CoreErrorCode.DECRYPT_CHUNK_FAILED,
15930
+ `[dStorage] Failed to decrypt chunk ${entry.index}: ${err}`,
15931
+ { cause: err }
15932
+ );
15933
+ }
15934
+ }
15935
+ if (offset + chunkBytes.length > manifest.totalSize) {
15936
+ throw new DStorageError(
15937
+ CoreErrorCode.CHUNK_OVERFLOWS_TOTAL_SIZE,
15938
+ `[dStorage] Chunk ${entry.index} overflows manifest totalSize`
15939
+ );
15940
+ }
15941
+ result.set(chunkBytes, offset);
15942
+ offset += chunkBytes.length;
15943
+ this.logger?.log(
15944
+ "dStorage",
15945
+ `Retrieved chunk ${entry.index + 1}/${manifest.chunkCount}: ${chunkBytes.length} bytes`
15946
+ );
15947
+ }
15948
+ if (offset !== manifest.totalSize) {
15949
+ throw new DStorageError(
15950
+ CoreErrorCode.REASSEMBLY_SIZE_MISMATCH,
15951
+ `[dStorage] Reassembled ${offset} bytes but manifest declares totalSize ${manifest.totalSize}`
15952
+ );
15953
+ }
15954
+ return {
15955
+ bytes: result,
15956
+ metadata: {},
15957
+ tags
15958
+ };
15959
+ }
15960
+ assertConnected() {
15961
+ if (!this._initialized && this.wallet === null) {
15962
+ throw new DStorageError(
15963
+ CoreErrorCode.NOT_INITIALIZED,
15964
+ "[dStorage] Not connected. Call dStorage.init() first."
15965
+ );
15966
+ }
15967
+ }
15968
+ };
15969
+
15970
+ // src/index.publish.ts
15971
+ init_dist();
15972
+
15973
+ // ../encryption/dist/index.mjs
15974
+ init_chunk_6IHXTW2Y();
15975
+ init_chunk_NAJYQ4J6();
15976
+ init_chunk_SW62VRE3();
15977
+ init_chunk_WBLC4ONG();
15978
+
15979
+ // src/index.publish.ts
15980
+ init_dist();
15981
+
15982
+ // ../storage/dist/chunk-URI7FNOW.mjs
15983
+ var StorageErrorCode = {
15984
+ // adapters/mock.ts (15000–15049)
15985
+ MOCK_AUTH_TOKEN_REQUIRED: 15001,
15986
+ MOCK_DATA_NOT_FOUND: 15002,
15987
+ // adapters/integrity.ts (15050–15149)
15988
+ CONTENT_HASH_MISMATCH: 15050,
15989
+ // adapters/arweave-local.ts (15150–15199)
15990
+ ARWEAVE_LOCAL_INVALID_ADDRESS: 15150,
15991
+ ARWEAVE_LOCAL_NOT_INSTALLED: 15151,
15992
+ // adapters/arweave.ts (15200–15399)
15993
+ ARWEAVE_AUTH_TOKEN_REQUIRED: 15200,
15994
+ ARWEAVE_NOT_INSTALLED: 15201,
15995
+ ARWEAVE_NO_SIGNED_TX: 15202,
15996
+ ARWEAVE_INVALID_TX_ID: 15203,
15997
+ ARWEAVE_TX_TOO_LARGE: 15204,
15998
+ ARWEAVE_TX_METADATA_FAILED: 15205,
15999
+ ARWEAVE_NO_DATA_RETURNED: 15206,
16000
+ ARWEAVE_SIZE_MISMATCH: 15207,
16001
+ ARWEAVE_HASH_TAG_MISSING: 15208,
16002
+ ARWEAVE_UPLOADER_INIT_FAILED: 15209,
16003
+ ARWEAVE_CHUNK_UPLOAD_FAILED: 15210,
16004
+ ARWEAVE_CONFIRM_TIMEOUT: 15211,
16005
+ ARWEAVE_GET_DATA_FAILED: 15212,
16006
+ // adapters/arweave-bundler.ts (15400–15599)
16007
+ ARWEAVE_BUNDLER_AUTH_TOKEN_REQUIRED: 15400,
16008
+ ARWEAVE_BUNDLER_LOCAL_NOT_SUPPORTED: 15401,
16009
+ ARWEAVE_BUNDLER_INVALID_TX_ID: 15402,
16010
+ ARWEAVE_BUNDLER_TX_METADATA_FAILED: 15403,
16011
+ ARWEAVE_BUNDLER_HASH_TAG_MISSING: 15404,
16012
+ ARWEAVE_BUNDLER_RETRIEVE_HTTP_ERROR: 15405,
16013
+ ARWEAVE_BUNDLER_SIZE_LIMIT_EXCEEDED: 15406,
16014
+ ARWEAVE_BUNDLER_SIZE_MISMATCH: 15407,
16015
+ ARWEAVE_BUNDLER_COST_ESTIMATE_FAILED: 15408,
16016
+ ARWEAVE_BUNDLER_NO_COST_ESTIMATE: 15409,
16017
+ ARWEAVE_BUNDLER_INVALID_SIGN_TX_RESPONSE: 15410,
16018
+ ARWEAVE_BUNDLER_INVALID_AUTH_TOKEN_FORMAT: 15411,
16019
+ ARWEAVE_BUNDLER_INVALID_AUTH_TOKEN_KEY: 15412,
16020
+ ARWEAVE_BUNDLER_NO_TX_ID_RETURNED: 15413,
16021
+ ARWEAVE_BUNDLER_UPLOAD_FAILED: 15414
16022
+ };
16023
+
16024
+ // ../payment/dist/chunk-IVL4WQ5Y.mjs
16025
+ var TOKENS = {
16026
+ AR: { symbol: "AR", name: "Arweave", decimals: 12 },
16027
+ DUST: { symbol: "DUST", name: "Midnight", decimals: 6 },
16028
+ MOCK: {
16029
+ symbol: "MOCK",
16030
+ name: "Mock Token",
16031
+ decimals: 6
16032
+ }
16033
+ };
16034
+
16035
+ // ../payment/dist/chunk-R6FT3AV4.mjs
16036
+ var MockPaymentAdapter = class {
16037
+ constructor(config) {
16038
+ this.network = "mock";
16039
+ this.token = config.token ?? "MOCK";
16040
+ this.type = config.type;
16041
+ this.quoteValidityMs = config.quoteValidityMs ?? 6e5;
16042
+ this.logger = config.logger;
16043
+ }
16044
+ async estimateCost(dataSizeBytes) {
16045
+ const kb = dataSizeBytes / 1024;
16046
+ const amount = Math.max(1e-4, kb * 1e-3).toFixed(6);
16047
+ this.logger?.log(
16048
+ "dStorage/mock-payment",
16049
+ `Estimated cost: ${amount} ${this.token}`
16050
+ );
16051
+ return {
16052
+ token: this.token,
16053
+ amount,
16054
+ network: this.network,
16055
+ type: this.type,
16056
+ dataSizeBytes,
16057
+ expiresAt: Date.now() + this.quoteValidityMs
16058
+ };
16059
+ }
16060
+ async pay(instruction, onStatus) {
16061
+ const tokenName = TOKENS[this.token]?.name ?? this.token;
16062
+ onStatus?.(`Requesting ${tokenName} wallet signature\u2026`);
16063
+ onStatus?.(`Broadcasting ${this.type} payment transaction\u2026`);
16064
+ onStatus?.("Awaiting confirmation\u2026");
16065
+ const txHash = generateSimulatedTxHash();
16066
+ const { amount } = await this.estimateCost(instruction.dataSizeBytes);
16067
+ this.logger?.log(
16068
+ "dStorage/mock-payment",
16069
+ `Simulated ${this.type} payment \u2192 txHash: ${txHash}`
16070
+ );
16071
+ return {
16072
+ txHash,
16073
+ token: this.token,
16074
+ amount,
16075
+ type: this.type,
16076
+ paidAt: Date.now(),
16077
+ serviceFee: "0"
16078
+ };
16079
+ }
16080
+ };
16081
+ function generateSimulatedTxHash() {
16082
+ const hex = Array.from(
16083
+ { length: 64 },
16084
+ () => Math.floor(Math.random() * 16).toString(16)
16085
+ ).join("");
16086
+ return `0x${hex}`;
16087
+ }
16088
+
16089
+ // ../payment/dist/index.mjs
16090
+ var import_blake32 = require("@noble/hashes/blake3.js");
16091
+ init_dist();
16092
+ init_dist();
16093
+ var PaymentErrorCode = {
16094
+ // adapters/arweave.ts (14000–14099)
16095
+ ARWEAVE_NOT_INSTALLED: 14001,
16096
+ ARWEAVE_WALLET_KEY_REQUIRED: 14002,
16097
+ ARWEAVE_CONNECT_NOT_FOUND: 14003,
16098
+ ARWEAVE_QUOTE_EXPIRED: 14004,
16099
+ // adapters/managed.ts (14100–14299)
16100
+ MANAGED_NON_OBJECT_RESPONSE: 14100,
16101
+ MANAGED_SERVER_FAILURE: 14101,
16102
+ MANAGED_MISSING_FIELD: 14102,
16103
+ MANAGED_PUBLIC_KEY_MISMATCH: 14103,
16104
+ MANAGED_TOKEN_REQUIRED: 14104,
16105
+ MANAGED_QUOTE_EXPIRED: 14105,
16106
+ MANAGED_UNBALANCED_TX: 14106,
16107
+ MANAGED_SERVER_UNREACHABLE: 14107,
16108
+ MANAGED_KEY_CHANGED: 14108,
16109
+ MANAGED_SIGN_HTTP_ERROR: 14109,
16110
+ MANAGED_NON_JSON_RESPONSE: 14110
16111
+ };
16112
+ var MAX_ERROR_BODY_LENGTH = 200;
16113
+ function sanitiseErrorBody(raw) {
16114
+ return raw.replace(/[\x00-\x1f\x7f]/g, " ").replace(/<[^>]*>/g, "").trim().slice(0, MAX_ERROR_BODY_LENGTH);
16115
+ }
16116
+ var DEFAULT_QUOTE_VALIDITY_MS = 12e4;
16117
+ var CONTENT_HASH_TAG = "X-Content-Hash";
16118
+ var ArweavePaymentAdapter = class {
16119
+ constructor(config = {}) {
16120
+ this.network = "arweave";
16121
+ this.token = "AR";
16122
+ this.type = "storage";
16123
+ this.arweaveClient = null;
16124
+ this.pendingUpload = null;
16125
+ this.signedUpload = null;
16126
+ this.gateway = config.gateway ?? {
16127
+ host: "arweave.net",
16128
+ port: 443,
16129
+ protocol: "https"
16130
+ };
16131
+ this.walletKey = config.walletKey;
16132
+ this.quoteValidityMs = config.quoteValidityMs ?? DEFAULT_QUOTE_VALIDITY_MS;
16133
+ this.logger = config.logger;
16134
+ }
16135
+ // ── Protected: lazy Arweave client ───────────────────────────────────────────
16136
+ async getClient() {
16137
+ if (this.arweaveClient) return this.arweaveClient;
16138
+ try {
16139
+ const _m = await import("arweave");
16140
+ const raw = _m.default;
16141
+ const ctor = typeof raw.init === "function" ? raw : raw.default ?? raw;
16142
+ this.arweaveClient = ctor.init(this.gateway ?? {});
16143
+ return this.arweaveClient;
16144
+ } catch {
16145
+ throw new DStorageError(
16146
+ PaymentErrorCode.ARWEAVE_NOT_INSTALLED,
16147
+ "[dStorage/arweave-payment] arweave package not installed. Run: npm install arweave"
16148
+ );
16149
+ }
16150
+ }
16151
+ // ── Protected: wallet resolution ─────────────────────────────────────────────
16152
+ resolveWallet() {
16153
+ if (this.walletKey && Object.keys(this.walletKey).length > 0) {
16154
+ return this.walletKey;
16155
+ }
16156
+ if (typeof window === "undefined") {
16157
+ throw new DStorageError(
16158
+ PaymentErrorCode.ARWEAVE_WALLET_KEY_REQUIRED,
16159
+ "[dStorage/arweave-payment] Node.js usage requires a walletKey in the config."
16160
+ );
16161
+ }
16162
+ if (typeof window.arweaveWallet === "undefined") {
16163
+ throw new DStorageError(
16164
+ PaymentErrorCode.ARWEAVE_CONNECT_NOT_FOUND,
16165
+ "[dStorage/arweave-payment] ArConnect extension not found. Install it from https://arconnect.io"
16166
+ );
16167
+ }
16168
+ return "use_wallet";
16169
+ }
16170
+ // ── PaymentAdapter: provideUploadData ─────────────────────────────────────────
16128
16171
  /**
16129
- * Rotate the encryption adapters protecting an existing reference without
16130
- * re-uploading the content.
16131
- *
16132
- * Useful when you want to:
16133
- * - Change a password: configure [newPassword, mnemonic] and call rotateKeys().
16134
- * The mnemonic unwraps the old DEK, the new envelope is wrapped under both
16135
- * new adapters, and the old password wrapper is dropped.
16136
- * - Add or remove adapters: any single adapter that matches a wrapper in the
16137
- * existing key envelope can authorise the rotation. The resulting envelope
16138
- * is wrapped under exactly the adapters currently configured on this SDK.
16139
- *
16140
- * The content in storage is not touched — only the on-chain key envelope is
16141
- * updated. writtenAt is preserved to reflect the original upload time.
16172
+ * Store the encrypted upload payload and metadata tags so that pay() can
16173
+ * build and sign the complete Arweave transaction in one step.
16174
+ * Called by MetaTransaction after the encrypt step, before pay().
16142
16175
  */
16143
- async rotateKeys(refId) {
16144
- try {
16145
- this.assertConnected();
16146
- if (!this.config.chainAdapter) {
16147
- throw new DStorageError(
16148
- CoreErrorCode.ROTATE_KEYS_REQUIRES_CHAIN,
16149
- "[dStorage] rotateKeys requires a chain adapter."
16150
- );
16151
- }
16152
- if (typeof this.config.chainAdapter.updateReference !== "function") {
16153
- throw new DStorageError(
16154
- CoreErrorCode.ROTATE_KEYS_NOT_SUPPORTED,
16155
- "[dStorage] rotateKeys is not supported by this chain adapter."
16156
- );
16157
- }
16158
- if (this._encryptionProviders.length === 0) {
16159
- throw new DStorageError(
16160
- CoreErrorCode.ROTATE_KEYS_REQUIRES_ENCRYPTION,
16161
- "[dStorage] rotateKeys requires at least one encryption adapter."
16162
- );
16163
- }
16164
- const ref = await this.config.chainAdapter.readReference(refId);
16165
- const ownerSecret = await this._computeOwnerSecret(refId);
16166
- const seed = await this._unwrapDek(ref.keyEnvelope);
16167
- try {
16168
- const wrapperEntries = await Promise.all(
16169
- this._encryptionProviders.map(async (p) => ({
16170
- wrapKeyName: p.name,
16171
- ...await p.wrapDek(seed)
16172
- }))
16173
- );
16174
- const newKeyEnvelope = serializeKeyEnvelope(
16175
- buildKeyEnvelope(wrapperEntries)
16176
- );
16177
- await this.config.chainAdapter.updateReference(
16178
- refId,
16179
- {
16180
- storageId: ref.storageId ?? "",
16181
- encryptionScheme: ref.encryptionScheme ?? "",
16182
- storageProvider: ref.storageProvider,
16183
- writtenAt: ref.writtenAt,
16184
- keyEnvelope: newKeyEnvelope,
16185
- ...ref.encryptedMetadata !== void 0 ? { encryptedMetadata: ref.encryptedMetadata } : {},
16186
- ...ref.contentHash !== void 0 ? { contentHash: ref.contentHash } : {}
16187
- },
16188
- ownerSecret,
16189
- ownerSecret
16190
- );
16191
- } finally {
16192
- seed.fill(0);
16193
- }
16194
- } catch (err) {
16195
- wrapError(err);
16196
- }
16176
+ provideUploadData(data, metadata = {}) {
16177
+ const hashHex = Array.from((0, import_blake32.blake3)(data)).map((b) => b.toString(16).padStart(2, "0")).join("");
16178
+ this.pendingUpload = {
16179
+ data,
16180
+ metadata: { [CONTENT_HASH_TAG]: hashHex, ...metadata }
16181
+ };
16182
+ this.logger?.log(
16183
+ "dStorage/arweave-payment",
16184
+ `Upload data staged: ${data.byteLength} bytes`
16185
+ );
16197
16186
  }
16187
+ // ── PaymentAdapter: estimateCost ─────────────────────────────────────────────
16198
16188
  /**
16199
- * Retrieve and decrypt data by its storageId.
16200
- * Decryption happens locally — the SDK never sends plaintext anywhere.
16201
- *
16202
- * Automatically detects chunked manifests and reassembles the original file.
16203
- *
16204
- * @param storageId The storageId returned from store()
16189
+ * Query the Arweave fee oracle for the real upload cost.
16205
16190
  */
16206
- async retrieveByStorageId(storageId, dekScheme, contentHash) {
16207
- try {
16208
- this.assertConnected();
16209
- const { bytes: rawBytes, tags } = await this.config.storageAdapter.retrieve(storageId);
16210
- if (contentHash) {
16211
- const computed = await computeBlake3Hex(rawBytes);
16212
- if (computed !== contentHash) {
16213
- throw new DStorageError(
16214
- CoreErrorCode.CONTENT_HASH_MISMATCH,
16215
- `[dStorage] Content hash mismatch for storageId ${storageId}. Expected ${contentHash}, got ${computed}. The retrieved bytes do not match the hash committed on-chain \u2014 the storage content may have been tampered with.`
16216
- );
16217
- }
16218
- }
16219
- return this._decryptAndReassemble(rawBytes, storageId, tags, dekScheme);
16220
- } catch (err) {
16221
- wrapError(err);
16222
- }
16191
+ async estimateCost(dataSizeBytes) {
16192
+ const arweave = await this.getClient();
16193
+ const winstons = await arweave.transactions.getPrice(
16194
+ dataSizeBytes
16195
+ );
16196
+ const amount = arweave.ar.winstonToAr(winstons);
16197
+ this.logger?.log(
16198
+ "dStorage/arweave-payment",
16199
+ `Estimated: ${amount} AR (${winstons} Winstons)`
16200
+ );
16201
+ return {
16202
+ token: this.token,
16203
+ amount,
16204
+ network: this.network,
16205
+ type: this.type,
16206
+ dataSizeBytes,
16207
+ expiresAt: Date.now() + this.quoteValidityMs
16208
+ };
16223
16209
  }
16210
+ // ── PaymentAdapter: pay ───────────────────────────────────────────────────────
16224
16211
  /**
16225
- * Unwrap the data DEK from a key envelope and return an XChaChaScheme built from it.
16226
- * Throws if the KEK is not available or the envelope cannot be parsed.
16227
- * Only valid for private uploads (envelope.wrappers must be present).
16212
+ * Sign-then-store flow (when provideUploadData was called):
16213
+ * Creates the Arweave transaction with data + tags, prompts the wallet
16214
+ * for approval (ArConnect popup or JWK signing), and stores the signed
16215
+ * transaction for ArweaveStorageAdapter.store() to post.
16216
+ *
16217
+ * Pass-through fallback (when provideUploadData was NOT called):
16218
+ * Returns a receipt acknowledging that payment is bundled with the upload.
16219
+ * ArweaveStorageAdapter.store() performs the full create → sign → post flow.
16228
16220
  */
16229
- async _getDekScheme(keyEnvelope) {
16230
- if (this._encryptionProviders.length === 0) {
16231
- throw new DStorageError(
16232
- CoreErrorCode.DECRYPT_NO_PROVIDERS,
16233
- "[dStorage] Cannot decrypt: no encryption providers. Call init() before retrieving private data."
16234
- );
16235
- }
16236
- if (!keyEnvelope) {
16221
+ async pay(instruction, onStatus) {
16222
+ if (Date.now() > instruction.expiresAt) {
16237
16223
  throw new DStorageError(
16238
- CoreErrorCode.DECRYPT_ENVELOPE_EMPTY,
16239
- "[dStorage] Cannot decrypt: key envelope is empty.",
16240
- { cause: { code: "envelope_empty" } }
16224
+ PaymentErrorCode.ARWEAVE_QUOTE_EXPIRED,
16225
+ `[dStorage/payment] Quote expired at ${new Date(instruction.expiresAt).toISOString()}. Request a fresh quote via estimateCost() before calling pay().`
16241
16226
  );
16242
16227
  }
16243
- const envelope = parseKeyEnvelope(keyEnvelope);
16244
- if (!envelope) {
16245
- throw new DStorageError(
16246
- CoreErrorCode.DECRYPT_ENVELOPE_MALFORMED,
16247
- "[dStorage] Cannot decrypt: key envelope is malformed or has an invalid structure. The stored reference may be corrupted or tampered with.",
16248
- { cause: { code: "envelope_malformed" } }
16249
- );
16228
+ if (!this.pendingUpload) {
16229
+ onStatus?.("AR fee will be deducted as part of the upload transaction.");
16230
+ return {
16231
+ txHash: "arweave-bundled-with-upload",
16232
+ token: this.token,
16233
+ amount: "0",
16234
+ type: this.type,
16235
+ paidAt: Date.now(),
16236
+ serviceFee: "0"
16237
+ };
16250
16238
  }
16251
- for (const wrapper of envelope.wrappers) {
16252
- const provider = this._encryptionProviders.find(
16253
- (p) => p.name === wrapper.wrapKeyName
16254
- );
16255
- if (!provider) continue;
16256
- try {
16257
- const dek = await provider.unwrapDek(wrapper);
16258
- return XChaChaScheme.fromKey(dek);
16259
- } catch {
16260
- }
16239
+ const { data, metadata } = this.pendingUpload;
16240
+ this.pendingUpload = null;
16241
+ const arweave = await this.getClient();
16242
+ const wallet = this.resolveWallet();
16243
+ onStatus?.("Creating Arweave transaction\u2026");
16244
+ const tx = await arweave.createTransaction({ data }, wallet);
16245
+ for (const [key, value] of Object.entries(metadata)) {
16246
+ tx.addTag(key, value);
16261
16247
  }
16248
+ onStatus?.("Requesting AR wallet approval\u2026");
16249
+ await arweave.transactions.sign(tx, wallet);
16250
+ const winstons = await arweave.transactions.getPrice(
16251
+ data.byteLength
16252
+ );
16253
+ const arCost = arweave.ar.winstonToAr(winstons);
16254
+ this.signedUpload = { tx, arCost };
16255
+ this.logger?.log(
16256
+ "dStorage/arweave-payment",
16257
+ `Transaction signed \u2192 txId: ${tx.id} cost: ~${arCost} AR`
16258
+ );
16259
+ return {
16260
+ txHash: tx.id,
16261
+ token: this.token,
16262
+ amount: arCost,
16263
+ type: this.type,
16264
+ paidAt: Date.now(),
16265
+ serviceFee: "0"
16266
+ };
16267
+ }
16268
+ // ── Consumed by ArweaveStorageAdapter ─────────────────────────────────────────
16269
+ /**
16270
+ * Returns the pre-signed transaction produced by pay(), then clears it.
16271
+ * Called by ArweaveStorageAdapter.store() to post the already-signed tx.
16272
+ * Returns null if pay() did not sign a transaction (pass-through mode).
16273
+ */
16274
+ consumeSignedUpload() {
16275
+ const signed = this.signedUpload;
16276
+ this.signedUpload = null;
16277
+ return signed;
16278
+ }
16279
+ };
16280
+ var POST_SIGN_TX_API_PATH = "/api/service/sign-tx";
16281
+ function uint8ArrayToHex(bytes) {
16282
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
16283
+ }
16284
+ function parseSignedTxBytes(signedTx) {
16285
+ if (signedTx.startsWith("midnight:")) {
16286
+ const colonIdx = signedTx.lastIndexOf(":");
16287
+ return hexToBytes(signedTx.slice(colonIdx + 1), "signedTx");
16288
+ }
16289
+ return hexToBytes(signedTx, "signedTx");
16290
+ }
16291
+ function parseSignTxResponse(raw) {
16292
+ if (typeof raw !== "object" || raw === null) {
16262
16293
  throw new DStorageError(
16263
- CoreErrorCode.DECRYPT_NO_WRAPPER_MATCHED,
16264
- "[dStorage] Cannot decrypt: this reference was not encrypted with any of your configured keys. Check that you are using the correct encryption adapter, password, seed phrase, or keypair.",
16265
- { cause: { code: "no_wrapper_matched" } }
16294
+ PaymentErrorCode.MANAGED_NON_OBJECT_RESPONSE,
16295
+ "[dStorage/managed-payment] Signing server returned a non-object response."
16266
16296
  );
16267
16297
  }
16268
- /**
16269
- * Unwrap the raw DEK bytes from a key envelope using the available KEKs.
16270
- * The ownerSecret for the reference can be re-derived as HKDF(dek, rawStorageId).
16271
- * Throws if no wrapper matches any available KEK.
16272
- */
16273
- async _unwrapDek(keyEnvelope) {
16274
- if (this._encryptionProviders.length === 0) {
16275
- throw new DStorageError(
16276
- CoreErrorCode.UNWRAP_DEK_NO_PROVIDERS,
16277
- "[dStorage] Cannot unwrap DEK: no encryption providers. Call init() first."
16278
- );
16279
- }
16280
- if (!keyEnvelope) {
16281
- throw new DStorageError(
16282
- CoreErrorCode.UNWRAP_DEK_ENVELOPE_EMPTY,
16283
- "[dStorage] Cannot unwrap DEK: key envelope is empty.",
16284
- { cause: { code: "envelope_empty" } }
16285
- );
16286
- }
16287
- const envelope = parseKeyEnvelope(keyEnvelope);
16288
- if (!envelope) {
16289
- throw new DStorageError(
16290
- CoreErrorCode.UNWRAP_DEK_ENVELOPE_MALFORMED,
16291
- "[dStorage] Cannot unwrap DEK: key envelope is malformed or has an invalid structure. The stored reference may be corrupted or tampered with.",
16292
- { cause: { code: "envelope_malformed" } }
16293
- );
16294
- }
16295
- for (const wrapper of envelope.wrappers) {
16296
- const provider = this._encryptionProviders.find(
16297
- (p) => p.name === wrapper.wrapKeyName
16298
- );
16299
- if (!provider) continue;
16300
- try {
16301
- return await provider.unwrapDek(wrapper);
16302
- } catch {
16303
- }
16304
- }
16298
+ const r = raw;
16299
+ if (r["success"] !== true) {
16305
16300
  throw new DStorageError(
16306
- CoreErrorCode.UNWRAP_DEK_NO_WRAPPER_MATCHED,
16307
- "[dStorage] Cannot unwrap DEK: this reference was not encrypted with any of your configured keys. Check that you are using the correct encryption adapter, password, seed phrase, or keypair.",
16308
- { cause: { code: "no_wrapper_matched" } }
16301
+ PaymentErrorCode.MANAGED_SERVER_FAILURE,
16302
+ `[dStorage/managed-payment] Signing server reported success:false \u2014 ${sanitiseErrorBody(JSON.stringify(r))}`
16309
16303
  );
16310
16304
  }
16311
- /**
16312
- * Compute the ownerSecret for an existing on-chain reference.
16313
- * Used by removeReference/updateReference to derive the ZK witness secret.
16314
- *
16315
- * Flow:
16316
- * 1. Read the reference from chain to get keyEnvelope + storageId
16317
- * 2. Unwrap the DEK from keyEnvelope using available KEKs
16318
- * 3. Recover raw storageId (decrypt with DEK if private, plaintext if public)
16319
- * 4. Return HKDF-SHA256(dek, rawStorageId) for private, or HKDF-SHA256(ownerSeed, rawStorageId) for public
16320
- */
16321
- async _computeOwnerSecret(refId) {
16322
- const ref = await this.config.chainAdapter.readReference(refId);
16323
- const isPublic = !ref.encryptionScheme;
16324
- if (isPublic) {
16325
- const ownerSeed = await this._unwrapDek(ref.keyEnvelope);
16326
- return deriveOwnerSecret(
16327
- ownerSeed,
16328
- new TextEncoder().encode(ref.storageId),
16329
- "dstorage:owner-secret:public:v1"
16305
+ for (const field of [
16306
+ "txId",
16307
+ "signature",
16308
+ "publicKey",
16309
+ "txAmount",
16310
+ "serviceFee"
16311
+ ]) {
16312
+ if (typeof r[field] !== "string") {
16313
+ throw new DStorageError(
16314
+ PaymentErrorCode.MANAGED_MISSING_FIELD,
16315
+ `[dStorage/managed-payment] Signing server response missing or invalid field: ${field}`
16330
16316
  );
16331
16317
  }
16332
- const dek = await this._unwrapDek(ref.keyEnvelope);
16333
- const dekScheme = XChaChaScheme.fromKey(dek);
16334
- const rawStorageIdBytes = ref.storageId ? new TextEncoder().encode(
16335
- await dekScheme.decryptStorageId(ref.storageId)
16336
- ) : METADATA_ONLY_OWNER_SECRET_SALT;
16337
- return deriveOwnerSecret(dek, rawStorageIdBytes);
16338
16318
  }
16339
- // Accepts an optional pre-built CryptoScheme (unwrapped from a key envelope).
16340
- // If no dekScheme is provided, content is treated as public (no decryption).
16341
- async _decryptAndReassemble(rawBytes, storageId, tags, dekScheme) {
16342
- const scheme = dekScheme ?? null;
16343
- if (scheme) {
16344
- let manifestBytes = null;
16345
- try {
16346
- manifestBytes = await scheme.decryptPayload(rawBytes, MANIFEST_AAD);
16347
- } catch {
16348
- }
16349
- if (manifestBytes !== null) {
16350
- const parsed = JSON.parse(
16351
- new TextDecoder().decode(manifestBytes)
16352
- );
16353
- if (!isManifest(parsed)) {
16354
- throw new DStorageError(
16355
- CoreErrorCode.MANIFEST_INVALID_STRUCTURE,
16356
- "[dStorage] Payload authenticated as manifest but has invalid structure"
16357
- );
16358
- }
16359
- this.logger?.log(
16360
- "dStorage",
16361
- `Manifest detected: reassembling ${parsed.chunkCount} chunks`
16362
- );
16363
- return this._retrieveChunked(parsed, scheme, tags);
16364
- }
16365
- let decryptedBytes;
16366
- try {
16367
- decryptedBytes = await scheme.decryptPayload(rawBytes, PAYLOAD_AAD);
16368
- } catch (err) {
16369
- throw new DStorageError(
16370
- CoreErrorCode.DECRYPT_PAYLOAD_FAILED,
16371
- `[dStorage] Failed to decrypt payload: ${err}`,
16372
- { cause: err }
16373
- );
16374
- }
16375
- this.logger?.log(
16376
- "dStorage",
16377
- `Retrieved by refId \u2192 storageId: ${storageId}`
16378
- );
16379
- return { bytes: decryptedBytes, metadata: {}, tags };
16380
- }
16381
- try {
16382
- const parsed = JSON.parse(new TextDecoder().decode(rawBytes));
16383
- if (isManifest(parsed)) {
16384
- this.logger?.log(
16385
- "dStorage",
16386
- `Manifest detected: reassembling ${parsed.chunkCount} chunks`
16387
- );
16388
- return this._retrieveChunked(parsed, null, tags);
16389
- }
16390
- } catch {
16391
- }
16392
- this.logger?.log(
16393
- "dStorage",
16394
- `Retrieved by refId \u2192 storageId: ${storageId}`
16319
+ return r;
16320
+ }
16321
+ function assertOwnerKeyMatch(responseKey, pinnedKey) {
16322
+ if (pinnedKey && responseKey !== pinnedKey) {
16323
+ throw new DStorageError(
16324
+ PaymentErrorCode.MANAGED_PUBLIC_KEY_MISMATCH,
16325
+ "[dStorage/managed-payment] Signing server returned a public key that does not match the pinned key from the auth token \u2014 possible server compromise or MITM."
16395
16326
  );
16396
- return {
16397
- bytes: rawBytes,
16398
- metadata: {},
16399
- tags
16400
- };
16401
16327
  }
16328
+ }
16329
+ var ManagedPaymentAdapter = class _ManagedPaymentAdapter extends ArweavePaymentAdapter {
16402
16330
  /**
16403
- * Retrieve and decrypt data using only its on-chain reference ID.
16404
- *
16405
- * Flow:
16406
- * 1. Look up the reference on-chain by refId to obtain the storage network ID.
16407
- * 2. Fetch the blob from the storage network.
16408
- * 3. Decrypt locally if an encryption key is available.
16409
- *
16410
- * Requires the chain adapter to implement readReference() (part of the
16411
- * ChainAdapter interface — shared by MidnightChainAdapter, MockChainAdapter, etc.).
16331
+ * Strip a RSA-4096 modulus suffix from a compound auth token
16332
+ * (<credential>.<base64url_683_chars>), returning only the credential.
16333
+ * A plain token (no '.' or wrong suffix length) is returned unchanged.
16334
+ * This lets ArweaveBundlerStorageAdapter compound tokens be passed directly
16335
+ * to any adapter that uses ManagedPaymentAdapter without breaking authentication.
16412
16336
  *
16413
- * @param refId The chainRefId returned from store()
16337
+ * lastIndexOf is intentional: the credential part may itself contain dots
16338
+ * (e.g. a JWT), so we always split at the *last* dot to reach the modulus.
16414
16339
  */
16415
- async retrieveByRefId(refId) {
16416
- try {
16417
- this.assertConnected();
16418
- if (!this.config.chainAdapter) {
16419
- throw new DStorageError(
16420
- CoreErrorCode.RETRIEVE_BY_REF_ID_REQUIRES_CHAIN,
16421
- "[dStorage] retrieveByRefId() requires a chain adapter. Configure one in DStorageConfig or use retrieveByStorageId() instead."
16422
- );
16423
- }
16424
- const ref = await this.config.chainAdapter.readReference(refId);
16425
- if (!ref)
16426
- throw new DStorageError(
16427
- CoreErrorCode.REF_NOT_FOUND,
16428
- `[dStorage] No on-chain reference found for refId: ${refId}`
16429
- );
16430
- if (ref.storageProvider && ref.storageProvider !== this.config.storageAdapter.name) {
16431
- throw new DStorageError(
16432
- CoreErrorCode.STORAGE_ADAPTER_MISMATCH,
16433
- `[dStorage] Storage adapter mismatch: reference "${refId}" was stored on "${ref.storageProvider}" but the configured adapter is "${this.config.storageAdapter.name}". Configure a "${ref.storageProvider}" storage adapter to retrieve this reference.`
16434
- );
16435
- }
16436
- const isPublic = !ref.encryptionScheme;
16437
- let storageId;
16438
- let dekScheme;
16439
- if (!ref.storageId) {
16440
- if (!isPublic && ref.keyEnvelope) {
16441
- dekScheme = await this._getDekScheme(ref.keyEnvelope);
16442
- }
16443
- } else if (isPublic) {
16444
- storageId = ref.storageId;
16445
- } else {
16446
- if (!ref.keyEnvelope) {
16447
- throw new DStorageError(
16448
- CoreErrorCode.RETRIEVE_BY_REF_ID_ENVELOPE_EMPTY,
16449
- `[dStorage] Cannot decrypt refId ${refId}: key envelope is empty.`,
16450
- { cause: { code: "envelope_empty" } }
16451
- );
16452
- }
16453
- dekScheme = await this._getDekScheme(ref.keyEnvelope);
16454
- storageId = await dekScheme.decryptStorageId(ref.storageId);
16455
- }
16456
- let contentBytes;
16457
- let contentTags;
16458
- if (storageId !== void 0) {
16459
- const r = await this.retrieveByStorageId(
16460
- storageId,
16461
- dekScheme,
16462
- ref.contentHash
16463
- );
16464
- contentBytes = r.bytes;
16465
- contentTags = r.tags;
16466
- } else {
16467
- contentBytes = new Uint8Array(0);
16468
- contentTags = {};
16469
- }
16470
- let metadata = {};
16471
- if (ref.encryptedMetadata && dekScheme) {
16472
- const raw = await dekScheme.decryptPayload(
16473
- base64urlToBytes(ref.encryptedMetadata),
16474
- METADATA_AAD
16475
- );
16476
- metadata = JSON.parse(new TextDecoder().decode(raw));
16340
+ static _parseAuthToken(token) {
16341
+ const dot = token.lastIndexOf(".");
16342
+ if (dot !== -1) {
16343
+ const suffix = token.slice(dot + 1);
16344
+ if (suffix.length === 683 && /^[A-Za-z0-9_-]+$/.test(suffix)) {
16345
+ return { credential: token.slice(0, dot), ownerPublicKey: suffix };
16477
16346
  }
16478
- return { bytes: contentBytes, tags: contentTags, metadata };
16479
- } catch (err) {
16480
- wrapError(err);
16481
16347
  }
16348
+ return { credential: token, ownerPublicKey: void 0 };
16349
+ }
16350
+ constructor(config) {
16351
+ super({
16352
+ ...config.gateway !== void 0 ? { gateway: config.gateway } : {},
16353
+ ...config.quoteValidityMs !== void 0 ? { quoteValidityMs: config.quoteValidityMs } : {},
16354
+ ...config.logger !== void 0 ? { logger: config.logger } : {}
16355
+ });
16356
+ this.signingServerUrl = config.signingServerUrl.replace(/\/$/, "");
16357
+ if (!config.authToken || config.authToken.trim() === "") {
16358
+ throw new DStorageError(
16359
+ PaymentErrorCode.MANAGED_TOKEN_REQUIRED,
16360
+ "[dStorage/managed-payment] authToken is required and must not be empty or whitespace."
16361
+ );
16362
+ }
16363
+ const { credential, ownerPublicKey } = _ManagedPaymentAdapter._parseAuthToken(config.authToken);
16364
+ this.authToken = credential;
16365
+ this.ownerPublicKey = ownerPublicKey;
16366
+ this.requestTimeoutMs = config.requestTimeoutMs ?? 3e4;
16367
+ this.network = config.network;
16368
+ this.type = config.type;
16482
16369
  }
16370
+ // ── PaymentAdapter: estimateCost ─────────────────────────────────────────────
16483
16371
  /**
16484
- * List references currently available from the configured chain adapter.
16372
+ * Query the Arweave fee oracle for the real upload cost.
16485
16373
  */
16486
- async listReferences(options) {
16487
- try {
16488
- if (!this.config.chainAdapter) {
16489
- throw new DStorageError(
16490
- CoreErrorCode.LIST_REFS_REQUIRES_CHAIN,
16491
- "[dStorage] listReferences() requires a chain adapter."
16492
- );
16493
- }
16494
- let rawItems;
16495
- if (options?.refIds && options.refIds.length > 0) {
16496
- rawItems = await Promise.all(
16497
- options.refIds.map(async (refId) => {
16498
- const ref = await this.config.chainAdapter.readReference(refId);
16499
- return {
16500
- refId,
16501
- storageId: ref.storageId ?? null,
16502
- encryptionScheme: ref.encryptionScheme ?? null,
16503
- keyEnvelope: ref.keyEnvelope,
16504
- storageProvider: ref.storageProvider,
16505
- ownerCommitment: "",
16506
- ...ref.encryptedMetadata !== void 0 ? { encryptedMetadata: ref.encryptedMetadata } : {}
16507
- };
16508
- })
16509
- );
16510
- } else {
16511
- if (typeof this.config.chainAdapter.listReferences !== "function") {
16512
- throw new DStorageError(
16513
- CoreErrorCode.LIST_REFS_NOT_SUPPORTED,
16514
- "[dStorage] listReferences() is not supported by this chain adapter."
16515
- );
16516
- }
16517
- rawItems = await this.config.chainAdapter.listReferences();
16518
- }
16519
- return Promise.all(
16520
- rawItems.map(async (item) => {
16521
- let storageId = null;
16522
- let decryptionError;
16523
- const isPublic = !item.encryptionScheme;
16524
- let dekScheme;
16525
- if (item.storageId) {
16526
- if (isPublic) {
16527
- storageId = item.storageId;
16528
- } else {
16529
- try {
16530
- dekScheme = await this._getDekScheme(item.keyEnvelope);
16531
- storageId = await dekScheme.decryptStorageId(item.storageId);
16532
- } catch (err) {
16533
- decryptionError = err instanceof Error ? err.message : String(err);
16534
- }
16535
- }
16536
- }
16537
- if (!decryptionError) {
16538
- if (isPublic) {
16539
- try {
16540
- await this._unwrapDek(item.keyEnvelope);
16541
- } catch (err) {
16542
- decryptionError = err instanceof Error ? err.message : String(err);
16543
- }
16544
- }
16545
- }
16546
- let metadata;
16547
- if (item.encryptedMetadata && dekScheme) {
16548
- try {
16549
- const raw = await dekScheme.decryptPayload(
16550
- base64urlToBytes(item.encryptedMetadata),
16551
- METADATA_AAD
16552
- );
16553
- metadata = JSON.parse(new TextDecoder().decode(raw));
16554
- } catch {
16555
- }
16556
- }
16557
- return {
16558
- refId: item.refId,
16559
- storageId,
16560
- storageProvider: item.storageProvider,
16561
- encryptionScheme: item.encryptionScheme ?? null,
16562
- keyEnvelope: item.keyEnvelope,
16563
- ownerCommitment: item.ownerCommitment,
16564
- ...decryptionError !== void 0 ? { decryptionError } : {},
16565
- ...metadata !== void 0 ? { metadata } : {}
16566
- };
16567
- })
16374
+ async estimateCost(dataSizeBytes) {
16375
+ if (this.network === "arweave") {
16376
+ return await super.estimateCost(dataSizeBytes);
16377
+ }
16378
+ const amount = "0.0012345";
16379
+ this.logger?.log(
16380
+ "dStorage/managed-payment",
16381
+ `Estimated: ${amount} ${this.token}`
16382
+ );
16383
+ return {
16384
+ token: this.token,
16385
+ // FIXME: should be probably obtained from the signing service
16386
+ amount,
16387
+ network: this.network,
16388
+ type: this.type,
16389
+ dataSizeBytes,
16390
+ txData: "---",
16391
+ expiresAt: Date.now() + this.quoteValidityMs
16392
+ };
16393
+ }
16394
+ // ── PaymentAdapter: pay ───────────────────────────────────────────────────────
16395
+ /**
16396
+ * Routes payment to the correct network-specific handler based on quote.network.
16397
+ *
16398
+ * The Arweave path falls back to the base-class pass-through if
16399
+ * provideUploadData() was not called. An unrecognised network throws.
16400
+ */
16401
+ async pay(instruction, onStatus) {
16402
+ if (Date.now() > instruction.expiresAt) {
16403
+ throw new DStorageError(
16404
+ PaymentErrorCode.MANAGED_QUOTE_EXPIRED,
16405
+ `[dStorage/payment] Quote expired at ${new Date(instruction.expiresAt).toISOString()}. Request a fresh quote via estimateCost() before calling pay().`
16568
16406
  );
16569
- } catch (err) {
16570
- wrapError(err);
16571
16407
  }
16408
+ if (this.network === "managedmock") {
16409
+ return this._payManagedMock(instruction, onStatus);
16410
+ }
16411
+ if (instruction.network === "midnight") {
16412
+ return this._payMidnight(instruction, onStatus);
16413
+ } else if (instruction.network === "arweave") {
16414
+ return this._payArweave(instruction, onStatus);
16415
+ } else if (instruction.network === "arweave_bundler") {
16416
+ return this._payArweaveBundler(instruction, onStatus);
16417
+ }
16418
+ throw Error(`Unsupported network: ${instruction.network}`);
16572
16419
  }
16420
+ // ── Network-specific wallet provider factories ────────────────────────────────
16573
16421
  /**
16574
- * Remove a reference from the on-chain registry.
16575
- * Only the owner of the reference can successfully call this — the chain
16576
- * enforces ownership via ZK proof using the witness-derived ownerSecret.
16422
+ * Returns a WalletProvider-compatible object for the Midnight chain whose
16423
+ * `balanceTx()` is fulfilled by the managed signing server instead of a
16424
+ * local wallet. The caller supplies the public keys (from whichever source
16425
+ * they have — HD derivation, a server endpoint, etc.); the SDK only handles
16426
+ * the signing-server round-trip.
16427
+ *
16428
+ * The returned object satisfies the @midnight-ntwrk/midnight-js-types
16429
+ * WalletProvider interface via structural typing — no Midnight package
16430
+ * import is required from the caller's side.
16431
+ *
16432
+ * @param coinPublicKey Midnight coin public key (hex) for the ZK fee circuit.
16433
+ * @param encryptionPublicKey Midnight encryption public key (hex).
16434
+ * @param onReceipt Optional callback invoked with the PaymentReceipt
16435
+ * after each successful balanceTx call. Used internally
16436
+ * by MidnightChainAdapter to capture receipt metadata;
16437
+ * external callers can omit it.
16577
16438
  */
16578
- async removeReference(refId) {
16579
- try {
16580
- if (!this.config.chainAdapter) {
16581
- throw new DStorageError(
16582
- CoreErrorCode.REMOVE_REF_REQUIRES_CHAIN,
16583
- "[dStorage] removeReference() requires a chain adapter."
16584
- );
16585
- }
16586
- if (typeof this.config.chainAdapter.removeReference !== "function") {
16587
- throw new DStorageError(
16588
- CoreErrorCode.REMOVE_REF_NOT_SUPPORTED,
16589
- "[dStorage] removeReference() is not supported by this chain adapter."
16439
+ createMidnightWalletProvider(coinPublicKey, encryptionPublicKey, onReceipt) {
16440
+ return {
16441
+ getCoinPublicKey: () => coinPublicKey,
16442
+ getEncryptionPublicKey: () => encryptionPublicKey,
16443
+ balanceTx: async (tx, _ttl) => {
16444
+ const txData = uint8ArrayToHex(tx.serialize());
16445
+ const receipt = await this.pay({
16446
+ network: "midnight",
16447
+ txData,
16448
+ dataSizeBytes: txData.length,
16449
+ expiresAt: Date.now() + 6e4
16450
+ });
16451
+ onReceipt?.(receipt);
16452
+ if (!receipt.signedTx) {
16453
+ throw new DStorageError(
16454
+ PaymentErrorCode.MANAGED_UNBALANCED_TX,
16455
+ "[dStorage/payment] Midnight managed payment server did not return a balanced transaction."
16456
+ );
16457
+ }
16458
+ const { Transaction } = await import("@midnight-ntwrk/midnight-js-protocol/ledger");
16459
+ return Transaction.deserialize(
16460
+ "signature",
16461
+ "proof",
16462
+ "binding",
16463
+ parseSignedTxBytes(receipt.signedTx)
16590
16464
  );
16591
16465
  }
16592
- const ownerSecret = await this._computeOwnerSecret(refId);
16593
- await this.config.chainAdapter.removeReference(refId, ownerSecret);
16594
- } catch (err) {
16595
- wrapError(err);
16596
- }
16466
+ };
16597
16467
  }
16468
+ // ── Network-specific payment handlers ────────────────────────────────────────
16598
16469
  /**
16599
- * Estimate the full cost (storage + chain) for a given payload size before committing.
16470
+ * Arweave remote-signing flow:
16471
+ * 1. Creates the Arweave transaction locally (data + tags, no local signing).
16472
+ * 2. Calls prepareChunks() to compute data_root (Merkle root) locally.
16473
+ * 3. Strips the raw data from the transaction before serialising — only the
16474
+ * data_root and data_size (not the actual bytes) are sent to the server.
16475
+ * 4. POSTs the stripped transaction JSON to the signing server.
16476
+ * 5. Applies the returned id, signature, and owner to the transaction.
16477
+ * 6. Restores the raw data so the chunked uploader can post it directly to
16478
+ * the Arweave gateway — the server never sees the file content.
16479
+ *
16480
+ * Falls back to the base-class pass-through if provideUploadData() was not called.
16600
16481
  */
16601
- async estimateCost(sizeBytes) {
16602
- try {
16603
- const [storageQuote, chainQuote] = await Promise.all([
16604
- this.config.storageAdapter.estimateCost(sizeBytes),
16605
- this.config.chainAdapter?.estimateCost()
16606
- ]);
16607
- return {
16608
- storageCost: { amount: storageQuote.amount, token: storageQuote.token },
16609
- ...chainQuote ? {
16610
- chainCost: { amount: chainQuote.amount, token: chainQuote.token }
16611
- } : {},
16612
- fileSizeBytes: sizeBytes
16613
- };
16614
- } catch (err) {
16615
- wrapError(err);
16482
+ async _payArweave(instruction, onStatus) {
16483
+ if (!this.pendingUpload) {
16484
+ return super.pay(instruction, onStatus);
16485
+ }
16486
+ const { data, metadata } = this.pendingUpload;
16487
+ this.pendingUpload = null;
16488
+ const arweave = await this.getClient();
16489
+ onStatus?.("Creating Arweave transaction\u2026");
16490
+ const tx = await arweave.createTransaction({ data });
16491
+ if (this.ownerPublicKey) {
16492
+ tx.owner = this.ownerPublicKey;
16616
16493
  }
16494
+ for (const [key, value] of Object.entries(metadata)) {
16495
+ tx.addTag(key, value);
16496
+ }
16497
+ await tx.prepareChunks(tx.data);
16498
+ const rawData = tx.data;
16499
+ tx.data = new Uint8Array(0);
16500
+ const body = JSON.stringify({
16501
+ txData: tx.toJSON(),
16502
+ network: instruction.network
16503
+ });
16504
+ const {
16505
+ txId,
16506
+ signature,
16507
+ publicKey,
16508
+ txAmount: arCost,
16509
+ serviceFee
16510
+ } = await this._sendSignTxRequest(body, instruction.network, onStatus);
16511
+ assertOwnerKeyMatch(publicKey, this.ownerPublicKey);
16512
+ onStatus?.("Applying remote signature\u2026");
16513
+ tx.setSignature({ id: txId, signature, owner: publicKey });
16514
+ tx.data = rawData;
16515
+ this.signedUpload = { tx, arCost };
16516
+ this.logger?.log(
16517
+ "dStorage/managed-payment",
16518
+ `Transaction signed remotely (${this.signingServerUrl} API service) \u2192 txId: ${txId} cost: ~${arCost} ${this.token} (service fee: ${serviceFee})`
16519
+ );
16520
+ return {
16521
+ txHash: txId,
16522
+ token: this.token,
16523
+ amount: arCost,
16524
+ type: this.type,
16525
+ paidAt: Date.now(),
16526
+ serviceFee
16527
+ };
16617
16528
  }
16618
16529
  /**
16619
- * Execute a full meta-transaction for a file:
16620
- * estimate encrypt pay storage upload pay chain → write reference
16621
- *
16622
- * Presents the entire flow as a single action with step-by-step progress.
16530
+ * Midnight payment flow:
16531
+ * POSTs the fee amount to the dStorage API service, which holds a funded
16532
+ * Midnight account and submits the chain transaction on behalf of the user.
16623
16533
  *
16624
- * @param file The File to encrypt and store
16625
- * @param onProgress Callback fired on every step state change
16626
- * @param config Optional token overrides and metadata
16627
- */
16628
- async executeMetaTransaction(file, onProgress, config = {}) {
16629
- try {
16630
- this.assertConnected();
16631
- if (this._encryptionProviders.length === 0) {
16632
- throw new DStorageError(
16633
- CoreErrorCode.META_TX_REQUIRES_ENCRYPTION,
16634
- "[dStorage] executeMetaTransaction() requires at least one encryption provider."
16635
- );
16636
- }
16637
- const metaTx = new MetaTx(this);
16638
- metaTx.onProgress(onProgress);
16639
- return metaTx.execute(file, config);
16640
- } catch (err) {
16641
- wrapError(err);
16642
- }
16643
- }
16644
- /**
16645
- * Generate a fresh random DEK, wrap it under the KEK(s), and return the crypto
16646
- * materials needed by MetaTx to encrypt payload and storageId.
16534
+ * POST {baseUrl}{POST_SIGN_TX_API_PATH}
16535
+ * { txData: string (hex-serialized UnboundTransaction), network: string, userId: string }
16536
+ * { txId: string, signature: string (hex-serialized FinalizedTransaction), ... }
16647
16537
  *
16648
- * For private stores: the DEK is wrapped under every available KEK. The ownerSecret
16649
- * can later be re-derived as HKDF(dek, rawStorageId) by any KEK holder.
16650
- * For public stores: a random ownerSeed is generated and wrapped under all KEKs (same pattern).
16538
+ * The hex-serialized FinalizedTransaction is returned in PaymentReceipt.signedTx
16539
+ * for the chain adapter to deserialize and pass to submitTx().
16651
16540
  */
16652
- async prepareUploadCrypto(isPublic = false) {
16653
- try {
16654
- if (this._encryptionProviders.length === 0) {
16655
- throw new DStorageError(
16656
- CoreErrorCode.PREPARE_UPLOAD_NO_PROVIDERS,
16657
- "[dStorage] Cannot prepare upload crypto: no encryption providers. Call init() first."
16658
- );
16659
- }
16660
- if (isPublic) {
16661
- const ownerSeed = generateDek();
16662
- const ownerSeedWrappers = await Promise.all(
16663
- this._encryptionProviders.map(async (provider) => ({
16664
- wrapKeyName: provider.name,
16665
- ...await provider.wrapDek(ownerSeed)
16666
- }))
16667
- );
16668
- const keyEnvelope2 = serializeKeyEnvelope(
16669
- buildKeyEnvelope(ownerSeedWrappers)
16670
- );
16671
- return {
16672
- uploadScheme: null,
16673
- keyEnvelope: keyEnvelope2,
16674
- encryptionScheme: "",
16675
- ownerSeed
16676
- };
16677
- }
16678
- const dek = generateDek();
16679
- const uploadScheme = XChaChaScheme.fromKey(dek);
16680
- const wrapperEntries = await Promise.all(
16681
- this._encryptionProviders.map(async (provider) => ({
16682
- wrapKeyName: provider.name,
16683
- ...await provider.wrapDek(dek)
16684
- }))
16685
- );
16686
- const keyEnvelope = serializeKeyEnvelope(
16687
- buildKeyEnvelope(wrapperEntries)
16688
- );
16689
- return {
16690
- uploadScheme,
16691
- keyEnvelope,
16692
- encryptionScheme: "xchacha20poly1305-v1",
16693
- dek
16694
- };
16695
- } catch (err) {
16696
- wrapError(err);
16697
- }
16541
+ async _payMidnight(instruction, onStatus) {
16542
+ const body = JSON.stringify({
16543
+ txData: instruction.txData ?? null,
16544
+ network: instruction.network
16545
+ });
16546
+ const {
16547
+ txId,
16548
+ signature,
16549
+ txAmount: dustCost,
16550
+ serviceFee
16551
+ } = await this._sendSignTxRequest(body, instruction.network, onStatus);
16552
+ this.logger?.log(
16553
+ "dStorage/managed-payment",
16554
+ `Midnight Tx signed remotely (${this.signingServerUrl} API service) \u2192 txId: ${txId} cost: ~${dustCost} ${this.token} (service fee: ${serviceFee})`
16555
+ );
16556
+ return {
16557
+ txHash: txId,
16558
+ token: "DUST",
16559
+ amount: dustCost,
16560
+ type: this.type,
16561
+ signedTx: signature,
16562
+ paidAt: Date.now(),
16563
+ serviceFee
16564
+ };
16698
16565
  }
16699
- // ── Private helpers ────────────────────────────────────────────────────────
16700
16566
  /**
16701
- * Upload large content by splitting into CHUNK_SIZE pieces, encrypting each
16702
- * independently, and storing a manifest that ties them together.
16703
- * Only the manifest's storageId is written on-chain.
16567
+ * Arweave Bundler
16704
16568
  */
16705
- async _uploadChunked(bytes, tags, onProgress, isPublic = false, rawMeta, refId) {
16706
- if (this._encryptionProviders.length === 0) {
16707
- throw new DStorageError(
16708
- CoreErrorCode.UPLOAD_NO_PROVIDERS,
16709
- "[dStorage] No encryption providers available. Call init() before uploading."
16710
- );
16711
- }
16712
- const totalChunks = Math.ceil(bytes.length / CHUNK_SIZE);
16713
- const sdkVersion = package_default.version ?? "unknown";
16714
- const encryptionScheme = isPublic ? "" : "xchacha20poly1305-v1";
16569
+ async _payArweaveBundler(instruction, onStatus) {
16570
+ const body = JSON.stringify({
16571
+ txData: instruction.txData ?? null,
16572
+ network: instruction.network,
16573
+ ...instruction.ownerKey !== void 0 ? { ownerKey: instruction.ownerKey } : {}
16574
+ });
16575
+ const {
16576
+ txId,
16577
+ signature,
16578
+ txAmount: arCost,
16579
+ serviceFee
16580
+ } = await this._sendSignTxRequest(body, instruction.network, onStatus);
16715
16581
  this.logger?.log(
16716
- "dStorage",
16717
- `Chunked upload: ${bytes.length} bytes \u2192 ${totalChunks} chunks${isPublic ? "" : `, encryption scheme: ${encryptionScheme}`}`
16582
+ "dStorage/managed-payment",
16583
+ `Arweave bundler Tx signed remotely (${this.signingServerUrl} API service) \u2192 txId: ${txId} cost: ~${arCost} ${this.token} (service fee: ${serviceFee})`
16718
16584
  );
16719
- let uploadScheme = null;
16720
- let keyEnvelope;
16721
- let chunkDek;
16722
- let ownerSeed;
16723
- if (!isPublic) {
16724
- chunkDek = generateDek();
16725
- uploadScheme = XChaChaScheme.fromKey(chunkDek);
16726
- const wrapperEntries = await Promise.all(
16727
- this._encryptionProviders.map(async (provider) => ({
16728
- wrapKeyName: provider.name,
16729
- ...await provider.wrapDek(chunkDek)
16730
- }))
16731
- );
16732
- keyEnvelope = serializeKeyEnvelope(buildKeyEnvelope(wrapperEntries));
16733
- } else {
16734
- ownerSeed = generateDek();
16735
- const ownerSeedWrappers = await Promise.all(
16736
- this._encryptionProviders.map(async (provider) => ({
16737
- wrapKeyName: provider.name,
16738
- ...await provider.wrapDek(ownerSeed)
16739
- }))
16740
- );
16741
- keyEnvelope = serializeKeyEnvelope(buildKeyEnvelope(ownerSeedWrappers));
16742
- }
16743
- const chunkEntries = [];
16744
- let bytesUploaded = 0;
16745
- for (let i = 0; i < totalChunks; i++) {
16746
- const start = i * CHUNK_SIZE;
16747
- const chunk = bytes.subarray(start, start + CHUNK_SIZE);
16748
- onProgress?.({
16749
- phase: "encrypting",
16750
- chunksUploaded: i,
16751
- totalChunks,
16752
- bytesUploaded,
16753
- totalBytes: bytes.length
16754
- });
16755
- let chunkData;
16756
- if (isPublic) {
16757
- chunkData = chunk;
16758
- } else {
16759
- chunkData = await uploadScheme.encryptPayload(
16760
- chunk,
16761
- chunkAad(i, totalChunks)
16762
- );
16763
- }
16764
- onProgress?.({
16765
- phase: "uploading",
16766
- chunksUploaded: i,
16767
- totalChunks,
16768
- bytesUploaded,
16769
- totalBytes: bytes.length
16770
- });
16771
- const chunkResult = await this.config.storageAdapter.store(chunkData, {
16772
- ...tags,
16773
- [TAG_SDK_VERSION]: sdkVersion,
16774
- [TAG_CONTENT_TYPE]: TAG_CONTENT_TYPE_OCTET_STREAM,
16775
- [TAG_CHUNK_INDEX]: String(i),
16776
- [TAG_CHUNK_TOTAL]: String(totalChunks)
16777
- });
16778
- chunkEntries.push({
16779
- index: i,
16780
- storageId: chunkResult.id,
16781
- size: chunk.length
16782
- });
16783
- bytesUploaded += chunk.length;
16784
- onProgress?.({
16785
- phase: "uploading",
16786
- chunksUploaded: i + 1,
16787
- totalChunks,
16788
- bytesUploaded,
16789
- totalBytes: bytes.length
16790
- });
16791
- }
16792
- onProgress?.({
16793
- phase: "finalizing",
16794
- chunksUploaded: totalChunks,
16795
- totalChunks,
16796
- bytesUploaded,
16797
- totalBytes: bytes.length
16798
- });
16799
- const manifest = {
16800
- v: 1,
16801
- type: "dstorage-chunked-manifest",
16802
- totalSize: bytes.length,
16803
- chunkSize: CHUNK_SIZE,
16804
- chunkCount: totalChunks,
16805
- provider: this.config.storageAdapter.name,
16806
- chunks: chunkEntries
16585
+ return {
16586
+ txHash: txId,
16587
+ token: "AR",
16588
+ amount: arCost,
16589
+ type: this.type,
16590
+ signedTx: signature,
16591
+ paidAt: Date.now(),
16592
+ serviceFee
16807
16593
  };
16808
- const manifestBytes = new TextEncoder().encode(JSON.stringify(manifest));
16809
- let manifestData;
16810
- if (isPublic) {
16811
- manifestData = manifestBytes;
16812
- } else {
16813
- manifestData = await uploadScheme.encryptPayload(
16814
- manifestBytes,
16815
- MANIFEST_AAD
16816
- );
16817
- }
16818
- const manifestResult = await this.config.storageAdapter.store(
16819
- manifestData,
16820
- {
16821
- [TAG_SDK_VERSION]: sdkVersion,
16822
- [TAG_CONTENT_TYPE]: isPublic ? TAG_CONTENT_TYPE_JSON : TAG_CONTENT_TYPE_OCTET_STREAM,
16823
- [TAG_MANIFEST_UPLOAD]: "true"
16824
- }
16825
- );
16826
- let onChainManifestId;
16827
- if (isPublic) {
16828
- onChainManifestId = manifestResult.id;
16829
- } else {
16830
- onChainManifestId = await uploadScheme.encryptStorageId(
16831
- manifestResult.id
16832
- );
16833
- }
16834
- const ownerSecret = chunkDek ? await deriveOwnerSecret(
16835
- chunkDek,
16836
- new TextEncoder().encode(manifestResult.id)
16837
- ) : await deriveOwnerSecret(
16838
- ownerSeed,
16839
- new TextEncoder().encode(manifestResult.id),
16840
- "dstorage:owner-secret:public:v1"
16594
+ }
16595
+ /**
16596
+ * Managed mock flow:
16597
+ * Sends a minimal request to the signing server with the literal network
16598
+ * identifier "TEST" — the server treats this as a sandbox/test flow with no
16599
+ * real funds involved. The SDK-side network name is "managedmock" to follow
16600
+ * the lowercase naming convention; the server-side string is always "TEST".
16601
+ */
16602
+ async _payManagedMock(instruction, onStatus) {
16603
+ const body = JSON.stringify({
16604
+ txData: instruction.txData || "mock",
16605
+ network: "TEST"
16606
+ });
16607
+ const { txId, txAmount, serviceFee } = await this._sendSignTxRequest(
16608
+ body,
16609
+ "TEST",
16610
+ onStatus
16841
16611
  );
16842
- if (this.config.chainAdapter) {
16843
- const contentHash = await computeBlake3Hex(manifestData);
16844
- const storeRecovery = {
16845
- storageId: manifestResult.id,
16846
- storageProvider: manifestResult.provider,
16847
- keyEnvelope: isPublic ? void 0 : keyEnvelope,
16848
- contentHash
16849
- };
16850
- onProgress?.({
16851
- phase: "stored",
16852
- chunksUploaded: totalChunks,
16853
- totalChunks,
16854
- bytesUploaded: bytes.length,
16855
- totalBytes: bytes.length,
16856
- recovery: storeRecovery
16857
- });
16858
- const encryptedMetadata = rawMeta && uploadScheme ? bytesToBase64url(
16859
- await uploadScheme.encryptPayload(
16860
- new TextEncoder().encode(JSON.stringify(rawMeta)),
16861
- METADATA_AAD
16862
- )
16863
- ) : void 0;
16864
- try {
16865
- const chainResult = await this.config.chainAdapter.writeReference({
16866
- storageId: onChainManifestId,
16867
- encryptionScheme,
16868
- storageProvider: manifestResult.provider,
16869
- ...refId !== void 0 ? { refId } : {},
16870
- ...encryptedMetadata !== void 0 ? { encryptedMetadata } : {},
16871
- writtenAt: Date.now(),
16872
- keyEnvelope,
16873
- contentHash,
16874
- ownerSecret
16875
- });
16876
- const resultRefId = chainResult.refId;
16877
- this.logger?.log(
16878
- "dStorage",
16879
- `Chunked upload complete \u2192 ${totalChunks} chunks + manifest, refId: ${resultRefId}`
16880
- );
16881
- return {
16882
- chainRefId: resultRefId,
16883
- storageId: manifestResult.id,
16884
- storageProvider: manifestResult.provider,
16885
- chainProvider: chainResult.provider,
16886
- uploadedAt: manifestResult.uploadedAt,
16887
- encryptionScheme,
16888
- costEstimate: {
16889
- storageCost: {
16890
- amount: manifestResult.cost?.amount ?? "unknown",
16891
- token: manifestResult.cost?.token ?? "AR"
16892
- },
16893
- chainCost: {
16894
- amount: chainResult.paymentReceipt?.amount ?? "unknown",
16895
- token: chainResult.paymentReceipt?.token ?? "DUST"
16896
- },
16897
- fileSizeBytes: bytes.length
16898
- }
16899
- };
16900
- } catch {
16901
- throw new StorePartialError(storeRecovery);
16902
- }
16903
- }
16904
16612
  this.logger?.log(
16905
- "dStorage",
16906
- `Chunked upload complete (storage-only) \u2192 ${totalChunks} chunks + manifest, storageId: ${manifestResult.id}`
16613
+ "dStorage/managed-payment",
16614
+ `ManagedMock Tx signed remotely (${this.signingServerUrl}) \u2192 txId: ${txId} cost: ~${txAmount} MOCK (service fee: ${serviceFee})`
16907
16615
  );
16908
16616
  return {
16909
- storageId: manifestResult.id,
16910
- storageProvider: manifestResult.provider,
16911
- uploadedAt: manifestResult.uploadedAt,
16912
- encryptionScheme,
16913
- costEstimate: {
16914
- storageCost: {
16915
- amount: manifestResult.cost?.amount ?? "unknown",
16916
- token: manifestResult.cost?.token ?? "AR"
16917
- },
16918
- fileSizeBytes: bytes.length
16919
- }
16617
+ txHash: txId,
16618
+ token: "MOCK",
16619
+ amount: txAmount,
16620
+ type: this.type,
16621
+ paidAt: Date.now(),
16622
+ serviceFee
16920
16623
  };
16921
16624
  }
16922
16625
  /**
16923
- * Reassemble a chunked file from its manifest.
16924
- * Fetches and decrypts each chunk sequentially to bound memory usage.
16626
+ * Shared HTTP helper: POSTs a transaction payload to the signing server and
16627
+ * returns the parsed SignTxResponse.
16628
+ *
16629
+ * - Arweave: txJson is the Arweave tx JSON object (from tx.toJSON()); the server
16630
+ * signs it and returns the signature + owner in the response fields.
16631
+ * - Midnight: txJson is the hex-serialized UnboundTransaction; the server balances
16632
+ * and signs it, returning the hex-serialized FinalizedTransaction in the
16633
+ * `signature` response field.
16634
+ *
16635
+ * Throws on network errors or non-2xx HTTP responses.
16925
16636
  */
16926
- async _retrieveChunked(manifest, scheme = null, tags = {}) {
16927
- if (manifest.chunks.length !== manifest.chunkCount) {
16637
+ async _sendSignTxRequest(body, network, onStatus) {
16638
+ onStatus?.(`Sending ${network} transaction to signing server\u2026`);
16639
+ let signResp;
16640
+ try {
16641
+ this.logger?.log(
16642
+ "dStorage/managed-payment",
16643
+ `${network}: delegating to managed payment service (serialized req length=${body.length})`
16644
+ );
16645
+ const signal = this.requestTimeoutMs > 0 ? AbortSignal.timeout(this.requestTimeoutMs) : void 0;
16646
+ signResp = await fetch(
16647
+ `${this.signingServerUrl}${POST_SIGN_TX_API_PATH}`,
16648
+ {
16649
+ method: "POST",
16650
+ headers: {
16651
+ "Content-Type": "application/json",
16652
+ Authorization: `Bearer ${this.authToken}`
16653
+ },
16654
+ body,
16655
+ ...signal !== void 0 ? { signal } : {}
16656
+ }
16657
+ );
16658
+ } catch (err) {
16928
16659
  throw new DStorageError(
16929
- CoreErrorCode.MANIFEST_CHUNK_COUNT_MISMATCH,
16930
- `[dStorage] Manifest declares ${manifest.chunkCount} chunks but contains ${manifest.chunks.length} entries`
16660
+ PaymentErrorCode.MANAGED_SERVER_UNREACHABLE,
16661
+ `[dStorage/managed-payment] Could not reach signing server: ${err}`,
16662
+ { cause: err }
16931
16663
  );
16932
16664
  }
16933
- const sorted = [...manifest.chunks].sort((a, b) => a.index - b.index);
16934
- for (let i = 0; i < sorted.length; i++) {
16935
- if (sorted[i].index !== i) {
16936
- throw new DStorageError(
16937
- CoreErrorCode.MANIFEST_NOT_CONTIGUOUS,
16938
- `[dStorage] Manifest chunk list is not contiguous: expected index ${i}, got ${sorted[i].index}`
16939
- );
16940
- }
16941
- }
16942
- const result = new Uint8Array(manifest.totalSize);
16943
- let offset = 0;
16944
- for (const entry of sorted) {
16945
- const { bytes: rawChunkBytes } = await this.config.storageAdapter.retrieve(entry.storageId);
16946
- let chunkBytes = rawChunkBytes;
16947
- if (scheme) {
16948
- try {
16949
- chunkBytes = await scheme.decryptPayload(
16950
- rawChunkBytes,
16951
- chunkAad(entry.index, manifest.chunkCount)
16952
- );
16953
- } catch (err) {
16954
- throw new DStorageError(
16955
- CoreErrorCode.DECRYPT_CHUNK_FAILED,
16956
- `[dStorage] Failed to decrypt chunk ${entry.index}: ${err}`,
16957
- { cause: err }
16958
- );
16959
- }
16960
- }
16961
- if (offset + chunkBytes.length > manifest.totalSize) {
16962
- throw new DStorageError(
16963
- CoreErrorCode.CHUNK_OVERFLOWS_TOTAL_SIZE,
16964
- `[dStorage] Chunk ${entry.index} overflows manifest totalSize`
16965
- );
16966
- }
16967
- result.set(chunkBytes, offset);
16968
- offset += chunkBytes.length;
16969
- this.logger?.log(
16970
- "dStorage",
16971
- `Retrieved chunk ${entry.index + 1}/${manifest.chunkCount}: ${chunkBytes.length} bytes`
16665
+ if (signResp.status === 409) {
16666
+ throw new DStorageError(
16667
+ PaymentErrorCode.MANAGED_KEY_CHANGED,
16668
+ "[dStorage/managed-payment] Signing server key has changed \u2014 re-issue your API token to obtain the updated public key."
16972
16669
  );
16973
16670
  }
16974
- if (offset !== manifest.totalSize) {
16671
+ if (!signResp.ok) {
16672
+ const rawBody = await signResp.text().catch(() => "");
16673
+ const body2 = sanitiseErrorBody(rawBody);
16975
16674
  throw new DStorageError(
16976
- CoreErrorCode.REASSEMBLY_SIZE_MISMATCH,
16977
- `[dStorage] Reassembled ${offset} bytes but manifest declares totalSize ${manifest.totalSize}`
16675
+ PaymentErrorCode.MANAGED_SIGN_HTTP_ERROR,
16676
+ `[dStorage/managed-payment] POST ${POST_SIGN_TX_API_PATH} failed: HTTP ${signResp.status}${body2 ? ` \u2014 ${body2}` : ""}`
16978
16677
  );
16979
16678
  }
16980
- return {
16981
- bytes: result,
16982
- metadata: {},
16983
- tags
16984
- };
16985
- }
16986
- assertConnected() {
16987
- if (!this._initialized && this.wallet === null) {
16679
+ let parsed;
16680
+ try {
16681
+ parsed = await signResp.json();
16682
+ } catch {
16988
16683
  throw new DStorageError(
16989
- CoreErrorCode.NOT_INITIALIZED,
16990
- "[dStorage] Not connected. Call dStorage.init() first."
16684
+ PaymentErrorCode.MANAGED_NON_JSON_RESPONSE,
16685
+ `[dStorage/managed-payment] Managed payment service returned a non-JSON response \u2014 check that the service URL is correct and reachable: ${this.signingServerUrl}${POST_SIGN_TX_API_PATH}`
16991
16686
  );
16992
16687
  }
16688
+ return parseSignTxResponse(parsed);
16993
16689
  }
16994
16690
  };
16995
16691
 
16996
- // src/index.publish.ts
16997
- init_dist();
16998
-
16999
- // ../encryption/dist/index.mjs
17000
- init_chunk_6IHXTW2Y();
17001
- init_chunk_NAJYQ4J6();
17002
- init_chunk_SW62VRE3();
17003
- init_chunk_WBLC4ONG();
17004
-
17005
- // src/index.publish.ts
17006
- init_dist();
17007
-
17008
- // ../storage/dist/chunk-URI7FNOW.mjs
17009
- var StorageErrorCode = {
17010
- // adapters/mock.ts (15000–15049)
17011
- MOCK_AUTH_TOKEN_REQUIRED: 15001,
17012
- MOCK_DATA_NOT_FOUND: 15002,
17013
- // adapters/integrity.ts (15050–15149)
17014
- CONTENT_HASH_MISMATCH: 15050,
17015
- // adapters/arweave-local.ts (15150–15199)
17016
- ARWEAVE_LOCAL_INVALID_ADDRESS: 15150,
17017
- ARWEAVE_LOCAL_NOT_INSTALLED: 15151,
17018
- // adapters/arweave.ts (15200–15399)
17019
- ARWEAVE_AUTH_TOKEN_REQUIRED: 15200,
17020
- ARWEAVE_NOT_INSTALLED: 15201,
17021
- ARWEAVE_NO_SIGNED_TX: 15202,
17022
- ARWEAVE_INVALID_TX_ID: 15203,
17023
- ARWEAVE_TX_TOO_LARGE: 15204,
17024
- ARWEAVE_TX_METADATA_FAILED: 15205,
17025
- ARWEAVE_NO_DATA_RETURNED: 15206,
17026
- ARWEAVE_SIZE_MISMATCH: 15207,
17027
- ARWEAVE_HASH_TAG_MISSING: 15208,
17028
- ARWEAVE_UPLOADER_INIT_FAILED: 15209,
17029
- ARWEAVE_CHUNK_UPLOAD_FAILED: 15210,
17030
- ARWEAVE_CONFIRM_TIMEOUT: 15211,
17031
- ARWEAVE_GET_DATA_FAILED: 15212,
17032
- // adapters/arweave-bundler.ts (15400–15599)
17033
- ARWEAVE_BUNDLER_AUTH_TOKEN_REQUIRED: 15400,
17034
- ARWEAVE_BUNDLER_LOCAL_NOT_SUPPORTED: 15401,
17035
- ARWEAVE_BUNDLER_INVALID_TX_ID: 15402,
17036
- ARWEAVE_BUNDLER_TX_METADATA_FAILED: 15403,
17037
- ARWEAVE_BUNDLER_HASH_TAG_MISSING: 15404,
17038
- ARWEAVE_BUNDLER_RETRIEVE_HTTP_ERROR: 15405,
17039
- ARWEAVE_BUNDLER_SIZE_LIMIT_EXCEEDED: 15406,
17040
- ARWEAVE_BUNDLER_SIZE_MISMATCH: 15407,
17041
- ARWEAVE_BUNDLER_COST_ESTIMATE_FAILED: 15408,
17042
- ARWEAVE_BUNDLER_NO_COST_ESTIMATE: 15409,
17043
- ARWEAVE_BUNDLER_INVALID_SIGN_TX_RESPONSE: 15410,
17044
- ARWEAVE_BUNDLER_INVALID_AUTH_TOKEN_FORMAT: 15411,
17045
- ARWEAVE_BUNDLER_INVALID_AUTH_TOKEN_KEY: 15412,
17046
- ARWEAVE_BUNDLER_NO_TX_ID_RETURNED: 15413,
17047
- ARWEAVE_BUNDLER_UPLOAD_FAILED: 15414
17048
- };
17049
-
17050
16692
  // ../storage/dist/chunk-BIT7XMIA.mjs
17051
16693
  init_dist();
17052
16694
  var memoryStore = /* @__PURE__ */ new Map();
@@ -19594,7 +19236,6 @@ var MidnightChainAdapter = class _MidnightChainAdapter {
19594
19236
  METADATA_AAD,
19595
19237
  METADATA_ONLY_OWNER_SECRET_SALT,
19596
19238
  ManagedPaymentAdapter,
19597
- MetaTx,
19598
19239
  MidnightChainAdapter,
19599
19240
  MidnightSimulatorChainAdapter,
19600
19241
  MnemonicEncryptionAdapter,
@@ -19617,7 +19258,6 @@ var MidnightChainAdapter = class _MidnightChainAdapter {
19617
19258
  computeBlake3Hex,
19618
19259
  decryptStorageIdXChaCha,
19619
19260
  decryptXChaCha,
19620
- defaultChainToken,
19621
19261
  deriveKek,
19622
19262
  deriveOwnerSecret,
19623
19263
  deriveSymmetricKeyFromSeed,