@unicitylabs/sphere-sdk 0.2.2 → 0.2.5
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/README.md +73 -79
- package/dist/core/index.cjs +1220 -275
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +422 -221
- package/dist/core/index.d.ts +422 -221
- package/dist/core/index.js +1219 -275
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/index.cjs +2077 -14
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js +2077 -14
- package/dist/impl/browser/index.js.map +1 -1
- package/dist/impl/browser/ipfs.cjs +1877 -513
- package/dist/impl/browser/ipfs.cjs.map +1 -1
- package/dist/impl/browser/ipfs.js +1877 -513
- package/dist/impl/browser/ipfs.js.map +1 -1
- package/dist/impl/nodejs/index.cjs +2222 -172
- package/dist/impl/nodejs/index.cjs.map +1 -1
- package/dist/impl/nodejs/index.d.cts +84 -3
- package/dist/impl/nodejs/index.d.ts +84 -3
- package/dist/impl/nodejs/index.js +2222 -172
- package/dist/impl/nodejs/index.js.map +1 -1
- package/dist/index.cjs +1231 -265
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +440 -67
- package/dist/index.d.ts +440 -67
- package/dist/index.js +1231 -265
- package/dist/index.js.map +1 -1
- package/package.json +25 -5
package/dist/index.cjs
CHANGED
|
@@ -498,6 +498,7 @@ __export(index_exports, {
|
|
|
498
498
|
TokenRegistry: () => TokenRegistry,
|
|
499
499
|
TokenValidator: () => TokenValidator,
|
|
500
500
|
archivedKeyFromTokenId: () => archivedKeyFromTokenId,
|
|
501
|
+
areSameNametag: () => import_nostr_js_sdk3.areSameNametag,
|
|
501
502
|
base58Decode: () => base58Decode,
|
|
502
503
|
base58Encode: () => base58Encode2,
|
|
503
504
|
buildTxfStorageData: () => buildTxfStorageData,
|
|
@@ -545,6 +546,7 @@ __export(index_exports, {
|
|
|
545
546
|
hasUncommittedTransactions: () => hasUncommittedTransactions,
|
|
546
547
|
hasValidTxfData: () => hasValidTxfData,
|
|
547
548
|
hash160: () => hash160,
|
|
549
|
+
hashNametag: () => import_nostr_js_sdk3.hashNametag,
|
|
548
550
|
hexToBytes: () => hexToBytes,
|
|
549
551
|
identityFromMnemonicSync: () => identityFromMnemonicSync,
|
|
550
552
|
initSphere: () => initSphere,
|
|
@@ -556,10 +558,12 @@ __export(index_exports, {
|
|
|
556
558
|
isKnownToken: () => isKnownToken,
|
|
557
559
|
isPaymentSessionTerminal: () => isPaymentSessionTerminal,
|
|
558
560
|
isPaymentSessionTimedOut: () => isPaymentSessionTimedOut,
|
|
561
|
+
isPhoneNumber: () => import_nostr_js_sdk3.isPhoneNumber,
|
|
559
562
|
isSQLiteDatabase: () => isSQLiteDatabase,
|
|
560
563
|
isTextWalletEncrypted: () => isTextWalletEncrypted,
|
|
561
564
|
isTokenKey: () => isTokenKey,
|
|
562
565
|
isValidBech32: () => isValidBech32,
|
|
566
|
+
isValidNametag: () => isValidNametag,
|
|
563
567
|
isValidPrivateKey: () => isValidPrivateKey,
|
|
564
568
|
isValidTokenId: () => isValidTokenId,
|
|
565
569
|
isWalletDatEncrypted: () => isWalletDatEncrypted,
|
|
@@ -567,6 +571,7 @@ __export(index_exports, {
|
|
|
567
571
|
keyFromTokenId: () => keyFromTokenId,
|
|
568
572
|
loadSphere: () => loadSphere,
|
|
569
573
|
mnemonicToSeedSync: () => mnemonicToSeedSync2,
|
|
574
|
+
normalizeNametag: () => import_nostr_js_sdk3.normalizeNametag,
|
|
570
575
|
normalizeSdkTokenToStorage: () => normalizeSdkTokenToStorage,
|
|
571
576
|
objectToTxf: () => objectToTxf,
|
|
572
577
|
parseAndDecryptWalletDat: () => parseAndDecryptWalletDat,
|
|
@@ -1754,7 +1759,7 @@ var L1PaymentsModule = class {
|
|
|
1754
1759
|
_transport;
|
|
1755
1760
|
constructor(config) {
|
|
1756
1761
|
this._config = {
|
|
1757
|
-
electrumUrl: config?.electrumUrl ?? "wss://fulcrum.
|
|
1762
|
+
electrumUrl: config?.electrumUrl ?? "wss://fulcrum.unicity.network:50004",
|
|
1758
1763
|
network: config?.network ?? "mainnet",
|
|
1759
1764
|
defaultFeeRate: config?.defaultFeeRate ?? 10,
|
|
1760
1765
|
enableVesting: config?.enableVesting ?? true
|
|
@@ -1786,10 +1791,17 @@ var L1PaymentsModule = class {
|
|
|
1786
1791
|
});
|
|
1787
1792
|
}
|
|
1788
1793
|
}
|
|
1789
|
-
|
|
1794
|
+
this._initialized = true;
|
|
1795
|
+
}
|
|
1796
|
+
/**
|
|
1797
|
+
* Ensure the Fulcrum WebSocket is connected. Called lazily before any
|
|
1798
|
+
* operation that needs the network. If the singleton is already connected
|
|
1799
|
+
* (e.g. by the address scanner), this is a no-op.
|
|
1800
|
+
*/
|
|
1801
|
+
async ensureConnected() {
|
|
1802
|
+
if (!isWebSocketConnected() && this._config.electrumUrl) {
|
|
1790
1803
|
await connect(this._config.electrumUrl);
|
|
1791
1804
|
}
|
|
1792
|
-
this._initialized = true;
|
|
1793
1805
|
}
|
|
1794
1806
|
destroy() {
|
|
1795
1807
|
if (isWebSocketConnected()) {
|
|
@@ -1847,6 +1859,7 @@ var L1PaymentsModule = class {
|
|
|
1847
1859
|
}
|
|
1848
1860
|
async send(request) {
|
|
1849
1861
|
this.ensureInitialized();
|
|
1862
|
+
await this.ensureConnected();
|
|
1850
1863
|
if (!this._wallet || !this._identity) {
|
|
1851
1864
|
return { success: false, error: "No wallet available" };
|
|
1852
1865
|
}
|
|
@@ -1881,6 +1894,7 @@ var L1PaymentsModule = class {
|
|
|
1881
1894
|
}
|
|
1882
1895
|
async getBalance() {
|
|
1883
1896
|
this.ensureInitialized();
|
|
1897
|
+
await this.ensureConnected();
|
|
1884
1898
|
const addresses = this._getWatchedAddresses();
|
|
1885
1899
|
let totalAlpha = 0;
|
|
1886
1900
|
let vestedSats = BigInt(0);
|
|
@@ -1912,6 +1926,7 @@ var L1PaymentsModule = class {
|
|
|
1912
1926
|
}
|
|
1913
1927
|
async getUtxos() {
|
|
1914
1928
|
this.ensureInitialized();
|
|
1929
|
+
await this.ensureConnected();
|
|
1915
1930
|
const result = [];
|
|
1916
1931
|
const currentHeight = await getCurrentBlockHeight();
|
|
1917
1932
|
const allUtxos = await this._getAllUtxos();
|
|
@@ -1947,42 +1962,73 @@ var L1PaymentsModule = class {
|
|
|
1947
1962
|
return result;
|
|
1948
1963
|
}
|
|
1949
1964
|
async getHistory(limit) {
|
|
1965
|
+
await this.ensureConnected();
|
|
1950
1966
|
this.ensureInitialized();
|
|
1951
1967
|
const addresses = this._getWatchedAddresses();
|
|
1952
1968
|
const transactions = [];
|
|
1953
1969
|
const seenTxids = /* @__PURE__ */ new Set();
|
|
1954
1970
|
const currentHeight = await getCurrentBlockHeight();
|
|
1971
|
+
const txCache = /* @__PURE__ */ new Map();
|
|
1972
|
+
const fetchTx = async (txid) => {
|
|
1973
|
+
if (txCache.has(txid)) return txCache.get(txid);
|
|
1974
|
+
const detail = await getTransaction(txid);
|
|
1975
|
+
txCache.set(txid, detail);
|
|
1976
|
+
return detail;
|
|
1977
|
+
};
|
|
1978
|
+
const addressSet = new Set(addresses.map((a) => a.toLowerCase()));
|
|
1955
1979
|
for (const address of addresses) {
|
|
1956
1980
|
const history = await getTransactionHistory(address);
|
|
1957
1981
|
for (const item of history) {
|
|
1958
1982
|
if (seenTxids.has(item.tx_hash)) continue;
|
|
1959
1983
|
seenTxids.add(item.tx_hash);
|
|
1960
|
-
const tx = await
|
|
1984
|
+
const tx = await fetchTx(item.tx_hash);
|
|
1961
1985
|
if (!tx) continue;
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1986
|
+
let isSend = false;
|
|
1987
|
+
for (const vin of tx.vin ?? []) {
|
|
1988
|
+
if (!vin.txid) continue;
|
|
1989
|
+
const prevTx = await fetchTx(vin.txid);
|
|
1990
|
+
if (prevTx?.vout?.[vin.vout]) {
|
|
1991
|
+
const prevOut = prevTx.vout[vin.vout];
|
|
1992
|
+
const prevAddrs = [
|
|
1993
|
+
...prevOut.scriptPubKey?.addresses ?? [],
|
|
1994
|
+
...prevOut.scriptPubKey?.address ? [prevOut.scriptPubKey.address] : []
|
|
1995
|
+
];
|
|
1996
|
+
if (prevAddrs.some((a) => addressSet.has(a.toLowerCase()))) {
|
|
1997
|
+
isSend = true;
|
|
1998
|
+
break;
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
let amountToUs = 0;
|
|
2003
|
+
let amountToOthers = 0;
|
|
1966
2004
|
let txAddress = address;
|
|
2005
|
+
let externalAddress = "";
|
|
1967
2006
|
if (tx.vout) {
|
|
1968
2007
|
for (const vout of tx.vout) {
|
|
1969
|
-
const voutAddresses =
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
const
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
2008
|
+
const voutAddresses = [
|
|
2009
|
+
...vout.scriptPubKey?.addresses ?? [],
|
|
2010
|
+
...vout.scriptPubKey?.address ? [vout.scriptPubKey.address] : []
|
|
2011
|
+
];
|
|
2012
|
+
const isOurs = voutAddresses.some((a) => addressSet.has(a.toLowerCase()));
|
|
2013
|
+
const valueSats = Math.floor((vout.value ?? 0) * 1e8);
|
|
2014
|
+
if (isOurs) {
|
|
2015
|
+
amountToUs += valueSats;
|
|
2016
|
+
if (!txAddress) txAddress = voutAddresses[0];
|
|
2017
|
+
} else {
|
|
2018
|
+
amountToOthers += valueSats;
|
|
2019
|
+
if (!externalAddress && voutAddresses.length > 0) {
|
|
2020
|
+
externalAddress = voutAddresses[0];
|
|
2021
|
+
}
|
|
1978
2022
|
}
|
|
1979
2023
|
}
|
|
1980
2024
|
}
|
|
2025
|
+
const amount = isSend ? amountToOthers.toString() : amountToUs.toString();
|
|
2026
|
+
const displayAddress = isSend ? externalAddress || txAddress : txAddress;
|
|
1981
2027
|
transactions.push({
|
|
1982
2028
|
txid: item.tx_hash,
|
|
1983
2029
|
type: isSend ? "send" : "receive",
|
|
1984
2030
|
amount,
|
|
1985
|
-
address:
|
|
2031
|
+
address: displayAddress,
|
|
1986
2032
|
confirmations: item.height > 0 ? currentHeight - item.height : 0,
|
|
1987
2033
|
timestamp: tx.time ? tx.time * 1e3 : Date.now(),
|
|
1988
2034
|
blockHeight: item.height > 0 ? item.height : void 0
|
|
@@ -1994,6 +2040,7 @@ var L1PaymentsModule = class {
|
|
|
1994
2040
|
}
|
|
1995
2041
|
async getTransaction(txid) {
|
|
1996
2042
|
this.ensureInitialized();
|
|
2043
|
+
await this.ensureConnected();
|
|
1997
2044
|
const tx = await getTransaction(txid);
|
|
1998
2045
|
if (!tx) return null;
|
|
1999
2046
|
const addresses = this._getWatchedAddresses();
|
|
@@ -2029,6 +2076,7 @@ var L1PaymentsModule = class {
|
|
|
2029
2076
|
}
|
|
2030
2077
|
async estimateFee(to, amount) {
|
|
2031
2078
|
this.ensureInitialized();
|
|
2079
|
+
await this.ensureConnected();
|
|
2032
2080
|
if (!this._wallet) {
|
|
2033
2081
|
return { fee: "0", feeRate: this._config.defaultFeeRate ?? 10 };
|
|
2034
2082
|
}
|
|
@@ -2371,6 +2419,7 @@ var import_MintCommitment = require("@unicitylabs/state-transition-sdk/lib/trans
|
|
|
2371
2419
|
var import_HashAlgorithm2 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
|
|
2372
2420
|
var import_UnmaskedPredicate2 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate");
|
|
2373
2421
|
var import_InclusionProofUtils2 = require("@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils");
|
|
2422
|
+
var import_nostr_js_sdk = require("@unicitylabs/nostr-js-sdk");
|
|
2374
2423
|
var UNICITY_TOKEN_TYPE_HEX = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
|
|
2375
2424
|
var NametagMinter = class {
|
|
2376
2425
|
client;
|
|
@@ -2395,7 +2444,8 @@ var NametagMinter = class {
|
|
|
2395
2444
|
*/
|
|
2396
2445
|
async isNametagAvailable(nametag) {
|
|
2397
2446
|
try {
|
|
2398
|
-
const
|
|
2447
|
+
const stripped = nametag.startsWith("@") ? nametag.slice(1) : nametag;
|
|
2448
|
+
const cleanNametag = (0, import_nostr_js_sdk.normalizeNametag)(stripped);
|
|
2399
2449
|
const nametagTokenId = await import_TokenId2.TokenId.fromNameTag(cleanNametag);
|
|
2400
2450
|
const isMinted = await this.client.isMinted(this.trustBase, nametagTokenId);
|
|
2401
2451
|
return !isMinted;
|
|
@@ -2412,7 +2462,8 @@ var NametagMinter = class {
|
|
|
2412
2462
|
* @returns MintNametagResult with token if successful
|
|
2413
2463
|
*/
|
|
2414
2464
|
async mintNametag(nametag, ownerAddress) {
|
|
2415
|
-
const
|
|
2465
|
+
const stripped = nametag.startsWith("@") ? nametag.slice(1) : nametag;
|
|
2466
|
+
const cleanNametag = (0, import_nostr_js_sdk.normalizeNametag)(stripped);
|
|
2416
2467
|
this.log(`Starting mint for nametag: ${cleanNametag}`);
|
|
2417
2468
|
try {
|
|
2418
2469
|
const nametagTokenId = await import_TokenId2.TokenId.fromNameTag(cleanNametag);
|
|
@@ -2552,7 +2603,9 @@ var STORAGE_KEYS_GLOBAL = {
|
|
|
2552
2603
|
/** Nametag cache per address (separate from tracked addresses registry) */
|
|
2553
2604
|
ADDRESS_NAMETAGS: "address_nametags",
|
|
2554
2605
|
/** Active addresses registry (JSON: TrackedAddressesStorage) */
|
|
2555
|
-
TRACKED_ADDRESSES: "tracked_addresses"
|
|
2606
|
+
TRACKED_ADDRESSES: "tracked_addresses",
|
|
2607
|
+
/** Last processed Nostr wallet event timestamp (unix seconds), keyed per pubkey */
|
|
2608
|
+
LAST_WALLET_EVENT_TS: "last_wallet_event_ts"
|
|
2556
2609
|
};
|
|
2557
2610
|
var STORAGE_KEYS_ADDRESS = {
|
|
2558
2611
|
/** Pending transfers for this address */
|
|
@@ -2564,7 +2617,9 @@ var STORAGE_KEYS_ADDRESS = {
|
|
|
2564
2617
|
/** Messages for this address */
|
|
2565
2618
|
MESSAGES: "messages",
|
|
2566
2619
|
/** Transaction history for this address */
|
|
2567
|
-
TRANSACTION_HISTORY: "transaction_history"
|
|
2620
|
+
TRANSACTION_HISTORY: "transaction_history",
|
|
2621
|
+
/** Pending V5 finalization tokens (unconfirmed instant split tokens) */
|
|
2622
|
+
PENDING_V5_TOKENS: "pending_v5_tokens"
|
|
2568
2623
|
};
|
|
2569
2624
|
var STORAGE_KEYS = {
|
|
2570
2625
|
...STORAGE_KEYS_GLOBAL,
|
|
@@ -2979,6 +3034,18 @@ function parseTxfStorageData(data) {
|
|
|
2979
3034
|
result.validationErrors.push(`Forked token ${parsed.tokenId}: invalid structure`);
|
|
2980
3035
|
}
|
|
2981
3036
|
}
|
|
3037
|
+
} else if (key.startsWith("token-")) {
|
|
3038
|
+
try {
|
|
3039
|
+
const entry = storageData[key];
|
|
3040
|
+
const txfToken = entry?.token;
|
|
3041
|
+
if (txfToken?.genesis?.data?.tokenId) {
|
|
3042
|
+
const tokenId = txfToken.genesis.data.tokenId;
|
|
3043
|
+
const token = txfToToken(tokenId, txfToken);
|
|
3044
|
+
result.tokens.push(token);
|
|
3045
|
+
}
|
|
3046
|
+
} catch (err) {
|
|
3047
|
+
result.validationErrors.push(`Token ${key}: ${err}`);
|
|
3048
|
+
}
|
|
2982
3049
|
}
|
|
2983
3050
|
}
|
|
2984
3051
|
return result;
|
|
@@ -3554,8 +3621,9 @@ var InstantSplitExecutor = class {
|
|
|
3554
3621
|
const criticalPathDuration = performance.now() - startTime;
|
|
3555
3622
|
console.log(`[InstantSplit] V5 complete in ${criticalPathDuration.toFixed(0)}ms`);
|
|
3556
3623
|
options?.onNostrDelivered?.(nostrEventId);
|
|
3624
|
+
let backgroundPromise;
|
|
3557
3625
|
if (!options?.skipBackground) {
|
|
3558
|
-
this.submitBackgroundV5(senderMintCommitment, recipientMintCommitment, transferCommitment, {
|
|
3626
|
+
backgroundPromise = this.submitBackgroundV5(senderMintCommitment, recipientMintCommitment, transferCommitment, {
|
|
3559
3627
|
signingService: this.signingService,
|
|
3560
3628
|
tokenType: tokenToSplit.type,
|
|
3561
3629
|
coinId,
|
|
@@ -3571,7 +3639,8 @@ var InstantSplitExecutor = class {
|
|
|
3571
3639
|
nostrEventId,
|
|
3572
3640
|
splitGroupId,
|
|
3573
3641
|
criticalPathDurationMs: criticalPathDuration,
|
|
3574
|
-
backgroundStarted: !options?.skipBackground
|
|
3642
|
+
backgroundStarted: !options?.skipBackground,
|
|
3643
|
+
backgroundPromise
|
|
3575
3644
|
};
|
|
3576
3645
|
} catch (error) {
|
|
3577
3646
|
const duration = performance.now() - startTime;
|
|
@@ -3633,7 +3702,7 @@ var InstantSplitExecutor = class {
|
|
|
3633
3702
|
this.client.submitMintCommitment(recipientMintCommitment).then((res) => ({ type: "recipientMint", status: res.status })).catch((err) => ({ type: "recipientMint", status: "ERROR", error: err })),
|
|
3634
3703
|
this.client.submitTransferCommitment(transferCommitment).then((res) => ({ type: "transfer", status: res.status })).catch((err) => ({ type: "transfer", status: "ERROR", error: err }))
|
|
3635
3704
|
]);
|
|
3636
|
-
submissions.then(async (results) => {
|
|
3705
|
+
return submissions.then(async (results) => {
|
|
3637
3706
|
const submitDuration = performance.now() - startTime;
|
|
3638
3707
|
console.log(`[InstantSplit] Background: Submissions complete in ${submitDuration.toFixed(0)}ms`);
|
|
3639
3708
|
context.onProgress?.({
|
|
@@ -4098,6 +4167,11 @@ var import_AddressScheme = require("@unicitylabs/state-transition-sdk/lib/addres
|
|
|
4098
4167
|
var import_UnmaskedPredicate5 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate");
|
|
4099
4168
|
var import_TokenState5 = require("@unicitylabs/state-transition-sdk/lib/token/TokenState");
|
|
4100
4169
|
var import_HashAlgorithm5 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
|
|
4170
|
+
var import_TokenType3 = require("@unicitylabs/state-transition-sdk/lib/token/TokenType");
|
|
4171
|
+
var import_MintCommitment3 = require("@unicitylabs/state-transition-sdk/lib/transaction/MintCommitment");
|
|
4172
|
+
var import_MintTransactionData3 = require("@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData");
|
|
4173
|
+
var import_InclusionProofUtils5 = require("@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils");
|
|
4174
|
+
var import_InclusionProof = require("@unicitylabs/state-transition-sdk/lib/transaction/InclusionProof");
|
|
4101
4175
|
function enrichWithRegistry(info) {
|
|
4102
4176
|
const registry = TokenRegistry.getInstance();
|
|
4103
4177
|
const def = registry.getDefinition(info.coinId);
|
|
@@ -4125,7 +4199,7 @@ async function parseTokenInfo(tokenData) {
|
|
|
4125
4199
|
try {
|
|
4126
4200
|
const sdkToken = await import_Token6.Token.fromJSON(data);
|
|
4127
4201
|
if (sdkToken.id) {
|
|
4128
|
-
defaultInfo.tokenId = sdkToken.id.
|
|
4202
|
+
defaultInfo.tokenId = sdkToken.id.toJSON();
|
|
4129
4203
|
}
|
|
4130
4204
|
if (sdkToken.coins && sdkToken.coins.coins) {
|
|
4131
4205
|
const rawCoins = sdkToken.coins.coins;
|
|
@@ -4295,6 +4369,13 @@ function extractTokenStateKey(token) {
|
|
|
4295
4369
|
if (!tokenId || !stateHash) return null;
|
|
4296
4370
|
return createTokenStateKey(tokenId, stateHash);
|
|
4297
4371
|
}
|
|
4372
|
+
function fromHex4(hex) {
|
|
4373
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
4374
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
4375
|
+
bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
|
|
4376
|
+
}
|
|
4377
|
+
return bytes;
|
|
4378
|
+
}
|
|
4298
4379
|
function hasSameGenesisTokenId(t1, t2) {
|
|
4299
4380
|
const id1 = extractTokenIdFromSdkData(t1.sdkData);
|
|
4300
4381
|
const id2 = extractTokenIdFromSdkData(t2.sdkData);
|
|
@@ -4384,6 +4465,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4384
4465
|
// Token State
|
|
4385
4466
|
tokens = /* @__PURE__ */ new Map();
|
|
4386
4467
|
pendingTransfers = /* @__PURE__ */ new Map();
|
|
4468
|
+
pendingBackgroundTasks = [];
|
|
4387
4469
|
// Repository State (tombstones, archives, forked, history)
|
|
4388
4470
|
tombstones = [];
|
|
4389
4471
|
archivedTokens = /* @__PURE__ */ new Map();
|
|
@@ -4408,6 +4490,12 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4408
4490
|
// Poll every 2s
|
|
4409
4491
|
static PROOF_POLLING_MAX_ATTEMPTS = 30;
|
|
4410
4492
|
// Max 30 attempts (~60s)
|
|
4493
|
+
// Storage event subscriptions (push-based sync)
|
|
4494
|
+
storageEventUnsubscribers = [];
|
|
4495
|
+
syncDebounceTimer = null;
|
|
4496
|
+
static SYNC_DEBOUNCE_MS = 500;
|
|
4497
|
+
/** Sync coalescing: concurrent sync() calls share the same operation */
|
|
4498
|
+
_syncInProgress = null;
|
|
4411
4499
|
constructor(config) {
|
|
4412
4500
|
this.moduleConfig = {
|
|
4413
4501
|
autoSync: config?.autoSync ?? true,
|
|
@@ -4416,10 +4504,13 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4416
4504
|
maxRetries: config?.maxRetries ?? 3,
|
|
4417
4505
|
debug: config?.debug ?? false
|
|
4418
4506
|
};
|
|
4419
|
-
|
|
4420
|
-
this.l1 = l1Enabled ? new L1PaymentsModule(config?.l1) : null;
|
|
4507
|
+
this.l1 = config?.l1 === null ? null : new L1PaymentsModule(config?.l1);
|
|
4421
4508
|
}
|
|
4422
|
-
/**
|
|
4509
|
+
/**
|
|
4510
|
+
* Get the current module configuration (excluding L1 config).
|
|
4511
|
+
*
|
|
4512
|
+
* @returns Resolved configuration with all defaults applied.
|
|
4513
|
+
*/
|
|
4423
4514
|
getConfig() {
|
|
4424
4515
|
return this.moduleConfig;
|
|
4425
4516
|
}
|
|
@@ -4460,9 +4551,9 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4460
4551
|
transport: deps.transport
|
|
4461
4552
|
});
|
|
4462
4553
|
}
|
|
4463
|
-
this.unsubscribeTransfers = deps.transport.onTokenTransfer(
|
|
4464
|
-
this.handleIncomingTransfer(transfer)
|
|
4465
|
-
|
|
4554
|
+
this.unsubscribeTransfers = deps.transport.onTokenTransfer(
|
|
4555
|
+
(transfer) => this.handleIncomingTransfer(transfer)
|
|
4556
|
+
);
|
|
4466
4557
|
if (deps.transport.onPaymentRequest) {
|
|
4467
4558
|
this.unsubscribePaymentRequests = deps.transport.onPaymentRequest((request) => {
|
|
4468
4559
|
this.handleIncomingPaymentRequest(request);
|
|
@@ -4473,9 +4564,14 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4473
4564
|
this.handlePaymentRequestResponse(response);
|
|
4474
4565
|
});
|
|
4475
4566
|
}
|
|
4567
|
+
this.subscribeToStorageEvents();
|
|
4476
4568
|
}
|
|
4477
4569
|
/**
|
|
4478
|
-
* Load
|
|
4570
|
+
* Load all token data from storage providers and restore wallet state.
|
|
4571
|
+
*
|
|
4572
|
+
* Loads tokens, nametag data, transaction history, and pending transfers
|
|
4573
|
+
* from configured storage providers. Restores pending V5 tokens and
|
|
4574
|
+
* triggers a fire-and-forget {@link resolveUnconfirmed} call.
|
|
4479
4575
|
*/
|
|
4480
4576
|
async load() {
|
|
4481
4577
|
this.ensureInitialized();
|
|
@@ -4492,6 +4588,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4492
4588
|
console.error(`[Payments] Failed to load from provider ${id}:`, err);
|
|
4493
4589
|
}
|
|
4494
4590
|
}
|
|
4591
|
+
await this.loadPendingV5Tokens();
|
|
4495
4592
|
await this.loadTokensFromFileStorage();
|
|
4496
4593
|
await this.loadNametagFromFileStorage();
|
|
4497
4594
|
const historyData = await this.deps.storage.get(STORAGE_KEYS_ADDRESS.TRANSACTION_HISTORY);
|
|
@@ -4509,9 +4606,14 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4509
4606
|
this.pendingTransfers.set(transfer.id, transfer);
|
|
4510
4607
|
}
|
|
4511
4608
|
}
|
|
4609
|
+
this.resolveUnconfirmed().catch(() => {
|
|
4610
|
+
});
|
|
4512
4611
|
}
|
|
4513
4612
|
/**
|
|
4514
|
-
* Cleanup
|
|
4613
|
+
* Cleanup all subscriptions, polling jobs, and pending resolvers.
|
|
4614
|
+
*
|
|
4615
|
+
* Should be called when the wallet is being shut down or the module is
|
|
4616
|
+
* no longer needed. Also destroys the L1 sub-module if present.
|
|
4515
4617
|
*/
|
|
4516
4618
|
destroy() {
|
|
4517
4619
|
this.unsubscribeTransfers?.();
|
|
@@ -4529,6 +4631,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4529
4631
|
resolver.reject(new Error("Module destroyed"));
|
|
4530
4632
|
}
|
|
4531
4633
|
this.pendingResponseResolvers.clear();
|
|
4634
|
+
this.unsubscribeStorageEvents();
|
|
4532
4635
|
if (this.l1) {
|
|
4533
4636
|
this.l1.destroy();
|
|
4534
4637
|
}
|
|
@@ -4545,7 +4648,8 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4545
4648
|
const result = {
|
|
4546
4649
|
id: crypto.randomUUID(),
|
|
4547
4650
|
status: "pending",
|
|
4548
|
-
tokens: []
|
|
4651
|
+
tokens: [],
|
|
4652
|
+
tokenTransfers: []
|
|
4549
4653
|
};
|
|
4550
4654
|
try {
|
|
4551
4655
|
const peerInfo = await this.deps.transport.resolve?.(request.recipient) ?? null;
|
|
@@ -4582,69 +4686,147 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4582
4686
|
await this.saveToOutbox(result, recipientPubkey);
|
|
4583
4687
|
result.status = "submitted";
|
|
4584
4688
|
const recipientNametag = request.recipient.startsWith("@") ? request.recipient.slice(1) : void 0;
|
|
4689
|
+
const transferMode = request.transferMode ?? "instant";
|
|
4585
4690
|
if (splitPlan.requiresSplit && splitPlan.tokenToSplit) {
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4691
|
+
if (transferMode === "conservative") {
|
|
4692
|
+
this.log("Executing conservative split...");
|
|
4693
|
+
const splitExecutor = new TokenSplitExecutor({
|
|
4694
|
+
stateTransitionClient: stClient,
|
|
4695
|
+
trustBase,
|
|
4696
|
+
signingService
|
|
4697
|
+
});
|
|
4698
|
+
const splitResult = await splitExecutor.executeSplit(
|
|
4699
|
+
splitPlan.tokenToSplit.sdkToken,
|
|
4700
|
+
splitPlan.splitAmount,
|
|
4701
|
+
splitPlan.remainderAmount,
|
|
4702
|
+
splitPlan.coinId,
|
|
4703
|
+
recipientAddress
|
|
4704
|
+
);
|
|
4705
|
+
const changeTokenData = splitResult.tokenForSender.toJSON();
|
|
4706
|
+
const changeUiToken = {
|
|
4707
|
+
id: crypto.randomUUID(),
|
|
4708
|
+
coinId: request.coinId,
|
|
4709
|
+
symbol: this.getCoinSymbol(request.coinId),
|
|
4710
|
+
name: this.getCoinName(request.coinId),
|
|
4711
|
+
decimals: this.getCoinDecimals(request.coinId),
|
|
4712
|
+
iconUrl: this.getCoinIconUrl(request.coinId),
|
|
4713
|
+
amount: splitPlan.remainderAmount.toString(),
|
|
4714
|
+
status: "confirmed",
|
|
4715
|
+
createdAt: Date.now(),
|
|
4716
|
+
updatedAt: Date.now(),
|
|
4717
|
+
sdkData: JSON.stringify(changeTokenData)
|
|
4718
|
+
};
|
|
4719
|
+
await this.addToken(changeUiToken, true);
|
|
4720
|
+
this.log(`Conservative split: change token saved: ${changeUiToken.id}`);
|
|
4721
|
+
await this.deps.transport.sendTokenTransfer(recipientPubkey, {
|
|
4722
|
+
sourceToken: JSON.stringify(splitResult.tokenForRecipient.toJSON()),
|
|
4723
|
+
transferTx: JSON.stringify(splitResult.recipientTransferTx.toJSON()),
|
|
4724
|
+
memo: request.memo
|
|
4725
|
+
});
|
|
4726
|
+
const splitCommitmentRequestId = splitResult.recipientTransferTx?.data?.requestId ?? splitResult.recipientTransferTx?.requestId;
|
|
4727
|
+
const splitRequestIdHex = splitCommitmentRequestId instanceof Uint8Array ? Array.from(splitCommitmentRequestId).map((b) => b.toString(16).padStart(2, "0")).join("") : splitCommitmentRequestId ? String(splitCommitmentRequestId) : void 0;
|
|
4728
|
+
await this.removeToken(splitPlan.tokenToSplit.uiToken.id, recipientNametag, true);
|
|
4729
|
+
result.tokenTransfers.push({
|
|
4730
|
+
sourceTokenId: splitPlan.tokenToSplit.uiToken.id,
|
|
4731
|
+
method: "split",
|
|
4732
|
+
requestIdHex: splitRequestIdHex
|
|
4733
|
+
});
|
|
4734
|
+
this.log(`Conservative split transfer completed`);
|
|
4735
|
+
} else {
|
|
4736
|
+
this.log("Executing instant split...");
|
|
4737
|
+
const devMode = this.deps.oracle.isDevMode?.() ?? false;
|
|
4738
|
+
const executor = new InstantSplitExecutor({
|
|
4739
|
+
stateTransitionClient: stClient,
|
|
4740
|
+
trustBase,
|
|
4741
|
+
signingService,
|
|
4742
|
+
devMode
|
|
4743
|
+
});
|
|
4744
|
+
const instantResult = await executor.executeSplitInstant(
|
|
4745
|
+
splitPlan.tokenToSplit.sdkToken,
|
|
4746
|
+
splitPlan.splitAmount,
|
|
4747
|
+
splitPlan.remainderAmount,
|
|
4748
|
+
splitPlan.coinId,
|
|
4749
|
+
recipientAddress,
|
|
4750
|
+
this.deps.transport,
|
|
4751
|
+
recipientPubkey,
|
|
4752
|
+
{
|
|
4753
|
+
onChangeTokenCreated: async (changeToken) => {
|
|
4754
|
+
const changeTokenData = changeToken.toJSON();
|
|
4755
|
+
const uiToken = {
|
|
4756
|
+
id: crypto.randomUUID(),
|
|
4757
|
+
coinId: request.coinId,
|
|
4758
|
+
symbol: this.getCoinSymbol(request.coinId),
|
|
4759
|
+
name: this.getCoinName(request.coinId),
|
|
4760
|
+
decimals: this.getCoinDecimals(request.coinId),
|
|
4761
|
+
iconUrl: this.getCoinIconUrl(request.coinId),
|
|
4762
|
+
amount: splitPlan.remainderAmount.toString(),
|
|
4763
|
+
status: "confirmed",
|
|
4764
|
+
createdAt: Date.now(),
|
|
4765
|
+
updatedAt: Date.now(),
|
|
4766
|
+
sdkData: JSON.stringify(changeTokenData)
|
|
4767
|
+
};
|
|
4768
|
+
await this.addToken(uiToken, true);
|
|
4769
|
+
this.log(`Change token saved via background: ${uiToken.id}`);
|
|
4770
|
+
},
|
|
4771
|
+
onStorageSync: async () => {
|
|
4772
|
+
await this.save();
|
|
4773
|
+
return true;
|
|
4774
|
+
}
|
|
4775
|
+
}
|
|
4776
|
+
);
|
|
4777
|
+
if (!instantResult.success) {
|
|
4778
|
+
throw new Error(instantResult.error || "Instant split failed");
|
|
4779
|
+
}
|
|
4780
|
+
if (instantResult.backgroundPromise) {
|
|
4781
|
+
this.pendingBackgroundTasks.push(instantResult.backgroundPromise);
|
|
4782
|
+
}
|
|
4783
|
+
await this.removeToken(splitPlan.tokenToSplit.uiToken.id, recipientNametag);
|
|
4784
|
+
result.tokenTransfers.push({
|
|
4785
|
+
sourceTokenId: splitPlan.tokenToSplit.uiToken.id,
|
|
4786
|
+
method: "split",
|
|
4787
|
+
splitGroupId: instantResult.splitGroupId,
|
|
4788
|
+
nostrEventId: instantResult.nostrEventId
|
|
4789
|
+
});
|
|
4790
|
+
this.log(`Instant split transfer completed`);
|
|
4791
|
+
}
|
|
4625
4792
|
}
|
|
4626
4793
|
for (const tokenWithAmount of splitPlan.tokensToTransferDirectly) {
|
|
4627
4794
|
const token = tokenWithAmount.uiToken;
|
|
4628
4795
|
const commitment = await this.createSdkCommitment(token, recipientAddress, signingService);
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4796
|
+
if (transferMode === "conservative") {
|
|
4797
|
+
console.log(`[Payments] CONSERVATIVE: Sending direct token ${token.id.slice(0, 8)}... to ${recipientPubkey.slice(0, 8)}...`);
|
|
4798
|
+
const submitResponse = await stClient.submitTransferCommitment(commitment);
|
|
4799
|
+
if (submitResponse.status !== "SUCCESS" && submitResponse.status !== "REQUEST_ID_EXISTS") {
|
|
4800
|
+
throw new Error(`Transfer commitment failed: ${submitResponse.status}`);
|
|
4801
|
+
}
|
|
4802
|
+
const inclusionProof = await (0, import_InclusionProofUtils5.waitInclusionProof)(trustBase, stClient, commitment);
|
|
4803
|
+
const transferTx = commitment.toTransaction(inclusionProof);
|
|
4804
|
+
await this.deps.transport.sendTokenTransfer(recipientPubkey, {
|
|
4805
|
+
sourceToken: JSON.stringify(tokenWithAmount.sdkToken.toJSON()),
|
|
4806
|
+
transferTx: JSON.stringify(transferTx.toJSON()),
|
|
4807
|
+
memo: request.memo
|
|
4808
|
+
});
|
|
4809
|
+
console.log(`[Payments] CONSERVATIVE: Direct token sent successfully`);
|
|
4810
|
+
} else {
|
|
4811
|
+
console.log(`[Payments] NOSTR-FIRST: Sending direct token ${token.id.slice(0, 8)}... to ${recipientPubkey.slice(0, 8)}...`);
|
|
4812
|
+
await this.deps.transport.sendTokenTransfer(recipientPubkey, {
|
|
4813
|
+
sourceToken: JSON.stringify(tokenWithAmount.sdkToken.toJSON()),
|
|
4814
|
+
commitmentData: JSON.stringify(commitment.toJSON()),
|
|
4815
|
+
memo: request.memo
|
|
4816
|
+
});
|
|
4817
|
+
console.log(`[Payments] NOSTR-FIRST: Direct token sent successfully`);
|
|
4818
|
+
stClient.submitTransferCommitment(commitment).catch(
|
|
4819
|
+
(err) => console.error("[Payments] Background commitment submit failed:", err)
|
|
4820
|
+
);
|
|
4635
4821
|
}
|
|
4636
|
-
const inclusionProof = await this.deps.oracle.waitForProofSdk(commitment);
|
|
4637
|
-
const transferTx = commitment.toTransaction(inclusionProof);
|
|
4638
4822
|
const requestIdBytes = commitment.requestId;
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
memo: request.memo
|
|
4823
|
+
const requestIdHex = requestIdBytes instanceof Uint8Array ? Array.from(requestIdBytes).map((b) => b.toString(16).padStart(2, "0")).join("") : String(requestIdBytes);
|
|
4824
|
+
result.tokenTransfers.push({
|
|
4825
|
+
sourceTokenId: token.id,
|
|
4826
|
+
method: "direct",
|
|
4827
|
+
requestIdHex
|
|
4645
4828
|
});
|
|
4646
|
-
|
|
4647
|
-
this.log(`Token ${token.id} transferred, txHash: ${result.txHash}`);
|
|
4829
|
+
this.log(`Token ${token.id} sent via ${transferMode.toUpperCase()}, requestId: ${requestIdHex}`);
|
|
4648
4830
|
await this.removeToken(token.id, recipientNametag, true);
|
|
4649
4831
|
}
|
|
4650
4832
|
result.status = "delivered";
|
|
@@ -4657,7 +4839,8 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4657
4839
|
coinId: request.coinId,
|
|
4658
4840
|
symbol: this.getCoinSymbol(request.coinId),
|
|
4659
4841
|
timestamp: Date.now(),
|
|
4660
|
-
recipientNametag
|
|
4842
|
+
recipientNametag,
|
|
4843
|
+
transferId: result.id
|
|
4661
4844
|
});
|
|
4662
4845
|
this.deps.emitEvent("transfer:confirmed", result);
|
|
4663
4846
|
return result;
|
|
@@ -4793,6 +4976,9 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4793
4976
|
}
|
|
4794
4977
|
);
|
|
4795
4978
|
if (result.success) {
|
|
4979
|
+
if (result.backgroundPromise) {
|
|
4980
|
+
this.pendingBackgroundTasks.push(result.backgroundPromise);
|
|
4981
|
+
}
|
|
4796
4982
|
const recipientNametag = request.recipient.startsWith("@") ? request.recipient.slice(1) : void 0;
|
|
4797
4983
|
await this.removeToken(tokenToSplit.id, recipientNametag, true);
|
|
4798
4984
|
await this.addToHistory({
|
|
@@ -4834,6 +5020,63 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4834
5020
|
*/
|
|
4835
5021
|
async processInstantSplitBundle(bundle, senderPubkey) {
|
|
4836
5022
|
this.ensureInitialized();
|
|
5023
|
+
if (!isInstantSplitBundleV5(bundle)) {
|
|
5024
|
+
return this.processInstantSplitBundleSync(bundle, senderPubkey);
|
|
5025
|
+
}
|
|
5026
|
+
try {
|
|
5027
|
+
const deterministicId = `v5split_${bundle.splitGroupId}`;
|
|
5028
|
+
if (this.tokens.has(deterministicId)) {
|
|
5029
|
+
this.log(`V5 bundle ${deterministicId.slice(0, 16)}... already exists, skipping duplicate`);
|
|
5030
|
+
return { success: true, durationMs: 0 };
|
|
5031
|
+
}
|
|
5032
|
+
const registry = TokenRegistry.getInstance();
|
|
5033
|
+
const pendingData = {
|
|
5034
|
+
type: "v5_bundle",
|
|
5035
|
+
stage: "RECEIVED",
|
|
5036
|
+
bundleJson: JSON.stringify(bundle),
|
|
5037
|
+
senderPubkey,
|
|
5038
|
+
savedAt: Date.now(),
|
|
5039
|
+
attemptCount: 0
|
|
5040
|
+
};
|
|
5041
|
+
const uiToken = {
|
|
5042
|
+
id: deterministicId,
|
|
5043
|
+
coinId: bundle.coinId,
|
|
5044
|
+
symbol: registry.getSymbol(bundle.coinId) || bundle.coinId,
|
|
5045
|
+
name: registry.getName(bundle.coinId) || bundle.coinId,
|
|
5046
|
+
decimals: registry.getDecimals(bundle.coinId) ?? 8,
|
|
5047
|
+
amount: bundle.amount,
|
|
5048
|
+
status: "submitted",
|
|
5049
|
+
// UNCONFIRMED
|
|
5050
|
+
createdAt: Date.now(),
|
|
5051
|
+
updatedAt: Date.now(),
|
|
5052
|
+
sdkData: JSON.stringify({ _pendingFinalization: pendingData })
|
|
5053
|
+
};
|
|
5054
|
+
await this.addToken(uiToken, false);
|
|
5055
|
+
this.log(`V5 bundle saved as unconfirmed: ${uiToken.id.slice(0, 8)}...`);
|
|
5056
|
+
this.deps.emitEvent("transfer:incoming", {
|
|
5057
|
+
id: bundle.splitGroupId,
|
|
5058
|
+
senderPubkey,
|
|
5059
|
+
tokens: [uiToken],
|
|
5060
|
+
receivedAt: Date.now()
|
|
5061
|
+
});
|
|
5062
|
+
await this.save();
|
|
5063
|
+
this.resolveUnconfirmed().catch(() => {
|
|
5064
|
+
});
|
|
5065
|
+
return { success: true, durationMs: 0 };
|
|
5066
|
+
} catch (error) {
|
|
5067
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5068
|
+
return {
|
|
5069
|
+
success: false,
|
|
5070
|
+
error: errorMessage,
|
|
5071
|
+
durationMs: 0
|
|
5072
|
+
};
|
|
5073
|
+
}
|
|
5074
|
+
}
|
|
5075
|
+
/**
|
|
5076
|
+
* Synchronous V4 bundle processing (dev mode only).
|
|
5077
|
+
* Kept for backward compatibility with V4 bundles.
|
|
5078
|
+
*/
|
|
5079
|
+
async processInstantSplitBundleSync(bundle, senderPubkey) {
|
|
4837
5080
|
try {
|
|
4838
5081
|
const signingService = await this.createSigningService();
|
|
4839
5082
|
const stClient = this.deps.oracle.getStateTransitionClient?.();
|
|
@@ -4919,7 +5162,10 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
4919
5162
|
}
|
|
4920
5163
|
}
|
|
4921
5164
|
/**
|
|
4922
|
-
*
|
|
5165
|
+
* Type-guard: check whether a payload is a valid {@link InstantSplitBundle} (V4 or V5).
|
|
5166
|
+
*
|
|
5167
|
+
* @param payload - The object to test.
|
|
5168
|
+
* @returns `true` if the payload matches the InstantSplitBundle shape.
|
|
4923
5169
|
*/
|
|
4924
5170
|
isInstantSplitBundle(payload) {
|
|
4925
5171
|
return isInstantSplitBundle(payload);
|
|
@@ -5000,39 +5246,57 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5000
5246
|
return [...this.paymentRequests];
|
|
5001
5247
|
}
|
|
5002
5248
|
/**
|
|
5003
|
-
* Get
|
|
5249
|
+
* Get the count of payment requests with status `'pending'`.
|
|
5250
|
+
*
|
|
5251
|
+
* @returns Number of pending incoming payment requests.
|
|
5004
5252
|
*/
|
|
5005
5253
|
getPendingPaymentRequestsCount() {
|
|
5006
5254
|
return this.paymentRequests.filter((r) => r.status === "pending").length;
|
|
5007
5255
|
}
|
|
5008
5256
|
/**
|
|
5009
|
-
* Accept a payment request
|
|
5257
|
+
* Accept a payment request and notify the requester.
|
|
5258
|
+
*
|
|
5259
|
+
* Marks the request as `'accepted'` and sends a response via transport.
|
|
5260
|
+
* The caller should subsequently call {@link send} to fulfill the payment.
|
|
5261
|
+
*
|
|
5262
|
+
* @param requestId - ID of the incoming payment request to accept.
|
|
5010
5263
|
*/
|
|
5011
5264
|
async acceptPaymentRequest(requestId2) {
|
|
5012
5265
|
this.updatePaymentRequestStatus(requestId2, "accepted");
|
|
5013
5266
|
await this.sendPaymentRequestResponse(requestId2, "accepted");
|
|
5014
5267
|
}
|
|
5015
5268
|
/**
|
|
5016
|
-
* Reject a payment request
|
|
5269
|
+
* Reject a payment request and notify the requester.
|
|
5270
|
+
*
|
|
5271
|
+
* @param requestId - ID of the incoming payment request to reject.
|
|
5017
5272
|
*/
|
|
5018
5273
|
async rejectPaymentRequest(requestId2) {
|
|
5019
5274
|
this.updatePaymentRequestStatus(requestId2, "rejected");
|
|
5020
5275
|
await this.sendPaymentRequestResponse(requestId2, "rejected");
|
|
5021
5276
|
}
|
|
5022
5277
|
/**
|
|
5023
|
-
* Mark a payment request as paid (
|
|
5278
|
+
* Mark a payment request as paid (local status update only).
|
|
5279
|
+
*
|
|
5280
|
+
* Typically called after a successful {@link send} to record that the
|
|
5281
|
+
* request has been fulfilled.
|
|
5282
|
+
*
|
|
5283
|
+
* @param requestId - ID of the incoming payment request to mark as paid.
|
|
5024
5284
|
*/
|
|
5025
5285
|
markPaymentRequestPaid(requestId2) {
|
|
5026
5286
|
this.updatePaymentRequestStatus(requestId2, "paid");
|
|
5027
5287
|
}
|
|
5028
5288
|
/**
|
|
5029
|
-
*
|
|
5289
|
+
* Remove all non-pending incoming payment requests from memory.
|
|
5290
|
+
*
|
|
5291
|
+
* Keeps only requests with status `'pending'`.
|
|
5030
5292
|
*/
|
|
5031
5293
|
clearProcessedPaymentRequests() {
|
|
5032
5294
|
this.paymentRequests = this.paymentRequests.filter((r) => r.status === "pending");
|
|
5033
5295
|
}
|
|
5034
5296
|
/**
|
|
5035
|
-
* Remove a specific payment request
|
|
5297
|
+
* Remove a specific incoming payment request by ID.
|
|
5298
|
+
*
|
|
5299
|
+
* @param requestId - ID of the payment request to remove.
|
|
5036
5300
|
*/
|
|
5037
5301
|
removePaymentRequest(requestId2) {
|
|
5038
5302
|
this.paymentRequests = this.paymentRequests.filter((r) => r.id !== requestId2);
|
|
@@ -5079,13 +5343,16 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5079
5343
|
if (this.paymentRequests.find((r) => r.id === transportRequest.id)) {
|
|
5080
5344
|
return;
|
|
5081
5345
|
}
|
|
5346
|
+
const coinId = transportRequest.request.coinId;
|
|
5347
|
+
const registry = TokenRegistry.getInstance();
|
|
5348
|
+
const coinDef = registry.getDefinition(coinId);
|
|
5082
5349
|
const request = {
|
|
5083
5350
|
id: transportRequest.id,
|
|
5084
5351
|
senderPubkey: transportRequest.senderTransportPubkey,
|
|
5352
|
+
senderNametag: transportRequest.senderNametag,
|
|
5085
5353
|
amount: transportRequest.request.amount,
|
|
5086
|
-
coinId
|
|
5087
|
-
symbol:
|
|
5088
|
-
// Use coinId as symbol for now
|
|
5354
|
+
coinId,
|
|
5355
|
+
symbol: coinDef?.symbol || coinId.slice(0, 8),
|
|
5089
5356
|
message: transportRequest.request.message,
|
|
5090
5357
|
recipientNametag: transportRequest.request.recipientNametag,
|
|
5091
5358
|
requestId: transportRequest.request.requestId,
|
|
@@ -5154,7 +5421,11 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5154
5421
|
});
|
|
5155
5422
|
}
|
|
5156
5423
|
/**
|
|
5157
|
-
* Cancel
|
|
5424
|
+
* Cancel an active {@link waitForPaymentResponse} call.
|
|
5425
|
+
*
|
|
5426
|
+
* The pending promise is rejected with a `'Cancelled'` error.
|
|
5427
|
+
*
|
|
5428
|
+
* @param requestId - The outgoing request ID whose wait should be cancelled.
|
|
5158
5429
|
*/
|
|
5159
5430
|
cancelWaitForPaymentResponse(requestId2) {
|
|
5160
5431
|
const resolver = this.pendingResponseResolvers.get(requestId2);
|
|
@@ -5165,14 +5436,16 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5165
5436
|
}
|
|
5166
5437
|
}
|
|
5167
5438
|
/**
|
|
5168
|
-
* Remove an outgoing payment request
|
|
5439
|
+
* Remove an outgoing payment request and cancel any pending wait.
|
|
5440
|
+
*
|
|
5441
|
+
* @param requestId - ID of the outgoing request to remove.
|
|
5169
5442
|
*/
|
|
5170
5443
|
removeOutgoingPaymentRequest(requestId2) {
|
|
5171
5444
|
this.outgoingPaymentRequests.delete(requestId2);
|
|
5172
5445
|
this.cancelWaitForPaymentResponse(requestId2);
|
|
5173
5446
|
}
|
|
5174
5447
|
/**
|
|
5175
|
-
*
|
|
5448
|
+
* Remove all outgoing payment requests that are `'paid'`, `'rejected'`, or `'expired'`.
|
|
5176
5449
|
*/
|
|
5177
5450
|
clearCompletedOutgoingPaymentRequests() {
|
|
5178
5451
|
for (const [id, request] of this.outgoingPaymentRequests) {
|
|
@@ -5244,6 +5517,71 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5244
5517
|
}
|
|
5245
5518
|
}
|
|
5246
5519
|
// ===========================================================================
|
|
5520
|
+
// Public API - Receive
|
|
5521
|
+
// ===========================================================================
|
|
5522
|
+
/**
|
|
5523
|
+
* Fetch and process pending incoming transfers from the transport layer.
|
|
5524
|
+
*
|
|
5525
|
+
* Performs a one-shot query to fetch all pending events, processes them
|
|
5526
|
+
* through the existing pipeline, and resolves after all stored events
|
|
5527
|
+
* are handled. Useful for batch/CLI apps that need explicit receive.
|
|
5528
|
+
*
|
|
5529
|
+
* When `finalize` is true, polls resolveUnconfirmed() + load() until all
|
|
5530
|
+
* tokens are confirmed or the timeout expires. Otherwise calls
|
|
5531
|
+
* resolveUnconfirmed() once to submit pending commitments.
|
|
5532
|
+
*
|
|
5533
|
+
* @param options - Optional receive options including finalization control
|
|
5534
|
+
* @param callback - Optional callback invoked for each newly received transfer
|
|
5535
|
+
* @returns ReceiveResult with transfers and finalization metadata
|
|
5536
|
+
*/
|
|
5537
|
+
async receive(options, callback) {
|
|
5538
|
+
this.ensureInitialized();
|
|
5539
|
+
if (!this.deps.transport.fetchPendingEvents) {
|
|
5540
|
+
throw new Error("Transport provider does not support fetchPendingEvents");
|
|
5541
|
+
}
|
|
5542
|
+
const opts = options ?? {};
|
|
5543
|
+
const tokensBefore = new Set(this.tokens.keys());
|
|
5544
|
+
await this.deps.transport.fetchPendingEvents();
|
|
5545
|
+
await this.load();
|
|
5546
|
+
const received = [];
|
|
5547
|
+
for (const [tokenId, token] of this.tokens) {
|
|
5548
|
+
if (!tokensBefore.has(tokenId)) {
|
|
5549
|
+
const transfer = {
|
|
5550
|
+
id: tokenId,
|
|
5551
|
+
senderPubkey: "",
|
|
5552
|
+
tokens: [token],
|
|
5553
|
+
receivedAt: Date.now()
|
|
5554
|
+
};
|
|
5555
|
+
received.push(transfer);
|
|
5556
|
+
if (callback) callback(transfer);
|
|
5557
|
+
}
|
|
5558
|
+
}
|
|
5559
|
+
const result = { transfers: received };
|
|
5560
|
+
if (opts.finalize) {
|
|
5561
|
+
const timeout = opts.timeout ?? 6e4;
|
|
5562
|
+
const pollInterval = opts.pollInterval ?? 2e3;
|
|
5563
|
+
const startTime = Date.now();
|
|
5564
|
+
while (Date.now() - startTime < timeout) {
|
|
5565
|
+
const resolution = await this.resolveUnconfirmed();
|
|
5566
|
+
result.finalization = resolution;
|
|
5567
|
+
if (opts.onProgress) opts.onProgress(resolution);
|
|
5568
|
+
const stillUnconfirmed = Array.from(this.tokens.values()).some(
|
|
5569
|
+
(t) => t.status === "submitted" || t.status === "pending"
|
|
5570
|
+
);
|
|
5571
|
+
if (!stillUnconfirmed) break;
|
|
5572
|
+
await new Promise((r) => setTimeout(r, pollInterval));
|
|
5573
|
+
await this.load();
|
|
5574
|
+
}
|
|
5575
|
+
result.finalizationDurationMs = Date.now() - startTime;
|
|
5576
|
+
result.timedOut = Array.from(this.tokens.values()).some(
|
|
5577
|
+
(t) => t.status === "submitted" || t.status === "pending"
|
|
5578
|
+
);
|
|
5579
|
+
} else {
|
|
5580
|
+
result.finalization = await this.resolveUnconfirmed();
|
|
5581
|
+
}
|
|
5582
|
+
return result;
|
|
5583
|
+
}
|
|
5584
|
+
// ===========================================================================
|
|
5247
5585
|
// Public API - Balance & Tokens
|
|
5248
5586
|
// ===========================================================================
|
|
5249
5587
|
/**
|
|
@@ -5253,10 +5591,20 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5253
5591
|
this.priceProvider = provider;
|
|
5254
5592
|
}
|
|
5255
5593
|
/**
|
|
5256
|
-
*
|
|
5257
|
-
*
|
|
5594
|
+
* Wait for all pending background operations (e.g., instant split change token creation).
|
|
5595
|
+
* Call this before process exit to ensure all tokens are saved.
|
|
5258
5596
|
*/
|
|
5259
|
-
async
|
|
5597
|
+
async waitForPendingOperations() {
|
|
5598
|
+
if (this.pendingBackgroundTasks.length > 0) {
|
|
5599
|
+
await Promise.allSettled(this.pendingBackgroundTasks);
|
|
5600
|
+
this.pendingBackgroundTasks = [];
|
|
5601
|
+
}
|
|
5602
|
+
}
|
|
5603
|
+
/**
|
|
5604
|
+
* Get total portfolio value in USD.
|
|
5605
|
+
* Returns null if PriceProvider is not configured.
|
|
5606
|
+
*/
|
|
5607
|
+
async getFiatBalance() {
|
|
5260
5608
|
const assets = await this.getAssets();
|
|
5261
5609
|
if (!this.priceProvider) {
|
|
5262
5610
|
return null;
|
|
@@ -5272,19 +5620,95 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5272
5620
|
return hasAnyPrice ? total : null;
|
|
5273
5621
|
}
|
|
5274
5622
|
/**
|
|
5275
|
-
* Get
|
|
5276
|
-
*
|
|
5623
|
+
* Get token balances grouped by coin type.
|
|
5624
|
+
*
|
|
5625
|
+
* Returns an array of {@link Asset} objects, one per coin type held.
|
|
5626
|
+
* Each entry includes confirmed and unconfirmed breakdowns. Tokens with
|
|
5627
|
+
* status `'spent'`, `'invalid'`, or `'transferring'` are excluded.
|
|
5628
|
+
*
|
|
5629
|
+
* This is synchronous — no price data is included. Use {@link getAssets}
|
|
5630
|
+
* for the async version with fiat pricing.
|
|
5631
|
+
*
|
|
5632
|
+
* @param coinId - Optional coin ID to filter by (e.g. hex string). When omitted, all coin types are returned.
|
|
5633
|
+
* @returns Array of balance summaries (synchronous — no await needed).
|
|
5634
|
+
*/
|
|
5635
|
+
getBalance(coinId) {
|
|
5636
|
+
return this.aggregateTokens(coinId);
|
|
5637
|
+
}
|
|
5638
|
+
/**
|
|
5639
|
+
* Get aggregated assets (tokens grouped by coinId) with price data.
|
|
5640
|
+
* Includes both confirmed and unconfirmed tokens with breakdown.
|
|
5277
5641
|
*/
|
|
5278
5642
|
async getAssets(coinId) {
|
|
5643
|
+
const rawAssets = this.aggregateTokens(coinId);
|
|
5644
|
+
if (!this.priceProvider || rawAssets.length === 0) {
|
|
5645
|
+
return rawAssets;
|
|
5646
|
+
}
|
|
5647
|
+
try {
|
|
5648
|
+
const registry = TokenRegistry.getInstance();
|
|
5649
|
+
const nameToCoins = /* @__PURE__ */ new Map();
|
|
5650
|
+
for (const asset of rawAssets) {
|
|
5651
|
+
const def = registry.getDefinition(asset.coinId);
|
|
5652
|
+
if (def?.name) {
|
|
5653
|
+
const existing = nameToCoins.get(def.name);
|
|
5654
|
+
if (existing) {
|
|
5655
|
+
existing.push(asset.coinId);
|
|
5656
|
+
} else {
|
|
5657
|
+
nameToCoins.set(def.name, [asset.coinId]);
|
|
5658
|
+
}
|
|
5659
|
+
}
|
|
5660
|
+
}
|
|
5661
|
+
if (nameToCoins.size > 0) {
|
|
5662
|
+
const tokenNames = Array.from(nameToCoins.keys());
|
|
5663
|
+
const prices = await this.priceProvider.getPrices(tokenNames);
|
|
5664
|
+
return rawAssets.map((raw) => {
|
|
5665
|
+
const def = registry.getDefinition(raw.coinId);
|
|
5666
|
+
const price = def?.name ? prices.get(def.name) : void 0;
|
|
5667
|
+
let fiatValueUsd = null;
|
|
5668
|
+
let fiatValueEur = null;
|
|
5669
|
+
if (price) {
|
|
5670
|
+
const humanAmount = Number(raw.totalAmount) / Math.pow(10, raw.decimals);
|
|
5671
|
+
fiatValueUsd = humanAmount * price.priceUsd;
|
|
5672
|
+
if (price.priceEur != null) {
|
|
5673
|
+
fiatValueEur = humanAmount * price.priceEur;
|
|
5674
|
+
}
|
|
5675
|
+
}
|
|
5676
|
+
return {
|
|
5677
|
+
...raw,
|
|
5678
|
+
priceUsd: price?.priceUsd ?? null,
|
|
5679
|
+
priceEur: price?.priceEur ?? null,
|
|
5680
|
+
change24h: price?.change24h ?? null,
|
|
5681
|
+
fiatValueUsd,
|
|
5682
|
+
fiatValueEur
|
|
5683
|
+
};
|
|
5684
|
+
});
|
|
5685
|
+
}
|
|
5686
|
+
} catch (error) {
|
|
5687
|
+
console.warn("[Payments] Failed to fetch prices, returning assets without price data:", error);
|
|
5688
|
+
}
|
|
5689
|
+
return rawAssets;
|
|
5690
|
+
}
|
|
5691
|
+
/**
|
|
5692
|
+
* Aggregate tokens by coinId with confirmed/unconfirmed breakdown.
|
|
5693
|
+
* Excludes tokens with status 'spent', 'invalid', or 'transferring'.
|
|
5694
|
+
*/
|
|
5695
|
+
aggregateTokens(coinId) {
|
|
5279
5696
|
const assetsMap = /* @__PURE__ */ new Map();
|
|
5280
5697
|
for (const token of this.tokens.values()) {
|
|
5281
|
-
if (token.status
|
|
5698
|
+
if (token.status === "spent" || token.status === "invalid" || token.status === "transferring") continue;
|
|
5282
5699
|
if (coinId && token.coinId !== coinId) continue;
|
|
5283
5700
|
const key = token.coinId;
|
|
5701
|
+
const amount = BigInt(token.amount);
|
|
5702
|
+
const isConfirmed = token.status === "confirmed";
|
|
5284
5703
|
const existing = assetsMap.get(key);
|
|
5285
5704
|
if (existing) {
|
|
5286
|
-
|
|
5287
|
-
|
|
5705
|
+
if (isConfirmed) {
|
|
5706
|
+
existing.confirmedAmount += amount;
|
|
5707
|
+
existing.confirmedTokenCount++;
|
|
5708
|
+
} else {
|
|
5709
|
+
existing.unconfirmedAmount += amount;
|
|
5710
|
+
existing.unconfirmedTokenCount++;
|
|
5711
|
+
}
|
|
5288
5712
|
} else {
|
|
5289
5713
|
assetsMap.set(key, {
|
|
5290
5714
|
coinId: token.coinId,
|
|
@@ -5292,78 +5716,42 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5292
5716
|
name: token.name,
|
|
5293
5717
|
decimals: token.decimals,
|
|
5294
5718
|
iconUrl: token.iconUrl,
|
|
5295
|
-
|
|
5296
|
-
|
|
5719
|
+
confirmedAmount: isConfirmed ? amount : 0n,
|
|
5720
|
+
unconfirmedAmount: isConfirmed ? 0n : amount,
|
|
5721
|
+
confirmedTokenCount: isConfirmed ? 1 : 0,
|
|
5722
|
+
unconfirmedTokenCount: isConfirmed ? 0 : 1
|
|
5297
5723
|
});
|
|
5298
5724
|
}
|
|
5299
5725
|
}
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
if (this.priceProvider && rawAssets.length > 0) {
|
|
5303
|
-
try {
|
|
5304
|
-
const registry = TokenRegistry.getInstance();
|
|
5305
|
-
const nameToCoins = /* @__PURE__ */ new Map();
|
|
5306
|
-
for (const asset of rawAssets) {
|
|
5307
|
-
const def = registry.getDefinition(asset.coinId);
|
|
5308
|
-
if (def?.name) {
|
|
5309
|
-
const existing = nameToCoins.get(def.name);
|
|
5310
|
-
if (existing) {
|
|
5311
|
-
existing.push(asset.coinId);
|
|
5312
|
-
} else {
|
|
5313
|
-
nameToCoins.set(def.name, [asset.coinId]);
|
|
5314
|
-
}
|
|
5315
|
-
}
|
|
5316
|
-
}
|
|
5317
|
-
if (nameToCoins.size > 0) {
|
|
5318
|
-
const tokenNames = Array.from(nameToCoins.keys());
|
|
5319
|
-
const prices = await this.priceProvider.getPrices(tokenNames);
|
|
5320
|
-
priceMap = /* @__PURE__ */ new Map();
|
|
5321
|
-
for (const [name, coinIds] of nameToCoins) {
|
|
5322
|
-
const price = prices.get(name);
|
|
5323
|
-
if (price) {
|
|
5324
|
-
for (const cid of coinIds) {
|
|
5325
|
-
priceMap.set(cid, {
|
|
5326
|
-
priceUsd: price.priceUsd,
|
|
5327
|
-
priceEur: price.priceEur,
|
|
5328
|
-
change24h: price.change24h
|
|
5329
|
-
});
|
|
5330
|
-
}
|
|
5331
|
-
}
|
|
5332
|
-
}
|
|
5333
|
-
}
|
|
5334
|
-
} catch (error) {
|
|
5335
|
-
console.warn("[Payments] Failed to fetch prices, returning assets without price data:", error);
|
|
5336
|
-
}
|
|
5337
|
-
}
|
|
5338
|
-
return rawAssets.map((raw) => {
|
|
5339
|
-
const price = priceMap?.get(raw.coinId);
|
|
5340
|
-
let fiatValueUsd = null;
|
|
5341
|
-
let fiatValueEur = null;
|
|
5342
|
-
if (price) {
|
|
5343
|
-
const humanAmount = Number(raw.totalAmount) / Math.pow(10, raw.decimals);
|
|
5344
|
-
fiatValueUsd = humanAmount * price.priceUsd;
|
|
5345
|
-
if (price.priceEur != null) {
|
|
5346
|
-
fiatValueEur = humanAmount * price.priceEur;
|
|
5347
|
-
}
|
|
5348
|
-
}
|
|
5726
|
+
return Array.from(assetsMap.values()).map((raw) => {
|
|
5727
|
+
const totalAmount = (raw.confirmedAmount + raw.unconfirmedAmount).toString();
|
|
5349
5728
|
return {
|
|
5350
5729
|
coinId: raw.coinId,
|
|
5351
5730
|
symbol: raw.symbol,
|
|
5352
5731
|
name: raw.name,
|
|
5353
5732
|
decimals: raw.decimals,
|
|
5354
5733
|
iconUrl: raw.iconUrl,
|
|
5355
|
-
totalAmount
|
|
5356
|
-
tokenCount: raw.
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5734
|
+
totalAmount,
|
|
5735
|
+
tokenCount: raw.confirmedTokenCount + raw.unconfirmedTokenCount,
|
|
5736
|
+
confirmedAmount: raw.confirmedAmount.toString(),
|
|
5737
|
+
unconfirmedAmount: raw.unconfirmedAmount.toString(),
|
|
5738
|
+
confirmedTokenCount: raw.confirmedTokenCount,
|
|
5739
|
+
unconfirmedTokenCount: raw.unconfirmedTokenCount,
|
|
5740
|
+
priceUsd: null,
|
|
5741
|
+
priceEur: null,
|
|
5742
|
+
change24h: null,
|
|
5743
|
+
fiatValueUsd: null,
|
|
5744
|
+
fiatValueEur: null
|
|
5362
5745
|
};
|
|
5363
5746
|
});
|
|
5364
5747
|
}
|
|
5365
5748
|
/**
|
|
5366
|
-
* Get all tokens
|
|
5749
|
+
* Get all tokens, optionally filtered by coin type and/or status.
|
|
5750
|
+
*
|
|
5751
|
+
* @param filter - Optional filter criteria.
|
|
5752
|
+
* @param filter.coinId - Return only tokens of this coin type.
|
|
5753
|
+
* @param filter.status - Return only tokens with this status (e.g. `'submitted'` for unconfirmed).
|
|
5754
|
+
* @returns Array of matching {@link Token} objects (synchronous).
|
|
5367
5755
|
*/
|
|
5368
5756
|
getTokens(filter) {
|
|
5369
5757
|
let tokens = Array.from(this.tokens.values());
|
|
@@ -5376,19 +5764,327 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5376
5764
|
return tokens;
|
|
5377
5765
|
}
|
|
5378
5766
|
/**
|
|
5379
|
-
* Get single token
|
|
5767
|
+
* Get a single token by its local ID.
|
|
5768
|
+
*
|
|
5769
|
+
* @param id - The local UUID assigned when the token was added.
|
|
5770
|
+
* @returns The token, or `undefined` if not found.
|
|
5380
5771
|
*/
|
|
5381
5772
|
getToken(id) {
|
|
5382
5773
|
return this.tokens.get(id);
|
|
5383
5774
|
}
|
|
5384
5775
|
// ===========================================================================
|
|
5776
|
+
// Public API - Unconfirmed Token Resolution
|
|
5777
|
+
// ===========================================================================
|
|
5778
|
+
/**
|
|
5779
|
+
* Attempt to resolve unconfirmed (status `'submitted'`) tokens by acquiring
|
|
5780
|
+
* their missing aggregator proofs.
|
|
5781
|
+
*
|
|
5782
|
+
* Each unconfirmed V5 token progresses through stages:
|
|
5783
|
+
* `RECEIVED` → `MINT_SUBMITTED` → `MINT_PROVEN` → `TRANSFER_SUBMITTED` → `FINALIZED`
|
|
5784
|
+
*
|
|
5785
|
+
* Uses 500 ms quick-timeouts per proof check so the call returns quickly even
|
|
5786
|
+
* when proofs are not yet available. Tokens that exceed 50 failed attempts are
|
|
5787
|
+
* marked `'invalid'`.
|
|
5788
|
+
*
|
|
5789
|
+
* Automatically called (fire-and-forget) by {@link load}.
|
|
5790
|
+
*
|
|
5791
|
+
* @returns Summary with counts of resolved, still-pending, and failed tokens plus per-token details.
|
|
5792
|
+
*/
|
|
5793
|
+
async resolveUnconfirmed() {
|
|
5794
|
+
this.ensureInitialized();
|
|
5795
|
+
const result = {
|
|
5796
|
+
resolved: 0,
|
|
5797
|
+
stillPending: 0,
|
|
5798
|
+
failed: 0,
|
|
5799
|
+
details: []
|
|
5800
|
+
};
|
|
5801
|
+
const stClient = this.deps.oracle.getStateTransitionClient?.();
|
|
5802
|
+
const trustBase = this.deps.oracle.getTrustBase?.();
|
|
5803
|
+
if (!stClient || !trustBase) return result;
|
|
5804
|
+
const signingService = await this.createSigningService();
|
|
5805
|
+
for (const [tokenId, token] of this.tokens) {
|
|
5806
|
+
if (token.status !== "submitted") continue;
|
|
5807
|
+
const pending2 = this.parsePendingFinalization(token.sdkData);
|
|
5808
|
+
if (!pending2) {
|
|
5809
|
+
result.stillPending++;
|
|
5810
|
+
continue;
|
|
5811
|
+
}
|
|
5812
|
+
if (pending2.type === "v5_bundle") {
|
|
5813
|
+
const progress = await this.resolveV5Token(tokenId, token, pending2, stClient, trustBase, signingService);
|
|
5814
|
+
result.details.push({ tokenId, stage: pending2.stage, status: progress });
|
|
5815
|
+
if (progress === "resolved") result.resolved++;
|
|
5816
|
+
else if (progress === "failed") result.failed++;
|
|
5817
|
+
else result.stillPending++;
|
|
5818
|
+
}
|
|
5819
|
+
}
|
|
5820
|
+
if (result.resolved > 0 || result.failed > 0) {
|
|
5821
|
+
await this.save();
|
|
5822
|
+
}
|
|
5823
|
+
return result;
|
|
5824
|
+
}
|
|
5825
|
+
// ===========================================================================
|
|
5826
|
+
// Private - V5 Lazy Resolution Helpers
|
|
5827
|
+
// ===========================================================================
|
|
5828
|
+
/**
|
|
5829
|
+
* Process a single V5 token through its finalization stages with quick-timeout proof checks.
|
|
5830
|
+
*/
|
|
5831
|
+
async resolveV5Token(tokenId, token, pending2, stClient, trustBase, signingService) {
|
|
5832
|
+
const bundle = JSON.parse(pending2.bundleJson);
|
|
5833
|
+
pending2.attemptCount++;
|
|
5834
|
+
pending2.lastAttemptAt = Date.now();
|
|
5835
|
+
try {
|
|
5836
|
+
if (pending2.stage === "RECEIVED") {
|
|
5837
|
+
const mintDataJson = JSON.parse(bundle.recipientMintData);
|
|
5838
|
+
const mintData = await import_MintTransactionData3.MintTransactionData.fromJSON(mintDataJson);
|
|
5839
|
+
const mintCommitment = await import_MintCommitment3.MintCommitment.create(mintData);
|
|
5840
|
+
const mintResponse = await stClient.submitMintCommitment(mintCommitment);
|
|
5841
|
+
if (mintResponse.status !== "SUCCESS" && mintResponse.status !== "REQUEST_ID_EXISTS") {
|
|
5842
|
+
throw new Error(`Mint submission failed: ${mintResponse.status}`);
|
|
5843
|
+
}
|
|
5844
|
+
pending2.stage = "MINT_SUBMITTED";
|
|
5845
|
+
this.updatePendingFinalization(token, pending2);
|
|
5846
|
+
}
|
|
5847
|
+
if (pending2.stage === "MINT_SUBMITTED") {
|
|
5848
|
+
const mintDataJson = JSON.parse(bundle.recipientMintData);
|
|
5849
|
+
const mintData = await import_MintTransactionData3.MintTransactionData.fromJSON(mintDataJson);
|
|
5850
|
+
const mintCommitment = await import_MintCommitment3.MintCommitment.create(mintData);
|
|
5851
|
+
const proof = await this.quickProofCheck(stClient, trustBase, mintCommitment);
|
|
5852
|
+
if (!proof) {
|
|
5853
|
+
this.updatePendingFinalization(token, pending2);
|
|
5854
|
+
return "pending";
|
|
5855
|
+
}
|
|
5856
|
+
pending2.mintProofJson = JSON.stringify(proof);
|
|
5857
|
+
pending2.stage = "MINT_PROVEN";
|
|
5858
|
+
this.updatePendingFinalization(token, pending2);
|
|
5859
|
+
}
|
|
5860
|
+
if (pending2.stage === "MINT_PROVEN") {
|
|
5861
|
+
const transferCommitmentJson = JSON.parse(bundle.transferCommitment);
|
|
5862
|
+
const transferCommitment = await import_TransferCommitment4.TransferCommitment.fromJSON(transferCommitmentJson);
|
|
5863
|
+
const transferResponse = await stClient.submitTransferCommitment(transferCommitment);
|
|
5864
|
+
if (transferResponse.status !== "SUCCESS" && transferResponse.status !== "REQUEST_ID_EXISTS") {
|
|
5865
|
+
throw new Error(`Transfer submission failed: ${transferResponse.status}`);
|
|
5866
|
+
}
|
|
5867
|
+
pending2.stage = "TRANSFER_SUBMITTED";
|
|
5868
|
+
this.updatePendingFinalization(token, pending2);
|
|
5869
|
+
}
|
|
5870
|
+
if (pending2.stage === "TRANSFER_SUBMITTED") {
|
|
5871
|
+
const transferCommitmentJson = JSON.parse(bundle.transferCommitment);
|
|
5872
|
+
const transferCommitment = await import_TransferCommitment4.TransferCommitment.fromJSON(transferCommitmentJson);
|
|
5873
|
+
const proof = await this.quickProofCheck(stClient, trustBase, transferCommitment);
|
|
5874
|
+
if (!proof) {
|
|
5875
|
+
this.updatePendingFinalization(token, pending2);
|
|
5876
|
+
return "pending";
|
|
5877
|
+
}
|
|
5878
|
+
const finalizedToken = await this.finalizeFromV5Bundle(bundle, pending2, signingService, stClient, trustBase);
|
|
5879
|
+
const confirmedToken = {
|
|
5880
|
+
id: token.id,
|
|
5881
|
+
coinId: token.coinId,
|
|
5882
|
+
symbol: token.symbol,
|
|
5883
|
+
name: token.name,
|
|
5884
|
+
decimals: token.decimals,
|
|
5885
|
+
iconUrl: token.iconUrl,
|
|
5886
|
+
amount: token.amount,
|
|
5887
|
+
status: "confirmed",
|
|
5888
|
+
createdAt: token.createdAt,
|
|
5889
|
+
updatedAt: Date.now(),
|
|
5890
|
+
sdkData: JSON.stringify(finalizedToken.toJSON())
|
|
5891
|
+
};
|
|
5892
|
+
this.tokens.set(tokenId, confirmedToken);
|
|
5893
|
+
await this.saveTokenToFileStorage(confirmedToken);
|
|
5894
|
+
await this.addToHistory({
|
|
5895
|
+
type: "RECEIVED",
|
|
5896
|
+
amount: confirmedToken.amount,
|
|
5897
|
+
coinId: confirmedToken.coinId,
|
|
5898
|
+
symbol: confirmedToken.symbol || "UNK",
|
|
5899
|
+
timestamp: Date.now(),
|
|
5900
|
+
senderPubkey: pending2.senderPubkey
|
|
5901
|
+
});
|
|
5902
|
+
this.log(`V5 token resolved: ${tokenId.slice(0, 8)}...`);
|
|
5903
|
+
return "resolved";
|
|
5904
|
+
}
|
|
5905
|
+
return "pending";
|
|
5906
|
+
} catch (error) {
|
|
5907
|
+
console.error(`[Payments] resolveV5Token failed for ${tokenId.slice(0, 8)}:`, error);
|
|
5908
|
+
if (pending2.attemptCount > 50) {
|
|
5909
|
+
token.status = "invalid";
|
|
5910
|
+
token.updatedAt = Date.now();
|
|
5911
|
+
this.tokens.set(tokenId, token);
|
|
5912
|
+
return "failed";
|
|
5913
|
+
}
|
|
5914
|
+
this.updatePendingFinalization(token, pending2);
|
|
5915
|
+
return "pending";
|
|
5916
|
+
}
|
|
5917
|
+
}
|
|
5918
|
+
/**
|
|
5919
|
+
* Non-blocking proof check with 500ms timeout.
|
|
5920
|
+
*/
|
|
5921
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5922
|
+
async quickProofCheck(stClient, trustBase, commitment, timeoutMs = 500) {
|
|
5923
|
+
try {
|
|
5924
|
+
const proof = await Promise.race([
|
|
5925
|
+
(0, import_InclusionProofUtils5.waitInclusionProof)(trustBase, stClient, commitment),
|
|
5926
|
+
new Promise((resolve) => setTimeout(() => resolve(null), timeoutMs))
|
|
5927
|
+
]);
|
|
5928
|
+
return proof;
|
|
5929
|
+
} catch {
|
|
5930
|
+
return null;
|
|
5931
|
+
}
|
|
5932
|
+
}
|
|
5933
|
+
/**
|
|
5934
|
+
* Perform V5 bundle finalization from stored bundle data and proofs.
|
|
5935
|
+
* Extracted from InstantSplitProcessor.processV5Bundle() steps 4-10.
|
|
5936
|
+
*/
|
|
5937
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5938
|
+
async finalizeFromV5Bundle(bundle, pending2, signingService, stClient, trustBase) {
|
|
5939
|
+
const mintDataJson = JSON.parse(bundle.recipientMintData);
|
|
5940
|
+
const mintData = await import_MintTransactionData3.MintTransactionData.fromJSON(mintDataJson);
|
|
5941
|
+
const mintCommitment = await import_MintCommitment3.MintCommitment.create(mintData);
|
|
5942
|
+
const mintProofJson = JSON.parse(pending2.mintProofJson);
|
|
5943
|
+
const mintProof = import_InclusionProof.InclusionProof.fromJSON(mintProofJson);
|
|
5944
|
+
const mintTransaction = mintCommitment.toTransaction(mintProof);
|
|
5945
|
+
const tokenType = new import_TokenType3.TokenType(fromHex4(bundle.tokenTypeHex));
|
|
5946
|
+
const senderMintedStateJson = JSON.parse(bundle.mintedTokenStateJson);
|
|
5947
|
+
const tokenJson = {
|
|
5948
|
+
version: "2.0",
|
|
5949
|
+
state: senderMintedStateJson,
|
|
5950
|
+
genesis: mintTransaction.toJSON(),
|
|
5951
|
+
transactions: [],
|
|
5952
|
+
nametags: []
|
|
5953
|
+
};
|
|
5954
|
+
const mintedToken = await import_Token6.Token.fromJSON(tokenJson);
|
|
5955
|
+
const transferCommitmentJson = JSON.parse(bundle.transferCommitment);
|
|
5956
|
+
const transferCommitment = await import_TransferCommitment4.TransferCommitment.fromJSON(transferCommitmentJson);
|
|
5957
|
+
const transferProof = await (0, import_InclusionProofUtils5.waitInclusionProof)(trustBase, stClient, transferCommitment);
|
|
5958
|
+
const transferTransaction = transferCommitment.toTransaction(transferProof);
|
|
5959
|
+
const transferSalt = fromHex4(bundle.transferSaltHex);
|
|
5960
|
+
const recipientPredicate = await import_UnmaskedPredicate5.UnmaskedPredicate.create(
|
|
5961
|
+
mintData.tokenId,
|
|
5962
|
+
tokenType,
|
|
5963
|
+
signingService,
|
|
5964
|
+
import_HashAlgorithm5.HashAlgorithm.SHA256,
|
|
5965
|
+
transferSalt
|
|
5966
|
+
);
|
|
5967
|
+
const recipientState = new import_TokenState5.TokenState(recipientPredicate, null);
|
|
5968
|
+
let nametagTokens = [];
|
|
5969
|
+
const recipientAddressStr = bundle.recipientAddressJson;
|
|
5970
|
+
if (recipientAddressStr.startsWith("PROXY://")) {
|
|
5971
|
+
if (bundle.nametagTokenJson) {
|
|
5972
|
+
try {
|
|
5973
|
+
const nametagToken = await import_Token6.Token.fromJSON(JSON.parse(bundle.nametagTokenJson));
|
|
5974
|
+
const { ProxyAddress } = await import("@unicitylabs/state-transition-sdk/lib/address/ProxyAddress");
|
|
5975
|
+
const proxy = await ProxyAddress.fromTokenId(nametagToken.id);
|
|
5976
|
+
if (proxy.address === recipientAddressStr) {
|
|
5977
|
+
nametagTokens = [nametagToken];
|
|
5978
|
+
}
|
|
5979
|
+
} catch {
|
|
5980
|
+
}
|
|
5981
|
+
}
|
|
5982
|
+
if (nametagTokens.length === 0 && this.nametag?.token) {
|
|
5983
|
+
try {
|
|
5984
|
+
const nametagToken = await import_Token6.Token.fromJSON(this.nametag.token);
|
|
5985
|
+
const { ProxyAddress } = await import("@unicitylabs/state-transition-sdk/lib/address/ProxyAddress");
|
|
5986
|
+
const proxy = await ProxyAddress.fromTokenId(nametagToken.id);
|
|
5987
|
+
if (proxy.address === recipientAddressStr) {
|
|
5988
|
+
nametagTokens = [nametagToken];
|
|
5989
|
+
}
|
|
5990
|
+
} catch {
|
|
5991
|
+
}
|
|
5992
|
+
}
|
|
5993
|
+
}
|
|
5994
|
+
return stClient.finalizeTransaction(trustBase, mintedToken, recipientState, transferTransaction, nametagTokens);
|
|
5995
|
+
}
|
|
5996
|
+
/**
|
|
5997
|
+
* Parse pending finalization metadata from token's sdkData.
|
|
5998
|
+
*/
|
|
5999
|
+
parsePendingFinalization(sdkData) {
|
|
6000
|
+
if (!sdkData) return null;
|
|
6001
|
+
try {
|
|
6002
|
+
const data = JSON.parse(sdkData);
|
|
6003
|
+
if (data._pendingFinalization && data._pendingFinalization.type === "v5_bundle") {
|
|
6004
|
+
return data._pendingFinalization;
|
|
6005
|
+
}
|
|
6006
|
+
return null;
|
|
6007
|
+
} catch {
|
|
6008
|
+
return null;
|
|
6009
|
+
}
|
|
6010
|
+
}
|
|
6011
|
+
/**
|
|
6012
|
+
* Update pending finalization metadata in token's sdkData.
|
|
6013
|
+
* Creates a new token object since sdkData is readonly.
|
|
6014
|
+
*/
|
|
6015
|
+
updatePendingFinalization(token, pending2) {
|
|
6016
|
+
const updated = {
|
|
6017
|
+
id: token.id,
|
|
6018
|
+
coinId: token.coinId,
|
|
6019
|
+
symbol: token.symbol,
|
|
6020
|
+
name: token.name,
|
|
6021
|
+
decimals: token.decimals,
|
|
6022
|
+
iconUrl: token.iconUrl,
|
|
6023
|
+
amount: token.amount,
|
|
6024
|
+
status: token.status,
|
|
6025
|
+
createdAt: token.createdAt,
|
|
6026
|
+
updatedAt: Date.now(),
|
|
6027
|
+
sdkData: JSON.stringify({ _pendingFinalization: pending2 })
|
|
6028
|
+
};
|
|
6029
|
+
this.tokens.set(token.id, updated);
|
|
6030
|
+
}
|
|
6031
|
+
/**
|
|
6032
|
+
* Save pending V5 tokens to key-value storage.
|
|
6033
|
+
* These tokens can't be serialized to TXF format (no genesis/state),
|
|
6034
|
+
* so we persist them separately and restore on load().
|
|
6035
|
+
*/
|
|
6036
|
+
async savePendingV5Tokens() {
|
|
6037
|
+
const pendingTokens = [];
|
|
6038
|
+
for (const token of this.tokens.values()) {
|
|
6039
|
+
if (this.parsePendingFinalization(token.sdkData)) {
|
|
6040
|
+
pendingTokens.push(token);
|
|
6041
|
+
}
|
|
6042
|
+
}
|
|
6043
|
+
if (pendingTokens.length > 0) {
|
|
6044
|
+
await this.deps.storage.set(
|
|
6045
|
+
STORAGE_KEYS_ADDRESS.PENDING_V5_TOKENS,
|
|
6046
|
+
JSON.stringify(pendingTokens)
|
|
6047
|
+
);
|
|
6048
|
+
} else {
|
|
6049
|
+
await this.deps.storage.set(STORAGE_KEYS_ADDRESS.PENDING_V5_TOKENS, "");
|
|
6050
|
+
}
|
|
6051
|
+
}
|
|
6052
|
+
/**
|
|
6053
|
+
* Load pending V5 tokens from key-value storage and merge into tokens map.
|
|
6054
|
+
* Called during load() to restore tokens that TXF format can't represent.
|
|
6055
|
+
*/
|
|
6056
|
+
async loadPendingV5Tokens() {
|
|
6057
|
+
const data = await this.deps.storage.get(STORAGE_KEYS_ADDRESS.PENDING_V5_TOKENS);
|
|
6058
|
+
if (!data) return;
|
|
6059
|
+
try {
|
|
6060
|
+
const pendingTokens = JSON.parse(data);
|
|
6061
|
+
for (const token of pendingTokens) {
|
|
6062
|
+
if (!this.tokens.has(token.id)) {
|
|
6063
|
+
this.tokens.set(token.id, token);
|
|
6064
|
+
}
|
|
6065
|
+
}
|
|
6066
|
+
if (pendingTokens.length > 0) {
|
|
6067
|
+
this.log(`Restored ${pendingTokens.length} pending V5 token(s)`);
|
|
6068
|
+
}
|
|
6069
|
+
} catch {
|
|
6070
|
+
}
|
|
6071
|
+
}
|
|
6072
|
+
// ===========================================================================
|
|
5385
6073
|
// Public API - Token Operations
|
|
5386
6074
|
// ===========================================================================
|
|
5387
6075
|
/**
|
|
5388
|
-
* Add a token
|
|
5389
|
-
*
|
|
5390
|
-
*
|
|
5391
|
-
*
|
|
6076
|
+
* Add a token to the wallet.
|
|
6077
|
+
*
|
|
6078
|
+
* Tokens are uniquely identified by a `(tokenId, stateHash)` composite key.
|
|
6079
|
+
* Duplicate detection:
|
|
6080
|
+
* - **Tombstoned** — rejected if the exact `(tokenId, stateHash)` pair has a tombstone.
|
|
6081
|
+
* - **Exact duplicate** — rejected if a token with the same composite key already exists.
|
|
6082
|
+
* - **State replacement** — if the same `tokenId` exists with a *different* `stateHash`,
|
|
6083
|
+
* the old state is archived and replaced with the incoming one.
|
|
6084
|
+
*
|
|
6085
|
+
* @param token - The token to add.
|
|
6086
|
+
* @param skipHistory - When `true`, do not create a `RECEIVED` transaction history entry (default `false`).
|
|
6087
|
+
* @returns `true` if the token was added, `false` if rejected as duplicate or tombstoned.
|
|
5392
6088
|
*/
|
|
5393
6089
|
async addToken(token, skipHistory = false) {
|
|
5394
6090
|
this.ensureInitialized();
|
|
@@ -5446,7 +6142,9 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5446
6142
|
});
|
|
5447
6143
|
}
|
|
5448
6144
|
await this.save();
|
|
5449
|
-
|
|
6145
|
+
if (!this.parsePendingFinalization(token.sdkData)) {
|
|
6146
|
+
await this.saveTokenToFileStorage(token);
|
|
6147
|
+
}
|
|
5450
6148
|
this.log(`Added token ${token.id}, total: ${this.tokens.size}`);
|
|
5451
6149
|
return true;
|
|
5452
6150
|
}
|
|
@@ -5503,6 +6201,9 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5503
6201
|
const data = fileData;
|
|
5504
6202
|
const tokenJson = data.token;
|
|
5505
6203
|
if (!tokenJson) continue;
|
|
6204
|
+
if (typeof tokenJson === "object" && tokenJson !== null && "_pendingFinalization" in tokenJson) {
|
|
6205
|
+
continue;
|
|
6206
|
+
}
|
|
5506
6207
|
let sdkTokenId;
|
|
5507
6208
|
if (typeof tokenJson === "object" && tokenJson !== null) {
|
|
5508
6209
|
const tokenObj = tokenJson;
|
|
@@ -5554,7 +6255,12 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5554
6255
|
this.log(`Loaded ${this.tokens.size} tokens from file storage`);
|
|
5555
6256
|
}
|
|
5556
6257
|
/**
|
|
5557
|
-
* Update an existing token
|
|
6258
|
+
* Update an existing token or add it if not found.
|
|
6259
|
+
*
|
|
6260
|
+
* Looks up the token by genesis `tokenId` (from `sdkData`) first, then by
|
|
6261
|
+
* `token.id`. If no match is found, falls back to {@link addToken}.
|
|
6262
|
+
*
|
|
6263
|
+
* @param token - The token with updated data. Must include a valid `id`.
|
|
5558
6264
|
*/
|
|
5559
6265
|
async updateToken(token) {
|
|
5560
6266
|
this.ensureInitialized();
|
|
@@ -5578,7 +6284,15 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5578
6284
|
this.log(`Updated token ${token.id}`);
|
|
5579
6285
|
}
|
|
5580
6286
|
/**
|
|
5581
|
-
* Remove a token
|
|
6287
|
+
* Remove a token from the wallet.
|
|
6288
|
+
*
|
|
6289
|
+
* The token is archived first, then a tombstone `(tokenId, stateHash)` is
|
|
6290
|
+
* created to prevent re-addition via Nostr re-delivery. A `SENT` history
|
|
6291
|
+
* entry is created unless `skipHistory` is `true`.
|
|
6292
|
+
*
|
|
6293
|
+
* @param tokenId - Local UUID of the token to remove.
|
|
6294
|
+
* @param recipientNametag - Optional nametag of the transfer recipient (for history).
|
|
6295
|
+
* @param skipHistory - When `true`, skip creating a transaction history entry (default `false`).
|
|
5582
6296
|
*/
|
|
5583
6297
|
async removeToken(tokenId, recipientNametag, skipHistory = false) {
|
|
5584
6298
|
this.ensureInitialized();
|
|
@@ -5640,13 +6354,22 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5640
6354
|
// Public API - Tombstones
|
|
5641
6355
|
// ===========================================================================
|
|
5642
6356
|
/**
|
|
5643
|
-
* Get all
|
|
6357
|
+
* Get all tombstone entries.
|
|
6358
|
+
*
|
|
6359
|
+
* Each tombstone is keyed by `(tokenId, stateHash)` and prevents a spent
|
|
6360
|
+
* token state from being re-added (e.g. via Nostr re-delivery).
|
|
6361
|
+
*
|
|
6362
|
+
* @returns A shallow copy of the tombstone array.
|
|
5644
6363
|
*/
|
|
5645
6364
|
getTombstones() {
|
|
5646
6365
|
return [...this.tombstones];
|
|
5647
6366
|
}
|
|
5648
6367
|
/**
|
|
5649
|
-
* Check
|
|
6368
|
+
* Check whether a specific `(tokenId, stateHash)` combination is tombstoned.
|
|
6369
|
+
*
|
|
6370
|
+
* @param tokenId - The genesis token ID.
|
|
6371
|
+
* @param stateHash - The state hash of the token version to check.
|
|
6372
|
+
* @returns `true` if the exact combination has been tombstoned.
|
|
5650
6373
|
*/
|
|
5651
6374
|
isStateTombstoned(tokenId, stateHash) {
|
|
5652
6375
|
return this.tombstones.some(
|
|
@@ -5654,8 +6377,13 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5654
6377
|
);
|
|
5655
6378
|
}
|
|
5656
6379
|
/**
|
|
5657
|
-
* Merge remote
|
|
5658
|
-
*
|
|
6380
|
+
* Merge tombstones received from a remote sync source.
|
|
6381
|
+
*
|
|
6382
|
+
* Any local token whose `(tokenId, stateHash)` matches a remote tombstone is
|
|
6383
|
+
* removed. The remote tombstones are then added to the local set (union merge).
|
|
6384
|
+
*
|
|
6385
|
+
* @param remoteTombstones - Tombstone entries from the remote source.
|
|
6386
|
+
* @returns Number of local tokens that were removed.
|
|
5659
6387
|
*/
|
|
5660
6388
|
async mergeTombstones(remoteTombstones) {
|
|
5661
6389
|
this.ensureInitialized();
|
|
@@ -5691,7 +6419,9 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5691
6419
|
return removedCount;
|
|
5692
6420
|
}
|
|
5693
6421
|
/**
|
|
5694
|
-
*
|
|
6422
|
+
* Remove tombstones older than `maxAge` and cap the list at 100 entries.
|
|
6423
|
+
*
|
|
6424
|
+
* @param maxAge - Maximum age in milliseconds (default: 30 days).
|
|
5695
6425
|
*/
|
|
5696
6426
|
async pruneTombstones(maxAge) {
|
|
5697
6427
|
const originalCount = this.tombstones.length;
|
|
@@ -5705,20 +6435,38 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5705
6435
|
// Public API - Archives
|
|
5706
6436
|
// ===========================================================================
|
|
5707
6437
|
/**
|
|
5708
|
-
* Get archived tokens
|
|
6438
|
+
* Get all archived (spent/superseded) tokens in TXF format.
|
|
6439
|
+
*
|
|
6440
|
+
* Archived tokens are kept for recovery and sync purposes. The map key is
|
|
6441
|
+
* the genesis token ID.
|
|
6442
|
+
*
|
|
6443
|
+
* @returns A shallow copy of the archived token map.
|
|
5709
6444
|
*/
|
|
5710
6445
|
getArchivedTokens() {
|
|
5711
6446
|
return new Map(this.archivedTokens);
|
|
5712
6447
|
}
|
|
5713
6448
|
/**
|
|
5714
|
-
* Get best archived version of a token
|
|
6449
|
+
* Get the best (most committed transactions) archived version of a token.
|
|
6450
|
+
*
|
|
6451
|
+
* Searches both archived and forked token maps and returns the version with
|
|
6452
|
+
* the highest number of committed transactions.
|
|
6453
|
+
*
|
|
6454
|
+
* @param tokenId - The genesis token ID to look up.
|
|
6455
|
+
* @returns The best TXF token version, or `null` if not found.
|
|
5715
6456
|
*/
|
|
5716
6457
|
getBestArchivedVersion(tokenId) {
|
|
5717
6458
|
return findBestTokenVersion(tokenId, this.archivedTokens, this.forkedTokens);
|
|
5718
6459
|
}
|
|
5719
6460
|
/**
|
|
5720
|
-
* Merge remote
|
|
5721
|
-
*
|
|
6461
|
+
* Merge archived tokens from a remote sync source.
|
|
6462
|
+
*
|
|
6463
|
+
* For each remote token:
|
|
6464
|
+
* - If missing locally, it is added.
|
|
6465
|
+
* - If the remote version is an incremental update of the local, it replaces it.
|
|
6466
|
+
* - If the histories diverge (fork), the remote version is stored via {@link storeForkedToken}.
|
|
6467
|
+
*
|
|
6468
|
+
* @param remoteArchived - Map of genesis token ID → TXF token from remote.
|
|
6469
|
+
* @returns Number of tokens that were updated or added locally.
|
|
5722
6470
|
*/
|
|
5723
6471
|
async mergeArchivedTokens(remoteArchived) {
|
|
5724
6472
|
let mergedCount = 0;
|
|
@@ -5741,7 +6489,11 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5741
6489
|
return mergedCount;
|
|
5742
6490
|
}
|
|
5743
6491
|
/**
|
|
5744
|
-
* Prune archived tokens
|
|
6492
|
+
* Prune archived tokens to keep at most `maxCount` entries.
|
|
6493
|
+
*
|
|
6494
|
+
* Oldest entries (by insertion order) are removed first.
|
|
6495
|
+
*
|
|
6496
|
+
* @param maxCount - Maximum number of archived tokens to retain (default: 100).
|
|
5745
6497
|
*/
|
|
5746
6498
|
async pruneArchivedTokens(maxCount = 100) {
|
|
5747
6499
|
if (this.archivedTokens.size <= maxCount) return;
|
|
@@ -5754,13 +6506,24 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5754
6506
|
// Public API - Forked Tokens
|
|
5755
6507
|
// ===========================================================================
|
|
5756
6508
|
/**
|
|
5757
|
-
* Get forked
|
|
6509
|
+
* Get all forked token versions.
|
|
6510
|
+
*
|
|
6511
|
+
* Forked tokens represent alternative histories detected during sync.
|
|
6512
|
+
* The map key is `{tokenId}_{stateHash}`.
|
|
6513
|
+
*
|
|
6514
|
+
* @returns A shallow copy of the forked tokens map.
|
|
5758
6515
|
*/
|
|
5759
6516
|
getForkedTokens() {
|
|
5760
6517
|
return new Map(this.forkedTokens);
|
|
5761
6518
|
}
|
|
5762
6519
|
/**
|
|
5763
|
-
* Store a forked token
|
|
6520
|
+
* Store a forked token version (alternative history).
|
|
6521
|
+
*
|
|
6522
|
+
* No-op if the exact `(tokenId, stateHash)` key already exists.
|
|
6523
|
+
*
|
|
6524
|
+
* @param tokenId - Genesis token ID.
|
|
6525
|
+
* @param stateHash - State hash of this forked version.
|
|
6526
|
+
* @param txfToken - The TXF token data to store.
|
|
5764
6527
|
*/
|
|
5765
6528
|
async storeForkedToken(tokenId, stateHash, txfToken) {
|
|
5766
6529
|
const key = `${tokenId}_${stateHash}`;
|
|
@@ -5770,8 +6533,10 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5770
6533
|
await this.save();
|
|
5771
6534
|
}
|
|
5772
6535
|
/**
|
|
5773
|
-
* Merge remote
|
|
5774
|
-
*
|
|
6536
|
+
* Merge forked tokens from a remote sync source. Only new keys are added.
|
|
6537
|
+
*
|
|
6538
|
+
* @param remoteForked - Map of `{tokenId}_{stateHash}` → TXF token from remote.
|
|
6539
|
+
* @returns Number of new forked tokens added.
|
|
5775
6540
|
*/
|
|
5776
6541
|
async mergeForkedTokens(remoteForked) {
|
|
5777
6542
|
let addedCount = 0;
|
|
@@ -5787,7 +6552,9 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5787
6552
|
return addedCount;
|
|
5788
6553
|
}
|
|
5789
6554
|
/**
|
|
5790
|
-
* Prune forked tokens
|
|
6555
|
+
* Prune forked tokens to keep at most `maxCount` entries.
|
|
6556
|
+
*
|
|
6557
|
+
* @param maxCount - Maximum number of forked tokens to retain (default: 50).
|
|
5791
6558
|
*/
|
|
5792
6559
|
async pruneForkedTokens(maxCount = 50) {
|
|
5793
6560
|
if (this.forkedTokens.size <= maxCount) return;
|
|
@@ -5800,13 +6567,19 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5800
6567
|
// Public API - Transaction History
|
|
5801
6568
|
// ===========================================================================
|
|
5802
6569
|
/**
|
|
5803
|
-
* Get transaction history
|
|
6570
|
+
* Get the transaction history sorted newest-first.
|
|
6571
|
+
*
|
|
6572
|
+
* @returns Array of {@link TransactionHistoryEntry} objects in descending timestamp order.
|
|
5804
6573
|
*/
|
|
5805
6574
|
getHistory() {
|
|
5806
6575
|
return [...this.transactionHistory].sort((a, b) => b.timestamp - a.timestamp);
|
|
5807
6576
|
}
|
|
5808
6577
|
/**
|
|
5809
|
-
*
|
|
6578
|
+
* Append an entry to the transaction history.
|
|
6579
|
+
*
|
|
6580
|
+
* A unique `id` is auto-generated. The entry is immediately persisted to storage.
|
|
6581
|
+
*
|
|
6582
|
+
* @param entry - History entry fields (without `id`).
|
|
5810
6583
|
*/
|
|
5811
6584
|
async addToHistory(entry) {
|
|
5812
6585
|
this.ensureInitialized();
|
|
@@ -5824,7 +6597,11 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5824
6597
|
// Public API - Nametag
|
|
5825
6598
|
// ===========================================================================
|
|
5826
6599
|
/**
|
|
5827
|
-
* Set nametag for current identity
|
|
6600
|
+
* Set the nametag data for the current identity.
|
|
6601
|
+
*
|
|
6602
|
+
* Persists to both key-value storage and file storage (lottery compatibility).
|
|
6603
|
+
*
|
|
6604
|
+
* @param nametag - The nametag data including minted token JSON.
|
|
5828
6605
|
*/
|
|
5829
6606
|
async setNametag(nametag) {
|
|
5830
6607
|
this.ensureInitialized();
|
|
@@ -5834,19 +6611,23 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5834
6611
|
this.log(`Nametag set: ${nametag.name}`);
|
|
5835
6612
|
}
|
|
5836
6613
|
/**
|
|
5837
|
-
* Get nametag
|
|
6614
|
+
* Get the current nametag data.
|
|
6615
|
+
*
|
|
6616
|
+
* @returns The nametag data, or `null` if no nametag is set.
|
|
5838
6617
|
*/
|
|
5839
6618
|
getNametag() {
|
|
5840
6619
|
return this.nametag;
|
|
5841
6620
|
}
|
|
5842
6621
|
/**
|
|
5843
|
-
* Check
|
|
6622
|
+
* Check whether a nametag is currently set.
|
|
6623
|
+
*
|
|
6624
|
+
* @returns `true` if nametag data is present.
|
|
5844
6625
|
*/
|
|
5845
6626
|
hasNametag() {
|
|
5846
6627
|
return this.nametag !== null;
|
|
5847
6628
|
}
|
|
5848
6629
|
/**
|
|
5849
|
-
*
|
|
6630
|
+
* Remove the current nametag data from memory and storage.
|
|
5850
6631
|
*/
|
|
5851
6632
|
async clearNametag() {
|
|
5852
6633
|
this.ensureInitialized();
|
|
@@ -5940,9 +6721,9 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
5940
6721
|
try {
|
|
5941
6722
|
const signingService = await this.createSigningService();
|
|
5942
6723
|
const { UnmaskedPredicateReference: UnmaskedPredicateReference4 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
|
|
5943
|
-
const { TokenType:
|
|
6724
|
+
const { TokenType: TokenType6 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenType");
|
|
5944
6725
|
const UNICITY_TOKEN_TYPE_HEX3 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
|
|
5945
|
-
const tokenType = new
|
|
6726
|
+
const tokenType = new TokenType6(Buffer.from(UNICITY_TOKEN_TYPE_HEX3, "hex"));
|
|
5946
6727
|
const addressRef = await UnmaskedPredicateReference4.create(
|
|
5947
6728
|
tokenType,
|
|
5948
6729
|
signingService.algorithm,
|
|
@@ -6003,11 +6784,27 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6003
6784
|
// Public API - Sync & Validate
|
|
6004
6785
|
// ===========================================================================
|
|
6005
6786
|
/**
|
|
6006
|
-
* Sync with all token storage providers (IPFS,
|
|
6007
|
-
*
|
|
6787
|
+
* Sync local token state with all configured token storage providers (IPFS, file, etc.).
|
|
6788
|
+
*
|
|
6789
|
+
* For each provider, the local data is packaged into TXF storage format, sent
|
|
6790
|
+
* to the provider's `sync()` method, and the merged result is applied locally.
|
|
6791
|
+
* Emits `sync:started`, `sync:completed`, and `sync:error` events.
|
|
6792
|
+
*
|
|
6793
|
+
* @returns Summary with counts of tokens added and removed during sync.
|
|
6008
6794
|
*/
|
|
6009
6795
|
async sync() {
|
|
6010
6796
|
this.ensureInitialized();
|
|
6797
|
+
if (this._syncInProgress) {
|
|
6798
|
+
return this._syncInProgress;
|
|
6799
|
+
}
|
|
6800
|
+
this._syncInProgress = this._doSync();
|
|
6801
|
+
try {
|
|
6802
|
+
return await this._syncInProgress;
|
|
6803
|
+
} finally {
|
|
6804
|
+
this._syncInProgress = null;
|
|
6805
|
+
}
|
|
6806
|
+
}
|
|
6807
|
+
async _doSync() {
|
|
6011
6808
|
this.deps.emitEvent("sync:started", { source: "payments" });
|
|
6012
6809
|
try {
|
|
6013
6810
|
const providers = this.getTokenStorageProviders();
|
|
@@ -6045,6 +6842,9 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6045
6842
|
});
|
|
6046
6843
|
}
|
|
6047
6844
|
}
|
|
6845
|
+
if (totalAdded > 0 || totalRemoved > 0) {
|
|
6846
|
+
await this.save();
|
|
6847
|
+
}
|
|
6048
6848
|
this.deps.emitEvent("sync:completed", {
|
|
6049
6849
|
source: "payments",
|
|
6050
6850
|
count: this.tokens.size
|
|
@@ -6058,6 +6858,66 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6058
6858
|
throw error;
|
|
6059
6859
|
}
|
|
6060
6860
|
}
|
|
6861
|
+
// ===========================================================================
|
|
6862
|
+
// Storage Event Subscription (Push-Based Sync)
|
|
6863
|
+
// ===========================================================================
|
|
6864
|
+
/**
|
|
6865
|
+
* Subscribe to 'storage:remote-updated' events from all token storage providers.
|
|
6866
|
+
* When a provider emits this event, a debounced sync is triggered.
|
|
6867
|
+
*/
|
|
6868
|
+
subscribeToStorageEvents() {
|
|
6869
|
+
this.unsubscribeStorageEvents();
|
|
6870
|
+
const providers = this.getTokenStorageProviders();
|
|
6871
|
+
for (const [providerId, provider] of providers) {
|
|
6872
|
+
if (provider.onEvent) {
|
|
6873
|
+
const unsub = provider.onEvent((event) => {
|
|
6874
|
+
if (event.type === "storage:remote-updated") {
|
|
6875
|
+
this.log("Remote update detected from provider", providerId, event.data);
|
|
6876
|
+
this.debouncedSyncFromRemoteUpdate(providerId, event.data);
|
|
6877
|
+
}
|
|
6878
|
+
});
|
|
6879
|
+
this.storageEventUnsubscribers.push(unsub);
|
|
6880
|
+
}
|
|
6881
|
+
}
|
|
6882
|
+
}
|
|
6883
|
+
/**
|
|
6884
|
+
* Unsubscribe from all storage provider events and clear debounce timer.
|
|
6885
|
+
*/
|
|
6886
|
+
unsubscribeStorageEvents() {
|
|
6887
|
+
for (const unsub of this.storageEventUnsubscribers) {
|
|
6888
|
+
unsub();
|
|
6889
|
+
}
|
|
6890
|
+
this.storageEventUnsubscribers = [];
|
|
6891
|
+
if (this.syncDebounceTimer) {
|
|
6892
|
+
clearTimeout(this.syncDebounceTimer);
|
|
6893
|
+
this.syncDebounceTimer = null;
|
|
6894
|
+
}
|
|
6895
|
+
}
|
|
6896
|
+
/**
|
|
6897
|
+
* Debounced sync triggered by a storage:remote-updated event.
|
|
6898
|
+
* Waits 500ms to batch rapid updates, then performs sync.
|
|
6899
|
+
*/
|
|
6900
|
+
debouncedSyncFromRemoteUpdate(providerId, eventData) {
|
|
6901
|
+
if (this.syncDebounceTimer) {
|
|
6902
|
+
clearTimeout(this.syncDebounceTimer);
|
|
6903
|
+
}
|
|
6904
|
+
this.syncDebounceTimer = setTimeout(() => {
|
|
6905
|
+
this.syncDebounceTimer = null;
|
|
6906
|
+
this.sync().then((result) => {
|
|
6907
|
+
const data = eventData;
|
|
6908
|
+
this.deps?.emitEvent("sync:remote-update", {
|
|
6909
|
+
providerId,
|
|
6910
|
+
name: data?.name ?? "",
|
|
6911
|
+
sequence: data?.sequence ?? 0,
|
|
6912
|
+
cid: data?.cid ?? "",
|
|
6913
|
+
added: result.added,
|
|
6914
|
+
removed: result.removed
|
|
6915
|
+
});
|
|
6916
|
+
}).catch((err) => {
|
|
6917
|
+
this.log("Auto-sync from remote update failed:", err);
|
|
6918
|
+
});
|
|
6919
|
+
}, _PaymentsModule.SYNC_DEBOUNCE_MS);
|
|
6920
|
+
}
|
|
6061
6921
|
/**
|
|
6062
6922
|
* Get all active token storage providers
|
|
6063
6923
|
*/
|
|
@@ -6073,15 +6933,24 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6073
6933
|
return /* @__PURE__ */ new Map();
|
|
6074
6934
|
}
|
|
6075
6935
|
/**
|
|
6076
|
-
*
|
|
6936
|
+
* Replace the set of token storage providers at runtime.
|
|
6937
|
+
*
|
|
6938
|
+
* Use when providers are added or removed dynamically (e.g. IPFS node started).
|
|
6939
|
+
*
|
|
6940
|
+
* @param providers - New map of provider ID → TokenStorageProvider.
|
|
6077
6941
|
*/
|
|
6078
6942
|
updateTokenStorageProviders(providers) {
|
|
6079
6943
|
if (this.deps) {
|
|
6080
6944
|
this.deps.tokenStorageProviders = providers;
|
|
6945
|
+
this.subscribeToStorageEvents();
|
|
6081
6946
|
}
|
|
6082
6947
|
}
|
|
6083
6948
|
/**
|
|
6084
|
-
* Validate tokens
|
|
6949
|
+
* Validate all tokens against the aggregator (oracle provider).
|
|
6950
|
+
*
|
|
6951
|
+
* Tokens that fail validation or are detected as spent are marked `'invalid'`.
|
|
6952
|
+
*
|
|
6953
|
+
* @returns Object with arrays of valid and invalid tokens.
|
|
6085
6954
|
*/
|
|
6086
6955
|
async validate() {
|
|
6087
6956
|
this.ensureInitialized();
|
|
@@ -6102,7 +6971,9 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6102
6971
|
return { valid, invalid };
|
|
6103
6972
|
}
|
|
6104
6973
|
/**
|
|
6105
|
-
* Get pending transfers
|
|
6974
|
+
* Get all in-progress (pending) outgoing transfers.
|
|
6975
|
+
*
|
|
6976
|
+
* @returns Array of {@link TransferResult} objects for transfers that have not yet completed.
|
|
6106
6977
|
*/
|
|
6107
6978
|
getPendingTransfers() {
|
|
6108
6979
|
return Array.from(this.pendingTransfers.values());
|
|
@@ -6166,9 +7037,9 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6166
7037
|
*/
|
|
6167
7038
|
async createDirectAddressFromPubkey(pubkeyHex) {
|
|
6168
7039
|
const { UnmaskedPredicateReference: UnmaskedPredicateReference4 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
|
|
6169
|
-
const { TokenType:
|
|
7040
|
+
const { TokenType: TokenType6 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenType");
|
|
6170
7041
|
const UNICITY_TOKEN_TYPE_HEX3 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
|
|
6171
|
-
const tokenType = new
|
|
7042
|
+
const tokenType = new TokenType6(Buffer.from(UNICITY_TOKEN_TYPE_HEX3, "hex"));
|
|
6172
7043
|
const pubkeyBytes = new Uint8Array(
|
|
6173
7044
|
pubkeyHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
|
|
6174
7045
|
);
|
|
@@ -6380,7 +7251,8 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6380
7251
|
this.deps.emitEvent("transfer:confirmed", {
|
|
6381
7252
|
id: crypto.randomUUID(),
|
|
6382
7253
|
status: "completed",
|
|
6383
|
-
tokens: [finalizedToken]
|
|
7254
|
+
tokens: [finalizedToken],
|
|
7255
|
+
tokenTransfers: []
|
|
6384
7256
|
});
|
|
6385
7257
|
await this.addToHistory({
|
|
6386
7258
|
type: "RECEIVED",
|
|
@@ -6403,14 +7275,26 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6403
7275
|
async handleIncomingTransfer(transfer) {
|
|
6404
7276
|
try {
|
|
6405
7277
|
const payload = transfer.payload;
|
|
7278
|
+
let instantBundle = null;
|
|
6406
7279
|
if (isInstantSplitBundle(payload)) {
|
|
7280
|
+
instantBundle = payload;
|
|
7281
|
+
} else if (payload.token) {
|
|
7282
|
+
try {
|
|
7283
|
+
const inner = typeof payload.token === "string" ? JSON.parse(payload.token) : payload.token;
|
|
7284
|
+
if (isInstantSplitBundle(inner)) {
|
|
7285
|
+
instantBundle = inner;
|
|
7286
|
+
}
|
|
7287
|
+
} catch {
|
|
7288
|
+
}
|
|
7289
|
+
}
|
|
7290
|
+
if (instantBundle) {
|
|
6407
7291
|
this.log("Processing INSTANT_SPLIT bundle...");
|
|
6408
7292
|
try {
|
|
6409
7293
|
if (!this.nametag) {
|
|
6410
7294
|
await this.loadNametagFromFileStorage();
|
|
6411
7295
|
}
|
|
6412
7296
|
const result = await this.processInstantSplitBundle(
|
|
6413
|
-
|
|
7297
|
+
instantBundle,
|
|
6414
7298
|
transfer.senderTransportPubkey
|
|
6415
7299
|
);
|
|
6416
7300
|
if (result.success) {
|
|
@@ -6423,6 +7307,11 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6423
7307
|
}
|
|
6424
7308
|
return;
|
|
6425
7309
|
}
|
|
7310
|
+
if (payload.sourceToken && payload.commitmentData && !payload.transferTx) {
|
|
7311
|
+
this.log("Processing NOSTR-FIRST commitment-only transfer...");
|
|
7312
|
+
await this.handleCommitmentOnlyTransfer(transfer, payload);
|
|
7313
|
+
return;
|
|
7314
|
+
}
|
|
6426
7315
|
let tokenData;
|
|
6427
7316
|
let finalizedSdkToken = null;
|
|
6428
7317
|
if (payload.sourceToken && payload.transferTx) {
|
|
@@ -6578,6 +7467,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6578
7467
|
console.error(`[Payments] Failed to save to provider ${id}:`, err);
|
|
6579
7468
|
}
|
|
6580
7469
|
}
|
|
7470
|
+
await this.savePendingV5Tokens();
|
|
6581
7471
|
}
|
|
6582
7472
|
async saveToOutbox(transfer, recipient) {
|
|
6583
7473
|
const outbox = await this.loadOutbox();
|
|
@@ -6595,8 +7485,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
6595
7485
|
}
|
|
6596
7486
|
async createStorageData() {
|
|
6597
7487
|
return await buildTxfStorageData(
|
|
6598
|
-
|
|
6599
|
-
// Empty - active tokens stored as token-xxx files
|
|
7488
|
+
Array.from(this.tokens.values()),
|
|
6600
7489
|
{
|
|
6601
7490
|
version: 1,
|
|
6602
7491
|
address: this.deps.identity.l1Address,
|
|
@@ -6781,7 +7670,7 @@ function createPaymentsModule(config) {
|
|
|
6781
7670
|
// modules/payments/TokenRecoveryService.ts
|
|
6782
7671
|
var import_TokenId4 = require("@unicitylabs/state-transition-sdk/lib/token/TokenId");
|
|
6783
7672
|
var import_TokenState6 = require("@unicitylabs/state-transition-sdk/lib/token/TokenState");
|
|
6784
|
-
var
|
|
7673
|
+
var import_TokenType4 = require("@unicitylabs/state-transition-sdk/lib/token/TokenType");
|
|
6785
7674
|
var import_CoinId5 = require("@unicitylabs/state-transition-sdk/lib/token/fungible/CoinId");
|
|
6786
7675
|
var import_HashAlgorithm6 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
|
|
6787
7676
|
var import_UnmaskedPredicate6 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate");
|
|
@@ -7845,15 +8734,20 @@ async function parseAndDecryptWalletDat(data, password, onProgress) {
|
|
|
7845
8734
|
|
|
7846
8735
|
// core/Sphere.ts
|
|
7847
8736
|
var import_SigningService2 = require("@unicitylabs/state-transition-sdk/lib/sign/SigningService");
|
|
7848
|
-
var
|
|
8737
|
+
var import_TokenType5 = require("@unicitylabs/state-transition-sdk/lib/token/TokenType");
|
|
7849
8738
|
var import_HashAlgorithm7 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
|
|
7850
8739
|
var import_UnmaskedPredicateReference3 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
|
|
8740
|
+
var import_nostr_js_sdk2 = require("@unicitylabs/nostr-js-sdk");
|
|
8741
|
+
function isValidNametag(nametag) {
|
|
8742
|
+
if ((0, import_nostr_js_sdk2.isPhoneNumber)(nametag)) return true;
|
|
8743
|
+
return /^[a-z0-9_-]{3,20}$/.test(nametag);
|
|
8744
|
+
}
|
|
7851
8745
|
var UNICITY_TOKEN_TYPE_HEX2 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
|
|
7852
8746
|
async function deriveL3PredicateAddress(privateKey) {
|
|
7853
8747
|
const secret = Buffer.from(privateKey, "hex");
|
|
7854
8748
|
const signingService = await import_SigningService2.SigningService.createFromSecret(secret);
|
|
7855
8749
|
const tokenTypeBytes = Buffer.from(UNICITY_TOKEN_TYPE_HEX2, "hex");
|
|
7856
|
-
const tokenType = new
|
|
8750
|
+
const tokenType = new import_TokenType5.TokenType(tokenTypeBytes);
|
|
7857
8751
|
const predicateRef = import_UnmaskedPredicateReference3.UnmaskedPredicateReference.create(
|
|
7858
8752
|
tokenType,
|
|
7859
8753
|
signingService.algorithm,
|
|
@@ -8019,8 +8913,8 @@ var Sphere = class _Sphere {
|
|
|
8019
8913
|
if (options.nametag) {
|
|
8020
8914
|
await sphere.registerNametag(options.nametag);
|
|
8021
8915
|
} else {
|
|
8022
|
-
await sphere.syncIdentityWithTransport();
|
|
8023
8916
|
await sphere.recoverNametagFromTransport();
|
|
8917
|
+
await sphere.syncIdentityWithTransport();
|
|
8024
8918
|
}
|
|
8025
8919
|
return sphere;
|
|
8026
8920
|
}
|
|
@@ -8067,9 +8961,14 @@ var Sphere = class _Sphere {
|
|
|
8067
8961
|
if (!options.mnemonic && !options.masterKey) {
|
|
8068
8962
|
throw new Error("Either mnemonic or masterKey is required");
|
|
8069
8963
|
}
|
|
8964
|
+
console.log("[Sphere.import] Starting import...");
|
|
8965
|
+
console.log("[Sphere.import] Clearing existing wallet data...");
|
|
8070
8966
|
await _Sphere.clear({ storage: options.storage, tokenStorage: options.tokenStorage });
|
|
8967
|
+
console.log("[Sphere.import] Clear done");
|
|
8071
8968
|
if (!options.storage.isConnected()) {
|
|
8969
|
+
console.log("[Sphere.import] Reconnecting storage...");
|
|
8072
8970
|
await options.storage.connect();
|
|
8971
|
+
console.log("[Sphere.import] Storage reconnected");
|
|
8073
8972
|
}
|
|
8074
8973
|
const sphere = new _Sphere(
|
|
8075
8974
|
options.storage,
|
|
@@ -8083,9 +8982,12 @@ var Sphere = class _Sphere {
|
|
|
8083
8982
|
if (!_Sphere.validateMnemonic(options.mnemonic)) {
|
|
8084
8983
|
throw new Error("Invalid mnemonic");
|
|
8085
8984
|
}
|
|
8985
|
+
console.log("[Sphere.import] Storing mnemonic...");
|
|
8086
8986
|
await sphere.storeMnemonic(options.mnemonic, options.derivationPath, options.basePath);
|
|
8987
|
+
console.log("[Sphere.import] Initializing identity from mnemonic...");
|
|
8087
8988
|
await sphere.initializeIdentityFromMnemonic(options.mnemonic, options.derivationPath);
|
|
8088
8989
|
} else if (options.masterKey) {
|
|
8990
|
+
console.log("[Sphere.import] Storing master key...");
|
|
8089
8991
|
await sphere.storeMasterKey(
|
|
8090
8992
|
options.masterKey,
|
|
8091
8993
|
options.chainCode,
|
|
@@ -8093,24 +8995,43 @@ var Sphere = class _Sphere {
|
|
|
8093
8995
|
options.basePath,
|
|
8094
8996
|
options.derivationMode
|
|
8095
8997
|
);
|
|
8998
|
+
console.log("[Sphere.import] Initializing identity from master key...");
|
|
8096
8999
|
await sphere.initializeIdentityFromMasterKey(
|
|
8097
9000
|
options.masterKey,
|
|
8098
9001
|
options.chainCode,
|
|
8099
9002
|
options.derivationPath
|
|
8100
9003
|
);
|
|
8101
9004
|
}
|
|
9005
|
+
console.log("[Sphere.import] Initializing providers...");
|
|
8102
9006
|
await sphere.initializeProviders();
|
|
9007
|
+
console.log("[Sphere.import] Providers initialized. Initializing modules...");
|
|
8103
9008
|
await sphere.initializeModules();
|
|
9009
|
+
console.log("[Sphere.import] Modules initialized");
|
|
8104
9010
|
if (!options.nametag) {
|
|
9011
|
+
console.log("[Sphere.import] Recovering nametag from transport...");
|
|
8105
9012
|
await sphere.recoverNametagFromTransport();
|
|
9013
|
+
console.log("[Sphere.import] Nametag recovery done");
|
|
9014
|
+
await sphere.syncIdentityWithTransport();
|
|
8106
9015
|
}
|
|
9016
|
+
console.log("[Sphere.import] Finalizing wallet creation...");
|
|
8107
9017
|
await sphere.finalizeWalletCreation();
|
|
8108
9018
|
sphere._initialized = true;
|
|
8109
9019
|
_Sphere.instance = sphere;
|
|
9020
|
+
console.log("[Sphere.import] Tracking address 0...");
|
|
8110
9021
|
await sphere.ensureAddressTracked(0);
|
|
8111
9022
|
if (options.nametag) {
|
|
9023
|
+
console.log("[Sphere.import] Registering nametag...");
|
|
8112
9024
|
await sphere.registerNametag(options.nametag);
|
|
8113
9025
|
}
|
|
9026
|
+
if (sphere._tokenStorageProviders.size > 0) {
|
|
9027
|
+
try {
|
|
9028
|
+
const syncResult = await sphere._payments.sync();
|
|
9029
|
+
console.log(`[Sphere.import] Auto-sync: +${syncResult.added} -${syncResult.removed}`);
|
|
9030
|
+
} catch (err) {
|
|
9031
|
+
console.warn("[Sphere.import] Auto-sync failed (non-fatal):", err);
|
|
9032
|
+
}
|
|
9033
|
+
}
|
|
9034
|
+
console.log("[Sphere.import] Import complete");
|
|
8114
9035
|
return sphere;
|
|
8115
9036
|
}
|
|
8116
9037
|
/**
|
|
@@ -8135,6 +9056,10 @@ var Sphere = class _Sphere {
|
|
|
8135
9056
|
static async clear(storageOrOptions) {
|
|
8136
9057
|
const storage = "get" in storageOrOptions ? storageOrOptions : storageOrOptions.storage;
|
|
8137
9058
|
const tokenStorage = "get" in storageOrOptions ? void 0 : storageOrOptions.tokenStorage;
|
|
9059
|
+
if (!storage.isConnected()) {
|
|
9060
|
+
await storage.connect();
|
|
9061
|
+
}
|
|
9062
|
+
console.log("[Sphere.clear] Removing storage keys...");
|
|
8138
9063
|
await storage.remove(STORAGE_KEYS_GLOBAL.MNEMONIC);
|
|
8139
9064
|
await storage.remove(STORAGE_KEYS_GLOBAL.MASTER_KEY);
|
|
8140
9065
|
await storage.remove(STORAGE_KEYS_GLOBAL.CHAIN_CODE);
|
|
@@ -8147,12 +9072,30 @@ var Sphere = class _Sphere {
|
|
|
8147
9072
|
await storage.remove(STORAGE_KEYS_GLOBAL.ADDRESS_NAMETAGS);
|
|
8148
9073
|
await storage.remove(STORAGE_KEYS_ADDRESS.PENDING_TRANSFERS);
|
|
8149
9074
|
await storage.remove(STORAGE_KEYS_ADDRESS.OUTBOX);
|
|
9075
|
+
console.log("[Sphere.clear] Storage keys removed");
|
|
8150
9076
|
if (tokenStorage?.clear) {
|
|
8151
|
-
|
|
9077
|
+
console.log("[Sphere.clear] Clearing token storage...");
|
|
9078
|
+
try {
|
|
9079
|
+
await Promise.race([
|
|
9080
|
+
tokenStorage.clear(),
|
|
9081
|
+
new Promise(
|
|
9082
|
+
(_, reject) => setTimeout(() => reject(new Error("tokenStorage.clear() timed out after 2s")), 2e3)
|
|
9083
|
+
)
|
|
9084
|
+
]);
|
|
9085
|
+
console.log("[Sphere.clear] Token storage cleared");
|
|
9086
|
+
} catch (err) {
|
|
9087
|
+
console.warn("[Sphere.clear] Token storage clear failed/timed out:", err);
|
|
9088
|
+
}
|
|
8152
9089
|
}
|
|
9090
|
+
console.log("[Sphere.clear] Destroying vesting classifier...");
|
|
8153
9091
|
await vestingClassifier.destroy();
|
|
9092
|
+
console.log("[Sphere.clear] Vesting classifier destroyed");
|
|
8154
9093
|
if (_Sphere.instance) {
|
|
9094
|
+
console.log("[Sphere.clear] Destroying Sphere instance...");
|
|
8155
9095
|
await _Sphere.instance.destroy();
|
|
9096
|
+
console.log("[Sphere.clear] Sphere instance destroyed");
|
|
9097
|
+
} else {
|
|
9098
|
+
console.log("[Sphere.clear] No Sphere instance to destroy");
|
|
8156
9099
|
}
|
|
8157
9100
|
}
|
|
8158
9101
|
/**
|
|
@@ -8533,7 +9476,8 @@ var Sphere = class _Sphere {
|
|
|
8533
9476
|
storage: options.storage,
|
|
8534
9477
|
transport: options.transport,
|
|
8535
9478
|
oracle: options.oracle,
|
|
8536
|
-
tokenStorage: options.tokenStorage
|
|
9479
|
+
tokenStorage: options.tokenStorage,
|
|
9480
|
+
l1: options.l1
|
|
8537
9481
|
});
|
|
8538
9482
|
return { success: true, mnemonic };
|
|
8539
9483
|
}
|
|
@@ -8546,7 +9490,8 @@ var Sphere = class _Sphere {
|
|
|
8546
9490
|
storage: options.storage,
|
|
8547
9491
|
transport: options.transport,
|
|
8548
9492
|
oracle: options.oracle,
|
|
8549
|
-
tokenStorage: options.tokenStorage
|
|
9493
|
+
tokenStorage: options.tokenStorage,
|
|
9494
|
+
l1: options.l1
|
|
8550
9495
|
});
|
|
8551
9496
|
return { success: true };
|
|
8552
9497
|
}
|
|
@@ -8605,7 +9550,8 @@ var Sphere = class _Sphere {
|
|
|
8605
9550
|
transport: options.transport,
|
|
8606
9551
|
oracle: options.oracle,
|
|
8607
9552
|
tokenStorage: options.tokenStorage,
|
|
8608
|
-
nametag: options.nametag
|
|
9553
|
+
nametag: options.nametag,
|
|
9554
|
+
l1: options.l1
|
|
8609
9555
|
});
|
|
8610
9556
|
return { success: true, sphere, mnemonic };
|
|
8611
9557
|
}
|
|
@@ -8634,7 +9580,8 @@ var Sphere = class _Sphere {
|
|
|
8634
9580
|
transport: options.transport,
|
|
8635
9581
|
oracle: options.oracle,
|
|
8636
9582
|
tokenStorage: options.tokenStorage,
|
|
8637
|
-
nametag: options.nametag
|
|
9583
|
+
nametag: options.nametag,
|
|
9584
|
+
l1: options.l1
|
|
8638
9585
|
});
|
|
8639
9586
|
return { success: true, sphere };
|
|
8640
9587
|
}
|
|
@@ -8665,7 +9612,8 @@ var Sphere = class _Sphere {
|
|
|
8665
9612
|
transport: options.transport,
|
|
8666
9613
|
oracle: options.oracle,
|
|
8667
9614
|
tokenStorage: options.tokenStorage,
|
|
8668
|
-
nametag: options.nametag
|
|
9615
|
+
nametag: options.nametag,
|
|
9616
|
+
l1: options.l1
|
|
8669
9617
|
});
|
|
8670
9618
|
return { success: true, sphere };
|
|
8671
9619
|
}
|
|
@@ -8684,7 +9632,8 @@ var Sphere = class _Sphere {
|
|
|
8684
9632
|
storage: options.storage,
|
|
8685
9633
|
transport: options.transport,
|
|
8686
9634
|
oracle: options.oracle,
|
|
8687
|
-
tokenStorage: options.tokenStorage
|
|
9635
|
+
tokenStorage: options.tokenStorage,
|
|
9636
|
+
l1: options.l1
|
|
8688
9637
|
});
|
|
8689
9638
|
if (result.success) {
|
|
8690
9639
|
const sphere2 = _Sphere.getInstance();
|
|
@@ -8733,7 +9682,8 @@ var Sphere = class _Sphere {
|
|
|
8733
9682
|
transport: options.transport,
|
|
8734
9683
|
oracle: options.oracle,
|
|
8735
9684
|
tokenStorage: options.tokenStorage,
|
|
8736
|
-
nametag: options.nametag
|
|
9685
|
+
nametag: options.nametag,
|
|
9686
|
+
l1: options.l1
|
|
8737
9687
|
});
|
|
8738
9688
|
return { success: true, sphere: sphere2, mnemonic };
|
|
8739
9689
|
}
|
|
@@ -8746,7 +9696,8 @@ var Sphere = class _Sphere {
|
|
|
8746
9696
|
transport: options.transport,
|
|
8747
9697
|
oracle: options.oracle,
|
|
8748
9698
|
tokenStorage: options.tokenStorage,
|
|
8749
|
-
nametag: options.nametag
|
|
9699
|
+
nametag: options.nametag,
|
|
9700
|
+
l1: options.l1
|
|
8750
9701
|
});
|
|
8751
9702
|
return { success: true, sphere };
|
|
8752
9703
|
}
|
|
@@ -8950,9 +9901,9 @@ var Sphere = class _Sphere {
|
|
|
8950
9901
|
if (index < 0) {
|
|
8951
9902
|
throw new Error("Address index must be non-negative");
|
|
8952
9903
|
}
|
|
8953
|
-
const newNametag = options?.nametag
|
|
8954
|
-
if (newNametag && !
|
|
8955
|
-
throw new Error("Invalid nametag format. Use alphanumeric
|
|
9904
|
+
const newNametag = options?.nametag ? this.cleanNametag(options.nametag) : void 0;
|
|
9905
|
+
if (newNametag && !isValidNametag(newNametag)) {
|
|
9906
|
+
throw new Error("Invalid nametag format. Use lowercase alphanumeric, underscore, or hyphen (3-20 chars), or a valid phone number.");
|
|
8956
9907
|
}
|
|
8957
9908
|
const addressInfo = this.deriveAddress(index, false);
|
|
8958
9909
|
const ipnsHash = sha256(addressInfo.publicKey, "hex").slice(0, 40);
|
|
@@ -9336,9 +10287,9 @@ var Sphere = class _Sphere {
|
|
|
9336
10287
|
*/
|
|
9337
10288
|
async registerNametag(nametag) {
|
|
9338
10289
|
this.ensureReady();
|
|
9339
|
-
const cleanNametag =
|
|
9340
|
-
if (!
|
|
9341
|
-
throw new Error("Invalid nametag format. Use alphanumeric
|
|
10290
|
+
const cleanNametag = this.cleanNametag(nametag);
|
|
10291
|
+
if (!isValidNametag(cleanNametag)) {
|
|
10292
|
+
throw new Error("Invalid nametag format. Use lowercase alphanumeric, underscore, or hyphen (3-20 chars), or a valid phone number.");
|
|
9342
10293
|
}
|
|
9343
10294
|
if (this._identity?.nametag) {
|
|
9344
10295
|
throw new Error(`Nametag already registered for address ${this._currentAddressIndex}: @${this._identity.nametag}`);
|
|
@@ -9609,46 +10560,49 @@ var Sphere = class _Sphere {
|
|
|
9609
10560
|
if (this._identity?.nametag) {
|
|
9610
10561
|
return;
|
|
9611
10562
|
}
|
|
9612
|
-
|
|
10563
|
+
let recoveredNametag = null;
|
|
10564
|
+
if (this._transport.recoverNametag) {
|
|
10565
|
+
try {
|
|
10566
|
+
recoveredNametag = await this._transport.recoverNametag();
|
|
10567
|
+
} catch {
|
|
10568
|
+
}
|
|
10569
|
+
}
|
|
10570
|
+
if (!recoveredNametag && this._transport.resolveAddressInfo && this._identity?.l1Address) {
|
|
10571
|
+
try {
|
|
10572
|
+
const info = await this._transport.resolveAddressInfo(this._identity.l1Address);
|
|
10573
|
+
if (info?.nametag) {
|
|
10574
|
+
recoveredNametag = info.nametag;
|
|
10575
|
+
}
|
|
10576
|
+
} catch {
|
|
10577
|
+
}
|
|
10578
|
+
}
|
|
10579
|
+
if (!recoveredNametag) {
|
|
9613
10580
|
return;
|
|
9614
10581
|
}
|
|
9615
10582
|
try {
|
|
9616
|
-
|
|
9617
|
-
|
|
9618
|
-
|
|
9619
|
-
|
|
9620
|
-
|
|
9621
|
-
|
|
9622
|
-
|
|
9623
|
-
|
|
9624
|
-
|
|
9625
|
-
nametags = /* @__PURE__ */ new Map();
|
|
9626
|
-
this._addressNametags.set(entry.addressId, nametags);
|
|
9627
|
-
}
|
|
9628
|
-
const nextIndex = nametags.size;
|
|
9629
|
-
nametags.set(nextIndex, recoveredNametag);
|
|
9630
|
-
await this.persistAddressNametags();
|
|
9631
|
-
if (this._transport.publishIdentityBinding) {
|
|
9632
|
-
await this._transport.publishIdentityBinding(
|
|
9633
|
-
this._identity.chainPubkey,
|
|
9634
|
-
this._identity.l1Address,
|
|
9635
|
-
this._identity.directAddress || "",
|
|
9636
|
-
recoveredNametag
|
|
9637
|
-
);
|
|
9638
|
-
}
|
|
9639
|
-
this.emitEvent("nametag:recovered", { nametag: recoveredNametag });
|
|
10583
|
+
if (this._identity) {
|
|
10584
|
+
this._identity.nametag = recoveredNametag;
|
|
10585
|
+
await this._updateCachedProxyAddress();
|
|
10586
|
+
}
|
|
10587
|
+
const entry = await this.ensureAddressTracked(this._currentAddressIndex);
|
|
10588
|
+
let nametags = this._addressNametags.get(entry.addressId);
|
|
10589
|
+
if (!nametags) {
|
|
10590
|
+
nametags = /* @__PURE__ */ new Map();
|
|
10591
|
+
this._addressNametags.set(entry.addressId, nametags);
|
|
9640
10592
|
}
|
|
10593
|
+
const nextIndex = nametags.size;
|
|
10594
|
+
nametags.set(nextIndex, recoveredNametag);
|
|
10595
|
+
await this.persistAddressNametags();
|
|
10596
|
+
this.emitEvent("nametag:recovered", { nametag: recoveredNametag });
|
|
9641
10597
|
} catch {
|
|
9642
10598
|
}
|
|
9643
10599
|
}
|
|
9644
10600
|
/**
|
|
9645
|
-
*
|
|
10601
|
+
* Strip @ prefix and normalize a nametag (lowercase, phone E.164, strip @unicity suffix).
|
|
9646
10602
|
*/
|
|
9647
|
-
|
|
9648
|
-
const
|
|
9649
|
-
|
|
9650
|
-
);
|
|
9651
|
-
return pattern.test(nametag);
|
|
10603
|
+
cleanNametag(raw) {
|
|
10604
|
+
const stripped = raw.startsWith("@") ? raw.slice(1) : raw;
|
|
10605
|
+
return (0, import_nostr_js_sdk2.normalizeNametag)(stripped);
|
|
9652
10606
|
}
|
|
9653
10607
|
// ===========================================================================
|
|
9654
10608
|
// Public Methods - Lifecycle
|
|
@@ -9846,8 +10800,12 @@ var Sphere = class _Sphere {
|
|
|
9846
10800
|
for (const provider of this._tokenStorageProviders.values()) {
|
|
9847
10801
|
provider.setIdentity(this._identity);
|
|
9848
10802
|
}
|
|
9849
|
-
|
|
9850
|
-
|
|
10803
|
+
if (!this._storage.isConnected()) {
|
|
10804
|
+
await this._storage.connect();
|
|
10805
|
+
}
|
|
10806
|
+
if (!this._transport.isConnected()) {
|
|
10807
|
+
await this._transport.connect();
|
|
10808
|
+
}
|
|
9851
10809
|
await this._oracle.initialize();
|
|
9852
10810
|
for (const provider of this._tokenStorageProviders.values()) {
|
|
9853
10811
|
await provider.initialize();
|
|
@@ -10344,6 +11302,9 @@ function createTokenValidator(options) {
|
|
|
10344
11302
|
return new TokenValidator(options);
|
|
10345
11303
|
}
|
|
10346
11304
|
|
|
11305
|
+
// index.ts
|
|
11306
|
+
var import_nostr_js_sdk3 = require("@unicitylabs/nostr-js-sdk");
|
|
11307
|
+
|
|
10347
11308
|
// price/CoinGeckoPriceProvider.ts
|
|
10348
11309
|
var CoinGeckoPriceProvider = class {
|
|
10349
11310
|
platform = "coingecko";
|
|
@@ -10480,6 +11441,7 @@ function createPriceProvider(config) {
|
|
|
10480
11441
|
TokenRegistry,
|
|
10481
11442
|
TokenValidator,
|
|
10482
11443
|
archivedKeyFromTokenId,
|
|
11444
|
+
areSameNametag,
|
|
10483
11445
|
base58Decode,
|
|
10484
11446
|
base58Encode,
|
|
10485
11447
|
buildTxfStorageData,
|
|
@@ -10527,6 +11489,7 @@ function createPriceProvider(config) {
|
|
|
10527
11489
|
hasUncommittedTransactions,
|
|
10528
11490
|
hasValidTxfData,
|
|
10529
11491
|
hash160,
|
|
11492
|
+
hashNametag,
|
|
10530
11493
|
hexToBytes,
|
|
10531
11494
|
identityFromMnemonicSync,
|
|
10532
11495
|
initSphere,
|
|
@@ -10538,10 +11501,12 @@ function createPriceProvider(config) {
|
|
|
10538
11501
|
isKnownToken,
|
|
10539
11502
|
isPaymentSessionTerminal,
|
|
10540
11503
|
isPaymentSessionTimedOut,
|
|
11504
|
+
isPhoneNumber,
|
|
10541
11505
|
isSQLiteDatabase,
|
|
10542
11506
|
isTextWalletEncrypted,
|
|
10543
11507
|
isTokenKey,
|
|
10544
11508
|
isValidBech32,
|
|
11509
|
+
isValidNametag,
|
|
10545
11510
|
isValidPrivateKey,
|
|
10546
11511
|
isValidTokenId,
|
|
10547
11512
|
isWalletDatEncrypted,
|
|
@@ -10549,6 +11514,7 @@ function createPriceProvider(config) {
|
|
|
10549
11514
|
keyFromTokenId,
|
|
10550
11515
|
loadSphere,
|
|
10551
11516
|
mnemonicToSeedSync,
|
|
11517
|
+
normalizeNametag,
|
|
10552
11518
|
normalizeSdkTokenToStorage,
|
|
10553
11519
|
objectToTxf,
|
|
10554
11520
|
parseAndDecryptWalletDat,
|