@unicitylabs/sphere-sdk 0.7.1-dev.3 → 0.7.2
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 +5 -77
- package/dist/core/index.cjs +536 -26
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +113 -1
- package/dist/core/index.d.ts +113 -1
- package/dist/core/index.js +535 -26
- 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/browser/ipfs.cjs.map +1 -1
- package/dist/impl/browser/ipfs.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 +631 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +233 -1
- package/dist/index.d.ts +233 -1
- package/dist/index.js +615 -26
- package/dist/index.js.map +1 -1
- package/dist/l1/index.cjs.map +1 -1
- package/dist/l1/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -943,6 +943,7 @@ __export(index_exports, {
|
|
|
943
943
|
buildTxfStorageData: () => buildTxfStorageData,
|
|
944
944
|
bytesToHex: () => bytesToHex3,
|
|
945
945
|
checkNetworkHealth: () => checkNetworkHealth,
|
|
946
|
+
coinIdsMatch: () => coinIdsMatch,
|
|
946
947
|
computeSwapId: () => computeSwapId,
|
|
947
948
|
countCommittedTransactions: () => countCommittedTransactions,
|
|
948
949
|
createAddress: () => createAddress,
|
|
@@ -961,22 +962,34 @@ __export(index_exports, {
|
|
|
961
962
|
createSwapModule: () => createSwapModule,
|
|
962
963
|
createTokenValidator: () => createTokenValidator,
|
|
963
964
|
decodeBech32: () => decodeBech32,
|
|
965
|
+
decrypt: () => decrypt2,
|
|
964
966
|
decryptCMasterKey: () => decryptCMasterKey,
|
|
967
|
+
decryptJson: () => decryptJson,
|
|
968
|
+
decryptMnemonic: () => decryptMnemonic,
|
|
965
969
|
decryptNametag: () => import_nostr_js_sdk6.decryptNametag,
|
|
966
970
|
decryptPrivateKey: () => decryptPrivateKey,
|
|
971
|
+
decryptSimple: () => decryptSimple,
|
|
967
972
|
decryptTextFormatKey: () => decryptTextFormatKey,
|
|
973
|
+
decryptWallet: () => decryptWallet,
|
|
974
|
+
decryptWithSalt: () => decryptWithSalt,
|
|
968
975
|
deriveAddressInfo: () => deriveAddressInfo,
|
|
969
976
|
deriveChildKey: () => deriveChildKey,
|
|
970
977
|
deriveKeyAtPath: () => deriveKeyAtPath,
|
|
971
978
|
doubleSha256: () => doubleSha256,
|
|
972
979
|
encodeBech32: () => encodeBech32,
|
|
980
|
+
encrypt: () => encrypt2,
|
|
981
|
+
encryptMnemonic: () => encryptMnemonic,
|
|
973
982
|
encryptNametag: () => import_nostr_js_sdk6.encryptNametag,
|
|
983
|
+
encryptSimple: () => encryptSimple,
|
|
984
|
+
encryptWallet: () => encryptWallet,
|
|
974
985
|
extractFromText: () => extractFromText,
|
|
975
986
|
findPattern: () => findPattern,
|
|
976
987
|
forkedKeyFromTokenIdAndState: () => forkedKeyFromTokenIdAndState,
|
|
977
988
|
formatAmount: () => formatAmount,
|
|
989
|
+
generateAddressFromMasterKey: () => generateAddressFromMasterKey,
|
|
978
990
|
generateMasterKey: () => generateMasterKey,
|
|
979
991
|
generateMnemonic: () => generateMnemonic2,
|
|
992
|
+
generatePrivateKey: () => generatePrivateKey,
|
|
980
993
|
getAddressHrp: () => getAddressHrp,
|
|
981
994
|
getAddressId: () => getAddressId,
|
|
982
995
|
getAddressStorageKey: () => getAddressStorageKey,
|
|
@@ -999,6 +1012,7 @@ __export(index_exports, {
|
|
|
999
1012
|
hashNametag: () => import_nostr_js_sdk6.hashNametag,
|
|
1000
1013
|
hashSignMessage: () => hashSignMessage,
|
|
1001
1014
|
hexToBytes: () => hexToBytes2,
|
|
1015
|
+
hexToWIF: () => hexToWIF,
|
|
1002
1016
|
identityFromMnemonicSync: () => identityFromMnemonicSync,
|
|
1003
1017
|
initSphere: () => initSphere,
|
|
1004
1018
|
isArchivedKey: () => isArchivedKey,
|
|
@@ -1028,6 +1042,7 @@ __export(index_exports, {
|
|
|
1028
1042
|
logger: () => logger,
|
|
1029
1043
|
mnemonicToSeedSync: () => mnemonicToSeedSync2,
|
|
1030
1044
|
normalizeAddress: () => normalizeAddress,
|
|
1045
|
+
normalizeCoinId: () => normalizeCoinId,
|
|
1031
1046
|
normalizeNametag: () => import_nostr_js_sdk6.normalizeNametag,
|
|
1032
1047
|
normalizeSdkTokenToStorage: () => normalizeSdkTokenToStorage,
|
|
1033
1048
|
objectToTxf: () => objectToTxf,
|
|
@@ -1041,6 +1056,7 @@ __export(index_exports, {
|
|
|
1041
1056
|
randomBytes: () => randomBytes2,
|
|
1042
1057
|
randomHex: () => randomHex,
|
|
1043
1058
|
randomUUID: () => randomUUID,
|
|
1059
|
+
recoverPubkeyFromSignature: () => recoverPubkeyFromSignature,
|
|
1044
1060
|
ripemd160: () => ripemd160,
|
|
1045
1061
|
sha256: () => sha2562,
|
|
1046
1062
|
signMessage: () => signMessage,
|
|
@@ -5093,6 +5109,27 @@ function verifySignedMessage(message, signature, expectedPubkey) {
|
|
|
5093
5109
|
return false;
|
|
5094
5110
|
}
|
|
5095
5111
|
}
|
|
5112
|
+
function recoverPubkeyFromSignature(message, signature) {
|
|
5113
|
+
if (signature.length !== 130) {
|
|
5114
|
+
throw new SphereError(
|
|
5115
|
+
`Invalid signature length: expected 130 hex chars, got ${signature.length}`,
|
|
5116
|
+
"SIGNING_ERROR"
|
|
5117
|
+
);
|
|
5118
|
+
}
|
|
5119
|
+
const v = parseInt(signature.slice(0, 2), 16) - 31;
|
|
5120
|
+
const r = signature.slice(2, 66);
|
|
5121
|
+
const s = signature.slice(66, 130);
|
|
5122
|
+
if (v < 0 || v > 3) {
|
|
5123
|
+
throw new SphereError(
|
|
5124
|
+
`Invalid recovery byte: v=${v} out of range [0..3]`,
|
|
5125
|
+
"SIGNING_ERROR"
|
|
5126
|
+
);
|
|
5127
|
+
}
|
|
5128
|
+
const hashHex = hashSignMessage(message);
|
|
5129
|
+
const hashBytes = Buffer.from(hashHex, "hex");
|
|
5130
|
+
const recovered = ec.recoverPubKey(hashBytes, { r, s }, v);
|
|
5131
|
+
return recovered.encode("hex", true);
|
|
5132
|
+
}
|
|
5096
5133
|
|
|
5097
5134
|
// l1/crypto.ts
|
|
5098
5135
|
var import_crypto_js3 = __toESM(require("crypto-js"), 1);
|
|
@@ -12513,6 +12550,132 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
12513
12550
|
};
|
|
12514
12551
|
}
|
|
12515
12552
|
}
|
|
12553
|
+
/**
|
|
12554
|
+
* Mint a fungible token directly to this wallet (genesis mint).
|
|
12555
|
+
*
|
|
12556
|
+
* Useful for test setups that need to seed a wallet with specific token
|
|
12557
|
+
* balances WITHOUT depending on the testnet faucet HTTP service. The
|
|
12558
|
+
* resulting token has the canonical CoinId bytes (passed in `coinIdHex`)
|
|
12559
|
+
* — when those bytes match a registered symbol in the TokenRegistry,
|
|
12560
|
+
* the token shows up under the symbol's name (e.g. "UCT"). There is no
|
|
12561
|
+
* cryptographic restriction on which key may issue a given CoinId; the
|
|
12562
|
+
* aggregator records the mint regardless of issuer identity.
|
|
12563
|
+
*
|
|
12564
|
+
* The flow:
|
|
12565
|
+
* 1. Generate a random TokenId.
|
|
12566
|
+
* 2. Build TokenCoinData with [(coinId, amount)].
|
|
12567
|
+
* 3. Build MintTransactionData with recipient = self (UnmaskedPredicate
|
|
12568
|
+
* from this wallet's signing service).
|
|
12569
|
+
* 4. Submit MintCommitment to the aggregator.
|
|
12570
|
+
* 5. Wait for the inclusion proof.
|
|
12571
|
+
* 6. Construct an SDK Token via Token.mint().
|
|
12572
|
+
* 7. Convert to wallet Token format and call addToken().
|
|
12573
|
+
*
|
|
12574
|
+
* @param coinIdHex - 64-char lowercase hex CoinId. Must match the bytes
|
|
12575
|
+
* used by the registered symbol if you want the wallet to recognize
|
|
12576
|
+
* the token as that symbol (e.g. UCT's coinId from the public registry).
|
|
12577
|
+
* @param amount - Amount in smallest units (multiply by 10^decimals
|
|
12578
|
+
* when converting from human values).
|
|
12579
|
+
* @returns Result with the resulting wallet Token and its on-chain id.
|
|
12580
|
+
*/
|
|
12581
|
+
async mintFungibleToken(coinIdHex, amount) {
|
|
12582
|
+
this.ensureInitialized();
|
|
12583
|
+
const stClient = this.deps.oracle.getStateTransitionClient?.();
|
|
12584
|
+
if (!stClient) {
|
|
12585
|
+
return { success: false, error: "State transition client not available" };
|
|
12586
|
+
}
|
|
12587
|
+
const trustBase = this.deps.oracle.getTrustBase?.();
|
|
12588
|
+
if (!trustBase) {
|
|
12589
|
+
return { success: false, error: "Trust base not available" };
|
|
12590
|
+
}
|
|
12591
|
+
try {
|
|
12592
|
+
const signingService = await this.createSigningService();
|
|
12593
|
+
const { TokenId: TokenId5 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenId");
|
|
12594
|
+
const { TokenCoinData: TokenCoinData3 } = await import("@unicitylabs/state-transition-sdk/lib/token/fungible/TokenCoinData");
|
|
12595
|
+
const { UnmaskedPredicateReference: UnmaskedPredicateReference4 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
|
|
12596
|
+
const tokenTypeBytes = fromHex4("f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509");
|
|
12597
|
+
const tokenType = new import_TokenType3.TokenType(tokenTypeBytes);
|
|
12598
|
+
const tokenIdBytes = new Uint8Array(32);
|
|
12599
|
+
crypto.getRandomValues(tokenIdBytes);
|
|
12600
|
+
const tokenId = new TokenId5(tokenIdBytes);
|
|
12601
|
+
const coinIdBytes = fromHex4(coinIdHex);
|
|
12602
|
+
const coinId = new import_CoinId4.CoinId(coinIdBytes);
|
|
12603
|
+
const coinData = TokenCoinData3.create([[coinId, amount]]);
|
|
12604
|
+
const addressRef = await UnmaskedPredicateReference4.create(
|
|
12605
|
+
tokenType,
|
|
12606
|
+
signingService.algorithm,
|
|
12607
|
+
signingService.publicKey,
|
|
12608
|
+
import_HashAlgorithm5.HashAlgorithm.SHA256
|
|
12609
|
+
);
|
|
12610
|
+
const ownerAddress = await addressRef.toAddress();
|
|
12611
|
+
const salt = new Uint8Array(32);
|
|
12612
|
+
crypto.getRandomValues(salt);
|
|
12613
|
+
const mintData = await import_MintTransactionData3.MintTransactionData.create(
|
|
12614
|
+
tokenId,
|
|
12615
|
+
tokenType,
|
|
12616
|
+
null,
|
|
12617
|
+
// tokenData: no metadata
|
|
12618
|
+
coinData,
|
|
12619
|
+
// fungible coin data
|
|
12620
|
+
ownerAddress,
|
|
12621
|
+
// recipient = self
|
|
12622
|
+
salt,
|
|
12623
|
+
null,
|
|
12624
|
+
// recipientDataHash
|
|
12625
|
+
null
|
|
12626
|
+
// reason: null (genesis, no burn predecessor)
|
|
12627
|
+
);
|
|
12628
|
+
const commitment = await import_MintCommitment3.MintCommitment.create(mintData);
|
|
12629
|
+
const MAX_RETRIES = 3;
|
|
12630
|
+
let lastStatus;
|
|
12631
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
12632
|
+
const response = await stClient.submitMintCommitment(commitment);
|
|
12633
|
+
lastStatus = response.status;
|
|
12634
|
+
if (response.status === "SUCCESS" || response.status === "REQUEST_ID_EXISTS") break;
|
|
12635
|
+
if (attempt === MAX_RETRIES) {
|
|
12636
|
+
return { success: false, error: `Mint submit failed after ${MAX_RETRIES} attempts: ${response.status}` };
|
|
12637
|
+
}
|
|
12638
|
+
await new Promise((r) => setTimeout(r, 1e3 * attempt));
|
|
12639
|
+
}
|
|
12640
|
+
if (lastStatus !== "SUCCESS" && lastStatus !== "REQUEST_ID_EXISTS") {
|
|
12641
|
+
return { success: false, error: `Mint submit failed: ${lastStatus}` };
|
|
12642
|
+
}
|
|
12643
|
+
const inclusionProof = await (0, import_InclusionProofUtils5.waitInclusionProof)(trustBase, stClient, commitment);
|
|
12644
|
+
const genesisTransaction = commitment.toTransaction(inclusionProof);
|
|
12645
|
+
const predicate = await import_UnmaskedPredicate5.UnmaskedPredicate.create(
|
|
12646
|
+
tokenId,
|
|
12647
|
+
tokenType,
|
|
12648
|
+
signingService,
|
|
12649
|
+
import_HashAlgorithm5.HashAlgorithm.SHA256,
|
|
12650
|
+
salt
|
|
12651
|
+
);
|
|
12652
|
+
const tokenState = new import_TokenState5.TokenState(predicate, null);
|
|
12653
|
+
const sdkToken = await import_Token6.Token.mint(trustBase, tokenState, genesisTransaction);
|
|
12654
|
+
const tokenIdHex = tokenId.toJSON();
|
|
12655
|
+
const symbol = this.getCoinSymbol(coinIdHex);
|
|
12656
|
+
const name = this.getCoinName(coinIdHex);
|
|
12657
|
+
const decimals = this.getCoinDecimals(coinIdHex);
|
|
12658
|
+
const iconUrl = this.getCoinIconUrl(coinIdHex);
|
|
12659
|
+
const uiToken = {
|
|
12660
|
+
id: tokenIdHex,
|
|
12661
|
+
coinId: coinIdHex,
|
|
12662
|
+
symbol,
|
|
12663
|
+
name,
|
|
12664
|
+
decimals,
|
|
12665
|
+
...iconUrl !== void 0 ? { iconUrl } : {},
|
|
12666
|
+
amount: amount.toString(),
|
|
12667
|
+
status: "confirmed",
|
|
12668
|
+
createdAt: Date.now(),
|
|
12669
|
+
updatedAt: Date.now(),
|
|
12670
|
+
sdkData: JSON.stringify(sdkToken.toJSON())
|
|
12671
|
+
};
|
|
12672
|
+
await this.addToken(uiToken);
|
|
12673
|
+
return { success: true, token: uiToken, tokenId: tokenIdHex };
|
|
12674
|
+
} catch (err) {
|
|
12675
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
12676
|
+
return { success: false, error: `Local mint failed: ${msg}` };
|
|
12677
|
+
}
|
|
12678
|
+
}
|
|
12516
12679
|
/**
|
|
12517
12680
|
* Check if a nametag is available for minting
|
|
12518
12681
|
* @param nametag - The nametag to check (e.g., "alice" or "@alice")
|
|
@@ -18719,6 +18882,24 @@ var AccountingModule = class _AccountingModule {
|
|
|
18719
18882
|
dirtyLedgerEntries = /* @__PURE__ */ new Set();
|
|
18720
18883
|
/** Count of unknown (not in invoiceTermsCache) invoice IDs in the ledger. */
|
|
18721
18884
|
unknownLedgerCount = 0;
|
|
18885
|
+
/**
|
|
18886
|
+
* Per-unknown-invoice first-seen timestamp for TTL eviction.
|
|
18887
|
+
*
|
|
18888
|
+
* W1 (steelman round-4): without TTL, an attacker who can deliver 500
|
|
18889
|
+
* inbound transfers with synthesized memo invoiceIds permanently exhausts
|
|
18890
|
+
* the unknown-ledger cap, after which legitimate orphan transfers (out-of-
|
|
18891
|
+
* order delivery for real swaps) are silently dropped at the cap-check.
|
|
18892
|
+
*
|
|
18893
|
+
* Round-5 perf: gated by `unknownLedgerNextSweepMs` to amortize the
|
|
18894
|
+
* sweep cost. The naive every-call sweep is O(N) where N=cap=500;
|
|
18895
|
+
* combined with the per-token cleanup loop inside the sweep it became
|
|
18896
|
+
* O(N×M) on every transfer under flood. Now we sweep at most every
|
|
18897
|
+
* `UNKNOWN_LEDGER_SWEEP_INTERVAL_MS` (60s) UNLESS the cap is currently
|
|
18898
|
+
* full, in which case we sweep on each call (the only path that can
|
|
18899
|
+
* actually drop a legitimate orphan).
|
|
18900
|
+
*/
|
|
18901
|
+
unknownLedgerFirstSeen = /* @__PURE__ */ new Map();
|
|
18902
|
+
unknownLedgerNextSweepMs = 0;
|
|
18722
18903
|
/** W17: Tracks whether tokenScanState has been mutated since last flush. */
|
|
18723
18904
|
tokenScanDirty = false;
|
|
18724
18905
|
/** W2 fix: Serialization guard for _flushDirtyLedgerEntries. */
|
|
@@ -19709,6 +19890,7 @@ var AccountingModule = class _AccountingModule {
|
|
|
19709
19890
|
}
|
|
19710
19891
|
if (this.invoiceLedger.has(tokenId) && !this.invoiceTermsCache.has(tokenId)) {
|
|
19711
19892
|
this.unknownLedgerCount = Math.max(0, this.unknownLedgerCount - 1);
|
|
19893
|
+
this.unknownLedgerFirstSeen.delete(tokenId);
|
|
19712
19894
|
}
|
|
19713
19895
|
this.invoiceTermsCache.set(tokenId, terms);
|
|
19714
19896
|
this._addToHashIndex(tokenId);
|
|
@@ -19977,6 +20159,29 @@ var AccountingModule = class _AccountingModule {
|
|
|
19977
20159
|
closed: this.closedInvoices.has(invoiceId)
|
|
19978
20160
|
};
|
|
19979
20161
|
}
|
|
20162
|
+
/**
|
|
20163
|
+
* Return the set of token IDs that are currently linked to the given
|
|
20164
|
+
* invoice. Populated by both the on-chain `_processTokenTransactions`
|
|
20165
|
+
* path (tokens with `inv:` references) and the transport-memo orphan
|
|
20166
|
+
* buffering path in `_handleIncomingTransfer`.
|
|
20167
|
+
*
|
|
20168
|
+
* Used by callers that want to scope per-invoice operations (e.g.
|
|
20169
|
+
* SwapModule.verifyPayout's L3 validation) to only the tokens that
|
|
20170
|
+
* cover this invoice — avoiding false negatives when the wallet
|
|
20171
|
+
* contains unrelated tokens of the same currency in unconfirmed or
|
|
20172
|
+
* spent state.
|
|
20173
|
+
*
|
|
20174
|
+
* Returns an empty set if no tokens are currently linked.
|
|
20175
|
+
*/
|
|
20176
|
+
getTokenIdsForInvoice(invoiceId) {
|
|
20177
|
+
const result = /* @__PURE__ */ new Set();
|
|
20178
|
+
for (const [tokenId, invoiceIds] of this.tokenInvoiceMap) {
|
|
20179
|
+
if (invoiceIds.has(invoiceId)) {
|
|
20180
|
+
result.add(tokenId);
|
|
20181
|
+
}
|
|
20182
|
+
}
|
|
20183
|
+
return result;
|
|
20184
|
+
}
|
|
19980
20185
|
/**
|
|
19981
20186
|
* Explicitly close an invoice. Only target parties may close (§8.3).
|
|
19982
20187
|
*
|
|
@@ -20296,6 +20501,7 @@ var AccountingModule = class _AccountingModule {
|
|
|
20296
20501
|
ledger.set(entryKey, forwardRef);
|
|
20297
20502
|
this.dirtyLedgerEntries.add(invoiceId);
|
|
20298
20503
|
this.balanceCache.delete(invoiceId);
|
|
20504
|
+
await this._persistProvisionalAndVerify(invoiceId, "payInvoice");
|
|
20299
20505
|
}
|
|
20300
20506
|
return result;
|
|
20301
20507
|
} finally {
|
|
@@ -20463,6 +20669,7 @@ var AccountingModule = class _AccountingModule {
|
|
|
20463
20669
|
this.dirtyLedgerEntries.add(invoiceId);
|
|
20464
20670
|
}
|
|
20465
20671
|
this.balanceCache.delete(invoiceId);
|
|
20672
|
+
await this._persistProvisionalAndVerify(invoiceId, "returnInvoicePayment");
|
|
20466
20673
|
}
|
|
20467
20674
|
return result;
|
|
20468
20675
|
} finally {
|
|
@@ -21542,9 +21749,30 @@ var AccountingModule = class _AccountingModule {
|
|
|
21542
21749
|
continue;
|
|
21543
21750
|
}
|
|
21544
21751
|
innerMap.set(entryKey, ref);
|
|
21545
|
-
|
|
21752
|
+
const HEX_64 = /^[a-f0-9]{64}$/i;
|
|
21753
|
+
if (entryKey.startsWith("mt:")) {
|
|
21754
|
+
const firstColon = entryKey.indexOf(":");
|
|
21755
|
+
const secondColon = entryKey.indexOf(":", firstColon + 1);
|
|
21756
|
+
if (secondColon > firstColon + 1) {
|
|
21757
|
+
const tokenIdFromKey2 = entryKey.slice(firstColon + 1, secondColon);
|
|
21758
|
+
if (HEX_64.test(tokenIdFromKey2)) {
|
|
21759
|
+
this._addToTokenInvoiceMap(tokenIdFromKey2, invoiceId);
|
|
21760
|
+
}
|
|
21761
|
+
}
|
|
21762
|
+
} else if (entryKey.startsWith("synthetic:")) {
|
|
21763
|
+
const afterPrefix = entryKey.slice("synthetic:".length);
|
|
21764
|
+
const tokenIdEnd = afterPrefix.indexOf(":");
|
|
21765
|
+
if (tokenIdEnd > 0) {
|
|
21766
|
+
const tokenId = afterPrefix.slice(0, tokenIdEnd);
|
|
21767
|
+
if (HEX_64.test(tokenId) && ref.transferId !== tokenId) {
|
|
21768
|
+
this._addToTokenInvoiceMap(tokenId, invoiceId);
|
|
21769
|
+
}
|
|
21770
|
+
}
|
|
21771
|
+
} else if (!ref.transferId.startsWith("provisional:") && ref.transferId.includes(":")) {
|
|
21546
21772
|
const tokenIdFromRef = ref.transferId.slice(0, ref.transferId.indexOf(":"));
|
|
21547
|
-
|
|
21773
|
+
if (HEX_64.test(tokenIdFromRef)) {
|
|
21774
|
+
this._addToTokenInvoiceMap(tokenIdFromRef, invoiceId);
|
|
21775
|
+
}
|
|
21548
21776
|
}
|
|
21549
21777
|
}
|
|
21550
21778
|
} catch (err) {
|
|
@@ -21745,7 +21973,14 @@ var AccountingModule = class _AccountingModule {
|
|
|
21745
21973
|
}
|
|
21746
21974
|
}
|
|
21747
21975
|
for (const [existingKey, existingRef] of ledger) {
|
|
21748
|
-
if (existingKey.startsWith("synthetic:") && existingRef.coinId === coinId && existingRef.paymentDirection === paymentDirection) {
|
|
21976
|
+
if ((existingKey.startsWith("synthetic:") || existingKey.startsWith("synthetic-tx:")) && existingRef.coinId === coinId && existingRef.paymentDirection === paymentDirection) {
|
|
21977
|
+
keysToDelete.push(existingKey);
|
|
21978
|
+
break;
|
|
21979
|
+
}
|
|
21980
|
+
}
|
|
21981
|
+
const mtPrefix = `mt:${tokenId}:`;
|
|
21982
|
+
for (const [existingKey, existingRef] of ledger) {
|
|
21983
|
+
if (existingKey.startsWith(mtPrefix) && existingRef.coinId === coinId && existingRef.paymentDirection === paymentDirection) {
|
|
21749
21984
|
keysToDelete.push(existingKey);
|
|
21750
21985
|
break;
|
|
21751
21986
|
}
|
|
@@ -21862,6 +22097,49 @@ var AccountingModule = class _AccountingModule {
|
|
|
21862
22097
|
});
|
|
21863
22098
|
}
|
|
21864
22099
|
}
|
|
22100
|
+
/**
|
|
22101
|
+
* Synchronously persist any pending provisional ledger entry for `invoiceId`
|
|
22102
|
+
* before returning to the caller. Used by `payInvoice` and
|
|
22103
|
+
* `returnInvoicePayment` to make the in-memory provisional entry durable
|
|
22104
|
+
* inside the same per-invoice gate that wrote it, closing the
|
|
22105
|
+
* crash-mid-conclude race that produces over-coverage on receivers.
|
|
22106
|
+
*
|
|
22107
|
+
* Implementation:
|
|
22108
|
+
* 1. Schedule a flush via the existing `_flushPromise` chain (so
|
|
22109
|
+
* concurrent `_handleTokenChange` callers waiting on the chain
|
|
22110
|
+
* observe ours as part of the sequence).
|
|
22111
|
+
* 2. Await OUR flush directly — NOT `_drainFlushPromise()`, which would
|
|
22112
|
+
* spin while concurrent token changes keep extending the chain and
|
|
22113
|
+
* hold the per-invoice gate for an unbounded number of additional
|
|
22114
|
+
* flushes. We only need OUR provisional entry durable.
|
|
22115
|
+
* 3. `_flushDirtyLedgerEntries` swallows per-invoice `storage.set`
|
|
22116
|
+
* rejections internally (sets a local `step1Failed` flag), leaving
|
|
22117
|
+
* the dirty entry on the set without re-throwing. So we post-check
|
|
22118
|
+
* `dirtyLedgerEntries.has(invoiceId)` and throw a `STORAGE_ERROR`
|
|
22119
|
+
* `SphereError` if our entry is still dirty — propagating to the
|
|
22120
|
+
* caller so they learn about the durability failure rather than
|
|
22121
|
+
* receiving a silent "success" return that lies on disk.
|
|
22122
|
+
*
|
|
22123
|
+
* @param invoiceId The invoice whose provisional entry must be durable.
|
|
22124
|
+
* @param callContext Used in the error message so the caller is named
|
|
22125
|
+
* ('payInvoice' / 'returnInvoicePayment') without
|
|
22126
|
+
* forcing a stack-trace inspection.
|
|
22127
|
+
*/
|
|
22128
|
+
async _persistProvisionalAndVerify(invoiceId, callContext) {
|
|
22129
|
+
const flushTrigger = (this._flushPromise ?? Promise.resolve()).then(() => this._flushDirtyLedgerEntries());
|
|
22130
|
+
const tracked = flushTrigger.catch(() => {
|
|
22131
|
+
}).finally(() => {
|
|
22132
|
+
if (this._flushPromise === tracked) this._flushPromise = null;
|
|
22133
|
+
});
|
|
22134
|
+
this._flushPromise = tracked;
|
|
22135
|
+
await flushTrigger;
|
|
22136
|
+
if (this.dirtyLedgerEntries.has(invoiceId)) {
|
|
22137
|
+
throw new SphereError(
|
|
22138
|
+
`${callContext}: provisional ledger entry for invoice ${invoiceId} failed to persist \u2014 caller should retry`,
|
|
22139
|
+
"STORAGE_ERROR"
|
|
22140
|
+
);
|
|
22141
|
+
}
|
|
22142
|
+
}
|
|
21865
22143
|
// ===========================================================================
|
|
21866
22144
|
// Internal: Event handlers
|
|
21867
22145
|
// ===========================================================================
|
|
@@ -21922,13 +22200,96 @@ var AccountingModule = class _AccountingModule {
|
|
|
21922
22200
|
}
|
|
21923
22201
|
}
|
|
21924
22202
|
if (!this.invoiceTermsCache.has(invoiceId)) {
|
|
21925
|
-
|
|
21926
|
-
|
|
21927
|
-
invoiceId
|
|
21928
|
-
|
|
21929
|
-
|
|
21930
|
-
|
|
21931
|
-
|
|
22203
|
+
let gracefullyGraduated = false;
|
|
22204
|
+
await this.withInvoiceGate(invoiceId, async () => {
|
|
22205
|
+
if (this.invoiceTermsCache.has(invoiceId)) {
|
|
22206
|
+
gracefullyGraduated = true;
|
|
22207
|
+
return;
|
|
22208
|
+
}
|
|
22209
|
+
const syntheticRef = this._buildSyntheticTransferRef(
|
|
22210
|
+
transfer,
|
|
22211
|
+
invoiceId,
|
|
22212
|
+
paymentDirection,
|
|
22213
|
+
confirmed
|
|
22214
|
+
);
|
|
22215
|
+
deps.emitEvent("invoice:unknown_reference", { invoiceId, transfer: syntheticRef });
|
|
22216
|
+
const MAX_UNKNOWN_INVOICE_IDS = 500;
|
|
22217
|
+
const UNKNOWN_LEDGER_TTL_MS = 30 * 60 * 1e3;
|
|
22218
|
+
const MAX_ORPHAN_ENTRIES_PER_INVOICE = 50;
|
|
22219
|
+
const UNKNOWN_LEDGER_SWEEP_INTERVAL_MS = 6e4;
|
|
22220
|
+
const nowMs = Date.now();
|
|
22221
|
+
const capFull = this.unknownLedgerCount >= MAX_UNKNOWN_INVOICE_IDS;
|
|
22222
|
+
const sweepDue = nowMs >= this.unknownLedgerNextSweepMs;
|
|
22223
|
+
if (this.unknownLedgerFirstSeen.size > 0 && (capFull || sweepDue)) {
|
|
22224
|
+
this.unknownLedgerNextSweepMs = nowMs + UNKNOWN_LEDGER_SWEEP_INTERVAL_MS;
|
|
22225
|
+
const expiredIds = [];
|
|
22226
|
+
for (const [unkId, firstSeen] of this.unknownLedgerFirstSeen) {
|
|
22227
|
+
if (nowMs - firstSeen > UNKNOWN_LEDGER_TTL_MS) {
|
|
22228
|
+
expiredIds.push(unkId);
|
|
22229
|
+
}
|
|
22230
|
+
}
|
|
22231
|
+
for (const expiredId of expiredIds) {
|
|
22232
|
+
if (!this.invoiceTermsCache.has(expiredId) && this.invoiceLedger.has(expiredId)) {
|
|
22233
|
+
this.invoiceLedger.delete(expiredId);
|
|
22234
|
+
this.unknownLedgerCount = Math.max(0, this.unknownLedgerCount - 1);
|
|
22235
|
+
for (const [tokenId, invoiceSet] of this.tokenInvoiceMap) {
|
|
22236
|
+
if (invoiceSet.has(expiredId)) {
|
|
22237
|
+
invoiceSet.delete(expiredId);
|
|
22238
|
+
if (invoiceSet.size === 0) this.tokenInvoiceMap.delete(tokenId);
|
|
22239
|
+
}
|
|
22240
|
+
}
|
|
22241
|
+
}
|
|
22242
|
+
this.unknownLedgerFirstSeen.delete(expiredId);
|
|
22243
|
+
}
|
|
22244
|
+
}
|
|
22245
|
+
if (!this.invoiceLedger.has(invoiceId)) {
|
|
22246
|
+
if (this.unknownLedgerCount >= MAX_UNKNOWN_INVOICE_IDS) {
|
|
22247
|
+
return;
|
|
22248
|
+
}
|
|
22249
|
+
this.invoiceLedger.set(invoiceId, /* @__PURE__ */ new Map());
|
|
22250
|
+
this.unknownLedgerCount++;
|
|
22251
|
+
this.unknownLedgerFirstSeen.set(invoiceId, nowMs);
|
|
22252
|
+
}
|
|
22253
|
+
const orphanLedger = this.invoiceLedger.get(invoiceId);
|
|
22254
|
+
let mtEntryCount = 0;
|
|
22255
|
+
for (const k of orphanLedger.keys()) {
|
|
22256
|
+
if (k.startsWith("mt:")) mtEntryCount++;
|
|
22257
|
+
}
|
|
22258
|
+
if (mtEntryCount >= MAX_ORPHAN_ENTRIES_PER_INVOICE) {
|
|
22259
|
+
return;
|
|
22260
|
+
}
|
|
22261
|
+
for (const token of transfer.tokens) {
|
|
22262
|
+
if (!token.id) continue;
|
|
22263
|
+
let onChainAttributed = false;
|
|
22264
|
+
const tokenKeyPrefix = `${token.id}:`;
|
|
22265
|
+
for (const existingKey of orphanLedger.keys()) {
|
|
22266
|
+
if (existingKey.startsWith(tokenKeyPrefix) && !existingKey.startsWith("mt:")) {
|
|
22267
|
+
onChainAttributed = true;
|
|
22268
|
+
break;
|
|
22269
|
+
}
|
|
22270
|
+
}
|
|
22271
|
+
if (!onChainAttributed) {
|
|
22272
|
+
if (mtEntryCount >= MAX_ORPHAN_ENTRIES_PER_INVOICE) {
|
|
22273
|
+
break;
|
|
22274
|
+
}
|
|
22275
|
+
const orphanKey = `mt:${token.id}:${transfer.id}`;
|
|
22276
|
+
if (!orphanLedger.has(orphanKey)) {
|
|
22277
|
+
orphanLedger.set(orphanKey, syntheticRef);
|
|
22278
|
+
mtEntryCount++;
|
|
22279
|
+
}
|
|
22280
|
+
}
|
|
22281
|
+
if (!this.tokenInvoiceMap.has(token.id)) {
|
|
22282
|
+
this.tokenInvoiceMap.set(token.id, /* @__PURE__ */ new Set());
|
|
22283
|
+
}
|
|
22284
|
+
this.tokenInvoiceMap.get(token.id).add(invoiceId);
|
|
22285
|
+
}
|
|
22286
|
+
this.dirtyLedgerEntries.add(invoiceId);
|
|
22287
|
+
this.balanceCache.delete(invoiceId);
|
|
22288
|
+
await this._flushDirtyLedgerEntries();
|
|
22289
|
+
});
|
|
22290
|
+
if (gracefullyGraduated) {
|
|
22291
|
+
await this._processInvoiceTransferEvent(transfer, invoiceId, paymentDirection, confirmed);
|
|
22292
|
+
}
|
|
21932
22293
|
return;
|
|
21933
22294
|
}
|
|
21934
22295
|
await this._processInvoiceTransferEvent(transfer, invoiceId, paymentDirection, confirmed);
|
|
@@ -22364,7 +22725,8 @@ var AccountingModule = class _AccountingModule {
|
|
|
22364
22725
|
}
|
|
22365
22726
|
const existingLedger = this.invoiceLedger.get(invoiceId);
|
|
22366
22727
|
const firstTokenId = transfer.tokens.find((t) => t.id)?.id;
|
|
22367
|
-
const syntheticKey = firstTokenId ? `synthetic:${firstTokenId}::${syntheticRef.coinId}` : `synthetic:${syntheticRef.transferId}::${syntheticRef.coinId}`;
|
|
22728
|
+
const syntheticKey = firstTokenId ? `synthetic:${firstTokenId}::${syntheticRef.coinId}` : `synthetic-tx:${syntheticRef.transferId}::${syntheticRef.coinId}`;
|
|
22729
|
+
let mutated = false;
|
|
22368
22730
|
if (!existingLedger.has(syntheticKey)) {
|
|
22369
22731
|
let hasRealEntry = false;
|
|
22370
22732
|
for (const tok of transfer.tokens) {
|
|
@@ -22379,8 +22741,25 @@ var AccountingModule = class _AccountingModule {
|
|
|
22379
22741
|
}
|
|
22380
22742
|
if (!hasRealEntry) {
|
|
22381
22743
|
existingLedger.set(syntheticKey, { ...syntheticRef });
|
|
22744
|
+
mutated = true;
|
|
22745
|
+
}
|
|
22746
|
+
}
|
|
22747
|
+
for (const tok of transfer.tokens) {
|
|
22748
|
+
if (!tok.id) continue;
|
|
22749
|
+
if (!this.tokenInvoiceMap.has(tok.id)) {
|
|
22750
|
+
this.tokenInvoiceMap.set(tok.id, /* @__PURE__ */ new Set());
|
|
22751
|
+
mutated = true;
|
|
22752
|
+
}
|
|
22753
|
+
const beforeSize = this.tokenInvoiceMap.get(tok.id).size;
|
|
22754
|
+
this.tokenInvoiceMap.get(tok.id).add(invoiceId);
|
|
22755
|
+
if (this.tokenInvoiceMap.get(tok.id).size !== beforeSize) {
|
|
22756
|
+
mutated = true;
|
|
22382
22757
|
}
|
|
22383
22758
|
}
|
|
22759
|
+
if (mutated) {
|
|
22760
|
+
this.dirtyLedgerEntries.add(invoiceId);
|
|
22761
|
+
this.balanceCache.delete(invoiceId);
|
|
22762
|
+
}
|
|
22384
22763
|
deps.emitEvent("invoice:payment", {
|
|
22385
22764
|
invoiceId,
|
|
22386
22765
|
transfer: syntheticRef,
|
|
@@ -22530,7 +22909,8 @@ var AccountingModule = class _AccountingModule {
|
|
|
22530
22909
|
this.invoiceLedger.set(invoiceId, /* @__PURE__ */ new Map());
|
|
22531
22910
|
}
|
|
22532
22911
|
const hLedger = this.invoiceLedger.get(invoiceId);
|
|
22533
|
-
const hKey = entry.tokenId ? `synthetic:${entry.tokenId}::${syntheticRef.coinId}` : `synthetic:${syntheticRef.transferId}::${syntheticRef.coinId}`;
|
|
22912
|
+
const hKey = entry.tokenId ? `synthetic:${entry.tokenId}::${syntheticRef.coinId}` : `synthetic-tx:${syntheticRef.transferId}::${syntheticRef.coinId}`;
|
|
22913
|
+
let hMutated = false;
|
|
22534
22914
|
if (!hLedger.has(hKey)) {
|
|
22535
22915
|
let hasRealEntry = false;
|
|
22536
22916
|
if (entry.tokenId) {
|
|
@@ -22543,8 +22923,24 @@ var AccountingModule = class _AccountingModule {
|
|
|
22543
22923
|
}
|
|
22544
22924
|
if (!hasRealEntry) {
|
|
22545
22925
|
hLedger.set(hKey, { ...syntheticRef });
|
|
22926
|
+
hMutated = true;
|
|
22546
22927
|
}
|
|
22547
22928
|
}
|
|
22929
|
+
if (entry.tokenId) {
|
|
22930
|
+
if (!this.tokenInvoiceMap.has(entry.tokenId)) {
|
|
22931
|
+
this.tokenInvoiceMap.set(entry.tokenId, /* @__PURE__ */ new Set());
|
|
22932
|
+
hMutated = true;
|
|
22933
|
+
}
|
|
22934
|
+
const beforeSize = this.tokenInvoiceMap.get(entry.tokenId).size;
|
|
22935
|
+
this.tokenInvoiceMap.get(entry.tokenId).add(invoiceId);
|
|
22936
|
+
if (this.tokenInvoiceMap.get(entry.tokenId).size !== beforeSize) {
|
|
22937
|
+
hMutated = true;
|
|
22938
|
+
}
|
|
22939
|
+
}
|
|
22940
|
+
if (hMutated) {
|
|
22941
|
+
this.dirtyLedgerEntries.add(invoiceId);
|
|
22942
|
+
this.balanceCache.delete(invoiceId);
|
|
22943
|
+
}
|
|
22548
22944
|
deps.emitEvent("invoice:payment", {
|
|
22549
22945
|
invoiceId,
|
|
22550
22946
|
transfer: syntheticRef,
|
|
@@ -25090,17 +25486,63 @@ var SwapModule = class {
|
|
|
25090
25486
|
for (const addr of allAddresses) {
|
|
25091
25487
|
myDirectAddresses.add(addr.directAddress);
|
|
25092
25488
|
}
|
|
25093
|
-
|
|
25094
|
-
|
|
25095
|
-
|
|
25096
|
-
|
|
25097
|
-
|
|
25489
|
+
const matchesPartyA = myDirectAddresses.has(swap.manifest.party_a_address);
|
|
25490
|
+
const matchesPartyB = myDirectAddresses.has(swap.manifest.party_b_address);
|
|
25491
|
+
if (matchesPartyA && matchesPartyB) {
|
|
25492
|
+
throw new SphereError(
|
|
25493
|
+
"Ambiguous party identity: local wallet matches both party_a_address and party_b_address",
|
|
25494
|
+
"SWAP_DEPOSIT_FAILED"
|
|
25495
|
+
);
|
|
25496
|
+
}
|
|
25497
|
+
let myExpectedCurrency;
|
|
25498
|
+
if (matchesPartyA) {
|
|
25499
|
+
myExpectedCurrency = swap.manifest.party_a_currency_to_change;
|
|
25500
|
+
} else if (matchesPartyB) {
|
|
25501
|
+
myExpectedCurrency = swap.manifest.party_b_currency_to_change;
|
|
25098
25502
|
} else {
|
|
25099
25503
|
throw new SphereError(
|
|
25100
25504
|
"Local wallet address does not match either party in the swap manifest",
|
|
25101
25505
|
"SWAP_DEPOSIT_FAILED"
|
|
25102
25506
|
);
|
|
25103
25507
|
}
|
|
25508
|
+
if (!myExpectedCurrency || myExpectedCurrency === "") {
|
|
25509
|
+
throw new SphereError(
|
|
25510
|
+
"Manifest currency_to_change is empty for this party",
|
|
25511
|
+
"SWAP_DEPOSIT_FAILED"
|
|
25512
|
+
);
|
|
25513
|
+
}
|
|
25514
|
+
const invoiceRefForAssetLookup = deps.accounting.getInvoice(swap.depositInvoiceId);
|
|
25515
|
+
if (!invoiceRefForAssetLookup) {
|
|
25516
|
+
throw new SphereError(
|
|
25517
|
+
"Deposit invoice not yet imported into accounting module",
|
|
25518
|
+
"SWAP_WRONG_STATE"
|
|
25519
|
+
);
|
|
25520
|
+
}
|
|
25521
|
+
const depositTarget = invoiceRefForAssetLookup.terms.targets[0];
|
|
25522
|
+
if (!depositTarget) {
|
|
25523
|
+
throw new SphereError(
|
|
25524
|
+
"Deposit invoice has no targets",
|
|
25525
|
+
"SWAP_DEPOSIT_FAILED"
|
|
25526
|
+
);
|
|
25527
|
+
}
|
|
25528
|
+
const assetIndex = depositTarget.assets.findIndex(
|
|
25529
|
+
(a) => a.coin !== void 0 && coinIdsMatch(a.coin[0], myExpectedCurrency)
|
|
25530
|
+
);
|
|
25531
|
+
if (assetIndex < 0) {
|
|
25532
|
+
throw new SphereError(
|
|
25533
|
+
`No asset matching expected currency ${myExpectedCurrency} found in deposit invoice`,
|
|
25534
|
+
"SWAP_DEPOSIT_FAILED"
|
|
25535
|
+
);
|
|
25536
|
+
}
|
|
25537
|
+
for (let i = assetIndex + 1; i < depositTarget.assets.length; i += 1) {
|
|
25538
|
+
const a = depositTarget.assets[i];
|
|
25539
|
+
if (a?.coin !== void 0 && coinIdsMatch(a.coin[0], myExpectedCurrency)) {
|
|
25540
|
+
throw new SphereError(
|
|
25541
|
+
`Ambiguous asset match in deposit invoice: slots ${assetIndex} and ${i} both match currency ${myExpectedCurrency}`,
|
|
25542
|
+
"SWAP_DEPOSIT_FAILED"
|
|
25543
|
+
);
|
|
25544
|
+
}
|
|
25545
|
+
}
|
|
25104
25546
|
return this.withSwapGate(swapId, async () => {
|
|
25105
25547
|
if (swap.progress !== "announced") {
|
|
25106
25548
|
throw new SphereError(
|
|
@@ -25206,7 +25648,6 @@ var SwapModule = class {
|
|
|
25206
25648
|
swap.updatedAt = Date.now();
|
|
25207
25649
|
this.clearLocalTimer(swap.swapId);
|
|
25208
25650
|
this.terminalSwapIds.add(swap.swapId);
|
|
25209
|
-
const entryIdx = this._storedTerminalEntries.length;
|
|
25210
25651
|
this._storedTerminalEntries.push({
|
|
25211
25652
|
swapId: swap.swapId,
|
|
25212
25653
|
progress: "failed",
|
|
@@ -25221,7 +25662,13 @@ var SwapModule = class {
|
|
|
25221
25662
|
swap.error = prevError;
|
|
25222
25663
|
swap.updatedAt = prevUpdatedAt;
|
|
25223
25664
|
this.terminalSwapIds.delete(swap.swapId);
|
|
25224
|
-
this._storedTerminalEntries.
|
|
25665
|
+
for (let i = this._storedTerminalEntries.length - 1; i >= 0; i--) {
|
|
25666
|
+
const entry = this._storedTerminalEntries[i];
|
|
25667
|
+
if (entry.swapId === swap.swapId && entry.progress === "failed") {
|
|
25668
|
+
this._storedTerminalEntries.splice(i, 1);
|
|
25669
|
+
break;
|
|
25670
|
+
}
|
|
25671
|
+
}
|
|
25225
25672
|
logger.warn(LOG_TAG3, `failPayout: persistSwap failed for ${swapId}; fraud detection will retry on next load:`, persistErr);
|
|
25226
25673
|
throw persistErr;
|
|
25227
25674
|
}
|
|
@@ -25265,9 +25712,25 @@ var SwapModule = class {
|
|
|
25265
25712
|
if (!targetStatus.coinAssets[0].isCovered) {
|
|
25266
25713
|
return returnFalse();
|
|
25267
25714
|
}
|
|
25268
|
-
|
|
25715
|
+
let netCoveredAmount;
|
|
25716
|
+
let expectedAmountBigInt;
|
|
25717
|
+
try {
|
|
25718
|
+
netCoveredAmount = BigInt(targetStatus.coinAssets[0].netCoveredAmount);
|
|
25719
|
+
expectedAmountBigInt = BigInt(expectedAmount);
|
|
25720
|
+
} catch (parseErr) {
|
|
25721
|
+
return failPayout(
|
|
25722
|
+
`MALFORMED_AMOUNT: failed to parse coverage amounts (netCoveredAmount=${targetStatus.coinAssets[0].netCoveredAmount}, expectedAmount=${expectedAmount}): ${parseErr instanceof Error ? parseErr.message : String(parseErr)}`
|
|
25723
|
+
);
|
|
25724
|
+
}
|
|
25725
|
+
if (netCoveredAmount < expectedAmountBigInt) {
|
|
25269
25726
|
return returnFalse();
|
|
25270
25727
|
}
|
|
25728
|
+
if (netCoveredAmount > expectedAmountBigInt) {
|
|
25729
|
+
const surplus = netCoveredAmount - expectedAmountBigInt;
|
|
25730
|
+
return failPayout(
|
|
25731
|
+
`OVER_COVERAGE: net=${netCoveredAmount.toString()}, expected=${expectedAmount}, surplus=${surplus.toString()} \u2014 surplus refund expected via auto-return; settlement halted`
|
|
25732
|
+
);
|
|
25733
|
+
}
|
|
25271
25734
|
const escrowAddr = swap.deal.escrowAddress ?? this.config.defaultEscrowAddress;
|
|
25272
25735
|
if (escrowAddr) {
|
|
25273
25736
|
const escrowPeer = await deps.resolve(escrowAddr);
|
|
@@ -25277,8 +25740,28 @@ var SwapModule = class {
|
|
|
25277
25740
|
}
|
|
25278
25741
|
const validationResult = await deps.payments.validate();
|
|
25279
25742
|
if (validationResult.invalid.length > 0) {
|
|
25280
|
-
|
|
25281
|
-
|
|
25743
|
+
const payoutTokenIds = deps.accounting.getTokenIdsForInvoice?.(swap.payoutInvoiceId) ?? /* @__PURE__ */ new Set();
|
|
25744
|
+
if (payoutTokenIds.size === 0) {
|
|
25745
|
+
logger.warn(
|
|
25746
|
+
LOG_TAG3,
|
|
25747
|
+
`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`
|
|
25748
|
+
);
|
|
25749
|
+
return returnFalse();
|
|
25750
|
+
}
|
|
25751
|
+
const relevantInvalid = validationResult.invalid.filter(
|
|
25752
|
+
(t) => payoutTokenIds.has(t.id)
|
|
25753
|
+
);
|
|
25754
|
+
if (relevantInvalid.length > 0) {
|
|
25755
|
+
logger.warn(
|
|
25756
|
+
LOG_TAG3,
|
|
25757
|
+
`verifyPayout for ${swapId.slice(0, 12)}: L3 validation found ${relevantInvalid.length} invalid token(s) covering this payout invoice \u2014 retry after wallet sync`
|
|
25758
|
+
);
|
|
25759
|
+
return returnFalse();
|
|
25760
|
+
}
|
|
25761
|
+
logger.debug(
|
|
25762
|
+
LOG_TAG3,
|
|
25763
|
+
`verifyPayout for ${swapId.slice(0, 12)}: ${validationResult.invalid.length} unrelated invalid token(s) ignored (not linked to this payout invoice)`
|
|
25764
|
+
);
|
|
25282
25765
|
}
|
|
25283
25766
|
if (swap.progress === "completed") {
|
|
25284
25767
|
swap.payoutVerified = true;
|
|
@@ -25457,8 +25940,22 @@ var SwapModule = class {
|
|
|
25457
25940
|
* @param dm - The incoming direct message.
|
|
25458
25941
|
*/
|
|
25459
25942
|
handleIncomingDM(dm) {
|
|
25943
|
+
if (dm.content.startsWith("{") && dm.content.includes('"invoice_delivery"')) {
|
|
25944
|
+
logger.warn(
|
|
25945
|
+
LOG_TAG3,
|
|
25946
|
+
`diag_swap_dm_arrived sender=${dm.senderPubkey.slice(0, 16)} length=${dm.content.length}`
|
|
25947
|
+
);
|
|
25948
|
+
}
|
|
25460
25949
|
const parsed = parseSwapDM(dm.content);
|
|
25461
|
-
if (!parsed)
|
|
25950
|
+
if (!parsed) {
|
|
25951
|
+
if (dm.content.startsWith("{") && dm.content.includes('"invoice_delivery"')) {
|
|
25952
|
+
logger.warn(
|
|
25953
|
+
LOG_TAG3,
|
|
25954
|
+
`diag_swap_dm_parse_rejected sender=${dm.senderPubkey.slice(0, 16)} prefix=${dm.content.slice(0, 80)}`
|
|
25955
|
+
);
|
|
25956
|
+
}
|
|
25957
|
+
return;
|
|
25958
|
+
}
|
|
25462
25959
|
void (async () => {
|
|
25463
25960
|
try {
|
|
25464
25961
|
switch (parsed.kind) {
|
|
@@ -25824,16 +26321,43 @@ var SwapModule = class {
|
|
|
25824
26321
|
// invoice_delivery (§12.4.2 + §12.4.3)
|
|
25825
26322
|
// ---------------------------------------------------------------
|
|
25826
26323
|
case "invoice_delivery": {
|
|
25827
|
-
|
|
26324
|
+
logger.warn(
|
|
26325
|
+
LOG_TAG3,
|
|
26326
|
+
`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)}`
|
|
26327
|
+
);
|
|
26328
|
+
if (!swapId) {
|
|
26329
|
+
logger.warn(LOG_TAG3, "diag_invoice_delivery_dropped reason=no_swap_id");
|
|
26330
|
+
return;
|
|
26331
|
+
}
|
|
25828
26332
|
const swap = this.swaps.get(swapId);
|
|
25829
|
-
if (!swap)
|
|
25830
|
-
|
|
26333
|
+
if (!swap) {
|
|
26334
|
+
logger.warn(
|
|
26335
|
+
LOG_TAG3,
|
|
26336
|
+
`diag_invoice_delivery_dropped reason=swap_not_in_map swap_id=${swapId.slice(0, 16)} known_swap_ids_count=${this.swaps.size}`
|
|
26337
|
+
);
|
|
26338
|
+
return;
|
|
26339
|
+
}
|
|
26340
|
+
if (!this.isFromExpectedEscrow(dm.senderPubkey, swap)) {
|
|
26341
|
+
logger.warn(
|
|
26342
|
+
LOG_TAG3,
|
|
26343
|
+
`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)}`
|
|
26344
|
+
);
|
|
26345
|
+
return;
|
|
26346
|
+
}
|
|
25831
26347
|
const deps = this.deps;
|
|
25832
26348
|
if (msg.invoice_type === "deposit") {
|
|
26349
|
+
logger.warn(
|
|
26350
|
+
LOG_TAG3,
|
|
26351
|
+
`diag_invoice_delivery_proceeding_to_import swap_id=${swapId.slice(0, 16)} progress=${swap.progress} invoice_id=${(msg.invoice_id ?? "").slice(0, 16)}`
|
|
26352
|
+
);
|
|
25833
26353
|
await this.withSwapGate(swapId, async () => {
|
|
25834
26354
|
if (isTerminalProgress(swap.progress)) return;
|
|
25835
26355
|
try {
|
|
25836
26356
|
await deps.accounting.importInvoice(msg.invoice_token);
|
|
26357
|
+
logger.warn(
|
|
26358
|
+
LOG_TAG3,
|
|
26359
|
+
`diag_invoice_imported swap_id=${swapId.slice(0, 16)} invoice_id=${(msg.invoice_id ?? "").slice(0, 16)} type=deposit`
|
|
26360
|
+
);
|
|
25837
26361
|
} catch (err) {
|
|
25838
26362
|
if (err instanceof SphereError && err.code === "INVOICE_ALREADY_EXISTS") {
|
|
25839
26363
|
logger.debug(LOG_TAG3, `Deposit invoice for swap ${swapId} already imported \u2014 relay re-delivery, continuing`);
|
|
@@ -26472,6 +26996,65 @@ init_constants();
|
|
|
26472
26996
|
var import_crypto_js6 = __toESM(require("crypto-js"), 1);
|
|
26473
26997
|
init_errors();
|
|
26474
26998
|
init_logger();
|
|
26999
|
+
var DEFAULT_ITERATIONS = 1e5;
|
|
27000
|
+
var KEY_SIZE = 256;
|
|
27001
|
+
var SALT_SIZE = 16;
|
|
27002
|
+
var IV_SIZE = 16;
|
|
27003
|
+
function deriveKey(password, salt, iterations) {
|
|
27004
|
+
return import_crypto_js6.default.PBKDF2(password, salt, {
|
|
27005
|
+
keySize: KEY_SIZE / 32,
|
|
27006
|
+
// WordArray uses 32-bit words
|
|
27007
|
+
iterations,
|
|
27008
|
+
hasher: import_crypto_js6.default.algo.SHA256
|
|
27009
|
+
});
|
|
27010
|
+
}
|
|
27011
|
+
function encrypt2(plaintext, password, options = {}) {
|
|
27012
|
+
const iterations = options.iterations ?? DEFAULT_ITERATIONS;
|
|
27013
|
+
const data = typeof plaintext === "string" ? plaintext : JSON.stringify(plaintext);
|
|
27014
|
+
const salt = import_crypto_js6.default.lib.WordArray.random(SALT_SIZE);
|
|
27015
|
+
const iv = import_crypto_js6.default.lib.WordArray.random(IV_SIZE);
|
|
27016
|
+
const key = deriveKey(password, salt, iterations);
|
|
27017
|
+
const encrypted = import_crypto_js6.default.AES.encrypt(data, key, {
|
|
27018
|
+
iv,
|
|
27019
|
+
mode: import_crypto_js6.default.mode.CBC,
|
|
27020
|
+
padding: import_crypto_js6.default.pad.Pkcs7
|
|
27021
|
+
});
|
|
27022
|
+
return {
|
|
27023
|
+
ciphertext: encrypted.ciphertext.toString(import_crypto_js6.default.enc.Base64),
|
|
27024
|
+
iv: iv.toString(import_crypto_js6.default.enc.Hex),
|
|
27025
|
+
salt: salt.toString(import_crypto_js6.default.enc.Hex),
|
|
27026
|
+
algorithm: "aes-256-cbc",
|
|
27027
|
+
kdf: "pbkdf2",
|
|
27028
|
+
iterations
|
|
27029
|
+
};
|
|
27030
|
+
}
|
|
27031
|
+
function decrypt2(encryptedData, password) {
|
|
27032
|
+
const salt = import_crypto_js6.default.enc.Hex.parse(encryptedData.salt);
|
|
27033
|
+
const iv = import_crypto_js6.default.enc.Hex.parse(encryptedData.iv);
|
|
27034
|
+
const key = deriveKey(password, salt, encryptedData.iterations);
|
|
27035
|
+
const ciphertext = import_crypto_js6.default.enc.Base64.parse(encryptedData.ciphertext);
|
|
27036
|
+
const cipherParams = import_crypto_js6.default.lib.CipherParams.create({
|
|
27037
|
+
ciphertext
|
|
27038
|
+
});
|
|
27039
|
+
const decrypted = import_crypto_js6.default.AES.decrypt(cipherParams, key, {
|
|
27040
|
+
iv,
|
|
27041
|
+
mode: import_crypto_js6.default.mode.CBC,
|
|
27042
|
+
padding: import_crypto_js6.default.pad.Pkcs7
|
|
27043
|
+
});
|
|
27044
|
+
const result = decrypted.toString(import_crypto_js6.default.enc.Utf8);
|
|
27045
|
+
if (!result) {
|
|
27046
|
+
throw new SphereError("Decryption failed: invalid password or corrupted data", "DECRYPTION_ERROR");
|
|
27047
|
+
}
|
|
27048
|
+
return result;
|
|
27049
|
+
}
|
|
27050
|
+
function decryptJson(encryptedData, password) {
|
|
27051
|
+
const decrypted = decrypt2(encryptedData, password);
|
|
27052
|
+
try {
|
|
27053
|
+
return JSON.parse(decrypted);
|
|
27054
|
+
} catch {
|
|
27055
|
+
throw new SphereError("Decryption failed: invalid JSON data", "DECRYPTION_ERROR");
|
|
27056
|
+
}
|
|
27057
|
+
}
|
|
26475
27058
|
function encryptSimple(plaintext, password) {
|
|
26476
27059
|
return import_crypto_js6.default.AES.encrypt(plaintext, password).toString();
|
|
26477
27060
|
}
|
|
@@ -26498,6 +27081,12 @@ function decryptWithSalt(ciphertext, password, salt) {
|
|
|
26498
27081
|
return null;
|
|
26499
27082
|
}
|
|
26500
27083
|
}
|
|
27084
|
+
function encryptMnemonic(mnemonic, password) {
|
|
27085
|
+
return encryptSimple(mnemonic, password);
|
|
27086
|
+
}
|
|
27087
|
+
function decryptMnemonic(encryptedMnemonic, password) {
|
|
27088
|
+
return decryptSimple(encryptedMnemonic, password);
|
|
27089
|
+
}
|
|
26501
27090
|
|
|
26502
27091
|
// core/scan.ts
|
|
26503
27092
|
init_logger();
|
|
@@ -31323,6 +31912,7 @@ function createPriceProvider(config) {
|
|
|
31323
31912
|
buildTxfStorageData,
|
|
31324
31913
|
bytesToHex,
|
|
31325
31914
|
checkNetworkHealth,
|
|
31915
|
+
coinIdsMatch,
|
|
31326
31916
|
computeSwapId,
|
|
31327
31917
|
countCommittedTransactions,
|
|
31328
31918
|
createAddress,
|
|
@@ -31341,22 +31931,34 @@ function createPriceProvider(config) {
|
|
|
31341
31931
|
createSwapModule,
|
|
31342
31932
|
createTokenValidator,
|
|
31343
31933
|
decodeBech32,
|
|
31934
|
+
decrypt,
|
|
31344
31935
|
decryptCMasterKey,
|
|
31936
|
+
decryptJson,
|
|
31937
|
+
decryptMnemonic,
|
|
31345
31938
|
decryptNametag,
|
|
31346
31939
|
decryptPrivateKey,
|
|
31940
|
+
decryptSimple,
|
|
31347
31941
|
decryptTextFormatKey,
|
|
31942
|
+
decryptWallet,
|
|
31943
|
+
decryptWithSalt,
|
|
31348
31944
|
deriveAddressInfo,
|
|
31349
31945
|
deriveChildKey,
|
|
31350
31946
|
deriveKeyAtPath,
|
|
31351
31947
|
doubleSha256,
|
|
31352
31948
|
encodeBech32,
|
|
31949
|
+
encrypt,
|
|
31950
|
+
encryptMnemonic,
|
|
31353
31951
|
encryptNametag,
|
|
31952
|
+
encryptSimple,
|
|
31953
|
+
encryptWallet,
|
|
31354
31954
|
extractFromText,
|
|
31355
31955
|
findPattern,
|
|
31356
31956
|
forkedKeyFromTokenIdAndState,
|
|
31357
31957
|
formatAmount,
|
|
31958
|
+
generateAddressFromMasterKey,
|
|
31358
31959
|
generateMasterKey,
|
|
31359
31960
|
generateMnemonic,
|
|
31961
|
+
generatePrivateKey,
|
|
31360
31962
|
getAddressHrp,
|
|
31361
31963
|
getAddressId,
|
|
31362
31964
|
getAddressStorageKey,
|
|
@@ -31379,6 +31981,7 @@ function createPriceProvider(config) {
|
|
|
31379
31981
|
hashNametag,
|
|
31380
31982
|
hashSignMessage,
|
|
31381
31983
|
hexToBytes,
|
|
31984
|
+
hexToWIF,
|
|
31382
31985
|
identityFromMnemonicSync,
|
|
31383
31986
|
initSphere,
|
|
31384
31987
|
isArchivedKey,
|
|
@@ -31408,6 +32011,7 @@ function createPriceProvider(config) {
|
|
|
31408
32011
|
logger,
|
|
31409
32012
|
mnemonicToSeedSync,
|
|
31410
32013
|
normalizeAddress,
|
|
32014
|
+
normalizeCoinId,
|
|
31411
32015
|
normalizeNametag,
|
|
31412
32016
|
normalizeSdkTokenToStorage,
|
|
31413
32017
|
objectToTxf,
|
|
@@ -31421,6 +32025,7 @@ function createPriceProvider(config) {
|
|
|
31421
32025
|
randomBytes,
|
|
31422
32026
|
randomHex,
|
|
31423
32027
|
randomUUID,
|
|
32028
|
+
recoverPubkeyFromSignature,
|
|
31424
32029
|
ripemd160,
|
|
31425
32030
|
sha256,
|
|
31426
32031
|
signMessage,
|