@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/index.cjs
CHANGED
|
@@ -894,6 +894,7 @@ var init_network = __esm({
|
|
|
894
894
|
// index.ts
|
|
895
895
|
var index_exports = {};
|
|
896
896
|
__export(index_exports, {
|
|
897
|
+
AuthVerificationError: () => AuthVerificationError,
|
|
897
898
|
COIN_TYPES: () => COIN_TYPES,
|
|
898
899
|
CoinGeckoPriceProvider: () => CoinGeckoPriceProvider,
|
|
899
900
|
CommunicationsModule: () => CommunicationsModule,
|
|
@@ -943,6 +944,8 @@ __export(index_exports, {
|
|
|
943
944
|
buildTxfStorageData: () => buildTxfStorageData,
|
|
944
945
|
bytesToHex: () => bytesToHex3,
|
|
945
946
|
checkNetworkHealth: () => checkNetworkHealth,
|
|
947
|
+
coinIdsMatch: () => coinIdsMatch,
|
|
948
|
+
computeDirectAddressFromChainPubkey: () => computeDirectAddressFromChainPubkey,
|
|
946
949
|
computeSwapId: () => computeSwapId,
|
|
947
950
|
countCommittedTransactions: () => countCommittedTransactions,
|
|
948
951
|
createAddress: () => createAddress,
|
|
@@ -961,22 +964,34 @@ __export(index_exports, {
|
|
|
961
964
|
createSwapModule: () => createSwapModule,
|
|
962
965
|
createTokenValidator: () => createTokenValidator,
|
|
963
966
|
decodeBech32: () => decodeBech32,
|
|
967
|
+
decrypt: () => decrypt2,
|
|
964
968
|
decryptCMasterKey: () => decryptCMasterKey,
|
|
969
|
+
decryptJson: () => decryptJson,
|
|
970
|
+
decryptMnemonic: () => decryptMnemonic,
|
|
965
971
|
decryptNametag: () => import_nostr_js_sdk6.decryptNametag,
|
|
966
972
|
decryptPrivateKey: () => decryptPrivateKey,
|
|
973
|
+
decryptSimple: () => decryptSimple,
|
|
967
974
|
decryptTextFormatKey: () => decryptTextFormatKey,
|
|
975
|
+
decryptWallet: () => decryptWallet,
|
|
976
|
+
decryptWithSalt: () => decryptWithSalt,
|
|
968
977
|
deriveAddressInfo: () => deriveAddressInfo,
|
|
969
978
|
deriveChildKey: () => deriveChildKey,
|
|
970
979
|
deriveKeyAtPath: () => deriveKeyAtPath,
|
|
971
980
|
doubleSha256: () => doubleSha256,
|
|
972
981
|
encodeBech32: () => encodeBech32,
|
|
982
|
+
encrypt: () => encrypt2,
|
|
983
|
+
encryptMnemonic: () => encryptMnemonic,
|
|
973
984
|
encryptNametag: () => import_nostr_js_sdk6.encryptNametag,
|
|
985
|
+
encryptSimple: () => encryptSimple,
|
|
986
|
+
encryptWallet: () => encryptWallet,
|
|
974
987
|
extractFromText: () => extractFromText,
|
|
975
988
|
findPattern: () => findPattern,
|
|
976
989
|
forkedKeyFromTokenIdAndState: () => forkedKeyFromTokenIdAndState,
|
|
977
990
|
formatAmount: () => formatAmount,
|
|
991
|
+
generateAddressFromMasterKey: () => generateAddressFromMasterKey,
|
|
978
992
|
generateMasterKey: () => generateMasterKey,
|
|
979
993
|
generateMnemonic: () => generateMnemonic2,
|
|
994
|
+
generatePrivateKey: () => generatePrivateKey,
|
|
980
995
|
getAddressHrp: () => getAddressHrp,
|
|
981
996
|
getAddressId: () => getAddressId,
|
|
982
997
|
getAddressStorageKey: () => getAddressStorageKey,
|
|
@@ -999,6 +1014,7 @@ __export(index_exports, {
|
|
|
999
1014
|
hashNametag: () => import_nostr_js_sdk6.hashNametag,
|
|
1000
1015
|
hashSignMessage: () => hashSignMessage,
|
|
1001
1016
|
hexToBytes: () => hexToBytes2,
|
|
1017
|
+
hexToWIF: () => hexToWIF,
|
|
1002
1018
|
identityFromMnemonicSync: () => identityFromMnemonicSync,
|
|
1003
1019
|
initSphere: () => initSphere,
|
|
1004
1020
|
isArchivedKey: () => isArchivedKey,
|
|
@@ -1028,6 +1044,7 @@ __export(index_exports, {
|
|
|
1028
1044
|
logger: () => logger,
|
|
1029
1045
|
mnemonicToSeedSync: () => mnemonicToSeedSync2,
|
|
1030
1046
|
normalizeAddress: () => normalizeAddress,
|
|
1047
|
+
normalizeCoinId: () => normalizeCoinId,
|
|
1031
1048
|
normalizeNametag: () => import_nostr_js_sdk6.normalizeNametag,
|
|
1032
1049
|
normalizeSdkTokenToStorage: () => normalizeSdkTokenToStorage,
|
|
1033
1050
|
objectToTxf: () => objectToTxf,
|
|
@@ -1058,6 +1075,7 @@ __export(index_exports, {
|
|
|
1058
1075
|
verifyManifestIntegrity: () => verifyManifestIntegrity,
|
|
1059
1076
|
verifyNametagBinding: () => verifyNametagBinding,
|
|
1060
1077
|
verifySignedMessage: () => verifySignedMessage,
|
|
1078
|
+
verifySphereAuth: () => verifySphereAuth,
|
|
1061
1079
|
verifySwapSignature: () => verifySwapSignature
|
|
1062
1080
|
});
|
|
1063
1081
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -12513,6 +12531,132 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
12513
12531
|
};
|
|
12514
12532
|
}
|
|
12515
12533
|
}
|
|
12534
|
+
/**
|
|
12535
|
+
* Mint a fungible token directly to this wallet (genesis mint).
|
|
12536
|
+
*
|
|
12537
|
+
* Useful for test setups that need to seed a wallet with specific token
|
|
12538
|
+
* balances WITHOUT depending on the testnet faucet HTTP service. The
|
|
12539
|
+
* resulting token has the canonical CoinId bytes (passed in `coinIdHex`)
|
|
12540
|
+
* — when those bytes match a registered symbol in the TokenRegistry,
|
|
12541
|
+
* the token shows up under the symbol's name (e.g. "UCT"). There is no
|
|
12542
|
+
* cryptographic restriction on which key may issue a given CoinId; the
|
|
12543
|
+
* aggregator records the mint regardless of issuer identity.
|
|
12544
|
+
*
|
|
12545
|
+
* The flow:
|
|
12546
|
+
* 1. Generate a random TokenId.
|
|
12547
|
+
* 2. Build TokenCoinData with [(coinId, amount)].
|
|
12548
|
+
* 3. Build MintTransactionData with recipient = self (UnmaskedPredicate
|
|
12549
|
+
* from this wallet's signing service).
|
|
12550
|
+
* 4. Submit MintCommitment to the aggregator.
|
|
12551
|
+
* 5. Wait for the inclusion proof.
|
|
12552
|
+
* 6. Construct an SDK Token via Token.mint().
|
|
12553
|
+
* 7. Convert to wallet Token format and call addToken().
|
|
12554
|
+
*
|
|
12555
|
+
* @param coinIdHex - 64-char lowercase hex CoinId. Must match the bytes
|
|
12556
|
+
* used by the registered symbol if you want the wallet to recognize
|
|
12557
|
+
* the token as that symbol (e.g. UCT's coinId from the public registry).
|
|
12558
|
+
* @param amount - Amount in smallest units (multiply by 10^decimals
|
|
12559
|
+
* when converting from human values).
|
|
12560
|
+
* @returns Result with the resulting wallet Token and its on-chain id.
|
|
12561
|
+
*/
|
|
12562
|
+
async mintFungibleToken(coinIdHex, amount) {
|
|
12563
|
+
this.ensureInitialized();
|
|
12564
|
+
const stClient = this.deps.oracle.getStateTransitionClient?.();
|
|
12565
|
+
if (!stClient) {
|
|
12566
|
+
return { success: false, error: "State transition client not available" };
|
|
12567
|
+
}
|
|
12568
|
+
const trustBase = this.deps.oracle.getTrustBase?.();
|
|
12569
|
+
if (!trustBase) {
|
|
12570
|
+
return { success: false, error: "Trust base not available" };
|
|
12571
|
+
}
|
|
12572
|
+
try {
|
|
12573
|
+
const signingService = await this.createSigningService();
|
|
12574
|
+
const { TokenId: TokenId5 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenId");
|
|
12575
|
+
const { TokenCoinData: TokenCoinData3 } = await import("@unicitylabs/state-transition-sdk/lib/token/fungible/TokenCoinData");
|
|
12576
|
+
const { UnmaskedPredicateReference: UnmaskedPredicateReference4 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
|
|
12577
|
+
const tokenTypeBytes = fromHex4("f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509");
|
|
12578
|
+
const tokenType = new import_TokenType3.TokenType(tokenTypeBytes);
|
|
12579
|
+
const tokenIdBytes = new Uint8Array(32);
|
|
12580
|
+
crypto.getRandomValues(tokenIdBytes);
|
|
12581
|
+
const tokenId = new TokenId5(tokenIdBytes);
|
|
12582
|
+
const coinIdBytes = fromHex4(coinIdHex);
|
|
12583
|
+
const coinId = new import_CoinId4.CoinId(coinIdBytes);
|
|
12584
|
+
const coinData = TokenCoinData3.create([[coinId, amount]]);
|
|
12585
|
+
const addressRef = await UnmaskedPredicateReference4.create(
|
|
12586
|
+
tokenType,
|
|
12587
|
+
signingService.algorithm,
|
|
12588
|
+
signingService.publicKey,
|
|
12589
|
+
import_HashAlgorithm5.HashAlgorithm.SHA256
|
|
12590
|
+
);
|
|
12591
|
+
const ownerAddress = await addressRef.toAddress();
|
|
12592
|
+
const salt = new Uint8Array(32);
|
|
12593
|
+
crypto.getRandomValues(salt);
|
|
12594
|
+
const mintData = await import_MintTransactionData3.MintTransactionData.create(
|
|
12595
|
+
tokenId,
|
|
12596
|
+
tokenType,
|
|
12597
|
+
null,
|
|
12598
|
+
// tokenData: no metadata
|
|
12599
|
+
coinData,
|
|
12600
|
+
// fungible coin data
|
|
12601
|
+
ownerAddress,
|
|
12602
|
+
// recipient = self
|
|
12603
|
+
salt,
|
|
12604
|
+
null,
|
|
12605
|
+
// recipientDataHash
|
|
12606
|
+
null
|
|
12607
|
+
// reason: null (genesis, no burn predecessor)
|
|
12608
|
+
);
|
|
12609
|
+
const commitment = await import_MintCommitment3.MintCommitment.create(mintData);
|
|
12610
|
+
const MAX_RETRIES = 3;
|
|
12611
|
+
let lastStatus;
|
|
12612
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
12613
|
+
const response = await stClient.submitMintCommitment(commitment);
|
|
12614
|
+
lastStatus = response.status;
|
|
12615
|
+
if (response.status === "SUCCESS" || response.status === "REQUEST_ID_EXISTS") break;
|
|
12616
|
+
if (attempt === MAX_RETRIES) {
|
|
12617
|
+
return { success: false, error: `Mint submit failed after ${MAX_RETRIES} attempts: ${response.status}` };
|
|
12618
|
+
}
|
|
12619
|
+
await new Promise((r) => setTimeout(r, 1e3 * attempt));
|
|
12620
|
+
}
|
|
12621
|
+
if (lastStatus !== "SUCCESS" && lastStatus !== "REQUEST_ID_EXISTS") {
|
|
12622
|
+
return { success: false, error: `Mint submit failed: ${lastStatus}` };
|
|
12623
|
+
}
|
|
12624
|
+
const inclusionProof = await (0, import_InclusionProofUtils5.waitInclusionProof)(trustBase, stClient, commitment);
|
|
12625
|
+
const genesisTransaction = commitment.toTransaction(inclusionProof);
|
|
12626
|
+
const predicate = await import_UnmaskedPredicate5.UnmaskedPredicate.create(
|
|
12627
|
+
tokenId,
|
|
12628
|
+
tokenType,
|
|
12629
|
+
signingService,
|
|
12630
|
+
import_HashAlgorithm5.HashAlgorithm.SHA256,
|
|
12631
|
+
salt
|
|
12632
|
+
);
|
|
12633
|
+
const tokenState = new import_TokenState5.TokenState(predicate, null);
|
|
12634
|
+
const sdkToken = await import_Token6.Token.mint(trustBase, tokenState, genesisTransaction);
|
|
12635
|
+
const tokenIdHex = tokenId.toJSON();
|
|
12636
|
+
const symbol = this.getCoinSymbol(coinIdHex);
|
|
12637
|
+
const name = this.getCoinName(coinIdHex);
|
|
12638
|
+
const decimals = this.getCoinDecimals(coinIdHex);
|
|
12639
|
+
const iconUrl = this.getCoinIconUrl(coinIdHex);
|
|
12640
|
+
const uiToken = {
|
|
12641
|
+
id: tokenIdHex,
|
|
12642
|
+
coinId: coinIdHex,
|
|
12643
|
+
symbol,
|
|
12644
|
+
name,
|
|
12645
|
+
decimals,
|
|
12646
|
+
...iconUrl !== void 0 ? { iconUrl } : {},
|
|
12647
|
+
amount: amount.toString(),
|
|
12648
|
+
status: "confirmed",
|
|
12649
|
+
createdAt: Date.now(),
|
|
12650
|
+
updatedAt: Date.now(),
|
|
12651
|
+
sdkData: JSON.stringify(sdkToken.toJSON())
|
|
12652
|
+
};
|
|
12653
|
+
await this.addToken(uiToken);
|
|
12654
|
+
return { success: true, token: uiToken, tokenId: tokenIdHex };
|
|
12655
|
+
} catch (err) {
|
|
12656
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
12657
|
+
return { success: false, error: `Local mint failed: ${msg}` };
|
|
12658
|
+
}
|
|
12659
|
+
}
|
|
12516
12660
|
/**
|
|
12517
12661
|
* Check if a nametag is available for minting
|
|
12518
12662
|
* @param nametag - The nametag to check (e.g., "alice" or "@alice")
|
|
@@ -18719,6 +18863,24 @@ var AccountingModule = class _AccountingModule {
|
|
|
18719
18863
|
dirtyLedgerEntries = /* @__PURE__ */ new Set();
|
|
18720
18864
|
/** Count of unknown (not in invoiceTermsCache) invoice IDs in the ledger. */
|
|
18721
18865
|
unknownLedgerCount = 0;
|
|
18866
|
+
/**
|
|
18867
|
+
* Per-unknown-invoice first-seen timestamp for TTL eviction.
|
|
18868
|
+
*
|
|
18869
|
+
* W1 (steelman round-4): without TTL, an attacker who can deliver 500
|
|
18870
|
+
* inbound transfers with synthesized memo invoiceIds permanently exhausts
|
|
18871
|
+
* the unknown-ledger cap, after which legitimate orphan transfers (out-of-
|
|
18872
|
+
* order delivery for real swaps) are silently dropped at the cap-check.
|
|
18873
|
+
*
|
|
18874
|
+
* Round-5 perf: gated by `unknownLedgerNextSweepMs` to amortize the
|
|
18875
|
+
* sweep cost. The naive every-call sweep is O(N) where N=cap=500;
|
|
18876
|
+
* combined with the per-token cleanup loop inside the sweep it became
|
|
18877
|
+
* O(N×M) on every transfer under flood. Now we sweep at most every
|
|
18878
|
+
* `UNKNOWN_LEDGER_SWEEP_INTERVAL_MS` (60s) UNLESS the cap is currently
|
|
18879
|
+
* full, in which case we sweep on each call (the only path that can
|
|
18880
|
+
* actually drop a legitimate orphan).
|
|
18881
|
+
*/
|
|
18882
|
+
unknownLedgerFirstSeen = /* @__PURE__ */ new Map();
|
|
18883
|
+
unknownLedgerNextSweepMs = 0;
|
|
18722
18884
|
/** W17: Tracks whether tokenScanState has been mutated since last flush. */
|
|
18723
18885
|
tokenScanDirty = false;
|
|
18724
18886
|
/** W2 fix: Serialization guard for _flushDirtyLedgerEntries. */
|
|
@@ -19709,6 +19871,7 @@ var AccountingModule = class _AccountingModule {
|
|
|
19709
19871
|
}
|
|
19710
19872
|
if (this.invoiceLedger.has(tokenId) && !this.invoiceTermsCache.has(tokenId)) {
|
|
19711
19873
|
this.unknownLedgerCount = Math.max(0, this.unknownLedgerCount - 1);
|
|
19874
|
+
this.unknownLedgerFirstSeen.delete(tokenId);
|
|
19712
19875
|
}
|
|
19713
19876
|
this.invoiceTermsCache.set(tokenId, terms);
|
|
19714
19877
|
this._addToHashIndex(tokenId);
|
|
@@ -19977,6 +20140,29 @@ var AccountingModule = class _AccountingModule {
|
|
|
19977
20140
|
closed: this.closedInvoices.has(invoiceId)
|
|
19978
20141
|
};
|
|
19979
20142
|
}
|
|
20143
|
+
/**
|
|
20144
|
+
* Return the set of token IDs that are currently linked to the given
|
|
20145
|
+
* invoice. Populated by both the on-chain `_processTokenTransactions`
|
|
20146
|
+
* path (tokens with `inv:` references) and the transport-memo orphan
|
|
20147
|
+
* buffering path in `_handleIncomingTransfer`.
|
|
20148
|
+
*
|
|
20149
|
+
* Used by callers that want to scope per-invoice operations (e.g.
|
|
20150
|
+
* SwapModule.verifyPayout's L3 validation) to only the tokens that
|
|
20151
|
+
* cover this invoice — avoiding false negatives when the wallet
|
|
20152
|
+
* contains unrelated tokens of the same currency in unconfirmed or
|
|
20153
|
+
* spent state.
|
|
20154
|
+
*
|
|
20155
|
+
* Returns an empty set if no tokens are currently linked.
|
|
20156
|
+
*/
|
|
20157
|
+
getTokenIdsForInvoice(invoiceId) {
|
|
20158
|
+
const result = /* @__PURE__ */ new Set();
|
|
20159
|
+
for (const [tokenId, invoiceIds] of this.tokenInvoiceMap) {
|
|
20160
|
+
if (invoiceIds.has(invoiceId)) {
|
|
20161
|
+
result.add(tokenId);
|
|
20162
|
+
}
|
|
20163
|
+
}
|
|
20164
|
+
return result;
|
|
20165
|
+
}
|
|
19980
20166
|
/**
|
|
19981
20167
|
* Explicitly close an invoice. Only target parties may close (§8.3).
|
|
19982
20168
|
*
|
|
@@ -20296,6 +20482,7 @@ var AccountingModule = class _AccountingModule {
|
|
|
20296
20482
|
ledger.set(entryKey, forwardRef);
|
|
20297
20483
|
this.dirtyLedgerEntries.add(invoiceId);
|
|
20298
20484
|
this.balanceCache.delete(invoiceId);
|
|
20485
|
+
await this._persistProvisionalAndVerify(invoiceId, "payInvoice");
|
|
20299
20486
|
}
|
|
20300
20487
|
return result;
|
|
20301
20488
|
} finally {
|
|
@@ -20463,6 +20650,7 @@ var AccountingModule = class _AccountingModule {
|
|
|
20463
20650
|
this.dirtyLedgerEntries.add(invoiceId);
|
|
20464
20651
|
}
|
|
20465
20652
|
this.balanceCache.delete(invoiceId);
|
|
20653
|
+
await this._persistProvisionalAndVerify(invoiceId, "returnInvoicePayment");
|
|
20466
20654
|
}
|
|
20467
20655
|
return result;
|
|
20468
20656
|
} finally {
|
|
@@ -21542,9 +21730,30 @@ var AccountingModule = class _AccountingModule {
|
|
|
21542
21730
|
continue;
|
|
21543
21731
|
}
|
|
21544
21732
|
innerMap.set(entryKey, ref);
|
|
21545
|
-
|
|
21733
|
+
const HEX_64 = /^[a-f0-9]{64}$/i;
|
|
21734
|
+
if (entryKey.startsWith("mt:")) {
|
|
21735
|
+
const firstColon = entryKey.indexOf(":");
|
|
21736
|
+
const secondColon = entryKey.indexOf(":", firstColon + 1);
|
|
21737
|
+
if (secondColon > firstColon + 1) {
|
|
21738
|
+
const tokenIdFromKey2 = entryKey.slice(firstColon + 1, secondColon);
|
|
21739
|
+
if (HEX_64.test(tokenIdFromKey2)) {
|
|
21740
|
+
this._addToTokenInvoiceMap(tokenIdFromKey2, invoiceId);
|
|
21741
|
+
}
|
|
21742
|
+
}
|
|
21743
|
+
} else if (entryKey.startsWith("synthetic:")) {
|
|
21744
|
+
const afterPrefix = entryKey.slice("synthetic:".length);
|
|
21745
|
+
const tokenIdEnd = afterPrefix.indexOf(":");
|
|
21746
|
+
if (tokenIdEnd > 0) {
|
|
21747
|
+
const tokenId = afterPrefix.slice(0, tokenIdEnd);
|
|
21748
|
+
if (HEX_64.test(tokenId) && ref.transferId !== tokenId) {
|
|
21749
|
+
this._addToTokenInvoiceMap(tokenId, invoiceId);
|
|
21750
|
+
}
|
|
21751
|
+
}
|
|
21752
|
+
} else if (!ref.transferId.startsWith("provisional:") && ref.transferId.includes(":")) {
|
|
21546
21753
|
const tokenIdFromRef = ref.transferId.slice(0, ref.transferId.indexOf(":"));
|
|
21547
|
-
|
|
21754
|
+
if (HEX_64.test(tokenIdFromRef)) {
|
|
21755
|
+
this._addToTokenInvoiceMap(tokenIdFromRef, invoiceId);
|
|
21756
|
+
}
|
|
21548
21757
|
}
|
|
21549
21758
|
}
|
|
21550
21759
|
} catch (err) {
|
|
@@ -21745,7 +21954,14 @@ var AccountingModule = class _AccountingModule {
|
|
|
21745
21954
|
}
|
|
21746
21955
|
}
|
|
21747
21956
|
for (const [existingKey, existingRef] of ledger) {
|
|
21748
|
-
if (existingKey.startsWith("synthetic:") && existingRef.coinId === coinId && existingRef.paymentDirection === paymentDirection) {
|
|
21957
|
+
if ((existingKey.startsWith("synthetic:") || existingKey.startsWith("synthetic-tx:")) && existingRef.coinId === coinId && existingRef.paymentDirection === paymentDirection) {
|
|
21958
|
+
keysToDelete.push(existingKey);
|
|
21959
|
+
break;
|
|
21960
|
+
}
|
|
21961
|
+
}
|
|
21962
|
+
const mtPrefix = `mt:${tokenId}:`;
|
|
21963
|
+
for (const [existingKey, existingRef] of ledger) {
|
|
21964
|
+
if (existingKey.startsWith(mtPrefix) && existingRef.coinId === coinId && existingRef.paymentDirection === paymentDirection) {
|
|
21749
21965
|
keysToDelete.push(existingKey);
|
|
21750
21966
|
break;
|
|
21751
21967
|
}
|
|
@@ -21862,6 +22078,49 @@ var AccountingModule = class _AccountingModule {
|
|
|
21862
22078
|
});
|
|
21863
22079
|
}
|
|
21864
22080
|
}
|
|
22081
|
+
/**
|
|
22082
|
+
* Synchronously persist any pending provisional ledger entry for `invoiceId`
|
|
22083
|
+
* before returning to the caller. Used by `payInvoice` and
|
|
22084
|
+
* `returnInvoicePayment` to make the in-memory provisional entry durable
|
|
22085
|
+
* inside the same per-invoice gate that wrote it, closing the
|
|
22086
|
+
* crash-mid-conclude race that produces over-coverage on receivers.
|
|
22087
|
+
*
|
|
22088
|
+
* Implementation:
|
|
22089
|
+
* 1. Schedule a flush via the existing `_flushPromise` chain (so
|
|
22090
|
+
* concurrent `_handleTokenChange` callers waiting on the chain
|
|
22091
|
+
* observe ours as part of the sequence).
|
|
22092
|
+
* 2. Await OUR flush directly — NOT `_drainFlushPromise()`, which would
|
|
22093
|
+
* spin while concurrent token changes keep extending the chain and
|
|
22094
|
+
* hold the per-invoice gate for an unbounded number of additional
|
|
22095
|
+
* flushes. We only need OUR provisional entry durable.
|
|
22096
|
+
* 3. `_flushDirtyLedgerEntries` swallows per-invoice `storage.set`
|
|
22097
|
+
* rejections internally (sets a local `step1Failed` flag), leaving
|
|
22098
|
+
* the dirty entry on the set without re-throwing. So we post-check
|
|
22099
|
+
* `dirtyLedgerEntries.has(invoiceId)` and throw a `STORAGE_ERROR`
|
|
22100
|
+
* `SphereError` if our entry is still dirty — propagating to the
|
|
22101
|
+
* caller so they learn about the durability failure rather than
|
|
22102
|
+
* receiving a silent "success" return that lies on disk.
|
|
22103
|
+
*
|
|
22104
|
+
* @param invoiceId The invoice whose provisional entry must be durable.
|
|
22105
|
+
* @param callContext Used in the error message so the caller is named
|
|
22106
|
+
* ('payInvoice' / 'returnInvoicePayment') without
|
|
22107
|
+
* forcing a stack-trace inspection.
|
|
22108
|
+
*/
|
|
22109
|
+
async _persistProvisionalAndVerify(invoiceId, callContext) {
|
|
22110
|
+
const flushTrigger = (this._flushPromise ?? Promise.resolve()).then(() => this._flushDirtyLedgerEntries());
|
|
22111
|
+
const tracked = flushTrigger.catch(() => {
|
|
22112
|
+
}).finally(() => {
|
|
22113
|
+
if (this._flushPromise === tracked) this._flushPromise = null;
|
|
22114
|
+
});
|
|
22115
|
+
this._flushPromise = tracked;
|
|
22116
|
+
await flushTrigger;
|
|
22117
|
+
if (this.dirtyLedgerEntries.has(invoiceId)) {
|
|
22118
|
+
throw new SphereError(
|
|
22119
|
+
`${callContext}: provisional ledger entry for invoice ${invoiceId} failed to persist \u2014 caller should retry`,
|
|
22120
|
+
"STORAGE_ERROR"
|
|
22121
|
+
);
|
|
22122
|
+
}
|
|
22123
|
+
}
|
|
21865
22124
|
// ===========================================================================
|
|
21866
22125
|
// Internal: Event handlers
|
|
21867
22126
|
// ===========================================================================
|
|
@@ -21922,13 +22181,96 @@ var AccountingModule = class _AccountingModule {
|
|
|
21922
22181
|
}
|
|
21923
22182
|
}
|
|
21924
22183
|
if (!this.invoiceTermsCache.has(invoiceId)) {
|
|
21925
|
-
|
|
21926
|
-
|
|
21927
|
-
invoiceId
|
|
21928
|
-
|
|
21929
|
-
|
|
21930
|
-
|
|
21931
|
-
|
|
22184
|
+
let gracefullyGraduated = false;
|
|
22185
|
+
await this.withInvoiceGate(invoiceId, async () => {
|
|
22186
|
+
if (this.invoiceTermsCache.has(invoiceId)) {
|
|
22187
|
+
gracefullyGraduated = true;
|
|
22188
|
+
return;
|
|
22189
|
+
}
|
|
22190
|
+
const syntheticRef = this._buildSyntheticTransferRef(
|
|
22191
|
+
transfer,
|
|
22192
|
+
invoiceId,
|
|
22193
|
+
paymentDirection,
|
|
22194
|
+
confirmed
|
|
22195
|
+
);
|
|
22196
|
+
deps.emitEvent("invoice:unknown_reference", { invoiceId, transfer: syntheticRef });
|
|
22197
|
+
const MAX_UNKNOWN_INVOICE_IDS = 500;
|
|
22198
|
+
const UNKNOWN_LEDGER_TTL_MS = 30 * 60 * 1e3;
|
|
22199
|
+
const MAX_ORPHAN_ENTRIES_PER_INVOICE = 50;
|
|
22200
|
+
const UNKNOWN_LEDGER_SWEEP_INTERVAL_MS = 6e4;
|
|
22201
|
+
const nowMs = Date.now();
|
|
22202
|
+
const capFull = this.unknownLedgerCount >= MAX_UNKNOWN_INVOICE_IDS;
|
|
22203
|
+
const sweepDue = nowMs >= this.unknownLedgerNextSweepMs;
|
|
22204
|
+
if (this.unknownLedgerFirstSeen.size > 0 && (capFull || sweepDue)) {
|
|
22205
|
+
this.unknownLedgerNextSweepMs = nowMs + UNKNOWN_LEDGER_SWEEP_INTERVAL_MS;
|
|
22206
|
+
const expiredIds = [];
|
|
22207
|
+
for (const [unkId, firstSeen] of this.unknownLedgerFirstSeen) {
|
|
22208
|
+
if (nowMs - firstSeen > UNKNOWN_LEDGER_TTL_MS) {
|
|
22209
|
+
expiredIds.push(unkId);
|
|
22210
|
+
}
|
|
22211
|
+
}
|
|
22212
|
+
for (const expiredId of expiredIds) {
|
|
22213
|
+
if (!this.invoiceTermsCache.has(expiredId) && this.invoiceLedger.has(expiredId)) {
|
|
22214
|
+
this.invoiceLedger.delete(expiredId);
|
|
22215
|
+
this.unknownLedgerCount = Math.max(0, this.unknownLedgerCount - 1);
|
|
22216
|
+
for (const [tokenId, invoiceSet] of this.tokenInvoiceMap) {
|
|
22217
|
+
if (invoiceSet.has(expiredId)) {
|
|
22218
|
+
invoiceSet.delete(expiredId);
|
|
22219
|
+
if (invoiceSet.size === 0) this.tokenInvoiceMap.delete(tokenId);
|
|
22220
|
+
}
|
|
22221
|
+
}
|
|
22222
|
+
}
|
|
22223
|
+
this.unknownLedgerFirstSeen.delete(expiredId);
|
|
22224
|
+
}
|
|
22225
|
+
}
|
|
22226
|
+
if (!this.invoiceLedger.has(invoiceId)) {
|
|
22227
|
+
if (this.unknownLedgerCount >= MAX_UNKNOWN_INVOICE_IDS) {
|
|
22228
|
+
return;
|
|
22229
|
+
}
|
|
22230
|
+
this.invoiceLedger.set(invoiceId, /* @__PURE__ */ new Map());
|
|
22231
|
+
this.unknownLedgerCount++;
|
|
22232
|
+
this.unknownLedgerFirstSeen.set(invoiceId, nowMs);
|
|
22233
|
+
}
|
|
22234
|
+
const orphanLedger = this.invoiceLedger.get(invoiceId);
|
|
22235
|
+
let mtEntryCount = 0;
|
|
22236
|
+
for (const k of orphanLedger.keys()) {
|
|
22237
|
+
if (k.startsWith("mt:")) mtEntryCount++;
|
|
22238
|
+
}
|
|
22239
|
+
if (mtEntryCount >= MAX_ORPHAN_ENTRIES_PER_INVOICE) {
|
|
22240
|
+
return;
|
|
22241
|
+
}
|
|
22242
|
+
for (const token of transfer.tokens) {
|
|
22243
|
+
if (!token.id) continue;
|
|
22244
|
+
let onChainAttributed = false;
|
|
22245
|
+
const tokenKeyPrefix = `${token.id}:`;
|
|
22246
|
+
for (const existingKey of orphanLedger.keys()) {
|
|
22247
|
+
if (existingKey.startsWith(tokenKeyPrefix) && !existingKey.startsWith("mt:")) {
|
|
22248
|
+
onChainAttributed = true;
|
|
22249
|
+
break;
|
|
22250
|
+
}
|
|
22251
|
+
}
|
|
22252
|
+
if (!onChainAttributed) {
|
|
22253
|
+
if (mtEntryCount >= MAX_ORPHAN_ENTRIES_PER_INVOICE) {
|
|
22254
|
+
break;
|
|
22255
|
+
}
|
|
22256
|
+
const orphanKey = `mt:${token.id}:${transfer.id}`;
|
|
22257
|
+
if (!orphanLedger.has(orphanKey)) {
|
|
22258
|
+
orphanLedger.set(orphanKey, syntheticRef);
|
|
22259
|
+
mtEntryCount++;
|
|
22260
|
+
}
|
|
22261
|
+
}
|
|
22262
|
+
if (!this.tokenInvoiceMap.has(token.id)) {
|
|
22263
|
+
this.tokenInvoiceMap.set(token.id, /* @__PURE__ */ new Set());
|
|
22264
|
+
}
|
|
22265
|
+
this.tokenInvoiceMap.get(token.id).add(invoiceId);
|
|
22266
|
+
}
|
|
22267
|
+
this.dirtyLedgerEntries.add(invoiceId);
|
|
22268
|
+
this.balanceCache.delete(invoiceId);
|
|
22269
|
+
await this._flushDirtyLedgerEntries();
|
|
22270
|
+
});
|
|
22271
|
+
if (gracefullyGraduated) {
|
|
22272
|
+
await this._processInvoiceTransferEvent(transfer, invoiceId, paymentDirection, confirmed);
|
|
22273
|
+
}
|
|
21932
22274
|
return;
|
|
21933
22275
|
}
|
|
21934
22276
|
await this._processInvoiceTransferEvent(transfer, invoiceId, paymentDirection, confirmed);
|
|
@@ -22364,7 +22706,8 @@ var AccountingModule = class _AccountingModule {
|
|
|
22364
22706
|
}
|
|
22365
22707
|
const existingLedger = this.invoiceLedger.get(invoiceId);
|
|
22366
22708
|
const firstTokenId = transfer.tokens.find((t) => t.id)?.id;
|
|
22367
|
-
const syntheticKey = firstTokenId ? `synthetic:${firstTokenId}::${syntheticRef.coinId}` : `synthetic:${syntheticRef.transferId}::${syntheticRef.coinId}`;
|
|
22709
|
+
const syntheticKey = firstTokenId ? `synthetic:${firstTokenId}::${syntheticRef.coinId}` : `synthetic-tx:${syntheticRef.transferId}::${syntheticRef.coinId}`;
|
|
22710
|
+
let mutated = false;
|
|
22368
22711
|
if (!existingLedger.has(syntheticKey)) {
|
|
22369
22712
|
let hasRealEntry = false;
|
|
22370
22713
|
for (const tok of transfer.tokens) {
|
|
@@ -22379,8 +22722,25 @@ var AccountingModule = class _AccountingModule {
|
|
|
22379
22722
|
}
|
|
22380
22723
|
if (!hasRealEntry) {
|
|
22381
22724
|
existingLedger.set(syntheticKey, { ...syntheticRef });
|
|
22725
|
+
mutated = true;
|
|
22382
22726
|
}
|
|
22383
22727
|
}
|
|
22728
|
+
for (const tok of transfer.tokens) {
|
|
22729
|
+
if (!tok.id) continue;
|
|
22730
|
+
if (!this.tokenInvoiceMap.has(tok.id)) {
|
|
22731
|
+
this.tokenInvoiceMap.set(tok.id, /* @__PURE__ */ new Set());
|
|
22732
|
+
mutated = true;
|
|
22733
|
+
}
|
|
22734
|
+
const beforeSize = this.tokenInvoiceMap.get(tok.id).size;
|
|
22735
|
+
this.tokenInvoiceMap.get(tok.id).add(invoiceId);
|
|
22736
|
+
if (this.tokenInvoiceMap.get(tok.id).size !== beforeSize) {
|
|
22737
|
+
mutated = true;
|
|
22738
|
+
}
|
|
22739
|
+
}
|
|
22740
|
+
if (mutated) {
|
|
22741
|
+
this.dirtyLedgerEntries.add(invoiceId);
|
|
22742
|
+
this.balanceCache.delete(invoiceId);
|
|
22743
|
+
}
|
|
22384
22744
|
deps.emitEvent("invoice:payment", {
|
|
22385
22745
|
invoiceId,
|
|
22386
22746
|
transfer: syntheticRef,
|
|
@@ -22530,7 +22890,8 @@ var AccountingModule = class _AccountingModule {
|
|
|
22530
22890
|
this.invoiceLedger.set(invoiceId, /* @__PURE__ */ new Map());
|
|
22531
22891
|
}
|
|
22532
22892
|
const hLedger = this.invoiceLedger.get(invoiceId);
|
|
22533
|
-
const hKey = entry.tokenId ? `synthetic:${entry.tokenId}::${syntheticRef.coinId}` : `synthetic:${syntheticRef.transferId}::${syntheticRef.coinId}`;
|
|
22893
|
+
const hKey = entry.tokenId ? `synthetic:${entry.tokenId}::${syntheticRef.coinId}` : `synthetic-tx:${syntheticRef.transferId}::${syntheticRef.coinId}`;
|
|
22894
|
+
let hMutated = false;
|
|
22534
22895
|
if (!hLedger.has(hKey)) {
|
|
22535
22896
|
let hasRealEntry = false;
|
|
22536
22897
|
if (entry.tokenId) {
|
|
@@ -22543,8 +22904,24 @@ var AccountingModule = class _AccountingModule {
|
|
|
22543
22904
|
}
|
|
22544
22905
|
if (!hasRealEntry) {
|
|
22545
22906
|
hLedger.set(hKey, { ...syntheticRef });
|
|
22907
|
+
hMutated = true;
|
|
22908
|
+
}
|
|
22909
|
+
}
|
|
22910
|
+
if (entry.tokenId) {
|
|
22911
|
+
if (!this.tokenInvoiceMap.has(entry.tokenId)) {
|
|
22912
|
+
this.tokenInvoiceMap.set(entry.tokenId, /* @__PURE__ */ new Set());
|
|
22913
|
+
hMutated = true;
|
|
22914
|
+
}
|
|
22915
|
+
const beforeSize = this.tokenInvoiceMap.get(entry.tokenId).size;
|
|
22916
|
+
this.tokenInvoiceMap.get(entry.tokenId).add(invoiceId);
|
|
22917
|
+
if (this.tokenInvoiceMap.get(entry.tokenId).size !== beforeSize) {
|
|
22918
|
+
hMutated = true;
|
|
22546
22919
|
}
|
|
22547
22920
|
}
|
|
22921
|
+
if (hMutated) {
|
|
22922
|
+
this.dirtyLedgerEntries.add(invoiceId);
|
|
22923
|
+
this.balanceCache.delete(invoiceId);
|
|
22924
|
+
}
|
|
22548
22925
|
deps.emitEvent("invoice:payment", {
|
|
22549
22926
|
invoiceId,
|
|
22550
22927
|
transfer: syntheticRef,
|
|
@@ -25090,17 +25467,63 @@ var SwapModule = class {
|
|
|
25090
25467
|
for (const addr of allAddresses) {
|
|
25091
25468
|
myDirectAddresses.add(addr.directAddress);
|
|
25092
25469
|
}
|
|
25093
|
-
|
|
25094
|
-
|
|
25095
|
-
|
|
25096
|
-
|
|
25097
|
-
|
|
25470
|
+
const matchesPartyA = myDirectAddresses.has(swap.manifest.party_a_address);
|
|
25471
|
+
const matchesPartyB = myDirectAddresses.has(swap.manifest.party_b_address);
|
|
25472
|
+
if (matchesPartyA && matchesPartyB) {
|
|
25473
|
+
throw new SphereError(
|
|
25474
|
+
"Ambiguous party identity: local wallet matches both party_a_address and party_b_address",
|
|
25475
|
+
"SWAP_DEPOSIT_FAILED"
|
|
25476
|
+
);
|
|
25477
|
+
}
|
|
25478
|
+
let myExpectedCurrency;
|
|
25479
|
+
if (matchesPartyA) {
|
|
25480
|
+
myExpectedCurrency = swap.manifest.party_a_currency_to_change;
|
|
25481
|
+
} else if (matchesPartyB) {
|
|
25482
|
+
myExpectedCurrency = swap.manifest.party_b_currency_to_change;
|
|
25098
25483
|
} else {
|
|
25099
25484
|
throw new SphereError(
|
|
25100
25485
|
"Local wallet address does not match either party in the swap manifest",
|
|
25101
25486
|
"SWAP_DEPOSIT_FAILED"
|
|
25102
25487
|
);
|
|
25103
25488
|
}
|
|
25489
|
+
if (!myExpectedCurrency || myExpectedCurrency === "") {
|
|
25490
|
+
throw new SphereError(
|
|
25491
|
+
"Manifest currency_to_change is empty for this party",
|
|
25492
|
+
"SWAP_DEPOSIT_FAILED"
|
|
25493
|
+
);
|
|
25494
|
+
}
|
|
25495
|
+
const invoiceRefForAssetLookup = deps.accounting.getInvoice(swap.depositInvoiceId);
|
|
25496
|
+
if (!invoiceRefForAssetLookup) {
|
|
25497
|
+
throw new SphereError(
|
|
25498
|
+
"Deposit invoice not yet imported into accounting module",
|
|
25499
|
+
"SWAP_WRONG_STATE"
|
|
25500
|
+
);
|
|
25501
|
+
}
|
|
25502
|
+
const depositTarget = invoiceRefForAssetLookup.terms.targets[0];
|
|
25503
|
+
if (!depositTarget) {
|
|
25504
|
+
throw new SphereError(
|
|
25505
|
+
"Deposit invoice has no targets",
|
|
25506
|
+
"SWAP_DEPOSIT_FAILED"
|
|
25507
|
+
);
|
|
25508
|
+
}
|
|
25509
|
+
const assetIndex = depositTarget.assets.findIndex(
|
|
25510
|
+
(a) => a.coin !== void 0 && coinIdsMatch(a.coin[0], myExpectedCurrency)
|
|
25511
|
+
);
|
|
25512
|
+
if (assetIndex < 0) {
|
|
25513
|
+
throw new SphereError(
|
|
25514
|
+
`No asset matching expected currency ${myExpectedCurrency} found in deposit invoice`,
|
|
25515
|
+
"SWAP_DEPOSIT_FAILED"
|
|
25516
|
+
);
|
|
25517
|
+
}
|
|
25518
|
+
for (let i = assetIndex + 1; i < depositTarget.assets.length; i += 1) {
|
|
25519
|
+
const a = depositTarget.assets[i];
|
|
25520
|
+
if (a?.coin !== void 0 && coinIdsMatch(a.coin[0], myExpectedCurrency)) {
|
|
25521
|
+
throw new SphereError(
|
|
25522
|
+
`Ambiguous asset match in deposit invoice: slots ${assetIndex} and ${i} both match currency ${myExpectedCurrency}`,
|
|
25523
|
+
"SWAP_DEPOSIT_FAILED"
|
|
25524
|
+
);
|
|
25525
|
+
}
|
|
25526
|
+
}
|
|
25104
25527
|
return this.withSwapGate(swapId, async () => {
|
|
25105
25528
|
if (swap.progress !== "announced") {
|
|
25106
25529
|
throw new SphereError(
|
|
@@ -25206,7 +25629,6 @@ var SwapModule = class {
|
|
|
25206
25629
|
swap.updatedAt = Date.now();
|
|
25207
25630
|
this.clearLocalTimer(swap.swapId);
|
|
25208
25631
|
this.terminalSwapIds.add(swap.swapId);
|
|
25209
|
-
const entryIdx = this._storedTerminalEntries.length;
|
|
25210
25632
|
this._storedTerminalEntries.push({
|
|
25211
25633
|
swapId: swap.swapId,
|
|
25212
25634
|
progress: "failed",
|
|
@@ -25221,7 +25643,13 @@ var SwapModule = class {
|
|
|
25221
25643
|
swap.error = prevError;
|
|
25222
25644
|
swap.updatedAt = prevUpdatedAt;
|
|
25223
25645
|
this.terminalSwapIds.delete(swap.swapId);
|
|
25224
|
-
this._storedTerminalEntries.
|
|
25646
|
+
for (let i = this._storedTerminalEntries.length - 1; i >= 0; i--) {
|
|
25647
|
+
const entry = this._storedTerminalEntries[i];
|
|
25648
|
+
if (entry.swapId === swap.swapId && entry.progress === "failed") {
|
|
25649
|
+
this._storedTerminalEntries.splice(i, 1);
|
|
25650
|
+
break;
|
|
25651
|
+
}
|
|
25652
|
+
}
|
|
25225
25653
|
logger.warn(LOG_TAG3, `failPayout: persistSwap failed for ${swapId}; fraud detection will retry on next load:`, persistErr);
|
|
25226
25654
|
throw persistErr;
|
|
25227
25655
|
}
|
|
@@ -25265,9 +25693,25 @@ var SwapModule = class {
|
|
|
25265
25693
|
if (!targetStatus.coinAssets[0].isCovered) {
|
|
25266
25694
|
return returnFalse();
|
|
25267
25695
|
}
|
|
25268
|
-
|
|
25696
|
+
let netCoveredAmount;
|
|
25697
|
+
let expectedAmountBigInt;
|
|
25698
|
+
try {
|
|
25699
|
+
netCoveredAmount = BigInt(targetStatus.coinAssets[0].netCoveredAmount);
|
|
25700
|
+
expectedAmountBigInt = BigInt(expectedAmount);
|
|
25701
|
+
} catch (parseErr) {
|
|
25702
|
+
return failPayout(
|
|
25703
|
+
`MALFORMED_AMOUNT: failed to parse coverage amounts (netCoveredAmount=${targetStatus.coinAssets[0].netCoveredAmount}, expectedAmount=${expectedAmount}): ${parseErr instanceof Error ? parseErr.message : String(parseErr)}`
|
|
25704
|
+
);
|
|
25705
|
+
}
|
|
25706
|
+
if (netCoveredAmount < expectedAmountBigInt) {
|
|
25269
25707
|
return returnFalse();
|
|
25270
25708
|
}
|
|
25709
|
+
if (netCoveredAmount > expectedAmountBigInt) {
|
|
25710
|
+
const surplus = netCoveredAmount - expectedAmountBigInt;
|
|
25711
|
+
return failPayout(
|
|
25712
|
+
`OVER_COVERAGE: net=${netCoveredAmount.toString()}, expected=${expectedAmount}, surplus=${surplus.toString()} \u2014 surplus refund expected via auto-return; settlement halted`
|
|
25713
|
+
);
|
|
25714
|
+
}
|
|
25271
25715
|
const escrowAddr = swap.deal.escrowAddress ?? this.config.defaultEscrowAddress;
|
|
25272
25716
|
if (escrowAddr) {
|
|
25273
25717
|
const escrowPeer = await deps.resolve(escrowAddr);
|
|
@@ -25277,8 +25721,28 @@ var SwapModule = class {
|
|
|
25277
25721
|
}
|
|
25278
25722
|
const validationResult = await deps.payments.validate();
|
|
25279
25723
|
if (validationResult.invalid.length > 0) {
|
|
25280
|
-
|
|
25281
|
-
|
|
25724
|
+
const payoutTokenIds = deps.accounting.getTokenIdsForInvoice?.(swap.payoutInvoiceId) ?? /* @__PURE__ */ new Set();
|
|
25725
|
+
if (payoutTokenIds.size === 0) {
|
|
25726
|
+
logger.warn(
|
|
25727
|
+
LOG_TAG3,
|
|
25728
|
+
`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`
|
|
25729
|
+
);
|
|
25730
|
+
return returnFalse();
|
|
25731
|
+
}
|
|
25732
|
+
const relevantInvalid = validationResult.invalid.filter(
|
|
25733
|
+
(t) => payoutTokenIds.has(t.id)
|
|
25734
|
+
);
|
|
25735
|
+
if (relevantInvalid.length > 0) {
|
|
25736
|
+
logger.warn(
|
|
25737
|
+
LOG_TAG3,
|
|
25738
|
+
`verifyPayout for ${swapId.slice(0, 12)}: L3 validation found ${relevantInvalid.length} invalid token(s) covering this payout invoice \u2014 retry after wallet sync`
|
|
25739
|
+
);
|
|
25740
|
+
return returnFalse();
|
|
25741
|
+
}
|
|
25742
|
+
logger.debug(
|
|
25743
|
+
LOG_TAG3,
|
|
25744
|
+
`verifyPayout for ${swapId.slice(0, 12)}: ${validationResult.invalid.length} unrelated invalid token(s) ignored (not linked to this payout invoice)`
|
|
25745
|
+
);
|
|
25282
25746
|
}
|
|
25283
25747
|
if (swap.progress === "completed") {
|
|
25284
25748
|
swap.payoutVerified = true;
|
|
@@ -25457,8 +25921,22 @@ var SwapModule = class {
|
|
|
25457
25921
|
* @param dm - The incoming direct message.
|
|
25458
25922
|
*/
|
|
25459
25923
|
handleIncomingDM(dm) {
|
|
25924
|
+
if (dm.content.startsWith("{") && dm.content.includes('"invoice_delivery"')) {
|
|
25925
|
+
logger.warn(
|
|
25926
|
+
LOG_TAG3,
|
|
25927
|
+
`diag_swap_dm_arrived sender=${dm.senderPubkey.slice(0, 16)} length=${dm.content.length}`
|
|
25928
|
+
);
|
|
25929
|
+
}
|
|
25460
25930
|
const parsed = parseSwapDM(dm.content);
|
|
25461
|
-
if (!parsed)
|
|
25931
|
+
if (!parsed) {
|
|
25932
|
+
if (dm.content.startsWith("{") && dm.content.includes('"invoice_delivery"')) {
|
|
25933
|
+
logger.warn(
|
|
25934
|
+
LOG_TAG3,
|
|
25935
|
+
`diag_swap_dm_parse_rejected sender=${dm.senderPubkey.slice(0, 16)} prefix=${dm.content.slice(0, 80)}`
|
|
25936
|
+
);
|
|
25937
|
+
}
|
|
25938
|
+
return;
|
|
25939
|
+
}
|
|
25462
25940
|
void (async () => {
|
|
25463
25941
|
try {
|
|
25464
25942
|
switch (parsed.kind) {
|
|
@@ -25824,16 +26302,43 @@ var SwapModule = class {
|
|
|
25824
26302
|
// invoice_delivery (§12.4.2 + §12.4.3)
|
|
25825
26303
|
// ---------------------------------------------------------------
|
|
25826
26304
|
case "invoice_delivery": {
|
|
25827
|
-
|
|
26305
|
+
logger.warn(
|
|
26306
|
+
LOG_TAG3,
|
|
26307
|
+
`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)}`
|
|
26308
|
+
);
|
|
26309
|
+
if (!swapId) {
|
|
26310
|
+
logger.warn(LOG_TAG3, "diag_invoice_delivery_dropped reason=no_swap_id");
|
|
26311
|
+
return;
|
|
26312
|
+
}
|
|
25828
26313
|
const swap = this.swaps.get(swapId);
|
|
25829
|
-
if (!swap)
|
|
25830
|
-
|
|
26314
|
+
if (!swap) {
|
|
26315
|
+
logger.warn(
|
|
26316
|
+
LOG_TAG3,
|
|
26317
|
+
`diag_invoice_delivery_dropped reason=swap_not_in_map swap_id=${swapId.slice(0, 16)} known_swap_ids_count=${this.swaps.size}`
|
|
26318
|
+
);
|
|
26319
|
+
return;
|
|
26320
|
+
}
|
|
26321
|
+
if (!this.isFromExpectedEscrow(dm.senderPubkey, swap)) {
|
|
26322
|
+
logger.warn(
|
|
26323
|
+
LOG_TAG3,
|
|
26324
|
+
`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)}`
|
|
26325
|
+
);
|
|
26326
|
+
return;
|
|
26327
|
+
}
|
|
25831
26328
|
const deps = this.deps;
|
|
25832
26329
|
if (msg.invoice_type === "deposit") {
|
|
26330
|
+
logger.warn(
|
|
26331
|
+
LOG_TAG3,
|
|
26332
|
+
`diag_invoice_delivery_proceeding_to_import swap_id=${swapId.slice(0, 16)} progress=${swap.progress} invoice_id=${(msg.invoice_id ?? "").slice(0, 16)}`
|
|
26333
|
+
);
|
|
25833
26334
|
await this.withSwapGate(swapId, async () => {
|
|
25834
26335
|
if (isTerminalProgress(swap.progress)) return;
|
|
25835
26336
|
try {
|
|
25836
26337
|
await deps.accounting.importInvoice(msg.invoice_token);
|
|
26338
|
+
logger.warn(
|
|
26339
|
+
LOG_TAG3,
|
|
26340
|
+
`diag_invoice_imported swap_id=${swapId.slice(0, 16)} invoice_id=${(msg.invoice_id ?? "").slice(0, 16)} type=deposit`
|
|
26341
|
+
);
|
|
25837
26342
|
} catch (err) {
|
|
25838
26343
|
if (err instanceof SphereError && err.code === "INVOICE_ALREADY_EXISTS") {
|
|
25839
26344
|
logger.debug(LOG_TAG3, `Deposit invoice for swap ${swapId} already imported \u2014 relay re-delivery, continuing`);
|
|
@@ -26472,6 +26977,65 @@ init_constants();
|
|
|
26472
26977
|
var import_crypto_js6 = __toESM(require("crypto-js"), 1);
|
|
26473
26978
|
init_errors();
|
|
26474
26979
|
init_logger();
|
|
26980
|
+
var DEFAULT_ITERATIONS = 1e5;
|
|
26981
|
+
var KEY_SIZE = 256;
|
|
26982
|
+
var SALT_SIZE = 16;
|
|
26983
|
+
var IV_SIZE = 16;
|
|
26984
|
+
function deriveKey(password, salt, iterations) {
|
|
26985
|
+
return import_crypto_js6.default.PBKDF2(password, salt, {
|
|
26986
|
+
keySize: KEY_SIZE / 32,
|
|
26987
|
+
// WordArray uses 32-bit words
|
|
26988
|
+
iterations,
|
|
26989
|
+
hasher: import_crypto_js6.default.algo.SHA256
|
|
26990
|
+
});
|
|
26991
|
+
}
|
|
26992
|
+
function encrypt2(plaintext, password, options = {}) {
|
|
26993
|
+
const iterations = options.iterations ?? DEFAULT_ITERATIONS;
|
|
26994
|
+
const data = typeof plaintext === "string" ? plaintext : JSON.stringify(plaintext);
|
|
26995
|
+
const salt = import_crypto_js6.default.lib.WordArray.random(SALT_SIZE);
|
|
26996
|
+
const iv = import_crypto_js6.default.lib.WordArray.random(IV_SIZE);
|
|
26997
|
+
const key = deriveKey(password, salt, iterations);
|
|
26998
|
+
const encrypted = import_crypto_js6.default.AES.encrypt(data, key, {
|
|
26999
|
+
iv,
|
|
27000
|
+
mode: import_crypto_js6.default.mode.CBC,
|
|
27001
|
+
padding: import_crypto_js6.default.pad.Pkcs7
|
|
27002
|
+
});
|
|
27003
|
+
return {
|
|
27004
|
+
ciphertext: encrypted.ciphertext.toString(import_crypto_js6.default.enc.Base64),
|
|
27005
|
+
iv: iv.toString(import_crypto_js6.default.enc.Hex),
|
|
27006
|
+
salt: salt.toString(import_crypto_js6.default.enc.Hex),
|
|
27007
|
+
algorithm: "aes-256-cbc",
|
|
27008
|
+
kdf: "pbkdf2",
|
|
27009
|
+
iterations
|
|
27010
|
+
};
|
|
27011
|
+
}
|
|
27012
|
+
function decrypt2(encryptedData, password) {
|
|
27013
|
+
const salt = import_crypto_js6.default.enc.Hex.parse(encryptedData.salt);
|
|
27014
|
+
const iv = import_crypto_js6.default.enc.Hex.parse(encryptedData.iv);
|
|
27015
|
+
const key = deriveKey(password, salt, encryptedData.iterations);
|
|
27016
|
+
const ciphertext = import_crypto_js6.default.enc.Base64.parse(encryptedData.ciphertext);
|
|
27017
|
+
const cipherParams = import_crypto_js6.default.lib.CipherParams.create({
|
|
27018
|
+
ciphertext
|
|
27019
|
+
});
|
|
27020
|
+
const decrypted = import_crypto_js6.default.AES.decrypt(cipherParams, key, {
|
|
27021
|
+
iv,
|
|
27022
|
+
mode: import_crypto_js6.default.mode.CBC,
|
|
27023
|
+
padding: import_crypto_js6.default.pad.Pkcs7
|
|
27024
|
+
});
|
|
27025
|
+
const result = decrypted.toString(import_crypto_js6.default.enc.Utf8);
|
|
27026
|
+
if (!result) {
|
|
27027
|
+
throw new SphereError("Decryption failed: invalid password or corrupted data", "DECRYPTION_ERROR");
|
|
27028
|
+
}
|
|
27029
|
+
return result;
|
|
27030
|
+
}
|
|
27031
|
+
function decryptJson(encryptedData, password) {
|
|
27032
|
+
const decrypted = decrypt2(encryptedData, password);
|
|
27033
|
+
try {
|
|
27034
|
+
return JSON.parse(decrypted);
|
|
27035
|
+
} catch {
|
|
27036
|
+
throw new SphereError("Decryption failed: invalid JSON data", "DECRYPTION_ERROR");
|
|
27037
|
+
}
|
|
27038
|
+
}
|
|
26475
27039
|
function encryptSimple(plaintext, password) {
|
|
26476
27040
|
return import_crypto_js6.default.AES.encrypt(plaintext, password).toString();
|
|
26477
27041
|
}
|
|
@@ -26498,6 +27062,12 @@ function decryptWithSalt(ciphertext, password, salt) {
|
|
|
26498
27062
|
return null;
|
|
26499
27063
|
}
|
|
26500
27064
|
}
|
|
27065
|
+
function encryptMnemonic(mnemonic, password) {
|
|
27066
|
+
return encryptSimple(mnemonic, password);
|
|
27067
|
+
}
|
|
27068
|
+
function decryptMnemonic(encryptedMnemonic, password) {
|
|
27069
|
+
return decryptSimple(encryptedMnemonic, password);
|
|
27070
|
+
}
|
|
26501
27071
|
|
|
26502
27072
|
// core/scan.ts
|
|
26503
27073
|
init_logger();
|
|
@@ -27241,27 +27811,42 @@ async function parseAndDecryptWalletDat(data, password, onProgress) {
|
|
|
27241
27811
|
|
|
27242
27812
|
// core/Sphere.ts
|
|
27243
27813
|
var import_SigningService2 = require("@unicitylabs/state-transition-sdk/lib/sign/SigningService");
|
|
27814
|
+
var import_nostr_js_sdk5 = require("@unicitylabs/nostr-js-sdk");
|
|
27815
|
+
|
|
27816
|
+
// core/address-derivation.ts
|
|
27244
27817
|
var import_TokenType5 = require("@unicitylabs/state-transition-sdk/lib/token/TokenType");
|
|
27245
27818
|
var import_HashAlgorithm7 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
|
|
27246
27819
|
var import_UnmaskedPredicateReference3 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
|
|
27247
|
-
var
|
|
27820
|
+
var UNICITY_TOKEN_TYPE_HEX2 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
|
|
27821
|
+
var COMPRESSED_PUBKEY_RE = /^(02|03)[0-9a-fA-F]{64}$/;
|
|
27822
|
+
async function computeDirectAddressFromChainPubkey(chainPubkey) {
|
|
27823
|
+
if (typeof chainPubkey !== "string" || !COMPRESSED_PUBKEY_RE.test(chainPubkey)) {
|
|
27824
|
+
throw new Error(
|
|
27825
|
+
`computeDirectAddressFromChainPubkey: chainPubkey must be 66-char hex with 02/03 prefix, got "${String(chainPubkey).slice(0, 12)}..."`
|
|
27826
|
+
);
|
|
27827
|
+
}
|
|
27828
|
+
const tokenTypeBytes = Buffer.from(UNICITY_TOKEN_TYPE_HEX2, "hex");
|
|
27829
|
+
const tokenType = new import_TokenType5.TokenType(tokenTypeBytes);
|
|
27830
|
+
const publicKeyBytes = Buffer.from(chainPubkey, "hex");
|
|
27831
|
+
const predicateRef = await import_UnmaskedPredicateReference3.UnmaskedPredicateReference.create(
|
|
27832
|
+
tokenType,
|
|
27833
|
+
"secp256k1",
|
|
27834
|
+
publicKeyBytes,
|
|
27835
|
+
import_HashAlgorithm7.HashAlgorithm.SHA256
|
|
27836
|
+
);
|
|
27837
|
+
return (await predicateRef.toAddress()).toString();
|
|
27838
|
+
}
|
|
27839
|
+
|
|
27840
|
+
// core/Sphere.ts
|
|
27248
27841
|
function isValidNametag2(nametag) {
|
|
27249
27842
|
if ((0, import_nostr_js_sdk5.isPhoneNumber)(nametag)) return true;
|
|
27250
27843
|
return /^[a-z0-9_-]{3,20}$/.test(nametag);
|
|
27251
27844
|
}
|
|
27252
|
-
var UNICITY_TOKEN_TYPE_HEX2 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
|
|
27253
27845
|
async function deriveL3PredicateAddress(privateKey) {
|
|
27254
27846
|
const secret = Buffer.from(privateKey, "hex");
|
|
27255
27847
|
const signingService = await import_SigningService2.SigningService.createFromSecret(secret);
|
|
27256
|
-
const
|
|
27257
|
-
|
|
27258
|
-
const predicateRef = import_UnmaskedPredicateReference3.UnmaskedPredicateReference.create(
|
|
27259
|
-
tokenType,
|
|
27260
|
-
signingService.algorithm,
|
|
27261
|
-
signingService.publicKey,
|
|
27262
|
-
import_HashAlgorithm7.HashAlgorithm.SHA256
|
|
27263
|
-
);
|
|
27264
|
-
return (await (await predicateRef).toAddress()).toString();
|
|
27848
|
+
const pubkeyHex = Buffer.from(signingService.publicKey).toString("hex");
|
|
27849
|
+
return computeDirectAddressFromChainPubkey(pubkeyHex);
|
|
27265
27850
|
}
|
|
27266
27851
|
var Sphere = class _Sphere {
|
|
27267
27852
|
// Singleton
|
|
@@ -31272,8 +31857,38 @@ function createPriceProvider(config) {
|
|
|
31272
31857
|
throw new SphereError(`Unsupported price platform: ${String(config.platform)}`, "INVALID_CONFIG");
|
|
31273
31858
|
}
|
|
31274
31859
|
}
|
|
31860
|
+
|
|
31861
|
+
// core/auth.ts
|
|
31862
|
+
var AuthVerificationError = class extends Error {
|
|
31863
|
+
code;
|
|
31864
|
+
constructor(message, code) {
|
|
31865
|
+
super(message);
|
|
31866
|
+
this.name = "AuthVerificationError";
|
|
31867
|
+
this.code = code;
|
|
31868
|
+
}
|
|
31869
|
+
};
|
|
31870
|
+
async function verifySphereAuth(input) {
|
|
31871
|
+
const { challenge, signature, chainPubkey } = input;
|
|
31872
|
+
let directAddress;
|
|
31873
|
+
try {
|
|
31874
|
+
directAddress = await computeDirectAddressFromChainPubkey(chainPubkey);
|
|
31875
|
+
} catch (err) {
|
|
31876
|
+
throw new AuthVerificationError(
|
|
31877
|
+
`chainPubkey is malformed: ${err.message}`,
|
|
31878
|
+
"PUBKEY_MALFORMED"
|
|
31879
|
+
);
|
|
31880
|
+
}
|
|
31881
|
+
if (!verifySignedMessage(challenge, signature, chainPubkey)) {
|
|
31882
|
+
throw new AuthVerificationError(
|
|
31883
|
+
"Signature does not verify against chainPubkey",
|
|
31884
|
+
"SIGNATURE_INVALID"
|
|
31885
|
+
);
|
|
31886
|
+
}
|
|
31887
|
+
return { chainPubkey, directAddress };
|
|
31888
|
+
}
|
|
31275
31889
|
// Annotate the CommonJS export names for ESM import in node:
|
|
31276
31890
|
0 && (module.exports = {
|
|
31891
|
+
AuthVerificationError,
|
|
31277
31892
|
COIN_TYPES,
|
|
31278
31893
|
CoinGeckoPriceProvider,
|
|
31279
31894
|
CommunicationsModule,
|
|
@@ -31323,6 +31938,8 @@ function createPriceProvider(config) {
|
|
|
31323
31938
|
buildTxfStorageData,
|
|
31324
31939
|
bytesToHex,
|
|
31325
31940
|
checkNetworkHealth,
|
|
31941
|
+
coinIdsMatch,
|
|
31942
|
+
computeDirectAddressFromChainPubkey,
|
|
31326
31943
|
computeSwapId,
|
|
31327
31944
|
countCommittedTransactions,
|
|
31328
31945
|
createAddress,
|
|
@@ -31341,22 +31958,34 @@ function createPriceProvider(config) {
|
|
|
31341
31958
|
createSwapModule,
|
|
31342
31959
|
createTokenValidator,
|
|
31343
31960
|
decodeBech32,
|
|
31961
|
+
decrypt,
|
|
31344
31962
|
decryptCMasterKey,
|
|
31963
|
+
decryptJson,
|
|
31964
|
+
decryptMnemonic,
|
|
31345
31965
|
decryptNametag,
|
|
31346
31966
|
decryptPrivateKey,
|
|
31967
|
+
decryptSimple,
|
|
31347
31968
|
decryptTextFormatKey,
|
|
31969
|
+
decryptWallet,
|
|
31970
|
+
decryptWithSalt,
|
|
31348
31971
|
deriveAddressInfo,
|
|
31349
31972
|
deriveChildKey,
|
|
31350
31973
|
deriveKeyAtPath,
|
|
31351
31974
|
doubleSha256,
|
|
31352
31975
|
encodeBech32,
|
|
31976
|
+
encrypt,
|
|
31977
|
+
encryptMnemonic,
|
|
31353
31978
|
encryptNametag,
|
|
31979
|
+
encryptSimple,
|
|
31980
|
+
encryptWallet,
|
|
31354
31981
|
extractFromText,
|
|
31355
31982
|
findPattern,
|
|
31356
31983
|
forkedKeyFromTokenIdAndState,
|
|
31357
31984
|
formatAmount,
|
|
31985
|
+
generateAddressFromMasterKey,
|
|
31358
31986
|
generateMasterKey,
|
|
31359
31987
|
generateMnemonic,
|
|
31988
|
+
generatePrivateKey,
|
|
31360
31989
|
getAddressHrp,
|
|
31361
31990
|
getAddressId,
|
|
31362
31991
|
getAddressStorageKey,
|
|
@@ -31379,6 +32008,7 @@ function createPriceProvider(config) {
|
|
|
31379
32008
|
hashNametag,
|
|
31380
32009
|
hashSignMessage,
|
|
31381
32010
|
hexToBytes,
|
|
32011
|
+
hexToWIF,
|
|
31382
32012
|
identityFromMnemonicSync,
|
|
31383
32013
|
initSphere,
|
|
31384
32014
|
isArchivedKey,
|
|
@@ -31408,6 +32038,7 @@ function createPriceProvider(config) {
|
|
|
31408
32038
|
logger,
|
|
31409
32039
|
mnemonicToSeedSync,
|
|
31410
32040
|
normalizeAddress,
|
|
32041
|
+
normalizeCoinId,
|
|
31411
32042
|
normalizeNametag,
|
|
31412
32043
|
normalizeSdkTokenToStorage,
|
|
31413
32044
|
objectToTxf,
|
|
@@ -31438,6 +32069,7 @@ function createPriceProvider(config) {
|
|
|
31438
32069
|
verifyManifestIntegrity,
|
|
31439
32070
|
verifyNametagBinding,
|
|
31440
32071
|
verifySignedMessage,
|
|
32072
|
+
verifySphereAuth,
|
|
31441
32073
|
verifySwapSignature
|
|
31442
32074
|
});
|
|
31443
32075
|
/*! Bundled license information:
|