@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.js
CHANGED
|
@@ -3013,9 +3013,14 @@ function planOps(params) {
|
|
|
3013
3013
|
ops.push({ plainKey, wireKey: wk, baseVersion: cur.version, sealVersion: cur.version + 1, isDelete: false, value });
|
|
3014
3014
|
}
|
|
3015
3015
|
}
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3016
|
+
const sweptWire = /* @__PURE__ */ new Set();
|
|
3017
|
+
for (const tokenId of params.tombstonedTokenIds ?? []) {
|
|
3018
|
+
const wk = wireKeyOf(tokenId);
|
|
3019
|
+
if (sweptWire.has(wk)) continue;
|
|
3020
|
+
sweptWire.add(wk);
|
|
3021
|
+
const cur = known.get(wk);
|
|
3022
|
+
if (cur && !cur.deleted && !liveWire.has(wk) && !reserved?.has(wk)) {
|
|
3023
|
+
ops.push({ plainKey: tokenId, wireKey: wk, baseVersion: cur.version, sealVersion: cur.version + 1, isDelete: true });
|
|
3019
3024
|
}
|
|
3020
3025
|
}
|
|
3021
3026
|
return ops;
|
|
@@ -3127,10 +3132,13 @@ var LoadDeltaTracker = class {
|
|
|
3127
3132
|
*/
|
|
3128
3133
|
async beginLoad(known) {
|
|
3129
3134
|
this.baseline = await this.loadBaseline();
|
|
3130
|
-
this.
|
|
3131
|
-
|
|
3132
|
-
this.baseline
|
|
3135
|
+
if (this.baseline) {
|
|
3136
|
+
const entries = new Map(this.baseline.entries ?? []);
|
|
3137
|
+
const sigOk = verifyRoot(this.bindingFor(this.baseline.cursor, this.baseline.root), this.baseline.sig, this.ownerPubForVerify());
|
|
3138
|
+
const entriesOk = (this.baseline.entries?.length ?? 0) > 0 && computeRoot(entries) === this.baseline.root;
|
|
3139
|
+
if (!sigOk || !entriesOk) this.baseline = null;
|
|
3133
3140
|
}
|
|
3141
|
+
this.accState = this.baseline ? new Map(this.baseline.entries ?? []) : new Map(known);
|
|
3134
3142
|
}
|
|
3135
3143
|
/**
|
|
3136
3144
|
* Re-arm the tracker for a SANCTIONED-RESET re-pass (Task 8.2). After a verified
|
|
@@ -3162,7 +3170,7 @@ var LoadDeltaTracker = class {
|
|
|
3162
3170
|
if (!mono.ok) return mono;
|
|
3163
3171
|
this.fold(page.entries);
|
|
3164
3172
|
if (this.baseline) {
|
|
3165
|
-
const verdict = this.
|
|
3173
|
+
const verdict = this.checkRegression();
|
|
3166
3174
|
if (!verdict.ok) return verdict;
|
|
3167
3175
|
}
|
|
3168
3176
|
return { ok: true, reset: false };
|
|
@@ -3198,14 +3206,22 @@ var LoadDeltaTracker = class {
|
|
|
3198
3206
|
this.accState = state;
|
|
3199
3207
|
}
|
|
3200
3208
|
/**
|
|
3201
|
-
*
|
|
3202
|
-
*
|
|
3203
|
-
*
|
|
3209
|
+
* Multi-device anti-rollback (finding: vault-anti-rollback-single-writer). Alarm
|
|
3210
|
+
* ONLY on a REGRESSION of a committed entry — a baseline key now MISSING from, or
|
|
3211
|
+
* at a LOWER version than, the folded server state. New keys and higher versions
|
|
3212
|
+
* are legitimate FORWARD progress (this OR another device wrote them), never a
|
|
3213
|
+
* rollback. Vault versions are strictly monotonic per key (create→update→delete→
|
|
3214
|
+
* resurrect all increment), so a version going backwards (or a known entry
|
|
3215
|
+
* vanishing) is the rollback signature, independent of which device authored the
|
|
3216
|
+
* forward writes. (The old root-EQUALITY check assumed a SINGLE writer and so
|
|
3217
|
+
* false-alarmed on every peer-device write — which broke multi-device sync.)
|
|
3204
3218
|
*/
|
|
3205
|
-
|
|
3206
|
-
const
|
|
3207
|
-
|
|
3208
|
-
|
|
3219
|
+
checkRegression() {
|
|
3220
|
+
for (const [key, base] of this.baseline.entries ?? []) {
|
|
3221
|
+
const cur = this.accState.get(key);
|
|
3222
|
+
if (!cur || cur.version < base.version) {
|
|
3223
|
+
return { ok: false, reason: "rollback" };
|
|
3224
|
+
}
|
|
3209
3225
|
}
|
|
3210
3226
|
return { ok: true };
|
|
3211
3227
|
}
|
|
@@ -3225,9 +3241,10 @@ var LoadDeltaTracker = class {
|
|
|
3225
3241
|
*/
|
|
3226
3242
|
async commitBaseline(cursor, epoch, _epochSig) {
|
|
3227
3243
|
if (this.config.baseline && this.walletPriv) {
|
|
3228
|
-
const
|
|
3244
|
+
const acc = this.accState ?? /* @__PURE__ */ new Map();
|
|
3245
|
+
const root = computeRoot(acc);
|
|
3229
3246
|
const sig = signRoot(this.walletPriv, this.bindingFor(cursor, root));
|
|
3230
|
-
const baseline = { cursor, root, sig, epoch };
|
|
3247
|
+
const baseline = { cursor, root, sig, epoch, entries: [...acc] };
|
|
3231
3248
|
await this.config.baseline.set(this.baselineKey(), JSON.stringify(baseline));
|
|
3232
3249
|
}
|
|
3233
3250
|
this.accState = null;
|
|
@@ -3244,7 +3261,7 @@ var LoadDeltaTracker = class {
|
|
|
3244
3261
|
const root = computeRoot(known);
|
|
3245
3262
|
const epoch = this.baseline?.epoch ?? 0;
|
|
3246
3263
|
const sig = signRoot(this.walletPriv, this.bindingFor(cursor, root));
|
|
3247
|
-
const baseline = { cursor, root, sig, epoch };
|
|
3264
|
+
const baseline = { cursor, root, sig, epoch, entries: [...known] };
|
|
3248
3265
|
this.baseline = baseline;
|
|
3249
3266
|
await this.config.baseline.set(this.baselineKey(), JSON.stringify(baseline));
|
|
3250
3267
|
}
|
|
@@ -3492,7 +3509,13 @@ var RemoteTokenStorageProvider = class {
|
|
|
3492
3509
|
this.wakeClient = new VaultWakeClient({
|
|
3493
3510
|
vaultUrl: this.config.vaultUrl,
|
|
3494
3511
|
auth: this.auth,
|
|
3495
|
-
|
|
3512
|
+
// Only wake when this provider is actually connected. A stale wake (e.g. fired
|
|
3513
|
+
// after shutdown/identity-switch, before stop() lands) must NOT drive a receive/
|
|
3514
|
+
// sync against torn-down wallet storage ("IndexedDBStorageProvider not connected").
|
|
3515
|
+
onWake: () => {
|
|
3516
|
+
if (this.status !== "connected") return;
|
|
3517
|
+
this.emit("storage:remote-updated", { source: "vault-wake" });
|
|
3518
|
+
}
|
|
3496
3519
|
});
|
|
3497
3520
|
this.wakeClient.start();
|
|
3498
3521
|
}
|
|
@@ -3576,11 +3599,13 @@ var RemoteTokenStorageProvider = class {
|
|
|
3576
3599
|
}
|
|
3577
3600
|
/** Diff the TXF snapshot vs internal last-known state into a list of CAS ops. */
|
|
3578
3601
|
planFlush(localData) {
|
|
3602
|
+
const tombstones = localData._tombstones ?? [];
|
|
3579
3603
|
return planOps({
|
|
3580
3604
|
tokens: extractTokens(localData),
|
|
3581
3605
|
known: this.known,
|
|
3582
3606
|
wireKeyOf: (plainKey) => this.wireKeyFor(plainKey),
|
|
3583
|
-
changed: (wk, value) => this.hasChanged(wk, value)
|
|
3607
|
+
changed: (wk, value) => this.hasChanged(wk, value),
|
|
3608
|
+
tombstonedTokenIds: tombstones.map((t) => t.tokenId)
|
|
3584
3609
|
});
|
|
3585
3610
|
}
|
|
3586
3611
|
hasChanged(wk, value) {
|
|
@@ -3992,6 +4017,16 @@ var RemoteTokenStorageProvider = class {
|
|
|
3992
4017
|
for (const e of this.known.values()) if (!e.deleted) n += 1;
|
|
3993
4018
|
return n;
|
|
3994
4019
|
}
|
|
4020
|
+
/**
|
|
4021
|
+
* True if the vault's last-known view has this token's wireKey TOMBSTONED — i.e.
|
|
4022
|
+
* another device SPENT it. PaymentsModule.load uses this to DROP a stale local
|
|
4023
|
+
* token the vault marked spent, so the `delete-resurrect` sync path can't
|
|
4024
|
+
* re-create it and inflate the balance (finding: vault-cross-device-resurrect).
|
|
4025
|
+
* `plainKey` is the genesis tokenId (v2TokenId === engine.tokenId === keyFromTokenId base).
|
|
4026
|
+
*/
|
|
4027
|
+
isPlainKeyDeleted(plainKey) {
|
|
4028
|
+
return this.known.get(this.wireKeyFor(plainKey))?.deleted === true;
|
|
4029
|
+
}
|
|
3995
4030
|
/** Snapshot the internal last-known state as a merkle `EntryState` map. */
|
|
3996
4031
|
knownAsEntryState() {
|
|
3997
4032
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -13307,26 +13342,52 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
13307
13342
|
await TokenRegistry.waitForReady();
|
|
13308
13343
|
const providers = this.getTokenStorageProviders();
|
|
13309
13344
|
const ownAddresses = await this.ownMetaAddresses();
|
|
13345
|
+
let mergedData = null;
|
|
13346
|
+
const mergedHistory = [];
|
|
13347
|
+
const tombKeys = /* @__PURE__ */ new Set();
|
|
13310
13348
|
for (const [id, provider] of providers) {
|
|
13311
|
-
|
|
13312
|
-
|
|
13313
|
-
|
|
13314
|
-
|
|
13315
|
-
|
|
13316
|
-
|
|
13317
|
-
|
|
13318
|
-
|
|
13319
|
-
|
|
13320
|
-
|
|
13321
|
-
|
|
13322
|
-
|
|
13323
|
-
|
|
13349
|
+
const result = await provider.load().catch((err) => {
|
|
13350
|
+
logger.error("Payments", `Failed to load from provider ${id}:`, err);
|
|
13351
|
+
return null;
|
|
13352
|
+
});
|
|
13353
|
+
if (!result || !result.success || !result.data) continue;
|
|
13354
|
+
const data = result.data;
|
|
13355
|
+
if (!this.isOwnAddress(data?._meta?.address, ownAddresses)) {
|
|
13356
|
+
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)`);
|
|
13357
|
+
continue;
|
|
13358
|
+
}
|
|
13359
|
+
if (!mergedData) mergedData = { _meta: data._meta };
|
|
13360
|
+
for (const [k, v] of Object.entries(data)) {
|
|
13361
|
+
if (k === "_meta" || k === "_history" || k === "_tombstones") continue;
|
|
13362
|
+
if (!(k in mergedData)) mergedData[k] = v;
|
|
13363
|
+
}
|
|
13364
|
+
for (const t of data._tombstones ?? []) {
|
|
13365
|
+
const tk = `${t.tokenId}:${t.stateHash}`;
|
|
13366
|
+
if (tombKeys.has(tk)) continue;
|
|
13367
|
+
tombKeys.add(tk);
|
|
13368
|
+
(mergedData._tombstones ??= []).push(t);
|
|
13369
|
+
}
|
|
13370
|
+
if (data._history?.length) mergedHistory.push(...data._history);
|
|
13371
|
+
logger.debug("Payments", `Merged token data from provider ${id}`);
|
|
13372
|
+
}
|
|
13373
|
+
if (mergedData) {
|
|
13374
|
+
const deletionCheckers = [...providers.values()].filter(
|
|
13375
|
+
(p) => typeof p.isPlainKeyDeleted === "function"
|
|
13376
|
+
);
|
|
13377
|
+
if (deletionCheckers.length > 0) {
|
|
13378
|
+
for (const k of Object.keys(mergedData)) {
|
|
13379
|
+
if (!/^_[0-9a-f]{64}$/i.test(k)) continue;
|
|
13380
|
+
const plainKey = k.slice(1);
|
|
13381
|
+
if (deletionCheckers.some((p) => p.isPlainKeyDeleted(plainKey))) {
|
|
13382
|
+
delete mergedData[k];
|
|
13383
|
+
logger.debug("Payments", `load(): dropping ${plainKey.slice(0, 12)}\u2026 \u2014 vault marks it spent (cross-device)`);
|
|
13324
13384
|
}
|
|
13325
|
-
logger.debug("Payments", `Loaded metadata from provider ${id}`);
|
|
13326
|
-
break;
|
|
13327
13385
|
}
|
|
13328
|
-
}
|
|
13329
|
-
|
|
13386
|
+
}
|
|
13387
|
+
this.loadFromStorageData(mergedData);
|
|
13388
|
+
await this.rebuildParsedTokenCache();
|
|
13389
|
+
if (mergedHistory.length > 0) {
|
|
13390
|
+
await this.importRemoteHistoryEntries(mergedHistory);
|
|
13330
13391
|
}
|
|
13331
13392
|
}
|
|
13332
13393
|
for (const [id, token] of this.tokens) {
|
|
@@ -15140,12 +15201,22 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
15140
15201
|
}
|
|
15141
15202
|
this.syncDebounceTimer = setTimeout(() => {
|
|
15142
15203
|
this.syncDebounceTimer = null;
|
|
15204
|
+
const storage = this.deps?.storage;
|
|
15205
|
+
if (storage?.isConnected && !storage.isConnected()) {
|
|
15206
|
+
logger.debug("Payments", "Remote-update wake ignored \u2014 wallet storage not connected");
|
|
15207
|
+
return;
|
|
15208
|
+
}
|
|
15143
15209
|
void (async () => {
|
|
15144
15210
|
try {
|
|
15145
15211
|
await this.receive();
|
|
15146
15212
|
} catch (err) {
|
|
15147
15213
|
logger.debug("Payments", "Auto-receive from remote update failed:", err);
|
|
15148
15214
|
}
|
|
15215
|
+
try {
|
|
15216
|
+
await this.load();
|
|
15217
|
+
} catch (err) {
|
|
15218
|
+
logger.debug("Payments", "Auto-load (reconcile) from remote update failed:", err);
|
|
15219
|
+
}
|
|
15149
15220
|
try {
|
|
15150
15221
|
const result = await this.sync();
|
|
15151
15222
|
const data = eventData;
|