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