FIX: ElectrumTransaction confirmation fields are optional until mined (#8093)#8597
Conversation
There was a problem hiding this comment.
Pull request overview
Updates Electrum transaction typings to match real Electrum verbose responses for mempool (unconfirmed) transactions, preventing runtime crashes from unguarded access to confirmation-only fields.
Changes:
- Make
blockhash,confirmations,time, andblocktimeoptional on bothElectrumTransactionand internalTransactiontypes. - Add nullish-coalescing guards where
confirmationsis compared numerically. - Add a unit test asserting a mempool-shaped verbose transaction object satisfies
ElectrumTransaction.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/electrum-transaction-types.test.ts | Adds a unit test documenting the mempool (unconfirmed) verbose-tx shape against ElectrumTransaction. |
| screen/wallets/PaymentCodesList.tsx | Guards confirmations access when checking if a prior BIP47 notification tx is confirmed. |
| hooks/useWidgetCommunication.ios.ts | Treats missing confirmations as 0 when filtering confirmed transactions. |
| class/wallets/types.ts | Marks confirmation-only fields optional on the internal Transaction type. |
| class/wallets/abstract-hd-electrum-wallet.ts | Simplifies timestamp assignment with a single fallback expression for unconfirmed txs. |
| blue_modules/BlueElectrum.ts | Exports ElectrumTransaction and makes confirmation-only fields optional (plus exports ElectrumTransactionWithHex). |
Comments suppressed due to low confidence (1)
screen/wallets/PaymentCodesList.tsx:302
- The first branch treats missing
confirmationsas 0 via?? 0, but the laterconfirmations === 0branch does not. Ifconfirmationsis absent on a mempool tx, this will skip the “unconfirmed” alert and proceed as if there was no prior notification tx. Consider normalizing once (e.g.,const conf = notificationTx.confirmations ?? 0) and using it for both comparisons soundefinedis consistently treated as 0.
if (notificationTx && (notificationTx.confirmations ?? 0) > 0) {
// we previously sent notification transaction to him, so just need to add him to internals
foundWallet.addBIP47Receiver(newPc);
await foundWallet.syncBip47ReceiversAddresses(newPc); // so we can unwrap and save all his possible addresses
// (for a case if already have txs with him, we will now be able to label them on tx list)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…lueWallet#8093) Follow-up to Copilot review on BlueWallet#8597. After making `Transaction.confirmations` optional, the BIP47 add-contact flow in `PaymentCodesList._addContact` only guarded one of the two branches that read `confirmations`. The other branch still compared `notificationTx.confirmations === 0`, which is `false` when the field is `undefined` (real mempool tx). Both branches would then skip and the code would fall through to creating a *duplicate* notification transaction even though a perfectly valid one already exists in the mempool. Normalize once via `const notificationTxConfirmations = notificationTx?.confirmations ?? 0;` and use the local in both comparisons so an unknown / mempool count is consistently treated as 0 (= unconfirmed).
…BlueWallet#8093) Electrum's `blockchain.transaction.get` verbose response does not include `blockhash`, `confirmations`, `time`, or `blocktime` when the transaction is still in the mempool. Both `ElectrumTransaction` in `blue_modules/BlueElectrum.ts` and the sibling `Transaction` type in `class/wallets/types.ts` declared all four as required, which silently let unguarded access compile and crash at runtime on real mempool data. - Mark the four confirmation-only fields optional on both types. They describe the same shape and have the same bug. - Export `ElectrumTransaction` so a regression test can reference it. - Collapse the two-line `tx.timestamp = tx.blocktime; if (!tx.blocktime) tx.timestamp = ...` pattern in `abstract-hd-electrum-wallet.ts` into a single `||` fallback — type-safe and runtime-equivalent. - Add nullish-coalesce guards at the two call sites that compared `confirmations` directly to a number. In `useWidgetCommunication.ios.ts`, `t.confirmations ?? 0` keeps the filter semantically unchanged. In `PaymentCodesList.tsx`, normalize once via `notificationTx?.confirmations ?? 0` and use the local in both the `> 0` (already confirmed) and `=== 0` (mempool / unconfirmed alert) branches — otherwise a mempool notification tx would skip both branches and the code would create a duplicate notification transaction. - Add `tests/unit/electrum-transaction-types.test.ts` documenting that a mempool-shaped object satisfies the type.
e48573e to
29c3117
Compare
GladosBlueWallet
left a comment
There was a problem hiding this comment.
The mempool shape stops lying, and the guards are where they needed to be. You contained the crash without inventing a new one. Approved.
|
Wake the fuck up samurai, we have PRs to merge [all PRs for @limpbrains] https://github.com/BlueWallet/BlueWallet/pulls/review-requested/limpbrains |
|
Wake the fuck up samurai, we have PRs to merge [all PRs for @Overtorment] https://github.com/BlueWallet/BlueWallet/pulls/review-requested/Overtorment |
GladosBlueWallet
left a comment
There was a problem hiding this comment.
The reconnect logic is still performing the ancient ritual of waiting on a connection that is already dead. A failed ping without resetting the client means the refresh walks straight back into the same socket-shaped grave.
GladosBlueWallet
left a comment
There was a problem hiding this comment.
The mempool shape is now modeled honestly, and the guards line up with the optional fields instead of bluffing. No stray runtime trap jumps out of this patch, so this one can ship.
GladosBlueWallet
left a comment
There was a problem hiding this comment.
The re-request landed, so I checked the same patch again. The guards still line up with the optional fields, and nothing here has started pretending undefined is a valid transaction state. Approved.
Overtorment
left a comment
There was a problem hiding this comment.
ok the change is sound in principle, but the test is dogshit quality ai slop, agents are really bad at writing tests.
pro tip: you literally has to say to your agent make sure tests are not bullshit, they dont just test mocks, or that data we ourselves put into mock is there; make sure tests test happy paths as well as edge cases
im going to merge it anyway, i was going to do some refactoring in tht area, and ill delete the slop test
DefaultPreference.setName was the one call in _initConnection outside the try/catch that guards the other preference writes. If the app group is unavailable it rejects, the connection attempt dies before reaching the Electrum client, and fire-and-forget callers (settings screen, internal retry loop) surface it as an unhandled rejection. Scoped equivalent of BlueWallet#7594 for this fork's older BlueElectrum.js. Their BlueWallet#8597 (optional confirmations on mempool txs) needs no port: this code base already normalizes confirmations at ingestion and null-checks the comparisons.
… guards, Electrum connect resilience (#200) * fix(ios): patch react-native-tcp-socket onConnect nil-address crash GCDAsyncSocket's localHost/connectedHost return nil once a socket has disconnected. If the connection drops between the TLS handshake completing and the connect event being built, onConnect: inserts nil into the event dictionary, throwing an uncaught NSInvalidArgumentException on the module queue and aborting the app. This is the top field crash on 2.0.x (Electrum connections during import/discovery are the main trigger). Ports BlueWallet's patch-package fix 1:1 (BlueWallet#8576) against the same pinned 6.4.1; upstream library bug is still open (Rapsssito/react-native-tcp-socket#197). The guard emits an error event instead of skipping silently so the JS side doesn't hang. * fix: crash focusing amount input via stale ref access The component mixed two ref styles: a createRef object and a callback ref that replaced it with the raw node on mount. Depending on timing, one of the two focus paths always dereferenced the wrong shape (.current on a raw node, or .focus() on an unmounted ref) and threw, which is a fatal crash in release builds. Uses the createRef object consistently and focuses via .current?. Same defect class as BlueWallet#7572; equivalent fix adapted to this component's older class-based shape. * fix: guard multisig xpub lookup and address-list derivation An undefined or unregistered cosigner entry reached isXprvString(undefined) inside address derivation, throwing a cryptic TypeError; on the addresses screen a single failing derivation killed the whole list. Throw a clear error from _getXpubFromCosigner and derive each address in the list behind try/catch so one bad entry degrades gracefully. Equivalent of BlueWallet#7893 adapted to this fork's value-based cosigner lookup. * fix: keep Electrum connect alive when app-group preferences fail DefaultPreference.setName was the one call in _initConnection outside the try/catch that guards the other preference writes. If the app group is unavailable it rejects, the connection attempt dies before reaching the Electrum client, and fire-and-forget callers (settings screen, internal retry loop) surface it as an unhandled rejection. Scoped equivalent of BlueWallet#7594 for this fork's older BlueElectrum.js. Their BlueWallet#8597 (optional confirmations on mempool txs) needs no port: this code base already normalizes confirmations at ingestion and null-checks the comparisons. * fix: reset pending-payment state when switching wallets on receive screen The receive screen's pending-payment watcher (showPendingBalance / showConfirmedBalance / initial balance baselines / eta / poll cadence) was left untouched by the wallet selector, and the view flags render stacked rather than exclusively. After a payment arrived and the user switched to another onchain wallet, the new wallet's address rendered together with the previous wallet's pending widget; the stale baselines could also make the poller misreport a received amount for the new wallet, and the stale bip21 string kept the poller checking the old address during the switch. Reset the whole watcher state machine in onWalletChange. The wallet selector on this screen is fork-specific, so there is no upstream BlueWallet fix to port. * fix: release camera when leaving the multisig cosigner-scan screen The cosigner scanner on add-multisig step 2 rendered its Camera unconditionally. After Create navigates to the wallet, the screen stays mounted in the stack underneath, so the camera session stayed open (camera-in-use indicator) and onReadCode kept firing — scanning any QR from an unrelated screen popped the not-a-cosigner validation alert. Gate the Camera on useIsFocused, the same pattern every other scanner screen in the repo already uses (ScanQRCode, ScanCodeSend, importMultisignature). * fix: remove LayoutAnimation, deprecated and unreliable on the new architecture LayoutAnimation.configureNext arms a GLOBAL animation for whatever layout commit happens next. Several call sites here fire at network-dependent moments (send-screen fee fetch, exchange-rate refresh), so the armed animation can collide with unrelated commits — on Fabric this intermittently leaves views with a layout frame but no drawing. Prime suspect for the field report of the send screen's amount/recipient inputs rendering as blank space while the rest of the screen stays intact (width, scroll-offset and empty-data explanations were each ruled out by on-device injection tests). Ports BlueWallet#8541, which removed every LayoutAnimation call after their RN 83 upgrade for the same reason. Only cosmetic easing on unit switches and fee/discovery loads is lost. * fix: clear amount and label inputs when switching wallets on receive screen The watcher-state reset left the custom amount and label fields populated, so after a wallet switch the form showed the previous wallet's values while the QR encoded the plain new address. Clear the label and reset the amount via the hook's existing resetInput, as the boltcard and cashier screens already do.

Closes #8093.
Why
Electrum's
blockchain.transaction.getverbose response does not includeblockhash,confirmations,time, orblocktimewhen the transaction is still in the mempool. TheElectrumTransactiontype inblue_modules/BlueElectrum.ts(and the siblingTransactiontype inclass/wallets/types.ts) declared all four as required, which silently let unguarded access compile and crash at runtime on real mempool data.How
ElectrumTransactionandTransaction. The two types describe the same shape and have the same bug.ElectrumTransactionso a regression test can reference it directly.tx.timestamp = tx.blocktime; if (!tx.blocktime) tx.timestamp = ...pattern inabstract-hd-electrum-wallet.tsinto a single||fallback — type-safe and runtime-equivalent.confirmationsto a number without a null check (useWidgetCommunication.ios.ts,PaymentCodesList.tsx). Behaviour is preserved:undefinedis treated as 0 confirmations, which was already the intent.tests/unit/electrum-transaction-types.test.tsdocumenting that a mempool-shaped object satisfies the type.Verification
npm run lint— pass (tsc + unused-loc + english-leftovers + eslint)npm run unit— 36 suites, 288 tests pass, 1 skipped, 0 failuresNo new dependencies. Pure type-correctness fix; no UI changes, so no screenshot.