@unicitylabs/sphere-sdk 0.9.2-dev.0 → 0.9.2-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/index.cjs +107 -36
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.js +107 -36
- package/dist/core/index.js.map +1 -1
- package/dist/index.cjs +107 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +107 -36
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3310,9 +3310,14 @@ function planOps(params) {
|
|
|
3310
3310
|
ops.push({ plainKey, wireKey: wk, baseVersion: cur.version, sealVersion: cur.version + 1, isDelete: false, value });
|
|
3311
3311
|
}
|
|
3312
3312
|
}
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3313
|
+
const sweptWire = /* @__PURE__ */ new Set();
|
|
3314
|
+
for (const tokenId of params.tombstonedTokenIds ?? []) {
|
|
3315
|
+
const wk = wireKeyOf(tokenId);
|
|
3316
|
+
if (sweptWire.has(wk)) continue;
|
|
3317
|
+
sweptWire.add(wk);
|
|
3318
|
+
const cur = known.get(wk);
|
|
3319
|
+
if (cur && !cur.deleted && !liveWire.has(wk) && !reserved?.has(wk)) {
|
|
3320
|
+
ops.push({ plainKey: tokenId, wireKey: wk, baseVersion: cur.version, sealVersion: cur.version + 1, isDelete: true });
|
|
3316
3321
|
}
|
|
3317
3322
|
}
|
|
3318
3323
|
return ops;
|
|
@@ -3424,10 +3429,13 @@ var LoadDeltaTracker = class {
|
|
|
3424
3429
|
*/
|
|
3425
3430
|
async beginLoad(known) {
|
|
3426
3431
|
this.baseline = await this.loadBaseline();
|
|
3427
|
-
this.
|
|
3428
|
-
|
|
3429
|
-
this.baseline
|
|
3432
|
+
if (this.baseline) {
|
|
3433
|
+
const entries = new Map(this.baseline.entries ?? []);
|
|
3434
|
+
const sigOk = verifyRoot(this.bindingFor(this.baseline.cursor, this.baseline.root), this.baseline.sig, this.ownerPubForVerify());
|
|
3435
|
+
const entriesOk = (this.baseline.entries?.length ?? 0) > 0 && computeRoot(entries) === this.baseline.root;
|
|
3436
|
+
if (!sigOk || !entriesOk) this.baseline = null;
|
|
3430
3437
|
}
|
|
3438
|
+
this.accState = this.baseline ? new Map(this.baseline.entries ?? []) : new Map(known);
|
|
3431
3439
|
}
|
|
3432
3440
|
/**
|
|
3433
3441
|
* Re-arm the tracker for a SANCTIONED-RESET re-pass (Task 8.2). After a verified
|
|
@@ -3459,7 +3467,7 @@ var LoadDeltaTracker = class {
|
|
|
3459
3467
|
if (!mono.ok) return mono;
|
|
3460
3468
|
this.fold(page.entries);
|
|
3461
3469
|
if (this.baseline) {
|
|
3462
|
-
const verdict = this.
|
|
3470
|
+
const verdict = this.checkRegression();
|
|
3463
3471
|
if (!verdict.ok) return verdict;
|
|
3464
3472
|
}
|
|
3465
3473
|
return { ok: true, reset: false };
|
|
@@ -3495,14 +3503,22 @@ var LoadDeltaTracker = class {
|
|
|
3495
3503
|
this.accState = state;
|
|
3496
3504
|
}
|
|
3497
3505
|
/**
|
|
3498
|
-
*
|
|
3499
|
-
*
|
|
3500
|
-
*
|
|
3506
|
+
* Multi-device anti-rollback (finding: vault-anti-rollback-single-writer). Alarm
|
|
3507
|
+
* ONLY on a REGRESSION of a committed entry — a baseline key now MISSING from, or
|
|
3508
|
+
* at a LOWER version than, the folded server state. New keys and higher versions
|
|
3509
|
+
* are legitimate FORWARD progress (this OR another device wrote them), never a
|
|
3510
|
+
* rollback. Vault versions are strictly monotonic per key (create→update→delete→
|
|
3511
|
+
* resurrect all increment), so a version going backwards (or a known entry
|
|
3512
|
+
* vanishing) is the rollback signature, independent of which device authored the
|
|
3513
|
+
* forward writes. (The old root-EQUALITY check assumed a SINGLE writer and so
|
|
3514
|
+
* false-alarmed on every peer-device write — which broke multi-device sync.)
|
|
3501
3515
|
*/
|
|
3502
|
-
|
|
3503
|
-
const
|
|
3504
|
-
|
|
3505
|
-
|
|
3516
|
+
checkRegression() {
|
|
3517
|
+
for (const [key, base] of this.baseline.entries ?? []) {
|
|
3518
|
+
const cur = this.accState.get(key);
|
|
3519
|
+
if (!cur || cur.version < base.version) {
|
|
3520
|
+
return { ok: false, reason: "rollback" };
|
|
3521
|
+
}
|
|
3506
3522
|
}
|
|
3507
3523
|
return { ok: true };
|
|
3508
3524
|
}
|
|
@@ -3522,9 +3538,10 @@ var LoadDeltaTracker = class {
|
|
|
3522
3538
|
*/
|
|
3523
3539
|
async commitBaseline(cursor, epoch, _epochSig) {
|
|
3524
3540
|
if (this.config.baseline && this.walletPriv) {
|
|
3525
|
-
const
|
|
3541
|
+
const acc = this.accState ?? /* @__PURE__ */ new Map();
|
|
3542
|
+
const root = computeRoot(acc);
|
|
3526
3543
|
const sig = signRoot(this.walletPriv, this.bindingFor(cursor, root));
|
|
3527
|
-
const baseline = { cursor, root, sig, epoch };
|
|
3544
|
+
const baseline = { cursor, root, sig, epoch, entries: [...acc] };
|
|
3528
3545
|
await this.config.baseline.set(this.baselineKey(), JSON.stringify(baseline));
|
|
3529
3546
|
}
|
|
3530
3547
|
this.accState = null;
|
|
@@ -3541,7 +3558,7 @@ var LoadDeltaTracker = class {
|
|
|
3541
3558
|
const root = computeRoot(known);
|
|
3542
3559
|
const epoch = this.baseline?.epoch ?? 0;
|
|
3543
3560
|
const sig = signRoot(this.walletPriv, this.bindingFor(cursor, root));
|
|
3544
|
-
const baseline = { cursor, root, sig, epoch };
|
|
3561
|
+
const baseline = { cursor, root, sig, epoch, entries: [...known] };
|
|
3545
3562
|
this.baseline = baseline;
|
|
3546
3563
|
await this.config.baseline.set(this.baselineKey(), JSON.stringify(baseline));
|
|
3547
3564
|
}
|
|
@@ -3789,7 +3806,13 @@ var RemoteTokenStorageProvider = class {
|
|
|
3789
3806
|
this.wakeClient = new VaultWakeClient({
|
|
3790
3807
|
vaultUrl: this.config.vaultUrl,
|
|
3791
3808
|
auth: this.auth,
|
|
3792
|
-
|
|
3809
|
+
// Only wake when this provider is actually connected. A stale wake (e.g. fired
|
|
3810
|
+
// after shutdown/identity-switch, before stop() lands) must NOT drive a receive/
|
|
3811
|
+
// sync against torn-down wallet storage ("IndexedDBStorageProvider not connected").
|
|
3812
|
+
onWake: () => {
|
|
3813
|
+
if (this.status !== "connected") return;
|
|
3814
|
+
this.emit("storage:remote-updated", { source: "vault-wake" });
|
|
3815
|
+
}
|
|
3793
3816
|
});
|
|
3794
3817
|
this.wakeClient.start();
|
|
3795
3818
|
}
|
|
@@ -3873,11 +3896,13 @@ var RemoteTokenStorageProvider = class {
|
|
|
3873
3896
|
}
|
|
3874
3897
|
/** Diff the TXF snapshot vs internal last-known state into a list of CAS ops. */
|
|
3875
3898
|
planFlush(localData) {
|
|
3899
|
+
const tombstones = localData._tombstones ?? [];
|
|
3876
3900
|
return planOps({
|
|
3877
3901
|
tokens: extractTokens(localData),
|
|
3878
3902
|
known: this.known,
|
|
3879
3903
|
wireKeyOf: (plainKey) => this.wireKeyFor(plainKey),
|
|
3880
|
-
changed: (wk, value) => this.hasChanged(wk, value)
|
|
3904
|
+
changed: (wk, value) => this.hasChanged(wk, value),
|
|
3905
|
+
tombstonedTokenIds: tombstones.map((t) => t.tokenId)
|
|
3881
3906
|
});
|
|
3882
3907
|
}
|
|
3883
3908
|
hasChanged(wk, value) {
|
|
@@ -4289,6 +4314,16 @@ var RemoteTokenStorageProvider = class {
|
|
|
4289
4314
|
for (const e of this.known.values()) if (!e.deleted) n += 1;
|
|
4290
4315
|
return n;
|
|
4291
4316
|
}
|
|
4317
|
+
/**
|
|
4318
|
+
* True if the vault's last-known view has this token's wireKey TOMBSTONED — i.e.
|
|
4319
|
+
* another device SPENT it. PaymentsModule.load uses this to DROP a stale local
|
|
4320
|
+
* token the vault marked spent, so the `delete-resurrect` sync path can't
|
|
4321
|
+
* re-create it and inflate the balance (finding: vault-cross-device-resurrect).
|
|
4322
|
+
* `plainKey` is the genesis tokenId (v2TokenId === engine.tokenId === keyFromTokenId base).
|
|
4323
|
+
*/
|
|
4324
|
+
isPlainKeyDeleted(plainKey) {
|
|
4325
|
+
return this.known.get(this.wireKeyFor(plainKey))?.deleted === true;
|
|
4326
|
+
}
|
|
4292
4327
|
/** Snapshot the internal last-known state as a merkle `EntryState` map. */
|
|
4293
4328
|
knownAsEntryState() {
|
|
4294
4329
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -13839,26 +13874,52 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
13839
13874
|
await TokenRegistry.waitForReady();
|
|
13840
13875
|
const providers = this.getTokenStorageProviders();
|
|
13841
13876
|
const ownAddresses = await this.ownMetaAddresses();
|
|
13877
|
+
let mergedData = null;
|
|
13878
|
+
const mergedHistory = [];
|
|
13879
|
+
const tombKeys = /* @__PURE__ */ new Set();
|
|
13842
13880
|
for (const [id, provider] of providers) {
|
|
13843
|
-
|
|
13844
|
-
|
|
13845
|
-
|
|
13846
|
-
|
|
13847
|
-
|
|
13848
|
-
|
|
13849
|
-
|
|
13850
|
-
|
|
13851
|
-
|
|
13852
|
-
|
|
13853
|
-
|
|
13854
|
-
|
|
13855
|
-
|
|
13881
|
+
const result = await provider.load().catch((err) => {
|
|
13882
|
+
logger.error("Payments", `Failed to load from provider ${id}:`, err);
|
|
13883
|
+
return null;
|
|
13884
|
+
});
|
|
13885
|
+
if (!result || !result.success || !result.data) continue;
|
|
13886
|
+
const data = result.data;
|
|
13887
|
+
if (!this.isOwnAddress(data?._meta?.address, ownAddresses)) {
|
|
13888
|
+
logger.warn("Payments", `Load: rejecting data from provider ${id} \u2014 address mismatch (got=${data._meta?.address?.slice(0, 20)}... expected one of this wallet's addresses)`);
|
|
13889
|
+
continue;
|
|
13890
|
+
}
|
|
13891
|
+
if (!mergedData) mergedData = { _meta: data._meta };
|
|
13892
|
+
for (const [k, v] of Object.entries(data)) {
|
|
13893
|
+
if (k === "_meta" || k === "_history" || k === "_tombstones") continue;
|
|
13894
|
+
if (!(k in mergedData)) mergedData[k] = v;
|
|
13895
|
+
}
|
|
13896
|
+
for (const t of data._tombstones ?? []) {
|
|
13897
|
+
const tk = `${t.tokenId}:${t.stateHash}`;
|
|
13898
|
+
if (tombKeys.has(tk)) continue;
|
|
13899
|
+
tombKeys.add(tk);
|
|
13900
|
+
(mergedData._tombstones ??= []).push(t);
|
|
13901
|
+
}
|
|
13902
|
+
if (data._history?.length) mergedHistory.push(...data._history);
|
|
13903
|
+
logger.debug("Payments", `Merged token data from provider ${id}`);
|
|
13904
|
+
}
|
|
13905
|
+
if (mergedData) {
|
|
13906
|
+
const deletionCheckers = [...providers.values()].filter(
|
|
13907
|
+
(p) => typeof p.isPlainKeyDeleted === "function"
|
|
13908
|
+
);
|
|
13909
|
+
if (deletionCheckers.length > 0) {
|
|
13910
|
+
for (const k of Object.keys(mergedData)) {
|
|
13911
|
+
if (!/^_[0-9a-f]{64}$/i.test(k)) continue;
|
|
13912
|
+
const plainKey = k.slice(1);
|
|
13913
|
+
if (deletionCheckers.some((p) => p.isPlainKeyDeleted(plainKey))) {
|
|
13914
|
+
delete mergedData[k];
|
|
13915
|
+
logger.debug("Payments", `load(): dropping ${plainKey.slice(0, 12)}\u2026 \u2014 vault marks it spent (cross-device)`);
|
|
13856
13916
|
}
|
|
13857
|
-
logger.debug("Payments", `Loaded metadata from provider ${id}`);
|
|
13858
|
-
break;
|
|
13859
13917
|
}
|
|
13860
|
-
}
|
|
13861
|
-
|
|
13918
|
+
}
|
|
13919
|
+
this.loadFromStorageData(mergedData);
|
|
13920
|
+
await this.rebuildParsedTokenCache();
|
|
13921
|
+
if (mergedHistory.length > 0) {
|
|
13922
|
+
await this.importRemoteHistoryEntries(mergedHistory);
|
|
13862
13923
|
}
|
|
13863
13924
|
}
|
|
13864
13925
|
for (const [id, token] of this.tokens) {
|
|
@@ -15672,12 +15733,22 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
15672
15733
|
}
|
|
15673
15734
|
this.syncDebounceTimer = setTimeout(() => {
|
|
15674
15735
|
this.syncDebounceTimer = null;
|
|
15736
|
+
const storage = this.deps?.storage;
|
|
15737
|
+
if (storage?.isConnected && !storage.isConnected()) {
|
|
15738
|
+
logger.debug("Payments", "Remote-update wake ignored \u2014 wallet storage not connected");
|
|
15739
|
+
return;
|
|
15740
|
+
}
|
|
15675
15741
|
void (async () => {
|
|
15676
15742
|
try {
|
|
15677
15743
|
await this.receive();
|
|
15678
15744
|
} catch (err) {
|
|
15679
15745
|
logger.debug("Payments", "Auto-receive from remote update failed:", err);
|
|
15680
15746
|
}
|
|
15747
|
+
try {
|
|
15748
|
+
await this.load();
|
|
15749
|
+
} catch (err) {
|
|
15750
|
+
logger.debug("Payments", "Auto-load (reconcile) from remote update failed:", err);
|
|
15751
|
+
}
|
|
15681
15752
|
try {
|
|
15682
15753
|
const result = await this.sync();
|
|
15683
15754
|
const data = eventData;
|