@unicitylabs/sphere-sdk 0.7.1-dev.3 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -77
- package/dist/core/index.cjs +541 -37
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +117 -1
- package/dist/core/index.d.ts +117 -1
- package/dist/core/index.js +540 -37
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js.map +1 -1
- package/dist/impl/nodejs/index.cjs.map +1 -1
- package/dist/impl/nodejs/index.js.map +1 -1
- package/dist/index.cjs +669 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +258 -1
- package/dist/index.d.ts +258 -1
- package/dist/index.js +651 -37
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/core/index.js
CHANGED
|
@@ -12084,6 +12084,132 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
12084
12084
|
};
|
|
12085
12085
|
}
|
|
12086
12086
|
}
|
|
12087
|
+
/**
|
|
12088
|
+
* Mint a fungible token directly to this wallet (genesis mint).
|
|
12089
|
+
*
|
|
12090
|
+
* Useful for test setups that need to seed a wallet with specific token
|
|
12091
|
+
* balances WITHOUT depending on the testnet faucet HTTP service. The
|
|
12092
|
+
* resulting token has the canonical CoinId bytes (passed in `coinIdHex`)
|
|
12093
|
+
* — when those bytes match a registered symbol in the TokenRegistry,
|
|
12094
|
+
* the token shows up under the symbol's name (e.g. "UCT"). There is no
|
|
12095
|
+
* cryptographic restriction on which key may issue a given CoinId; the
|
|
12096
|
+
* aggregator records the mint regardless of issuer identity.
|
|
12097
|
+
*
|
|
12098
|
+
* The flow:
|
|
12099
|
+
* 1. Generate a random TokenId.
|
|
12100
|
+
* 2. Build TokenCoinData with [(coinId, amount)].
|
|
12101
|
+
* 3. Build MintTransactionData with recipient = self (UnmaskedPredicate
|
|
12102
|
+
* from this wallet's signing service).
|
|
12103
|
+
* 4. Submit MintCommitment to the aggregator.
|
|
12104
|
+
* 5. Wait for the inclusion proof.
|
|
12105
|
+
* 6. Construct an SDK Token via Token.mint().
|
|
12106
|
+
* 7. Convert to wallet Token format and call addToken().
|
|
12107
|
+
*
|
|
12108
|
+
* @param coinIdHex - 64-char lowercase hex CoinId. Must match the bytes
|
|
12109
|
+
* used by the registered symbol if you want the wallet to recognize
|
|
12110
|
+
* the token as that symbol (e.g. UCT's coinId from the public registry).
|
|
12111
|
+
* @param amount - Amount in smallest units (multiply by 10^decimals
|
|
12112
|
+
* when converting from human values).
|
|
12113
|
+
* @returns Result with the resulting wallet Token and its on-chain id.
|
|
12114
|
+
*/
|
|
12115
|
+
async mintFungibleToken(coinIdHex, amount) {
|
|
12116
|
+
this.ensureInitialized();
|
|
12117
|
+
const stClient = this.deps.oracle.getStateTransitionClient?.();
|
|
12118
|
+
if (!stClient) {
|
|
12119
|
+
return { success: false, error: "State transition client not available" };
|
|
12120
|
+
}
|
|
12121
|
+
const trustBase = this.deps.oracle.getTrustBase?.();
|
|
12122
|
+
if (!trustBase) {
|
|
12123
|
+
return { success: false, error: "Trust base not available" };
|
|
12124
|
+
}
|
|
12125
|
+
try {
|
|
12126
|
+
const signingService = await this.createSigningService();
|
|
12127
|
+
const { TokenId: TokenId5 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenId");
|
|
12128
|
+
const { TokenCoinData: TokenCoinData3 } = await import("@unicitylabs/state-transition-sdk/lib/token/fungible/TokenCoinData");
|
|
12129
|
+
const { UnmaskedPredicateReference: UnmaskedPredicateReference4 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
|
|
12130
|
+
const tokenTypeBytes = fromHex4("f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509");
|
|
12131
|
+
const tokenType = new TokenType3(tokenTypeBytes);
|
|
12132
|
+
const tokenIdBytes = new Uint8Array(32);
|
|
12133
|
+
crypto.getRandomValues(tokenIdBytes);
|
|
12134
|
+
const tokenId = new TokenId5(tokenIdBytes);
|
|
12135
|
+
const coinIdBytes = fromHex4(coinIdHex);
|
|
12136
|
+
const coinId = new CoinId4(coinIdBytes);
|
|
12137
|
+
const coinData = TokenCoinData3.create([[coinId, amount]]);
|
|
12138
|
+
const addressRef = await UnmaskedPredicateReference4.create(
|
|
12139
|
+
tokenType,
|
|
12140
|
+
signingService.algorithm,
|
|
12141
|
+
signingService.publicKey,
|
|
12142
|
+
HashAlgorithm5.SHA256
|
|
12143
|
+
);
|
|
12144
|
+
const ownerAddress = await addressRef.toAddress();
|
|
12145
|
+
const salt = new Uint8Array(32);
|
|
12146
|
+
crypto.getRandomValues(salt);
|
|
12147
|
+
const mintData = await MintTransactionData3.create(
|
|
12148
|
+
tokenId,
|
|
12149
|
+
tokenType,
|
|
12150
|
+
null,
|
|
12151
|
+
// tokenData: no metadata
|
|
12152
|
+
coinData,
|
|
12153
|
+
// fungible coin data
|
|
12154
|
+
ownerAddress,
|
|
12155
|
+
// recipient = self
|
|
12156
|
+
salt,
|
|
12157
|
+
null,
|
|
12158
|
+
// recipientDataHash
|
|
12159
|
+
null
|
|
12160
|
+
// reason: null (genesis, no burn predecessor)
|
|
12161
|
+
);
|
|
12162
|
+
const commitment = await MintCommitment3.create(mintData);
|
|
12163
|
+
const MAX_RETRIES = 3;
|
|
12164
|
+
let lastStatus;
|
|
12165
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
12166
|
+
const response = await stClient.submitMintCommitment(commitment);
|
|
12167
|
+
lastStatus = response.status;
|
|
12168
|
+
if (response.status === "SUCCESS" || response.status === "REQUEST_ID_EXISTS") break;
|
|
12169
|
+
if (attempt === MAX_RETRIES) {
|
|
12170
|
+
return { success: false, error: `Mint submit failed after ${MAX_RETRIES} attempts: ${response.status}` };
|
|
12171
|
+
}
|
|
12172
|
+
await new Promise((r) => setTimeout(r, 1e3 * attempt));
|
|
12173
|
+
}
|
|
12174
|
+
if (lastStatus !== "SUCCESS" && lastStatus !== "REQUEST_ID_EXISTS") {
|
|
12175
|
+
return { success: false, error: `Mint submit failed: ${lastStatus}` };
|
|
12176
|
+
}
|
|
12177
|
+
const inclusionProof = await waitInclusionProof5(trustBase, stClient, commitment);
|
|
12178
|
+
const genesisTransaction = commitment.toTransaction(inclusionProof);
|
|
12179
|
+
const predicate = await UnmaskedPredicate5.create(
|
|
12180
|
+
tokenId,
|
|
12181
|
+
tokenType,
|
|
12182
|
+
signingService,
|
|
12183
|
+
HashAlgorithm5.SHA256,
|
|
12184
|
+
salt
|
|
12185
|
+
);
|
|
12186
|
+
const tokenState = new TokenState5(predicate, null);
|
|
12187
|
+
const sdkToken = await SdkToken2.mint(trustBase, tokenState, genesisTransaction);
|
|
12188
|
+
const tokenIdHex = tokenId.toJSON();
|
|
12189
|
+
const symbol = this.getCoinSymbol(coinIdHex);
|
|
12190
|
+
const name = this.getCoinName(coinIdHex);
|
|
12191
|
+
const decimals = this.getCoinDecimals(coinIdHex);
|
|
12192
|
+
const iconUrl = this.getCoinIconUrl(coinIdHex);
|
|
12193
|
+
const uiToken = {
|
|
12194
|
+
id: tokenIdHex,
|
|
12195
|
+
coinId: coinIdHex,
|
|
12196
|
+
symbol,
|
|
12197
|
+
name,
|
|
12198
|
+
decimals,
|
|
12199
|
+
...iconUrl !== void 0 ? { iconUrl } : {},
|
|
12200
|
+
amount: amount.toString(),
|
|
12201
|
+
status: "confirmed",
|
|
12202
|
+
createdAt: Date.now(),
|
|
12203
|
+
updatedAt: Date.now(),
|
|
12204
|
+
sdkData: JSON.stringify(sdkToken.toJSON())
|
|
12205
|
+
};
|
|
12206
|
+
await this.addToken(uiToken);
|
|
12207
|
+
return { success: true, token: uiToken, tokenId: tokenIdHex };
|
|
12208
|
+
} catch (err) {
|
|
12209
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
12210
|
+
return { success: false, error: `Local mint failed: ${msg}` };
|
|
12211
|
+
}
|
|
12212
|
+
}
|
|
12087
12213
|
/**
|
|
12088
12214
|
* Check if a nametag is available for minting
|
|
12089
12215
|
* @param nametag - The nametag to check (e.g., "alice" or "@alice")
|
|
@@ -18294,6 +18420,24 @@ var AccountingModule = class _AccountingModule {
|
|
|
18294
18420
|
dirtyLedgerEntries = /* @__PURE__ */ new Set();
|
|
18295
18421
|
/** Count of unknown (not in invoiceTermsCache) invoice IDs in the ledger. */
|
|
18296
18422
|
unknownLedgerCount = 0;
|
|
18423
|
+
/**
|
|
18424
|
+
* Per-unknown-invoice first-seen timestamp for TTL eviction.
|
|
18425
|
+
*
|
|
18426
|
+
* W1 (steelman round-4): without TTL, an attacker who can deliver 500
|
|
18427
|
+
* inbound transfers with synthesized memo invoiceIds permanently exhausts
|
|
18428
|
+
* the unknown-ledger cap, after which legitimate orphan transfers (out-of-
|
|
18429
|
+
* order delivery for real swaps) are silently dropped at the cap-check.
|
|
18430
|
+
*
|
|
18431
|
+
* Round-5 perf: gated by `unknownLedgerNextSweepMs` to amortize the
|
|
18432
|
+
* sweep cost. The naive every-call sweep is O(N) where N=cap=500;
|
|
18433
|
+
* combined with the per-token cleanup loop inside the sweep it became
|
|
18434
|
+
* O(N×M) on every transfer under flood. Now we sweep at most every
|
|
18435
|
+
* `UNKNOWN_LEDGER_SWEEP_INTERVAL_MS` (60s) UNLESS the cap is currently
|
|
18436
|
+
* full, in which case we sweep on each call (the only path that can
|
|
18437
|
+
* actually drop a legitimate orphan).
|
|
18438
|
+
*/
|
|
18439
|
+
unknownLedgerFirstSeen = /* @__PURE__ */ new Map();
|
|
18440
|
+
unknownLedgerNextSweepMs = 0;
|
|
18297
18441
|
/** W17: Tracks whether tokenScanState has been mutated since last flush. */
|
|
18298
18442
|
tokenScanDirty = false;
|
|
18299
18443
|
/** W2 fix: Serialization guard for _flushDirtyLedgerEntries. */
|
|
@@ -19284,6 +19428,7 @@ var AccountingModule = class _AccountingModule {
|
|
|
19284
19428
|
}
|
|
19285
19429
|
if (this.invoiceLedger.has(tokenId) && !this.invoiceTermsCache.has(tokenId)) {
|
|
19286
19430
|
this.unknownLedgerCount = Math.max(0, this.unknownLedgerCount - 1);
|
|
19431
|
+
this.unknownLedgerFirstSeen.delete(tokenId);
|
|
19287
19432
|
}
|
|
19288
19433
|
this.invoiceTermsCache.set(tokenId, terms);
|
|
19289
19434
|
this._addToHashIndex(tokenId);
|
|
@@ -19552,6 +19697,29 @@ var AccountingModule = class _AccountingModule {
|
|
|
19552
19697
|
closed: this.closedInvoices.has(invoiceId)
|
|
19553
19698
|
};
|
|
19554
19699
|
}
|
|
19700
|
+
/**
|
|
19701
|
+
* Return the set of token IDs that are currently linked to the given
|
|
19702
|
+
* invoice. Populated by both the on-chain `_processTokenTransactions`
|
|
19703
|
+
* path (tokens with `inv:` references) and the transport-memo orphan
|
|
19704
|
+
* buffering path in `_handleIncomingTransfer`.
|
|
19705
|
+
*
|
|
19706
|
+
* Used by callers that want to scope per-invoice operations (e.g.
|
|
19707
|
+
* SwapModule.verifyPayout's L3 validation) to only the tokens that
|
|
19708
|
+
* cover this invoice — avoiding false negatives when the wallet
|
|
19709
|
+
* contains unrelated tokens of the same currency in unconfirmed or
|
|
19710
|
+
* spent state.
|
|
19711
|
+
*
|
|
19712
|
+
* Returns an empty set if no tokens are currently linked.
|
|
19713
|
+
*/
|
|
19714
|
+
getTokenIdsForInvoice(invoiceId) {
|
|
19715
|
+
const result = /* @__PURE__ */ new Set();
|
|
19716
|
+
for (const [tokenId, invoiceIds] of this.tokenInvoiceMap) {
|
|
19717
|
+
if (invoiceIds.has(invoiceId)) {
|
|
19718
|
+
result.add(tokenId);
|
|
19719
|
+
}
|
|
19720
|
+
}
|
|
19721
|
+
return result;
|
|
19722
|
+
}
|
|
19555
19723
|
/**
|
|
19556
19724
|
* Explicitly close an invoice. Only target parties may close (§8.3).
|
|
19557
19725
|
*
|
|
@@ -19871,6 +20039,7 @@ var AccountingModule = class _AccountingModule {
|
|
|
19871
20039
|
ledger.set(entryKey, forwardRef);
|
|
19872
20040
|
this.dirtyLedgerEntries.add(invoiceId);
|
|
19873
20041
|
this.balanceCache.delete(invoiceId);
|
|
20042
|
+
await this._persistProvisionalAndVerify(invoiceId, "payInvoice");
|
|
19874
20043
|
}
|
|
19875
20044
|
return result;
|
|
19876
20045
|
} finally {
|
|
@@ -20038,6 +20207,7 @@ var AccountingModule = class _AccountingModule {
|
|
|
20038
20207
|
this.dirtyLedgerEntries.add(invoiceId);
|
|
20039
20208
|
}
|
|
20040
20209
|
this.balanceCache.delete(invoiceId);
|
|
20210
|
+
await this._persistProvisionalAndVerify(invoiceId, "returnInvoicePayment");
|
|
20041
20211
|
}
|
|
20042
20212
|
return result;
|
|
20043
20213
|
} finally {
|
|
@@ -21117,9 +21287,30 @@ var AccountingModule = class _AccountingModule {
|
|
|
21117
21287
|
continue;
|
|
21118
21288
|
}
|
|
21119
21289
|
innerMap.set(entryKey, ref);
|
|
21120
|
-
|
|
21290
|
+
const HEX_64 = /^[a-f0-9]{64}$/i;
|
|
21291
|
+
if (entryKey.startsWith("mt:")) {
|
|
21292
|
+
const firstColon = entryKey.indexOf(":");
|
|
21293
|
+
const secondColon = entryKey.indexOf(":", firstColon + 1);
|
|
21294
|
+
if (secondColon > firstColon + 1) {
|
|
21295
|
+
const tokenIdFromKey2 = entryKey.slice(firstColon + 1, secondColon);
|
|
21296
|
+
if (HEX_64.test(tokenIdFromKey2)) {
|
|
21297
|
+
this._addToTokenInvoiceMap(tokenIdFromKey2, invoiceId);
|
|
21298
|
+
}
|
|
21299
|
+
}
|
|
21300
|
+
} else if (entryKey.startsWith("synthetic:")) {
|
|
21301
|
+
const afterPrefix = entryKey.slice("synthetic:".length);
|
|
21302
|
+
const tokenIdEnd = afterPrefix.indexOf(":");
|
|
21303
|
+
if (tokenIdEnd > 0) {
|
|
21304
|
+
const tokenId = afterPrefix.slice(0, tokenIdEnd);
|
|
21305
|
+
if (HEX_64.test(tokenId) && ref.transferId !== tokenId) {
|
|
21306
|
+
this._addToTokenInvoiceMap(tokenId, invoiceId);
|
|
21307
|
+
}
|
|
21308
|
+
}
|
|
21309
|
+
} else if (!ref.transferId.startsWith("provisional:") && ref.transferId.includes(":")) {
|
|
21121
21310
|
const tokenIdFromRef = ref.transferId.slice(0, ref.transferId.indexOf(":"));
|
|
21122
|
-
|
|
21311
|
+
if (HEX_64.test(tokenIdFromRef)) {
|
|
21312
|
+
this._addToTokenInvoiceMap(tokenIdFromRef, invoiceId);
|
|
21313
|
+
}
|
|
21123
21314
|
}
|
|
21124
21315
|
}
|
|
21125
21316
|
} catch (err) {
|
|
@@ -21320,7 +21511,14 @@ var AccountingModule = class _AccountingModule {
|
|
|
21320
21511
|
}
|
|
21321
21512
|
}
|
|
21322
21513
|
for (const [existingKey, existingRef] of ledger) {
|
|
21323
|
-
if (existingKey.startsWith("synthetic:") && existingRef.coinId === coinId && existingRef.paymentDirection === paymentDirection) {
|
|
21514
|
+
if ((existingKey.startsWith("synthetic:") || existingKey.startsWith("synthetic-tx:")) && existingRef.coinId === coinId && existingRef.paymentDirection === paymentDirection) {
|
|
21515
|
+
keysToDelete.push(existingKey);
|
|
21516
|
+
break;
|
|
21517
|
+
}
|
|
21518
|
+
}
|
|
21519
|
+
const mtPrefix = `mt:${tokenId}:`;
|
|
21520
|
+
for (const [existingKey, existingRef] of ledger) {
|
|
21521
|
+
if (existingKey.startsWith(mtPrefix) && existingRef.coinId === coinId && existingRef.paymentDirection === paymentDirection) {
|
|
21324
21522
|
keysToDelete.push(existingKey);
|
|
21325
21523
|
break;
|
|
21326
21524
|
}
|
|
@@ -21437,6 +21635,49 @@ var AccountingModule = class _AccountingModule {
|
|
|
21437
21635
|
});
|
|
21438
21636
|
}
|
|
21439
21637
|
}
|
|
21638
|
+
/**
|
|
21639
|
+
* Synchronously persist any pending provisional ledger entry for `invoiceId`
|
|
21640
|
+
* before returning to the caller. Used by `payInvoice` and
|
|
21641
|
+
* `returnInvoicePayment` to make the in-memory provisional entry durable
|
|
21642
|
+
* inside the same per-invoice gate that wrote it, closing the
|
|
21643
|
+
* crash-mid-conclude race that produces over-coverage on receivers.
|
|
21644
|
+
*
|
|
21645
|
+
* Implementation:
|
|
21646
|
+
* 1. Schedule a flush via the existing `_flushPromise` chain (so
|
|
21647
|
+
* concurrent `_handleTokenChange` callers waiting on the chain
|
|
21648
|
+
* observe ours as part of the sequence).
|
|
21649
|
+
* 2. Await OUR flush directly — NOT `_drainFlushPromise()`, which would
|
|
21650
|
+
* spin while concurrent token changes keep extending the chain and
|
|
21651
|
+
* hold the per-invoice gate for an unbounded number of additional
|
|
21652
|
+
* flushes. We only need OUR provisional entry durable.
|
|
21653
|
+
* 3. `_flushDirtyLedgerEntries` swallows per-invoice `storage.set`
|
|
21654
|
+
* rejections internally (sets a local `step1Failed` flag), leaving
|
|
21655
|
+
* the dirty entry on the set without re-throwing. So we post-check
|
|
21656
|
+
* `dirtyLedgerEntries.has(invoiceId)` and throw a `STORAGE_ERROR`
|
|
21657
|
+
* `SphereError` if our entry is still dirty — propagating to the
|
|
21658
|
+
* caller so they learn about the durability failure rather than
|
|
21659
|
+
* receiving a silent "success" return that lies on disk.
|
|
21660
|
+
*
|
|
21661
|
+
* @param invoiceId The invoice whose provisional entry must be durable.
|
|
21662
|
+
* @param callContext Used in the error message so the caller is named
|
|
21663
|
+
* ('payInvoice' / 'returnInvoicePayment') without
|
|
21664
|
+
* forcing a stack-trace inspection.
|
|
21665
|
+
*/
|
|
21666
|
+
async _persistProvisionalAndVerify(invoiceId, callContext) {
|
|
21667
|
+
const flushTrigger = (this._flushPromise ?? Promise.resolve()).then(() => this._flushDirtyLedgerEntries());
|
|
21668
|
+
const tracked = flushTrigger.catch(() => {
|
|
21669
|
+
}).finally(() => {
|
|
21670
|
+
if (this._flushPromise === tracked) this._flushPromise = null;
|
|
21671
|
+
});
|
|
21672
|
+
this._flushPromise = tracked;
|
|
21673
|
+
await flushTrigger;
|
|
21674
|
+
if (this.dirtyLedgerEntries.has(invoiceId)) {
|
|
21675
|
+
throw new SphereError(
|
|
21676
|
+
`${callContext}: provisional ledger entry for invoice ${invoiceId} failed to persist \u2014 caller should retry`,
|
|
21677
|
+
"STORAGE_ERROR"
|
|
21678
|
+
);
|
|
21679
|
+
}
|
|
21680
|
+
}
|
|
21440
21681
|
// ===========================================================================
|
|
21441
21682
|
// Internal: Event handlers
|
|
21442
21683
|
// ===========================================================================
|
|
@@ -21497,13 +21738,96 @@ var AccountingModule = class _AccountingModule {
|
|
|
21497
21738
|
}
|
|
21498
21739
|
}
|
|
21499
21740
|
if (!this.invoiceTermsCache.has(invoiceId)) {
|
|
21500
|
-
|
|
21501
|
-
|
|
21502
|
-
invoiceId
|
|
21503
|
-
|
|
21504
|
-
|
|
21505
|
-
|
|
21506
|
-
|
|
21741
|
+
let gracefullyGraduated = false;
|
|
21742
|
+
await this.withInvoiceGate(invoiceId, async () => {
|
|
21743
|
+
if (this.invoiceTermsCache.has(invoiceId)) {
|
|
21744
|
+
gracefullyGraduated = true;
|
|
21745
|
+
return;
|
|
21746
|
+
}
|
|
21747
|
+
const syntheticRef = this._buildSyntheticTransferRef(
|
|
21748
|
+
transfer,
|
|
21749
|
+
invoiceId,
|
|
21750
|
+
paymentDirection,
|
|
21751
|
+
confirmed
|
|
21752
|
+
);
|
|
21753
|
+
deps.emitEvent("invoice:unknown_reference", { invoiceId, transfer: syntheticRef });
|
|
21754
|
+
const MAX_UNKNOWN_INVOICE_IDS = 500;
|
|
21755
|
+
const UNKNOWN_LEDGER_TTL_MS = 30 * 60 * 1e3;
|
|
21756
|
+
const MAX_ORPHAN_ENTRIES_PER_INVOICE = 50;
|
|
21757
|
+
const UNKNOWN_LEDGER_SWEEP_INTERVAL_MS = 6e4;
|
|
21758
|
+
const nowMs = Date.now();
|
|
21759
|
+
const capFull = this.unknownLedgerCount >= MAX_UNKNOWN_INVOICE_IDS;
|
|
21760
|
+
const sweepDue = nowMs >= this.unknownLedgerNextSweepMs;
|
|
21761
|
+
if (this.unknownLedgerFirstSeen.size > 0 && (capFull || sweepDue)) {
|
|
21762
|
+
this.unknownLedgerNextSweepMs = nowMs + UNKNOWN_LEDGER_SWEEP_INTERVAL_MS;
|
|
21763
|
+
const expiredIds = [];
|
|
21764
|
+
for (const [unkId, firstSeen] of this.unknownLedgerFirstSeen) {
|
|
21765
|
+
if (nowMs - firstSeen > UNKNOWN_LEDGER_TTL_MS) {
|
|
21766
|
+
expiredIds.push(unkId);
|
|
21767
|
+
}
|
|
21768
|
+
}
|
|
21769
|
+
for (const expiredId of expiredIds) {
|
|
21770
|
+
if (!this.invoiceTermsCache.has(expiredId) && this.invoiceLedger.has(expiredId)) {
|
|
21771
|
+
this.invoiceLedger.delete(expiredId);
|
|
21772
|
+
this.unknownLedgerCount = Math.max(0, this.unknownLedgerCount - 1);
|
|
21773
|
+
for (const [tokenId, invoiceSet] of this.tokenInvoiceMap) {
|
|
21774
|
+
if (invoiceSet.has(expiredId)) {
|
|
21775
|
+
invoiceSet.delete(expiredId);
|
|
21776
|
+
if (invoiceSet.size === 0) this.tokenInvoiceMap.delete(tokenId);
|
|
21777
|
+
}
|
|
21778
|
+
}
|
|
21779
|
+
}
|
|
21780
|
+
this.unknownLedgerFirstSeen.delete(expiredId);
|
|
21781
|
+
}
|
|
21782
|
+
}
|
|
21783
|
+
if (!this.invoiceLedger.has(invoiceId)) {
|
|
21784
|
+
if (this.unknownLedgerCount >= MAX_UNKNOWN_INVOICE_IDS) {
|
|
21785
|
+
return;
|
|
21786
|
+
}
|
|
21787
|
+
this.invoiceLedger.set(invoiceId, /* @__PURE__ */ new Map());
|
|
21788
|
+
this.unknownLedgerCount++;
|
|
21789
|
+
this.unknownLedgerFirstSeen.set(invoiceId, nowMs);
|
|
21790
|
+
}
|
|
21791
|
+
const orphanLedger = this.invoiceLedger.get(invoiceId);
|
|
21792
|
+
let mtEntryCount = 0;
|
|
21793
|
+
for (const k of orphanLedger.keys()) {
|
|
21794
|
+
if (k.startsWith("mt:")) mtEntryCount++;
|
|
21795
|
+
}
|
|
21796
|
+
if (mtEntryCount >= MAX_ORPHAN_ENTRIES_PER_INVOICE) {
|
|
21797
|
+
return;
|
|
21798
|
+
}
|
|
21799
|
+
for (const token of transfer.tokens) {
|
|
21800
|
+
if (!token.id) continue;
|
|
21801
|
+
let onChainAttributed = false;
|
|
21802
|
+
const tokenKeyPrefix = `${token.id}:`;
|
|
21803
|
+
for (const existingKey of orphanLedger.keys()) {
|
|
21804
|
+
if (existingKey.startsWith(tokenKeyPrefix) && !existingKey.startsWith("mt:")) {
|
|
21805
|
+
onChainAttributed = true;
|
|
21806
|
+
break;
|
|
21807
|
+
}
|
|
21808
|
+
}
|
|
21809
|
+
if (!onChainAttributed) {
|
|
21810
|
+
if (mtEntryCount >= MAX_ORPHAN_ENTRIES_PER_INVOICE) {
|
|
21811
|
+
break;
|
|
21812
|
+
}
|
|
21813
|
+
const orphanKey = `mt:${token.id}:${transfer.id}`;
|
|
21814
|
+
if (!orphanLedger.has(orphanKey)) {
|
|
21815
|
+
orphanLedger.set(orphanKey, syntheticRef);
|
|
21816
|
+
mtEntryCount++;
|
|
21817
|
+
}
|
|
21818
|
+
}
|
|
21819
|
+
if (!this.tokenInvoiceMap.has(token.id)) {
|
|
21820
|
+
this.tokenInvoiceMap.set(token.id, /* @__PURE__ */ new Set());
|
|
21821
|
+
}
|
|
21822
|
+
this.tokenInvoiceMap.get(token.id).add(invoiceId);
|
|
21823
|
+
}
|
|
21824
|
+
this.dirtyLedgerEntries.add(invoiceId);
|
|
21825
|
+
this.balanceCache.delete(invoiceId);
|
|
21826
|
+
await this._flushDirtyLedgerEntries();
|
|
21827
|
+
});
|
|
21828
|
+
if (gracefullyGraduated) {
|
|
21829
|
+
await this._processInvoiceTransferEvent(transfer, invoiceId, paymentDirection, confirmed);
|
|
21830
|
+
}
|
|
21507
21831
|
return;
|
|
21508
21832
|
}
|
|
21509
21833
|
await this._processInvoiceTransferEvent(transfer, invoiceId, paymentDirection, confirmed);
|
|
@@ -21939,7 +22263,8 @@ var AccountingModule = class _AccountingModule {
|
|
|
21939
22263
|
}
|
|
21940
22264
|
const existingLedger = this.invoiceLedger.get(invoiceId);
|
|
21941
22265
|
const firstTokenId = transfer.tokens.find((t) => t.id)?.id;
|
|
21942
|
-
const syntheticKey = firstTokenId ? `synthetic:${firstTokenId}::${syntheticRef.coinId}` : `synthetic:${syntheticRef.transferId}::${syntheticRef.coinId}`;
|
|
22266
|
+
const syntheticKey = firstTokenId ? `synthetic:${firstTokenId}::${syntheticRef.coinId}` : `synthetic-tx:${syntheticRef.transferId}::${syntheticRef.coinId}`;
|
|
22267
|
+
let mutated = false;
|
|
21943
22268
|
if (!existingLedger.has(syntheticKey)) {
|
|
21944
22269
|
let hasRealEntry = false;
|
|
21945
22270
|
for (const tok of transfer.tokens) {
|
|
@@ -21954,8 +22279,25 @@ var AccountingModule = class _AccountingModule {
|
|
|
21954
22279
|
}
|
|
21955
22280
|
if (!hasRealEntry) {
|
|
21956
22281
|
existingLedger.set(syntheticKey, { ...syntheticRef });
|
|
22282
|
+
mutated = true;
|
|
21957
22283
|
}
|
|
21958
22284
|
}
|
|
22285
|
+
for (const tok of transfer.tokens) {
|
|
22286
|
+
if (!tok.id) continue;
|
|
22287
|
+
if (!this.tokenInvoiceMap.has(tok.id)) {
|
|
22288
|
+
this.tokenInvoiceMap.set(tok.id, /* @__PURE__ */ new Set());
|
|
22289
|
+
mutated = true;
|
|
22290
|
+
}
|
|
22291
|
+
const beforeSize = this.tokenInvoiceMap.get(tok.id).size;
|
|
22292
|
+
this.tokenInvoiceMap.get(tok.id).add(invoiceId);
|
|
22293
|
+
if (this.tokenInvoiceMap.get(tok.id).size !== beforeSize) {
|
|
22294
|
+
mutated = true;
|
|
22295
|
+
}
|
|
22296
|
+
}
|
|
22297
|
+
if (mutated) {
|
|
22298
|
+
this.dirtyLedgerEntries.add(invoiceId);
|
|
22299
|
+
this.balanceCache.delete(invoiceId);
|
|
22300
|
+
}
|
|
21959
22301
|
deps.emitEvent("invoice:payment", {
|
|
21960
22302
|
invoiceId,
|
|
21961
22303
|
transfer: syntheticRef,
|
|
@@ -22105,7 +22447,8 @@ var AccountingModule = class _AccountingModule {
|
|
|
22105
22447
|
this.invoiceLedger.set(invoiceId, /* @__PURE__ */ new Map());
|
|
22106
22448
|
}
|
|
22107
22449
|
const hLedger = this.invoiceLedger.get(invoiceId);
|
|
22108
|
-
const hKey = entry.tokenId ? `synthetic:${entry.tokenId}::${syntheticRef.coinId}` : `synthetic:${syntheticRef.transferId}::${syntheticRef.coinId}`;
|
|
22450
|
+
const hKey = entry.tokenId ? `synthetic:${entry.tokenId}::${syntheticRef.coinId}` : `synthetic-tx:${syntheticRef.transferId}::${syntheticRef.coinId}`;
|
|
22451
|
+
let hMutated = false;
|
|
22109
22452
|
if (!hLedger.has(hKey)) {
|
|
22110
22453
|
let hasRealEntry = false;
|
|
22111
22454
|
if (entry.tokenId) {
|
|
@@ -22118,8 +22461,24 @@ var AccountingModule = class _AccountingModule {
|
|
|
22118
22461
|
}
|
|
22119
22462
|
if (!hasRealEntry) {
|
|
22120
22463
|
hLedger.set(hKey, { ...syntheticRef });
|
|
22464
|
+
hMutated = true;
|
|
22121
22465
|
}
|
|
22122
22466
|
}
|
|
22467
|
+
if (entry.tokenId) {
|
|
22468
|
+
if (!this.tokenInvoiceMap.has(entry.tokenId)) {
|
|
22469
|
+
this.tokenInvoiceMap.set(entry.tokenId, /* @__PURE__ */ new Set());
|
|
22470
|
+
hMutated = true;
|
|
22471
|
+
}
|
|
22472
|
+
const beforeSize = this.tokenInvoiceMap.get(entry.tokenId).size;
|
|
22473
|
+
this.tokenInvoiceMap.get(entry.tokenId).add(invoiceId);
|
|
22474
|
+
if (this.tokenInvoiceMap.get(entry.tokenId).size !== beforeSize) {
|
|
22475
|
+
hMutated = true;
|
|
22476
|
+
}
|
|
22477
|
+
}
|
|
22478
|
+
if (hMutated) {
|
|
22479
|
+
this.dirtyLedgerEntries.add(invoiceId);
|
|
22480
|
+
this.balanceCache.delete(invoiceId);
|
|
22481
|
+
}
|
|
22123
22482
|
deps.emitEvent("invoice:payment", {
|
|
22124
22483
|
invoiceId,
|
|
22125
22484
|
transfer: syntheticRef,
|
|
@@ -24650,17 +25009,63 @@ var SwapModule = class {
|
|
|
24650
25009
|
for (const addr of allAddresses) {
|
|
24651
25010
|
myDirectAddresses.add(addr.directAddress);
|
|
24652
25011
|
}
|
|
24653
|
-
|
|
24654
|
-
|
|
24655
|
-
|
|
24656
|
-
|
|
24657
|
-
|
|
25012
|
+
const matchesPartyA = myDirectAddresses.has(swap.manifest.party_a_address);
|
|
25013
|
+
const matchesPartyB = myDirectAddresses.has(swap.manifest.party_b_address);
|
|
25014
|
+
if (matchesPartyA && matchesPartyB) {
|
|
25015
|
+
throw new SphereError(
|
|
25016
|
+
"Ambiguous party identity: local wallet matches both party_a_address and party_b_address",
|
|
25017
|
+
"SWAP_DEPOSIT_FAILED"
|
|
25018
|
+
);
|
|
25019
|
+
}
|
|
25020
|
+
let myExpectedCurrency;
|
|
25021
|
+
if (matchesPartyA) {
|
|
25022
|
+
myExpectedCurrency = swap.manifest.party_a_currency_to_change;
|
|
25023
|
+
} else if (matchesPartyB) {
|
|
25024
|
+
myExpectedCurrency = swap.manifest.party_b_currency_to_change;
|
|
24658
25025
|
} else {
|
|
24659
25026
|
throw new SphereError(
|
|
24660
25027
|
"Local wallet address does not match either party in the swap manifest",
|
|
24661
25028
|
"SWAP_DEPOSIT_FAILED"
|
|
24662
25029
|
);
|
|
24663
25030
|
}
|
|
25031
|
+
if (!myExpectedCurrency || myExpectedCurrency === "") {
|
|
25032
|
+
throw new SphereError(
|
|
25033
|
+
"Manifest currency_to_change is empty for this party",
|
|
25034
|
+
"SWAP_DEPOSIT_FAILED"
|
|
25035
|
+
);
|
|
25036
|
+
}
|
|
25037
|
+
const invoiceRefForAssetLookup = deps.accounting.getInvoice(swap.depositInvoiceId);
|
|
25038
|
+
if (!invoiceRefForAssetLookup) {
|
|
25039
|
+
throw new SphereError(
|
|
25040
|
+
"Deposit invoice not yet imported into accounting module",
|
|
25041
|
+
"SWAP_WRONG_STATE"
|
|
25042
|
+
);
|
|
25043
|
+
}
|
|
25044
|
+
const depositTarget = invoiceRefForAssetLookup.terms.targets[0];
|
|
25045
|
+
if (!depositTarget) {
|
|
25046
|
+
throw new SphereError(
|
|
25047
|
+
"Deposit invoice has no targets",
|
|
25048
|
+
"SWAP_DEPOSIT_FAILED"
|
|
25049
|
+
);
|
|
25050
|
+
}
|
|
25051
|
+
const assetIndex = depositTarget.assets.findIndex(
|
|
25052
|
+
(a) => a.coin !== void 0 && coinIdsMatch(a.coin[0], myExpectedCurrency)
|
|
25053
|
+
);
|
|
25054
|
+
if (assetIndex < 0) {
|
|
25055
|
+
throw new SphereError(
|
|
25056
|
+
`No asset matching expected currency ${myExpectedCurrency} found in deposit invoice`,
|
|
25057
|
+
"SWAP_DEPOSIT_FAILED"
|
|
25058
|
+
);
|
|
25059
|
+
}
|
|
25060
|
+
for (let i = assetIndex + 1; i < depositTarget.assets.length; i += 1) {
|
|
25061
|
+
const a = depositTarget.assets[i];
|
|
25062
|
+
if (a?.coin !== void 0 && coinIdsMatch(a.coin[0], myExpectedCurrency)) {
|
|
25063
|
+
throw new SphereError(
|
|
25064
|
+
`Ambiguous asset match in deposit invoice: slots ${assetIndex} and ${i} both match currency ${myExpectedCurrency}`,
|
|
25065
|
+
"SWAP_DEPOSIT_FAILED"
|
|
25066
|
+
);
|
|
25067
|
+
}
|
|
25068
|
+
}
|
|
24664
25069
|
return this.withSwapGate(swapId, async () => {
|
|
24665
25070
|
if (swap.progress !== "announced") {
|
|
24666
25071
|
throw new SphereError(
|
|
@@ -24766,7 +25171,6 @@ var SwapModule = class {
|
|
|
24766
25171
|
swap.updatedAt = Date.now();
|
|
24767
25172
|
this.clearLocalTimer(swap.swapId);
|
|
24768
25173
|
this.terminalSwapIds.add(swap.swapId);
|
|
24769
|
-
const entryIdx = this._storedTerminalEntries.length;
|
|
24770
25174
|
this._storedTerminalEntries.push({
|
|
24771
25175
|
swapId: swap.swapId,
|
|
24772
25176
|
progress: "failed",
|
|
@@ -24781,7 +25185,13 @@ var SwapModule = class {
|
|
|
24781
25185
|
swap.error = prevError;
|
|
24782
25186
|
swap.updatedAt = prevUpdatedAt;
|
|
24783
25187
|
this.terminalSwapIds.delete(swap.swapId);
|
|
24784
|
-
this._storedTerminalEntries.
|
|
25188
|
+
for (let i = this._storedTerminalEntries.length - 1; i >= 0; i--) {
|
|
25189
|
+
const entry = this._storedTerminalEntries[i];
|
|
25190
|
+
if (entry.swapId === swap.swapId && entry.progress === "failed") {
|
|
25191
|
+
this._storedTerminalEntries.splice(i, 1);
|
|
25192
|
+
break;
|
|
25193
|
+
}
|
|
25194
|
+
}
|
|
24785
25195
|
logger.warn(LOG_TAG3, `failPayout: persistSwap failed for ${swapId}; fraud detection will retry on next load:`, persistErr);
|
|
24786
25196
|
throw persistErr;
|
|
24787
25197
|
}
|
|
@@ -24825,9 +25235,25 @@ var SwapModule = class {
|
|
|
24825
25235
|
if (!targetStatus.coinAssets[0].isCovered) {
|
|
24826
25236
|
return returnFalse();
|
|
24827
25237
|
}
|
|
24828
|
-
|
|
25238
|
+
let netCoveredAmount;
|
|
25239
|
+
let expectedAmountBigInt;
|
|
25240
|
+
try {
|
|
25241
|
+
netCoveredAmount = BigInt(targetStatus.coinAssets[0].netCoveredAmount);
|
|
25242
|
+
expectedAmountBigInt = BigInt(expectedAmount);
|
|
25243
|
+
} catch (parseErr) {
|
|
25244
|
+
return failPayout(
|
|
25245
|
+
`MALFORMED_AMOUNT: failed to parse coverage amounts (netCoveredAmount=${targetStatus.coinAssets[0].netCoveredAmount}, expectedAmount=${expectedAmount}): ${parseErr instanceof Error ? parseErr.message : String(parseErr)}`
|
|
25246
|
+
);
|
|
25247
|
+
}
|
|
25248
|
+
if (netCoveredAmount < expectedAmountBigInt) {
|
|
24829
25249
|
return returnFalse();
|
|
24830
25250
|
}
|
|
25251
|
+
if (netCoveredAmount > expectedAmountBigInt) {
|
|
25252
|
+
const surplus = netCoveredAmount - expectedAmountBigInt;
|
|
25253
|
+
return failPayout(
|
|
25254
|
+
`OVER_COVERAGE: net=${netCoveredAmount.toString()}, expected=${expectedAmount}, surplus=${surplus.toString()} \u2014 surplus refund expected via auto-return; settlement halted`
|
|
25255
|
+
);
|
|
25256
|
+
}
|
|
24831
25257
|
const escrowAddr = swap.deal.escrowAddress ?? this.config.defaultEscrowAddress;
|
|
24832
25258
|
if (escrowAddr) {
|
|
24833
25259
|
const escrowPeer = await deps.resolve(escrowAddr);
|
|
@@ -24837,8 +25263,28 @@ var SwapModule = class {
|
|
|
24837
25263
|
}
|
|
24838
25264
|
const validationResult = await deps.payments.validate();
|
|
24839
25265
|
if (validationResult.invalid.length > 0) {
|
|
24840
|
-
|
|
24841
|
-
|
|
25266
|
+
const payoutTokenIds = deps.accounting.getTokenIdsForInvoice?.(swap.payoutInvoiceId) ?? /* @__PURE__ */ new Set();
|
|
25267
|
+
if (payoutTokenIds.size === 0) {
|
|
25268
|
+
logger.warn(
|
|
25269
|
+
LOG_TAG3,
|
|
25270
|
+
`verifyPayout for ${swapId.slice(0, 12)}: ${validationResult.invalid.length} invalid token(s) but tokenInvoiceMap is empty for this payout invoice \u2014 failing closed until reverse index rebuilds`
|
|
25271
|
+
);
|
|
25272
|
+
return returnFalse();
|
|
25273
|
+
}
|
|
25274
|
+
const relevantInvalid = validationResult.invalid.filter(
|
|
25275
|
+
(t) => payoutTokenIds.has(t.id)
|
|
25276
|
+
);
|
|
25277
|
+
if (relevantInvalid.length > 0) {
|
|
25278
|
+
logger.warn(
|
|
25279
|
+
LOG_TAG3,
|
|
25280
|
+
`verifyPayout for ${swapId.slice(0, 12)}: L3 validation found ${relevantInvalid.length} invalid token(s) covering this payout invoice \u2014 retry after wallet sync`
|
|
25281
|
+
);
|
|
25282
|
+
return returnFalse();
|
|
25283
|
+
}
|
|
25284
|
+
logger.debug(
|
|
25285
|
+
LOG_TAG3,
|
|
25286
|
+
`verifyPayout for ${swapId.slice(0, 12)}: ${validationResult.invalid.length} unrelated invalid token(s) ignored (not linked to this payout invoice)`
|
|
25287
|
+
);
|
|
24842
25288
|
}
|
|
24843
25289
|
if (swap.progress === "completed") {
|
|
24844
25290
|
swap.payoutVerified = true;
|
|
@@ -25017,8 +25463,22 @@ var SwapModule = class {
|
|
|
25017
25463
|
* @param dm - The incoming direct message.
|
|
25018
25464
|
*/
|
|
25019
25465
|
handleIncomingDM(dm) {
|
|
25466
|
+
if (dm.content.startsWith("{") && dm.content.includes('"invoice_delivery"')) {
|
|
25467
|
+
logger.warn(
|
|
25468
|
+
LOG_TAG3,
|
|
25469
|
+
`diag_swap_dm_arrived sender=${dm.senderPubkey.slice(0, 16)} length=${dm.content.length}`
|
|
25470
|
+
);
|
|
25471
|
+
}
|
|
25020
25472
|
const parsed = parseSwapDM(dm.content);
|
|
25021
|
-
if (!parsed)
|
|
25473
|
+
if (!parsed) {
|
|
25474
|
+
if (dm.content.startsWith("{") && dm.content.includes('"invoice_delivery"')) {
|
|
25475
|
+
logger.warn(
|
|
25476
|
+
LOG_TAG3,
|
|
25477
|
+
`diag_swap_dm_parse_rejected sender=${dm.senderPubkey.slice(0, 16)} prefix=${dm.content.slice(0, 80)}`
|
|
25478
|
+
);
|
|
25479
|
+
}
|
|
25480
|
+
return;
|
|
25481
|
+
}
|
|
25022
25482
|
void (async () => {
|
|
25023
25483
|
try {
|
|
25024
25484
|
switch (parsed.kind) {
|
|
@@ -25384,16 +25844,43 @@ var SwapModule = class {
|
|
|
25384
25844
|
// invoice_delivery (§12.4.2 + §12.4.3)
|
|
25385
25845
|
// ---------------------------------------------------------------
|
|
25386
25846
|
case "invoice_delivery": {
|
|
25387
|
-
|
|
25847
|
+
logger.warn(
|
|
25848
|
+
LOG_TAG3,
|
|
25849
|
+
`diag_invoice_delivery_received swap_id=${swapId?.slice(0, 16)} sender=${dm.senderPubkey.slice(0, 16)} invoice_type=${msg.invoice_type} invoice_id=${msg.invoice_id?.slice(0, 16)}`
|
|
25850
|
+
);
|
|
25851
|
+
if (!swapId) {
|
|
25852
|
+
logger.warn(LOG_TAG3, "diag_invoice_delivery_dropped reason=no_swap_id");
|
|
25853
|
+
return;
|
|
25854
|
+
}
|
|
25388
25855
|
const swap = this.swaps.get(swapId);
|
|
25389
|
-
if (!swap)
|
|
25390
|
-
|
|
25856
|
+
if (!swap) {
|
|
25857
|
+
logger.warn(
|
|
25858
|
+
LOG_TAG3,
|
|
25859
|
+
`diag_invoice_delivery_dropped reason=swap_not_in_map swap_id=${swapId.slice(0, 16)} known_swap_ids_count=${this.swaps.size}`
|
|
25860
|
+
);
|
|
25861
|
+
return;
|
|
25862
|
+
}
|
|
25863
|
+
if (!this.isFromExpectedEscrow(dm.senderPubkey, swap)) {
|
|
25864
|
+
logger.warn(
|
|
25865
|
+
LOG_TAG3,
|
|
25866
|
+
`diag_invoice_delivery_dropped reason=not_expected_escrow swap_id=${swapId.slice(0, 16)} sender=${dm.senderPubkey.slice(0, 16)} expected_escrow_pubkey=${swap.escrowPubkey?.slice(0, 16)} expected_escrow_addr=${swap.escrowDirectAddress?.slice(0, 24)}`
|
|
25867
|
+
);
|
|
25868
|
+
return;
|
|
25869
|
+
}
|
|
25391
25870
|
const deps = this.deps;
|
|
25392
25871
|
if (msg.invoice_type === "deposit") {
|
|
25872
|
+
logger.warn(
|
|
25873
|
+
LOG_TAG3,
|
|
25874
|
+
`diag_invoice_delivery_proceeding_to_import swap_id=${swapId.slice(0, 16)} progress=${swap.progress} invoice_id=${(msg.invoice_id ?? "").slice(0, 16)}`
|
|
25875
|
+
);
|
|
25393
25876
|
await this.withSwapGate(swapId, async () => {
|
|
25394
25877
|
if (isTerminalProgress(swap.progress)) return;
|
|
25395
25878
|
try {
|
|
25396
25879
|
await deps.accounting.importInvoice(msg.invoice_token);
|
|
25880
|
+
logger.warn(
|
|
25881
|
+
LOG_TAG3,
|
|
25882
|
+
`diag_invoice_imported swap_id=${swapId.slice(0, 16)} invoice_id=${(msg.invoice_id ?? "").slice(0, 16)} type=deposit`
|
|
25883
|
+
);
|
|
25397
25884
|
} catch (err) {
|
|
25398
25885
|
if (err instanceof SphereError && err.code === "INVOICE_ALREADY_EXISTS") {
|
|
25399
25886
|
logger.debug(LOG_TAG3, `Deposit invoice for swap ${swapId} already imported \u2014 relay re-delivery, continuing`);
|
|
@@ -26886,27 +27373,42 @@ async function parseAndDecryptWalletDat(data, password, onProgress) {
|
|
|
26886
27373
|
|
|
26887
27374
|
// core/Sphere.ts
|
|
26888
27375
|
import { SigningService as SigningService2 } from "@unicitylabs/state-transition-sdk/lib/sign/SigningService";
|
|
27376
|
+
import { normalizeNametag as normalizeNametag2, isPhoneNumber } from "@unicitylabs/nostr-js-sdk";
|
|
27377
|
+
|
|
27378
|
+
// core/address-derivation.ts
|
|
26889
27379
|
import { TokenType as TokenType5 } from "@unicitylabs/state-transition-sdk/lib/token/TokenType";
|
|
26890
27380
|
import { HashAlgorithm as HashAlgorithm7 } from "@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm";
|
|
26891
27381
|
import { UnmaskedPredicateReference as UnmaskedPredicateReference3 } from "@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference";
|
|
26892
|
-
|
|
27382
|
+
var UNICITY_TOKEN_TYPE_HEX2 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
|
|
27383
|
+
var COMPRESSED_PUBKEY_RE = /^(02|03)[0-9a-fA-F]{64}$/;
|
|
27384
|
+
async function computeDirectAddressFromChainPubkey(chainPubkey) {
|
|
27385
|
+
if (typeof chainPubkey !== "string" || !COMPRESSED_PUBKEY_RE.test(chainPubkey)) {
|
|
27386
|
+
throw new Error(
|
|
27387
|
+
`computeDirectAddressFromChainPubkey: chainPubkey must be 66-char hex with 02/03 prefix, got "${String(chainPubkey).slice(0, 12)}..."`
|
|
27388
|
+
);
|
|
27389
|
+
}
|
|
27390
|
+
const tokenTypeBytes = Buffer.from(UNICITY_TOKEN_TYPE_HEX2, "hex");
|
|
27391
|
+
const tokenType = new TokenType5(tokenTypeBytes);
|
|
27392
|
+
const publicKeyBytes = Buffer.from(chainPubkey, "hex");
|
|
27393
|
+
const predicateRef = await UnmaskedPredicateReference3.create(
|
|
27394
|
+
tokenType,
|
|
27395
|
+
"secp256k1",
|
|
27396
|
+
publicKeyBytes,
|
|
27397
|
+
HashAlgorithm7.SHA256
|
|
27398
|
+
);
|
|
27399
|
+
return (await predicateRef.toAddress()).toString();
|
|
27400
|
+
}
|
|
27401
|
+
|
|
27402
|
+
// core/Sphere.ts
|
|
26893
27403
|
function isValidNametag2(nametag) {
|
|
26894
27404
|
if (isPhoneNumber(nametag)) return true;
|
|
26895
27405
|
return /^[a-z0-9_-]{3,20}$/.test(nametag);
|
|
26896
27406
|
}
|
|
26897
|
-
var UNICITY_TOKEN_TYPE_HEX2 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
|
|
26898
27407
|
async function deriveL3PredicateAddress(privateKey) {
|
|
26899
27408
|
const secret = Buffer.from(privateKey, "hex");
|
|
26900
27409
|
const signingService = await SigningService2.createFromSecret(secret);
|
|
26901
|
-
const
|
|
26902
|
-
|
|
26903
|
-
const predicateRef = UnmaskedPredicateReference3.create(
|
|
26904
|
-
tokenType,
|
|
26905
|
-
signingService.algorithm,
|
|
26906
|
-
signingService.publicKey,
|
|
26907
|
-
HashAlgorithm7.SHA256
|
|
26908
|
-
);
|
|
26909
|
-
return (await (await predicateRef).toAddress()).toString();
|
|
27410
|
+
const pubkeyHex = Buffer.from(signingService.publicKey).toString("hex");
|
|
27411
|
+
return computeDirectAddressFromChainPubkey(pubkeyHex);
|
|
26910
27412
|
}
|
|
26911
27413
|
var Sphere = class _Sphere {
|
|
26912
27414
|
// Singleton
|
|
@@ -30290,6 +30792,7 @@ export {
|
|
|
30290
30792
|
base58Encode,
|
|
30291
30793
|
bytesToHex3 as bytesToHex,
|
|
30292
30794
|
checkNetworkHealth,
|
|
30795
|
+
computeDirectAddressFromChainPubkey,
|
|
30293
30796
|
computeHash160,
|
|
30294
30797
|
convertBits,
|
|
30295
30798
|
createAddress,
|