@bsv/wallet-toolbox-client 2.8.0 → 2.10.0
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/out/index.client.cjs +764 -401
- package/out/index.client.cjs.map +1 -1
- package/out/index.client.d.cts +252 -56
- package/out/index.client.d.cts.map +1 -1
- package/out/index.client.d.mts +252 -56
- package/out/index.client.d.mts.map +1 -1
- package/out/index.client.mjs +764 -403
- package/out/index.client.mjs.map +1 -1
- package/package.json +2 -2
package/out/index.client.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
-
import { AuthFetch, BEEF_V1, BEEF_V2, Beef, BeefParty, BigNumber, BlockHeadersService, CachedKeyDeriver, Certificate, Curve, Hash, LocalKVStore, LockingScript, LookupResolver, MasterCertificate, MerklePath, P2PKH, PrivateKey, ProtoWallet, PublicKey, PushDrop, RPuzzle, Random, SHIPBroadcaster, Script, ScriptEvaluationError, Signature, Spend, SymmetricKey, Telemetry, Transaction, TransactionSignature, Utils, Validation, Validation as Validation$1, VerifiableCertificate, createNonce, defaultHttpClient, verifyNonce } from "@bsv/sdk";
|
|
2
|
+
import { AuthFetch, BEEF_V1, BEEF_V2, Beef, BeefParty, BeefTx, BigNumber, BlockHeadersService, CachedKeyDeriver, Certificate, Curve, Hash, LocalKVStore, LockingScript, LookupResolver, MasterCertificate, MerklePath, P2PKH, PrivateKey, ProtoWallet, PublicKey, PushDrop, RPuzzle, Random, SHIPBroadcaster, Script, ScriptEvaluationError, Signature, Spend, SymmetricKey, Telemetry, Transaction, TransactionSignature, Utils, Validation, Validation as Validation$1, VerifiableCertificate, createNonce, defaultHttpClient, verifyNonce } from "@bsv/sdk";
|
|
3
3
|
import { deleteDB, openDB } from "idb";
|
|
4
4
|
import { AESGCM, AESGCMDecrypt } from "@bsv/sdk/primitives/AESGCM";
|
|
5
5
|
import argon2Api from "hash-wasm/dist/argon2.umd.min.js";
|
|
@@ -4046,7 +4046,7 @@ var EntitySyncState = class EntitySyncState extends EntityBase {
|
|
|
4046
4046
|
log += formatSyncSection("OUTPUTS", c.outputs, (r) => `${r.outputId} ${r.txid}.${r.vout} ${r.transactionId} ${r.spendable ? "spendable" : ""} sats:${r.satoshis}`);
|
|
4047
4047
|
return log;
|
|
4048
4048
|
}
|
|
4049
|
-
async processSyncChunk(writer, args, chunk) {
|
|
4049
|
+
async processSyncChunk(writer, args, chunk, trx) {
|
|
4050
4050
|
const mes = [
|
|
4051
4051
|
new MergeEntity(chunk.provenTxs, EntityProvenTx.mergeFind, this.syncMap.provenTx),
|
|
4052
4052
|
new MergeEntity(chunk.outputBaskets, EntityOutputBasket.mergeFind, this.syncMap.outputBasket),
|
|
@@ -4067,16 +4067,16 @@ var EntitySyncState = class EntitySyncState extends EntityBase {
|
|
|
4067
4067
|
let done = true;
|
|
4068
4068
|
if (chunk.user != null) {
|
|
4069
4069
|
const ei = chunk.user;
|
|
4070
|
-
const { found, eo } = await EntityUser.mergeFind(writer, this.userId, ei);
|
|
4070
|
+
const { found, eo } = await EntityUser.mergeFind(writer, this.userId, ei, trx);
|
|
4071
4071
|
if (found) {
|
|
4072
|
-
if (await eo.mergeExisting(writer, args.since, ei)) {
|
|
4072
|
+
if (await eo.mergeExisting(writer, args.since, ei, void 0, trx)) {
|
|
4073
4073
|
maxUpdated_at = maxDate(maxUpdated_at, ei.updated_at);
|
|
4074
4074
|
updates++;
|
|
4075
4075
|
}
|
|
4076
4076
|
}
|
|
4077
4077
|
}
|
|
4078
4078
|
for (const me of mes) {
|
|
4079
|
-
const r = await me.merge(args.since, writer, this.userId, this.syncMap);
|
|
4079
|
+
const r = await me.merge(args.since, writer, this.userId, this.syncMap, trx);
|
|
4080
4080
|
me.esm.count += me.stateArray?.length || 0;
|
|
4081
4081
|
updates += r.updates;
|
|
4082
4082
|
inserts += r.inserts;
|
|
@@ -4087,7 +4087,7 @@ var EntitySyncState = class EntitySyncState extends EntityBase {
|
|
|
4087
4087
|
this.when = maxUpdated_at;
|
|
4088
4088
|
for (const me of mes) me.esm.count = 0;
|
|
4089
4089
|
}
|
|
4090
|
-
await this.updateStorage(writer, false);
|
|
4090
|
+
await this.updateStorage(writer, false, trx);
|
|
4091
4091
|
return {
|
|
4092
4092
|
done,
|
|
4093
4093
|
maxUpdated_at,
|
|
@@ -8219,28 +8219,70 @@ function verifyActionBatchManifestDigest(manifest) {
|
|
|
8219
8219
|
//#endregion
|
|
8220
8220
|
//#region ../src/utility/beefForTxids.ts
|
|
8221
8221
|
/**
|
|
8222
|
-
* Return
|
|
8222
|
+
* Return a minimal BEEF when the source contains data outside the requested
|
|
8223
|
+
* transaction dependency closure. Return undefined when no pruning is needed.
|
|
8223
8224
|
*
|
|
8224
|
-
*
|
|
8225
|
-
*
|
|
8225
|
+
* This form lets forwarding clients retain the caller's original bytes in the
|
|
8226
|
+
* common no-op case instead of rebuilding and reserializing an equivalent BEEF.
|
|
8227
|
+
*/
|
|
8228
|
+
function pruneBeefForTxids(source, txids) {
|
|
8229
|
+
const selection = selectTransactions(source, txids);
|
|
8230
|
+
if (selection.transactions.size === source.txs.length && selection.bumpIndexes.size === source.bumps.length) return;
|
|
8231
|
+
return copySelection(source, selection);
|
|
8232
|
+
}
|
|
8233
|
+
/**
|
|
8234
|
+
* Return an independent minimal BEEF needed to prove the requested transactions.
|
|
8235
|
+
*
|
|
8236
|
+
* Transactions remain in source order and are sorted by Beef when serialized.
|
|
8237
|
+
* The source is indexed and walked once, using an explicit stack so a hostile
|
|
8238
|
+
* dependency depth cannot exhaust the JavaScript call stack.
|
|
8226
8239
|
*/
|
|
8227
8240
|
function beefForTxids(source, txids) {
|
|
8228
|
-
|
|
8241
|
+
return copySelection(source, selectTransactions(source, txids));
|
|
8242
|
+
}
|
|
8243
|
+
function selectTransactions(source, txids) {
|
|
8244
|
+
const byTxid = /* @__PURE__ */ new Map();
|
|
8245
|
+
for (const tx of source.txs) byTxid.set(tx.txid, tx);
|
|
8246
|
+
const transactions = /* @__PURE__ */ new Set();
|
|
8247
|
+
const bumpIndexes = /* @__PURE__ */ new Set();
|
|
8229
8248
|
const visited = /* @__PURE__ */ new Set();
|
|
8230
|
-
const
|
|
8231
|
-
|
|
8249
|
+
const stack = [...txids];
|
|
8250
|
+
while (stack.length > 0) {
|
|
8251
|
+
const txid = stack.pop();
|
|
8252
|
+
if (txid == null || visited.has(txid)) continue;
|
|
8232
8253
|
visited.add(txid);
|
|
8233
|
-
const
|
|
8234
|
-
if (
|
|
8235
|
-
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8254
|
+
const tx = byTxid.get(txid);
|
|
8255
|
+
if (tx == null) continue;
|
|
8256
|
+
transactions.add(tx);
|
|
8257
|
+
const bumpIndex = tx.bumpIndex;
|
|
8258
|
+
if (bumpIndex != null && Number.isSafeInteger(bumpIndex) && bumpIndex >= 0 && bumpIndex < source.bumps.length) bumpIndexes.add(bumpIndex);
|
|
8259
|
+
for (const inputTxid of tx.inputTxids) if (!visited.has(inputTxid)) stack.push(inputTxid);
|
|
8260
|
+
}
|
|
8261
|
+
return {
|
|
8262
|
+
transactions,
|
|
8263
|
+
bumpIndexes
|
|
8240
8264
|
};
|
|
8241
|
-
|
|
8265
|
+
}
|
|
8266
|
+
function copySelection(source, selection) {
|
|
8267
|
+
const beef = new Beef(source.version);
|
|
8268
|
+
const bumpIndexMap = /* @__PURE__ */ new Map();
|
|
8269
|
+
for (let index = 0; index < source.bumps.length; index++) {
|
|
8270
|
+
if (!selection.bumpIndexes.has(index)) continue;
|
|
8271
|
+
bumpIndexMap.set(index, beef.bumps.length);
|
|
8272
|
+
beef.bumps.push(cloneMerklePath(source.bumps[index]));
|
|
8273
|
+
}
|
|
8274
|
+
for (const sourceTx of source.txs) {
|
|
8275
|
+
if (!selection.transactions.has(sourceTx)) continue;
|
|
8276
|
+
const bumpIndex = sourceTx.bumpIndex == null ? void 0 : bumpIndexMap.get(sourceTx.bumpIndex);
|
|
8277
|
+
const rawTx = sourceTx.rawTxUint8Array;
|
|
8278
|
+
const copy = rawTx == null ? BeefTx.fromTxid(sourceTx.txid, bumpIndex) : new BeefTx(Uint8Array.from(rawTx), bumpIndex, Array.from(sourceTx.inputTxids));
|
|
8279
|
+
beef.txs.push(copy);
|
|
8280
|
+
}
|
|
8242
8281
|
return beef;
|
|
8243
8282
|
}
|
|
8283
|
+
function cloneMerklePath(source) {
|
|
8284
|
+
return new MerklePath(source.blockHeight, source.path.map((level) => level.map((leaf) => ({ ...leaf }))), false, false);
|
|
8285
|
+
}
|
|
8244
8286
|
//#endregion
|
|
8245
8287
|
//#region ../src/storage/methods/offsetKey.ts
|
|
8246
8288
|
function keyOffsetToHashedSecret(pub, keyOffset) {
|
|
@@ -10938,7 +10980,7 @@ function validateRequiredOutputs(storage, userId, vargs) {
|
|
|
10938
10980
|
* @returns {xinputs} extended validated required inputs.
|
|
10939
10981
|
*/
|
|
10940
10982
|
async function validateRequiredInputs(storage, userId, vargs) {
|
|
10941
|
-
|
|
10983
|
+
let beef = new Beef();
|
|
10942
10984
|
if (vargs.inputs.length === 0) return {
|
|
10943
10985
|
storageBeef: beef,
|
|
10944
10986
|
beef,
|
|
@@ -10964,6 +11006,7 @@ async function validateRequiredInputs(storage, userId, vargs) {
|
|
|
10964
11006
|
inputsByTxid[input.outpoint.txid] ||= [];
|
|
10965
11007
|
inputsByTxid[input.outpoint.txid].push(input);
|
|
10966
11008
|
}
|
|
11009
|
+
beef = beefForTxids(beef, Object.keys(inputsByTxid));
|
|
10967
11010
|
const localKnownInputTxids = {};
|
|
10968
11011
|
for (const [txid, txInputs] of Object.entries(inputsByTxid)) localKnownInputTxids[txid] = txInputs.every((input) => {
|
|
10969
11012
|
const output = preloadedOutputsByOutpoint[`${input.outpoint.txid}.${input.outpoint.vout}`];
|
|
@@ -15225,11 +15268,19 @@ var StorageProvider = class StorageProvider extends StorageReaderWriter {
|
|
|
15225
15268
|
return await this.updateOutput(output.outputId, { basketId: void 0 });
|
|
15226
15269
|
}
|
|
15227
15270
|
async processSyncChunk(args, chunk) {
|
|
15228
|
-
|
|
15229
|
-
|
|
15230
|
-
|
|
15231
|
-
|
|
15232
|
-
|
|
15271
|
+
return await this.transaction(async (trx) => {
|
|
15272
|
+
const user = verifyTruthy(verifyOneOrNone(await this.findUsers({
|
|
15273
|
+
partial: { identityKey: args.identityKey },
|
|
15274
|
+
trx
|
|
15275
|
+
})));
|
|
15276
|
+
return await new EntitySyncState(verifyOne(await this.findSyncStates({
|
|
15277
|
+
partial: {
|
|
15278
|
+
storageIdentityKey: args.fromStorageIdentityKey,
|
|
15279
|
+
userId: user.userId
|
|
15280
|
+
},
|
|
15281
|
+
trx
|
|
15282
|
+
}))).processSyncChunk(this, args, chunk, trx);
|
|
15283
|
+
});
|
|
15233
15284
|
}
|
|
15234
15285
|
/**
|
|
15235
15286
|
* Handles storage changes when a valid MerklePath and mined block header are found for a ProvenTxReq txid.
|
|
@@ -17294,8 +17345,10 @@ var StorageIdb = class extends StorageProvider {
|
|
|
17294
17345
|
await tx.done;
|
|
17295
17346
|
return r;
|
|
17296
17347
|
} catch (err) {
|
|
17297
|
-
|
|
17298
|
-
|
|
17348
|
+
try {
|
|
17349
|
+
tx.abort();
|
|
17350
|
+
await tx.done;
|
|
17351
|
+
} catch {}
|
|
17299
17352
|
throw err;
|
|
17300
17353
|
}
|
|
17301
17354
|
}
|
|
@@ -18262,6 +18315,25 @@ var StorageClientBase = class {
|
|
|
18262
18315
|
* @returns `StorageCreateActionResults` supporting additional wallet processing to yield `createAction` results.
|
|
18263
18316
|
*/
|
|
18264
18317
|
async createAction(auth, args) {
|
|
18318
|
+
if (args.inputBEEF != null) if (args.inputs.length === 0) args = {
|
|
18319
|
+
...args,
|
|
18320
|
+
inputBEEF: void 0
|
|
18321
|
+
};
|
|
18322
|
+
else {
|
|
18323
|
+
let source;
|
|
18324
|
+
try {
|
|
18325
|
+
source = Beef.fromBinary(args.inputBEEF);
|
|
18326
|
+
} catch {
|
|
18327
|
+
source = void 0;
|
|
18328
|
+
}
|
|
18329
|
+
if (source != null) {
|
|
18330
|
+
const pruned = pruneBeefForTxids(source, args.inputs.map((input) => input.outpoint.txid));
|
|
18331
|
+
if (pruned != null) args = {
|
|
18332
|
+
...args,
|
|
18333
|
+
inputBEEF: pruned.toBinary()
|
|
18334
|
+
};
|
|
18335
|
+
}
|
|
18336
|
+
}
|
|
18265
18337
|
return await this.rpcCall("createAction", [auth, args]);
|
|
18266
18338
|
}
|
|
18267
18339
|
/**
|
|
@@ -19562,6 +19634,24 @@ function isLiveBlockHeader(header) {
|
|
|
19562
19634
|
return "chainWork" in header && typeof header.previousHash === "string";
|
|
19563
19635
|
}
|
|
19564
19636
|
//#endregion
|
|
19637
|
+
//#region ../src/services/chaintracker/chaintracks/Api/BulkFileDataValidatorApi.ts
|
|
19638
|
+
/**
|
|
19639
|
+
* Identifies deterministic rejection of the supplied immutable bytes.
|
|
19640
|
+
* Operational failures such as worker crashes and queue saturation deliberately
|
|
19641
|
+
* use ordinary errors so callers preserve the cache entry and avoid downloading
|
|
19642
|
+
* a replacement that cannot be validated.
|
|
19643
|
+
*
|
|
19644
|
+
* @public
|
|
19645
|
+
*/
|
|
19646
|
+
var BulkFileDataValidationError = class extends Error {
|
|
19647
|
+
data;
|
|
19648
|
+
constructor(message, data) {
|
|
19649
|
+
super(message);
|
|
19650
|
+
this.data = data;
|
|
19651
|
+
this.name = "BulkFileDataValidationError";
|
|
19652
|
+
}
|
|
19653
|
+
};
|
|
19654
|
+
//#endregion
|
|
19565
19655
|
//#region ../src/services/chaintracker/chaintracks/util/HeightRange.ts
|
|
19566
19656
|
/**
|
|
19567
19657
|
* Represents a range of block heights.
|
|
@@ -19780,6 +19870,8 @@ var Chaintracks = class {
|
|
|
19780
19870
|
lastPresentHeight = -1;
|
|
19781
19871
|
lastPresentHeightMsecs = 0;
|
|
19782
19872
|
lastPresentHeightMaxAge = 60 * 1e3;
|
|
19873
|
+
presentHeightRefresh;
|
|
19874
|
+
mainLoopHeartbeatMsecs = 0;
|
|
19783
19875
|
lock = new SingleWriterMultiReaderLock();
|
|
19784
19876
|
sourceStatus = /* @__PURE__ */ new Map();
|
|
19785
19877
|
constructor(options) {
|
|
@@ -19817,12 +19909,31 @@ var Chaintracks = class {
|
|
|
19817
19909
|
return this.chain;
|
|
19818
19910
|
}
|
|
19819
19911
|
/**
|
|
19820
|
-
*
|
|
19821
|
-
*
|
|
19912
|
+
* Returns the last known valid height immediately and refreshes stale state
|
|
19913
|
+
* once in the background. Cold start waits for the single shared refresh.
|
|
19822
19914
|
*/
|
|
19823
19915
|
async getPresentHeight() {
|
|
19824
19916
|
const now = Date.now();
|
|
19825
19917
|
if (this.lastPresentHeight >= 0 && now - this.lastPresentHeightMsecs < this.lastPresentHeightMaxAge) return this.lastPresentHeight;
|
|
19918
|
+
if (this.lastPresentHeight >= 0) {
|
|
19919
|
+
this.refreshPresentHeight().catch((error) => {
|
|
19920
|
+
this.log(`Background present-height refresh failed: ${WalletError.fromUnknown(error).message}`);
|
|
19921
|
+
});
|
|
19922
|
+
return this.lastPresentHeight;
|
|
19923
|
+
}
|
|
19924
|
+
return await this.refreshPresentHeight();
|
|
19925
|
+
}
|
|
19926
|
+
async refreshPresentHeight() {
|
|
19927
|
+
if (this.presentHeightRefresh != null) return await this.presentHeightRefresh;
|
|
19928
|
+
const refresh = this.loadPresentHeight();
|
|
19929
|
+
this.presentHeightRefresh = refresh;
|
|
19930
|
+
try {
|
|
19931
|
+
return await refresh;
|
|
19932
|
+
} finally {
|
|
19933
|
+
if (this.presentHeightRefresh === refresh) this.presentHeightRefresh = void 0;
|
|
19934
|
+
}
|
|
19935
|
+
}
|
|
19936
|
+
async loadPresentHeight() {
|
|
19826
19937
|
for (const [index, bulk] of this.bulkIngestors.entries()) {
|
|
19827
19938
|
const source = this.sourceName("bulk", index, bulk);
|
|
19828
19939
|
try {
|
|
@@ -19830,7 +19941,7 @@ var Chaintracks = class {
|
|
|
19830
19941
|
if (presentHeight != null && Number.isInteger(presentHeight) && presentHeight >= 0) {
|
|
19831
19942
|
this.markSourceSuccess(source, "bulk");
|
|
19832
19943
|
this.lastPresentHeight = presentHeight;
|
|
19833
|
-
this.lastPresentHeightMsecs = now;
|
|
19944
|
+
this.lastPresentHeightMsecs = Date.now();
|
|
19834
19945
|
return presentHeight;
|
|
19835
19946
|
}
|
|
19836
19947
|
} catch (uerr) {
|
|
@@ -19845,7 +19956,7 @@ var Chaintracks = class {
|
|
|
19845
19956
|
const localHeight = Math.max(ranges.bulk.maxHeight, ranges.live.maxHeight);
|
|
19846
19957
|
if (localHeight >= 0) {
|
|
19847
19958
|
this.lastPresentHeight = localHeight;
|
|
19848
|
-
this.lastPresentHeightMsecs = now;
|
|
19959
|
+
this.lastPresentHeightMsecs = Date.now();
|
|
19849
19960
|
return localHeight;
|
|
19850
19961
|
}
|
|
19851
19962
|
} catch (error) {
|
|
@@ -19856,6 +19967,19 @@ var Chaintracks = class {
|
|
|
19856
19967
|
async currentHeight() {
|
|
19857
19968
|
return await this.getPresentHeight();
|
|
19858
19969
|
}
|
|
19970
|
+
/** Returns local process state without locks, storage reads, or network I/O. */
|
|
19971
|
+
getAvailabilitySnapshot() {
|
|
19972
|
+
return {
|
|
19973
|
+
available: this.available,
|
|
19974
|
+
startupError: this.startupError?.message,
|
|
19975
|
+
presentHeight: this.lastPresentHeight >= 0 ? this.lastPresentHeight : void 0,
|
|
19976
|
+
presentHeightUpdatedAt: this.lastPresentHeightMsecs > 0 ? new Date(this.lastPresentHeightMsecs).toISOString() : void 0,
|
|
19977
|
+
presentHeightRefreshInFlight: this.presentHeightRefresh != null,
|
|
19978
|
+
mainLoopHeartbeatAt: this.mainLoopHeartbeatMsecs > 0 ? new Date(this.mainLoopHeartbeatMsecs).toISOString() : void 0,
|
|
19979
|
+
sources: Array.from(this.sourceStatus.values()).map((status) => ({ ...status })),
|
|
19980
|
+
bulkData: this.storage.bulkManager.getStats()
|
|
19981
|
+
};
|
|
19982
|
+
}
|
|
19859
19983
|
async subscribeHeaders(listener) {
|
|
19860
19984
|
const ID = randomBytesBase64(8);
|
|
19861
19985
|
this.callbacks.header[ID] = listener;
|
|
@@ -19920,6 +20044,7 @@ var Chaintracks = class {
|
|
|
19920
20044
|
for (const liveIn of this.liveIngestors) await liveIn.shutdown();
|
|
19921
20045
|
for (const bulkIn of this.bulkIngestors) await bulkIn.shutdown();
|
|
19922
20046
|
await Promise.all(this.promises);
|
|
20047
|
+
await this.storage.bulkManager.destroy();
|
|
19923
20048
|
await this.storage.destroy();
|
|
19924
20049
|
this.available = false;
|
|
19925
20050
|
this.stopMainThread = false;
|
|
@@ -20201,9 +20326,11 @@ var Chaintracks = class {
|
|
|
20201
20326
|
const syncCheckRepeatMsecs = 1800 * 1e3;
|
|
20202
20327
|
while (!this.stopMainThread) try {
|
|
20203
20328
|
const now = Date.now();
|
|
20329
|
+
this.mainLoopHeartbeatMsecs = now;
|
|
20204
20330
|
lastSyncCheck = now;
|
|
20205
20331
|
lastBulkSync = await this.runBulkSyncIfNeeded(now, lastBulkSync, cdnSyncRepeatMsecs);
|
|
20206
20332
|
await this.processLiveHeaderQueue(lastSyncCheck, syncCheckRepeatMsecs);
|
|
20333
|
+
this.mainLoopHeartbeatMsecs = Date.now();
|
|
20207
20334
|
} catch (error_) {
|
|
20208
20335
|
const e = WalletError.fromUnknown(error_);
|
|
20209
20336
|
if (this.available) this.log(`Error occurred during chaintracks main thread processing: ${e.stack || e.message}`);
|
|
@@ -20215,7 +20342,7 @@ var Chaintracks = class {
|
|
|
20215
20342
|
}
|
|
20216
20343
|
/** Returns (potentially updated) lastBulkSync timestamp. */
|
|
20217
20344
|
async runBulkSyncIfNeeded(now, lastBulkSync, cdnSyncRepeatMsecs) {
|
|
20218
|
-
const presentHeight = await this.
|
|
20345
|
+
const presentHeight = await this.refreshPresentHeight();
|
|
20219
20346
|
const before = await this.storage.getAvailableHeightRanges();
|
|
20220
20347
|
let skipBulkSync = !before.live.isEmpty && before.live.maxHeight >= presentHeight - this.addLiveRecursionLimit / 2;
|
|
20221
20348
|
if (skipBulkSync && now - lastBulkSync > cdnSyncRepeatMsecs) skipBulkSync = false;
|
|
@@ -21364,12 +21491,12 @@ var ChaintracksFetch = class {
|
|
|
21364
21491
|
this.maxRetryMsecs = positiveSafeInteger(options.maxRetryMsecs, DEFAULT_MAX_RETRY_MSECS, "maxRetryMsecs");
|
|
21365
21492
|
this.random = options.random ?? Math.random;
|
|
21366
21493
|
}
|
|
21367
|
-
async download(url, maxResponseBytes) {
|
|
21494
|
+
async download(url, maxResponseBytes, options) {
|
|
21368
21495
|
const responseLimit = maxResponseBytes == null ? this.maxResponseBytes : Math.min(this.maxResponseBytes, positiveSafeInteger(maxResponseBytes, this.maxResponseBytes, "maxResponseBytes"));
|
|
21369
21496
|
return await this.requestBytes(url, {
|
|
21370
21497
|
method: "GET",
|
|
21371
21498
|
headers: { Accept: "application/octet-stream" }
|
|
21372
|
-
}, "download", responseLimit);
|
|
21499
|
+
}, "download", responseLimit, options);
|
|
21373
21500
|
}
|
|
21374
21501
|
async fetchJson(url) {
|
|
21375
21502
|
const bytes = await this.requestBytes(url, {
|
|
@@ -21378,8 +21505,9 @@ var ChaintracksFetch = class {
|
|
|
21378
21505
|
}, "fetch JSON", this.maxResponseBytes);
|
|
21379
21506
|
return JSON.parse(new TextDecoder().decode(bytes));
|
|
21380
21507
|
}
|
|
21381
|
-
async requestBytes(url, init, kind, maxResponseBytes) {
|
|
21508
|
+
async requestBytes(url, init, kind, maxResponseBytes, downloadOptions) {
|
|
21382
21509
|
for (let retry = 0;; retry++) {
|
|
21510
|
+
if (retry > 0) await downloadOptions?.beforeRetry?.(retry + 1);
|
|
21383
21511
|
const controller = new AbortController();
|
|
21384
21512
|
const timeout = setTimeout(() => controller.abort(), this.timeoutMsecs);
|
|
21385
21513
|
try {
|
|
@@ -21455,6 +21583,38 @@ var ChaintracksFetch = class {
|
|
|
21455
21583
|
}
|
|
21456
21584
|
};
|
|
21457
21585
|
//#endregion
|
|
21586
|
+
//#region ../src/services/chaintracker/chaintracks/util/InlineBulkFileDataValidator.ts
|
|
21587
|
+
/**
|
|
21588
|
+
* Portable complete-object validator. Node services should normally inject
|
|
21589
|
+
* `NodeBulkFileDataValidator`; browser and mobile consumers retain this
|
|
21590
|
+
* dependency-free fallback.
|
|
21591
|
+
*
|
|
21592
|
+
* @public
|
|
21593
|
+
*/
|
|
21594
|
+
var InlineBulkFileDataValidator = class {
|
|
21595
|
+
async validate(request) {
|
|
21596
|
+
try {
|
|
21597
|
+
const expectedLength = request.count * 80;
|
|
21598
|
+
if (request.data.length !== expectedLength) throw new WERR_INVALID_PARAMETER("file.data", `bulk file ${request.fileName} data length ${request.data.length} does not match expected count ${request.count}`);
|
|
21599
|
+
const fileHash = asString(Hash.sha256(asArray(request.data)), "base64");
|
|
21600
|
+
if (request.fileHash != null && fileHash !== request.fileHash) throw new WERR_INVALID_PARAMETER("fileHash", `a match for retrieved data for ${request.fileName}`);
|
|
21601
|
+
const { lastHeaderHash, lastChainWork } = validateBufferOfHeaders(request.data, request.prevHash, 0, request.count, request.prevChainWork);
|
|
21602
|
+
if (request.lastHash && request.lastHash !== lastHeaderHash) throw new WERR_INVALID_PARAMETER("file.lastHash", `expected ${request.lastHash} but got ${lastHeaderHash}`);
|
|
21603
|
+
if (request.lastChainWork && request.lastChainWork !== lastChainWork) throw new WERR_INVALID_PARAMETER("file.lastChainWork", `expected ${request.lastChainWork} but got ${lastChainWork}`);
|
|
21604
|
+
if (request.firstHeight === 0 && request.chain != null) validateGenesisHeader(request.data, request.chain);
|
|
21605
|
+
return {
|
|
21606
|
+
data: request.data,
|
|
21607
|
+
fileHash,
|
|
21608
|
+
lastHeaderHash,
|
|
21609
|
+
lastChainWork
|
|
21610
|
+
};
|
|
21611
|
+
} catch (error) {
|
|
21612
|
+
if (error instanceof BulkFileDataValidationError) throw error;
|
|
21613
|
+
throw new BulkFileDataValidationError(error instanceof Error ? error.message : String(error), request.data);
|
|
21614
|
+
}
|
|
21615
|
+
}
|
|
21616
|
+
};
|
|
21617
|
+
//#endregion
|
|
21458
21618
|
//#region ../src/services/chaintracker/chaintracks/util/BulkFileDataManager.ts
|
|
21459
21619
|
/**
|
|
21460
21620
|
* Manages bulk file data (typically 8MB chunks of 100,000 headers each).
|
|
@@ -21480,6 +21640,7 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
21480
21640
|
fileHashToIndex = {};
|
|
21481
21641
|
lock = new SingleWriterMultiReaderLock();
|
|
21482
21642
|
inFlightLoads = /* @__PURE__ */ new Map();
|
|
21643
|
+
failedLoads = /* @__PURE__ */ new Map();
|
|
21483
21644
|
storage;
|
|
21484
21645
|
stats = {
|
|
21485
21646
|
memoryHits: 0,
|
|
@@ -21489,7 +21650,8 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
21489
21650
|
persistentCacheRejects: 0,
|
|
21490
21651
|
coalescedLoads: 0,
|
|
21491
21652
|
downloads: 0,
|
|
21492
|
-
downloadedBytes: 0
|
|
21653
|
+
downloadedBytes: 0,
|
|
21654
|
+
loadBackoffs: 0
|
|
21493
21655
|
};
|
|
21494
21656
|
chain;
|
|
21495
21657
|
maxPerFile;
|
|
@@ -21498,6 +21660,8 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
21498
21660
|
fromKnownSourceUrl;
|
|
21499
21661
|
cache;
|
|
21500
21662
|
downloadBudget;
|
|
21663
|
+
validator;
|
|
21664
|
+
failedLoadRetryMsecs;
|
|
21501
21665
|
constructor(options) {
|
|
21502
21666
|
const resolvedOptions = typeof options === "object" ? options : BulkFileDataManager.createDefaultOptions(options);
|
|
21503
21667
|
this.chain = resolvedOptions.chain;
|
|
@@ -21507,10 +21671,17 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
21507
21671
|
this.fetch = resolvedOptions.fetch;
|
|
21508
21672
|
this.cache = resolvedOptions.cache;
|
|
21509
21673
|
this.downloadBudget = resolvedOptions.downloadBudget;
|
|
21674
|
+
this.validator = resolvedOptions.validator ?? new InlineBulkFileDataValidator();
|
|
21675
|
+
this.failedLoadRetryMsecs = resolvedOptions.failedLoadRetryMsecs ?? 30 * 1e3;
|
|
21676
|
+
if (!Number.isSafeInteger(this.failedLoadRetryMsecs) || this.failedLoadRetryMsecs < 0) throw new WERR_INVALID_PARAMETER("failedLoadRetryMsecs", "a non-negative safe integer");
|
|
21510
21677
|
this.deleteBulkFilesNoLock();
|
|
21511
21678
|
}
|
|
21512
21679
|
getStats() {
|
|
21513
|
-
return {
|
|
21680
|
+
return {
|
|
21681
|
+
...this.stats,
|
|
21682
|
+
validation: this.validator.getStats?.(),
|
|
21683
|
+
downloadBudget: this.downloadBudget?.snapshot?.()
|
|
21684
|
+
};
|
|
21514
21685
|
}
|
|
21515
21686
|
async deleteBulkFiles() {
|
|
21516
21687
|
return await this.lock.withWriteLock(async () => this.deleteBulkFilesNoLock());
|
|
@@ -21729,9 +21900,29 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
21729
21900
|
});
|
|
21730
21901
|
}
|
|
21731
21902
|
async getDataFromFile(file, offset, length) {
|
|
21732
|
-
const
|
|
21733
|
-
|
|
21734
|
-
|
|
21903
|
+
const resolved = await this.lock.withReadLock(async () => {
|
|
21904
|
+
const resolved = this.getBfdForHeight(file.firstHeight);
|
|
21905
|
+
if (resolved == null || resolved.count < file.count) throw new WERR_INVALID_PARAMETER("file", `a match for ${file.firstHeight}, ${file.count} in the BulkFileDataManager.`);
|
|
21906
|
+
return {
|
|
21907
|
+
current: resolved,
|
|
21908
|
+
snapshot: snapshotBfd(resolved)
|
|
21909
|
+
};
|
|
21910
|
+
});
|
|
21911
|
+
return await this.getDataFromSnapshot(resolved.current, resolved.snapshot, offset, length);
|
|
21912
|
+
}
|
|
21913
|
+
async getDataFromSnapshot(original, snapshot, offset, length) {
|
|
21914
|
+
const data = await this.getDataFromFileNoLock(snapshot, offset, length);
|
|
21915
|
+
if (snapshot.data != null) await this.lock.withWriteLock(async () => {
|
|
21916
|
+
if (this.bfds.includes(original) && original.fileHash === snapshot.fileHash && original.firstHeight === snapshot.firstHeight && original.count === snapshot.count) {
|
|
21917
|
+
original.data = snapshot.data;
|
|
21918
|
+
original.validated = true;
|
|
21919
|
+
original.lastHash = snapshot.lastHash;
|
|
21920
|
+
original.lastChainWork = snapshot.lastChainWork;
|
|
21921
|
+
original.mru = Date.now();
|
|
21922
|
+
this.ensureMaxRetained();
|
|
21923
|
+
}
|
|
21924
|
+
});
|
|
21925
|
+
return data;
|
|
21735
21926
|
}
|
|
21736
21927
|
async getDataFromFileNoLock(bfd, offset, length) {
|
|
21737
21928
|
const fileLength = bfd.count * 80;
|
|
@@ -21742,15 +21933,19 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
21742
21933
|
return (await this.ensureData(bfd)).slice(offset, offset + length);
|
|
21743
21934
|
}
|
|
21744
21935
|
async findHeaderForHeightOrUndefined(height) {
|
|
21745
|
-
|
|
21936
|
+
const resolved = await this.lock.withReadLock(async () => {
|
|
21746
21937
|
if (!Number.isInteger(height) || height < 0) throw new WERR_INVALID_PARAMETER("height", `a non-negative integer (${height}).`);
|
|
21747
21938
|
const file = this.bfds.find((f) => f.firstHeight <= height && f.firstHeight + f.count > height);
|
|
21748
21939
|
if (file == null) return void 0;
|
|
21749
|
-
|
|
21750
|
-
|
|
21751
|
-
|
|
21752
|
-
|
|
21940
|
+
return {
|
|
21941
|
+
current: file,
|
|
21942
|
+
snapshot: snapshotBfd(file),
|
|
21943
|
+
offset: (height - file.firstHeight) * 80
|
|
21944
|
+
};
|
|
21753
21945
|
});
|
|
21946
|
+
if (resolved == null) return void 0;
|
|
21947
|
+
const data = await this.getDataFromSnapshot(resolved.current, resolved.snapshot, resolved.offset, 80);
|
|
21948
|
+
return data == null ? void 0 : deserializeBlockHeader(data, height, 0);
|
|
21754
21949
|
}
|
|
21755
21950
|
async getFileForHeight(height) {
|
|
21756
21951
|
return await this.lock.withReadLock(async () => {
|
|
@@ -21805,22 +22000,30 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
21805
22000
|
return bfd;
|
|
21806
22001
|
}
|
|
21807
22002
|
async validateBfdData(bfd, expectedFileHash) {
|
|
21808
|
-
await this.ensureData(bfd);
|
|
21809
|
-
|
|
21810
|
-
bfd.fileHash = asString(Hash.sha256(asArray(bfd.data)), "base64");
|
|
21811
|
-
if (expectedFileHash && expectedFileHash !== bfd.fileHash) throw new WERR_INVALID_PARAMETER("file.fileHash", `expected ${expectedFileHash} but got ${bfd.fileHash}`);
|
|
21812
|
-
this.validateBfdHeaders(bfd);
|
|
22003
|
+
const data = await this.ensureData(bfd);
|
|
22004
|
+
bfd.data = await this.validateRetrievedData(bfd, data, expectedFileHash);
|
|
21813
22005
|
}
|
|
21814
|
-
validateBfdHeaders(bfd) {
|
|
22006
|
+
async validateBfdHeaders(bfd, expectedFileHash = bfd.fileHash) {
|
|
21815
22007
|
const pbf = bfd.firstHeight > 0 ? this.getBfdForHeight(bfd.firstHeight - 1) : void 0;
|
|
21816
22008
|
const prevHash = pbf?.lastHash ?? "00".repeat(32);
|
|
21817
22009
|
const prevChainWork = pbf?.lastChainWork ?? "00".repeat(32);
|
|
21818
|
-
const
|
|
21819
|
-
|
|
21820
|
-
|
|
21821
|
-
|
|
21822
|
-
|
|
21823
|
-
|
|
22010
|
+
const result = await this.validator.validate({
|
|
22011
|
+
fileName: bfd.fileName,
|
|
22012
|
+
data: bfd.data,
|
|
22013
|
+
count: bfd.count,
|
|
22014
|
+
fileHash: expectedFileHash,
|
|
22015
|
+
firstHeight: bfd.firstHeight,
|
|
22016
|
+
prevHash,
|
|
22017
|
+
prevChainWork,
|
|
22018
|
+
lastHash: bfd.lastHash,
|
|
22019
|
+
lastChainWork: bfd.lastChainWork,
|
|
22020
|
+
chain: bfd.chain
|
|
22021
|
+
});
|
|
22022
|
+
bfd.data = result.data;
|
|
22023
|
+
bfd.fileHash = result.fileHash;
|
|
22024
|
+
bfd.lastHash = result.lastHeaderHash;
|
|
22025
|
+
bfd.lastChainWork = result.lastChainWork;
|
|
22026
|
+
return result.data;
|
|
21824
22027
|
}
|
|
21825
22028
|
async ReValidate() {
|
|
21826
22029
|
return await this.lock.withReadLock(async () => await this.ReValidateNoLock());
|
|
@@ -22011,62 +22214,96 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
22011
22214
|
this.ensureMaxRetained();
|
|
22012
22215
|
return data;
|
|
22013
22216
|
}
|
|
22217
|
+
const failed = this.failedLoads.get(key);
|
|
22218
|
+
if (failed != null) {
|
|
22219
|
+
if (Date.now() < failed.retryAt) {
|
|
22220
|
+
this.stats.loadBackoffs++;
|
|
22221
|
+
throw failed.error;
|
|
22222
|
+
}
|
|
22223
|
+
this.failedLoads.delete(key);
|
|
22224
|
+
}
|
|
22014
22225
|
const load = this.loadAndValidateData(bfd);
|
|
22015
22226
|
this.inFlightLoads.set(key, load);
|
|
22016
22227
|
try {
|
|
22017
22228
|
const data = await load;
|
|
22018
22229
|
bfd.data = data;
|
|
22019
22230
|
bfd.validated = true;
|
|
22231
|
+
this.failedLoads.delete(key);
|
|
22020
22232
|
bfd.mru = Date.now();
|
|
22021
22233
|
this.ensureMaxRetained();
|
|
22022
22234
|
return data;
|
|
22235
|
+
} catch (error) {
|
|
22236
|
+
const resolved = error instanceof Error ? error : new Error(String(error));
|
|
22237
|
+
this.failedLoads.set(key, {
|
|
22238
|
+
retryAt: Date.now() + this.failedLoadRetryMsecs,
|
|
22239
|
+
error: resolved
|
|
22240
|
+
});
|
|
22241
|
+
throw resolved;
|
|
22023
22242
|
} finally {
|
|
22024
22243
|
if (this.inFlightLoads.get(key) === load) this.inFlightLoads.delete(key);
|
|
22025
22244
|
}
|
|
22026
22245
|
}
|
|
22027
22246
|
async loadAndValidateData(bfd) {
|
|
22028
|
-
|
|
22029
|
-
|
|
22030
|
-
|
|
22031
|
-
|
|
22032
|
-
|
|
22033
|
-
|
|
22034
|
-
}
|
|
22035
|
-
if (this.cache != null) {
|
|
22036
|
-
const cached = await this.cache.get(bfd);
|
|
22037
|
-
if (cached != null) try {
|
|
22038
|
-
this.validateRetrievedData(bfd, cached);
|
|
22039
|
-
this.stats.persistentCacheHits++;
|
|
22040
|
-
return cached;
|
|
22041
|
-
} catch (error) {
|
|
22042
|
-
this.stats.persistentCacheRejects++;
|
|
22043
|
-
await this.cache.delete?.(bfd);
|
|
22044
|
-
this.log(`Rejected corrupt bulk-header cache entry ${bfd.fileName}: ${String(error)}`);
|
|
22045
|
-
}
|
|
22046
|
-
else this.stats.persistentCacheMisses++;
|
|
22047
|
-
}
|
|
22048
|
-
if (this.fetch != null && bfd.sourceUrl) {
|
|
22049
|
-
const expectedBytes = bfd.count * 80;
|
|
22050
|
-
await this.downloadBudget?.consume(expectedBytes);
|
|
22051
|
-
const url = this.fetch.pathJoin(bfd.sourceUrl, bfd.fileName);
|
|
22052
|
-
const downloaded = await this.fetch.download(url, expectedBytes);
|
|
22053
|
-
if (downloaded == null) throw new WERR_INVALID_PARAMETER("sourceUrl", `data not found for sourceUrl ${url}`);
|
|
22054
|
-
this.validateRetrievedData(bfd, downloaded);
|
|
22055
|
-
this.stats.downloads++;
|
|
22056
|
-
this.stats.downloadedBytes += downloaded.length;
|
|
22057
|
-
await this.cache?.set(bfd, downloaded);
|
|
22058
|
-
return downloaded;
|
|
22059
|
-
}
|
|
22247
|
+
const stored = await this.loadFromStorage(bfd);
|
|
22248
|
+
if (stored != null) return stored;
|
|
22249
|
+
const cached = await this.loadFromCache(bfd);
|
|
22250
|
+
if (cached != null) return cached;
|
|
22251
|
+
const downloaded = await this.loadFromRemote(bfd);
|
|
22252
|
+
if (downloaded != null) return downloaded;
|
|
22060
22253
|
throw new WERR_INVALID_PARAMETER("data", `defined. Unable to retrieve data for ${bfd.fileName}`);
|
|
22061
22254
|
}
|
|
22062
|
-
|
|
22063
|
-
if (
|
|
22064
|
-
|
|
22255
|
+
async loadFromStorage(bfd) {
|
|
22256
|
+
if (this.storage == null || !bfd.fileId) return void 0;
|
|
22257
|
+
const stored = await this.storage.getBulkFileData(bfd.fileId);
|
|
22258
|
+
if (stored == null) throw new WERR_INVALID_PARAMETER("fileId", `valid, data not found for fileId ${bfd.fileId}`);
|
|
22259
|
+
const validated = await this.validateRetrievedData(bfd, stored);
|
|
22260
|
+
this.stats.storageHits++;
|
|
22261
|
+
return validated;
|
|
22262
|
+
}
|
|
22263
|
+
async loadFromCache(bfd) {
|
|
22264
|
+
if (this.cache == null) return void 0;
|
|
22265
|
+
const cached = await this.cache.get(bfd);
|
|
22266
|
+
if (cached == null) {
|
|
22267
|
+
this.stats.persistentCacheMisses++;
|
|
22268
|
+
return;
|
|
22269
|
+
}
|
|
22270
|
+
try {
|
|
22271
|
+
const validated = await this.validateRetrievedData(bfd, cached);
|
|
22272
|
+
this.stats.persistentCacheHits++;
|
|
22273
|
+
await this.cache.promoteValidated?.(bfd, validated);
|
|
22274
|
+
return validated;
|
|
22275
|
+
} catch (error) {
|
|
22276
|
+
if (!(error instanceof BulkFileDataValidationError)) throw error;
|
|
22277
|
+
this.stats.persistentCacheRejects++;
|
|
22278
|
+
let rejectedData = error.data;
|
|
22279
|
+
if (!(rejectedData instanceof Uint8Array) && cached.byteLength > 0) rejectedData = cached;
|
|
22280
|
+
await this.cache.quarantine?.(bfd, String(error), rejectedData);
|
|
22281
|
+
this.log(`Rejected corrupt bulk-header cache entry ${bfd.fileName}: ${String(error)}`);
|
|
22282
|
+
return;
|
|
22283
|
+
}
|
|
22284
|
+
}
|
|
22285
|
+
async loadFromRemote(bfd) {
|
|
22286
|
+
if (this.fetch == null || !bfd.sourceUrl) return void 0;
|
|
22287
|
+
const expectedBytes = bfd.count * 80;
|
|
22288
|
+
await this.downloadBudget?.consume(expectedBytes);
|
|
22289
|
+
const url = this.fetch.pathJoin(bfd.sourceUrl, bfd.fileName);
|
|
22290
|
+
const downloaded = await this.fetch.download(url, expectedBytes, { beforeRetry: async () => await this.downloadBudget?.consume(expectedBytes) });
|
|
22291
|
+
if (downloaded == null) throw new WERR_INVALID_PARAMETER("sourceUrl", `data not found for sourceUrl ${url}`);
|
|
22292
|
+
const validated = await this.validateRetrievedData(bfd, downloaded);
|
|
22293
|
+
this.stats.downloads++;
|
|
22294
|
+
this.stats.downloadedBytes += validated.length;
|
|
22295
|
+
await this.cache?.set(bfd, validated);
|
|
22296
|
+
return validated;
|
|
22297
|
+
}
|
|
22298
|
+
async validateRetrievedData(bfd, data, expectedFileHash = bfd.fileHash) {
|
|
22065
22299
|
const candidate = {
|
|
22066
22300
|
...bfd,
|
|
22067
22301
|
data
|
|
22068
22302
|
};
|
|
22069
|
-
this.validateBfdHeaders(candidate);
|
|
22303
|
+
const validated = await this.validateBfdHeaders(candidate, expectedFileHash);
|
|
22304
|
+
bfd.lastHash = candidate.lastHash;
|
|
22305
|
+
bfd.lastChainWork = candidate.lastChainWork;
|
|
22306
|
+
return validated;
|
|
22070
22307
|
}
|
|
22071
22308
|
ensureMaxRetained() {
|
|
22072
22309
|
if (this.maxRetained === void 0) return;
|
|
@@ -22102,17 +22339,24 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
22102
22339
|
i++;
|
|
22103
22340
|
const data = await reader.read();
|
|
22104
22341
|
if (data == null || data.length === 0) break;
|
|
22105
|
-
const
|
|
22106
|
-
|
|
22107
|
-
|
|
22342
|
+
const validated = await this.validator.validate({
|
|
22343
|
+
fileName: toFileName(i),
|
|
22344
|
+
data,
|
|
22345
|
+
count: data.length / 80,
|
|
22346
|
+
firstHeight,
|
|
22347
|
+
prevHash: lastHeaderHash,
|
|
22348
|
+
prevChainWork: lastChainWork,
|
|
22349
|
+
chain
|
|
22350
|
+
});
|
|
22351
|
+
await toFs.writeFile(toPath(i), validated.data);
|
|
22108
22352
|
const file = {
|
|
22109
22353
|
chain,
|
|
22110
|
-
count: data.length / 80,
|
|
22111
|
-
fileHash,
|
|
22354
|
+
count: validated.data.length / 80,
|
|
22355
|
+
fileHash: validated.fileHash,
|
|
22112
22356
|
fileName: toFileName(i),
|
|
22113
22357
|
firstHeight,
|
|
22114
|
-
lastChainWork:
|
|
22115
|
-
lastHash:
|
|
22358
|
+
lastChainWork: validated.lastChainWork,
|
|
22359
|
+
lastHash: validated.lastHeaderHash,
|
|
22116
22360
|
prevChainWork: lastChainWork,
|
|
22117
22361
|
prevHash: lastHeaderHash,
|
|
22118
22362
|
sourceUrl
|
|
@@ -22124,7 +22368,16 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
22124
22368
|
}
|
|
22125
22369
|
await toFs.writeFile(toJsonPath(), asUint8Array(JSON.stringify(toBulkFiles), "utf8"));
|
|
22126
22370
|
}
|
|
22371
|
+
async destroy() {
|
|
22372
|
+
await this.validator.destroy?.();
|
|
22373
|
+
}
|
|
22127
22374
|
};
|
|
22375
|
+
function snapshotBfd(file) {
|
|
22376
|
+
return {
|
|
22377
|
+
...file,
|
|
22378
|
+
data: file.data
|
|
22379
|
+
};
|
|
22380
|
+
}
|
|
22128
22381
|
function selectBulkHeaderFiles(files, chain, maxPerFile) {
|
|
22129
22382
|
const r = [];
|
|
22130
22383
|
let height = 0;
|
|
@@ -26993,7 +27246,8 @@ function createDefaultBulkFileDataManager(params) {
|
|
|
26993
27246
|
maxRetained: params.maxRetained,
|
|
26994
27247
|
fromKnownSourceUrl: params.cdnUrl,
|
|
26995
27248
|
cache: params.sources.bulkFileCache,
|
|
26996
|
-
downloadBudget: params.sources.bulkFileDownloadBudget
|
|
27249
|
+
downloadBudget: params.sources.bulkFileDownloadBudget,
|
|
27250
|
+
validator: params.sources.bulkFileDataValidator
|
|
26997
27251
|
});
|
|
26998
27252
|
}
|
|
26999
27253
|
function createDefaultChaintracksStorageOptions(params) {
|
|
@@ -27518,6 +27772,7 @@ var FixedWindowBulkFileDownloadBudget = class {
|
|
|
27518
27772
|
return {
|
|
27519
27773
|
maxBytes: this.maxBytes,
|
|
27520
27774
|
consumedBytes: this.consumedBytes,
|
|
27775
|
+
remainingBytes: this.maxBytes - this.consumedBytes,
|
|
27521
27776
|
windowStartedAt: this.windowStartedAt,
|
|
27522
27777
|
windowMsecs: this.windowMsecs
|
|
27523
27778
|
};
|
|
@@ -30749,6 +31004,7 @@ var SetupClient = class SetupClient {
|
|
|
30749
31004
|
};
|
|
30750
31005
|
//#endregion
|
|
30751
31006
|
//#region ../src/CWIStyleWalletManager.ts
|
|
31007
|
+
const CWI_COMPONENT = "wallet-toolbox.cwi-manager";
|
|
30752
31008
|
/**
|
|
30753
31009
|
* Number of rounds used in PBKDF2 for deriving password keys.
|
|
30754
31010
|
*/
|
|
@@ -30981,11 +31237,11 @@ var OverlayUMPTokenInteractor = class {
|
|
|
30981
31237
|
* @param hash The 32-byte SHA-256 hash of the presentation key.
|
|
30982
31238
|
* @returns A UMPToken object (including currentOutpoint) if found, otherwise undefined.
|
|
30983
31239
|
*/
|
|
30984
|
-
async findByPresentationKeyHash(hash) {
|
|
30985
|
-
return
|
|
31240
|
+
async findByPresentationKeyHash(hash, options) {
|
|
31241
|
+
return this.findToken({
|
|
30986
31242
|
service: "ls_users",
|
|
30987
31243
|
query: { presentationHash: Utils.toHex(hash) }
|
|
30988
|
-
}, "presentation");
|
|
31244
|
+
}, "presentation", options);
|
|
30989
31245
|
}
|
|
30990
31246
|
/**
|
|
30991
31247
|
* Finds a UMP token on-chain by the given recovery key hash, if it exists.
|
|
@@ -30994,13 +31250,13 @@ var OverlayUMPTokenInteractor = class {
|
|
|
30994
31250
|
* @param hash The 32-byte SHA-256 hash of the recovery key.
|
|
30995
31251
|
* @returns A UMPToken object (including currentOutpoint) if found, otherwise undefined.
|
|
30996
31252
|
*/
|
|
30997
|
-
async findByRecoveryKeyHash(hash) {
|
|
30998
|
-
return
|
|
31253
|
+
async findByRecoveryKeyHash(hash, options) {
|
|
31254
|
+
return this.findToken({
|
|
30999
31255
|
service: "ls_users",
|
|
31000
31256
|
query: { recoveryHash: Utils.toHex(hash) }
|
|
31001
|
-
}, "recovery");
|
|
31257
|
+
}, "recovery", options);
|
|
31002
31258
|
}
|
|
31003
|
-
async findToken(question, lookupKind) {
|
|
31259
|
+
async findToken(question, lookupKind, options) {
|
|
31004
31260
|
const correlationId = this.telemetry.enabled === true ? this.telemetry.createCorrelationId() : void 0;
|
|
31005
31261
|
const startedAt = Date.now();
|
|
31006
31262
|
this.telemetry.capture({
|
|
@@ -31017,34 +31273,39 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31017
31273
|
correlationId
|
|
31018
31274
|
});
|
|
31019
31275
|
} catch (error) {
|
|
31020
|
-
const diagnostics = this.
|
|
31021
|
-
this.
|
|
31276
|
+
const diagnostics = this.emptyStats(correlationId);
|
|
31277
|
+
this.lookupFailed(lookupKind, "lookup-unavailable", diagnostics, startedAt, error);
|
|
31022
31278
|
throw new UMPTokenLookupError("lookup-unavailable", diagnostics, { cause: error });
|
|
31023
31279
|
}
|
|
31024
|
-
const diagnostics = this.
|
|
31280
|
+
const diagnostics = this.diagnosticsFor(resolution);
|
|
31025
31281
|
const tokens = this.parseLookupAnswers(resolution.answer);
|
|
31026
31282
|
const expectedHash = question.query[lookupKind === "presentation" ? "presentationHash" : "recoveryHash"].toLowerCase();
|
|
31027
31283
|
const matchingTokens = tokens.filter((token) => Utils.toHex(lookupKind === "presentation" ? token.presentationHash : token.recoveryHash).toLowerCase() === expectedHash);
|
|
31028
31284
|
if (matchingTokens.length > 1) {
|
|
31029
31285
|
const newest = this.resolveNewestToken(matchingTokens, resolution.answer.outputs);
|
|
31030
31286
|
if (newest != null) {
|
|
31031
|
-
this.
|
|
31287
|
+
this.lookupDone(lookupKind, "found", diagnostics, startedAt, { supersededTokens: matchingTokens.length - 1 });
|
|
31032
31288
|
return newest;
|
|
31033
31289
|
}
|
|
31290
|
+
const pinned = options?.pinnedOutpoint ? matchingTokens.find((token) => token.currentOutpoint === options.pinnedOutpoint) : void 0;
|
|
31291
|
+
if (pinned != null) {
|
|
31292
|
+
this.lookupDone(lookupKind, "found", diagnostics, startedAt);
|
|
31293
|
+
return pinned;
|
|
31294
|
+
}
|
|
31034
31295
|
const reason = "token-ambiguous";
|
|
31035
|
-
this.
|
|
31296
|
+
this.lookupFailed(lookupKind, reason, diagnostics, startedAt);
|
|
31036
31297
|
throw new UMPTokenLookupError(reason, diagnostics);
|
|
31037
31298
|
}
|
|
31038
31299
|
if (matchingTokens.length === 1) {
|
|
31039
|
-
this.
|
|
31300
|
+
this.lookupDone(lookupKind, "found", diagnostics, startedAt);
|
|
31040
31301
|
return matchingTokens[0];
|
|
31041
31302
|
}
|
|
31042
31303
|
if (resolution.progress.emptyHosts > 0) {
|
|
31043
|
-
this.
|
|
31304
|
+
this.lookupDone(lookupKind, "not-found", diagnostics, startedAt);
|
|
31044
31305
|
return;
|
|
31045
31306
|
}
|
|
31046
31307
|
const reason = resolution.answer.outputs.length > 0 ? "token-malformed" : "lookup-incomplete";
|
|
31047
|
-
this.
|
|
31308
|
+
this.lookupFailed(lookupKind, reason, diagnostics, startedAt);
|
|
31048
31309
|
throw new UMPTokenLookupError(reason, diagnostics);
|
|
31049
31310
|
}
|
|
31050
31311
|
/**
|
|
@@ -31075,7 +31336,7 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31075
31336
|
spent: /* @__PURE__ */ new Set()
|
|
31076
31337
|
};
|
|
31077
31338
|
evidence.txs.push(tx);
|
|
31078
|
-
this.
|
|
31339
|
+
this.collectSpends(tx, evidence.spent, /* @__PURE__ */ new Set());
|
|
31079
31340
|
evidenceByCandidate.set(outpoint, evidence);
|
|
31080
31341
|
} catch {}
|
|
31081
31342
|
if (evidenceByCandidate.size !== candidates.size) return void 0;
|
|
@@ -31084,7 +31345,7 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31084
31345
|
const provenContinuations = survivors.filter((outpoint) => {
|
|
31085
31346
|
const evidence = evidenceByCandidate.get(outpoint);
|
|
31086
31347
|
const token = candidates.get(outpoint);
|
|
31087
|
-
return evidence != null && token != null && evidence.txs.some((tx) => this.
|
|
31348
|
+
return evidence != null && token != null && evidence.txs.some((tx) => this.consumesIdentity(tx, token));
|
|
31088
31349
|
});
|
|
31089
31350
|
if (provenContinuations.length !== 1) return void 0;
|
|
31090
31351
|
return candidates.get(provenContinuations[0]);
|
|
@@ -31095,7 +31356,7 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31095
31356
|
* hash — on-chain proof that the candidate is an update of a same-identity
|
|
31096
31357
|
* predecessor rather than an independently minted token.
|
|
31097
31358
|
*/
|
|
31098
|
-
|
|
31359
|
+
consumesIdentity(tx, token) {
|
|
31099
31360
|
const presentationHash = Utils.toHex(token.presentationHash);
|
|
31100
31361
|
const recoveryHash = Utils.toHex(token.recoveryHash);
|
|
31101
31362
|
for (const input of tx.inputs) {
|
|
@@ -31121,7 +31382,7 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31121
31382
|
* renditions are absent from the lookup answer. Iterative so arbitrarily
|
|
31122
31383
|
* long update chains cannot exhaust the call stack.
|
|
31123
31384
|
*/
|
|
31124
|
-
|
|
31385
|
+
collectSpends(tx, spent, visited) {
|
|
31125
31386
|
const pending = [tx];
|
|
31126
31387
|
while (pending.length > 0) {
|
|
31127
31388
|
const current = pending.pop();
|
|
@@ -31136,7 +31397,7 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31136
31397
|
}
|
|
31137
31398
|
}
|
|
31138
31399
|
}
|
|
31139
|
-
|
|
31400
|
+
emptyStats(correlationId) {
|
|
31140
31401
|
return {
|
|
31141
31402
|
hostCount: 0,
|
|
31142
31403
|
completedHosts: 0,
|
|
@@ -31149,7 +31410,7 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31149
31410
|
...correlationId !== void 0 ? { correlationId } : {}
|
|
31150
31411
|
};
|
|
31151
31412
|
}
|
|
31152
|
-
|
|
31413
|
+
diagnosticsFor(resolution) {
|
|
31153
31414
|
const progress = resolution.progress;
|
|
31154
31415
|
return {
|
|
31155
31416
|
hostCount: progress.hostCount,
|
|
@@ -31163,7 +31424,7 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31163
31424
|
...progress.correlationId !== void 0 ? { correlationId: progress.correlationId } : {}
|
|
31164
31425
|
};
|
|
31165
31426
|
}
|
|
31166
|
-
|
|
31427
|
+
lookupAttrs(diagnostics) {
|
|
31167
31428
|
return {
|
|
31168
31429
|
hostCount: diagnostics.hostCount,
|
|
31169
31430
|
completedHosts: diagnostics.completedHosts,
|
|
@@ -31175,7 +31436,7 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31175
31436
|
outputCount: diagnostics.outputCount
|
|
31176
31437
|
};
|
|
31177
31438
|
}
|
|
31178
|
-
|
|
31439
|
+
lookupDone(lookupKind, result, diagnostics, startedAt, extraAttributes = {}) {
|
|
31179
31440
|
this.telemetry.capture({
|
|
31180
31441
|
name: "wallet-toolbox.ump.lookup.completed",
|
|
31181
31442
|
component: "wallet-toolbox.ump",
|
|
@@ -31185,12 +31446,12 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31185
31446
|
lookupKind,
|
|
31186
31447
|
result,
|
|
31187
31448
|
durationMs: Date.now() - startedAt,
|
|
31188
|
-
...this.
|
|
31449
|
+
...this.lookupAttrs(diagnostics),
|
|
31189
31450
|
...extraAttributes
|
|
31190
31451
|
}
|
|
31191
31452
|
});
|
|
31192
31453
|
}
|
|
31193
|
-
|
|
31454
|
+
lookupFailed(lookupKind, reason, diagnostics, startedAt, error) {
|
|
31194
31455
|
this.telemetry.capture({
|
|
31195
31456
|
name: "wallet-toolbox.ump.lookup.indeterminate",
|
|
31196
31457
|
component: "wallet-toolbox.ump",
|
|
@@ -31200,7 +31461,7 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31200
31461
|
lookupKind,
|
|
31201
31462
|
reason,
|
|
31202
31463
|
durationMs: Date.now() - startedAt,
|
|
31203
|
-
...this.
|
|
31464
|
+
...this.lookupAttrs(diagnostics)
|
|
31204
31465
|
},
|
|
31205
31466
|
error
|
|
31206
31467
|
});
|
|
@@ -31218,27 +31479,27 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31218
31479
|
* @returns The outpoint of the newly created UMP token (e.g. "abcd1234...ef.0").
|
|
31219
31480
|
*/
|
|
31220
31481
|
async buildAndSend(wallet, adminOriginator, token, oldTokenToConsume) {
|
|
31221
|
-
const fields = this.
|
|
31482
|
+
const fields = this.tokenFields(token);
|
|
31222
31483
|
const tokenOutput = [{
|
|
31223
31484
|
lockingScript: (await new PushDrop(wallet, adminOriginator).lock(fields, [2, "admin user management token"], "1", "self", true, true)).toHex(),
|
|
31224
31485
|
satoshis: 1,
|
|
31225
31486
|
outputDescription: "New UMP token output"
|
|
31226
31487
|
}];
|
|
31227
|
-
const { resolvedOldToken, inputToken } = await this.
|
|
31488
|
+
const { resolvedOldToken, inputToken } = await this.resolveOldInput(oldTokenToConsume);
|
|
31228
31489
|
const inputs = resolvedOldToken?.currentOutpoint ? [{
|
|
31229
31490
|
outpoint: resolvedOldToken.currentOutpoint,
|
|
31230
31491
|
unlockingScriptLength: 73,
|
|
31231
31492
|
inputDescription: "Consume old UMP token"
|
|
31232
31493
|
}] : [];
|
|
31233
|
-
const createResult = await this.
|
|
31234
|
-
if (!createResult.signableTransaction) return
|
|
31494
|
+
const createResult = await this.createAction(wallet, adminOriginator, inputs, tokenOutput, inputToken, resolvedOldToken);
|
|
31495
|
+
if (!createResult.signableTransaction) return this.broadcastFinal(createResult);
|
|
31235
31496
|
const reference = createResult.signableTransaction.reference;
|
|
31236
31497
|
const partialTx = Transaction.fromBEEF(createResult.signableTransaction.tx);
|
|
31237
|
-
if (resolvedOldToken?.currentOutpoint) return
|
|
31238
|
-
return
|
|
31498
|
+
if (resolvedOldToken?.currentOutpoint) return this.renewToken(wallet, adminOriginator, reference, partialTx);
|
|
31499
|
+
return this.broadcastNew(wallet, adminOriginator, reference);
|
|
31239
31500
|
}
|
|
31240
31501
|
/** Assembles the ordered number[][] fields array from a UMPToken. */
|
|
31241
|
-
|
|
31502
|
+
tokenFields(token) {
|
|
31242
31503
|
const fields = [];
|
|
31243
31504
|
fields[0] = token.passwordSalt;
|
|
31244
31505
|
fields[1] = token.passwordPresentationPrimary;
|
|
@@ -31265,20 +31526,20 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31265
31526
|
return fields;
|
|
31266
31527
|
}
|
|
31267
31528
|
/** Looks up the old token on the overlay; returns undefined resolved token if not found. */
|
|
31268
|
-
async
|
|
31529
|
+
async resolveOldInput(oldTokenToConsume) {
|
|
31269
31530
|
if (!oldTokenToConsume?.currentOutpoint) return {
|
|
31270
31531
|
resolvedOldToken: void 0,
|
|
31271
31532
|
inputToken: void 0
|
|
31272
31533
|
};
|
|
31273
31534
|
const inputToken = await this.findByOutpoint(oldTokenToConsume.currentOutpoint);
|
|
31274
|
-
if (inputToken == null) throw new Error("
|
|
31535
|
+
if (inputToken == null) throw new Error("Previous UMP token unavailable; update refused.");
|
|
31275
31536
|
return {
|
|
31276
31537
|
resolvedOldToken: oldTokenToConsume,
|
|
31277
31538
|
inputToken
|
|
31278
31539
|
};
|
|
31279
31540
|
}
|
|
31280
31541
|
/** Creates the UMP action without dropping a required old-token input on failure. */
|
|
31281
|
-
async
|
|
31542
|
+
async createAction(wallet, adminOriginator, inputs, outputs, inputToken, resolvedOldToken) {
|
|
31282
31543
|
try {
|
|
31283
31544
|
return await wallet.createAction({
|
|
31284
31545
|
description: resolvedOldToken == null ? "Create new UMP token" : "Renew UMP token (consume old, create new)",
|
|
@@ -31305,43 +31566,43 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31305
31566
|
}
|
|
31306
31567
|
}
|
|
31307
31568
|
/** Handles a fully-finalized (no signable tx) createAction result — broadcasts and returns outpoint. */
|
|
31308
|
-
async
|
|
31569
|
+
async broadcastFinal(createResult) {
|
|
31309
31570
|
const finalTxid = createResult.txid || (createResult.tx != null ? Transaction.fromAtomicBEEF(createResult.tx).id("hex") : void 0);
|
|
31310
|
-
if (!finalTxid) throw new Error("
|
|
31311
|
-
if (createResult.tx == null) throw new Error("
|
|
31571
|
+
if (!finalTxid) throw new Error("UMP transaction was not finalized.");
|
|
31572
|
+
if (createResult.tx == null) throw new Error("UMP transaction data missing.");
|
|
31312
31573
|
const broadcastTx = Transaction.fromAtomicBEEF(createResult.tx);
|
|
31313
31574
|
const result = await this.broadcaster.broadcast(broadcastTx);
|
|
31314
|
-
this.
|
|
31575
|
+
this.assertBroadcast(result, "create-finalized");
|
|
31315
31576
|
return `${finalTxid}.0`;
|
|
31316
31577
|
}
|
|
31317
31578
|
/** Signs the old-token input and broadcasts — used during UMP token renewal. */
|
|
31318
|
-
async
|
|
31579
|
+
async renewToken(wallet, adminOriginator, reference, partialTx) {
|
|
31319
31580
|
const unlockingScript = await new PushDrop(wallet, adminOriginator).unlock([2, "admin user management token"], "1", "self").sign(partialTx, 0);
|
|
31320
31581
|
const signResult = await wallet.signAction({
|
|
31321
31582
|
reference,
|
|
31322
31583
|
spends: { 0: { unlockingScript: unlockingScript.toHex() } }
|
|
31323
31584
|
}, adminOriginator);
|
|
31324
31585
|
const finalTxid = signResult.txid || (signResult.tx == null ? "" : Transaction.fromAtomicBEEF(signResult.tx).id("hex"));
|
|
31325
|
-
if (!finalTxid) throw new Error("Could not finalize
|
|
31326
|
-
if (signResult.tx == null) throw new Error("
|
|
31586
|
+
if (!finalTxid) throw new Error("Could not finalize renewed UMP token.");
|
|
31587
|
+
if (signResult.tx == null) throw new Error("Renewed UMP token transaction data missing.");
|
|
31327
31588
|
const result = await this.broadcaster.broadcast(Transaction.fromAtomicBEEF(signResult.tx));
|
|
31328
|
-
this.
|
|
31589
|
+
this.assertBroadcast(result, "renew");
|
|
31329
31590
|
return `${finalTxid}.0`;
|
|
31330
31591
|
}
|
|
31331
31592
|
/** Signs without input spending and broadcasts — used when creating a brand-new UMP token. */
|
|
31332
|
-
async
|
|
31593
|
+
async broadcastNew(wallet, adminOriginator, reference) {
|
|
31333
31594
|
const signResult = await wallet.signAction({
|
|
31334
31595
|
reference,
|
|
31335
31596
|
spends: {}
|
|
31336
31597
|
}, adminOriginator);
|
|
31337
31598
|
const finalTxid = signResult.txid || (signResult.tx == null ? "" : Transaction.fromAtomicBEEF(signResult.tx).id("hex"));
|
|
31338
|
-
if (!finalTxid) throw new Error("
|
|
31339
|
-
if (signResult.tx == null) throw new Error("
|
|
31599
|
+
if (!finalTxid) throw new Error("Could not finalize new UMP token.");
|
|
31600
|
+
if (signResult.tx == null) throw new Error("New UMP token transaction data missing.");
|
|
31340
31601
|
const result = await this.broadcaster.broadcast(Transaction.fromAtomicBEEF(signResult.tx));
|
|
31341
|
-
this.
|
|
31602
|
+
this.assertBroadcast(result, "create");
|
|
31342
31603
|
return `${finalTxid}.0`;
|
|
31343
31604
|
}
|
|
31344
|
-
|
|
31605
|
+
assertBroadcast(result, operation) {
|
|
31345
31606
|
const succeeded = result.status === "success";
|
|
31346
31607
|
this.telemetry.capture({
|
|
31347
31608
|
name: succeeded ? "wallet-toolbox.ump.broadcast.completed" : "wallet-toolbox.ump.broadcast.failed",
|
|
@@ -31370,12 +31631,12 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31370
31631
|
if (answer.type !== "output-list" || answer.outputs.length === 0) return [];
|
|
31371
31632
|
const tokens = [];
|
|
31372
31633
|
for (const output of answer.outputs) {
|
|
31373
|
-
const token = this.
|
|
31634
|
+
const token = this.parseOutput(output);
|
|
31374
31635
|
if (token != null) tokens.push(token);
|
|
31375
31636
|
}
|
|
31376
31637
|
return tokens;
|
|
31377
31638
|
}
|
|
31378
|
-
|
|
31639
|
+
parseOutput(output) {
|
|
31379
31640
|
try {
|
|
31380
31641
|
const tx = Transaction.fromBEEF(output.beef);
|
|
31381
31642
|
const txOutput = tx.outputs[output.outputIndex];
|
|
@@ -31425,14 +31686,14 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31425
31686
|
correlationId
|
|
31426
31687
|
});
|
|
31427
31688
|
} catch (error) {
|
|
31428
|
-
const diagnostics = this.
|
|
31429
|
-
this.
|
|
31689
|
+
const diagnostics = this.emptyStats(correlationId);
|
|
31690
|
+
this.lookupFailed("outpoint", "lookup-unavailable", diagnostics, startedAt, error);
|
|
31430
31691
|
throw new UMPTokenLookupError("lookup-unavailable", diagnostics, { cause: error });
|
|
31431
31692
|
}
|
|
31432
31693
|
if (resolution.answer.outputs.length === 0) {
|
|
31433
31694
|
if (resolution.progress.emptyHosts === 0) {
|
|
31434
|
-
const diagnostics = this.
|
|
31435
|
-
this.
|
|
31695
|
+
const diagnostics = this.diagnosticsFor(resolution);
|
|
31696
|
+
this.lookupFailed("outpoint", "lookup-incomplete", diagnostics, startedAt);
|
|
31436
31697
|
throw new UMPTokenLookupError("lookup-incomplete", diagnostics);
|
|
31437
31698
|
}
|
|
31438
31699
|
return;
|
|
@@ -31574,20 +31835,20 @@ var CWIStyleWalletManager = class {
|
|
|
31574
31835
|
if (this._initSnapshot !== void 0) {
|
|
31575
31836
|
this.telemetry.capture({
|
|
31576
31837
|
name: "wallet-toolbox.snapshot.initialization.started",
|
|
31577
|
-
component:
|
|
31838
|
+
component: CWI_COMPONENT,
|
|
31578
31839
|
severity: "debug"
|
|
31579
31840
|
});
|
|
31580
31841
|
try {
|
|
31581
31842
|
await this.loadSnapshot(this._initSnapshot);
|
|
31582
31843
|
this.telemetry.capture({
|
|
31583
31844
|
name: "wallet-toolbox.snapshot.initialization.completed",
|
|
31584
|
-
component:
|
|
31845
|
+
component: CWI_COMPONENT,
|
|
31585
31846
|
severity: "info"
|
|
31586
31847
|
});
|
|
31587
31848
|
} catch (error) {
|
|
31588
31849
|
this.telemetry.capture({
|
|
31589
31850
|
name: "wallet-toolbox.snapshot.initialization.failed",
|
|
31590
|
-
component:
|
|
31851
|
+
component: CWI_COMPONENT,
|
|
31591
31852
|
severity: "error",
|
|
31592
31853
|
error
|
|
31593
31854
|
});
|
|
@@ -31596,9 +31857,11 @@ var CWIStyleWalletManager = class {
|
|
|
31596
31857
|
}
|
|
31597
31858
|
}
|
|
31598
31859
|
/**
|
|
31599
|
-
* Provides the presentation key.
|
|
31860
|
+
* Provides the presentation key. A WAB operator pin may be supplied by the
|
|
31861
|
+
* authentication manager; normal lookup and lineage resolution always run
|
|
31862
|
+
* before this ambiguity-only fallback.
|
|
31600
31863
|
*/
|
|
31601
|
-
async providePresentationKey(key) {
|
|
31864
|
+
async providePresentationKey(key, lookupOptions) {
|
|
31602
31865
|
if (this.authenticated) throw new Error("User is already authenticated");
|
|
31603
31866
|
if (this.authenticationMode === "recovery-key-and-password") throw new Error("Presentation key is not needed in this mode");
|
|
31604
31867
|
if (key.length !== 32 || key.some((byte) => !Number.isInteger(byte) || byte < 0 || byte > 255)) throw new TypeError("Presentation key must contain exactly 32 bytes.");
|
|
@@ -31607,17 +31870,17 @@ var CWIStyleWalletManager = class {
|
|
|
31607
31870
|
const startedAt = Date.now();
|
|
31608
31871
|
this.telemetry.capture({
|
|
31609
31872
|
name: "wallet-toolbox.authentication.account-lookup.started",
|
|
31610
|
-
component:
|
|
31873
|
+
component: CWI_COMPONENT,
|
|
31611
31874
|
severity: "debug",
|
|
31612
31875
|
attributes: { lookupKind: "presentation" }
|
|
31613
31876
|
});
|
|
31614
31877
|
let token;
|
|
31615
31878
|
try {
|
|
31616
|
-
token = await this.UMPTokenInteractor.findByPresentationKeyHash(hash);
|
|
31879
|
+
token = await this.UMPTokenInteractor.findByPresentationKeyHash(hash, lookupOptions);
|
|
31617
31880
|
} catch (error) {
|
|
31618
31881
|
this.telemetry.capture({
|
|
31619
31882
|
name: "wallet-toolbox.authentication.account-lookup.failed",
|
|
31620
|
-
component:
|
|
31883
|
+
component: CWI_COMPONENT,
|
|
31621
31884
|
severity: "warn",
|
|
31622
31885
|
attributes: {
|
|
31623
31886
|
lookupKind: "presentation",
|
|
@@ -31637,7 +31900,7 @@ var CWIStyleWalletManager = class {
|
|
|
31637
31900
|
}
|
|
31638
31901
|
this.telemetry.capture({
|
|
31639
31902
|
name: "wallet-toolbox.authentication.account-lookup.completed",
|
|
31640
|
-
component:
|
|
31903
|
+
component: CWI_COMPONENT,
|
|
31641
31904
|
severity: "info",
|
|
31642
31905
|
attributes: {
|
|
31643
31906
|
lookupKind: "presentation",
|
|
@@ -31653,11 +31916,11 @@ var CWIStyleWalletManager = class {
|
|
|
31653
31916
|
if (this.authenticated) throw new Error("User is already authenticated");
|
|
31654
31917
|
if (this.authenticationMode === "presentation-key-and-recovery-key") throw new Error("Password is not needed in this mode");
|
|
31655
31918
|
if (this.authenticationFlow === "unknown") throw new Error("Determine account status with a presentation or recovery key before providing a password.");
|
|
31656
|
-
if (this.authenticationFlow === "existing-user") await this.
|
|
31657
|
-
else await this.
|
|
31919
|
+
if (this.authenticationFlow === "existing-user") await this.unlockExisting(password);
|
|
31920
|
+
else await this.createNewUser(password);
|
|
31658
31921
|
}
|
|
31659
31922
|
/** Handles the password step for an existing user — derives keys, sets up infrastructure. */
|
|
31660
|
-
async
|
|
31923
|
+
async unlockExisting(password) {
|
|
31661
31924
|
if (this.currentUMPToken == null) throw new Error("Provide presentation or recovery key first.");
|
|
31662
31925
|
const derivedPasswordKey = await derivePasswordKey(this.currentUMPToken, Utils.toArray(password, "utf8"));
|
|
31663
31926
|
let rootPrimaryKey;
|
|
@@ -31670,11 +31933,11 @@ var CWIStyleWalletManager = class {
|
|
|
31670
31933
|
rootPrimaryKey = new SymmetricKey(this.XOR(this.recoveryKey, derivedPasswordKey)).decrypt(this.currentUMPToken.passwordRecoveryPrimary);
|
|
31671
31934
|
rootPrivilegedKey = new SymmetricKey(this.XOR(rootPrimaryKey, derivedPasswordKey)).decrypt(this.currentUMPToken.passwordPrimaryPrivileged);
|
|
31672
31935
|
}
|
|
31673
|
-
await this.
|
|
31936
|
+
await this.setupRoot(rootPrimaryKey, rootPrivilegedKey);
|
|
31674
31937
|
await this.switchProfile(this.activeProfileId);
|
|
31675
31938
|
}
|
|
31676
31939
|
/** Handles the password step for a new user — generates keys, builds UMP token, publishes on-chain. */
|
|
31677
|
-
async
|
|
31940
|
+
async createNewUser(password) {
|
|
31678
31941
|
if (this.authenticationMode !== "presentation-key-and-password") throw new Error("New-user flow requires presentation key and password mode.");
|
|
31679
31942
|
if (this.presentationKey == null) throw new Error("No presentation key provided for new-user flow.");
|
|
31680
31943
|
const recoveryKey = Random(32);
|
|
@@ -31713,14 +31976,14 @@ var CWIStyleWalletManager = class {
|
|
|
31713
31976
|
passwordKdf: this.kdfConfig
|
|
31714
31977
|
};
|
|
31715
31978
|
this.currentUMPToken = newToken;
|
|
31716
|
-
await this.
|
|
31979
|
+
await this.setupRoot(rootPrimaryKey);
|
|
31717
31980
|
await this.switchProfile(DEFAULT_PROFILE_ID);
|
|
31718
31981
|
if (this.newWalletFunder != null && this.underlying != null) try {
|
|
31719
31982
|
await this.newWalletFunder(this.presentationKey, this.underlying, this.adminOriginator);
|
|
31720
31983
|
} catch (error) {
|
|
31721
31984
|
this.telemetry.capture({
|
|
31722
31985
|
name: "wallet-toolbox.authentication.new-wallet-funding.failed",
|
|
31723
|
-
component:
|
|
31986
|
+
component: CWI_COMPONENT,
|
|
31724
31987
|
severity: "error",
|
|
31725
31988
|
error: /* @__PURE__ */ new Error("New wallet funding failed.")
|
|
31726
31989
|
});
|
|
@@ -31751,7 +32014,7 @@ var CWIStyleWalletManager = class {
|
|
|
31751
32014
|
const xorKey = this.XOR(this.presentationKey, recoveryKey);
|
|
31752
32015
|
const rootPrimaryKey = new SymmetricKey(xorKey).decrypt(this.currentUMPToken.presentationRecoveryPrimary);
|
|
31753
32016
|
const rootPrivilegedKey = new SymmetricKey(xorKey).decrypt(this.currentUMPToken.presentationRecoveryPrivileged);
|
|
31754
|
-
await this.
|
|
32017
|
+
await this.setupRoot(rootPrimaryKey, rootPrivilegedKey);
|
|
31755
32018
|
await this.switchProfile(this.activeProfileId);
|
|
31756
32019
|
}
|
|
31757
32020
|
}
|
|
@@ -31782,7 +32045,7 @@ var CWIStyleWalletManager = class {
|
|
|
31782
32045
|
if (snapshot.length > 16777216) throw new Error("Snapshot exceeds the maximum supported size.");
|
|
31783
32046
|
this.telemetry.capture({
|
|
31784
32047
|
name: "wallet-toolbox.snapshot.saved",
|
|
31785
|
-
component:
|
|
32048
|
+
component: CWI_COMPONENT,
|
|
31786
32049
|
severity: "info",
|
|
31787
32050
|
attributes: {
|
|
31788
32051
|
formatVersion: 2,
|
|
@@ -31820,12 +32083,12 @@ var CWIStyleWalletManager = class {
|
|
|
31820
32083
|
const tokenBytes = payloadReader.read(tokenLen);
|
|
31821
32084
|
const token = this.deserializeUMPToken(tokenBytes);
|
|
31822
32085
|
this.currentUMPToken = token;
|
|
31823
|
-
await this.
|
|
32086
|
+
await this.setupRoot(rootPrimaryKey);
|
|
31824
32087
|
await this.switchProfile(activeProfileId);
|
|
31825
32088
|
this.authenticationFlow = "existing-user";
|
|
31826
32089
|
this.telemetry.capture({
|
|
31827
32090
|
name: "wallet-toolbox.snapshot.loaded",
|
|
31828
|
-
component:
|
|
32091
|
+
component: CWI_COMPONENT,
|
|
31829
32092
|
severity: "info",
|
|
31830
32093
|
attributes: {
|
|
31831
32094
|
formatVersion: version,
|
|
@@ -31836,7 +32099,7 @@ var CWIStyleWalletManager = class {
|
|
|
31836
32099
|
this.destroy();
|
|
31837
32100
|
this.telemetry.capture({
|
|
31838
32101
|
name: "wallet-toolbox.snapshot.load-failed",
|
|
31839
|
-
component:
|
|
32102
|
+
component: CWI_COMPONENT,
|
|
31840
32103
|
severity: "error",
|
|
31841
32104
|
error
|
|
31842
32105
|
});
|
|
@@ -31853,7 +32116,7 @@ var CWIStyleWalletManager = class {
|
|
|
31853
32116
|
if (refreshed == null) return false;
|
|
31854
32117
|
if (refreshed.currentOutpoint && currentToken.currentOutpoint && refreshed.currentOutpoint === currentToken.currentOutpoint) return false;
|
|
31855
32118
|
this.currentUMPToken = refreshed;
|
|
31856
|
-
await this.
|
|
32119
|
+
await this.setupRoot(this.rootPrimaryKey);
|
|
31857
32120
|
this.saveSnapshot();
|
|
31858
32121
|
return true;
|
|
31859
32122
|
}
|
|
@@ -31912,7 +32175,7 @@ var CWIStyleWalletManager = class {
|
|
|
31912
32175
|
createdAt: Math.floor(Date.now() / 1e3)
|
|
31913
32176
|
};
|
|
31914
32177
|
this.profiles.push(newProfile);
|
|
31915
|
-
await this.
|
|
32178
|
+
await this.updateFactors(this.currentUMPToken.passwordSalt, await this.getFactor("passwordKey"), await this.getFactor("presentationKey"), await this.getFactor("recoveryKey"), this.rootPrimaryKey, await this.getFactor("privilegedKey"), this.profiles);
|
|
31916
32179
|
return newProfile.id;
|
|
31917
32180
|
}
|
|
31918
32181
|
/**
|
|
@@ -31929,7 +32192,7 @@ var CWIStyleWalletManager = class {
|
|
|
31929
32192
|
if (profileIndex === -1) throw new Error("Profile not found.");
|
|
31930
32193
|
this.profiles.splice(profileIndex, 1);
|
|
31931
32194
|
if (this.activeProfileId.every((x, i) => x === profileId[i])) await this.switchProfile(DEFAULT_PROFILE_ID);
|
|
31932
|
-
await this.
|
|
32195
|
+
await this.updateFactors(this.currentUMPToken.passwordSalt, await this.getFactor("passwordKey"), await this.getFactor("presentationKey"), await this.getFactor("recoveryKey"), this.rootPrimaryKey, await this.getFactor("privilegedKey"), this.profiles);
|
|
31933
32196
|
}
|
|
31934
32197
|
/**
|
|
31935
32198
|
* Switches the active profile. This re-derives keys and rebuilds the underlying wallet.
|
|
@@ -31970,14 +32233,14 @@ var CWIStyleWalletManager = class {
|
|
|
31970
32233
|
const recoveryKey = await this.getFactor("recoveryKey");
|
|
31971
32234
|
const presentationKey = await this.getFactor("presentationKey");
|
|
31972
32235
|
const rootPrivilegedKey = await this.getFactor("privilegedKey");
|
|
31973
|
-
await this.
|
|
32236
|
+
await this.updateFactors(passwordSalt, newPasswordKey, presentationKey, recoveryKey, this.rootPrimaryKey, rootPrivilegedKey, this.profiles);
|
|
31974
32237
|
}
|
|
31975
32238
|
/**
|
|
31976
32239
|
* Retrieves the current recovery key. Requires privileged access.
|
|
31977
32240
|
*/
|
|
31978
32241
|
async getRecoveryKey() {
|
|
31979
32242
|
if (!this.authenticated || this.currentUMPToken == null || this.rootPrivilegedKeyManager == null) throw new Error("Not authenticated or missing required data.");
|
|
31980
|
-
return
|
|
32243
|
+
return this.getFactor("recoveryKey");
|
|
31981
32244
|
}
|
|
31982
32245
|
/**
|
|
31983
32246
|
* Changes the user's recovery key. Prompts user to save the new key.
|
|
@@ -31989,7 +32252,7 @@ var CWIStyleWalletManager = class {
|
|
|
31989
32252
|
const rootPrivilegedKey = await this.getFactor("privilegedKey");
|
|
31990
32253
|
const newRecoveryKey = Random(32);
|
|
31991
32254
|
await this.recoveryKeySaver(newRecoveryKey);
|
|
31992
|
-
await this.
|
|
32255
|
+
await this.updateFactors(this.currentUMPToken.passwordSalt, passwordKey, presentationKey, newRecoveryKey, this.rootPrimaryKey, rootPrivilegedKey, this.profiles);
|
|
31993
32256
|
}
|
|
31994
32257
|
/**
|
|
31995
32258
|
* Changes the user's presentation key.
|
|
@@ -32000,7 +32263,7 @@ var CWIStyleWalletManager = class {
|
|
|
32000
32263
|
const recoveryKey = await this.getFactor("recoveryKey");
|
|
32001
32264
|
const passwordKey = await this.getFactor("passwordKey");
|
|
32002
32265
|
const rootPrivilegedKey = await this.getFactor("privilegedKey");
|
|
32003
|
-
await this.
|
|
32266
|
+
await this.updateFactors(this.currentUMPToken.passwordSalt, passwordKey, newPresentationKey, recoveryKey, this.rootPrimaryKey, rootPrivilegedKey, this.profiles);
|
|
32004
32267
|
if (this.presentationKey != null) this.presentationKey = newPresentationKey;
|
|
32005
32268
|
}
|
|
32006
32269
|
/**
|
|
@@ -32046,7 +32309,7 @@ var CWIStyleWalletManager = class {
|
|
|
32046
32309
|
} catch (error) {
|
|
32047
32310
|
this.telemetry.capture({
|
|
32048
32311
|
name: "wallet-toolbox.authentication.factor-decryption.failed",
|
|
32049
|
-
component:
|
|
32312
|
+
component: CWI_COMPONENT,
|
|
32050
32313
|
severity: "error",
|
|
32051
32314
|
attributes: { factor: factorName },
|
|
32052
32315
|
error
|
|
@@ -32059,7 +32322,7 @@ var CWIStyleWalletManager = class {
|
|
|
32059
32322
|
* Recomputes UMP token fields with updated factors and profiles, then publishes the update.
|
|
32060
32323
|
* This operation requires the *root* privileged key and the *default* profile wallet.
|
|
32061
32324
|
*/
|
|
32062
|
-
async
|
|
32325
|
+
async updateFactors(passwordSalt, passwordKey, presentationKey, recoveryKey, rootPrimaryKey, rootPrivilegedKey, profiles) {
|
|
32063
32326
|
if (!this.authenticated || this.rootPrimaryKey == null || this.currentUMPToken == null) throw new Error("Wallet is not properly authenticated or missing data for update.");
|
|
32064
32327
|
const oldTokenToConsume = { ...this.currentUMPToken };
|
|
32065
32328
|
if (!oldTokenToConsume.currentOutpoint) throw new Error("Cannot update UMP token: Old token has no outpoint.");
|
|
@@ -32110,7 +32373,7 @@ var CWIStyleWalletManager = class {
|
|
|
32110
32373
|
if (!currentActiveId.every((x) => x === 0)) {
|
|
32111
32374
|
this.telemetry.capture({
|
|
32112
32375
|
name: "wallet-toolbox.ump.profile-switch.started",
|
|
32113
|
-
component:
|
|
32376
|
+
component: CWI_COMPONENT,
|
|
32114
32377
|
severity: "debug",
|
|
32115
32378
|
attributes: { reason: "token-update" }
|
|
32116
32379
|
});
|
|
@@ -32126,7 +32389,7 @@ var CWIStyleWalletManager = class {
|
|
|
32126
32389
|
await this.switchProfile(currentActiveId);
|
|
32127
32390
|
this.telemetry.capture({
|
|
32128
32391
|
name: "wallet-toolbox.ump.profile-switch.completed",
|
|
32129
|
-
component:
|
|
32392
|
+
component: CWI_COMPONENT,
|
|
32130
32393
|
severity: "debug",
|
|
32131
32394
|
attributes: { reason: "token-update" }
|
|
32132
32395
|
});
|
|
@@ -32253,9 +32516,9 @@ var CWIStyleWalletManager = class {
|
|
|
32253
32516
|
* @param rootPrimaryKey The user's root primary key (32 bytes).
|
|
32254
32517
|
* @param ephemeralRootPrivilegedKey Optional root privileged key (e.g., during recovery flows).
|
|
32255
32518
|
*/
|
|
32256
|
-
async
|
|
32519
|
+
async setupRoot(rootKey, ephemeralRootPrivilegedKey) {
|
|
32257
32520
|
if (this.currentUMPToken == null) throw new Error("A UMP token must exist before setting up root infrastructure!");
|
|
32258
|
-
this.rootPrimaryKey =
|
|
32521
|
+
this.rootPrimaryKey = rootKey;
|
|
32259
32522
|
let oneTimePrivilegedKey = ephemeralRootPrivilegedKey == null ? void 0 : new PrivateKey(ephemeralRootPrivilegedKey);
|
|
32260
32523
|
this.rootPrivilegedKeyManager = new PrivilegedKeyManager(async (reason) => {
|
|
32261
32524
|
if (oneTimePrivilegedKey != null) {
|
|
@@ -32276,7 +32539,7 @@ var CWIStyleWalletManager = class {
|
|
|
32276
32539
|
});
|
|
32277
32540
|
this.profiles = [];
|
|
32278
32541
|
if (this.currentUMPToken.profilesEncrypted != null && this.currentUMPToken.profilesEncrypted.length > 0) try {
|
|
32279
|
-
const decryptedProfileBytes = new SymmetricKey(
|
|
32542
|
+
const decryptedProfileBytes = new SymmetricKey(rootKey).decrypt(this.currentUMPToken.profilesEncrypted);
|
|
32280
32543
|
const profilesJson = Utils.toUTF8(decryptedProfileBytes);
|
|
32281
32544
|
const profiles = JSON.parse(profilesJson);
|
|
32282
32545
|
if (!Array.isArray(profiles) || profiles.length > 1e3 || !profiles.every(isValidProfile)) throw new Error("Decrypted profile data is invalid or exceeds supported bounds.");
|
|
@@ -32285,7 +32548,7 @@ var CWIStyleWalletManager = class {
|
|
|
32285
32548
|
this.profiles = [];
|
|
32286
32549
|
this.telemetry.capture({
|
|
32287
32550
|
name: "wallet-toolbox.profile.load-failed",
|
|
32288
|
-
component:
|
|
32551
|
+
component: CWI_COMPONENT,
|
|
32289
32552
|
severity: "error",
|
|
32290
32553
|
error
|
|
32291
32554
|
});
|
|
@@ -32294,98 +32557,98 @@ var CWIStyleWalletManager = class {
|
|
|
32294
32557
|
}
|
|
32295
32558
|
this.authenticated = true;
|
|
32296
32559
|
}
|
|
32297
|
-
|
|
32560
|
+
assertReady(originator) {
|
|
32298
32561
|
if (!this.authenticated) throw new Error("User is not authenticated.");
|
|
32299
32562
|
if (this.underlying == null) throw new Error("Underlying wallet for the active profile is not initialized.");
|
|
32300
32563
|
if (originator === this.adminOriginator) throw new Error("External applications are not allowed to use the admin originator.");
|
|
32301
32564
|
}
|
|
32302
32565
|
async getPublicKey(args, originator) {
|
|
32303
|
-
this.
|
|
32304
|
-
return
|
|
32566
|
+
this.assertReady(originator);
|
|
32567
|
+
return this.underlying.getPublicKey(args, originator);
|
|
32305
32568
|
}
|
|
32306
32569
|
async revealCounterpartyKeyLinkage(args, originator) {
|
|
32307
|
-
this.
|
|
32308
|
-
return
|
|
32570
|
+
this.assertReady(originator);
|
|
32571
|
+
return this.underlying.revealCounterpartyKeyLinkage(args, originator);
|
|
32309
32572
|
}
|
|
32310
32573
|
async revealSpecificKeyLinkage(args, originator) {
|
|
32311
|
-
this.
|
|
32312
|
-
return
|
|
32574
|
+
this.assertReady(originator);
|
|
32575
|
+
return this.underlying.revealSpecificKeyLinkage(args, originator);
|
|
32313
32576
|
}
|
|
32314
32577
|
async encrypt(args, originator) {
|
|
32315
|
-
this.
|
|
32316
|
-
return
|
|
32578
|
+
this.assertReady(originator);
|
|
32579
|
+
return this.underlying.encrypt(args, originator);
|
|
32317
32580
|
}
|
|
32318
32581
|
async decrypt(args, originator) {
|
|
32319
|
-
this.
|
|
32320
|
-
return
|
|
32582
|
+
this.assertReady(originator);
|
|
32583
|
+
return this.underlying.decrypt(args, originator);
|
|
32321
32584
|
}
|
|
32322
32585
|
async createHmac(args, originator) {
|
|
32323
|
-
this.
|
|
32324
|
-
return
|
|
32586
|
+
this.assertReady(originator);
|
|
32587
|
+
return this.underlying.createHmac(args, originator);
|
|
32325
32588
|
}
|
|
32326
32589
|
async verifyHmac(args, originator) {
|
|
32327
|
-
this.
|
|
32328
|
-
return
|
|
32590
|
+
this.assertReady(originator);
|
|
32591
|
+
return this.underlying.verifyHmac(args, originator);
|
|
32329
32592
|
}
|
|
32330
32593
|
async createSignature(args, originator) {
|
|
32331
|
-
this.
|
|
32332
|
-
return
|
|
32594
|
+
this.assertReady(originator);
|
|
32595
|
+
return this.underlying.createSignature(args, originator);
|
|
32333
32596
|
}
|
|
32334
32597
|
async verifySignature(args, originator) {
|
|
32335
|
-
this.
|
|
32336
|
-
return
|
|
32598
|
+
this.assertReady(originator);
|
|
32599
|
+
return this.underlying.verifySignature(args, originator);
|
|
32337
32600
|
}
|
|
32338
32601
|
async createAction(args, originator) {
|
|
32339
|
-
this.
|
|
32340
|
-
return
|
|
32602
|
+
this.assertReady(originator);
|
|
32603
|
+
return this.underlying.createAction(args, originator);
|
|
32341
32604
|
}
|
|
32342
32605
|
async signAction(args, originator) {
|
|
32343
|
-
this.
|
|
32344
|
-
return
|
|
32606
|
+
this.assertReady(originator);
|
|
32607
|
+
return this.underlying.signAction(args, originator);
|
|
32345
32608
|
}
|
|
32346
32609
|
async abortAction(args, originator) {
|
|
32347
|
-
this.
|
|
32348
|
-
return
|
|
32610
|
+
this.assertReady(originator);
|
|
32611
|
+
return this.underlying.abortAction(args, originator);
|
|
32349
32612
|
}
|
|
32350
32613
|
async listActions(args, originator) {
|
|
32351
|
-
this.
|
|
32352
|
-
return
|
|
32614
|
+
this.assertReady(originator);
|
|
32615
|
+
return this.underlying.listActions(args, originator);
|
|
32353
32616
|
}
|
|
32354
32617
|
async internalizeAction(args, originator) {
|
|
32355
|
-
this.
|
|
32356
|
-
return
|
|
32618
|
+
this.assertReady(originator);
|
|
32619
|
+
return this.underlying.internalizeAction(args, originator);
|
|
32357
32620
|
}
|
|
32358
32621
|
async listOutputs(args, originator) {
|
|
32359
|
-
this.
|
|
32360
|
-
return
|
|
32622
|
+
this.assertReady(originator);
|
|
32623
|
+
return this.underlying.listOutputs(args, originator);
|
|
32361
32624
|
}
|
|
32362
32625
|
async relinquishOutput(args, originator) {
|
|
32363
|
-
this.
|
|
32364
|
-
return
|
|
32626
|
+
this.assertReady(originator);
|
|
32627
|
+
return this.underlying.relinquishOutput(args, originator);
|
|
32365
32628
|
}
|
|
32366
32629
|
async acquireCertificate(args, originator) {
|
|
32367
|
-
this.
|
|
32368
|
-
return
|
|
32630
|
+
this.assertReady(originator);
|
|
32631
|
+
return this.underlying.acquireCertificate(args, originator);
|
|
32369
32632
|
}
|
|
32370
32633
|
async listCertificates(args, originator) {
|
|
32371
|
-
this.
|
|
32372
|
-
return
|
|
32634
|
+
this.assertReady(originator);
|
|
32635
|
+
return this.underlying.listCertificates(args, originator);
|
|
32373
32636
|
}
|
|
32374
32637
|
async proveCertificate(args, originator) {
|
|
32375
|
-
this.
|
|
32376
|
-
return
|
|
32638
|
+
this.assertReady(originator);
|
|
32639
|
+
return this.underlying.proveCertificate(args, originator);
|
|
32377
32640
|
}
|
|
32378
32641
|
async relinquishCertificate(args, originator) {
|
|
32379
|
-
this.
|
|
32380
|
-
return
|
|
32642
|
+
this.assertReady(originator);
|
|
32643
|
+
return this.underlying.relinquishCertificate(args, originator);
|
|
32381
32644
|
}
|
|
32382
32645
|
async discoverByIdentityKey(args, originator) {
|
|
32383
|
-
this.
|
|
32384
|
-
return
|
|
32646
|
+
this.assertReady(originator);
|
|
32647
|
+
return this.underlying.discoverByIdentityKey(args, originator);
|
|
32385
32648
|
}
|
|
32386
32649
|
async discoverByAttributes(args, originator) {
|
|
32387
|
-
this.
|
|
32388
|
-
return
|
|
32650
|
+
this.assertReady(originator);
|
|
32651
|
+
return this.underlying.discoverByAttributes(args, originator);
|
|
32389
32652
|
}
|
|
32390
32653
|
async isAuthenticated(_, originator) {
|
|
32391
32654
|
if (!this.authenticated) throw new Error("User is not authenticated.");
|
|
@@ -32395,23 +32658,23 @@ var CWIStyleWalletManager = class {
|
|
|
32395
32658
|
async waitForAuthentication(_, originator) {
|
|
32396
32659
|
if (originator === this.adminOriginator) throw new Error("External applications are not allowed to use the admin originator.");
|
|
32397
32660
|
while (!this.authenticated || this.underlying == null) await new Promise((resolve) => setTimeout(resolve, 100));
|
|
32398
|
-
return
|
|
32661
|
+
return this.underlying.waitForAuthentication({}, originator);
|
|
32399
32662
|
}
|
|
32400
32663
|
async getHeight(_, originator) {
|
|
32401
|
-
this.
|
|
32402
|
-
return
|
|
32664
|
+
this.assertReady(originator);
|
|
32665
|
+
return this.underlying.getHeight({}, originator);
|
|
32403
32666
|
}
|
|
32404
32667
|
async getHeaderForHeight(args, originator) {
|
|
32405
|
-
this.
|
|
32406
|
-
return
|
|
32668
|
+
this.assertReady(originator);
|
|
32669
|
+
return this.underlying.getHeaderForHeight(args, originator);
|
|
32407
32670
|
}
|
|
32408
32671
|
async getNetwork(_, originator) {
|
|
32409
|
-
this.
|
|
32410
|
-
return
|
|
32672
|
+
this.assertReady(originator);
|
|
32673
|
+
return this.underlying.getNetwork({}, originator);
|
|
32411
32674
|
}
|
|
32412
32675
|
async getVersion(_, originator) {
|
|
32413
|
-
this.
|
|
32414
|
-
return
|
|
32676
|
+
this.assertReady(originator);
|
|
32677
|
+
return this.underlying.getVersion({}, originator);
|
|
32415
32678
|
}
|
|
32416
32679
|
};
|
|
32417
32680
|
//#endregion
|
|
@@ -32755,6 +33018,8 @@ const DEFAULT_MAX_RESPONSE_BYTES = 1024 * 1024;
|
|
|
32755
33018
|
const MAX_CONFIGURED_TIMEOUT_MS = 12e4;
|
|
32756
33019
|
const MAX_CONFIGURED_REQUEST_BYTES = 10 * 1024 * 1024;
|
|
32757
33020
|
const MAX_CONFIGURED_RESPONSE_BYTES = 10 * 1024 * 1024;
|
|
33021
|
+
const WAB_COMPONENT = "wallet-toolbox.wab-transport";
|
|
33022
|
+
const WAB_REQUEST_EVENT = "wallet-toolbox.wab.request.";
|
|
32758
33023
|
const defaultFetch = typeof globalThis !== "undefined" && typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : void 0;
|
|
32759
33024
|
/**
|
|
32760
33025
|
* A privacy-safe WAB transport failure. Response bodies and request payloads
|
|
@@ -32793,8 +33058,8 @@ function normalizeServerUrl(serverUrl) {
|
|
|
32793
33058
|
} catch {
|
|
32794
33059
|
throw new WABClientError("WAB_INVALID_CONFIGURATION", "WAB server URL must be an absolute URL.", false);
|
|
32795
33060
|
}
|
|
32796
|
-
if (parsed.username !== "" || parsed.password !== "" || parsed.search !== "" || parsed.hash !== "") throw new WABClientError("WAB_INVALID_CONFIGURATION", "WAB
|
|
32797
|
-
if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && isLocalHostname(parsed.hostname))) throw new WABClientError("WAB_INVALID_CONFIGURATION", "WAB
|
|
33061
|
+
if (parsed.username !== "" || parsed.password !== "" || parsed.search !== "" || parsed.hash !== "") throw new WABClientError("WAB_INVALID_CONFIGURATION", "WAB URL cannot include credentials, query, or fragment.", false);
|
|
33062
|
+
if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && isLocalHostname(parsed.hostname))) throw new WABClientError("WAB_INVALID_CONFIGURATION", "WAB URL requires HTTPS except on localhost.", false);
|
|
32798
33063
|
let pathname = parsed.pathname;
|
|
32799
33064
|
while (pathname.endsWith("/")) pathname = pathname.slice(0, -1);
|
|
32800
33065
|
return {
|
|
@@ -32804,7 +33069,7 @@ function normalizeServerUrl(serverUrl) {
|
|
|
32804
33069
|
}
|
|
32805
33070
|
function normalizePositiveInteger(value, fallback, maximum, name) {
|
|
32806
33071
|
const resolved = value ?? fallback;
|
|
32807
|
-
if (!Number.isInteger(resolved) || resolved <= 0 || resolved > maximum) throw new WABClientError("WAB_INVALID_CONFIGURATION", `${name} must be
|
|
33072
|
+
if (!Number.isInteger(resolved) || resolved <= 0 || resolved > maximum) throw new WABClientError("WAB_INVALID_CONFIGURATION", `${name} must be an integer from 1 to ${maximum}.`, false);
|
|
32808
33073
|
return resolved;
|
|
32809
33074
|
}
|
|
32810
33075
|
function assertSafePath(path) {
|
|
@@ -32829,20 +33094,20 @@ var WABTransport = class {
|
|
|
32829
33094
|
serverUrl;
|
|
32830
33095
|
serverOrigin;
|
|
32831
33096
|
telemetry;
|
|
32832
|
-
|
|
32833
|
-
|
|
32834
|
-
|
|
32835
|
-
|
|
33097
|
+
fetcher;
|
|
33098
|
+
timeout;
|
|
33099
|
+
requestLimit;
|
|
33100
|
+
responseLimit;
|
|
32836
33101
|
constructor(serverUrl, options = {}) {
|
|
32837
33102
|
const normalized = normalizeServerUrl(serverUrl);
|
|
32838
33103
|
this.serverUrl = normalized.baseUrl;
|
|
32839
33104
|
this.serverOrigin = normalized.origin;
|
|
32840
|
-
const
|
|
32841
|
-
if (typeof
|
|
32842
|
-
this.
|
|
32843
|
-
this.
|
|
32844
|
-
this.
|
|
32845
|
-
this.
|
|
33105
|
+
const fetcher = options.fetch ?? defaultFetch;
|
|
33106
|
+
if (typeof fetcher !== "function") throw new WABClientError("WAB_INVALID_CONFIGURATION", "WABClient requires a fetch implementation.", false);
|
|
33107
|
+
this.fetcher = fetcher;
|
|
33108
|
+
this.timeout = normalizePositiveInteger(options.timeoutMs, DEFAULT_TIMEOUT_MS, MAX_CONFIGURED_TIMEOUT_MS, "timeoutMs");
|
|
33109
|
+
this.requestLimit = normalizePositiveInteger(options.maxRequestBytes, DEFAULT_MAX_REQUEST_BYTES, MAX_CONFIGURED_REQUEST_BYTES, "maxRequestBytes");
|
|
33110
|
+
this.responseLimit = normalizePositiveInteger(options.maxResponseBytes, DEFAULT_MAX_RESPONSE_BYTES, MAX_CONFIGURED_RESPONSE_BYTES, "maxResponseBytes");
|
|
32846
33111
|
this.telemetry = new Telemetry(options.telemetry);
|
|
32847
33112
|
}
|
|
32848
33113
|
createCorrelationId() {
|
|
@@ -32850,38 +33115,9 @@ var WABTransport = class {
|
|
|
32850
33115
|
return isSafeCorrelationId(correlationId) ? correlationId : new Telemetry().createCorrelationId();
|
|
32851
33116
|
}
|
|
32852
33117
|
async request(path, options) {
|
|
32853
|
-
const metadata = this.createRequestMetadata(path, options);
|
|
32854
|
-
this.captureRequestStarted(metadata);
|
|
32855
|
-
const body = this.encodeRequestBody(options, metadata);
|
|
32856
|
-
const timeout = this.startRequestTimeout(metadata.errorContext);
|
|
32857
|
-
const response = await this.fetchResponse(metadata, body, timeout);
|
|
32858
|
-
const responseContext = this.createResponseContext(response, metadata);
|
|
32859
|
-
this.assertSuccessfulResponse(response, responseContext, metadata, timeout);
|
|
32860
|
-
const responseText = await this.readResponseText(response, responseContext, metadata, timeout);
|
|
32861
|
-
const parsed = this.parseResponseObject(responseText, response, responseContext, metadata);
|
|
32862
|
-
this.telemetry.capture({
|
|
32863
|
-
name: "wallet-toolbox.wab.request.completed",
|
|
32864
|
-
component: "wallet-toolbox.wab-transport",
|
|
32865
|
-
severity: "info",
|
|
32866
|
-
correlationId: metadata.correlationId,
|
|
32867
|
-
attributes: {
|
|
32868
|
-
operation: metadata.operation,
|
|
32869
|
-
method: metadata.method,
|
|
32870
|
-
route: metadata.path,
|
|
32871
|
-
serverOrigin: this.serverOrigin,
|
|
32872
|
-
status: response.status,
|
|
32873
|
-
endpointMarkerPresent: responseContext.endpointMarkerPresent,
|
|
32874
|
-
responseCorrelationMatched: responseContext.responseCorrelationMatched,
|
|
32875
|
-
responseBytes: new TextEncoder().encode(responseText).byteLength,
|
|
32876
|
-
durationMs: Date.now() - metadata.startedAt
|
|
32877
|
-
}
|
|
32878
|
-
});
|
|
32879
|
-
return parsed;
|
|
32880
|
-
}
|
|
32881
|
-
createRequestMetadata(path, options) {
|
|
32882
33118
|
assertSafePath(path);
|
|
32883
33119
|
const correlationId = options.correlationId != null && isSafeCorrelationId(options.correlationId) ? options.correlationId : this.createCorrelationId();
|
|
32884
|
-
|
|
33120
|
+
const metadata = {
|
|
32885
33121
|
method: options.method ?? "POST",
|
|
32886
33122
|
path,
|
|
32887
33123
|
operation: options.operation,
|
|
@@ -32893,46 +33129,73 @@ var WABTransport = class {
|
|
|
32893
33129
|
route: path
|
|
32894
33130
|
}
|
|
32895
33131
|
};
|
|
32896
|
-
}
|
|
32897
|
-
captureRequestStarted(metadata) {
|
|
32898
33132
|
this.telemetry.capture({
|
|
32899
|
-
name:
|
|
32900
|
-
component:
|
|
33133
|
+
name: `${WAB_REQUEST_EVENT}started`,
|
|
33134
|
+
component: WAB_COMPONENT,
|
|
32901
33135
|
severity: "debug",
|
|
33136
|
+
correlationId,
|
|
33137
|
+
attributes: {
|
|
33138
|
+
operation: metadata.operation,
|
|
33139
|
+
method: metadata.method,
|
|
33140
|
+
route: path,
|
|
33141
|
+
serverOrigin: this.serverOrigin
|
|
33142
|
+
}
|
|
33143
|
+
});
|
|
33144
|
+
const body = this.bodyFor(options, metadata);
|
|
33145
|
+
const timeout = this.startTimer(metadata.errorContext);
|
|
33146
|
+
const response = await this.fetch(metadata, body, timeout);
|
|
33147
|
+
const responseContext = {
|
|
33148
|
+
...metadata.errorContext,
|
|
33149
|
+
endpointMarkerPresent: isWabResponse(response),
|
|
33150
|
+
responseCorrelationMatched: response.headers.get("X-Correlation-ID") === correlationId
|
|
33151
|
+
};
|
|
33152
|
+
this.checkResponse(response, responseContext, metadata, timeout);
|
|
33153
|
+
const responseText = await this.readText(response, responseContext, metadata, timeout);
|
|
33154
|
+
const parsed = this.parse(responseText, response, responseContext, metadata);
|
|
33155
|
+
this.telemetry.capture({
|
|
33156
|
+
name: `${WAB_REQUEST_EVENT}completed`,
|
|
33157
|
+
component: WAB_COMPONENT,
|
|
33158
|
+
severity: "info",
|
|
32902
33159
|
correlationId: metadata.correlationId,
|
|
32903
33160
|
attributes: {
|
|
32904
33161
|
operation: metadata.operation,
|
|
32905
33162
|
method: metadata.method,
|
|
32906
33163
|
route: metadata.path,
|
|
32907
|
-
serverOrigin: this.serverOrigin
|
|
33164
|
+
serverOrigin: this.serverOrigin,
|
|
33165
|
+
status: response.status,
|
|
33166
|
+
endpointMarkerPresent: responseContext.endpointMarkerPresent,
|
|
33167
|
+
responseCorrelationMatched: responseContext.responseCorrelationMatched,
|
|
33168
|
+
responseBytes: new TextEncoder().encode(responseText).byteLength,
|
|
33169
|
+
durationMs: Date.now() - metadata.startedAt
|
|
32908
33170
|
}
|
|
32909
33171
|
});
|
|
33172
|
+
return parsed;
|
|
32910
33173
|
}
|
|
32911
|
-
|
|
33174
|
+
bodyFor(options, metadata) {
|
|
32912
33175
|
let body;
|
|
32913
33176
|
try {
|
|
32914
33177
|
body = options.body === void 0 ? void 0 : JSON.stringify(options.body);
|
|
32915
33178
|
} catch (cause) {
|
|
32916
|
-
const error = new WABClientError("WAB_INVALID_REQUEST", "WAB request
|
|
33179
|
+
const error = new WABClientError("WAB_INVALID_REQUEST", "WAB request encoding failed.", false, void 0, {
|
|
32917
33180
|
...metadata.errorContext,
|
|
32918
33181
|
cause
|
|
32919
33182
|
});
|
|
32920
|
-
this.
|
|
33183
|
+
this.report(metadata, error);
|
|
32921
33184
|
throw error;
|
|
32922
33185
|
}
|
|
32923
33186
|
if (options.body !== void 0 && body === void 0) {
|
|
32924
|
-
const error = new WABClientError("WAB_INVALID_REQUEST", "WAB request
|
|
32925
|
-
this.
|
|
33187
|
+
const error = new WABClientError("WAB_INVALID_REQUEST", "WAB request is not JSON-serializable.", false, void 0, metadata.errorContext);
|
|
33188
|
+
this.report(metadata, error);
|
|
32926
33189
|
throw error;
|
|
32927
33190
|
}
|
|
32928
|
-
if (body != null && new TextEncoder().encode(body).byteLength > this.
|
|
32929
|
-
const error = new WABClientError("WAB_REQUEST_TOO_LARGE", "WAB request
|
|
32930
|
-
this.
|
|
33191
|
+
if (body != null && new TextEncoder().encode(body).byteLength > this.requestLimit) {
|
|
33192
|
+
const error = new WABClientError("WAB_REQUEST_TOO_LARGE", "WAB request exceeds its size limit.", false, void 0, metadata.errorContext);
|
|
33193
|
+
this.report(metadata, error);
|
|
32931
33194
|
throw error;
|
|
32932
33195
|
}
|
|
32933
33196
|
return body;
|
|
32934
33197
|
}
|
|
32935
|
-
|
|
33198
|
+
startTimer(errorContext) {
|
|
32936
33199
|
const timeout = {
|
|
32937
33200
|
controller: new AbortController(),
|
|
32938
33201
|
timedOut: false
|
|
@@ -32942,12 +33205,12 @@ var WABTransport = class {
|
|
|
32942
33205
|
timeout.timedOut = true;
|
|
32943
33206
|
timeout.controller.abort();
|
|
32944
33207
|
reject(new WABClientError("WAB_TIMEOUT", "WAB request timed out.", true, void 0, errorContext));
|
|
32945
|
-
}, this.
|
|
33208
|
+
}, this.timeout);
|
|
32946
33209
|
});
|
|
32947
33210
|
return timeout;
|
|
32948
33211
|
}
|
|
32949
|
-
async
|
|
32950
|
-
const requestPromise = Promise.resolve().then(() => this.
|
|
33212
|
+
async fetch(metadata, body, timeout) {
|
|
33213
|
+
const requestPromise = Promise.resolve().then(() => this.fetcher(`${this.serverUrl}${metadata.path}`, {
|
|
32951
33214
|
method: metadata.method,
|
|
32952
33215
|
headers: {
|
|
32953
33216
|
Accept: "application/json",
|
|
@@ -32971,33 +33234,26 @@ var WABTransport = class {
|
|
|
32971
33234
|
...metadata.errorContext,
|
|
32972
33235
|
cause
|
|
32973
33236
|
});
|
|
32974
|
-
else error = new WABClientError("WAB_NETWORK_ERROR", "WAB request failed before
|
|
33237
|
+
else error = new WABClientError("WAB_NETWORK_ERROR", "WAB request failed before response.", true, void 0, {
|
|
32975
33238
|
...metadata.errorContext,
|
|
32976
33239
|
cause
|
|
32977
33240
|
});
|
|
32978
|
-
this.
|
|
33241
|
+
this.report(metadata, error);
|
|
32979
33242
|
throw error;
|
|
32980
33243
|
}
|
|
32981
33244
|
}
|
|
32982
|
-
|
|
32983
|
-
return {
|
|
32984
|
-
...metadata.errorContext,
|
|
32985
|
-
endpointMarkerPresent: isWabResponse(response),
|
|
32986
|
-
responseCorrelationMatched: response.headers.get("X-Correlation-ID") === metadata.correlationId
|
|
32987
|
-
};
|
|
32988
|
-
}
|
|
32989
|
-
assertSuccessfulResponse(response, responseContext, metadata, timeout) {
|
|
33245
|
+
checkResponse(response, responseContext, metadata, timeout) {
|
|
32990
33246
|
if (response.ok) return;
|
|
32991
33247
|
if (timeout.timer !== void 0) clearTimeout(timeout.timer);
|
|
32992
33248
|
const endpointMismatch = response.status === 404 && responseContext.endpointMarkerPresent !== true;
|
|
32993
|
-
const error = new WABClientError(endpointMismatch ? "WAB_ENDPOINT_MISMATCH" : "WAB_HTTP_ERROR", endpointMismatch ? "
|
|
32994
|
-
this.
|
|
33249
|
+
const error = new WABClientError(endpointMismatch ? "WAB_ENDPOINT_MISMATCH" : "WAB_HTTP_ERROR", endpointMismatch ? "WAB endpoint is incompatible." : `WAB request failed with HTTP status ${response.status}.`, isRetryableStatus(response.status), response.status, responseContext);
|
|
33250
|
+
this.report(metadata, error);
|
|
32995
33251
|
response.body?.cancel().catch(() => {});
|
|
32996
33252
|
throw error;
|
|
32997
33253
|
}
|
|
32998
|
-
async
|
|
33254
|
+
async readText(response, responseContext, metadata, timeout) {
|
|
32999
33255
|
try {
|
|
33000
|
-
return await Promise.race([this.
|
|
33256
|
+
return await Promise.race([this.read(response, responseContext), timeout.promise]);
|
|
33001
33257
|
} catch (cause) {
|
|
33002
33258
|
let error;
|
|
33003
33259
|
if (cause instanceof WABClientError) error = cause;
|
|
@@ -33005,17 +33261,17 @@ var WABTransport = class {
|
|
|
33005
33261
|
...responseContext,
|
|
33006
33262
|
cause
|
|
33007
33263
|
});
|
|
33008
|
-
else error = new WABClientError("WAB_INVALID_RESPONSE", "WAB response
|
|
33264
|
+
else error = new WABClientError("WAB_INVALID_RESPONSE", "WAB response read failed.", true, response.status, {
|
|
33009
33265
|
...responseContext,
|
|
33010
33266
|
cause
|
|
33011
33267
|
});
|
|
33012
|
-
this.
|
|
33268
|
+
this.report(metadata, error);
|
|
33013
33269
|
throw error;
|
|
33014
33270
|
} finally {
|
|
33015
33271
|
if (timeout.timer !== void 0) clearTimeout(timeout.timer);
|
|
33016
33272
|
}
|
|
33017
33273
|
}
|
|
33018
|
-
|
|
33274
|
+
parse(responseText, response, responseContext, metadata) {
|
|
33019
33275
|
let parsed;
|
|
33020
33276
|
try {
|
|
33021
33277
|
parsed = JSON.parse(responseText);
|
|
@@ -33024,37 +33280,32 @@ var WABTransport = class {
|
|
|
33024
33280
|
...responseContext,
|
|
33025
33281
|
cause
|
|
33026
33282
|
});
|
|
33027
|
-
this.
|
|
33283
|
+
this.report(metadata, error);
|
|
33028
33284
|
throw error;
|
|
33029
33285
|
}
|
|
33030
33286
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
33031
33287
|
const error = new WABClientError("WAB_INVALID_RESPONSE", "WAB response must be a JSON object.", true, response.status, responseContext);
|
|
33032
|
-
this.
|
|
33288
|
+
this.report(metadata, error);
|
|
33033
33289
|
throw error;
|
|
33034
33290
|
}
|
|
33035
33291
|
return parsed;
|
|
33036
33292
|
}
|
|
33037
|
-
|
|
33038
|
-
this.captureFailure(metadata.operation, metadata.method, metadata.path, metadata.correlationId, metadata.startedAt, error);
|
|
33039
|
-
}
|
|
33040
|
-
async readBoundedResponse(response, responseContext) {
|
|
33041
|
-
await this.rejectOversizedDeclaredResponse(response, responseContext);
|
|
33042
|
-
const reader = response.body?.getReader();
|
|
33043
|
-
if (reader == null) return await this.readBoundedArrayBuffer(response, responseContext);
|
|
33044
|
-
return await this.readBoundedStream(reader, response, responseContext);
|
|
33045
|
-
}
|
|
33046
|
-
async rejectOversizedDeclaredResponse(response, responseContext) {
|
|
33293
|
+
async read(response, responseContext) {
|
|
33047
33294
|
const contentLength = Number(response.headers.get("content-length"));
|
|
33048
|
-
if (
|
|
33049
|
-
|
|
33050
|
-
|
|
33295
|
+
if (Number.isFinite(contentLength) && contentLength > this.responseLimit) {
|
|
33296
|
+
await this.stopBody(response);
|
|
33297
|
+
throw this.sizeError(response, responseContext);
|
|
33298
|
+
}
|
|
33299
|
+
const reader = response.body?.getReader();
|
|
33300
|
+
if (reader == null) return this.readBuffer(response, responseContext);
|
|
33301
|
+
return this.readStream(reader, response, responseContext);
|
|
33051
33302
|
}
|
|
33052
|
-
async
|
|
33303
|
+
async readBuffer(response, responseContext) {
|
|
33053
33304
|
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
33054
|
-
if (bytes.byteLength > this.
|
|
33305
|
+
if (bytes.byteLength > this.responseLimit) throw this.sizeError(response, responseContext);
|
|
33055
33306
|
return new TextDecoder().decode(bytes);
|
|
33056
33307
|
}
|
|
33057
|
-
async
|
|
33308
|
+
async readStream(reader, response, responseContext) {
|
|
33058
33309
|
const chunks = [];
|
|
33059
33310
|
let total = 0;
|
|
33060
33311
|
while (true) {
|
|
@@ -33062,15 +33313,12 @@ var WABTransport = class {
|
|
|
33062
33313
|
if (done) break;
|
|
33063
33314
|
if (value == null) continue;
|
|
33064
33315
|
total += value.byteLength;
|
|
33065
|
-
if (total > this.
|
|
33066
|
-
await this.
|
|
33067
|
-
throw this.
|
|
33316
|
+
if (total > this.responseLimit) {
|
|
33317
|
+
await this.stopReader(reader);
|
|
33318
|
+
throw this.sizeError(response, responseContext);
|
|
33068
33319
|
}
|
|
33069
33320
|
chunks.push(value);
|
|
33070
33321
|
}
|
|
33071
|
-
return this.decodeChunks(chunks, total);
|
|
33072
|
-
}
|
|
33073
|
-
decodeChunks(chunks, total) {
|
|
33074
33322
|
const bytes = new Uint8Array(total);
|
|
33075
33323
|
let offset = 0;
|
|
33076
33324
|
for (const chunk of chunks) {
|
|
@@ -33079,35 +33327,35 @@ var WABTransport = class {
|
|
|
33079
33327
|
}
|
|
33080
33328
|
return new TextDecoder().decode(bytes);
|
|
33081
33329
|
}
|
|
33082
|
-
|
|
33083
|
-
return new WABClientError("WAB_RESPONSE_TOO_LARGE", "WAB response
|
|
33330
|
+
sizeError(response, responseContext) {
|
|
33331
|
+
return new WABClientError("WAB_RESPONSE_TOO_LARGE", "WAB response exceeds its size limit.", false, response.status, responseContext);
|
|
33084
33332
|
}
|
|
33085
|
-
async
|
|
33333
|
+
async stopBody(response) {
|
|
33086
33334
|
try {
|
|
33087
33335
|
await response.body?.cancel();
|
|
33088
33336
|
} catch {}
|
|
33089
33337
|
}
|
|
33090
|
-
async
|
|
33338
|
+
async stopReader(reader) {
|
|
33091
33339
|
try {
|
|
33092
33340
|
await reader.cancel();
|
|
33093
33341
|
} catch {}
|
|
33094
33342
|
}
|
|
33095
|
-
|
|
33343
|
+
report(metadata, error) {
|
|
33096
33344
|
this.telemetry.capture({
|
|
33097
|
-
name:
|
|
33098
|
-
component:
|
|
33345
|
+
name: `${WAB_REQUEST_EVENT}failed`,
|
|
33346
|
+
component: WAB_COMPONENT,
|
|
33099
33347
|
severity: error.retryable ? "warn" : "error",
|
|
33100
|
-
correlationId,
|
|
33348
|
+
correlationId: metadata.correlationId,
|
|
33101
33349
|
attributes: {
|
|
33102
|
-
operation,
|
|
33103
|
-
method,
|
|
33104
|
-
route: path,
|
|
33350
|
+
operation: metadata.operation,
|
|
33351
|
+
method: metadata.method,
|
|
33352
|
+
route: metadata.path,
|
|
33105
33353
|
serverOrigin: this.serverOrigin,
|
|
33106
33354
|
retryable: error.retryable,
|
|
33107
33355
|
...error.status !== void 0 ? { status: error.status } : {},
|
|
33108
33356
|
...error.endpointMarkerPresent !== void 0 ? { endpointMarkerPresent: error.endpointMarkerPresent } : {},
|
|
33109
33357
|
...error.responseCorrelationMatched !== void 0 ? { responseCorrelationMatched: error.responseCorrelationMatched } : {},
|
|
33110
|
-
durationMs: Date.now() - startedAt
|
|
33358
|
+
durationMs: Date.now() - metadata.startedAt
|
|
33111
33359
|
},
|
|
33112
33360
|
error
|
|
33113
33361
|
});
|
|
@@ -33216,8 +33464,8 @@ var WABClient = class {
|
|
|
33216
33464
|
constructor(serverUrl, options = {}) {
|
|
33217
33465
|
this.transport = new WABTransport(serverUrl, options);
|
|
33218
33466
|
}
|
|
33219
|
-
|
|
33220
|
-
return
|
|
33467
|
+
getInfo() {
|
|
33468
|
+
return this.transport.request("/info", {
|
|
33221
33469
|
method: "GET",
|
|
33222
33470
|
operation: "get-info"
|
|
33223
33471
|
});
|
|
@@ -33227,15 +33475,15 @@ var WABClient = class {
|
|
|
33227
33475
|
}
|
|
33228
33476
|
async startAuthMethod(authMethod, presentationKey, payload, correlationId) {
|
|
33229
33477
|
assertHexIdentifier(presentationKey, "presentationKey");
|
|
33230
|
-
return
|
|
33478
|
+
return authMethod.startAuth(this.transport.serverUrl, presentationKey, payload, this.transport, correlationId);
|
|
33231
33479
|
}
|
|
33232
33480
|
async completeAuthMethod(authMethod, presentationKey, payload, correlationId) {
|
|
33233
33481
|
assertHexIdentifier(presentationKey, "presentationKey");
|
|
33234
|
-
return
|
|
33482
|
+
return authMethod.completeAuth(this.transport.serverUrl, presentationKey, payload, this.transport, correlationId);
|
|
33235
33483
|
}
|
|
33236
33484
|
async listLinkedMethods(presentationKey) {
|
|
33237
33485
|
assertHexIdentifier(presentationKey, "presentationKey");
|
|
33238
|
-
return
|
|
33486
|
+
return this.transport.request("/user/linkedMethods", {
|
|
33239
33487
|
operation: "list-linked-methods",
|
|
33240
33488
|
body: { presentationKey }
|
|
33241
33489
|
});
|
|
@@ -33243,7 +33491,7 @@ var WABClient = class {
|
|
|
33243
33491
|
async unlinkMethod(presentationKey, authMethodId) {
|
|
33244
33492
|
assertHexIdentifier(presentationKey, "presentationKey");
|
|
33245
33493
|
if (!Number.isSafeInteger(authMethodId) || authMethodId <= 0) throw new TypeError("authMethodId must be a positive safe integer.");
|
|
33246
|
-
return
|
|
33494
|
+
return this.transport.request("/user/unlinkMethod", {
|
|
33247
33495
|
operation: "unlink-method",
|
|
33248
33496
|
body: {
|
|
33249
33497
|
presentationKey,
|
|
@@ -33253,14 +33501,14 @@ var WABClient = class {
|
|
|
33253
33501
|
}
|
|
33254
33502
|
async requestFaucet(presentationKey) {
|
|
33255
33503
|
assertHexIdentifier(presentationKey, "presentationKey");
|
|
33256
|
-
return
|
|
33504
|
+
return this.transport.request("/faucet/request", {
|
|
33257
33505
|
operation: "request-faucet",
|
|
33258
33506
|
body: { presentationKey }
|
|
33259
33507
|
});
|
|
33260
33508
|
}
|
|
33261
33509
|
async deleteUser(presentationKey) {
|
|
33262
33510
|
assertHexIdentifier(presentationKey, "presentationKey");
|
|
33263
|
-
return
|
|
33511
|
+
return this.transport.request("/user/delete", {
|
|
33264
33512
|
operation: "delete-user",
|
|
33265
33513
|
body: { presentationKey }
|
|
33266
33514
|
});
|
|
@@ -33269,7 +33517,7 @@ var WABClient = class {
|
|
|
33269
33517
|
assertMethodType(methodType);
|
|
33270
33518
|
assertHexIdentifier(userIdHash, "userIdHash");
|
|
33271
33519
|
const normalizedPayload = normalizeAuthPayload(methodType, payload);
|
|
33272
|
-
return
|
|
33520
|
+
return this.transport.request("/auth/start", {
|
|
33273
33521
|
operation: "start-share-auth",
|
|
33274
33522
|
body: {
|
|
33275
33523
|
methodType,
|
|
@@ -33282,7 +33530,7 @@ var WABClient = class {
|
|
|
33282
33530
|
assertMethodType(methodType);
|
|
33283
33531
|
assertHexIdentifier(userIdHash, "userIdHash");
|
|
33284
33532
|
const normalizedPayload = normalizeAuthPayload(methodType, payload);
|
|
33285
|
-
return
|
|
33533
|
+
return this.transport.request("/share/store", {
|
|
33286
33534
|
operation: "store-share",
|
|
33287
33535
|
body: {
|
|
33288
33536
|
methodType,
|
|
@@ -33296,7 +33544,7 @@ var WABClient = class {
|
|
|
33296
33544
|
assertMethodType(methodType);
|
|
33297
33545
|
assertHexIdentifier(userIdHash, "userIdHash");
|
|
33298
33546
|
const normalizedPayload = normalizeAuthPayload(methodType, payload);
|
|
33299
|
-
return
|
|
33547
|
+
return this.transport.request("/share/retrieve", {
|
|
33300
33548
|
operation: "retrieve-share",
|
|
33301
33549
|
body: {
|
|
33302
33550
|
methodType,
|
|
@@ -33309,7 +33557,7 @@ var WABClient = class {
|
|
|
33309
33557
|
assertMethodType(methodType);
|
|
33310
33558
|
assertHexIdentifier(userIdHash, "userIdHash");
|
|
33311
33559
|
const normalizedPayload = normalizeAuthPayload(methodType, payload);
|
|
33312
|
-
return
|
|
33560
|
+
return this.transport.request("/share/update", {
|
|
33313
33561
|
operation: "update-share",
|
|
33314
33562
|
body: {
|
|
33315
33563
|
methodType,
|
|
@@ -33323,7 +33571,7 @@ var WABClient = class {
|
|
|
33323
33571
|
assertMethodType(methodType);
|
|
33324
33572
|
assertHexIdentifier(userIdHash, "userIdHash");
|
|
33325
33573
|
const normalizedPayload = normalizeAuthPayload(methodType, payload);
|
|
33326
|
-
return
|
|
33574
|
+
return this.transport.request("/share/delete", {
|
|
33327
33575
|
operation: "delete-share-user",
|
|
33328
33576
|
body: {
|
|
33329
33577
|
methodType,
|
|
@@ -33337,9 +33585,13 @@ var WABClient = class {
|
|
|
33337
33585
|
//#region ../src/WalletAuthenticationManager.ts
|
|
33338
33586
|
const DEFAULT_AUTH_SESSION_TTL_MS = 600 * 1e3;
|
|
33339
33587
|
const MAX_AUTH_SESSION_TTL_MS = 3600 * 1e3;
|
|
33588
|
+
const AUTH_COMPONENT = "wallet-toolbox.authentication-manager";
|
|
33589
|
+
const AUTH_EVENT = "wallet-toolbox.authentication.";
|
|
33590
|
+
const EXISTING_USER = "existing-user";
|
|
33591
|
+
const NEW_USER = "new-user";
|
|
33340
33592
|
var WABAccountContinuityError = class extends Error {
|
|
33341
33593
|
code = "WERR_WAB_ACCOUNT_CONTINUITY";
|
|
33342
|
-
constructor(message = "WAB and UMP
|
|
33594
|
+
constructor(message = "WAB and UMP accounts disagree; retry or recover.") {
|
|
33343
33595
|
super(message);
|
|
33344
33596
|
this.name = "WABAccountContinuityError";
|
|
33345
33597
|
}
|
|
@@ -33354,6 +33606,7 @@ var WalletAuthenticationManager = class extends CWIStyleWalletManager {
|
|
|
33354
33606
|
wabClient;
|
|
33355
33607
|
authMethod;
|
|
33356
33608
|
authSession;
|
|
33609
|
+
phoneChangeSession;
|
|
33357
33610
|
authSessionTtlMs;
|
|
33358
33611
|
constructor(...[adminOriginator, walletBuilder, interactor, recoveryKeySaver, passwordRetriever, wabClient, authMethod, stateSnapshot, options = {}]) {
|
|
33359
33612
|
super(adminOriginator, walletBuilder, interactor, recoveryKeySaver, passwordRetriever, async (presentationKey, wallet, adminOriginator) => {
|
|
@@ -33376,7 +33629,7 @@ var WalletAuthenticationManager = class extends CWIStyleWalletManager {
|
|
|
33376
33629
|
description: "Fund wallet",
|
|
33377
33630
|
options: { acceptDelayedBroadcast: false }
|
|
33378
33631
|
}, adminOriginator);
|
|
33379
|
-
if (faucetRedeemTXCreationResult.signableTransaction == null) throw new Error("Faucet redemption
|
|
33632
|
+
if (faucetRedeemTXCreationResult.signableTransaction == null) throw new Error("Faucet redemption was not signable.");
|
|
33380
33633
|
const faucetRedeemTX = Transaction.fromAtomicBEEF(faucetRedeemTXCreationResult.signableTransaction.tx);
|
|
33381
33634
|
const faucetRedemptionPuzzle = new RPuzzle();
|
|
33382
33635
|
const randomRedemptionPrivateKey = PrivateKey.fromRandom();
|
|
@@ -33409,7 +33662,7 @@ var WalletAuthenticationManager = class extends CWIStyleWalletManager {
|
|
|
33409
33662
|
* using the chosen AuthMethodInteractor.
|
|
33410
33663
|
*/
|
|
33411
33664
|
async startAuth(payload) {
|
|
33412
|
-
if (this.authMethod == null) throw new Error("No
|
|
33665
|
+
if (this.authMethod == null) throw new Error("No WAB authentication method selected.");
|
|
33413
33666
|
const authMethod = this.authMethod;
|
|
33414
33667
|
if (this.authenticated) throw new Error("User is already authenticated");
|
|
33415
33668
|
this.cancelAuth();
|
|
@@ -33422,8 +33675,8 @@ var WalletAuthenticationManager = class extends CWIStyleWalletManager {
|
|
|
33422
33675
|
...correlationId !== void 0 ? { correlationId } : {}
|
|
33423
33676
|
};
|
|
33424
33677
|
this.telemetry.capture({
|
|
33425
|
-
name:
|
|
33426
|
-
component:
|
|
33678
|
+
name: `${AUTH_EVENT}wab-start.started`,
|
|
33679
|
+
component: AUTH_COMPONENT,
|
|
33427
33680
|
severity: "debug",
|
|
33428
33681
|
correlationId,
|
|
33429
33682
|
attributes: { methodType: authMethod.methodType }
|
|
@@ -33435,8 +33688,8 @@ var WalletAuthenticationManager = class extends CWIStyleWalletManager {
|
|
|
33435
33688
|
throw new Error(message);
|
|
33436
33689
|
}
|
|
33437
33690
|
this.telemetry.capture({
|
|
33438
|
-
name:
|
|
33439
|
-
component:
|
|
33691
|
+
name: `${AUTH_EVENT}wab-start.completed`,
|
|
33692
|
+
component: AUTH_COMPONENT,
|
|
33440
33693
|
severity: "info",
|
|
33441
33694
|
correlationId,
|
|
33442
33695
|
attributes: { methodType: authMethod.methodType }
|
|
@@ -33444,8 +33697,8 @@ var WalletAuthenticationManager = class extends CWIStyleWalletManager {
|
|
|
33444
33697
|
} catch (error) {
|
|
33445
33698
|
this.cancelAuth();
|
|
33446
33699
|
this.telemetry.capture({
|
|
33447
|
-
name:
|
|
33448
|
-
component:
|
|
33700
|
+
name: `${AUTH_EVENT}wab-start.failed`,
|
|
33701
|
+
component: AUTH_COMPONENT,
|
|
33449
33702
|
severity: "warn",
|
|
33450
33703
|
correlationId,
|
|
33451
33704
|
attributes: { methodType: authMethod.methodType },
|
|
@@ -33458,22 +33711,22 @@ var WalletAuthenticationManager = class extends CWIStyleWalletManager {
|
|
|
33458
33711
|
* Completes the WAB-based flow, retrieving the final presentationKey from WAB if successful.
|
|
33459
33712
|
*/
|
|
33460
33713
|
async completeAuth(payload) {
|
|
33461
|
-
if (this.authMethod == null || this.authSession == null) throw new Error("
|
|
33714
|
+
if (this.authMethod == null || this.authSession == null) throw new Error("Start WAB authentication first.");
|
|
33462
33715
|
const authMethod = this.authMethod;
|
|
33463
33716
|
if (this.authSession.methodType !== authMethod.methodType) {
|
|
33464
33717
|
this.cancelAuth();
|
|
33465
|
-
throw new Error("
|
|
33718
|
+
throw new Error("WAB authentication method changed; restart.");
|
|
33466
33719
|
}
|
|
33467
33720
|
if (Date.now() >= this.authSession.expiresAt) {
|
|
33468
33721
|
this.cancelAuth();
|
|
33469
|
-
throw new Error("
|
|
33722
|
+
throw new Error("WAB authentication expired; restart.");
|
|
33470
33723
|
}
|
|
33471
33724
|
const session = this.authSession;
|
|
33472
33725
|
const result = await this.wabClient.completeAuthMethod(authMethod, session.presentationKey, payload, session.correlationId);
|
|
33473
33726
|
if (result.success !== true || result.presentationKey == null || result.presentationKey.length === 0) {
|
|
33474
33727
|
this.telemetry.capture({
|
|
33475
|
-
name:
|
|
33476
|
-
component:
|
|
33728
|
+
name: `${AUTH_EVENT}wab-complete.rejected`,
|
|
33729
|
+
component: AUTH_COMPONENT,
|
|
33477
33730
|
severity: "warn",
|
|
33478
33731
|
correlationId: session.correlationId,
|
|
33479
33732
|
attributes: { methodType: session.methodType }
|
|
@@ -33488,11 +33741,11 @@ var WalletAuthenticationManager = class extends CWIStyleWalletManager {
|
|
|
33488
33741
|
this.cancelAuth();
|
|
33489
33742
|
const wabAccountStatus = this.inferAccountStatus(result, session.presentationKey);
|
|
33490
33743
|
try {
|
|
33491
|
-
await this.
|
|
33744
|
+
await this.provideWABPresentationKey(result, wabAccountStatus);
|
|
33492
33745
|
} catch (error) {
|
|
33493
33746
|
this.telemetry.capture({
|
|
33494
|
-
name:
|
|
33495
|
-
component:
|
|
33747
|
+
name: `${AUTH_EVENT}ump-continuity.failed`,
|
|
33748
|
+
component: AUTH_COMPONENT,
|
|
33496
33749
|
severity: "warn",
|
|
33497
33750
|
correlationId: session.correlationId,
|
|
33498
33751
|
attributes: {
|
|
@@ -33503,18 +33756,18 @@ var WalletAuthenticationManager = class extends CWIStyleWalletManager {
|
|
|
33503
33756
|
});
|
|
33504
33757
|
throw error;
|
|
33505
33758
|
}
|
|
33506
|
-
if (wabAccountStatus ===
|
|
33759
|
+
if (wabAccountStatus === EXISTING_USER && this.authenticationFlow !== EXISTING_USER) {
|
|
33507
33760
|
super.destroy();
|
|
33508
33761
|
const error = new WABAccountContinuityError();
|
|
33509
33762
|
this.telemetry.capture({
|
|
33510
|
-
name:
|
|
33511
|
-
component:
|
|
33763
|
+
name: `${AUTH_EVENT}account-continuity.mismatch`,
|
|
33764
|
+
component: AUTH_COMPONENT,
|
|
33512
33765
|
severity: "error",
|
|
33513
33766
|
correlationId: session.correlationId,
|
|
33514
33767
|
attributes: {
|
|
33515
33768
|
methodType: session.methodType,
|
|
33516
33769
|
wabAccountStatus,
|
|
33517
|
-
umpAccountStatus:
|
|
33770
|
+
umpAccountStatus: NEW_USER
|
|
33518
33771
|
},
|
|
33519
33772
|
error
|
|
33520
33773
|
});
|
|
@@ -33522,8 +33775,8 @@ var WalletAuthenticationManager = class extends CWIStyleWalletManager {
|
|
|
33522
33775
|
}
|
|
33523
33776
|
const continuity = wabAccountStatus === this.authenticationFlow ? "matched" : "ump-existing";
|
|
33524
33777
|
this.telemetry.capture({
|
|
33525
|
-
name:
|
|
33526
|
-
component:
|
|
33778
|
+
name: `${AUTH_EVENT}completed`,
|
|
33779
|
+
component: AUTH_COMPONENT,
|
|
33527
33780
|
severity: continuity === "matched" ? "info" : "warn",
|
|
33528
33781
|
correlationId: session.correlationId,
|
|
33529
33782
|
attributes: {
|
|
@@ -33537,23 +33790,131 @@ var WalletAuthenticationManager = class extends CWIStyleWalletManager {
|
|
|
33537
33790
|
cancelAuth() {
|
|
33538
33791
|
this.authSession = void 0;
|
|
33539
33792
|
}
|
|
33793
|
+
readPendingPhoneChange(result) {
|
|
33794
|
+
const presentationKey = result.pendingPresentationKey;
|
|
33795
|
+
const changeId = result.pendingPhoneChangeId;
|
|
33796
|
+
if (presentationKey === void 0 && changeId === void 0) return void 0;
|
|
33797
|
+
if (!/^[0-9a-fA-F]{64}$/.test(presentationKey ?? "") || !Number.isSafeInteger(changeId) || changeId <= 0) throw new WABAccountContinuityError("WAB returned invalid pending phone-change data.");
|
|
33798
|
+
return {
|
|
33799
|
+
presentationKey,
|
|
33800
|
+
changeId
|
|
33801
|
+
};
|
|
33802
|
+
}
|
|
33803
|
+
async provideWABPresentationKey(result, wabAccountStatus) {
|
|
33804
|
+
const umpTokenOutpoint = typeof result.umpTokenOutpoint === "string" ? result.umpTokenOutpoint : void 0;
|
|
33805
|
+
const lookupOptions = umpTokenOutpoint == null ? void 0 : { pinnedOutpoint: umpTokenOutpoint };
|
|
33806
|
+
const pending = this.readPendingPhoneChange(result);
|
|
33807
|
+
let usePending = false;
|
|
33808
|
+
try {
|
|
33809
|
+
await this.providePresentationKey(Utils.toArray(result.presentationKey, "hex"), lookupOptions);
|
|
33810
|
+
} catch (error) {
|
|
33811
|
+
if (pending == null) throw error;
|
|
33812
|
+
usePending = true;
|
|
33813
|
+
}
|
|
33814
|
+
if (pending != null && (usePending || wabAccountStatus === EXISTING_USER && this.authenticationFlow !== EXISTING_USER)) {
|
|
33815
|
+
await this.providePresentationKey(Utils.toArray(pending.presentationKey, "hex"), lookupOptions);
|
|
33816
|
+
if (this.authenticationFlow === EXISTING_USER) await this.finalizePendingPhoneChange(result.presentationKey, pending);
|
|
33817
|
+
}
|
|
33818
|
+
}
|
|
33819
|
+
async finalizePendingPhoneChange(currentPresentationKey, pending) {
|
|
33820
|
+
const finalized = await this.phoneChange("finalize", {
|
|
33821
|
+
changeId: pending.changeId,
|
|
33822
|
+
presentationKey: currentPresentationKey,
|
|
33823
|
+
newPresentationKey: pending.presentationKey
|
|
33824
|
+
});
|
|
33825
|
+
if (finalized.success !== true || finalized.changeId !== pending.changeId) throw new WABAccountContinuityError(finalized.message || "WAB could not finalize the pending phone change.");
|
|
33826
|
+
}
|
|
33827
|
+
/**
|
|
33828
|
+
* Starts OTP verification for a replacement phone number. The same number
|
|
33829
|
+
* is valid and intentionally produces a fresh presentation key/hash.
|
|
33830
|
+
*/
|
|
33831
|
+
async startPhoneNumberChange(phoneNumber) {
|
|
33832
|
+
if (!this.authenticated) throw new Error("Not authenticated");
|
|
33833
|
+
const normalizedPhone = phoneNumber.trim();
|
|
33834
|
+
const currentPresentationKey = Utils.toHex(await this.getFactor("presentationKey"));
|
|
33835
|
+
const response = await this.phoneChange("start", {
|
|
33836
|
+
presentationKey: currentPresentationKey,
|
|
33837
|
+
phoneNumber: normalizedPhone
|
|
33838
|
+
});
|
|
33839
|
+
if (response.success !== true) throw new Error(response.message || "Phone change failed");
|
|
33840
|
+
this.phoneChangeSession = {
|
|
33841
|
+
phoneNumber: normalizedPhone,
|
|
33842
|
+
presentationKey: currentPresentationKey
|
|
33843
|
+
};
|
|
33844
|
+
}
|
|
33845
|
+
/**
|
|
33846
|
+
* Completes phone verification and stages the WAB association before
|
|
33847
|
+
* publishing the UMP key rotation. WAB retains both the current and pending
|
|
33848
|
+
* presentation keys until finalization, so either side of an interrupted
|
|
33849
|
+
* transition remains recoverable on the next verified login.
|
|
33850
|
+
*/
|
|
33851
|
+
async completePhoneNumberChange(otp) {
|
|
33852
|
+
const session = this.phoneChangeSession;
|
|
33853
|
+
if (session == null) throw new Error("No phone change");
|
|
33854
|
+
if (session.changeToken == null) {
|
|
33855
|
+
const authorization = await this.phoneChange("complete", {
|
|
33856
|
+
presentationKey: session.presentationKey,
|
|
33857
|
+
phoneNumber: session.phoneNumber,
|
|
33858
|
+
otp: otp.trim()
|
|
33859
|
+
});
|
|
33860
|
+
if (authorization.success !== true) throw new Error(authorization.message || "Phone change failed");
|
|
33861
|
+
if (/^[0-9a-fA-F]{64}$/.test(authorization.pendingPresentationKey ?? "") && Number.isSafeInteger(authorization.pendingPhoneChangeId) && authorization.pendingPhoneChangeId > 0) {
|
|
33862
|
+
session.newKey = Utils.toArray(authorization.pendingPresentationKey, "hex");
|
|
33863
|
+
session.changeId = authorization.pendingPhoneChangeId;
|
|
33864
|
+
} else if (typeof authorization.changeToken === "string" && authorization.changeToken.length > 0) session.changeToken = authorization.changeToken;
|
|
33865
|
+
else throw new Error(authorization.message || "Phone change failed");
|
|
33866
|
+
}
|
|
33867
|
+
session.newKey ??= Random(32);
|
|
33868
|
+
if (session.changeId == null) {
|
|
33869
|
+
const committed = await this.phoneChange("commit", {
|
|
33870
|
+
changeToken: session.changeToken,
|
|
33871
|
+
presentationKey: session.presentationKey,
|
|
33872
|
+
newPresentationKey: Utils.toHex(session.newKey)
|
|
33873
|
+
});
|
|
33874
|
+
if (committed.success !== true || !Number.isSafeInteger(committed.changeId) || committed.changeId <= 0) throw new Error(committed.message || "Phone change failed");
|
|
33875
|
+
session.changeId = committed.changeId;
|
|
33876
|
+
}
|
|
33877
|
+
const changeId = session.changeId;
|
|
33878
|
+
if (session.umpUpdated !== true) {
|
|
33879
|
+
await this.changePresentationKey(session.newKey);
|
|
33880
|
+
session.umpUpdated = true;
|
|
33881
|
+
}
|
|
33882
|
+
const finalized = await this.phoneChange("finalize", {
|
|
33883
|
+
changeId,
|
|
33884
|
+
presentationKey: session.presentationKey,
|
|
33885
|
+
newPresentationKey: Utils.toHex(session.newKey)
|
|
33886
|
+
});
|
|
33887
|
+
if (finalized.success !== true || finalized.changeId !== changeId) throw new Error(finalized.message || "Phone change failed");
|
|
33888
|
+
this.phoneChangeSession = void 0;
|
|
33889
|
+
return { changeId };
|
|
33890
|
+
}
|
|
33891
|
+
cancelPhoneNumberChange() {
|
|
33892
|
+
this.phoneChangeSession = void 0;
|
|
33893
|
+
}
|
|
33540
33894
|
destroy() {
|
|
33541
33895
|
this.cancelAuth();
|
|
33896
|
+
this.cancelPhoneNumberChange();
|
|
33542
33897
|
super.destroy();
|
|
33543
33898
|
}
|
|
33899
|
+
phoneChange(phase, body) {
|
|
33900
|
+
return this.wabClient.transport.request(`/auth/phone-change/${phase}`, {
|
|
33901
|
+
operation: "phone-change",
|
|
33902
|
+
body
|
|
33903
|
+
});
|
|
33904
|
+
}
|
|
33544
33905
|
inferAccountStatus(result, temporaryPresentationKey) {
|
|
33545
33906
|
if (result.presentationKey == null) throw new WABAccountContinuityError("WAB did not return a presentation key.");
|
|
33546
33907
|
const keyMatchesTemporary = this.constantTimeHexEqual(result.presentationKey, temporaryPresentationKey);
|
|
33547
33908
|
const rawAccountStatus = result.accountStatus;
|
|
33548
|
-
if (rawAccountStatus !== void 0 && rawAccountStatus !==
|
|
33909
|
+
if (rawAccountStatus !== void 0 && rawAccountStatus !== NEW_USER && rawAccountStatus !== EXISTING_USER) throw new WABAccountContinuityError("WAB returned an invalid account status.");
|
|
33549
33910
|
const rawExistingUser = result.existingUser;
|
|
33550
|
-
if (rawExistingUser !== void 0 && typeof rawExistingUser !== "boolean") throw new WABAccountContinuityError("WAB returned
|
|
33551
|
-
if (rawAccountStatus !== void 0 && rawExistingUser !== void 0 && rawAccountStatus ===
|
|
33911
|
+
if (rawExistingUser !== void 0 && typeof rawExistingUser !== "boolean") throw new WABAccountContinuityError("WAB returned invalid existing-user data.");
|
|
33912
|
+
if (rawAccountStatus !== void 0 && rawExistingUser !== void 0 && rawAccountStatus === EXISTING_USER !== rawExistingUser) throw new WABAccountContinuityError("WAB returned conflicting account status.");
|
|
33552
33913
|
let compatibilityStatus;
|
|
33553
|
-
if (typeof rawExistingUser === "boolean") compatibilityStatus = rawExistingUser ?
|
|
33914
|
+
if (typeof rawExistingUser === "boolean") compatibilityStatus = rawExistingUser ? EXISTING_USER : NEW_USER;
|
|
33554
33915
|
const explicitStatus = rawAccountStatus ?? compatibilityStatus;
|
|
33555
|
-
if (explicitStatus ===
|
|
33556
|
-
return explicitStatus ?? (keyMatchesTemporary ?
|
|
33916
|
+
if (explicitStatus === NEW_USER && !keyMatchesTemporary || explicitStatus === EXISTING_USER && keyMatchesTemporary) throw new WABAccountContinuityError("WAB returned conflicting account status.");
|
|
33917
|
+
return explicitStatus ?? (keyMatchesTemporary ? NEW_USER : EXISTING_USER);
|
|
33557
33918
|
}
|
|
33558
33919
|
constantTimeHexEqual(left, right) {
|
|
33559
33920
|
if (left.length !== right.length) return false;
|
|
@@ -36781,6 +37142,6 @@ var WalletPermissionsManager = class WalletPermissionsManager {
|
|
|
36781
37142
|
}
|
|
36782
37143
|
};
|
|
36783
37144
|
//#endregion
|
|
36784
|
-
export { ARGON2ID_DEFAULT_HASH_LENGTH, ARGON2ID_DEFAULT_ITERATIONS, ARGON2ID_DEFAULT_MEMORY_KIB, ARGON2ID_DEFAULT_PARALLELISM, ARGON2ID_MAX_ITERATIONS, ARGON2ID_MAX_MEMORY_KIB, ARGON2ID_MAX_PARALLELISM, AuthMethodInteractor, BHServiceClient, BRC153_REFERENCE_PREFIX, BulkFileDataManager, BulkFileDataReader, BulkFilesReader, BulkFilesReaderFs, BulkFilesReaderStorage, BulkHeaderFile, BulkHeaderFileFs, BulkHeaderFileStorage, BulkHeaderFiles, BulkIngestorBase, BulkIngestorCDN, BulkIngestorCDNBabbage, BulkIngestorChaintracks, BulkIngestorWhatsOnChainCdn, BulkStorageBase, CWIStyleWalletManager, Chaintracks, ChaintracksChainTracker, ChaintracksFetch, ChaintracksFetchError, ChaintracksServiceClient, ChaintracksStorageBase, ChaintracksStorageIdb, ChaintracksStorageNoDb, DEFAULT_MANAGED_CHANGE_MAX_OUTPUTS_PER_ACTION, DEFAULT_MANAGED_CHANGE_MIGRATION_INPUTS_PER_ACTION, DEFAULT_MANAGED_CHANGE_MINIMUM_SATOSHIS, DEFAULT_MANAGED_CHANGE_PENDING_COMPARISON_INPUTS, DEFAULT_MANAGED_CHANGE_TARGET_UTXOS, DEFAULT_PROFILE_ID, DEFAULT_SETTINGS, DevConsoleInteractor, EntityBase, EntityCertificate, EntityCertificateField, EntityCommission, EntityOutput, EntityOutputBasket, EntityOutputTag, EntityOutputTagMap, EntityProvenTx, EntityProvenTxReq, EntitySyncState, EntityTransaction, EntityTxLabel, EntityTxLabelMap, EntityUser, FixedWindowBulkFileDownloadBudget, GoChaintracksServiceClient, HeightRange, KDF_MAX_HASH_LENGTH, LEGACY_MANAGED_CHANGE_MINIMUM_SATOSHIS, LiveIngestorBase, LiveIngestorChaintracksSSE, LiveIngestorWhatsOnChainPoll, LocalChainTracker, MAX_STATE_SNAPSHOT_BYTES, MergeEntity, Monitor, OverlayUMPTokenInteractor, PBKDF2_MAX_ITERATIONS, PBKDF2_NUM_ROUNDS, PersonaIDInteractor, PrivilegedKeyManager, ScriptTemplateBRC29, Services, SetupClient, SimpleWalletManager, StorageClient, StorageIdb, StorageProvider, StorageSyncReader, TESTNET_DEFAULT_SETTINGS, TwilioPhoneInteractor, UMPTokenLookupError, WABAccountContinuityError, WABClient, WABClientError, WABTransport, Wallet, WalletAuthenticationManager, WalletLogger, WalletPermissionsManager, WalletSettingsManager, WalletSigner, WalletStorageManager, WhatsOnChainServices, applyBrc153ReferenceLabel, arcDefaultUrl, arcGorillaPoolUrl, arcadeDefaultUrl, arraysEqual, asArray, asBsvSdkPrivateKey, asBsvSdkPublickKey, asBsvSdkScript, asBsvSdkTx, asString, asUint8Array, brc29ProtocolID, buildChaintracksOptionsWithIngestors, convertProofToMerklePath, createAndStartDefaultChaintracks, createDefaultBulkFileDataManager, createDefaultChaintracksClient, createDefaultChaintracksStorageOptions, createDefaultIdbChaintracksOptions, createDefaultNoDbChaintracksOptions, createDefaultWalletServicesOptions, createIdbChaintracks, createNoDbChaintracks, createSyncMap, decryptBRC39, defaultManagedChangePolicy, doubleSha256BE, doubleSha256LE, encryptBRC39, exportBRC38, exportBRC38Json, exportBRC39, getIdentityKey, getLabelToSpecOp, getListOutputsSpecOp, importBRC38, importBRC39, isAutoSpendableChangeOutput, isBaseBlockHeader, isBlockHeader, isBrc153ReferenceLabel, isLegacyManagedChangeBasketDefault, isLive, isLiveBlockHeader, isManagedChangeOutput, logCreateActionArgs, logWalletError, logger, makeAtomicBeef, makeBrc114ActionTimeLabel, makeBrc153ReferenceLabel, managedChangeOutputFields, maxDate, optionalArraysEqual, outputColumnsWithoutLockingScript, parseBRC38Json, parseBrc114ActionTimeLabels, parseBrc153ReferenceLabel, parseFileLink, parseTxScriptOffsets, partitionActionLabels, randomBytes, randomBytesBase64, randomBytesHex, resolveDefaultChaintracksArguments, sdk_exports as sdk, selectBulkHeaderFiles, sha256Hash, stampLog, stampLogFormat, startChaintracks, tableAuthSessionToPeerSession, throwDummyReviewActions, toBinaryBaseBlockHeader, toDefaultChaintracksArguments, toLookupNetworkPreset, toWalletNetwork, transactionColumnsWithoutRawTx, upgradeLegacyManagedChangeBasketDefault, blockHeaderUtilities_exports as utils, validateManagedChangePolicy, validateScriptHash, validateSecondsSinceEpoch, validateStorageFeeModel, verifyHexString, verifyId, verifyInteger, verifyNumber, verifyOne, verifyOneOrNone, verifyOptionalHexString, verifyTruthy, wait, wocGetHeadersHeaderToBlockHeader };
|
|
37145
|
+
export { ARGON2ID_DEFAULT_HASH_LENGTH, ARGON2ID_DEFAULT_ITERATIONS, ARGON2ID_DEFAULT_MEMORY_KIB, ARGON2ID_DEFAULT_PARALLELISM, ARGON2ID_MAX_ITERATIONS, ARGON2ID_MAX_MEMORY_KIB, ARGON2ID_MAX_PARALLELISM, AuthMethodInteractor, BHServiceClient, BRC153_REFERENCE_PREFIX, BulkFileDataManager, BulkFileDataReader, BulkFileDataValidationError, BulkFilesReader, BulkFilesReaderFs, BulkFilesReaderStorage, BulkHeaderFile, BulkHeaderFileFs, BulkHeaderFileStorage, BulkHeaderFiles, BulkIngestorBase, BulkIngestorCDN, BulkIngestorCDNBabbage, BulkIngestorChaintracks, BulkIngestorWhatsOnChainCdn, BulkStorageBase, CWIStyleWalletManager, Chaintracks, ChaintracksChainTracker, ChaintracksFetch, ChaintracksFetchError, ChaintracksServiceClient, ChaintracksStorageBase, ChaintracksStorageIdb, ChaintracksStorageNoDb, DEFAULT_MANAGED_CHANGE_MAX_OUTPUTS_PER_ACTION, DEFAULT_MANAGED_CHANGE_MIGRATION_INPUTS_PER_ACTION, DEFAULT_MANAGED_CHANGE_MINIMUM_SATOSHIS, DEFAULT_MANAGED_CHANGE_PENDING_COMPARISON_INPUTS, DEFAULT_MANAGED_CHANGE_TARGET_UTXOS, DEFAULT_PROFILE_ID, DEFAULT_SETTINGS, DevConsoleInteractor, EntityBase, EntityCertificate, EntityCertificateField, EntityCommission, EntityOutput, EntityOutputBasket, EntityOutputTag, EntityOutputTagMap, EntityProvenTx, EntityProvenTxReq, EntitySyncState, EntityTransaction, EntityTxLabel, EntityTxLabelMap, EntityUser, FixedWindowBulkFileDownloadBudget, GoChaintracksServiceClient, HeightRange, InlineBulkFileDataValidator, KDF_MAX_HASH_LENGTH, LEGACY_MANAGED_CHANGE_MINIMUM_SATOSHIS, LiveIngestorBase, LiveIngestorChaintracksSSE, LiveIngestorWhatsOnChainPoll, LocalChainTracker, MAX_STATE_SNAPSHOT_BYTES, MergeEntity, Monitor, OverlayUMPTokenInteractor, PBKDF2_MAX_ITERATIONS, PBKDF2_NUM_ROUNDS, PersonaIDInteractor, PrivilegedKeyManager, ScriptTemplateBRC29, Services, SetupClient, SimpleWalletManager, StorageClient, StorageIdb, StorageProvider, StorageSyncReader, TESTNET_DEFAULT_SETTINGS, TwilioPhoneInteractor, UMPTokenLookupError, WABAccountContinuityError, WABClient, WABClientError, WABTransport, Wallet, WalletAuthenticationManager, WalletLogger, WalletPermissionsManager, WalletSettingsManager, WalletSigner, WalletStorageManager, WhatsOnChainServices, applyBrc153ReferenceLabel, arcDefaultUrl, arcGorillaPoolUrl, arcadeDefaultUrl, arraysEqual, asArray, asBsvSdkPrivateKey, asBsvSdkPublickKey, asBsvSdkScript, asBsvSdkTx, asString, asUint8Array, brc29ProtocolID, buildChaintracksOptionsWithIngestors, convertProofToMerklePath, createAndStartDefaultChaintracks, createDefaultBulkFileDataManager, createDefaultChaintracksClient, createDefaultChaintracksStorageOptions, createDefaultIdbChaintracksOptions, createDefaultNoDbChaintracksOptions, createDefaultWalletServicesOptions, createIdbChaintracks, createNoDbChaintracks, createSyncMap, decryptBRC39, defaultManagedChangePolicy, doubleSha256BE, doubleSha256LE, encryptBRC39, exportBRC38, exportBRC38Json, exportBRC39, getIdentityKey, getLabelToSpecOp, getListOutputsSpecOp, importBRC38, importBRC39, isAutoSpendableChangeOutput, isBaseBlockHeader, isBlockHeader, isBrc153ReferenceLabel, isLegacyManagedChangeBasketDefault, isLive, isLiveBlockHeader, isManagedChangeOutput, logCreateActionArgs, logWalletError, logger, makeAtomicBeef, makeBrc114ActionTimeLabel, makeBrc153ReferenceLabel, managedChangeOutputFields, maxDate, optionalArraysEqual, outputColumnsWithoutLockingScript, parseBRC38Json, parseBrc114ActionTimeLabels, parseBrc153ReferenceLabel, parseFileLink, parseTxScriptOffsets, partitionActionLabels, randomBytes, randomBytesBase64, randomBytesHex, resolveDefaultChaintracksArguments, sdk_exports as sdk, selectBulkHeaderFiles, sha256Hash, stampLog, stampLogFormat, startChaintracks, tableAuthSessionToPeerSession, throwDummyReviewActions, toBinaryBaseBlockHeader, toDefaultChaintracksArguments, toLookupNetworkPreset, toWalletNetwork, transactionColumnsWithoutRawTx, upgradeLegacyManagedChangeBasketDefault, blockHeaderUtilities_exports as utils, validateManagedChangePolicy, validateScriptHash, validateSecondsSinceEpoch, validateStorageFeeModel, verifyHexString, verifyId, verifyInteger, verifyNumber, verifyOne, verifyOneOrNone, verifyOptionalHexString, verifyTruthy, wait, wocGetHeadersHeaderToBlockHeader };
|
|
36785
37146
|
|
|
36786
37147
|
//# sourceMappingURL=index.client.mjs.map
|