@unicitylabs/sphere-sdk 0.5.0 → 0.5.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/connect/index.cjs +3 -1
- package/dist/connect/index.cjs.map +1 -1
- package/dist/connect/index.js +3 -1
- package/dist/connect/index.js.map +1 -1
- package/dist/core/index.cjs +205 -41
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +22 -0
- package/dist/core/index.d.ts +22 -0
- package/dist/core/index.js +205 -41
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/connect/index.cjs +3 -1
- package/dist/impl/browser/connect/index.cjs.map +1 -1
- package/dist/impl/browser/connect/index.js +3 -1
- package/dist/impl/browser/connect/index.js.map +1 -1
- package/dist/impl/browser/index.cjs +3 -1
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js +3 -1
- package/dist/impl/browser/index.js.map +1 -1
- package/dist/impl/browser/ipfs.cjs +3 -1
- package/dist/impl/browser/ipfs.cjs.map +1 -1
- package/dist/impl/browser/ipfs.js +3 -1
- package/dist/impl/browser/ipfs.js.map +1 -1
- package/dist/impl/nodejs/connect/index.cjs +3 -1
- package/dist/impl/nodejs/connect/index.cjs.map +1 -1
- package/dist/impl/nodejs/connect/index.js +3 -1
- package/dist/impl/nodejs/connect/index.js.map +1 -1
- package/dist/impl/nodejs/index.cjs +3 -1
- package/dist/impl/nodejs/index.cjs.map +1 -1
- package/dist/impl/nodejs/index.js +3 -1
- package/dist/impl/nodejs/index.js.map +1 -1
- package/dist/index.cjs +205 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +205 -41
- package/dist/index.js.map +1 -1
- package/dist/l1/index.cjs +3 -1
- package/dist/l1/index.cjs.map +1 -1
- package/dist/l1/index.js +3 -1
- package/dist/l1/index.js.map +1 -1
- package/package.json +1 -1
package/dist/core/index.d.cts
CHANGED
|
@@ -1928,6 +1928,11 @@ declare class PaymentsModule {
|
|
|
1928
1928
|
private proofPollingInterval;
|
|
1929
1929
|
private static readonly PROOF_POLLING_INTERVAL_MS;
|
|
1930
1930
|
private static readonly PROOF_POLLING_MAX_ATTEMPTS;
|
|
1931
|
+
private resolveUnconfirmedTimer;
|
|
1932
|
+
private static readonly RESOLVE_UNCONFIRMED_INTERVAL_MS;
|
|
1933
|
+
private loadedPromise;
|
|
1934
|
+
private loaded;
|
|
1935
|
+
private processedSplitGroupIds;
|
|
1931
1936
|
private storageEventUnsubscribers;
|
|
1932
1937
|
private syncDebounceTimer;
|
|
1933
1938
|
private static readonly SYNC_DEBOUNCE_MS;
|
|
@@ -2225,6 +2230,13 @@ declare class PaymentsModule {
|
|
|
2225
2230
|
* @returns Summary with counts of resolved, still-pending, and failed tokens plus per-token details.
|
|
2226
2231
|
*/
|
|
2227
2232
|
resolveUnconfirmed(): Promise<UnconfirmedResolutionResult>;
|
|
2233
|
+
/**
|
|
2234
|
+
* Start a periodic interval that retries resolveUnconfirmed() until all
|
|
2235
|
+
* tokens are confirmed or failed. Stops automatically when nothing is
|
|
2236
|
+
* pending and is cleaned up by destroy().
|
|
2237
|
+
*/
|
|
2238
|
+
private scheduleResolveUnconfirmed;
|
|
2239
|
+
private stopResolveUnconfirmedPolling;
|
|
2228
2240
|
/**
|
|
2229
2241
|
* Process a single V5 token through its finalization stages with quick-timeout proof checks.
|
|
2230
2242
|
*/
|
|
@@ -2258,6 +2270,16 @@ declare class PaymentsModule {
|
|
|
2258
2270
|
* Called during load() to restore tokens that TXF format can't represent.
|
|
2259
2271
|
*/
|
|
2260
2272
|
private loadPendingV5Tokens;
|
|
2273
|
+
/**
|
|
2274
|
+
* Persist the set of processed splitGroupIds to KV storage.
|
|
2275
|
+
* This ensures Nostr re-deliveries are ignored across page reloads,
|
|
2276
|
+
* even when the confirmed token's in-memory ID differs from v5split_{id}.
|
|
2277
|
+
*/
|
|
2278
|
+
private saveProcessedSplitGroupIds;
|
|
2279
|
+
/**
|
|
2280
|
+
* Load processed splitGroupIds from KV storage.
|
|
2281
|
+
*/
|
|
2282
|
+
private loadProcessedSplitGroupIds;
|
|
2261
2283
|
/**
|
|
2262
2284
|
* Add a token to the wallet.
|
|
2263
2285
|
*
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1928,6 +1928,11 @@ declare class PaymentsModule {
|
|
|
1928
1928
|
private proofPollingInterval;
|
|
1929
1929
|
private static readonly PROOF_POLLING_INTERVAL_MS;
|
|
1930
1930
|
private static readonly PROOF_POLLING_MAX_ATTEMPTS;
|
|
1931
|
+
private resolveUnconfirmedTimer;
|
|
1932
|
+
private static readonly RESOLVE_UNCONFIRMED_INTERVAL_MS;
|
|
1933
|
+
private loadedPromise;
|
|
1934
|
+
private loaded;
|
|
1935
|
+
private processedSplitGroupIds;
|
|
1931
1936
|
private storageEventUnsubscribers;
|
|
1932
1937
|
private syncDebounceTimer;
|
|
1933
1938
|
private static readonly SYNC_DEBOUNCE_MS;
|
|
@@ -2225,6 +2230,13 @@ declare class PaymentsModule {
|
|
|
2225
2230
|
* @returns Summary with counts of resolved, still-pending, and failed tokens plus per-token details.
|
|
2226
2231
|
*/
|
|
2227
2232
|
resolveUnconfirmed(): Promise<UnconfirmedResolutionResult>;
|
|
2233
|
+
/**
|
|
2234
|
+
* Start a periodic interval that retries resolveUnconfirmed() until all
|
|
2235
|
+
* tokens are confirmed or failed. Stops automatically when nothing is
|
|
2236
|
+
* pending and is cleaned up by destroy().
|
|
2237
|
+
*/
|
|
2238
|
+
private scheduleResolveUnconfirmed;
|
|
2239
|
+
private stopResolveUnconfirmedPolling;
|
|
2228
2240
|
/**
|
|
2229
2241
|
* Process a single V5 token through its finalization stages with quick-timeout proof checks.
|
|
2230
2242
|
*/
|
|
@@ -2258,6 +2270,16 @@ declare class PaymentsModule {
|
|
|
2258
2270
|
* Called during load() to restore tokens that TXF format can't represent.
|
|
2259
2271
|
*/
|
|
2260
2272
|
private loadPendingV5Tokens;
|
|
2273
|
+
/**
|
|
2274
|
+
* Persist the set of processed splitGroupIds to KV storage.
|
|
2275
|
+
* This ensures Nostr re-deliveries are ignored across page reloads,
|
|
2276
|
+
* even when the confirmed token's in-memory ID differs from v5split_{id}.
|
|
2277
|
+
*/
|
|
2278
|
+
private saveProcessedSplitGroupIds;
|
|
2279
|
+
/**
|
|
2280
|
+
* Load processed splitGroupIds from KV storage.
|
|
2281
|
+
*/
|
|
2282
|
+
private loadProcessedSplitGroupIds;
|
|
2261
2283
|
/**
|
|
2262
2284
|
* Add a token to the wallet.
|
|
2263
2285
|
*
|
package/dist/core/index.js
CHANGED
|
@@ -87,7 +87,9 @@ var init_constants = __esm({
|
|
|
87
87
|
/** Group chat: members for this address */
|
|
88
88
|
GROUP_CHAT_MEMBERS: "group_chat_members",
|
|
89
89
|
/** Group chat: processed event IDs for deduplication */
|
|
90
|
-
GROUP_CHAT_PROCESSED_EVENTS: "group_chat_processed_events"
|
|
90
|
+
GROUP_CHAT_PROCESSED_EVENTS: "group_chat_processed_events",
|
|
91
|
+
/** Processed V5 split group IDs for Nostr re-delivery dedup */
|
|
92
|
+
PROCESSED_SPLIT_GROUP_IDS: "processed_split_group_ids"
|
|
91
93
|
};
|
|
92
94
|
STORAGE_KEYS = {
|
|
93
95
|
...STORAGE_KEYS_GLOBAL,
|
|
@@ -4300,6 +4302,17 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4300
4302
|
// Poll every 2s
|
|
4301
4303
|
static PROOF_POLLING_MAX_ATTEMPTS = 30;
|
|
4302
4304
|
// Max 30 attempts (~60s)
|
|
4305
|
+
// Periodic retry for resolveUnconfirmed (V5 lazy finalization)
|
|
4306
|
+
resolveUnconfirmedTimer = null;
|
|
4307
|
+
static RESOLVE_UNCONFIRMED_INTERVAL_MS = 1e4;
|
|
4308
|
+
// Retry every 10s
|
|
4309
|
+
// Guard: ensure load() completes before processing incoming bundles
|
|
4310
|
+
loadedPromise = null;
|
|
4311
|
+
loaded = false;
|
|
4312
|
+
// Persistent dedup: tracks splitGroupIds that have been fully processed.
|
|
4313
|
+
// Survives page reloads via KV storage so Nostr re-deliveries are ignored
|
|
4314
|
+
// even when the confirmed token's in-memory ID differs from v5split_{id}.
|
|
4315
|
+
processedSplitGroupIds = /* @__PURE__ */ new Set();
|
|
4303
4316
|
// Storage event subscriptions (push-based sync)
|
|
4304
4317
|
storageEventUnsubscribers = [];
|
|
4305
4318
|
syncDebounceTimer = null;
|
|
@@ -4385,31 +4398,40 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4385
4398
|
*/
|
|
4386
4399
|
async load() {
|
|
4387
4400
|
this.ensureInitialized();
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4401
|
+
const doLoad = async () => {
|
|
4402
|
+
await TokenRegistry.waitForReady();
|
|
4403
|
+
const providers = this.getTokenStorageProviders();
|
|
4404
|
+
for (const [id, provider] of providers) {
|
|
4405
|
+
try {
|
|
4406
|
+
const result = await provider.load();
|
|
4407
|
+
if (result.success && result.data) {
|
|
4408
|
+
this.loadFromStorageData(result.data);
|
|
4409
|
+
this.log(`Loaded metadata from provider ${id}`);
|
|
4410
|
+
break;
|
|
4411
|
+
}
|
|
4412
|
+
} catch (err) {
|
|
4413
|
+
console.error(`[Payments] Failed to load from provider ${id}:`, err);
|
|
4397
4414
|
}
|
|
4398
|
-
} catch (err) {
|
|
4399
|
-
console.error(`[Payments] Failed to load from provider ${id}:`, err);
|
|
4400
4415
|
}
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
const
|
|
4407
|
-
|
|
4408
|
-
|
|
4416
|
+
const loadedTokens = Array.from(this.tokens.values()).map((t) => `${t.id.slice(0, 12)}(${t.status})`);
|
|
4417
|
+
console.log(`[Payments][DEBUG] load(): from TXF providers: ${this.tokens.size} tokens [${loadedTokens.join(", ")}]`);
|
|
4418
|
+
await this.loadPendingV5Tokens();
|
|
4419
|
+
await this.loadProcessedSplitGroupIds();
|
|
4420
|
+
await this.loadHistory();
|
|
4421
|
+
const pending2 = await this.deps.storage.get(STORAGE_KEYS_ADDRESS.PENDING_TRANSFERS);
|
|
4422
|
+
if (pending2) {
|
|
4423
|
+
const transfers = JSON.parse(pending2);
|
|
4424
|
+
for (const transfer of transfers) {
|
|
4425
|
+
this.pendingTransfers.set(transfer.id, transfer);
|
|
4426
|
+
}
|
|
4409
4427
|
}
|
|
4410
|
-
|
|
4428
|
+
this.loaded = true;
|
|
4429
|
+
};
|
|
4430
|
+
this.loadedPromise = doLoad();
|
|
4431
|
+
await this.loadedPromise;
|
|
4411
4432
|
this.resolveUnconfirmed().catch(() => {
|
|
4412
4433
|
});
|
|
4434
|
+
this.scheduleResolveUnconfirmed();
|
|
4413
4435
|
}
|
|
4414
4436
|
/**
|
|
4415
4437
|
* Cleanup all subscriptions, polling jobs, and pending resolvers.
|
|
@@ -4428,6 +4450,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4428
4450
|
this.paymentRequestResponseHandlers.clear();
|
|
4429
4451
|
this.stopProofPolling();
|
|
4430
4452
|
this.proofPollingJobs.clear();
|
|
4453
|
+
this.stopResolveUnconfirmedPolling();
|
|
4431
4454
|
for (const [, resolver] of this.pendingResponseResolvers) {
|
|
4432
4455
|
clearTimeout(resolver.timeout);
|
|
4433
4456
|
resolver.reject(new Error("Module destroyed"));
|
|
@@ -4834,13 +4857,16 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4834
4857
|
*/
|
|
4835
4858
|
async processInstantSplitBundle(bundle, senderPubkey, memo) {
|
|
4836
4859
|
this.ensureInitialized();
|
|
4860
|
+
if (!this.loaded && this.loadedPromise) {
|
|
4861
|
+
await this.loadedPromise;
|
|
4862
|
+
}
|
|
4837
4863
|
if (!isInstantSplitBundleV5(bundle)) {
|
|
4838
4864
|
return this.processInstantSplitBundleSync(bundle, senderPubkey, memo);
|
|
4839
4865
|
}
|
|
4840
4866
|
try {
|
|
4841
4867
|
const deterministicId = `v5split_${bundle.splitGroupId}`;
|
|
4842
|
-
if (this.tokens.has(deterministicId)) {
|
|
4843
|
-
|
|
4868
|
+
if (this.tokens.has(deterministicId) || this.processedSplitGroupIds.has(bundle.splitGroupId)) {
|
|
4869
|
+
console.log(`[Payments] V5 bundle ${bundle.splitGroupId.slice(0, 12)}... already processed, skipping`);
|
|
4844
4870
|
return { success: true, durationMs: 0 };
|
|
4845
4871
|
}
|
|
4846
4872
|
const registry = TokenRegistry.getInstance();
|
|
@@ -4866,7 +4892,8 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4866
4892
|
sdkData: JSON.stringify({ _pendingFinalization: pendingData })
|
|
4867
4893
|
};
|
|
4868
4894
|
await this.addToken(uiToken);
|
|
4869
|
-
this.
|
|
4895
|
+
this.processedSplitGroupIds.add(bundle.splitGroupId);
|
|
4896
|
+
await this.saveProcessedSplitGroupIds();
|
|
4870
4897
|
const senderInfo = await this.resolveSenderInfo(senderPubkey);
|
|
4871
4898
|
await this.addToHistory({
|
|
4872
4899
|
type: "RECEIVED",
|
|
@@ -4890,6 +4917,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4890
4917
|
await this.save();
|
|
4891
4918
|
this.resolveUnconfirmed().catch(() => {
|
|
4892
4919
|
});
|
|
4920
|
+
this.scheduleResolveUnconfirmed();
|
|
4893
4921
|
return { success: true, durationMs: 0 };
|
|
4894
4922
|
} catch (error) {
|
|
4895
4923
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -5636,28 +5664,70 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5636
5664
|
};
|
|
5637
5665
|
const stClient = this.deps.oracle.getStateTransitionClient?.();
|
|
5638
5666
|
const trustBase = this.deps.oracle.getTrustBase?.();
|
|
5639
|
-
if (!stClient || !trustBase)
|
|
5667
|
+
if (!stClient || !trustBase) {
|
|
5668
|
+
console.log(`[V5-RESOLVE] resolveUnconfirmed: EARLY EXIT \u2014 stClient=${!!stClient} trustBase=${!!trustBase}`);
|
|
5669
|
+
return result;
|
|
5670
|
+
}
|
|
5640
5671
|
const signingService = await this.createSigningService();
|
|
5672
|
+
const submittedCount = Array.from(this.tokens.values()).filter((t) => t.status === "submitted").length;
|
|
5673
|
+
console.log(`[V5-RESOLVE] resolveUnconfirmed: ${submittedCount} submitted token(s) to process`);
|
|
5641
5674
|
for (const [tokenId, token] of this.tokens) {
|
|
5642
5675
|
if (token.status !== "submitted") continue;
|
|
5643
5676
|
const pending2 = this.parsePendingFinalization(token.sdkData);
|
|
5644
5677
|
if (!pending2) {
|
|
5678
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 16)}: no pending finalization metadata, skipping`);
|
|
5645
5679
|
result.stillPending++;
|
|
5646
5680
|
continue;
|
|
5647
5681
|
}
|
|
5648
5682
|
if (pending2.type === "v5_bundle") {
|
|
5683
|
+
console.log(`[V5-RESOLVE] Processing ${tokenId.slice(0, 16)}... stage=${pending2.stage} attempt=${pending2.attemptCount}`);
|
|
5649
5684
|
const progress = await this.resolveV5Token(tokenId, token, pending2, stClient, trustBase, signingService);
|
|
5685
|
+
console.log(`[V5-RESOLVE] Result for ${tokenId.slice(0, 16)}...: ${progress} (stage now: ${pending2.stage})`);
|
|
5650
5686
|
result.details.push({ tokenId, stage: pending2.stage, status: progress });
|
|
5651
5687
|
if (progress === "resolved") result.resolved++;
|
|
5652
5688
|
else if (progress === "failed") result.failed++;
|
|
5653
5689
|
else result.stillPending++;
|
|
5654
5690
|
}
|
|
5655
5691
|
}
|
|
5656
|
-
if (result.resolved > 0 || result.failed > 0) {
|
|
5692
|
+
if (result.resolved > 0 || result.failed > 0 || result.stillPending > 0) {
|
|
5693
|
+
console.log(`[V5-RESOLVE] Saving: resolved=${result.resolved} failed=${result.failed} stillPending=${result.stillPending}`);
|
|
5657
5694
|
await this.save();
|
|
5658
5695
|
}
|
|
5659
5696
|
return result;
|
|
5660
5697
|
}
|
|
5698
|
+
/**
|
|
5699
|
+
* Start a periodic interval that retries resolveUnconfirmed() until all
|
|
5700
|
+
* tokens are confirmed or failed. Stops automatically when nothing is
|
|
5701
|
+
* pending and is cleaned up by destroy().
|
|
5702
|
+
*/
|
|
5703
|
+
scheduleResolveUnconfirmed() {
|
|
5704
|
+
if (this.resolveUnconfirmedTimer) return;
|
|
5705
|
+
const hasUnconfirmed = Array.from(this.tokens.values()).some(
|
|
5706
|
+
(t) => t.status === "submitted"
|
|
5707
|
+
);
|
|
5708
|
+
if (!hasUnconfirmed) {
|
|
5709
|
+
console.log(`[V5-RESOLVE] scheduleResolveUnconfirmed: no submitted tokens, not starting timer`);
|
|
5710
|
+
return;
|
|
5711
|
+
}
|
|
5712
|
+
console.log(`[V5-RESOLVE] scheduleResolveUnconfirmed: starting periodic retry (every ${_PaymentsModule.RESOLVE_UNCONFIRMED_INTERVAL_MS}ms)`);
|
|
5713
|
+
this.resolveUnconfirmedTimer = setInterval(async () => {
|
|
5714
|
+
try {
|
|
5715
|
+
const result = await this.resolveUnconfirmed();
|
|
5716
|
+
if (result.stillPending === 0) {
|
|
5717
|
+
console.log(`[V5-RESOLVE] All tokens resolved, stopping periodic retry`);
|
|
5718
|
+
this.stopResolveUnconfirmedPolling();
|
|
5719
|
+
}
|
|
5720
|
+
} catch (err) {
|
|
5721
|
+
console.log(`[V5-RESOLVE] Periodic retry error:`, err);
|
|
5722
|
+
}
|
|
5723
|
+
}, _PaymentsModule.RESOLVE_UNCONFIRMED_INTERVAL_MS);
|
|
5724
|
+
}
|
|
5725
|
+
stopResolveUnconfirmedPolling() {
|
|
5726
|
+
if (this.resolveUnconfirmedTimer) {
|
|
5727
|
+
clearInterval(this.resolveUnconfirmedTimer);
|
|
5728
|
+
this.resolveUnconfirmedTimer = null;
|
|
5729
|
+
}
|
|
5730
|
+
}
|
|
5661
5731
|
// ===========================================================================
|
|
5662
5732
|
// Private - V5 Lazy Resolution Helpers
|
|
5663
5733
|
// ===========================================================================
|
|
@@ -5670,10 +5740,12 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5670
5740
|
pending2.lastAttemptAt = Date.now();
|
|
5671
5741
|
try {
|
|
5672
5742
|
if (pending2.stage === "RECEIVED") {
|
|
5743
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: RECEIVED \u2192 submitting mint commitment...`);
|
|
5673
5744
|
const mintDataJson = JSON.parse(bundle.recipientMintData);
|
|
5674
5745
|
const mintData = await MintTransactionData3.fromJSON(mintDataJson);
|
|
5675
5746
|
const mintCommitment = await MintCommitment3.create(mintData);
|
|
5676
5747
|
const mintResponse = await stClient.submitMintCommitment(mintCommitment);
|
|
5748
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: mint response status=${mintResponse.status}`);
|
|
5677
5749
|
if (mintResponse.status !== "SUCCESS" && mintResponse.status !== "REQUEST_ID_EXISTS") {
|
|
5678
5750
|
throw new Error(`Mint submission failed: ${mintResponse.status}`);
|
|
5679
5751
|
}
|
|
@@ -5681,22 +5753,27 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5681
5753
|
this.updatePendingFinalization(token, pending2);
|
|
5682
5754
|
}
|
|
5683
5755
|
if (pending2.stage === "MINT_SUBMITTED") {
|
|
5756
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: MINT_SUBMITTED \u2192 checking mint proof...`);
|
|
5684
5757
|
const mintDataJson = JSON.parse(bundle.recipientMintData);
|
|
5685
5758
|
const mintData = await MintTransactionData3.fromJSON(mintDataJson);
|
|
5686
5759
|
const mintCommitment = await MintCommitment3.create(mintData);
|
|
5687
5760
|
const proof = await this.quickProofCheck(stClient, trustBase, mintCommitment);
|
|
5688
5761
|
if (!proof) {
|
|
5762
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: mint proof not yet available, staying MINT_SUBMITTED`);
|
|
5689
5763
|
this.updatePendingFinalization(token, pending2);
|
|
5690
5764
|
return "pending";
|
|
5691
5765
|
}
|
|
5766
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: mint proof obtained!`);
|
|
5692
5767
|
pending2.mintProofJson = JSON.stringify(proof);
|
|
5693
5768
|
pending2.stage = "MINT_PROVEN";
|
|
5694
5769
|
this.updatePendingFinalization(token, pending2);
|
|
5695
5770
|
}
|
|
5696
5771
|
if (pending2.stage === "MINT_PROVEN") {
|
|
5772
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: MINT_PROVEN \u2192 submitting transfer commitment...`);
|
|
5697
5773
|
const transferCommitmentJson = JSON.parse(bundle.transferCommitment);
|
|
5698
5774
|
const transferCommitment = await TransferCommitment4.fromJSON(transferCommitmentJson);
|
|
5699
5775
|
const transferResponse = await stClient.submitTransferCommitment(transferCommitment);
|
|
5776
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: transfer response status=${transferResponse.status}`);
|
|
5700
5777
|
if (transferResponse.status !== "SUCCESS" && transferResponse.status !== "REQUEST_ID_EXISTS") {
|
|
5701
5778
|
throw new Error(`Transfer submission failed: ${transferResponse.status}`);
|
|
5702
5779
|
}
|
|
@@ -5704,13 +5781,16 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5704
5781
|
this.updatePendingFinalization(token, pending2);
|
|
5705
5782
|
}
|
|
5706
5783
|
if (pending2.stage === "TRANSFER_SUBMITTED") {
|
|
5784
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: TRANSFER_SUBMITTED \u2192 checking transfer proof...`);
|
|
5707
5785
|
const transferCommitmentJson = JSON.parse(bundle.transferCommitment);
|
|
5708
5786
|
const transferCommitment = await TransferCommitment4.fromJSON(transferCommitmentJson);
|
|
5709
5787
|
const proof = await this.quickProofCheck(stClient, trustBase, transferCommitment);
|
|
5710
5788
|
if (!proof) {
|
|
5789
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: transfer proof not yet available, staying TRANSFER_SUBMITTED`);
|
|
5711
5790
|
this.updatePendingFinalization(token, pending2);
|
|
5712
5791
|
return "pending";
|
|
5713
5792
|
}
|
|
5793
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: transfer proof obtained! Finalizing...`);
|
|
5714
5794
|
const finalizedToken = await this.finalizeFromV5Bundle(bundle, pending2, signingService, stClient, trustBase);
|
|
5715
5795
|
const confirmedToken = {
|
|
5716
5796
|
id: token.id,
|
|
@@ -5726,6 +5806,12 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5726
5806
|
sdkData: JSON.stringify(finalizedToken.toJSON())
|
|
5727
5807
|
};
|
|
5728
5808
|
this.tokens.set(tokenId, confirmedToken);
|
|
5809
|
+
this.deps.emitEvent("transfer:confirmed", {
|
|
5810
|
+
id: crypto.randomUUID(),
|
|
5811
|
+
status: "completed",
|
|
5812
|
+
tokens: [confirmedToken],
|
|
5813
|
+
tokenTransfers: []
|
|
5814
|
+
});
|
|
5729
5815
|
this.log(`V5 token resolved: ${tokenId.slice(0, 8)}...`);
|
|
5730
5816
|
return "resolved";
|
|
5731
5817
|
}
|
|
@@ -5867,11 +5953,20 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5867
5953
|
}
|
|
5868
5954
|
}
|
|
5869
5955
|
if (pendingTokens.length > 0) {
|
|
5956
|
+
const json = JSON.stringify(pendingTokens);
|
|
5957
|
+
this.log(`[V5-PERSIST] Saving ${pendingTokens.length} pending V5 token(s): ${pendingTokens.map((t) => t.id.slice(0, 16)).join(", ")} (${json.length} bytes)`);
|
|
5870
5958
|
await this.deps.storage.set(
|
|
5871
5959
|
STORAGE_KEYS_ADDRESS.PENDING_V5_TOKENS,
|
|
5872
|
-
|
|
5960
|
+
json
|
|
5873
5961
|
);
|
|
5962
|
+
const verify = await this.deps.storage.get(STORAGE_KEYS_ADDRESS.PENDING_V5_TOKENS);
|
|
5963
|
+
if (!verify) {
|
|
5964
|
+
console.error("[Payments][V5-PERSIST] CRITICAL: KV write succeeded but read-back is empty!");
|
|
5965
|
+
} else {
|
|
5966
|
+
this.log(`[V5-PERSIST] Verified: read-back ${verify.length} bytes`);
|
|
5967
|
+
}
|
|
5874
5968
|
} else {
|
|
5969
|
+
this.log(`[V5-PERSIST] No pending V5 tokens to save (total tokens: ${this.tokens.size}), clearing KV`);
|
|
5875
5970
|
await this.deps.storage.set(STORAGE_KEYS_ADDRESS.PENDING_V5_TOKENS, "");
|
|
5876
5971
|
}
|
|
5877
5972
|
}
|
|
@@ -5881,16 +5976,47 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5881
5976
|
*/
|
|
5882
5977
|
async loadPendingV5Tokens() {
|
|
5883
5978
|
const data = await this.deps.storage.get(STORAGE_KEYS_ADDRESS.PENDING_V5_TOKENS);
|
|
5979
|
+
this.log(`[V5-PERSIST] loadPendingV5Tokens: KV data = ${data ? `${data.length} bytes` : "null/empty"}`);
|
|
5884
5980
|
if (!data) return;
|
|
5885
5981
|
try {
|
|
5886
5982
|
const pendingTokens = JSON.parse(data);
|
|
5983
|
+
this.log(`[V5-PERSIST] Parsed ${pendingTokens.length} pending V5 token(s): ${pendingTokens.map((t) => t.id.slice(0, 16)).join(", ")}`);
|
|
5887
5984
|
for (const token of pendingTokens) {
|
|
5888
5985
|
if (!this.tokens.has(token.id)) {
|
|
5889
5986
|
this.tokens.set(token.id, token);
|
|
5987
|
+
this.log(`[V5-PERSIST] Restored token ${token.id.slice(0, 16)} (status=${token.status})`);
|
|
5988
|
+
} else {
|
|
5989
|
+
this.log(`[V5-PERSIST] Token ${token.id.slice(0, 16)} already in map, skipping`);
|
|
5890
5990
|
}
|
|
5891
5991
|
}
|
|
5892
|
-
|
|
5893
|
-
|
|
5992
|
+
} catch (err) {
|
|
5993
|
+
console.error("[Payments][V5-PERSIST] Failed to parse pending V5 tokens:", err);
|
|
5994
|
+
}
|
|
5995
|
+
}
|
|
5996
|
+
/**
|
|
5997
|
+
* Persist the set of processed splitGroupIds to KV storage.
|
|
5998
|
+
* This ensures Nostr re-deliveries are ignored across page reloads,
|
|
5999
|
+
* even when the confirmed token's in-memory ID differs from v5split_{id}.
|
|
6000
|
+
*/
|
|
6001
|
+
async saveProcessedSplitGroupIds() {
|
|
6002
|
+
const ids = Array.from(this.processedSplitGroupIds);
|
|
6003
|
+
if (ids.length > 0) {
|
|
6004
|
+
await this.deps.storage.set(
|
|
6005
|
+
STORAGE_KEYS_ADDRESS.PROCESSED_SPLIT_GROUP_IDS,
|
|
6006
|
+
JSON.stringify(ids)
|
|
6007
|
+
);
|
|
6008
|
+
}
|
|
6009
|
+
}
|
|
6010
|
+
/**
|
|
6011
|
+
* Load processed splitGroupIds from KV storage.
|
|
6012
|
+
*/
|
|
6013
|
+
async loadProcessedSplitGroupIds() {
|
|
6014
|
+
const data = await this.deps.storage.get(STORAGE_KEYS_ADDRESS.PROCESSED_SPLIT_GROUP_IDS);
|
|
6015
|
+
if (!data) return;
|
|
6016
|
+
try {
|
|
6017
|
+
const ids = JSON.parse(data);
|
|
6018
|
+
for (const id of ids) {
|
|
6019
|
+
this.processedSplitGroupIds.add(id);
|
|
5894
6020
|
}
|
|
5895
6021
|
} catch {
|
|
5896
6022
|
}
|
|
@@ -6545,7 +6671,32 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6545
6671
|
try {
|
|
6546
6672
|
const result = await provider.sync(localData);
|
|
6547
6673
|
if (result.success && result.merged) {
|
|
6674
|
+
const savedTokens = new Map(this.tokens);
|
|
6548
6675
|
this.loadFromStorageData(result.merged);
|
|
6676
|
+
let restoredCount = 0;
|
|
6677
|
+
for (const [tokenId, token] of savedTokens) {
|
|
6678
|
+
if (this.tokens.has(tokenId)) continue;
|
|
6679
|
+
const sdkTokenId = extractTokenIdFromSdkData(token.sdkData);
|
|
6680
|
+
const stateHash = extractStateHashFromSdkData(token.sdkData);
|
|
6681
|
+
if (sdkTokenId && stateHash && this.isStateTombstoned(sdkTokenId, stateHash)) {
|
|
6682
|
+
continue;
|
|
6683
|
+
}
|
|
6684
|
+
if (sdkTokenId) {
|
|
6685
|
+
let hasEquivalent = false;
|
|
6686
|
+
for (const existing of this.tokens.values()) {
|
|
6687
|
+
if (extractTokenIdFromSdkData(existing.sdkData) === sdkTokenId) {
|
|
6688
|
+
hasEquivalent = true;
|
|
6689
|
+
break;
|
|
6690
|
+
}
|
|
6691
|
+
}
|
|
6692
|
+
if (hasEquivalent) continue;
|
|
6693
|
+
}
|
|
6694
|
+
this.tokens.set(tokenId, token);
|
|
6695
|
+
restoredCount++;
|
|
6696
|
+
}
|
|
6697
|
+
if (restoredCount > 0) {
|
|
6698
|
+
console.log(`[Payments] Sync: restored ${restoredCount} token(s) lost by loadFromStorageData`);
|
|
6699
|
+
}
|
|
6549
6700
|
if (this.nametags.length === 0 && savedNametags.length > 0) {
|
|
6550
6701
|
this.nametags = savedNametags;
|
|
6551
6702
|
}
|
|
@@ -6879,8 +7030,9 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6879
7030
|
return;
|
|
6880
7031
|
}
|
|
6881
7032
|
this.tokens.set(token.id, token);
|
|
7033
|
+
console.log(`[Payments][DEBUG] NOSTR-FIRST: saving token id=${token.id.slice(0, 16)} status=${token.status} sdkData.length=${token.sdkData?.length}`);
|
|
6882
7034
|
await this.save();
|
|
6883
|
-
|
|
7035
|
+
console.log(`[Payments][DEBUG] NOSTR-FIRST: save() completed, tokens.size=${this.tokens.size}`);
|
|
6884
7036
|
const senderInfo = await this.resolveSenderInfo(transfer.senderTransportPubkey);
|
|
6885
7037
|
const incomingTransfer = {
|
|
6886
7038
|
id: transfer.id,
|
|
@@ -7037,8 +7189,12 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
7037
7189
|
}
|
|
7038
7190
|
}
|
|
7039
7191
|
async handleIncomingTransfer(transfer) {
|
|
7192
|
+
if (!this.loaded && this.loadedPromise) {
|
|
7193
|
+
await this.loadedPromise;
|
|
7194
|
+
}
|
|
7040
7195
|
try {
|
|
7041
7196
|
const payload = transfer.payload;
|
|
7197
|
+
console.log("[Payments][DEBUG] handleIncomingTransfer: keys=", Object.keys(payload).join(","));
|
|
7042
7198
|
let instantBundle = null;
|
|
7043
7199
|
if (isInstantSplitBundle(payload)) {
|
|
7044
7200
|
instantBundle = payload;
|
|
@@ -7070,7 +7226,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
7070
7226
|
return;
|
|
7071
7227
|
}
|
|
7072
7228
|
if (payload.sourceToken && payload.commitmentData && !payload.transferTx) {
|
|
7073
|
-
|
|
7229
|
+
console.log("[Payments][DEBUG] >>> NOSTR-FIRST commitment-only transfer detected");
|
|
7074
7230
|
await this.handleCommitmentOnlyTransfer(transfer, payload);
|
|
7075
7231
|
return;
|
|
7076
7232
|
}
|
|
@@ -7233,17 +7389,24 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
7233
7389
|
// ===========================================================================
|
|
7234
7390
|
async save() {
|
|
7235
7391
|
const providers = this.getTokenStorageProviders();
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
return
|
|
7239
|
-
}
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7392
|
+
const tokenStats = Array.from(this.tokens.values()).map((t) => {
|
|
7393
|
+
const txf = tokenToTxf(t);
|
|
7394
|
+
return `${t.id.slice(0, 12)}(${t.status},txf=${!!txf})`;
|
|
7395
|
+
});
|
|
7396
|
+
console.log(`[Payments][DEBUG] save(): providers=${providers.size}, tokens=[${tokenStats.join(", ")}]`);
|
|
7397
|
+
if (providers.size > 0) {
|
|
7398
|
+
const data = await this.createStorageData();
|
|
7399
|
+
const dataKeys = Object.keys(data).filter((k) => k.startsWith("token-"));
|
|
7400
|
+
console.log(`[Payments][DEBUG] save(): TXF keys=${dataKeys.length} (${dataKeys.join(", ")})`);
|
|
7401
|
+
for (const [id, provider] of providers) {
|
|
7402
|
+
try {
|
|
7403
|
+
await provider.save(data);
|
|
7404
|
+
} catch (err) {
|
|
7405
|
+
console.error(`[Payments] Failed to save to provider ${id}:`, err);
|
|
7406
|
+
}
|
|
7246
7407
|
}
|
|
7408
|
+
} else {
|
|
7409
|
+
console.log("[Payments][DEBUG] save(): No token storage providers - TXF not persisted");
|
|
7247
7410
|
}
|
|
7248
7411
|
await this.savePendingV5Tokens();
|
|
7249
7412
|
}
|
|
@@ -7279,6 +7442,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
7279
7442
|
}
|
|
7280
7443
|
loadFromStorageData(data) {
|
|
7281
7444
|
const parsed = parseTxfStorageData(data);
|
|
7445
|
+
console.log(`[Payments][DEBUG] loadFromStorageData: parsed ${parsed.tokens.length} tokens, ${parsed.tombstones.length} tombstones, errors=[${parsed.validationErrors.join("; ")}]`);
|
|
7282
7446
|
this.tombstones = parsed.tombstones;
|
|
7283
7447
|
this.tokens.clear();
|
|
7284
7448
|
for (const token of parsed.tokens) {
|