@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.cjs
CHANGED
|
@@ -881,6 +881,7 @@ __export(core_exports, {
|
|
|
881
881
|
base58Encode: () => base58Encode,
|
|
882
882
|
bytesToHex: () => bytesToHex3,
|
|
883
883
|
checkNetworkHealth: () => checkNetworkHealth,
|
|
884
|
+
computeDirectAddressFromChainPubkey: () => computeDirectAddressFromChainPubkey,
|
|
884
885
|
computeHash160: () => computeHash160,
|
|
885
886
|
convertBits: () => convertBits,
|
|
886
887
|
createAddress: () => createAddress,
|
|
@@ -12164,6 +12165,132 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
12164
12165
|
};
|
|
12165
12166
|
}
|
|
12166
12167
|
}
|
|
12168
|
+
/**
|
|
12169
|
+
* Mint a fungible token directly to this wallet (genesis mint).
|
|
12170
|
+
*
|
|
12171
|
+
* Useful for test setups that need to seed a wallet with specific token
|
|
12172
|
+
* balances WITHOUT depending on the testnet faucet HTTP service. The
|
|
12173
|
+
* resulting token has the canonical CoinId bytes (passed in `coinIdHex`)
|
|
12174
|
+
* — when those bytes match a registered symbol in the TokenRegistry,
|
|
12175
|
+
* the token shows up under the symbol's name (e.g. "UCT"). There is no
|
|
12176
|
+
* cryptographic restriction on which key may issue a given CoinId; the
|
|
12177
|
+
* aggregator records the mint regardless of issuer identity.
|
|
12178
|
+
*
|
|
12179
|
+
* The flow:
|
|
12180
|
+
* 1. Generate a random TokenId.
|
|
12181
|
+
* 2. Build TokenCoinData with [(coinId, amount)].
|
|
12182
|
+
* 3. Build MintTransactionData with recipient = self (UnmaskedPredicate
|
|
12183
|
+
* from this wallet's signing service).
|
|
12184
|
+
* 4. Submit MintCommitment to the aggregator.
|
|
12185
|
+
* 5. Wait for the inclusion proof.
|
|
12186
|
+
* 6. Construct an SDK Token via Token.mint().
|
|
12187
|
+
* 7. Convert to wallet Token format and call addToken().
|
|
12188
|
+
*
|
|
12189
|
+
* @param coinIdHex - 64-char lowercase hex CoinId. Must match the bytes
|
|
12190
|
+
* used by the registered symbol if you want the wallet to recognize
|
|
12191
|
+
* the token as that symbol (e.g. UCT's coinId from the public registry).
|
|
12192
|
+
* @param amount - Amount in smallest units (multiply by 10^decimals
|
|
12193
|
+
* when converting from human values).
|
|
12194
|
+
* @returns Result with the resulting wallet Token and its on-chain id.
|
|
12195
|
+
*/
|
|
12196
|
+
async mintFungibleToken(coinIdHex, amount) {
|
|
12197
|
+
this.ensureInitialized();
|
|
12198
|
+
const stClient = this.deps.oracle.getStateTransitionClient?.();
|
|
12199
|
+
if (!stClient) {
|
|
12200
|
+
return { success: false, error: "State transition client not available" };
|
|
12201
|
+
}
|
|
12202
|
+
const trustBase = this.deps.oracle.getTrustBase?.();
|
|
12203
|
+
if (!trustBase) {
|
|
12204
|
+
return { success: false, error: "Trust base not available" };
|
|
12205
|
+
}
|
|
12206
|
+
try {
|
|
12207
|
+
const signingService = await this.createSigningService();
|
|
12208
|
+
const { TokenId: TokenId5 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenId");
|
|
12209
|
+
const { TokenCoinData: TokenCoinData3 } = await import("@unicitylabs/state-transition-sdk/lib/token/fungible/TokenCoinData");
|
|
12210
|
+
const { UnmaskedPredicateReference: UnmaskedPredicateReference4 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
|
|
12211
|
+
const tokenTypeBytes = fromHex4("f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509");
|
|
12212
|
+
const tokenType = new import_TokenType3.TokenType(tokenTypeBytes);
|
|
12213
|
+
const tokenIdBytes = new Uint8Array(32);
|
|
12214
|
+
crypto.getRandomValues(tokenIdBytes);
|
|
12215
|
+
const tokenId = new TokenId5(tokenIdBytes);
|
|
12216
|
+
const coinIdBytes = fromHex4(coinIdHex);
|
|
12217
|
+
const coinId = new import_CoinId4.CoinId(coinIdBytes);
|
|
12218
|
+
const coinData = TokenCoinData3.create([[coinId, amount]]);
|
|
12219
|
+
const addressRef = await UnmaskedPredicateReference4.create(
|
|
12220
|
+
tokenType,
|
|
12221
|
+
signingService.algorithm,
|
|
12222
|
+
signingService.publicKey,
|
|
12223
|
+
import_HashAlgorithm5.HashAlgorithm.SHA256
|
|
12224
|
+
);
|
|
12225
|
+
const ownerAddress = await addressRef.toAddress();
|
|
12226
|
+
const salt = new Uint8Array(32);
|
|
12227
|
+
crypto.getRandomValues(salt);
|
|
12228
|
+
const mintData = await import_MintTransactionData3.MintTransactionData.create(
|
|
12229
|
+
tokenId,
|
|
12230
|
+
tokenType,
|
|
12231
|
+
null,
|
|
12232
|
+
// tokenData: no metadata
|
|
12233
|
+
coinData,
|
|
12234
|
+
// fungible coin data
|
|
12235
|
+
ownerAddress,
|
|
12236
|
+
// recipient = self
|
|
12237
|
+
salt,
|
|
12238
|
+
null,
|
|
12239
|
+
// recipientDataHash
|
|
12240
|
+
null
|
|
12241
|
+
// reason: null (genesis, no burn predecessor)
|
|
12242
|
+
);
|
|
12243
|
+
const commitment = await import_MintCommitment3.MintCommitment.create(mintData);
|
|
12244
|
+
const MAX_RETRIES = 3;
|
|
12245
|
+
let lastStatus;
|
|
12246
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
12247
|
+
const response = await stClient.submitMintCommitment(commitment);
|
|
12248
|
+
lastStatus = response.status;
|
|
12249
|
+
if (response.status === "SUCCESS" || response.status === "REQUEST_ID_EXISTS") break;
|
|
12250
|
+
if (attempt === MAX_RETRIES) {
|
|
12251
|
+
return { success: false, error: `Mint submit failed after ${MAX_RETRIES} attempts: ${response.status}` };
|
|
12252
|
+
}
|
|
12253
|
+
await new Promise((r) => setTimeout(r, 1e3 * attempt));
|
|
12254
|
+
}
|
|
12255
|
+
if (lastStatus !== "SUCCESS" && lastStatus !== "REQUEST_ID_EXISTS") {
|
|
12256
|
+
return { success: false, error: `Mint submit failed: ${lastStatus}` };
|
|
12257
|
+
}
|
|
12258
|
+
const inclusionProof = await (0, import_InclusionProofUtils5.waitInclusionProof)(trustBase, stClient, commitment);
|
|
12259
|
+
const genesisTransaction = commitment.toTransaction(inclusionProof);
|
|
12260
|
+
const predicate = await import_UnmaskedPredicate5.UnmaskedPredicate.create(
|
|
12261
|
+
tokenId,
|
|
12262
|
+
tokenType,
|
|
12263
|
+
signingService,
|
|
12264
|
+
import_HashAlgorithm5.HashAlgorithm.SHA256,
|
|
12265
|
+
salt
|
|
12266
|
+
);
|
|
12267
|
+
const tokenState = new import_TokenState5.TokenState(predicate, null);
|
|
12268
|
+
const sdkToken = await import_Token6.Token.mint(trustBase, tokenState, genesisTransaction);
|
|
12269
|
+
const tokenIdHex = tokenId.toJSON();
|
|
12270
|
+
const symbol = this.getCoinSymbol(coinIdHex);
|
|
12271
|
+
const name = this.getCoinName(coinIdHex);
|
|
12272
|
+
const decimals = this.getCoinDecimals(coinIdHex);
|
|
12273
|
+
const iconUrl = this.getCoinIconUrl(coinIdHex);
|
|
12274
|
+
const uiToken = {
|
|
12275
|
+
id: tokenIdHex,
|
|
12276
|
+
coinId: coinIdHex,
|
|
12277
|
+
symbol,
|
|
12278
|
+
name,
|
|
12279
|
+
decimals,
|
|
12280
|
+
...iconUrl !== void 0 ? { iconUrl } : {},
|
|
12281
|
+
amount: amount.toString(),
|
|
12282
|
+
status: "confirmed",
|
|
12283
|
+
createdAt: Date.now(),
|
|
12284
|
+
updatedAt: Date.now(),
|
|
12285
|
+
sdkData: JSON.stringify(sdkToken.toJSON())
|
|
12286
|
+
};
|
|
12287
|
+
await this.addToken(uiToken);
|
|
12288
|
+
return { success: true, token: uiToken, tokenId: tokenIdHex };
|
|
12289
|
+
} catch (err) {
|
|
12290
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
12291
|
+
return { success: false, error: `Local mint failed: ${msg}` };
|
|
12292
|
+
}
|
|
12293
|
+
}
|
|
12167
12294
|
/**
|
|
12168
12295
|
* Check if a nametag is available for minting
|
|
12169
12296
|
* @param nametag - The nametag to check (e.g., "alice" or "@alice")
|
|
@@ -18370,6 +18497,24 @@ var AccountingModule = class _AccountingModule {
|
|
|
18370
18497
|
dirtyLedgerEntries = /* @__PURE__ */ new Set();
|
|
18371
18498
|
/** Count of unknown (not in invoiceTermsCache) invoice IDs in the ledger. */
|
|
18372
18499
|
unknownLedgerCount = 0;
|
|
18500
|
+
/**
|
|
18501
|
+
* Per-unknown-invoice first-seen timestamp for TTL eviction.
|
|
18502
|
+
*
|
|
18503
|
+
* W1 (steelman round-4): without TTL, an attacker who can deliver 500
|
|
18504
|
+
* inbound transfers with synthesized memo invoiceIds permanently exhausts
|
|
18505
|
+
* the unknown-ledger cap, after which legitimate orphan transfers (out-of-
|
|
18506
|
+
* order delivery for real swaps) are silently dropped at the cap-check.
|
|
18507
|
+
*
|
|
18508
|
+
* Round-5 perf: gated by `unknownLedgerNextSweepMs` to amortize the
|
|
18509
|
+
* sweep cost. The naive every-call sweep is O(N) where N=cap=500;
|
|
18510
|
+
* combined with the per-token cleanup loop inside the sweep it became
|
|
18511
|
+
* O(N×M) on every transfer under flood. Now we sweep at most every
|
|
18512
|
+
* `UNKNOWN_LEDGER_SWEEP_INTERVAL_MS` (60s) UNLESS the cap is currently
|
|
18513
|
+
* full, in which case we sweep on each call (the only path that can
|
|
18514
|
+
* actually drop a legitimate orphan).
|
|
18515
|
+
*/
|
|
18516
|
+
unknownLedgerFirstSeen = /* @__PURE__ */ new Map();
|
|
18517
|
+
unknownLedgerNextSweepMs = 0;
|
|
18373
18518
|
/** W17: Tracks whether tokenScanState has been mutated since last flush. */
|
|
18374
18519
|
tokenScanDirty = false;
|
|
18375
18520
|
/** W2 fix: Serialization guard for _flushDirtyLedgerEntries. */
|
|
@@ -19360,6 +19505,7 @@ var AccountingModule = class _AccountingModule {
|
|
|
19360
19505
|
}
|
|
19361
19506
|
if (this.invoiceLedger.has(tokenId) && !this.invoiceTermsCache.has(tokenId)) {
|
|
19362
19507
|
this.unknownLedgerCount = Math.max(0, this.unknownLedgerCount - 1);
|
|
19508
|
+
this.unknownLedgerFirstSeen.delete(tokenId);
|
|
19363
19509
|
}
|
|
19364
19510
|
this.invoiceTermsCache.set(tokenId, terms);
|
|
19365
19511
|
this._addToHashIndex(tokenId);
|
|
@@ -19628,6 +19774,29 @@ var AccountingModule = class _AccountingModule {
|
|
|
19628
19774
|
closed: this.closedInvoices.has(invoiceId)
|
|
19629
19775
|
};
|
|
19630
19776
|
}
|
|
19777
|
+
/**
|
|
19778
|
+
* Return the set of token IDs that are currently linked to the given
|
|
19779
|
+
* invoice. Populated by both the on-chain `_processTokenTransactions`
|
|
19780
|
+
* path (tokens with `inv:` references) and the transport-memo orphan
|
|
19781
|
+
* buffering path in `_handleIncomingTransfer`.
|
|
19782
|
+
*
|
|
19783
|
+
* Used by callers that want to scope per-invoice operations (e.g.
|
|
19784
|
+
* SwapModule.verifyPayout's L3 validation) to only the tokens that
|
|
19785
|
+
* cover this invoice — avoiding false negatives when the wallet
|
|
19786
|
+
* contains unrelated tokens of the same currency in unconfirmed or
|
|
19787
|
+
* spent state.
|
|
19788
|
+
*
|
|
19789
|
+
* Returns an empty set if no tokens are currently linked.
|
|
19790
|
+
*/
|
|
19791
|
+
getTokenIdsForInvoice(invoiceId) {
|
|
19792
|
+
const result = /* @__PURE__ */ new Set();
|
|
19793
|
+
for (const [tokenId, invoiceIds] of this.tokenInvoiceMap) {
|
|
19794
|
+
if (invoiceIds.has(invoiceId)) {
|
|
19795
|
+
result.add(tokenId);
|
|
19796
|
+
}
|
|
19797
|
+
}
|
|
19798
|
+
return result;
|
|
19799
|
+
}
|
|
19631
19800
|
/**
|
|
19632
19801
|
* Explicitly close an invoice. Only target parties may close (§8.3).
|
|
19633
19802
|
*
|
|
@@ -19947,6 +20116,7 @@ var AccountingModule = class _AccountingModule {
|
|
|
19947
20116
|
ledger.set(entryKey, forwardRef);
|
|
19948
20117
|
this.dirtyLedgerEntries.add(invoiceId);
|
|
19949
20118
|
this.balanceCache.delete(invoiceId);
|
|
20119
|
+
await this._persistProvisionalAndVerify(invoiceId, "payInvoice");
|
|
19950
20120
|
}
|
|
19951
20121
|
return result;
|
|
19952
20122
|
} finally {
|
|
@@ -20114,6 +20284,7 @@ var AccountingModule = class _AccountingModule {
|
|
|
20114
20284
|
this.dirtyLedgerEntries.add(invoiceId);
|
|
20115
20285
|
}
|
|
20116
20286
|
this.balanceCache.delete(invoiceId);
|
|
20287
|
+
await this._persistProvisionalAndVerify(invoiceId, "returnInvoicePayment");
|
|
20117
20288
|
}
|
|
20118
20289
|
return result;
|
|
20119
20290
|
} finally {
|
|
@@ -21193,9 +21364,30 @@ var AccountingModule = class _AccountingModule {
|
|
|
21193
21364
|
continue;
|
|
21194
21365
|
}
|
|
21195
21366
|
innerMap.set(entryKey, ref);
|
|
21196
|
-
|
|
21367
|
+
const HEX_64 = /^[a-f0-9]{64}$/i;
|
|
21368
|
+
if (entryKey.startsWith("mt:")) {
|
|
21369
|
+
const firstColon = entryKey.indexOf(":");
|
|
21370
|
+
const secondColon = entryKey.indexOf(":", firstColon + 1);
|
|
21371
|
+
if (secondColon > firstColon + 1) {
|
|
21372
|
+
const tokenIdFromKey2 = entryKey.slice(firstColon + 1, secondColon);
|
|
21373
|
+
if (HEX_64.test(tokenIdFromKey2)) {
|
|
21374
|
+
this._addToTokenInvoiceMap(tokenIdFromKey2, invoiceId);
|
|
21375
|
+
}
|
|
21376
|
+
}
|
|
21377
|
+
} else if (entryKey.startsWith("synthetic:")) {
|
|
21378
|
+
const afterPrefix = entryKey.slice("synthetic:".length);
|
|
21379
|
+
const tokenIdEnd = afterPrefix.indexOf(":");
|
|
21380
|
+
if (tokenIdEnd > 0) {
|
|
21381
|
+
const tokenId = afterPrefix.slice(0, tokenIdEnd);
|
|
21382
|
+
if (HEX_64.test(tokenId) && ref.transferId !== tokenId) {
|
|
21383
|
+
this._addToTokenInvoiceMap(tokenId, invoiceId);
|
|
21384
|
+
}
|
|
21385
|
+
}
|
|
21386
|
+
} else if (!ref.transferId.startsWith("provisional:") && ref.transferId.includes(":")) {
|
|
21197
21387
|
const tokenIdFromRef = ref.transferId.slice(0, ref.transferId.indexOf(":"));
|
|
21198
|
-
|
|
21388
|
+
if (HEX_64.test(tokenIdFromRef)) {
|
|
21389
|
+
this._addToTokenInvoiceMap(tokenIdFromRef, invoiceId);
|
|
21390
|
+
}
|
|
21199
21391
|
}
|
|
21200
21392
|
}
|
|
21201
21393
|
} catch (err) {
|
|
@@ -21396,7 +21588,14 @@ var AccountingModule = class _AccountingModule {
|
|
|
21396
21588
|
}
|
|
21397
21589
|
}
|
|
21398
21590
|
for (const [existingKey, existingRef] of ledger) {
|
|
21399
|
-
if (existingKey.startsWith("synthetic:") && existingRef.coinId === coinId && existingRef.paymentDirection === paymentDirection) {
|
|
21591
|
+
if ((existingKey.startsWith("synthetic:") || existingKey.startsWith("synthetic-tx:")) && existingRef.coinId === coinId && existingRef.paymentDirection === paymentDirection) {
|
|
21592
|
+
keysToDelete.push(existingKey);
|
|
21593
|
+
break;
|
|
21594
|
+
}
|
|
21595
|
+
}
|
|
21596
|
+
const mtPrefix = `mt:${tokenId}:`;
|
|
21597
|
+
for (const [existingKey, existingRef] of ledger) {
|
|
21598
|
+
if (existingKey.startsWith(mtPrefix) && existingRef.coinId === coinId && existingRef.paymentDirection === paymentDirection) {
|
|
21400
21599
|
keysToDelete.push(existingKey);
|
|
21401
21600
|
break;
|
|
21402
21601
|
}
|
|
@@ -21513,6 +21712,49 @@ var AccountingModule = class _AccountingModule {
|
|
|
21513
21712
|
});
|
|
21514
21713
|
}
|
|
21515
21714
|
}
|
|
21715
|
+
/**
|
|
21716
|
+
* Synchronously persist any pending provisional ledger entry for `invoiceId`
|
|
21717
|
+
* before returning to the caller. Used by `payInvoice` and
|
|
21718
|
+
* `returnInvoicePayment` to make the in-memory provisional entry durable
|
|
21719
|
+
* inside the same per-invoice gate that wrote it, closing the
|
|
21720
|
+
* crash-mid-conclude race that produces over-coverage on receivers.
|
|
21721
|
+
*
|
|
21722
|
+
* Implementation:
|
|
21723
|
+
* 1. Schedule a flush via the existing `_flushPromise` chain (so
|
|
21724
|
+
* concurrent `_handleTokenChange` callers waiting on the chain
|
|
21725
|
+
* observe ours as part of the sequence).
|
|
21726
|
+
* 2. Await OUR flush directly — NOT `_drainFlushPromise()`, which would
|
|
21727
|
+
* spin while concurrent token changes keep extending the chain and
|
|
21728
|
+
* hold the per-invoice gate for an unbounded number of additional
|
|
21729
|
+
* flushes. We only need OUR provisional entry durable.
|
|
21730
|
+
* 3. `_flushDirtyLedgerEntries` swallows per-invoice `storage.set`
|
|
21731
|
+
* rejections internally (sets a local `step1Failed` flag), leaving
|
|
21732
|
+
* the dirty entry on the set without re-throwing. So we post-check
|
|
21733
|
+
* `dirtyLedgerEntries.has(invoiceId)` and throw a `STORAGE_ERROR`
|
|
21734
|
+
* `SphereError` if our entry is still dirty — propagating to the
|
|
21735
|
+
* caller so they learn about the durability failure rather than
|
|
21736
|
+
* receiving a silent "success" return that lies on disk.
|
|
21737
|
+
*
|
|
21738
|
+
* @param invoiceId The invoice whose provisional entry must be durable.
|
|
21739
|
+
* @param callContext Used in the error message so the caller is named
|
|
21740
|
+
* ('payInvoice' / 'returnInvoicePayment') without
|
|
21741
|
+
* forcing a stack-trace inspection.
|
|
21742
|
+
*/
|
|
21743
|
+
async _persistProvisionalAndVerify(invoiceId, callContext) {
|
|
21744
|
+
const flushTrigger = (this._flushPromise ?? Promise.resolve()).then(() => this._flushDirtyLedgerEntries());
|
|
21745
|
+
const tracked = flushTrigger.catch(() => {
|
|
21746
|
+
}).finally(() => {
|
|
21747
|
+
if (this._flushPromise === tracked) this._flushPromise = null;
|
|
21748
|
+
});
|
|
21749
|
+
this._flushPromise = tracked;
|
|
21750
|
+
await flushTrigger;
|
|
21751
|
+
if (this.dirtyLedgerEntries.has(invoiceId)) {
|
|
21752
|
+
throw new SphereError(
|
|
21753
|
+
`${callContext}: provisional ledger entry for invoice ${invoiceId} failed to persist \u2014 caller should retry`,
|
|
21754
|
+
"STORAGE_ERROR"
|
|
21755
|
+
);
|
|
21756
|
+
}
|
|
21757
|
+
}
|
|
21516
21758
|
// ===========================================================================
|
|
21517
21759
|
// Internal: Event handlers
|
|
21518
21760
|
// ===========================================================================
|
|
@@ -21573,13 +21815,96 @@ var AccountingModule = class _AccountingModule {
|
|
|
21573
21815
|
}
|
|
21574
21816
|
}
|
|
21575
21817
|
if (!this.invoiceTermsCache.has(invoiceId)) {
|
|
21576
|
-
|
|
21577
|
-
|
|
21578
|
-
invoiceId
|
|
21579
|
-
|
|
21580
|
-
|
|
21581
|
-
|
|
21582
|
-
|
|
21818
|
+
let gracefullyGraduated = false;
|
|
21819
|
+
await this.withInvoiceGate(invoiceId, async () => {
|
|
21820
|
+
if (this.invoiceTermsCache.has(invoiceId)) {
|
|
21821
|
+
gracefullyGraduated = true;
|
|
21822
|
+
return;
|
|
21823
|
+
}
|
|
21824
|
+
const syntheticRef = this._buildSyntheticTransferRef(
|
|
21825
|
+
transfer,
|
|
21826
|
+
invoiceId,
|
|
21827
|
+
paymentDirection,
|
|
21828
|
+
confirmed
|
|
21829
|
+
);
|
|
21830
|
+
deps.emitEvent("invoice:unknown_reference", { invoiceId, transfer: syntheticRef });
|
|
21831
|
+
const MAX_UNKNOWN_INVOICE_IDS = 500;
|
|
21832
|
+
const UNKNOWN_LEDGER_TTL_MS = 30 * 60 * 1e3;
|
|
21833
|
+
const MAX_ORPHAN_ENTRIES_PER_INVOICE = 50;
|
|
21834
|
+
const UNKNOWN_LEDGER_SWEEP_INTERVAL_MS = 6e4;
|
|
21835
|
+
const nowMs = Date.now();
|
|
21836
|
+
const capFull = this.unknownLedgerCount >= MAX_UNKNOWN_INVOICE_IDS;
|
|
21837
|
+
const sweepDue = nowMs >= this.unknownLedgerNextSweepMs;
|
|
21838
|
+
if (this.unknownLedgerFirstSeen.size > 0 && (capFull || sweepDue)) {
|
|
21839
|
+
this.unknownLedgerNextSweepMs = nowMs + UNKNOWN_LEDGER_SWEEP_INTERVAL_MS;
|
|
21840
|
+
const expiredIds = [];
|
|
21841
|
+
for (const [unkId, firstSeen] of this.unknownLedgerFirstSeen) {
|
|
21842
|
+
if (nowMs - firstSeen > UNKNOWN_LEDGER_TTL_MS) {
|
|
21843
|
+
expiredIds.push(unkId);
|
|
21844
|
+
}
|
|
21845
|
+
}
|
|
21846
|
+
for (const expiredId of expiredIds) {
|
|
21847
|
+
if (!this.invoiceTermsCache.has(expiredId) && this.invoiceLedger.has(expiredId)) {
|
|
21848
|
+
this.invoiceLedger.delete(expiredId);
|
|
21849
|
+
this.unknownLedgerCount = Math.max(0, this.unknownLedgerCount - 1);
|
|
21850
|
+
for (const [tokenId, invoiceSet] of this.tokenInvoiceMap) {
|
|
21851
|
+
if (invoiceSet.has(expiredId)) {
|
|
21852
|
+
invoiceSet.delete(expiredId);
|
|
21853
|
+
if (invoiceSet.size === 0) this.tokenInvoiceMap.delete(tokenId);
|
|
21854
|
+
}
|
|
21855
|
+
}
|
|
21856
|
+
}
|
|
21857
|
+
this.unknownLedgerFirstSeen.delete(expiredId);
|
|
21858
|
+
}
|
|
21859
|
+
}
|
|
21860
|
+
if (!this.invoiceLedger.has(invoiceId)) {
|
|
21861
|
+
if (this.unknownLedgerCount >= MAX_UNKNOWN_INVOICE_IDS) {
|
|
21862
|
+
return;
|
|
21863
|
+
}
|
|
21864
|
+
this.invoiceLedger.set(invoiceId, /* @__PURE__ */ new Map());
|
|
21865
|
+
this.unknownLedgerCount++;
|
|
21866
|
+
this.unknownLedgerFirstSeen.set(invoiceId, nowMs);
|
|
21867
|
+
}
|
|
21868
|
+
const orphanLedger = this.invoiceLedger.get(invoiceId);
|
|
21869
|
+
let mtEntryCount = 0;
|
|
21870
|
+
for (const k of orphanLedger.keys()) {
|
|
21871
|
+
if (k.startsWith("mt:")) mtEntryCount++;
|
|
21872
|
+
}
|
|
21873
|
+
if (mtEntryCount >= MAX_ORPHAN_ENTRIES_PER_INVOICE) {
|
|
21874
|
+
return;
|
|
21875
|
+
}
|
|
21876
|
+
for (const token of transfer.tokens) {
|
|
21877
|
+
if (!token.id) continue;
|
|
21878
|
+
let onChainAttributed = false;
|
|
21879
|
+
const tokenKeyPrefix = `${token.id}:`;
|
|
21880
|
+
for (const existingKey of orphanLedger.keys()) {
|
|
21881
|
+
if (existingKey.startsWith(tokenKeyPrefix) && !existingKey.startsWith("mt:")) {
|
|
21882
|
+
onChainAttributed = true;
|
|
21883
|
+
break;
|
|
21884
|
+
}
|
|
21885
|
+
}
|
|
21886
|
+
if (!onChainAttributed) {
|
|
21887
|
+
if (mtEntryCount >= MAX_ORPHAN_ENTRIES_PER_INVOICE) {
|
|
21888
|
+
break;
|
|
21889
|
+
}
|
|
21890
|
+
const orphanKey = `mt:${token.id}:${transfer.id}`;
|
|
21891
|
+
if (!orphanLedger.has(orphanKey)) {
|
|
21892
|
+
orphanLedger.set(orphanKey, syntheticRef);
|
|
21893
|
+
mtEntryCount++;
|
|
21894
|
+
}
|
|
21895
|
+
}
|
|
21896
|
+
if (!this.tokenInvoiceMap.has(token.id)) {
|
|
21897
|
+
this.tokenInvoiceMap.set(token.id, /* @__PURE__ */ new Set());
|
|
21898
|
+
}
|
|
21899
|
+
this.tokenInvoiceMap.get(token.id).add(invoiceId);
|
|
21900
|
+
}
|
|
21901
|
+
this.dirtyLedgerEntries.add(invoiceId);
|
|
21902
|
+
this.balanceCache.delete(invoiceId);
|
|
21903
|
+
await this._flushDirtyLedgerEntries();
|
|
21904
|
+
});
|
|
21905
|
+
if (gracefullyGraduated) {
|
|
21906
|
+
await this._processInvoiceTransferEvent(transfer, invoiceId, paymentDirection, confirmed);
|
|
21907
|
+
}
|
|
21583
21908
|
return;
|
|
21584
21909
|
}
|
|
21585
21910
|
await this._processInvoiceTransferEvent(transfer, invoiceId, paymentDirection, confirmed);
|
|
@@ -22015,7 +22340,8 @@ var AccountingModule = class _AccountingModule {
|
|
|
22015
22340
|
}
|
|
22016
22341
|
const existingLedger = this.invoiceLedger.get(invoiceId);
|
|
22017
22342
|
const firstTokenId = transfer.tokens.find((t) => t.id)?.id;
|
|
22018
|
-
const syntheticKey = firstTokenId ? `synthetic:${firstTokenId}::${syntheticRef.coinId}` : `synthetic:${syntheticRef.transferId}::${syntheticRef.coinId}`;
|
|
22343
|
+
const syntheticKey = firstTokenId ? `synthetic:${firstTokenId}::${syntheticRef.coinId}` : `synthetic-tx:${syntheticRef.transferId}::${syntheticRef.coinId}`;
|
|
22344
|
+
let mutated = false;
|
|
22019
22345
|
if (!existingLedger.has(syntheticKey)) {
|
|
22020
22346
|
let hasRealEntry = false;
|
|
22021
22347
|
for (const tok of transfer.tokens) {
|
|
@@ -22030,7 +22356,24 @@ var AccountingModule = class _AccountingModule {
|
|
|
22030
22356
|
}
|
|
22031
22357
|
if (!hasRealEntry) {
|
|
22032
22358
|
existingLedger.set(syntheticKey, { ...syntheticRef });
|
|
22359
|
+
mutated = true;
|
|
22360
|
+
}
|
|
22361
|
+
}
|
|
22362
|
+
for (const tok of transfer.tokens) {
|
|
22363
|
+
if (!tok.id) continue;
|
|
22364
|
+
if (!this.tokenInvoiceMap.has(tok.id)) {
|
|
22365
|
+
this.tokenInvoiceMap.set(tok.id, /* @__PURE__ */ new Set());
|
|
22366
|
+
mutated = true;
|
|
22033
22367
|
}
|
|
22368
|
+
const beforeSize = this.tokenInvoiceMap.get(tok.id).size;
|
|
22369
|
+
this.tokenInvoiceMap.get(tok.id).add(invoiceId);
|
|
22370
|
+
if (this.tokenInvoiceMap.get(tok.id).size !== beforeSize) {
|
|
22371
|
+
mutated = true;
|
|
22372
|
+
}
|
|
22373
|
+
}
|
|
22374
|
+
if (mutated) {
|
|
22375
|
+
this.dirtyLedgerEntries.add(invoiceId);
|
|
22376
|
+
this.balanceCache.delete(invoiceId);
|
|
22034
22377
|
}
|
|
22035
22378
|
deps.emitEvent("invoice:payment", {
|
|
22036
22379
|
invoiceId,
|
|
@@ -22181,7 +22524,8 @@ var AccountingModule = class _AccountingModule {
|
|
|
22181
22524
|
this.invoiceLedger.set(invoiceId, /* @__PURE__ */ new Map());
|
|
22182
22525
|
}
|
|
22183
22526
|
const hLedger = this.invoiceLedger.get(invoiceId);
|
|
22184
|
-
const hKey = entry.tokenId ? `synthetic:${entry.tokenId}::${syntheticRef.coinId}` : `synthetic:${syntheticRef.transferId}::${syntheticRef.coinId}`;
|
|
22527
|
+
const hKey = entry.tokenId ? `synthetic:${entry.tokenId}::${syntheticRef.coinId}` : `synthetic-tx:${syntheticRef.transferId}::${syntheticRef.coinId}`;
|
|
22528
|
+
let hMutated = false;
|
|
22185
22529
|
if (!hLedger.has(hKey)) {
|
|
22186
22530
|
let hasRealEntry = false;
|
|
22187
22531
|
if (entry.tokenId) {
|
|
@@ -22194,8 +22538,24 @@ var AccountingModule = class _AccountingModule {
|
|
|
22194
22538
|
}
|
|
22195
22539
|
if (!hasRealEntry) {
|
|
22196
22540
|
hLedger.set(hKey, { ...syntheticRef });
|
|
22541
|
+
hMutated = true;
|
|
22542
|
+
}
|
|
22543
|
+
}
|
|
22544
|
+
if (entry.tokenId) {
|
|
22545
|
+
if (!this.tokenInvoiceMap.has(entry.tokenId)) {
|
|
22546
|
+
this.tokenInvoiceMap.set(entry.tokenId, /* @__PURE__ */ new Set());
|
|
22547
|
+
hMutated = true;
|
|
22548
|
+
}
|
|
22549
|
+
const beforeSize = this.tokenInvoiceMap.get(entry.tokenId).size;
|
|
22550
|
+
this.tokenInvoiceMap.get(entry.tokenId).add(invoiceId);
|
|
22551
|
+
if (this.tokenInvoiceMap.get(entry.tokenId).size !== beforeSize) {
|
|
22552
|
+
hMutated = true;
|
|
22197
22553
|
}
|
|
22198
22554
|
}
|
|
22555
|
+
if (hMutated) {
|
|
22556
|
+
this.dirtyLedgerEntries.add(invoiceId);
|
|
22557
|
+
this.balanceCache.delete(invoiceId);
|
|
22558
|
+
}
|
|
22199
22559
|
deps.emitEvent("invoice:payment", {
|
|
22200
22560
|
invoiceId,
|
|
22201
22561
|
transfer: syntheticRef,
|
|
@@ -24726,17 +25086,63 @@ var SwapModule = class {
|
|
|
24726
25086
|
for (const addr of allAddresses) {
|
|
24727
25087
|
myDirectAddresses.add(addr.directAddress);
|
|
24728
25088
|
}
|
|
24729
|
-
|
|
24730
|
-
|
|
24731
|
-
|
|
24732
|
-
|
|
24733
|
-
|
|
25089
|
+
const matchesPartyA = myDirectAddresses.has(swap.manifest.party_a_address);
|
|
25090
|
+
const matchesPartyB = myDirectAddresses.has(swap.manifest.party_b_address);
|
|
25091
|
+
if (matchesPartyA && matchesPartyB) {
|
|
25092
|
+
throw new SphereError(
|
|
25093
|
+
"Ambiguous party identity: local wallet matches both party_a_address and party_b_address",
|
|
25094
|
+
"SWAP_DEPOSIT_FAILED"
|
|
25095
|
+
);
|
|
25096
|
+
}
|
|
25097
|
+
let myExpectedCurrency;
|
|
25098
|
+
if (matchesPartyA) {
|
|
25099
|
+
myExpectedCurrency = swap.manifest.party_a_currency_to_change;
|
|
25100
|
+
} else if (matchesPartyB) {
|
|
25101
|
+
myExpectedCurrency = swap.manifest.party_b_currency_to_change;
|
|
24734
25102
|
} else {
|
|
24735
25103
|
throw new SphereError(
|
|
24736
25104
|
"Local wallet address does not match either party in the swap manifest",
|
|
24737
25105
|
"SWAP_DEPOSIT_FAILED"
|
|
24738
25106
|
);
|
|
24739
25107
|
}
|
|
25108
|
+
if (!myExpectedCurrency || myExpectedCurrency === "") {
|
|
25109
|
+
throw new SphereError(
|
|
25110
|
+
"Manifest currency_to_change is empty for this party",
|
|
25111
|
+
"SWAP_DEPOSIT_FAILED"
|
|
25112
|
+
);
|
|
25113
|
+
}
|
|
25114
|
+
const invoiceRefForAssetLookup = deps.accounting.getInvoice(swap.depositInvoiceId);
|
|
25115
|
+
if (!invoiceRefForAssetLookup) {
|
|
25116
|
+
throw new SphereError(
|
|
25117
|
+
"Deposit invoice not yet imported into accounting module",
|
|
25118
|
+
"SWAP_WRONG_STATE"
|
|
25119
|
+
);
|
|
25120
|
+
}
|
|
25121
|
+
const depositTarget = invoiceRefForAssetLookup.terms.targets[0];
|
|
25122
|
+
if (!depositTarget) {
|
|
25123
|
+
throw new SphereError(
|
|
25124
|
+
"Deposit invoice has no targets",
|
|
25125
|
+
"SWAP_DEPOSIT_FAILED"
|
|
25126
|
+
);
|
|
25127
|
+
}
|
|
25128
|
+
const assetIndex = depositTarget.assets.findIndex(
|
|
25129
|
+
(a) => a.coin !== void 0 && coinIdsMatch(a.coin[0], myExpectedCurrency)
|
|
25130
|
+
);
|
|
25131
|
+
if (assetIndex < 0) {
|
|
25132
|
+
throw new SphereError(
|
|
25133
|
+
`No asset matching expected currency ${myExpectedCurrency} found in deposit invoice`,
|
|
25134
|
+
"SWAP_DEPOSIT_FAILED"
|
|
25135
|
+
);
|
|
25136
|
+
}
|
|
25137
|
+
for (let i = assetIndex + 1; i < depositTarget.assets.length; i += 1) {
|
|
25138
|
+
const a = depositTarget.assets[i];
|
|
25139
|
+
if (a?.coin !== void 0 && coinIdsMatch(a.coin[0], myExpectedCurrency)) {
|
|
25140
|
+
throw new SphereError(
|
|
25141
|
+
`Ambiguous asset match in deposit invoice: slots ${assetIndex} and ${i} both match currency ${myExpectedCurrency}`,
|
|
25142
|
+
"SWAP_DEPOSIT_FAILED"
|
|
25143
|
+
);
|
|
25144
|
+
}
|
|
25145
|
+
}
|
|
24740
25146
|
return this.withSwapGate(swapId, async () => {
|
|
24741
25147
|
if (swap.progress !== "announced") {
|
|
24742
25148
|
throw new SphereError(
|
|
@@ -24842,7 +25248,6 @@ var SwapModule = class {
|
|
|
24842
25248
|
swap.updatedAt = Date.now();
|
|
24843
25249
|
this.clearLocalTimer(swap.swapId);
|
|
24844
25250
|
this.terminalSwapIds.add(swap.swapId);
|
|
24845
|
-
const entryIdx = this._storedTerminalEntries.length;
|
|
24846
25251
|
this._storedTerminalEntries.push({
|
|
24847
25252
|
swapId: swap.swapId,
|
|
24848
25253
|
progress: "failed",
|
|
@@ -24857,7 +25262,13 @@ var SwapModule = class {
|
|
|
24857
25262
|
swap.error = prevError;
|
|
24858
25263
|
swap.updatedAt = prevUpdatedAt;
|
|
24859
25264
|
this.terminalSwapIds.delete(swap.swapId);
|
|
24860
|
-
this._storedTerminalEntries.
|
|
25265
|
+
for (let i = this._storedTerminalEntries.length - 1; i >= 0; i--) {
|
|
25266
|
+
const entry = this._storedTerminalEntries[i];
|
|
25267
|
+
if (entry.swapId === swap.swapId && entry.progress === "failed") {
|
|
25268
|
+
this._storedTerminalEntries.splice(i, 1);
|
|
25269
|
+
break;
|
|
25270
|
+
}
|
|
25271
|
+
}
|
|
24861
25272
|
logger.warn(LOG_TAG3, `failPayout: persistSwap failed for ${swapId}; fraud detection will retry on next load:`, persistErr);
|
|
24862
25273
|
throw persistErr;
|
|
24863
25274
|
}
|
|
@@ -24901,9 +25312,25 @@ var SwapModule = class {
|
|
|
24901
25312
|
if (!targetStatus.coinAssets[0].isCovered) {
|
|
24902
25313
|
return returnFalse();
|
|
24903
25314
|
}
|
|
24904
|
-
|
|
25315
|
+
let netCoveredAmount;
|
|
25316
|
+
let expectedAmountBigInt;
|
|
25317
|
+
try {
|
|
25318
|
+
netCoveredAmount = BigInt(targetStatus.coinAssets[0].netCoveredAmount);
|
|
25319
|
+
expectedAmountBigInt = BigInt(expectedAmount);
|
|
25320
|
+
} catch (parseErr) {
|
|
25321
|
+
return failPayout(
|
|
25322
|
+
`MALFORMED_AMOUNT: failed to parse coverage amounts (netCoveredAmount=${targetStatus.coinAssets[0].netCoveredAmount}, expectedAmount=${expectedAmount}): ${parseErr instanceof Error ? parseErr.message : String(parseErr)}`
|
|
25323
|
+
);
|
|
25324
|
+
}
|
|
25325
|
+
if (netCoveredAmount < expectedAmountBigInt) {
|
|
24905
25326
|
return returnFalse();
|
|
24906
25327
|
}
|
|
25328
|
+
if (netCoveredAmount > expectedAmountBigInt) {
|
|
25329
|
+
const surplus = netCoveredAmount - expectedAmountBigInt;
|
|
25330
|
+
return failPayout(
|
|
25331
|
+
`OVER_COVERAGE: net=${netCoveredAmount.toString()}, expected=${expectedAmount}, surplus=${surplus.toString()} \u2014 surplus refund expected via auto-return; settlement halted`
|
|
25332
|
+
);
|
|
25333
|
+
}
|
|
24907
25334
|
const escrowAddr = swap.deal.escrowAddress ?? this.config.defaultEscrowAddress;
|
|
24908
25335
|
if (escrowAddr) {
|
|
24909
25336
|
const escrowPeer = await deps.resolve(escrowAddr);
|
|
@@ -24913,8 +25340,28 @@ var SwapModule = class {
|
|
|
24913
25340
|
}
|
|
24914
25341
|
const validationResult = await deps.payments.validate();
|
|
24915
25342
|
if (validationResult.invalid.length > 0) {
|
|
24916
|
-
|
|
24917
|
-
|
|
25343
|
+
const payoutTokenIds = deps.accounting.getTokenIdsForInvoice?.(swap.payoutInvoiceId) ?? /* @__PURE__ */ new Set();
|
|
25344
|
+
if (payoutTokenIds.size === 0) {
|
|
25345
|
+
logger.warn(
|
|
25346
|
+
LOG_TAG3,
|
|
25347
|
+
`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`
|
|
25348
|
+
);
|
|
25349
|
+
return returnFalse();
|
|
25350
|
+
}
|
|
25351
|
+
const relevantInvalid = validationResult.invalid.filter(
|
|
25352
|
+
(t) => payoutTokenIds.has(t.id)
|
|
25353
|
+
);
|
|
25354
|
+
if (relevantInvalid.length > 0) {
|
|
25355
|
+
logger.warn(
|
|
25356
|
+
LOG_TAG3,
|
|
25357
|
+
`verifyPayout for ${swapId.slice(0, 12)}: L3 validation found ${relevantInvalid.length} invalid token(s) covering this payout invoice \u2014 retry after wallet sync`
|
|
25358
|
+
);
|
|
25359
|
+
return returnFalse();
|
|
25360
|
+
}
|
|
25361
|
+
logger.debug(
|
|
25362
|
+
LOG_TAG3,
|
|
25363
|
+
`verifyPayout for ${swapId.slice(0, 12)}: ${validationResult.invalid.length} unrelated invalid token(s) ignored (not linked to this payout invoice)`
|
|
25364
|
+
);
|
|
24918
25365
|
}
|
|
24919
25366
|
if (swap.progress === "completed") {
|
|
24920
25367
|
swap.payoutVerified = true;
|
|
@@ -25093,8 +25540,22 @@ var SwapModule = class {
|
|
|
25093
25540
|
* @param dm - The incoming direct message.
|
|
25094
25541
|
*/
|
|
25095
25542
|
handleIncomingDM(dm) {
|
|
25543
|
+
if (dm.content.startsWith("{") && dm.content.includes('"invoice_delivery"')) {
|
|
25544
|
+
logger.warn(
|
|
25545
|
+
LOG_TAG3,
|
|
25546
|
+
`diag_swap_dm_arrived sender=${dm.senderPubkey.slice(0, 16)} length=${dm.content.length}`
|
|
25547
|
+
);
|
|
25548
|
+
}
|
|
25096
25549
|
const parsed = parseSwapDM(dm.content);
|
|
25097
|
-
if (!parsed)
|
|
25550
|
+
if (!parsed) {
|
|
25551
|
+
if (dm.content.startsWith("{") && dm.content.includes('"invoice_delivery"')) {
|
|
25552
|
+
logger.warn(
|
|
25553
|
+
LOG_TAG3,
|
|
25554
|
+
`diag_swap_dm_parse_rejected sender=${dm.senderPubkey.slice(0, 16)} prefix=${dm.content.slice(0, 80)}`
|
|
25555
|
+
);
|
|
25556
|
+
}
|
|
25557
|
+
return;
|
|
25558
|
+
}
|
|
25098
25559
|
void (async () => {
|
|
25099
25560
|
try {
|
|
25100
25561
|
switch (parsed.kind) {
|
|
@@ -25460,16 +25921,43 @@ var SwapModule = class {
|
|
|
25460
25921
|
// invoice_delivery (§12.4.2 + §12.4.3)
|
|
25461
25922
|
// ---------------------------------------------------------------
|
|
25462
25923
|
case "invoice_delivery": {
|
|
25463
|
-
|
|
25924
|
+
logger.warn(
|
|
25925
|
+
LOG_TAG3,
|
|
25926
|
+
`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)}`
|
|
25927
|
+
);
|
|
25928
|
+
if (!swapId) {
|
|
25929
|
+
logger.warn(LOG_TAG3, "diag_invoice_delivery_dropped reason=no_swap_id");
|
|
25930
|
+
return;
|
|
25931
|
+
}
|
|
25464
25932
|
const swap = this.swaps.get(swapId);
|
|
25465
|
-
if (!swap)
|
|
25466
|
-
|
|
25933
|
+
if (!swap) {
|
|
25934
|
+
logger.warn(
|
|
25935
|
+
LOG_TAG3,
|
|
25936
|
+
`diag_invoice_delivery_dropped reason=swap_not_in_map swap_id=${swapId.slice(0, 16)} known_swap_ids_count=${this.swaps.size}`
|
|
25937
|
+
);
|
|
25938
|
+
return;
|
|
25939
|
+
}
|
|
25940
|
+
if (!this.isFromExpectedEscrow(dm.senderPubkey, swap)) {
|
|
25941
|
+
logger.warn(
|
|
25942
|
+
LOG_TAG3,
|
|
25943
|
+
`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)}`
|
|
25944
|
+
);
|
|
25945
|
+
return;
|
|
25946
|
+
}
|
|
25467
25947
|
const deps = this.deps;
|
|
25468
25948
|
if (msg.invoice_type === "deposit") {
|
|
25949
|
+
logger.warn(
|
|
25950
|
+
LOG_TAG3,
|
|
25951
|
+
`diag_invoice_delivery_proceeding_to_import swap_id=${swapId.slice(0, 16)} progress=${swap.progress} invoice_id=${(msg.invoice_id ?? "").slice(0, 16)}`
|
|
25952
|
+
);
|
|
25469
25953
|
await this.withSwapGate(swapId, async () => {
|
|
25470
25954
|
if (isTerminalProgress(swap.progress)) return;
|
|
25471
25955
|
try {
|
|
25472
25956
|
await deps.accounting.importInvoice(msg.invoice_token);
|
|
25957
|
+
logger.warn(
|
|
25958
|
+
LOG_TAG3,
|
|
25959
|
+
`diag_invoice_imported swap_id=${swapId.slice(0, 16)} invoice_id=${(msg.invoice_id ?? "").slice(0, 16)} type=deposit`
|
|
25960
|
+
);
|
|
25473
25961
|
} catch (err) {
|
|
25474
25962
|
if (err instanceof SphereError && err.code === "INVOICE_ALREADY_EXISTS") {
|
|
25475
25963
|
logger.debug(LOG_TAG3, `Deposit invoice for swap ${swapId} already imported \u2014 relay re-delivery, continuing`);
|
|
@@ -26962,27 +27450,42 @@ async function parseAndDecryptWalletDat(data, password, onProgress) {
|
|
|
26962
27450
|
|
|
26963
27451
|
// core/Sphere.ts
|
|
26964
27452
|
var import_SigningService2 = require("@unicitylabs/state-transition-sdk/lib/sign/SigningService");
|
|
27453
|
+
var import_nostr_js_sdk5 = require("@unicitylabs/nostr-js-sdk");
|
|
27454
|
+
|
|
27455
|
+
// core/address-derivation.ts
|
|
26965
27456
|
var import_TokenType5 = require("@unicitylabs/state-transition-sdk/lib/token/TokenType");
|
|
26966
27457
|
var import_HashAlgorithm7 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
|
|
26967
27458
|
var import_UnmaskedPredicateReference3 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
|
|
26968
|
-
var
|
|
27459
|
+
var UNICITY_TOKEN_TYPE_HEX2 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
|
|
27460
|
+
var COMPRESSED_PUBKEY_RE = /^(02|03)[0-9a-fA-F]{64}$/;
|
|
27461
|
+
async function computeDirectAddressFromChainPubkey(chainPubkey) {
|
|
27462
|
+
if (typeof chainPubkey !== "string" || !COMPRESSED_PUBKEY_RE.test(chainPubkey)) {
|
|
27463
|
+
throw new Error(
|
|
27464
|
+
`computeDirectAddressFromChainPubkey: chainPubkey must be 66-char hex with 02/03 prefix, got "${String(chainPubkey).slice(0, 12)}..."`
|
|
27465
|
+
);
|
|
27466
|
+
}
|
|
27467
|
+
const tokenTypeBytes = Buffer.from(UNICITY_TOKEN_TYPE_HEX2, "hex");
|
|
27468
|
+
const tokenType = new import_TokenType5.TokenType(tokenTypeBytes);
|
|
27469
|
+
const publicKeyBytes = Buffer.from(chainPubkey, "hex");
|
|
27470
|
+
const predicateRef = await import_UnmaskedPredicateReference3.UnmaskedPredicateReference.create(
|
|
27471
|
+
tokenType,
|
|
27472
|
+
"secp256k1",
|
|
27473
|
+
publicKeyBytes,
|
|
27474
|
+
import_HashAlgorithm7.HashAlgorithm.SHA256
|
|
27475
|
+
);
|
|
27476
|
+
return (await predicateRef.toAddress()).toString();
|
|
27477
|
+
}
|
|
27478
|
+
|
|
27479
|
+
// core/Sphere.ts
|
|
26969
27480
|
function isValidNametag2(nametag) {
|
|
26970
27481
|
if ((0, import_nostr_js_sdk5.isPhoneNumber)(nametag)) return true;
|
|
26971
27482
|
return /^[a-z0-9_-]{3,20}$/.test(nametag);
|
|
26972
27483
|
}
|
|
26973
|
-
var UNICITY_TOKEN_TYPE_HEX2 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
|
|
26974
27484
|
async function deriveL3PredicateAddress(privateKey) {
|
|
26975
27485
|
const secret = Buffer.from(privateKey, "hex");
|
|
26976
27486
|
const signingService = await import_SigningService2.SigningService.createFromSecret(secret);
|
|
26977
|
-
const
|
|
26978
|
-
|
|
26979
|
-
const predicateRef = import_UnmaskedPredicateReference3.UnmaskedPredicateReference.create(
|
|
26980
|
-
tokenType,
|
|
26981
|
-
signingService.algorithm,
|
|
26982
|
-
signingService.publicKey,
|
|
26983
|
-
import_HashAlgorithm7.HashAlgorithm.SHA256
|
|
26984
|
-
);
|
|
26985
|
-
return (await (await predicateRef).toAddress()).toString();
|
|
27487
|
+
const pubkeyHex = Buffer.from(signingService.publicKey).toString("hex");
|
|
27488
|
+
return computeDirectAddressFromChainPubkey(pubkeyHex);
|
|
26986
27489
|
}
|
|
26987
27490
|
var Sphere = class _Sphere {
|
|
26988
27491
|
// Singleton
|
|
@@ -30367,6 +30870,7 @@ async function runCustomCheck(name, checkFn, timeoutMs) {
|
|
|
30367
30870
|
base58Encode,
|
|
30368
30871
|
bytesToHex,
|
|
30369
30872
|
checkNetworkHealth,
|
|
30873
|
+
computeDirectAddressFromChainPubkey,
|
|
30370
30874
|
computeHash160,
|
|
30371
30875
|
convertBits,
|
|
30372
30876
|
createAddress,
|