Thanks to visit codestin.com
Credit goes to github.com

Skip to content

FIX: ElectrumTransaction confirmation fields are optional until mined (#8093)#8597

Merged
Overtorment merged 1 commit into
BlueWallet:masterfrom
PeterXMR:fix-8093-electrum-tx-type
May 28, 2026
Merged

FIX: ElectrumTransaction confirmation fields are optional until mined (#8093)#8597
Overtorment merged 1 commit into
BlueWallet:masterfrom
PeterXMR:fix-8093-electrum-tx-type

Conversation

@PeterXMR

Copy link
Copy Markdown
Contributor

Closes #8093.

Why

Electrum's blockchain.transaction.get verbose response does not include blockhash, confirmations, time, or blocktime when the transaction is still in the mempool. The ElectrumTransaction type 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.

How

  • Mark the four confirmation-only fields optional on both ElectrumTransaction and Transaction. The two types describe the same shape and have the same bug.
  • Export ElectrumTransaction so a regression test can reference it directly.
  • 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 to a number without a null check (useWidgetCommunication.ios.ts, PaymentCodesList.tsx). Behaviour is preserved: undefined is treated as 0 confirmations, which was already the intent.
  • Add tests/unit/electrum-transaction-types.test.ts documenting 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 failures

No new dependencies. Pure type-correctness fix; no UI changes, so no screenshot.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, and blocktime optional on both ElectrumTransaction and internal Transaction types.
  • Add nullish-coalescing guards where confirmations is 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 confirmations as 0 via ?? 0, but the later confirmations === 0 branch does not. If confirmations is 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 so undefined is 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.

PeterXMR added a commit to PeterXMR/BlueWallet that referenced this pull request May 26, 2026
…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.
@PeterXMR PeterXMR force-pushed the fix-8093-electrum-tx-type branch from e48573e to 29c3117 Compare May 26, 2026 21:00

@GladosBlueWallet GladosBlueWallet left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mempool shape stops lying, and the guards are where they needed to be. You contained the crash without inventing a new one. Approved.

@GladosBlueWallet

Copy link
Copy Markdown
Collaborator

Wake the fuck up samurai, we have PRs to merge

image

[all PRs for @limpbrains] https://github.com/BlueWallet/BlueWallet/pulls/review-requested/limpbrains

@GladosBlueWallet

Copy link
Copy Markdown
Collaborator

Wake the fuck up samurai, we have PRs to merge

image

[all PRs for @Overtorment] https://github.com/BlueWallet/BlueWallet/pulls/review-requested/Overtorment

@GladosBlueWallet GladosBlueWallet left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread blue_modules/BlueElectrum.ts
Comment thread blue_modules/BlueElectrum.ts
Comment thread blue_modules/BlueElectrum.ts

@GladosBlueWallet GladosBlueWallet left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 GladosBlueWallet left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 Overtorment left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@Overtorment Overtorment merged commit d7261d4 into BlueWallet:master May 28, 2026
12 of 15 checks passed
Danswar added a commit to Danswar/btc-wallet that referenced this pull request Jul 9, 2026
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.
TaprootFreak pushed a commit to DFXswiss/btc-wallet that referenced this pull request Jul 9, 2026
… 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix ElectrumTransaction type

4 participants