@unicitylabs/sphere-sdk 0.4.9 → 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 +384 -119
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +244 -194
- package/dist/core/index.d.ts +244 -194
- package/dist/core/index.js +384 -119
- 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 +67 -3
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js +67 -3
- 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 +64 -5
- package/dist/impl/nodejs/index.cjs.map +1 -1
- package/dist/impl/nodejs/index.d.cts +668 -628
- package/dist/impl/nodejs/index.d.ts +668 -628
- package/dist/impl/nodejs/index.js +64 -5
- package/dist/impl/nodejs/index.js.map +1 -1
- package/dist/index.cjs +384 -119
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +248 -194
- package/dist/index.d.ts +248 -194
- package/dist/index.js +384 -119
- 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.cjs
CHANGED
|
@@ -107,7 +107,9 @@ var init_constants = __esm({
|
|
|
107
107
|
/** Group chat: members for this address */
|
|
108
108
|
GROUP_CHAT_MEMBERS: "group_chat_members",
|
|
109
109
|
/** Group chat: processed event IDs for deduplication */
|
|
110
|
-
GROUP_CHAT_PROCESSED_EVENTS: "group_chat_processed_events"
|
|
110
|
+
GROUP_CHAT_PROCESSED_EVENTS: "group_chat_processed_events",
|
|
111
|
+
/** Processed V5 split group IDs for Nostr re-delivery dedup */
|
|
112
|
+
PROCESSED_SPLIT_GROUP_IDS: "processed_split_group_ids"
|
|
111
113
|
};
|
|
112
114
|
STORAGE_KEYS = {
|
|
113
115
|
...STORAGE_KEYS_GLOBAL,
|
|
@@ -3874,7 +3876,7 @@ var InstantSplitExecutor = class {
|
|
|
3874
3876
|
token: JSON.stringify(bundle),
|
|
3875
3877
|
proof: null,
|
|
3876
3878
|
// Proof is included in the bundle
|
|
3877
|
-
memo:
|
|
3879
|
+
memo: options?.memo,
|
|
3878
3880
|
sender: {
|
|
3879
3881
|
transportPubkey: senderPubkey
|
|
3880
3882
|
}
|
|
@@ -4433,6 +4435,11 @@ var import_MintCommitment3 = require("@unicitylabs/state-transition-sdk/lib/tran
|
|
|
4433
4435
|
var import_MintTransactionData3 = require("@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData");
|
|
4434
4436
|
var import_InclusionProofUtils5 = require("@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils");
|
|
4435
4437
|
var import_InclusionProof = require("@unicitylabs/state-transition-sdk/lib/transaction/InclusionProof");
|
|
4438
|
+
function computeHistoryDedupKey(type, tokenId, transferId) {
|
|
4439
|
+
if (type === "SENT" && transferId) return `${type}_transfer_${transferId}`;
|
|
4440
|
+
if (tokenId) return `${type}_${tokenId}`;
|
|
4441
|
+
return `${type}_${crypto.randomUUID()}`;
|
|
4442
|
+
}
|
|
4436
4443
|
function enrichWithRegistry(info) {
|
|
4437
4444
|
const registry = TokenRegistry.getInstance();
|
|
4438
4445
|
const def = registry.getDefinition(info.coinId);
|
|
@@ -4731,7 +4738,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4731
4738
|
tombstones = [];
|
|
4732
4739
|
archivedTokens = /* @__PURE__ */ new Map();
|
|
4733
4740
|
forkedTokens = /* @__PURE__ */ new Map();
|
|
4734
|
-
|
|
4741
|
+
_historyCache = [];
|
|
4735
4742
|
nametags = [];
|
|
4736
4743
|
// Payment Requests State (Incoming)
|
|
4737
4744
|
paymentRequests = [];
|
|
@@ -4751,6 +4758,17 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4751
4758
|
// Poll every 2s
|
|
4752
4759
|
static PROOF_POLLING_MAX_ATTEMPTS = 30;
|
|
4753
4760
|
// Max 30 attempts (~60s)
|
|
4761
|
+
// Periodic retry for resolveUnconfirmed (V5 lazy finalization)
|
|
4762
|
+
resolveUnconfirmedTimer = null;
|
|
4763
|
+
static RESOLVE_UNCONFIRMED_INTERVAL_MS = 1e4;
|
|
4764
|
+
// Retry every 10s
|
|
4765
|
+
// Guard: ensure load() completes before processing incoming bundles
|
|
4766
|
+
loadedPromise = null;
|
|
4767
|
+
loaded = false;
|
|
4768
|
+
// Persistent dedup: tracks splitGroupIds that have been fully processed.
|
|
4769
|
+
// Survives page reloads via KV storage so Nostr re-deliveries are ignored
|
|
4770
|
+
// even when the confirmed token's in-memory ID differs from v5split_{id}.
|
|
4771
|
+
processedSplitGroupIds = /* @__PURE__ */ new Set();
|
|
4754
4772
|
// Storage event subscriptions (push-based sync)
|
|
4755
4773
|
storageEventUnsubscribers = [];
|
|
4756
4774
|
syncDebounceTimer = null;
|
|
@@ -4800,7 +4818,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4800
4818
|
this.tombstones = [];
|
|
4801
4819
|
this.archivedTokens.clear();
|
|
4802
4820
|
this.forkedTokens.clear();
|
|
4803
|
-
this.
|
|
4821
|
+
this._historyCache = [];
|
|
4804
4822
|
this.nametags = [];
|
|
4805
4823
|
this.deps = deps;
|
|
4806
4824
|
this.priceProvider = deps.price ?? null;
|
|
@@ -4836,38 +4854,40 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4836
4854
|
*/
|
|
4837
4855
|
async load() {
|
|
4838
4856
|
this.ensureInitialized();
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4857
|
+
const doLoad = async () => {
|
|
4858
|
+
await TokenRegistry.waitForReady();
|
|
4859
|
+
const providers = this.getTokenStorageProviders();
|
|
4860
|
+
for (const [id, provider] of providers) {
|
|
4861
|
+
try {
|
|
4862
|
+
const result = await provider.load();
|
|
4863
|
+
if (result.success && result.data) {
|
|
4864
|
+
this.loadFromStorageData(result.data);
|
|
4865
|
+
this.log(`Loaded metadata from provider ${id}`);
|
|
4866
|
+
break;
|
|
4867
|
+
}
|
|
4868
|
+
} catch (err) {
|
|
4869
|
+
console.error(`[Payments] Failed to load from provider ${id}:`, err);
|
|
4848
4870
|
}
|
|
4849
|
-
} catch (err) {
|
|
4850
|
-
console.error(`[Payments] Failed to load from provider ${id}:`, err);
|
|
4851
|
-
}
|
|
4852
|
-
}
|
|
4853
|
-
await this.loadPendingV5Tokens();
|
|
4854
|
-
const historyData = await this.deps.storage.get(STORAGE_KEYS_ADDRESS.TRANSACTION_HISTORY);
|
|
4855
|
-
if (historyData) {
|
|
4856
|
-
try {
|
|
4857
|
-
this.transactionHistory = JSON.parse(historyData);
|
|
4858
|
-
} catch {
|
|
4859
|
-
this.transactionHistory = [];
|
|
4860
4871
|
}
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4872
|
+
const loadedTokens = Array.from(this.tokens.values()).map((t) => `${t.id.slice(0, 12)}(${t.status})`);
|
|
4873
|
+
console.log(`[Payments][DEBUG] load(): from TXF providers: ${this.tokens.size} tokens [${loadedTokens.join(", ")}]`);
|
|
4874
|
+
await this.loadPendingV5Tokens();
|
|
4875
|
+
await this.loadProcessedSplitGroupIds();
|
|
4876
|
+
await this.loadHistory();
|
|
4877
|
+
const pending2 = await this.deps.storage.get(STORAGE_KEYS_ADDRESS.PENDING_TRANSFERS);
|
|
4878
|
+
if (pending2) {
|
|
4879
|
+
const transfers = JSON.parse(pending2);
|
|
4880
|
+
for (const transfer of transfers) {
|
|
4881
|
+
this.pendingTransfers.set(transfer.id, transfer);
|
|
4882
|
+
}
|
|
4867
4883
|
}
|
|
4868
|
-
|
|
4884
|
+
this.loaded = true;
|
|
4885
|
+
};
|
|
4886
|
+
this.loadedPromise = doLoad();
|
|
4887
|
+
await this.loadedPromise;
|
|
4869
4888
|
this.resolveUnconfirmed().catch(() => {
|
|
4870
4889
|
});
|
|
4890
|
+
this.scheduleResolveUnconfirmed();
|
|
4871
4891
|
}
|
|
4872
4892
|
/**
|
|
4873
4893
|
* Cleanup all subscriptions, polling jobs, and pending resolvers.
|
|
@@ -4886,6 +4906,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4886
4906
|
this.paymentRequestResponseHandlers.clear();
|
|
4887
4907
|
this.stopProofPolling();
|
|
4888
4908
|
this.proofPollingJobs.clear();
|
|
4909
|
+
this.stopResolveUnconfirmedPolling();
|
|
4889
4910
|
for (const [, resolver] of this.pendingResponseResolvers) {
|
|
4890
4911
|
clearTimeout(resolver.timeout);
|
|
4891
4912
|
resolver.reject(new Error("Module destroyed"));
|
|
@@ -4945,7 +4966,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4945
4966
|
}
|
|
4946
4967
|
await this.saveToOutbox(result, recipientPubkey);
|
|
4947
4968
|
result.status = "submitted";
|
|
4948
|
-
const recipientNametag = request.recipient.startsWith("@") ? request.recipient.slice(1) : void 0;
|
|
4969
|
+
const recipientNametag = peerInfo?.nametag || (request.recipient.startsWith("@") ? request.recipient.slice(1) : void 0);
|
|
4949
4970
|
const transferMode = request.transferMode ?? "instant";
|
|
4950
4971
|
if (splitPlan.requiresSplit && splitPlan.tokenToSplit) {
|
|
4951
4972
|
if (transferMode === "conservative") {
|
|
@@ -4976,7 +4997,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4976
4997
|
updatedAt: Date.now(),
|
|
4977
4998
|
sdkData: JSON.stringify(changeTokenData)
|
|
4978
4999
|
};
|
|
4979
|
-
await this.addToken(changeUiToken
|
|
5000
|
+
await this.addToken(changeUiToken);
|
|
4980
5001
|
this.log(`Conservative split: change token saved: ${changeUiToken.id}`);
|
|
4981
5002
|
await this.deps.transport.sendTokenTransfer(recipientPubkey, {
|
|
4982
5003
|
sourceToken: JSON.stringify(splitResult.tokenForRecipient.toJSON()),
|
|
@@ -4985,7 +5006,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4985
5006
|
});
|
|
4986
5007
|
const splitCommitmentRequestId = splitResult.recipientTransferTx?.data?.requestId ?? splitResult.recipientTransferTx?.requestId;
|
|
4987
5008
|
const splitRequestIdHex = splitCommitmentRequestId instanceof Uint8Array ? Array.from(splitCommitmentRequestId).map((b) => b.toString(16).padStart(2, "0")).join("") : splitCommitmentRequestId ? String(splitCommitmentRequestId) : void 0;
|
|
4988
|
-
await this.removeToken(splitPlan.tokenToSplit.uiToken.id
|
|
5009
|
+
await this.removeToken(splitPlan.tokenToSplit.uiToken.id);
|
|
4989
5010
|
result.tokenTransfers.push({
|
|
4990
5011
|
sourceTokenId: splitPlan.tokenToSplit.uiToken.id,
|
|
4991
5012
|
method: "split",
|
|
@@ -5010,6 +5031,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5010
5031
|
this.deps.transport,
|
|
5011
5032
|
recipientPubkey,
|
|
5012
5033
|
{
|
|
5034
|
+
memo: request.memo,
|
|
5013
5035
|
onChangeTokenCreated: async (changeToken) => {
|
|
5014
5036
|
const changeTokenData = changeToken.toJSON();
|
|
5015
5037
|
const uiToken = {
|
|
@@ -5025,7 +5047,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5025
5047
|
updatedAt: Date.now(),
|
|
5026
5048
|
sdkData: JSON.stringify(changeTokenData)
|
|
5027
5049
|
};
|
|
5028
|
-
await this.addToken(uiToken
|
|
5050
|
+
await this.addToken(uiToken);
|
|
5029
5051
|
this.log(`Change token saved via background: ${uiToken.id}`);
|
|
5030
5052
|
},
|
|
5031
5053
|
onStorageSync: async () => {
|
|
@@ -5040,7 +5062,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5040
5062
|
if (instantResult.backgroundPromise) {
|
|
5041
5063
|
this.pendingBackgroundTasks.push(instantResult.backgroundPromise);
|
|
5042
5064
|
}
|
|
5043
|
-
await this.removeToken(splitPlan.tokenToSplit.uiToken.id
|
|
5065
|
+
await this.removeToken(splitPlan.tokenToSplit.uiToken.id);
|
|
5044
5066
|
result.tokenTransfers.push({
|
|
5045
5067
|
sourceTokenId: splitPlan.tokenToSplit.uiToken.id,
|
|
5046
5068
|
method: "split",
|
|
@@ -5087,20 +5109,25 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5087
5109
|
requestIdHex
|
|
5088
5110
|
});
|
|
5089
5111
|
this.log(`Token ${token.id} sent via ${transferMode.toUpperCase()}, requestId: ${requestIdHex}`);
|
|
5090
|
-
await this.removeToken(token.id
|
|
5112
|
+
await this.removeToken(token.id);
|
|
5091
5113
|
}
|
|
5092
5114
|
result.status = "delivered";
|
|
5093
5115
|
await this.save();
|
|
5094
5116
|
await this.removeFromOutbox(result.id);
|
|
5095
5117
|
result.status = "completed";
|
|
5118
|
+
const sentTokenId = result.tokens[0] ? extractTokenIdFromSdkData(result.tokens[0].sdkData) : void 0;
|
|
5096
5119
|
await this.addToHistory({
|
|
5097
5120
|
type: "SENT",
|
|
5098
5121
|
amount: request.amount,
|
|
5099
5122
|
coinId: request.coinId,
|
|
5100
5123
|
symbol: this.getCoinSymbol(request.coinId),
|
|
5101
5124
|
timestamp: Date.now(),
|
|
5125
|
+
recipientPubkey,
|
|
5102
5126
|
recipientNametag,
|
|
5103
|
-
|
|
5127
|
+
recipientAddress: peerInfo?.directAddress || recipientAddress?.toString() || recipientPubkey,
|
|
5128
|
+
memo: request.memo,
|
|
5129
|
+
transferId: result.id,
|
|
5130
|
+
tokenId: sentTokenId || void 0
|
|
5104
5131
|
});
|
|
5105
5132
|
this.deps.emitEvent("transfer:confirmed", result);
|
|
5106
5133
|
return result;
|
|
@@ -5211,6 +5238,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5211
5238
|
recipientPubkey,
|
|
5212
5239
|
{
|
|
5213
5240
|
...options,
|
|
5241
|
+
memo: request.memo,
|
|
5214
5242
|
onChangeTokenCreated: async (changeToken) => {
|
|
5215
5243
|
const changeTokenData = changeToken.toJSON();
|
|
5216
5244
|
const uiToken = {
|
|
@@ -5226,7 +5254,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5226
5254
|
updatedAt: Date.now(),
|
|
5227
5255
|
sdkData: JSON.stringify(changeTokenData)
|
|
5228
5256
|
};
|
|
5229
|
-
await this.addToken(uiToken
|
|
5257
|
+
await this.addToken(uiToken);
|
|
5230
5258
|
this.log(`Change token saved via background: ${uiToken.id}`);
|
|
5231
5259
|
},
|
|
5232
5260
|
onStorageSync: async () => {
|
|
@@ -5239,15 +5267,20 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5239
5267
|
if (result.backgroundPromise) {
|
|
5240
5268
|
this.pendingBackgroundTasks.push(result.backgroundPromise);
|
|
5241
5269
|
}
|
|
5242
|
-
|
|
5243
|
-
|
|
5270
|
+
await this.removeToken(tokenToSplit.id);
|
|
5271
|
+
const recipientNametag = peerInfo?.nametag || (request.recipient.startsWith("@") ? request.recipient.slice(1) : void 0);
|
|
5272
|
+
const splitTokenId = extractTokenIdFromSdkData(tokenToSplit.sdkData);
|
|
5244
5273
|
await this.addToHistory({
|
|
5245
5274
|
type: "SENT",
|
|
5246
5275
|
amount: request.amount,
|
|
5247
5276
|
coinId: request.coinId,
|
|
5248
5277
|
symbol: this.getCoinSymbol(request.coinId),
|
|
5249
5278
|
timestamp: Date.now(),
|
|
5250
|
-
|
|
5279
|
+
recipientPubkey,
|
|
5280
|
+
recipientNametag,
|
|
5281
|
+
recipientAddress: peerInfo?.directAddress || recipientAddress?.toString() || recipientPubkey,
|
|
5282
|
+
memo: request.memo,
|
|
5283
|
+
tokenId: splitTokenId || void 0
|
|
5251
5284
|
});
|
|
5252
5285
|
await this.save();
|
|
5253
5286
|
} else {
|
|
@@ -5278,15 +5311,18 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5278
5311
|
* @param senderPubkey - Sender's public key for verification
|
|
5279
5312
|
* @returns Processing result with finalized token
|
|
5280
5313
|
*/
|
|
5281
|
-
async processInstantSplitBundle(bundle, senderPubkey) {
|
|
5314
|
+
async processInstantSplitBundle(bundle, senderPubkey, memo) {
|
|
5282
5315
|
this.ensureInitialized();
|
|
5316
|
+
if (!this.loaded && this.loadedPromise) {
|
|
5317
|
+
await this.loadedPromise;
|
|
5318
|
+
}
|
|
5283
5319
|
if (!isInstantSplitBundleV5(bundle)) {
|
|
5284
|
-
return this.processInstantSplitBundleSync(bundle, senderPubkey);
|
|
5320
|
+
return this.processInstantSplitBundleSync(bundle, senderPubkey, memo);
|
|
5285
5321
|
}
|
|
5286
5322
|
try {
|
|
5287
5323
|
const deterministicId = `v5split_${bundle.splitGroupId}`;
|
|
5288
|
-
if (this.tokens.has(deterministicId)) {
|
|
5289
|
-
|
|
5324
|
+
if (this.tokens.has(deterministicId) || this.processedSplitGroupIds.has(bundle.splitGroupId)) {
|
|
5325
|
+
console.log(`[Payments] V5 bundle ${bundle.splitGroupId.slice(0, 12)}... already processed, skipping`);
|
|
5290
5326
|
return { success: true, durationMs: 0 };
|
|
5291
5327
|
}
|
|
5292
5328
|
const registry = TokenRegistry.getInstance();
|
|
@@ -5311,17 +5347,33 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5311
5347
|
updatedAt: Date.now(),
|
|
5312
5348
|
sdkData: JSON.stringify({ _pendingFinalization: pendingData })
|
|
5313
5349
|
};
|
|
5314
|
-
await this.addToken(uiToken
|
|
5315
|
-
this.
|
|
5350
|
+
await this.addToken(uiToken);
|
|
5351
|
+
this.processedSplitGroupIds.add(bundle.splitGroupId);
|
|
5352
|
+
await this.saveProcessedSplitGroupIds();
|
|
5353
|
+
const senderInfo = await this.resolveSenderInfo(senderPubkey);
|
|
5354
|
+
await this.addToHistory({
|
|
5355
|
+
type: "RECEIVED",
|
|
5356
|
+
amount: bundle.amount,
|
|
5357
|
+
coinId: bundle.coinId,
|
|
5358
|
+
symbol: uiToken.symbol,
|
|
5359
|
+
timestamp: Date.now(),
|
|
5360
|
+
senderPubkey,
|
|
5361
|
+
...senderInfo,
|
|
5362
|
+
memo,
|
|
5363
|
+
tokenId: deterministicId
|
|
5364
|
+
});
|
|
5316
5365
|
this.deps.emitEvent("transfer:incoming", {
|
|
5317
5366
|
id: bundle.splitGroupId,
|
|
5318
5367
|
senderPubkey,
|
|
5368
|
+
senderNametag: senderInfo.senderNametag,
|
|
5319
5369
|
tokens: [uiToken],
|
|
5370
|
+
memo,
|
|
5320
5371
|
receivedAt: Date.now()
|
|
5321
5372
|
});
|
|
5322
5373
|
await this.save();
|
|
5323
5374
|
this.resolveUnconfirmed().catch(() => {
|
|
5324
5375
|
});
|
|
5376
|
+
this.scheduleResolveUnconfirmed();
|
|
5325
5377
|
return { success: true, durationMs: 0 };
|
|
5326
5378
|
} catch (error) {
|
|
5327
5379
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -5336,7 +5388,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5336
5388
|
* Synchronous V4 bundle processing (dev mode only).
|
|
5337
5389
|
* Kept for backward compatibility with V4 bundles.
|
|
5338
5390
|
*/
|
|
5339
|
-
async processInstantSplitBundleSync(bundle, senderPubkey) {
|
|
5391
|
+
async processInstantSplitBundleSync(bundle, senderPubkey, memo) {
|
|
5340
5392
|
try {
|
|
5341
5393
|
const signingService = await this.createSigningService();
|
|
5342
5394
|
const stClient = this.deps.oracle.getStateTransitionClient?.();
|
|
@@ -5396,19 +5448,26 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5396
5448
|
sdkData: JSON.stringify(tokenData)
|
|
5397
5449
|
};
|
|
5398
5450
|
await this.addToken(uiToken);
|
|
5451
|
+
const receivedTokenId = extractTokenIdFromSdkData(uiToken.sdkData);
|
|
5452
|
+
const senderInfo = await this.resolveSenderInfo(senderPubkey);
|
|
5399
5453
|
await this.addToHistory({
|
|
5400
5454
|
type: "RECEIVED",
|
|
5401
5455
|
amount: bundle.amount,
|
|
5402
5456
|
coinId: info.coinId,
|
|
5403
5457
|
symbol: info.symbol,
|
|
5404
5458
|
timestamp: Date.now(),
|
|
5405
|
-
senderPubkey
|
|
5459
|
+
senderPubkey,
|
|
5460
|
+
...senderInfo,
|
|
5461
|
+
memo,
|
|
5462
|
+
tokenId: receivedTokenId || uiToken.id
|
|
5406
5463
|
});
|
|
5407
5464
|
await this.save();
|
|
5408
5465
|
this.deps.emitEvent("transfer:incoming", {
|
|
5409
5466
|
id: bundle.splitGroupId,
|
|
5410
5467
|
senderPubkey,
|
|
5468
|
+
senderNametag: senderInfo.senderNametag,
|
|
5411
5469
|
tokens: [uiToken],
|
|
5470
|
+
memo,
|
|
5412
5471
|
receivedAt: Date.now()
|
|
5413
5472
|
});
|
|
5414
5473
|
}
|
|
@@ -6061,28 +6120,70 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6061
6120
|
};
|
|
6062
6121
|
const stClient = this.deps.oracle.getStateTransitionClient?.();
|
|
6063
6122
|
const trustBase = this.deps.oracle.getTrustBase?.();
|
|
6064
|
-
if (!stClient || !trustBase)
|
|
6123
|
+
if (!stClient || !trustBase) {
|
|
6124
|
+
console.log(`[V5-RESOLVE] resolveUnconfirmed: EARLY EXIT \u2014 stClient=${!!stClient} trustBase=${!!trustBase}`);
|
|
6125
|
+
return result;
|
|
6126
|
+
}
|
|
6065
6127
|
const signingService = await this.createSigningService();
|
|
6128
|
+
const submittedCount = Array.from(this.tokens.values()).filter((t) => t.status === "submitted").length;
|
|
6129
|
+
console.log(`[V5-RESOLVE] resolveUnconfirmed: ${submittedCount} submitted token(s) to process`);
|
|
6066
6130
|
for (const [tokenId, token] of this.tokens) {
|
|
6067
6131
|
if (token.status !== "submitted") continue;
|
|
6068
6132
|
const pending2 = this.parsePendingFinalization(token.sdkData);
|
|
6069
6133
|
if (!pending2) {
|
|
6134
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 16)}: no pending finalization metadata, skipping`);
|
|
6070
6135
|
result.stillPending++;
|
|
6071
6136
|
continue;
|
|
6072
6137
|
}
|
|
6073
6138
|
if (pending2.type === "v5_bundle") {
|
|
6139
|
+
console.log(`[V5-RESOLVE] Processing ${tokenId.slice(0, 16)}... stage=${pending2.stage} attempt=${pending2.attemptCount}`);
|
|
6074
6140
|
const progress = await this.resolveV5Token(tokenId, token, pending2, stClient, trustBase, signingService);
|
|
6141
|
+
console.log(`[V5-RESOLVE] Result for ${tokenId.slice(0, 16)}...: ${progress} (stage now: ${pending2.stage})`);
|
|
6075
6142
|
result.details.push({ tokenId, stage: pending2.stage, status: progress });
|
|
6076
6143
|
if (progress === "resolved") result.resolved++;
|
|
6077
6144
|
else if (progress === "failed") result.failed++;
|
|
6078
6145
|
else result.stillPending++;
|
|
6079
6146
|
}
|
|
6080
6147
|
}
|
|
6081
|
-
if (result.resolved > 0 || result.failed > 0) {
|
|
6148
|
+
if (result.resolved > 0 || result.failed > 0 || result.stillPending > 0) {
|
|
6149
|
+
console.log(`[V5-RESOLVE] Saving: resolved=${result.resolved} failed=${result.failed} stillPending=${result.stillPending}`);
|
|
6082
6150
|
await this.save();
|
|
6083
6151
|
}
|
|
6084
6152
|
return result;
|
|
6085
6153
|
}
|
|
6154
|
+
/**
|
|
6155
|
+
* Start a periodic interval that retries resolveUnconfirmed() until all
|
|
6156
|
+
* tokens are confirmed or failed. Stops automatically when nothing is
|
|
6157
|
+
* pending and is cleaned up by destroy().
|
|
6158
|
+
*/
|
|
6159
|
+
scheduleResolveUnconfirmed() {
|
|
6160
|
+
if (this.resolveUnconfirmedTimer) return;
|
|
6161
|
+
const hasUnconfirmed = Array.from(this.tokens.values()).some(
|
|
6162
|
+
(t) => t.status === "submitted"
|
|
6163
|
+
);
|
|
6164
|
+
if (!hasUnconfirmed) {
|
|
6165
|
+
console.log(`[V5-RESOLVE] scheduleResolveUnconfirmed: no submitted tokens, not starting timer`);
|
|
6166
|
+
return;
|
|
6167
|
+
}
|
|
6168
|
+
console.log(`[V5-RESOLVE] scheduleResolveUnconfirmed: starting periodic retry (every ${_PaymentsModule.RESOLVE_UNCONFIRMED_INTERVAL_MS}ms)`);
|
|
6169
|
+
this.resolveUnconfirmedTimer = setInterval(async () => {
|
|
6170
|
+
try {
|
|
6171
|
+
const result = await this.resolveUnconfirmed();
|
|
6172
|
+
if (result.stillPending === 0) {
|
|
6173
|
+
console.log(`[V5-RESOLVE] All tokens resolved, stopping periodic retry`);
|
|
6174
|
+
this.stopResolveUnconfirmedPolling();
|
|
6175
|
+
}
|
|
6176
|
+
} catch (err) {
|
|
6177
|
+
console.log(`[V5-RESOLVE] Periodic retry error:`, err);
|
|
6178
|
+
}
|
|
6179
|
+
}, _PaymentsModule.RESOLVE_UNCONFIRMED_INTERVAL_MS);
|
|
6180
|
+
}
|
|
6181
|
+
stopResolveUnconfirmedPolling() {
|
|
6182
|
+
if (this.resolveUnconfirmedTimer) {
|
|
6183
|
+
clearInterval(this.resolveUnconfirmedTimer);
|
|
6184
|
+
this.resolveUnconfirmedTimer = null;
|
|
6185
|
+
}
|
|
6186
|
+
}
|
|
6086
6187
|
// ===========================================================================
|
|
6087
6188
|
// Private - V5 Lazy Resolution Helpers
|
|
6088
6189
|
// ===========================================================================
|
|
@@ -6095,10 +6196,12 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6095
6196
|
pending2.lastAttemptAt = Date.now();
|
|
6096
6197
|
try {
|
|
6097
6198
|
if (pending2.stage === "RECEIVED") {
|
|
6199
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: RECEIVED \u2192 submitting mint commitment...`);
|
|
6098
6200
|
const mintDataJson = JSON.parse(bundle.recipientMintData);
|
|
6099
6201
|
const mintData = await import_MintTransactionData3.MintTransactionData.fromJSON(mintDataJson);
|
|
6100
6202
|
const mintCommitment = await import_MintCommitment3.MintCommitment.create(mintData);
|
|
6101
6203
|
const mintResponse = await stClient.submitMintCommitment(mintCommitment);
|
|
6204
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: mint response status=${mintResponse.status}`);
|
|
6102
6205
|
if (mintResponse.status !== "SUCCESS" && mintResponse.status !== "REQUEST_ID_EXISTS") {
|
|
6103
6206
|
throw new Error(`Mint submission failed: ${mintResponse.status}`);
|
|
6104
6207
|
}
|
|
@@ -6106,22 +6209,27 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6106
6209
|
this.updatePendingFinalization(token, pending2);
|
|
6107
6210
|
}
|
|
6108
6211
|
if (pending2.stage === "MINT_SUBMITTED") {
|
|
6212
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: MINT_SUBMITTED \u2192 checking mint proof...`);
|
|
6109
6213
|
const mintDataJson = JSON.parse(bundle.recipientMintData);
|
|
6110
6214
|
const mintData = await import_MintTransactionData3.MintTransactionData.fromJSON(mintDataJson);
|
|
6111
6215
|
const mintCommitment = await import_MintCommitment3.MintCommitment.create(mintData);
|
|
6112
6216
|
const proof = await this.quickProofCheck(stClient, trustBase, mintCommitment);
|
|
6113
6217
|
if (!proof) {
|
|
6218
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: mint proof not yet available, staying MINT_SUBMITTED`);
|
|
6114
6219
|
this.updatePendingFinalization(token, pending2);
|
|
6115
6220
|
return "pending";
|
|
6116
6221
|
}
|
|
6222
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: mint proof obtained!`);
|
|
6117
6223
|
pending2.mintProofJson = JSON.stringify(proof);
|
|
6118
6224
|
pending2.stage = "MINT_PROVEN";
|
|
6119
6225
|
this.updatePendingFinalization(token, pending2);
|
|
6120
6226
|
}
|
|
6121
6227
|
if (pending2.stage === "MINT_PROVEN") {
|
|
6228
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: MINT_PROVEN \u2192 submitting transfer commitment...`);
|
|
6122
6229
|
const transferCommitmentJson = JSON.parse(bundle.transferCommitment);
|
|
6123
6230
|
const transferCommitment = await import_TransferCommitment4.TransferCommitment.fromJSON(transferCommitmentJson);
|
|
6124
6231
|
const transferResponse = await stClient.submitTransferCommitment(transferCommitment);
|
|
6232
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: transfer response status=${transferResponse.status}`);
|
|
6125
6233
|
if (transferResponse.status !== "SUCCESS" && transferResponse.status !== "REQUEST_ID_EXISTS") {
|
|
6126
6234
|
throw new Error(`Transfer submission failed: ${transferResponse.status}`);
|
|
6127
6235
|
}
|
|
@@ -6129,13 +6237,16 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6129
6237
|
this.updatePendingFinalization(token, pending2);
|
|
6130
6238
|
}
|
|
6131
6239
|
if (pending2.stage === "TRANSFER_SUBMITTED") {
|
|
6240
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: TRANSFER_SUBMITTED \u2192 checking transfer proof...`);
|
|
6132
6241
|
const transferCommitmentJson = JSON.parse(bundle.transferCommitment);
|
|
6133
6242
|
const transferCommitment = await import_TransferCommitment4.TransferCommitment.fromJSON(transferCommitmentJson);
|
|
6134
6243
|
const proof = await this.quickProofCheck(stClient, trustBase, transferCommitment);
|
|
6135
6244
|
if (!proof) {
|
|
6245
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: transfer proof not yet available, staying TRANSFER_SUBMITTED`);
|
|
6136
6246
|
this.updatePendingFinalization(token, pending2);
|
|
6137
6247
|
return "pending";
|
|
6138
6248
|
}
|
|
6249
|
+
console.log(`[V5-RESOLVE] ${tokenId.slice(0, 12)}: transfer proof obtained! Finalizing...`);
|
|
6139
6250
|
const finalizedToken = await this.finalizeFromV5Bundle(bundle, pending2, signingService, stClient, trustBase);
|
|
6140
6251
|
const confirmedToken = {
|
|
6141
6252
|
id: token.id,
|
|
@@ -6151,13 +6262,11 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6151
6262
|
sdkData: JSON.stringify(finalizedToken.toJSON())
|
|
6152
6263
|
};
|
|
6153
6264
|
this.tokens.set(tokenId, confirmedToken);
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
timestamp: Date.now(),
|
|
6160
|
-
senderPubkey: pending2.senderPubkey
|
|
6265
|
+
this.deps.emitEvent("transfer:confirmed", {
|
|
6266
|
+
id: crypto.randomUUID(),
|
|
6267
|
+
status: "completed",
|
|
6268
|
+
tokens: [confirmedToken],
|
|
6269
|
+
tokenTransfers: []
|
|
6161
6270
|
});
|
|
6162
6271
|
this.log(`V5 token resolved: ${tokenId.slice(0, 8)}...`);
|
|
6163
6272
|
return "resolved";
|
|
@@ -6300,11 +6409,20 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6300
6409
|
}
|
|
6301
6410
|
}
|
|
6302
6411
|
if (pendingTokens.length > 0) {
|
|
6412
|
+
const json = JSON.stringify(pendingTokens);
|
|
6413
|
+
this.log(`[V5-PERSIST] Saving ${pendingTokens.length} pending V5 token(s): ${pendingTokens.map((t) => t.id.slice(0, 16)).join(", ")} (${json.length} bytes)`);
|
|
6303
6414
|
await this.deps.storage.set(
|
|
6304
6415
|
STORAGE_KEYS_ADDRESS.PENDING_V5_TOKENS,
|
|
6305
|
-
|
|
6416
|
+
json
|
|
6306
6417
|
);
|
|
6418
|
+
const verify = await this.deps.storage.get(STORAGE_KEYS_ADDRESS.PENDING_V5_TOKENS);
|
|
6419
|
+
if (!verify) {
|
|
6420
|
+
console.error("[Payments][V5-PERSIST] CRITICAL: KV write succeeded but read-back is empty!");
|
|
6421
|
+
} else {
|
|
6422
|
+
this.log(`[V5-PERSIST] Verified: read-back ${verify.length} bytes`);
|
|
6423
|
+
}
|
|
6307
6424
|
} else {
|
|
6425
|
+
this.log(`[V5-PERSIST] No pending V5 tokens to save (total tokens: ${this.tokens.size}), clearing KV`);
|
|
6308
6426
|
await this.deps.storage.set(STORAGE_KEYS_ADDRESS.PENDING_V5_TOKENS, "");
|
|
6309
6427
|
}
|
|
6310
6428
|
}
|
|
@@ -6314,16 +6432,47 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6314
6432
|
*/
|
|
6315
6433
|
async loadPendingV5Tokens() {
|
|
6316
6434
|
const data = await this.deps.storage.get(STORAGE_KEYS_ADDRESS.PENDING_V5_TOKENS);
|
|
6435
|
+
this.log(`[V5-PERSIST] loadPendingV5Tokens: KV data = ${data ? `${data.length} bytes` : "null/empty"}`);
|
|
6317
6436
|
if (!data) return;
|
|
6318
6437
|
try {
|
|
6319
6438
|
const pendingTokens = JSON.parse(data);
|
|
6439
|
+
this.log(`[V5-PERSIST] Parsed ${pendingTokens.length} pending V5 token(s): ${pendingTokens.map((t) => t.id.slice(0, 16)).join(", ")}`);
|
|
6320
6440
|
for (const token of pendingTokens) {
|
|
6321
6441
|
if (!this.tokens.has(token.id)) {
|
|
6322
6442
|
this.tokens.set(token.id, token);
|
|
6443
|
+
this.log(`[V5-PERSIST] Restored token ${token.id.slice(0, 16)} (status=${token.status})`);
|
|
6444
|
+
} else {
|
|
6445
|
+
this.log(`[V5-PERSIST] Token ${token.id.slice(0, 16)} already in map, skipping`);
|
|
6323
6446
|
}
|
|
6324
6447
|
}
|
|
6325
|
-
|
|
6326
|
-
|
|
6448
|
+
} catch (err) {
|
|
6449
|
+
console.error("[Payments][V5-PERSIST] Failed to parse pending V5 tokens:", err);
|
|
6450
|
+
}
|
|
6451
|
+
}
|
|
6452
|
+
/**
|
|
6453
|
+
* Persist the set of processed splitGroupIds to KV storage.
|
|
6454
|
+
* This ensures Nostr re-deliveries are ignored across page reloads,
|
|
6455
|
+
* even when the confirmed token's in-memory ID differs from v5split_{id}.
|
|
6456
|
+
*/
|
|
6457
|
+
async saveProcessedSplitGroupIds() {
|
|
6458
|
+
const ids = Array.from(this.processedSplitGroupIds);
|
|
6459
|
+
if (ids.length > 0) {
|
|
6460
|
+
await this.deps.storage.set(
|
|
6461
|
+
STORAGE_KEYS_ADDRESS.PROCESSED_SPLIT_GROUP_IDS,
|
|
6462
|
+
JSON.stringify(ids)
|
|
6463
|
+
);
|
|
6464
|
+
}
|
|
6465
|
+
}
|
|
6466
|
+
/**
|
|
6467
|
+
* Load processed splitGroupIds from KV storage.
|
|
6468
|
+
*/
|
|
6469
|
+
async loadProcessedSplitGroupIds() {
|
|
6470
|
+
const data = await this.deps.storage.get(STORAGE_KEYS_ADDRESS.PROCESSED_SPLIT_GROUP_IDS);
|
|
6471
|
+
if (!data) return;
|
|
6472
|
+
try {
|
|
6473
|
+
const ids = JSON.parse(data);
|
|
6474
|
+
for (const id of ids) {
|
|
6475
|
+
this.processedSplitGroupIds.add(id);
|
|
6327
6476
|
}
|
|
6328
6477
|
} catch {
|
|
6329
6478
|
}
|
|
@@ -6342,10 +6491,9 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6342
6491
|
* the old state is archived and replaced with the incoming one.
|
|
6343
6492
|
*
|
|
6344
6493
|
* @param token - The token to add.
|
|
6345
|
-
* @param skipHistory - When `true`, do not create a `RECEIVED` transaction history entry (default `false`).
|
|
6346
6494
|
* @returns `true` if the token was added, `false` if rejected as duplicate or tombstoned.
|
|
6347
6495
|
*/
|
|
6348
|
-
async addToken(token
|
|
6496
|
+
async addToken(token) {
|
|
6349
6497
|
this.ensureInitialized();
|
|
6350
6498
|
const incomingTokenId = extractTokenIdFromSdkData(token.sdkData);
|
|
6351
6499
|
const incomingStateHash = extractStateHashFromSdkData(token.sdkData);
|
|
@@ -6391,15 +6539,6 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6391
6539
|
}
|
|
6392
6540
|
this.tokens.set(token.id, token);
|
|
6393
6541
|
await this.archiveToken(token);
|
|
6394
|
-
if (!skipHistory && token.coinId && token.amount) {
|
|
6395
|
-
await this.addToHistory({
|
|
6396
|
-
type: "RECEIVED",
|
|
6397
|
-
amount: token.amount,
|
|
6398
|
-
coinId: token.coinId,
|
|
6399
|
-
symbol: token.symbol || "UNK",
|
|
6400
|
-
timestamp: token.createdAt || Date.now()
|
|
6401
|
-
});
|
|
6402
|
-
}
|
|
6403
6542
|
await this.save();
|
|
6404
6543
|
this.log(`Added token ${token.id}, total: ${this.tokens.size}`);
|
|
6405
6544
|
return true;
|
|
@@ -6426,7 +6565,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6426
6565
|
}
|
|
6427
6566
|
}
|
|
6428
6567
|
if (!found) {
|
|
6429
|
-
await this.addToken(token
|
|
6568
|
+
await this.addToken(token);
|
|
6430
6569
|
return;
|
|
6431
6570
|
}
|
|
6432
6571
|
await this.archiveToken(token);
|
|
@@ -6441,10 +6580,8 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6441
6580
|
* entry is created unless `skipHistory` is `true`.
|
|
6442
6581
|
*
|
|
6443
6582
|
* @param tokenId - Local UUID of the token to remove.
|
|
6444
|
-
* @param recipientNametag - Optional nametag of the transfer recipient (for history).
|
|
6445
|
-
* @param skipHistory - When `true`, skip creating a transaction history entry (default `false`).
|
|
6446
6583
|
*/
|
|
6447
|
-
async removeToken(tokenId
|
|
6584
|
+
async removeToken(tokenId) {
|
|
6448
6585
|
this.ensureInitialized();
|
|
6449
6586
|
const token = this.tokens.get(tokenId);
|
|
6450
6587
|
if (!token) return;
|
|
@@ -6462,16 +6599,6 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6462
6599
|
this.log(`Warning: Could not create tombstone for token ${tokenId.slice(0, 8)}... (missing tokenId or stateHash)`);
|
|
6463
6600
|
}
|
|
6464
6601
|
this.tokens.delete(tokenId);
|
|
6465
|
-
if (!skipHistory && token.coinId && token.amount) {
|
|
6466
|
-
await this.addToHistory({
|
|
6467
|
-
type: "SENT",
|
|
6468
|
-
amount: token.amount,
|
|
6469
|
-
coinId: token.coinId,
|
|
6470
|
-
symbol: token.symbol || "UNK",
|
|
6471
|
-
timestamp: Date.now(),
|
|
6472
|
-
recipientNametag
|
|
6473
|
-
});
|
|
6474
|
-
}
|
|
6475
6602
|
await this.save();
|
|
6476
6603
|
}
|
|
6477
6604
|
// ===========================================================================
|
|
@@ -6696,26 +6823,104 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6696
6823
|
* @returns Array of {@link TransactionHistoryEntry} objects in descending timestamp order.
|
|
6697
6824
|
*/
|
|
6698
6825
|
getHistory() {
|
|
6699
|
-
return [...this.
|
|
6826
|
+
return [...this._historyCache].sort((a, b) => b.timestamp - a.timestamp);
|
|
6827
|
+
}
|
|
6828
|
+
/**
|
|
6829
|
+
* Best-effort resolve sender's DIRECT address and nametag from their transport pubkey.
|
|
6830
|
+
* Returns empty object if transport doesn't support resolution or lookup fails.
|
|
6831
|
+
*/
|
|
6832
|
+
async resolveSenderInfo(senderTransportPubkey) {
|
|
6833
|
+
try {
|
|
6834
|
+
if (this.deps?.transport?.resolveTransportPubkeyInfo) {
|
|
6835
|
+
const peerInfo = await this.deps.transport.resolveTransportPubkeyInfo(senderTransportPubkey);
|
|
6836
|
+
if (peerInfo) {
|
|
6837
|
+
return {
|
|
6838
|
+
senderAddress: peerInfo.directAddress || void 0,
|
|
6839
|
+
senderNametag: peerInfo.nametag || void 0
|
|
6840
|
+
};
|
|
6841
|
+
}
|
|
6842
|
+
}
|
|
6843
|
+
} catch {
|
|
6844
|
+
}
|
|
6845
|
+
return {};
|
|
6700
6846
|
}
|
|
6701
6847
|
/**
|
|
6702
6848
|
* Append an entry to the transaction history.
|
|
6703
6849
|
*
|
|
6704
|
-
* A unique `id`
|
|
6850
|
+
* A unique `id` and `dedupKey` are auto-generated. The entry is persisted to
|
|
6851
|
+
* the local token storage provider's `history` store (IndexedDB / file).
|
|
6852
|
+
* Duplicate entries with the same `dedupKey` are silently ignored (upsert).
|
|
6705
6853
|
*
|
|
6706
|
-
* @param entry - History entry fields (without `id`).
|
|
6854
|
+
* @param entry - History entry fields (without `id` and `dedupKey`).
|
|
6707
6855
|
*/
|
|
6708
6856
|
async addToHistory(entry) {
|
|
6709
6857
|
this.ensureInitialized();
|
|
6858
|
+
const dedupKey = computeHistoryDedupKey(entry.type, entry.tokenId, entry.transferId);
|
|
6710
6859
|
const historyEntry = {
|
|
6711
6860
|
id: crypto.randomUUID(),
|
|
6861
|
+
dedupKey,
|
|
6712
6862
|
...entry
|
|
6713
6863
|
};
|
|
6714
|
-
this.
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
);
|
|
6864
|
+
const provider = this.getLocalTokenStorageProvider();
|
|
6865
|
+
if (provider?.addHistoryEntry) {
|
|
6866
|
+
await provider.addHistoryEntry(historyEntry);
|
|
6867
|
+
}
|
|
6868
|
+
const existingIdx = this._historyCache.findIndex((e) => e.dedupKey === dedupKey);
|
|
6869
|
+
if (existingIdx >= 0) {
|
|
6870
|
+
this._historyCache[existingIdx] = historyEntry;
|
|
6871
|
+
} else {
|
|
6872
|
+
this._historyCache.push(historyEntry);
|
|
6873
|
+
}
|
|
6874
|
+
this.deps.emitEvent("history:updated", historyEntry);
|
|
6875
|
+
}
|
|
6876
|
+
/**
|
|
6877
|
+
* Load history from the local token storage provider into the in-memory cache.
|
|
6878
|
+
* Also performs one-time migration from legacy KV storage.
|
|
6879
|
+
*/
|
|
6880
|
+
async loadHistory() {
|
|
6881
|
+
const provider = this.getLocalTokenStorageProvider();
|
|
6882
|
+
if (provider?.getHistoryEntries) {
|
|
6883
|
+
this._historyCache = await provider.getHistoryEntries();
|
|
6884
|
+
const legacyData = await this.deps.storage.get(STORAGE_KEYS_ADDRESS.TRANSACTION_HISTORY);
|
|
6885
|
+
if (legacyData) {
|
|
6886
|
+
try {
|
|
6887
|
+
const legacyEntries = JSON.parse(legacyData);
|
|
6888
|
+
const records = legacyEntries.map((e) => ({
|
|
6889
|
+
...e,
|
|
6890
|
+
dedupKey: e.dedupKey || computeHistoryDedupKey(e.type, e.tokenId, e.transferId)
|
|
6891
|
+
}));
|
|
6892
|
+
const imported = await provider.importHistoryEntries?.(records) ?? 0;
|
|
6893
|
+
if (imported > 0) {
|
|
6894
|
+
this._historyCache = await provider.getHistoryEntries();
|
|
6895
|
+
this.log(`Migrated ${imported} history entries from KV to history store`);
|
|
6896
|
+
}
|
|
6897
|
+
await this.deps.storage.remove(STORAGE_KEYS_ADDRESS.TRANSACTION_HISTORY);
|
|
6898
|
+
} catch {
|
|
6899
|
+
}
|
|
6900
|
+
}
|
|
6901
|
+
} else {
|
|
6902
|
+
const historyData = await this.deps.storage.get(STORAGE_KEYS_ADDRESS.TRANSACTION_HISTORY);
|
|
6903
|
+
if (historyData) {
|
|
6904
|
+
try {
|
|
6905
|
+
this._historyCache = JSON.parse(historyData);
|
|
6906
|
+
} catch {
|
|
6907
|
+
this._historyCache = [];
|
|
6908
|
+
}
|
|
6909
|
+
}
|
|
6910
|
+
}
|
|
6911
|
+
}
|
|
6912
|
+
/**
|
|
6913
|
+
* Get the first local token storage provider (for history operations).
|
|
6914
|
+
*/
|
|
6915
|
+
getLocalTokenStorageProvider() {
|
|
6916
|
+
const providers = this.getTokenStorageProviders();
|
|
6917
|
+
for (const [, provider] of providers) {
|
|
6918
|
+
if (provider.type === "local") return provider;
|
|
6919
|
+
}
|
|
6920
|
+
for (const [, provider] of providers) {
|
|
6921
|
+
return provider;
|
|
6922
|
+
}
|
|
6923
|
+
return null;
|
|
6719
6924
|
}
|
|
6720
6925
|
// ===========================================================================
|
|
6721
6926
|
// Public API - Nametag
|
|
@@ -6922,7 +7127,32 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6922
7127
|
try {
|
|
6923
7128
|
const result = await provider.sync(localData);
|
|
6924
7129
|
if (result.success && result.merged) {
|
|
7130
|
+
const savedTokens = new Map(this.tokens);
|
|
6925
7131
|
this.loadFromStorageData(result.merged);
|
|
7132
|
+
let restoredCount = 0;
|
|
7133
|
+
for (const [tokenId, token] of savedTokens) {
|
|
7134
|
+
if (this.tokens.has(tokenId)) continue;
|
|
7135
|
+
const sdkTokenId = extractTokenIdFromSdkData(token.sdkData);
|
|
7136
|
+
const stateHash = extractStateHashFromSdkData(token.sdkData);
|
|
7137
|
+
if (sdkTokenId && stateHash && this.isStateTombstoned(sdkTokenId, stateHash)) {
|
|
7138
|
+
continue;
|
|
7139
|
+
}
|
|
7140
|
+
if (sdkTokenId) {
|
|
7141
|
+
let hasEquivalent = false;
|
|
7142
|
+
for (const existing of this.tokens.values()) {
|
|
7143
|
+
if (extractTokenIdFromSdkData(existing.sdkData) === sdkTokenId) {
|
|
7144
|
+
hasEquivalent = true;
|
|
7145
|
+
break;
|
|
7146
|
+
}
|
|
7147
|
+
}
|
|
7148
|
+
if (hasEquivalent) continue;
|
|
7149
|
+
}
|
|
7150
|
+
this.tokens.set(tokenId, token);
|
|
7151
|
+
restoredCount++;
|
|
7152
|
+
}
|
|
7153
|
+
if (restoredCount > 0) {
|
|
7154
|
+
console.log(`[Payments] Sync: restored ${restoredCount} token(s) lost by loadFromStorageData`);
|
|
7155
|
+
}
|
|
6926
7156
|
if (this.nametags.length === 0 && savedNametags.length > 0) {
|
|
6927
7157
|
this.nametags = savedNametags;
|
|
6928
7158
|
}
|
|
@@ -7256,16 +7486,30 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
7256
7486
|
return;
|
|
7257
7487
|
}
|
|
7258
7488
|
this.tokens.set(token.id, token);
|
|
7489
|
+
console.log(`[Payments][DEBUG] NOSTR-FIRST: saving token id=${token.id.slice(0, 16)} status=${token.status} sdkData.length=${token.sdkData?.length}`);
|
|
7259
7490
|
await this.save();
|
|
7260
|
-
|
|
7491
|
+
console.log(`[Payments][DEBUG] NOSTR-FIRST: save() completed, tokens.size=${this.tokens.size}`);
|
|
7492
|
+
const senderInfo = await this.resolveSenderInfo(transfer.senderTransportPubkey);
|
|
7261
7493
|
const incomingTransfer = {
|
|
7262
7494
|
id: transfer.id,
|
|
7263
7495
|
senderPubkey: transfer.senderTransportPubkey,
|
|
7496
|
+
senderNametag: senderInfo.senderNametag,
|
|
7264
7497
|
tokens: [token],
|
|
7265
7498
|
memo: payload.memo,
|
|
7266
7499
|
receivedAt: transfer.timestamp
|
|
7267
7500
|
};
|
|
7268
7501
|
this.deps.emitEvent("transfer:incoming", incomingTransfer);
|
|
7502
|
+
await this.addToHistory({
|
|
7503
|
+
type: "RECEIVED",
|
|
7504
|
+
amount: token.amount,
|
|
7505
|
+
coinId: token.coinId,
|
|
7506
|
+
symbol: token.symbol,
|
|
7507
|
+
timestamp: Date.now(),
|
|
7508
|
+
senderPubkey: transfer.senderTransportPubkey,
|
|
7509
|
+
...senderInfo,
|
|
7510
|
+
memo: payload.memo,
|
|
7511
|
+
tokenId: nostrTokenId || token.id
|
|
7512
|
+
});
|
|
7269
7513
|
try {
|
|
7270
7514
|
const commitment = await import_TransferCommitment4.TransferCommitment.fromJSON(commitmentInput);
|
|
7271
7515
|
const requestIdBytes = commitment.requestId;
|
|
@@ -7283,7 +7527,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
7283
7527
|
attemptCount: 0,
|
|
7284
7528
|
lastAttemptAt: 0,
|
|
7285
7529
|
onProofReceived: async (tokenId) => {
|
|
7286
|
-
await this.finalizeReceivedToken(tokenId, sourceTokenInput, commitmentInput
|
|
7530
|
+
await this.finalizeReceivedToken(tokenId, sourceTokenInput, commitmentInput);
|
|
7287
7531
|
}
|
|
7288
7532
|
});
|
|
7289
7533
|
} catch (err) {
|
|
@@ -7342,7 +7586,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
7342
7586
|
/**
|
|
7343
7587
|
* Finalize a received token after proof is available
|
|
7344
7588
|
*/
|
|
7345
|
-
async finalizeReceivedToken(tokenId, sourceTokenInput, commitmentInput
|
|
7589
|
+
async finalizeReceivedToken(tokenId, sourceTokenInput, commitmentInput) {
|
|
7346
7590
|
try {
|
|
7347
7591
|
const token = this.tokens.get(tokenId);
|
|
7348
7592
|
if (!token) {
|
|
@@ -7390,14 +7634,6 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
7390
7634
|
tokens: [finalizedToken],
|
|
7391
7635
|
tokenTransfers: []
|
|
7392
7636
|
});
|
|
7393
|
-
await this.addToHistory({
|
|
7394
|
-
type: "RECEIVED",
|
|
7395
|
-
amount: finalizedToken.amount,
|
|
7396
|
-
coinId: finalizedToken.coinId,
|
|
7397
|
-
symbol: finalizedToken.symbol,
|
|
7398
|
-
timestamp: Date.now(),
|
|
7399
|
-
senderPubkey
|
|
7400
|
-
});
|
|
7401
7637
|
} catch (error) {
|
|
7402
7638
|
console.error("[Payments] Failed to finalize received token:", error);
|
|
7403
7639
|
const token = this.tokens.get(tokenId);
|
|
@@ -7409,8 +7645,12 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
7409
7645
|
}
|
|
7410
7646
|
}
|
|
7411
7647
|
async handleIncomingTransfer(transfer) {
|
|
7648
|
+
if (!this.loaded && this.loadedPromise) {
|
|
7649
|
+
await this.loadedPromise;
|
|
7650
|
+
}
|
|
7412
7651
|
try {
|
|
7413
7652
|
const payload = transfer.payload;
|
|
7653
|
+
console.log("[Payments][DEBUG] handleIncomingTransfer: keys=", Object.keys(payload).join(","));
|
|
7414
7654
|
let instantBundle = null;
|
|
7415
7655
|
if (isInstantSplitBundle(payload)) {
|
|
7416
7656
|
instantBundle = payload;
|
|
@@ -7428,7 +7668,8 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
7428
7668
|
try {
|
|
7429
7669
|
const result = await this.processInstantSplitBundle(
|
|
7430
7670
|
instantBundle,
|
|
7431
|
-
transfer.senderTransportPubkey
|
|
7671
|
+
transfer.senderTransportPubkey,
|
|
7672
|
+
payload.memo
|
|
7432
7673
|
);
|
|
7433
7674
|
if (result.success) {
|
|
7434
7675
|
this.log("INSTANT_SPLIT processed successfully");
|
|
@@ -7441,7 +7682,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
7441
7682
|
return;
|
|
7442
7683
|
}
|
|
7443
7684
|
if (payload.sourceToken && payload.commitmentData && !payload.transferTx) {
|
|
7444
|
-
|
|
7685
|
+
console.log("[Payments][DEBUG] >>> NOSTR-FIRST commitment-only transfer detected");
|
|
7445
7686
|
await this.handleCommitmentOnlyTransfer(transfer, payload);
|
|
7446
7687
|
return;
|
|
7447
7688
|
}
|
|
@@ -7546,10 +7787,26 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
7546
7787
|
updatedAt: Date.now(),
|
|
7547
7788
|
sdkData: typeof tokenData === "string" ? tokenData : JSON.stringify(tokenData)
|
|
7548
7789
|
};
|
|
7549
|
-
await this.addToken(token);
|
|
7790
|
+
const added = await this.addToken(token);
|
|
7791
|
+
const senderInfo = await this.resolveSenderInfo(transfer.senderTransportPubkey);
|
|
7792
|
+
if (added) {
|
|
7793
|
+
const incomingTokenId = extractTokenIdFromSdkData(token.sdkData);
|
|
7794
|
+
await this.addToHistory({
|
|
7795
|
+
type: "RECEIVED",
|
|
7796
|
+
amount: token.amount,
|
|
7797
|
+
coinId: token.coinId,
|
|
7798
|
+
symbol: token.symbol,
|
|
7799
|
+
timestamp: Date.now(),
|
|
7800
|
+
senderPubkey: transfer.senderTransportPubkey,
|
|
7801
|
+
...senderInfo,
|
|
7802
|
+
memo: payload.memo,
|
|
7803
|
+
tokenId: incomingTokenId || token.id
|
|
7804
|
+
});
|
|
7805
|
+
}
|
|
7550
7806
|
const incomingTransfer = {
|
|
7551
7807
|
id: transfer.id,
|
|
7552
7808
|
senderPubkey: transfer.senderTransportPubkey,
|
|
7809
|
+
senderNametag: senderInfo.senderNametag,
|
|
7553
7810
|
tokens: [token],
|
|
7554
7811
|
memo: payload.memo,
|
|
7555
7812
|
receivedAt: transfer.timestamp
|
|
@@ -7588,17 +7845,24 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
7588
7845
|
// ===========================================================================
|
|
7589
7846
|
async save() {
|
|
7590
7847
|
const providers = this.getTokenStorageProviders();
|
|
7591
|
-
|
|
7592
|
-
|
|
7593
|
-
return
|
|
7594
|
-
}
|
|
7595
|
-
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
|
|
7600
|
-
|
|
7848
|
+
const tokenStats = Array.from(this.tokens.values()).map((t) => {
|
|
7849
|
+
const txf = tokenToTxf(t);
|
|
7850
|
+
return `${t.id.slice(0, 12)}(${t.status},txf=${!!txf})`;
|
|
7851
|
+
});
|
|
7852
|
+
console.log(`[Payments][DEBUG] save(): providers=${providers.size}, tokens=[${tokenStats.join(", ")}]`);
|
|
7853
|
+
if (providers.size > 0) {
|
|
7854
|
+
const data = await this.createStorageData();
|
|
7855
|
+
const dataKeys = Object.keys(data).filter((k) => k.startsWith("token-"));
|
|
7856
|
+
console.log(`[Payments][DEBUG] save(): TXF keys=${dataKeys.length} (${dataKeys.join(", ")})`);
|
|
7857
|
+
for (const [id, provider] of providers) {
|
|
7858
|
+
try {
|
|
7859
|
+
await provider.save(data);
|
|
7860
|
+
} catch (err) {
|
|
7861
|
+
console.error(`[Payments] Failed to save to provider ${id}:`, err);
|
|
7862
|
+
}
|
|
7601
7863
|
}
|
|
7864
|
+
} else {
|
|
7865
|
+
console.log("[Payments][DEBUG] save(): No token storage providers - TXF not persisted");
|
|
7602
7866
|
}
|
|
7603
7867
|
await this.savePendingV5Tokens();
|
|
7604
7868
|
}
|
|
@@ -7634,6 +7898,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
7634
7898
|
}
|
|
7635
7899
|
loadFromStorageData(data) {
|
|
7636
7900
|
const parsed = parseTxfStorageData(data);
|
|
7901
|
+
console.log(`[Payments][DEBUG] loadFromStorageData: parsed ${parsed.tokens.length} tokens, ${parsed.tombstones.length} tombstones, errors=[${parsed.validationErrors.join("; ")}]`);
|
|
7637
7902
|
this.tombstones = parsed.tombstones;
|
|
7638
7903
|
this.tokens.clear();
|
|
7639
7904
|
for (const token of parsed.tokens) {
|