@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/index.d.cts
CHANGED
|
@@ -2572,6 +2572,11 @@ declare class PaymentsModule {
|
|
|
2572
2572
|
private proofPollingInterval;
|
|
2573
2573
|
private static readonly PROOF_POLLING_INTERVAL_MS;
|
|
2574
2574
|
private static readonly PROOF_POLLING_MAX_ATTEMPTS;
|
|
2575
|
+
private resolveUnconfirmedTimer;
|
|
2576
|
+
private static readonly RESOLVE_UNCONFIRMED_INTERVAL_MS;
|
|
2577
|
+
private loadedPromise;
|
|
2578
|
+
private loaded;
|
|
2579
|
+
private processedSplitGroupIds;
|
|
2575
2580
|
private storageEventUnsubscribers;
|
|
2576
2581
|
private syncDebounceTimer;
|
|
2577
2582
|
private static readonly SYNC_DEBOUNCE_MS;
|
|
@@ -2869,6 +2874,13 @@ declare class PaymentsModule {
|
|
|
2869
2874
|
* @returns Summary with counts of resolved, still-pending, and failed tokens plus per-token details.
|
|
2870
2875
|
*/
|
|
2871
2876
|
resolveUnconfirmed(): Promise<UnconfirmedResolutionResult>;
|
|
2877
|
+
/**
|
|
2878
|
+
* Start a periodic interval that retries resolveUnconfirmed() until all
|
|
2879
|
+
* tokens are confirmed or failed. Stops automatically when nothing is
|
|
2880
|
+
* pending and is cleaned up by destroy().
|
|
2881
|
+
*/
|
|
2882
|
+
private scheduleResolveUnconfirmed;
|
|
2883
|
+
private stopResolveUnconfirmedPolling;
|
|
2872
2884
|
/**
|
|
2873
2885
|
* Process a single V5 token through its finalization stages with quick-timeout proof checks.
|
|
2874
2886
|
*/
|
|
@@ -2902,6 +2914,16 @@ declare class PaymentsModule {
|
|
|
2902
2914
|
* Called during load() to restore tokens that TXF format can't represent.
|
|
2903
2915
|
*/
|
|
2904
2916
|
private loadPendingV5Tokens;
|
|
2917
|
+
/**
|
|
2918
|
+
* Persist the set of processed splitGroupIds to KV storage.
|
|
2919
|
+
* This ensures Nostr re-deliveries are ignored across page reloads,
|
|
2920
|
+
* even when the confirmed token's in-memory ID differs from v5split_{id}.
|
|
2921
|
+
*/
|
|
2922
|
+
private saveProcessedSplitGroupIds;
|
|
2923
|
+
/**
|
|
2924
|
+
* Load processed splitGroupIds from KV storage.
|
|
2925
|
+
*/
|
|
2926
|
+
private loadProcessedSplitGroupIds;
|
|
2905
2927
|
/**
|
|
2906
2928
|
* Add a token to the wallet.
|
|
2907
2929
|
*
|
|
@@ -3755,6 +3777,8 @@ declare const STORAGE_KEYS_ADDRESS: {
|
|
|
3755
3777
|
readonly GROUP_CHAT_MEMBERS: "group_chat_members";
|
|
3756
3778
|
/** Group chat: processed event IDs for deduplication */
|
|
3757
3779
|
readonly GROUP_CHAT_PROCESSED_EVENTS: "group_chat_processed_events";
|
|
3780
|
+
/** Processed V5 split group IDs for Nostr re-delivery dedup */
|
|
3781
|
+
readonly PROCESSED_SPLIT_GROUP_IDS: "processed_split_group_ids";
|
|
3758
3782
|
};
|
|
3759
3783
|
/** @deprecated Use STORAGE_KEYS_GLOBAL and STORAGE_KEYS_ADDRESS instead */
|
|
3760
3784
|
declare const STORAGE_KEYS: {
|
|
@@ -3778,6 +3802,8 @@ declare const STORAGE_KEYS: {
|
|
|
3778
3802
|
readonly GROUP_CHAT_MEMBERS: "group_chat_members";
|
|
3779
3803
|
/** Group chat: processed event IDs for deduplication */
|
|
3780
3804
|
readonly GROUP_CHAT_PROCESSED_EVENTS: "group_chat_processed_events";
|
|
3805
|
+
/** Processed V5 split group IDs for Nostr re-delivery dedup */
|
|
3806
|
+
readonly PROCESSED_SPLIT_GROUP_IDS: "processed_split_group_ids";
|
|
3781
3807
|
/** Encrypted BIP39 mnemonic */
|
|
3782
3808
|
readonly MNEMONIC: "mnemonic";
|
|
3783
3809
|
/** Encrypted master private key */
|
package/dist/index.d.ts
CHANGED
|
@@ -2572,6 +2572,11 @@ declare class PaymentsModule {
|
|
|
2572
2572
|
private proofPollingInterval;
|
|
2573
2573
|
private static readonly PROOF_POLLING_INTERVAL_MS;
|
|
2574
2574
|
private static readonly PROOF_POLLING_MAX_ATTEMPTS;
|
|
2575
|
+
private resolveUnconfirmedTimer;
|
|
2576
|
+
private static readonly RESOLVE_UNCONFIRMED_INTERVAL_MS;
|
|
2577
|
+
private loadedPromise;
|
|
2578
|
+
private loaded;
|
|
2579
|
+
private processedSplitGroupIds;
|
|
2575
2580
|
private storageEventUnsubscribers;
|
|
2576
2581
|
private syncDebounceTimer;
|
|
2577
2582
|
private static readonly SYNC_DEBOUNCE_MS;
|
|
@@ -2869,6 +2874,13 @@ declare class PaymentsModule {
|
|
|
2869
2874
|
* @returns Summary with counts of resolved, still-pending, and failed tokens plus per-token details.
|
|
2870
2875
|
*/
|
|
2871
2876
|
resolveUnconfirmed(): Promise<UnconfirmedResolutionResult>;
|
|
2877
|
+
/**
|
|
2878
|
+
* Start a periodic interval that retries resolveUnconfirmed() until all
|
|
2879
|
+
* tokens are confirmed or failed. Stops automatically when nothing is
|
|
2880
|
+
* pending and is cleaned up by destroy().
|
|
2881
|
+
*/
|
|
2882
|
+
private scheduleResolveUnconfirmed;
|
|
2883
|
+
private stopResolveUnconfirmedPolling;
|
|
2872
2884
|
/**
|
|
2873
2885
|
* Process a single V5 token through its finalization stages with quick-timeout proof checks.
|
|
2874
2886
|
*/
|
|
@@ -2902,6 +2914,16 @@ declare class PaymentsModule {
|
|
|
2902
2914
|
* Called during load() to restore tokens that TXF format can't represent.
|
|
2903
2915
|
*/
|
|
2904
2916
|
private loadPendingV5Tokens;
|
|
2917
|
+
/**
|
|
2918
|
+
* Persist the set of processed splitGroupIds to KV storage.
|
|
2919
|
+
* This ensures Nostr re-deliveries are ignored across page reloads,
|
|
2920
|
+
* even when the confirmed token's in-memory ID differs from v5split_{id}.
|
|
2921
|
+
*/
|
|
2922
|
+
private saveProcessedSplitGroupIds;
|
|
2923
|
+
/**
|
|
2924
|
+
* Load processed splitGroupIds from KV storage.
|
|
2925
|
+
*/
|
|
2926
|
+
private loadProcessedSplitGroupIds;
|
|
2905
2927
|
/**
|
|
2906
2928
|
* Add a token to the wallet.
|
|
2907
2929
|
*
|
|
@@ -3755,6 +3777,8 @@ declare const STORAGE_KEYS_ADDRESS: {
|
|
|
3755
3777
|
readonly GROUP_CHAT_MEMBERS: "group_chat_members";
|
|
3756
3778
|
/** Group chat: processed event IDs for deduplication */
|
|
3757
3779
|
readonly GROUP_CHAT_PROCESSED_EVENTS: "group_chat_processed_events";
|
|
3780
|
+
/** Processed V5 split group IDs for Nostr re-delivery dedup */
|
|
3781
|
+
readonly PROCESSED_SPLIT_GROUP_IDS: "processed_split_group_ids";
|
|
3758
3782
|
};
|
|
3759
3783
|
/** @deprecated Use STORAGE_KEYS_GLOBAL and STORAGE_KEYS_ADDRESS instead */
|
|
3760
3784
|
declare const STORAGE_KEYS: {
|
|
@@ -3778,6 +3802,8 @@ declare const STORAGE_KEYS: {
|
|
|
3778
3802
|
readonly GROUP_CHAT_MEMBERS: "group_chat_members";
|
|
3779
3803
|
/** Group chat: processed event IDs for deduplication */
|
|
3780
3804
|
readonly GROUP_CHAT_PROCESSED_EVENTS: "group_chat_processed_events";
|
|
3805
|
+
/** Processed V5 split group IDs for Nostr re-delivery dedup */
|
|
3806
|
+
readonly PROCESSED_SPLIT_GROUP_IDS: "processed_split_group_ids";
|
|
3781
3807
|
/** Encrypted BIP39 mnemonic */
|
|
3782
3808
|
readonly MNEMONIC: "mnemonic";
|
|
3783
3809
|
/** Encrypted master private key */
|
package/dist/index.js
CHANGED
|
@@ -91,7 +91,9 @@ var init_constants = __esm({
|
|
|
91
91
|
/** Group chat: members for this address */
|
|
92
92
|
GROUP_CHAT_MEMBERS: "group_chat_members",
|
|
93
93
|
/** Group chat: processed event IDs for deduplication */
|
|
94
|
-
GROUP_CHAT_PROCESSED_EVENTS: "group_chat_processed_events"
|
|
94
|
+
GROUP_CHAT_PROCESSED_EVENTS: "group_chat_processed_events",
|
|
95
|
+
/** Processed V5 split group IDs for Nostr re-delivery dedup */
|
|
96
|
+
PROCESSED_SPLIT_GROUP_IDS: "processed_split_group_ids"
|
|
95
97
|
};
|
|
96
98
|
STORAGE_KEYS = {
|
|
97
99
|
...STORAGE_KEYS_GLOBAL,
|
|
@@ -4595,6 +4597,17 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4595
4597
|
// Poll every 2s
|
|
4596
4598
|
static PROOF_POLLING_MAX_ATTEMPTS = 30;
|
|
4597
4599
|
// Max 30 attempts (~60s)
|
|
4600
|
+
// Periodic retry for resolveUnconfirmed (V5 lazy finalization)
|
|
4601
|
+
resolveUnconfirmedTimer = null;
|
|
4602
|
+
static RESOLVE_UNCONFIRMED_INTERVAL_MS = 1e4;
|
|
4603
|
+
// Retry every 10s
|
|
4604
|
+
// Guard: ensure load() completes before processing incoming bundles
|
|
4605
|
+
loadedPromise = null;
|
|
4606
|
+
loaded = false;
|
|
4607
|
+
// Persistent dedup: tracks splitGroupIds that have been fully processed.
|
|
4608
|
+
// Survives page reloads via KV storage so Nostr re-deliveries are ignored
|
|
4609
|
+
// even when the confirmed token's in-memory ID differs from v5split_{id}.
|
|
4610
|
+
processedSplitGroupIds = /* @__PURE__ */ new Set();
|
|
4598
4611
|
// Storage event subscriptions (push-based sync)
|
|
4599
4612
|
storageEventUnsubscribers = [];
|
|
4600
4613
|
syncDebounceTimer = null;
|
|
@@ -4680,31 +4693,40 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4680
4693
|
*/
|
|
4681
4694
|
async load() {
|
|
4682
4695
|
this.ensureInitialized();
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4696
|
+
const doLoad = async () => {
|
|
4697
|
+
await TokenRegistry.waitForReady();
|
|
4698
|
+
const providers = this.getTokenStorageProviders();
|
|
4699
|
+
for (const [id, provider] of providers) {
|
|
4700
|
+
try {
|
|
4701
|
+
const result = await provider.load();
|
|
4702
|
+
if (result.success && result.data) {
|
|
4703
|
+
this.loadFromStorageData(result.data);
|
|
4704
|
+
this.log(`Loaded metadata from provider ${id}`);
|
|
4705
|
+
break;
|
|
4706
|
+
}
|
|
4707
|
+
} catch (err) {
|
|
4708
|
+
console.error(`[Payments] Failed to load from provider ${id}:`, err);
|
|
4692
4709
|
}
|
|
4693
|
-
} catch (err) {
|
|
4694
|
-
console.error(`[Payments] Failed to load from provider ${id}:`, err);
|
|
4695
4710
|
}
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
const
|
|
4702
|
-
|
|
4703
|
-
|
|
4711
|
+
const loadedTokens = Array.from(this.tokens.values()).map((t) => `${t.id.slice(0, 12)}(${t.status})`);
|
|
4712
|
+
console.log(`[Payments][DEBUG] load(): from TXF providers: ${this.tokens.size} tokens [${loadedTokens.join(", ")}]`);
|
|
4713
|
+
await this.loadPendingV5Tokens();
|
|
4714
|
+
await this.loadProcessedSplitGroupIds();
|
|
4715
|
+
await this.loadHistory();
|
|
4716
|
+
const pending2 = await this.deps.storage.get(STORAGE_KEYS_ADDRESS.PENDING_TRANSFERS);
|
|
4717
|
+
if (pending2) {
|
|
4718
|
+
const transfers = JSON.parse(pending2);
|
|
4719
|
+
for (const transfer of transfers) {
|
|
4720
|
+
this.pendingTransfers.set(transfer.id, transfer);
|
|
4721
|
+
}
|
|
4704
4722
|
}
|
|
4705
|
-
|
|
4723
|
+
this.loaded = true;
|
|
4724
|
+
};
|
|
4725
|
+
this.loadedPromise = doLoad();
|
|
4726
|
+
await this.loadedPromise;
|
|
4706
4727
|
this.resolveUnconfirmed().catch(() => {
|
|
4707
4728
|
});
|
|
4729
|
+
this.scheduleResolveUnconfirmed();
|
|
4708
4730
|
}
|
|
4709
4731
|
/**
|
|
4710
4732
|
* Cleanup all subscriptions, polling jobs, and pending resolvers.
|
|
@@ -4723,6 +4745,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4723
4745
|
this.paymentRequestResponseHandlers.clear();
|
|
4724
4746
|
this.stopProofPolling();
|
|
4725
4747
|
this.proofPollingJobs.clear();
|
|
4748
|
+
this.stopResolveUnconfirmedPolling();
|
|
4726
4749
|
for (const [, resolver] of this.pendingResponseResolvers) {
|
|
4727
4750
|
clearTimeout(resolver.timeout);
|
|
4728
4751
|
resolver.reject(new Error("Module destroyed"));
|
|
@@ -5129,13 +5152,16 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5129
5152
|
*/
|
|
5130
5153
|
async processInstantSplitBundle(bundle, senderPubkey, memo) {
|
|
5131
5154
|
this.ensureInitialized();
|
|
5155
|
+
if (!this.loaded && this.loadedPromise) {
|
|
5156
|
+
await this.loadedPromise;
|
|
5157
|
+
}
|
|
5132
5158
|
if (!isInstantSplitBundleV5(bundle)) {
|
|
5133
5159
|
return this.processInstantSplitBundleSync(bundle, senderPubkey, memo);
|
|
5134
5160
|
}
|
|
5135
5161
|
try {
|
|
5136
5162
|
const deterministicId = `v5split_${bundle.splitGroupId}`;
|
|
5137
|
-
if (this.tokens.has(deterministicId)) {
|
|
5138
|
-
|
|
5163
|
+
if (this.tokens.has(deterministicId) || this.processedSplitGroupIds.has(bundle.splitGroupId)) {
|
|
5164
|
+
console.log(`[Payments] V5 bundle ${bundle.splitGroupId.slice(0, 12)}... already processed, skipping`);
|
|
5139
5165
|
return { success: true, durationMs: 0 };
|
|
5140
5166
|
}
|
|
5141
5167
|
const registry = TokenRegistry.getInstance();
|
|
@@ -5161,7 +5187,8 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5161
5187
|
sdkData: JSON.stringify({ _pendingFinalization: pendingData })
|
|
5162
5188
|
};
|
|
5163
5189
|
await this.addToken(uiToken);
|
|
5164
|
-
this.
|
|
5190
|
+
this.processedSplitGroupIds.add(bundle.splitGroupId);
|
|
5191
|
+
await this.saveProcessedSplitGroupIds();
|
|
5165
5192
|
const senderInfo = await this.resolveSenderInfo(senderPubkey);
|
|
5166
5193
|
await this.addToHistory({
|
|
5167
5194
|
type: "RECEIVED",
|
|
@@ -5185,6 +5212,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5185
5212
|
await this.save();
|
|
5186
5213
|
this.resolveUnconfirmed().catch(() => {
|
|
5187
5214
|
});
|
|
5215
|
+
this.scheduleResolveUnconfirmed();
|
|
5188
5216
|
return { success: true, durationMs: 0 };
|
|
5189
5217
|
} catch (error) {
|
|
5190
5218
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -5931,28 +5959,70 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5931
5959
|
};
|
|
5932
5960
|
const stClient = this.deps.oracle.getStateTransitionClient?.();
|
|
5933
5961
|
const trustBase = this.deps.oracle.getTrustBase?.();
|
|
5934
|
-
if (!stClient || !trustBase)
|
|
5962
|
+
if (!stClient || !trustBase) {
|
|
5963
|
+
console.log(`[V5-RESOLVE] resolveUnconfirmed: EARLY EXIT \u2014 stClient=${!!stClient} trustBase=${!!trustBase}`);
|
|
5964
|
+
return result;
|
|
5965
|
+
}
|
|
5935
5966
|
const signingService = await this.createSigningService();
|
|
5967
|
+
const submittedCount = Array.from(this.tokens.values()).filter((t) => t.status === "submitted").length;
|
|
5968
|
+
console.log(`[V5-RESOLVE] resolveUnconfirmed: ${submittedCount} submitted token(s) to process`);
|
|
5936
5969
|
for (const [tokenId, token] of this.tokens) {
|
|
5937
5970
|
if (token.status !== "submitted") continue;
|
|
5938
5971
|
const pending2 = this.parsePendingFinalization(token.sdkData);
|
|
5939
5972
|
if (!pending2) {
|
|
5973
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 16)}: no pending finalization metadata, skipping`);
|
|
5940
5974
|
result.stillPending++;
|
|
5941
5975
|
continue;
|
|
5942
5976
|
}
|
|
5943
5977
|
if (pending2.type === "v5_bundle") {
|
|
5978
|
+
console.log(`[V5-RESOLVE] Processing ${tokenId.slice(0, 16)}... stage=${pending2.stage} attempt=${pending2.attemptCount}`);
|
|
5944
5979
|
const progress = await this.resolveV5Token(tokenId, token, pending2, stClient, trustBase, signingService);
|
|
5980
|
+
console.log(`[V5-RESOLVE] Result for ${tokenId.slice(0, 16)}...: ${progress} (stage now: ${pending2.stage})`);
|
|
5945
5981
|
result.details.push({ tokenId, stage: pending2.stage, status: progress });
|
|
5946
5982
|
if (progress === "resolved") result.resolved++;
|
|
5947
5983
|
else if (progress === "failed") result.failed++;
|
|
5948
5984
|
else result.stillPending++;
|
|
5949
5985
|
}
|
|
5950
5986
|
}
|
|
5951
|
-
if (result.resolved > 0 || result.failed > 0) {
|
|
5987
|
+
if (result.resolved > 0 || result.failed > 0 || result.stillPending > 0) {
|
|
5988
|
+
console.log(`[V5-RESOLVE] Saving: resolved=${result.resolved} failed=${result.failed} stillPending=${result.stillPending}`);
|
|
5952
5989
|
await this.save();
|
|
5953
5990
|
}
|
|
5954
5991
|
return result;
|
|
5955
5992
|
}
|
|
5993
|
+
/**
|
|
5994
|
+
* Start a periodic interval that retries resolveUnconfirmed() until all
|
|
5995
|
+
* tokens are confirmed or failed. Stops automatically when nothing is
|
|
5996
|
+
* pending and is cleaned up by destroy().
|
|
5997
|
+
*/
|
|
5998
|
+
scheduleResolveUnconfirmed() {
|
|
5999
|
+
if (this.resolveUnconfirmedTimer) return;
|
|
6000
|
+
const hasUnconfirmed = Array.from(this.tokens.values()).some(
|
|
6001
|
+
(t) => t.status === "submitted"
|
|
6002
|
+
);
|
|
6003
|
+
if (!hasUnconfirmed) {
|
|
6004
|
+
console.log(`[V5-RESOLVE] scheduleResolveUnconfirmed: no submitted tokens, not starting timer`);
|
|
6005
|
+
return;
|
|
6006
|
+
}
|
|
6007
|
+
console.log(`[V5-RESOLVE] scheduleResolveUnconfirmed: starting periodic retry (every ${_PaymentsModule.RESOLVE_UNCONFIRMED_INTERVAL_MS}ms)`);
|
|
6008
|
+
this.resolveUnconfirmedTimer = setInterval(async () => {
|
|
6009
|
+
try {
|
|
6010
|
+
const result = await this.resolveUnconfirmed();
|
|
6011
|
+
if (result.stillPending === 0) {
|
|
6012
|
+
console.log(`[V5-RESOLVE] All tokens resolved, stopping periodic retry`);
|
|
6013
|
+
this.stopResolveUnconfirmedPolling();
|
|
6014
|
+
}
|
|
6015
|
+
} catch (err) {
|
|
6016
|
+
console.log(`[V5-RESOLVE] Periodic retry error:`, err);
|
|
6017
|
+
}
|
|
6018
|
+
}, _PaymentsModule.RESOLVE_UNCONFIRMED_INTERVAL_MS);
|
|
6019
|
+
}
|
|
6020
|
+
stopResolveUnconfirmedPolling() {
|
|
6021
|
+
if (this.resolveUnconfirmedTimer) {
|
|
6022
|
+
clearInterval(this.resolveUnconfirmedTimer);
|
|
6023
|
+
this.resolveUnconfirmedTimer = null;
|
|
6024
|
+
}
|
|
6025
|
+
}
|
|
5956
6026
|
// ===========================================================================
|
|
5957
6027
|
// Private - V5 Lazy Resolution Helpers
|
|
5958
6028
|
// ===========================================================================
|
|
@@ -5965,10 +6035,12 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5965
6035
|
pending2.lastAttemptAt = Date.now();
|
|
5966
6036
|
try {
|
|
5967
6037
|
if (pending2.stage === "RECEIVED") {
|
|
6038
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: RECEIVED \u2192 submitting mint commitment...`);
|
|
5968
6039
|
const mintDataJson = JSON.parse(bundle.recipientMintData);
|
|
5969
6040
|
const mintData = await MintTransactionData3.fromJSON(mintDataJson);
|
|
5970
6041
|
const mintCommitment = await MintCommitment3.create(mintData);
|
|
5971
6042
|
const mintResponse = await stClient.submitMintCommitment(mintCommitment);
|
|
6043
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: mint response status=${mintResponse.status}`);
|
|
5972
6044
|
if (mintResponse.status !== "SUCCESS" && mintResponse.status !== "REQUEST_ID_EXISTS") {
|
|
5973
6045
|
throw new Error(`Mint submission failed: ${mintResponse.status}`);
|
|
5974
6046
|
}
|
|
@@ -5976,22 +6048,27 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5976
6048
|
this.updatePendingFinalization(token, pending2);
|
|
5977
6049
|
}
|
|
5978
6050
|
if (pending2.stage === "MINT_SUBMITTED") {
|
|
6051
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: MINT_SUBMITTED \u2192 checking mint proof...`);
|
|
5979
6052
|
const mintDataJson = JSON.parse(bundle.recipientMintData);
|
|
5980
6053
|
const mintData = await MintTransactionData3.fromJSON(mintDataJson);
|
|
5981
6054
|
const mintCommitment = await MintCommitment3.create(mintData);
|
|
5982
6055
|
const proof = await this.quickProofCheck(stClient, trustBase, mintCommitment);
|
|
5983
6056
|
if (!proof) {
|
|
6057
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: mint proof not yet available, staying MINT_SUBMITTED`);
|
|
5984
6058
|
this.updatePendingFinalization(token, pending2);
|
|
5985
6059
|
return "pending";
|
|
5986
6060
|
}
|
|
6061
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: mint proof obtained!`);
|
|
5987
6062
|
pending2.mintProofJson = JSON.stringify(proof);
|
|
5988
6063
|
pending2.stage = "MINT_PROVEN";
|
|
5989
6064
|
this.updatePendingFinalization(token, pending2);
|
|
5990
6065
|
}
|
|
5991
6066
|
if (pending2.stage === "MINT_PROVEN") {
|
|
6067
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: MINT_PROVEN \u2192 submitting transfer commitment...`);
|
|
5992
6068
|
const transferCommitmentJson = JSON.parse(bundle.transferCommitment);
|
|
5993
6069
|
const transferCommitment = await TransferCommitment4.fromJSON(transferCommitmentJson);
|
|
5994
6070
|
const transferResponse = await stClient.submitTransferCommitment(transferCommitment);
|
|
6071
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: transfer response status=${transferResponse.status}`);
|
|
5995
6072
|
if (transferResponse.status !== "SUCCESS" && transferResponse.status !== "REQUEST_ID_EXISTS") {
|
|
5996
6073
|
throw new Error(`Transfer submission failed: ${transferResponse.status}`);
|
|
5997
6074
|
}
|
|
@@ -5999,13 +6076,16 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5999
6076
|
this.updatePendingFinalization(token, pending2);
|
|
6000
6077
|
}
|
|
6001
6078
|
if (pending2.stage === "TRANSFER_SUBMITTED") {
|
|
6079
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: TRANSFER_SUBMITTED \u2192 checking transfer proof...`);
|
|
6002
6080
|
const transferCommitmentJson = JSON.parse(bundle.transferCommitment);
|
|
6003
6081
|
const transferCommitment = await TransferCommitment4.fromJSON(transferCommitmentJson);
|
|
6004
6082
|
const proof = await this.quickProofCheck(stClient, trustBase, transferCommitment);
|
|
6005
6083
|
if (!proof) {
|
|
6084
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: transfer proof not yet available, staying TRANSFER_SUBMITTED`);
|
|
6006
6085
|
this.updatePendingFinalization(token, pending2);
|
|
6007
6086
|
return "pending";
|
|
6008
6087
|
}
|
|
6088
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: transfer proof obtained! Finalizing...`);
|
|
6009
6089
|
const finalizedToken = await this.finalizeFromV5Bundle(bundle, pending2, signingService, stClient, trustBase);
|
|
6010
6090
|
const confirmedToken = {
|
|
6011
6091
|
id: token.id,
|
|
@@ -6021,6 +6101,12 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6021
6101
|
sdkData: JSON.stringify(finalizedToken.toJSON())
|
|
6022
6102
|
};
|
|
6023
6103
|
this.tokens.set(tokenId, confirmedToken);
|
|
6104
|
+
this.deps.emitEvent("transfer:confirmed", {
|
|
6105
|
+
id: crypto.randomUUID(),
|
|
6106
|
+
status: "completed",
|
|
6107
|
+
tokens: [confirmedToken],
|
|
6108
|
+
tokenTransfers: []
|
|
6109
|
+
});
|
|
6024
6110
|
this.log(`V5 token resolved: ${tokenId.slice(0, 8)}...`);
|
|
6025
6111
|
return "resolved";
|
|
6026
6112
|
}
|
|
@@ -6162,11 +6248,20 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6162
6248
|
}
|
|
6163
6249
|
}
|
|
6164
6250
|
if (pendingTokens.length > 0) {
|
|
6251
|
+
const json = JSON.stringify(pendingTokens);
|
|
6252
|
+
this.log(`[V5-PERSIST] Saving ${pendingTokens.length} pending V5 token(s): ${pendingTokens.map((t) => t.id.slice(0, 16)).join(", ")} (${json.length} bytes)`);
|
|
6165
6253
|
await this.deps.storage.set(
|
|
6166
6254
|
STORAGE_KEYS_ADDRESS.PENDING_V5_TOKENS,
|
|
6167
|
-
|
|
6255
|
+
json
|
|
6168
6256
|
);
|
|
6257
|
+
const verify = await this.deps.storage.get(STORAGE_KEYS_ADDRESS.PENDING_V5_TOKENS);
|
|
6258
|
+
if (!verify) {
|
|
6259
|
+
console.error("[Payments][V5-PERSIST] CRITICAL: KV write succeeded but read-back is empty!");
|
|
6260
|
+
} else {
|
|
6261
|
+
this.log(`[V5-PERSIST] Verified: read-back ${verify.length} bytes`);
|
|
6262
|
+
}
|
|
6169
6263
|
} else {
|
|
6264
|
+
this.log(`[V5-PERSIST] No pending V5 tokens to save (total tokens: ${this.tokens.size}), clearing KV`);
|
|
6170
6265
|
await this.deps.storage.set(STORAGE_KEYS_ADDRESS.PENDING_V5_TOKENS, "");
|
|
6171
6266
|
}
|
|
6172
6267
|
}
|
|
@@ -6176,16 +6271,47 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6176
6271
|
*/
|
|
6177
6272
|
async loadPendingV5Tokens() {
|
|
6178
6273
|
const data = await this.deps.storage.get(STORAGE_KEYS_ADDRESS.PENDING_V5_TOKENS);
|
|
6274
|
+
this.log(`[V5-PERSIST] loadPendingV5Tokens: KV data = ${data ? `${data.length} bytes` : "null/empty"}`);
|
|
6179
6275
|
if (!data) return;
|
|
6180
6276
|
try {
|
|
6181
6277
|
const pendingTokens = JSON.parse(data);
|
|
6278
|
+
this.log(`[V5-PERSIST] Parsed ${pendingTokens.length} pending V5 token(s): ${pendingTokens.map((t) => t.id.slice(0, 16)).join(", ")}`);
|
|
6182
6279
|
for (const token of pendingTokens) {
|
|
6183
6280
|
if (!this.tokens.has(token.id)) {
|
|
6184
6281
|
this.tokens.set(token.id, token);
|
|
6282
|
+
this.log(`[V5-PERSIST] Restored token ${token.id.slice(0, 16)} (status=${token.status})`);
|
|
6283
|
+
} else {
|
|
6284
|
+
this.log(`[V5-PERSIST] Token ${token.id.slice(0, 16)} already in map, skipping`);
|
|
6185
6285
|
}
|
|
6186
6286
|
}
|
|
6187
|
-
|
|
6188
|
-
|
|
6287
|
+
} catch (err) {
|
|
6288
|
+
console.error("[Payments][V5-PERSIST] Failed to parse pending V5 tokens:", err);
|
|
6289
|
+
}
|
|
6290
|
+
}
|
|
6291
|
+
/**
|
|
6292
|
+
* Persist the set of processed splitGroupIds to KV storage.
|
|
6293
|
+
* This ensures Nostr re-deliveries are ignored across page reloads,
|
|
6294
|
+
* even when the confirmed token's in-memory ID differs from v5split_{id}.
|
|
6295
|
+
*/
|
|
6296
|
+
async saveProcessedSplitGroupIds() {
|
|
6297
|
+
const ids = Array.from(this.processedSplitGroupIds);
|
|
6298
|
+
if (ids.length > 0) {
|
|
6299
|
+
await this.deps.storage.set(
|
|
6300
|
+
STORAGE_KEYS_ADDRESS.PROCESSED_SPLIT_GROUP_IDS,
|
|
6301
|
+
JSON.stringify(ids)
|
|
6302
|
+
);
|
|
6303
|
+
}
|
|
6304
|
+
}
|
|
6305
|
+
/**
|
|
6306
|
+
* Load processed splitGroupIds from KV storage.
|
|
6307
|
+
*/
|
|
6308
|
+
async loadProcessedSplitGroupIds() {
|
|
6309
|
+
const data = await this.deps.storage.get(STORAGE_KEYS_ADDRESS.PROCESSED_SPLIT_GROUP_IDS);
|
|
6310
|
+
if (!data) return;
|
|
6311
|
+
try {
|
|
6312
|
+
const ids = JSON.parse(data);
|
|
6313
|
+
for (const id of ids) {
|
|
6314
|
+
this.processedSplitGroupIds.add(id);
|
|
6189
6315
|
}
|
|
6190
6316
|
} catch {
|
|
6191
6317
|
}
|
|
@@ -6840,7 +6966,32 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6840
6966
|
try {
|
|
6841
6967
|
const result = await provider.sync(localData);
|
|
6842
6968
|
if (result.success && result.merged) {
|
|
6969
|
+
const savedTokens = new Map(this.tokens);
|
|
6843
6970
|
this.loadFromStorageData(result.merged);
|
|
6971
|
+
let restoredCount = 0;
|
|
6972
|
+
for (const [tokenId, token] of savedTokens) {
|
|
6973
|
+
if (this.tokens.has(tokenId)) continue;
|
|
6974
|
+
const sdkTokenId = extractTokenIdFromSdkData(token.sdkData);
|
|
6975
|
+
const stateHash = extractStateHashFromSdkData(token.sdkData);
|
|
6976
|
+
if (sdkTokenId && stateHash && this.isStateTombstoned(sdkTokenId, stateHash)) {
|
|
6977
|
+
continue;
|
|
6978
|
+
}
|
|
6979
|
+
if (sdkTokenId) {
|
|
6980
|
+
let hasEquivalent = false;
|
|
6981
|
+
for (const existing of this.tokens.values()) {
|
|
6982
|
+
if (extractTokenIdFromSdkData(existing.sdkData) === sdkTokenId) {
|
|
6983
|
+
hasEquivalent = true;
|
|
6984
|
+
break;
|
|
6985
|
+
}
|
|
6986
|
+
}
|
|
6987
|
+
if (hasEquivalent) continue;
|
|
6988
|
+
}
|
|
6989
|
+
this.tokens.set(tokenId, token);
|
|
6990
|
+
restoredCount++;
|
|
6991
|
+
}
|
|
6992
|
+
if (restoredCount > 0) {
|
|
6993
|
+
console.log(`[Payments] Sync: restored ${restoredCount} token(s) lost by loadFromStorageData`);
|
|
6994
|
+
}
|
|
6844
6995
|
if (this.nametags.length === 0 && savedNametags.length > 0) {
|
|
6845
6996
|
this.nametags = savedNametags;
|
|
6846
6997
|
}
|
|
@@ -7174,8 +7325,9 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
7174
7325
|
return;
|
|
7175
7326
|
}
|
|
7176
7327
|
this.tokens.set(token.id, token);
|
|
7328
|
+
console.log(`[Payments][DEBUG] NOSTR-FIRST: saving token id=${token.id.slice(0, 16)} status=${token.status} sdkData.length=${token.sdkData?.length}`);
|
|
7177
7329
|
await this.save();
|
|
7178
|
-
|
|
7330
|
+
console.log(`[Payments][DEBUG] NOSTR-FIRST: save() completed, tokens.size=${this.tokens.size}`);
|
|
7179
7331
|
const senderInfo = await this.resolveSenderInfo(transfer.senderTransportPubkey);
|
|
7180
7332
|
const incomingTransfer = {
|
|
7181
7333
|
id: transfer.id,
|
|
@@ -7332,8 +7484,12 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
7332
7484
|
}
|
|
7333
7485
|
}
|
|
7334
7486
|
async handleIncomingTransfer(transfer) {
|
|
7487
|
+
if (!this.loaded && this.loadedPromise) {
|
|
7488
|
+
await this.loadedPromise;
|
|
7489
|
+
}
|
|
7335
7490
|
try {
|
|
7336
7491
|
const payload = transfer.payload;
|
|
7492
|
+
console.log("[Payments][DEBUG] handleIncomingTransfer: keys=", Object.keys(payload).join(","));
|
|
7337
7493
|
let instantBundle = null;
|
|
7338
7494
|
if (isInstantSplitBundle(payload)) {
|
|
7339
7495
|
instantBundle = payload;
|
|
@@ -7365,7 +7521,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
7365
7521
|
return;
|
|
7366
7522
|
}
|
|
7367
7523
|
if (payload.sourceToken && payload.commitmentData && !payload.transferTx) {
|
|
7368
|
-
|
|
7524
|
+
console.log("[Payments][DEBUG] >>> NOSTR-FIRST commitment-only transfer detected");
|
|
7369
7525
|
await this.handleCommitmentOnlyTransfer(transfer, payload);
|
|
7370
7526
|
return;
|
|
7371
7527
|
}
|
|
@@ -7528,17 +7684,24 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
7528
7684
|
// ===========================================================================
|
|
7529
7685
|
async save() {
|
|
7530
7686
|
const providers = this.getTokenStorageProviders();
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
return
|
|
7534
|
-
}
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
7538
|
-
|
|
7539
|
-
|
|
7540
|
-
|
|
7687
|
+
const tokenStats = Array.from(this.tokens.values()).map((t) => {
|
|
7688
|
+
const txf = tokenToTxf(t);
|
|
7689
|
+
return `${t.id.slice(0, 12)}(${t.status},txf=${!!txf})`;
|
|
7690
|
+
});
|
|
7691
|
+
console.log(`[Payments][DEBUG] save(): providers=${providers.size}, tokens=[${tokenStats.join(", ")}]`);
|
|
7692
|
+
if (providers.size > 0) {
|
|
7693
|
+
const data = await this.createStorageData();
|
|
7694
|
+
const dataKeys = Object.keys(data).filter((k) => k.startsWith("token-"));
|
|
7695
|
+
console.log(`[Payments][DEBUG] save(): TXF keys=${dataKeys.length} (${dataKeys.join(", ")})`);
|
|
7696
|
+
for (const [id, provider] of providers) {
|
|
7697
|
+
try {
|
|
7698
|
+
await provider.save(data);
|
|
7699
|
+
} catch (err) {
|
|
7700
|
+
console.error(`[Payments] Failed to save to provider ${id}:`, err);
|
|
7701
|
+
}
|
|
7541
7702
|
}
|
|
7703
|
+
} else {
|
|
7704
|
+
console.log("[Payments][DEBUG] save(): No token storage providers - TXF not persisted");
|
|
7542
7705
|
}
|
|
7543
7706
|
await this.savePendingV5Tokens();
|
|
7544
7707
|
}
|
|
@@ -7574,6 +7737,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
7574
7737
|
}
|
|
7575
7738
|
loadFromStorageData(data) {
|
|
7576
7739
|
const parsed = parseTxfStorageData(data);
|
|
7740
|
+
console.log(`[Payments][DEBUG] loadFromStorageData: parsed ${parsed.tokens.length} tokens, ${parsed.tombstones.length} tombstones, errors=[${parsed.validationErrors.join("; ")}]`);
|
|
7577
7741
|
this.tombstones = parsed.tombstones;
|
|
7578
7742
|
this.tokens.clear();
|
|
7579
7743
|
for (const token of parsed.tokens) {
|