@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/core/index.cjs
CHANGED
|
@@ -3116,9 +3116,14 @@ function planOps(params) {
|
|
|
3116
3116
|
ops.push({ plainKey, wireKey: wk, baseVersion: cur.version, sealVersion: cur.version + 1, isDelete: false, value });
|
|
3117
3117
|
}
|
|
3118
3118
|
}
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3119
|
+
const sweptWire = /* @__PURE__ */ new Set();
|
|
3120
|
+
for (const tokenId of params.tombstonedTokenIds ?? []) {
|
|
3121
|
+
const wk = wireKeyOf(tokenId);
|
|
3122
|
+
if (sweptWire.has(wk)) continue;
|
|
3123
|
+
sweptWire.add(wk);
|
|
3124
|
+
const cur = known.get(wk);
|
|
3125
|
+
if (cur && !cur.deleted && !liveWire.has(wk) && !reserved?.has(wk)) {
|
|
3126
|
+
ops.push({ plainKey: tokenId, wireKey: wk, baseVersion: cur.version, sealVersion: cur.version + 1, isDelete: true });
|
|
3122
3127
|
}
|
|
3123
3128
|
}
|
|
3124
3129
|
return ops;
|
|
@@ -3230,10 +3235,13 @@ var LoadDeltaTracker = class {
|
|
|
3230
3235
|
*/
|
|
3231
3236
|
async beginLoad(known) {
|
|
3232
3237
|
this.baseline = await this.loadBaseline();
|
|
3233
|
-
this.
|
|
3234
|
-
|
|
3235
|
-
this.baseline
|
|
3238
|
+
if (this.baseline) {
|
|
3239
|
+
const entries = new Map(this.baseline.entries ?? []);
|
|
3240
|
+
const sigOk = verifyRoot(this.bindingFor(this.baseline.cursor, this.baseline.root), this.baseline.sig, this.ownerPubForVerify());
|
|
3241
|
+
const entriesOk = (this.baseline.entries?.length ?? 0) > 0 && computeRoot(entries) === this.baseline.root;
|
|
3242
|
+
if (!sigOk || !entriesOk) this.baseline = null;
|
|
3236
3243
|
}
|
|
3244
|
+
this.accState = this.baseline ? new Map(this.baseline.entries ?? []) : new Map(known);
|
|
3237
3245
|
}
|
|
3238
3246
|
/**
|
|
3239
3247
|
* Re-arm the tracker for a SANCTIONED-RESET re-pass (Task 8.2). After a verified
|
|
@@ -3265,7 +3273,7 @@ var LoadDeltaTracker = class {
|
|
|
3265
3273
|
if (!mono.ok) return mono;
|
|
3266
3274
|
this.fold(page.entries);
|
|
3267
3275
|
if (this.baseline) {
|
|
3268
|
-
const verdict = this.
|
|
3276
|
+
const verdict = this.checkRegression();
|
|
3269
3277
|
if (!verdict.ok) return verdict;
|
|
3270
3278
|
}
|
|
3271
3279
|
return { ok: true, reset: false };
|
|
@@ -3301,14 +3309,22 @@ var LoadDeltaTracker = class {
|
|
|
3301
3309
|
this.accState = state;
|
|
3302
3310
|
}
|
|
3303
3311
|
/**
|
|
3304
|
-
*
|
|
3305
|
-
*
|
|
3306
|
-
*
|
|
3312
|
+
* Multi-device anti-rollback (finding: vault-anti-rollback-single-writer). Alarm
|
|
3313
|
+
* ONLY on a REGRESSION of a committed entry — a baseline key now MISSING from, or
|
|
3314
|
+
* at a LOWER version than, the folded server state. New keys and higher versions
|
|
3315
|
+
* are legitimate FORWARD progress (this OR another device wrote them), never a
|
|
3316
|
+
* rollback. Vault versions are strictly monotonic per key (create→update→delete→
|
|
3317
|
+
* resurrect all increment), so a version going backwards (or a known entry
|
|
3318
|
+
* vanishing) is the rollback signature, independent of which device authored the
|
|
3319
|
+
* forward writes. (The old root-EQUALITY check assumed a SINGLE writer and so
|
|
3320
|
+
* false-alarmed on every peer-device write — which broke multi-device sync.)
|
|
3307
3321
|
*/
|
|
3308
|
-
|
|
3309
|
-
const
|
|
3310
|
-
|
|
3311
|
-
|
|
3322
|
+
checkRegression() {
|
|
3323
|
+
for (const [key, base] of this.baseline.entries ?? []) {
|
|
3324
|
+
const cur = this.accState.get(key);
|
|
3325
|
+
if (!cur || cur.version < base.version) {
|
|
3326
|
+
return { ok: false, reason: "rollback" };
|
|
3327
|
+
}
|
|
3312
3328
|
}
|
|
3313
3329
|
return { ok: true };
|
|
3314
3330
|
}
|
|
@@ -3328,9 +3344,10 @@ var LoadDeltaTracker = class {
|
|
|
3328
3344
|
*/
|
|
3329
3345
|
async commitBaseline(cursor, epoch, _epochSig) {
|
|
3330
3346
|
if (this.config.baseline && this.walletPriv) {
|
|
3331
|
-
const
|
|
3347
|
+
const acc = this.accState ?? /* @__PURE__ */ new Map();
|
|
3348
|
+
const root = computeRoot(acc);
|
|
3332
3349
|
const sig = signRoot(this.walletPriv, this.bindingFor(cursor, root));
|
|
3333
|
-
const baseline = { cursor, root, sig, epoch };
|
|
3350
|
+
const baseline = { cursor, root, sig, epoch, entries: [...acc] };
|
|
3334
3351
|
await this.config.baseline.set(this.baselineKey(), JSON.stringify(baseline));
|
|
3335
3352
|
}
|
|
3336
3353
|
this.accState = null;
|
|
@@ -3347,7 +3364,7 @@ var LoadDeltaTracker = class {
|
|
|
3347
3364
|
const root = computeRoot(known);
|
|
3348
3365
|
const epoch = this.baseline?.epoch ?? 0;
|
|
3349
3366
|
const sig = signRoot(this.walletPriv, this.bindingFor(cursor, root));
|
|
3350
|
-
const baseline = { cursor, root, sig, epoch };
|
|
3367
|
+
const baseline = { cursor, root, sig, epoch, entries: [...known] };
|
|
3351
3368
|
this.baseline = baseline;
|
|
3352
3369
|
await this.config.baseline.set(this.baselineKey(), JSON.stringify(baseline));
|
|
3353
3370
|
}
|
|
@@ -3595,7 +3612,13 @@ var RemoteTokenStorageProvider = class {
|
|
|
3595
3612
|
this.wakeClient = new VaultWakeClient({
|
|
3596
3613
|
vaultUrl: this.config.vaultUrl,
|
|
3597
3614
|
auth: this.auth,
|
|
3598
|
-
|
|
3615
|
+
// Only wake when this provider is actually connected. A stale wake (e.g. fired
|
|
3616
|
+
// after shutdown/identity-switch, before stop() lands) must NOT drive a receive/
|
|
3617
|
+
// sync against torn-down wallet storage ("IndexedDBStorageProvider not connected").
|
|
3618
|
+
onWake: () => {
|
|
3619
|
+
if (this.status !== "connected") return;
|
|
3620
|
+
this.emit("storage:remote-updated", { source: "vault-wake" });
|
|
3621
|
+
}
|
|
3599
3622
|
});
|
|
3600
3623
|
this.wakeClient.start();
|
|
3601
3624
|
}
|
|
@@ -3679,11 +3702,13 @@ var RemoteTokenStorageProvider = class {
|
|
|
3679
3702
|
}
|
|
3680
3703
|
/** Diff the TXF snapshot vs internal last-known state into a list of CAS ops. */
|
|
3681
3704
|
planFlush(localData) {
|
|
3705
|
+
const tombstones = localData._tombstones ?? [];
|
|
3682
3706
|
return planOps({
|
|
3683
3707
|
tokens: extractTokens(localData),
|
|
3684
3708
|
known: this.known,
|
|
3685
3709
|
wireKeyOf: (plainKey) => this.wireKeyFor(plainKey),
|
|
3686
|
-
changed: (wk, value) => this.hasChanged(wk, value)
|
|
3710
|
+
changed: (wk, value) => this.hasChanged(wk, value),
|
|
3711
|
+
tombstonedTokenIds: tombstones.map((t) => t.tokenId)
|
|
3687
3712
|
});
|
|
3688
3713
|
}
|
|
3689
3714
|
hasChanged(wk, value) {
|
|
@@ -4095,6 +4120,16 @@ var RemoteTokenStorageProvider = class {
|
|
|
4095
4120
|
for (const e of this.known.values()) if (!e.deleted) n += 1;
|
|
4096
4121
|
return n;
|
|
4097
4122
|
}
|
|
4123
|
+
/**
|
|
4124
|
+
* True if the vault's last-known view has this token's wireKey TOMBSTONED — i.e.
|
|
4125
|
+
* another device SPENT it. PaymentsModule.load uses this to DROP a stale local
|
|
4126
|
+
* token the vault marked spent, so the `delete-resurrect` sync path can't
|
|
4127
|
+
* re-create it and inflate the balance (finding: vault-cross-device-resurrect).
|
|
4128
|
+
* `plainKey` is the genesis tokenId (v2TokenId === engine.tokenId === keyFromTokenId base).
|
|
4129
|
+
*/
|
|
4130
|
+
isPlainKeyDeleted(plainKey) {
|
|
4131
|
+
return this.known.get(this.wireKeyFor(plainKey))?.deleted === true;
|
|
4132
|
+
}
|
|
4098
4133
|
/** Snapshot the internal last-known state as a merkle `EntryState` map. */
|
|
4099
4134
|
knownAsEntryState() {
|
|
4100
4135
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -13388,26 +13423,52 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
13388
13423
|
await TokenRegistry.waitForReady();
|
|
13389
13424
|
const providers = this.getTokenStorageProviders();
|
|
13390
13425
|
const ownAddresses = await this.ownMetaAddresses();
|
|
13426
|
+
let mergedData = null;
|
|
13427
|
+
const mergedHistory = [];
|
|
13428
|
+
const tombKeys = /* @__PURE__ */ new Set();
|
|
13391
13429
|
for (const [id, provider] of providers) {
|
|
13392
|
-
|
|
13393
|
-
|
|
13394
|
-
|
|
13395
|
-
|
|
13396
|
-
|
|
13397
|
-
|
|
13398
|
-
|
|
13399
|
-
|
|
13400
|
-
|
|
13401
|
-
|
|
13402
|
-
|
|
13403
|
-
|
|
13404
|
-
|
|
13430
|
+
const result = await provider.load().catch((err) => {
|
|
13431
|
+
logger.error("Payments", `Failed to load from provider ${id}:`, err);
|
|
13432
|
+
return null;
|
|
13433
|
+
});
|
|
13434
|
+
if (!result || !result.success || !result.data) continue;
|
|
13435
|
+
const data = result.data;
|
|
13436
|
+
if (!this.isOwnAddress(data?._meta?.address, ownAddresses)) {
|
|
13437
|
+
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)`);
|
|
13438
|
+
continue;
|
|
13439
|
+
}
|
|
13440
|
+
if (!mergedData) mergedData = { _meta: data._meta };
|
|
13441
|
+
for (const [k, v] of Object.entries(data)) {
|
|
13442
|
+
if (k === "_meta" || k === "_history" || k === "_tombstones") continue;
|
|
13443
|
+
if (!(k in mergedData)) mergedData[k] = v;
|
|
13444
|
+
}
|
|
13445
|
+
for (const t of data._tombstones ?? []) {
|
|
13446
|
+
const tk = `${t.tokenId}:${t.stateHash}`;
|
|
13447
|
+
if (tombKeys.has(tk)) continue;
|
|
13448
|
+
tombKeys.add(tk);
|
|
13449
|
+
(mergedData._tombstones ??= []).push(t);
|
|
13450
|
+
}
|
|
13451
|
+
if (data._history?.length) mergedHistory.push(...data._history);
|
|
13452
|
+
logger.debug("Payments", `Merged token data from provider ${id}`);
|
|
13453
|
+
}
|
|
13454
|
+
if (mergedData) {
|
|
13455
|
+
const deletionCheckers = [...providers.values()].filter(
|
|
13456
|
+
(p) => typeof p.isPlainKeyDeleted === "function"
|
|
13457
|
+
);
|
|
13458
|
+
if (deletionCheckers.length > 0) {
|
|
13459
|
+
for (const k of Object.keys(mergedData)) {
|
|
13460
|
+
if (!/^_[0-9a-f]{64}$/i.test(k)) continue;
|
|
13461
|
+
const plainKey = k.slice(1);
|
|
13462
|
+
if (deletionCheckers.some((p) => p.isPlainKeyDeleted(plainKey))) {
|
|
13463
|
+
delete mergedData[k];
|
|
13464
|
+
logger.debug("Payments", `load(): dropping ${plainKey.slice(0, 12)}\u2026 \u2014 vault marks it spent (cross-device)`);
|
|
13405
13465
|
}
|
|
13406
|
-
logger.debug("Payments", `Loaded metadata from provider ${id}`);
|
|
13407
|
-
break;
|
|
13408
13466
|
}
|
|
13409
|
-
}
|
|
13410
|
-
|
|
13467
|
+
}
|
|
13468
|
+
this.loadFromStorageData(mergedData);
|
|
13469
|
+
await this.rebuildParsedTokenCache();
|
|
13470
|
+
if (mergedHistory.length > 0) {
|
|
13471
|
+
await this.importRemoteHistoryEntries(mergedHistory);
|
|
13411
13472
|
}
|
|
13412
13473
|
}
|
|
13413
13474
|
for (const [id, token] of this.tokens) {
|
|
@@ -15221,12 +15282,22 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
15221
15282
|
}
|
|
15222
15283
|
this.syncDebounceTimer = setTimeout(() => {
|
|
15223
15284
|
this.syncDebounceTimer = null;
|
|
15285
|
+
const storage = this.deps?.storage;
|
|
15286
|
+
if (storage?.isConnected && !storage.isConnected()) {
|
|
15287
|
+
logger.debug("Payments", "Remote-update wake ignored \u2014 wallet storage not connected");
|
|
15288
|
+
return;
|
|
15289
|
+
}
|
|
15224
15290
|
void (async () => {
|
|
15225
15291
|
try {
|
|
15226
15292
|
await this.receive();
|
|
15227
15293
|
} catch (err) {
|
|
15228
15294
|
logger.debug("Payments", "Auto-receive from remote update failed:", err);
|
|
15229
15295
|
}
|
|
15296
|
+
try {
|
|
15297
|
+
await this.load();
|
|
15298
|
+
} catch (err) {
|
|
15299
|
+
logger.debug("Payments", "Auto-load (reconcile) from remote update failed:", err);
|
|
15300
|
+
}
|
|
15230
15301
|
try {
|
|
15231
15302
|
const result = await this.sync();
|
|
15232
15303
|
const data = eventData;
|