Fix RBF until events arrive - #247

Merged
ben-kaufman merged 1 commit into
feat/onchain-eventsfrom
fix/rbf-display
Nov 27, 2025
Merged

Fix RBF until events arrive#247
ben-kaufman merged 1 commit into
feat/onchain-eventsfrom
fix/rbf-display

Conversation

@ben-kaufman

Copy link
Copy Markdown
Contributor

Fix RBF until onchain events arrive

Screen.Recording.2025-11-27.at.11.18.08.AM.mov

@claude

claudeBot commented Nov 27, 2025

Copy link
Copy Markdown
Contributor

Pull Request Review: Fix RBF until events arrive

Summary

This PR addresses a timing issue in RBF (Replace-By-Fee) transaction handling. The change ensures that the original transaction remains visible in the UI with a boosting status until the replacement transaction is detected via onchain events, rather than immediately marking it as removed.

Code Quality and Architecture

Positive aspects:

  • Clean fix: The solution is minimal and focused, changing only what's necessary
  • Symmetric handling: CPFP already used isBoosted = true with boostTxIds.append(), and now RBF uses the same pattern temporarily
  • Proper cleanup: When the replacement event arrives, both doesExist = false and isBoosted = false are set (CoreService.swift:359)
  • Good logging: Clear log messages explain the state transitions

Changes align with AGENTS.md conventions:

  • Uses proper service layer patterns
  • Maintains existing Bitcoin/Lightning terminology
  • Follows the established activity tracking patterns

Logic Analysis

Before this PR:
In boostOnchainTransaction (RBF path), the code set onchainActivity.doesExist = false immediately

After this PR:
In boostOnchainTransaction (RBF path), the code sets onchainActivity.isBoosted = true (NOT adding to boostTxIds here - that happens in handleOnchainTransactionReplaced)

Why this works:

  1. User initiates RBF and original tx is marked with isBoosted = true
  2. UI shows Confirming in ~X min (boosted) status (ActivityItemView.swift:277-284)
  3. LDK fires onchainTransactionReplaced event
  4. Handler marks original as doesExist = false AND isBoosted = false (CoreService.swift:358-359)
  5. Replacement tx gets isBoosted = true and boostTxIds updated (CoreService.swift:404-405)

Potential Issues and Questions

1. Inconsistency with CPFP pattern
In CPFP (line 986-987), the code sets both isBoosted = true and appends to boostTxIds. In RBF (line 1003), it only sets isBoosted = true without adding to boostTxIds.

This appears intentional since CPFP parent tx still exists (so it makes sense to add the child txid immediately) while RBF original tx will be replaced (and the replacement gets tracked via the event handler). However, a clarifying comment would be helpful.

2. Race condition consideration
What happens if the onchainTransactionReplaced event arrives before the boostOnchainTransaction update completes? Both operations use ServiceQueue.background(.core), so they should be serialized. This is likely safe, but worth verifying in testing.

3. ActivityItemViewModel refresh logic
In ActivityItemViewModel.swift:77-90, there is logic that searches for RBF replacements by matching isBoosted = true, same txType, same value, and later timestamp. During the window where the original has isBoosted = true but the replacement has not arrived yet, this logic might incorrectly match the original to itself. The 3-attempt retry logic (line 95-99) suggests this was anticipated, which is good.

Performance Considerations

Positive:

  • No additional database queries
  • No blocking operations added
  • The removed log statement (AppViewModel.swift) reduces noise

Suggestion: The removed log was actually useful for debugging. Consider downgrading to .debug level instead of removing it entirely.

Security and Bitcoin Considerations

Secure:

  • No changes to actual RBF transaction creation
  • No changes to fee calculation
  • Proper state transitions maintain data integrity
  • Users get better feedback (transaction stays visible during boost)

Test Coverage

Existing tests:

  • TxBumpingTests.swift has comprehensive RBF testing (testBumpFeeByRbf)
  • Tests verify the RBF transaction is created and confirmed
  • Gap: Tests do not verify the isBoosted flag behavior or UI state transitions

Recommendation: Add a unit test to verify the isBoosted state transitions during RBF

UI/UX Impact

User experience improvement:

  • Users now see Confirming in ~X min (boosted) instead of transaction disappearing
  • Reduces confusion during the event propagation window
  • Status icon changes to hourglass with yellow accent

Edge case to verify: If the replacement event never arrives (network issue, node restart, etc.), the original stays in boosted state indefinitely. Is there a timeout or recovery mechanism?

Recommendations

  1. Add clarifying comment (optional but helpful) at CoreService.swift:1003
  2. Restore debug logging (optional) in AppViewModel.swift
  3. Add unit test for isBoosted state transitions during RBF (recommended)
  4. Manual testing of the edge cases mentioned above

Verdict: LGTM with minor suggestions

This is a well-targeted fix that improves UX during RBF operations. The code is clean, follows existing patterns, and addresses a real timing issue. The suggestions above are mostly nice-to-haves for clarity and testing robustness.

Approve with optional follow-ups. The PR is ready to merge as-is, with the recommendations being good candidates for future improvements.

@ben-kaufman
ben-kaufman merged commit ab36651 into feat/onchain-eventsNov 27, 2025
5 checks passed
@ben-kaufman
ben-kaufman deleted the fix/rbf-display branch November 27, 2025 17:01
BitcoinErrorLog pushed a commit to BitcoinErrorLog/bitkit-ios that referenced this pull request Dec 15, 2025
BitcoinErrorLog pushed a commit to BitcoinErrorLog/bitkit-ios that referenced this pull request Feb 7, 2026
Sign up for freeto 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.

2 participants

@ben-kaufman@pwltr
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Fix RBF until events arrive - #247

Merged
ben-kaufman merged 1 commit into
feat/onchain-eventsfrom
fix/rbf-display
Nov 27, 2025
Merged

Fix RBF until events arrive#247
ben-kaufman merged 1 commit into
feat/onchain-eventsfrom
fix/rbf-display

Conversation

@ben-kaufman

Copy link
Copy Markdown
Contributor

Fix RBF until onchain events arrive

Screen.Recording.2025-11-27.at.11.18.08.AM.mov

@claude

claudeBot commented Nov 27, 2025

Copy link
Copy Markdown
Contributor

Pull Request Review: Fix RBF until events arrive

Summary

This PR addresses a timing issue in RBF (Replace-By-Fee) transaction handling. The change ensures that the original transaction remains visible in the UI with a boosting status until the replacement transaction is detected via onchain events, rather than immediately marking it as removed.

Code Quality and Architecture

Positive aspects:

  • Clean fix: The solution is minimal and focused, changing only what's necessary
  • Symmetric handling: CPFP already used isBoosted = true with boostTxIds.append(), and now RBF uses the same pattern temporarily
  • Proper cleanup: When the replacement event arrives, both doesExist = false and isBoosted = false are set (CoreService.swift:359)
  • Good logging: Clear log messages explain the state transitions

Changes align with AGENTS.md conventions:

  • Uses proper service layer patterns
  • Maintains existing Bitcoin/Lightning terminology
  • Follows the established activity tracking patterns

Logic Analysis

Before this PR:
In boostOnchainTransaction (RBF path), the code set onchainActivity.doesExist = false immediately

After this PR:
In boostOnchainTransaction (RBF path), the code sets onchainActivity.isBoosted = true (NOT adding to boostTxIds here - that happens in handleOnchainTransactionReplaced)

Why this works:

  1. User initiates RBF and original tx is marked with isBoosted = true
  2. UI shows Confirming in ~X min (boosted) status (ActivityItemView.swift:277-284)
  3. LDK fires onchainTransactionReplaced event
  4. Handler marks original as doesExist = false AND isBoosted = false (CoreService.swift:358-359)
  5. Replacement tx gets isBoosted = true and boostTxIds updated (CoreService.swift:404-405)

Potential Issues and Questions

1. Inconsistency with CPFP pattern
In CPFP (line 986-987), the code sets both isBoosted = true and appends to boostTxIds. In RBF (line 1003), it only sets isBoosted = true without adding to boostTxIds.

This appears intentional since CPFP parent tx still exists (so it makes sense to add the child txid immediately) while RBF original tx will be replaced (and the replacement gets tracked via the event handler). However, a clarifying comment would be helpful.

2. Race condition consideration
What happens if the onchainTransactionReplaced event arrives before the boostOnchainTransaction update completes? Both operations use ServiceQueue.background(.core), so they should be serialized. This is likely safe, but worth verifying in testing.

3. ActivityItemViewModel refresh logic
In ActivityItemViewModel.swift:77-90, there is logic that searches for RBF replacements by matching isBoosted = true, same txType, same value, and later timestamp. During the window where the original has isBoosted = true but the replacement has not arrived yet, this logic might incorrectly match the original to itself. The 3-attempt retry logic (line 95-99) suggests this was anticipated, which is good.

Performance Considerations

Positive:

  • No additional database queries
  • No blocking operations added
  • The removed log statement (AppViewModel.swift) reduces noise

Suggestion: The removed log was actually useful for debugging. Consider downgrading to .debug level instead of removing it entirely.

Security and Bitcoin Considerations

Secure:

  • No changes to actual RBF transaction creation
  • No changes to fee calculation
  • Proper state transitions maintain data integrity
  • Users get better feedback (transaction stays visible during boost)

Test Coverage

Existing tests:

  • TxBumpingTests.swift has comprehensive RBF testing (testBumpFeeByRbf)
  • Tests verify the RBF transaction is created and confirmed
  • Gap: Tests do not verify the isBoosted flag behavior or UI state transitions

Recommendation: Add a unit test to verify the isBoosted state transitions during RBF

UI/UX Impact

User experience improvement:

  • Users now see Confirming in ~X min (boosted) instead of transaction disappearing
  • Reduces confusion during the event propagation window
  • Status icon changes to hourglass with yellow accent

Edge case to verify: If the replacement event never arrives (network issue, node restart, etc.), the original stays in boosted state indefinitely. Is there a timeout or recovery mechanism?

Recommendations

  1. Add clarifying comment (optional but helpful) at CoreService.swift:1003
  2. Restore debug logging (optional) in AppViewModel.swift
  3. Add unit test for isBoosted state transitions during RBF (recommended)
  4. Manual testing of the edge cases mentioned above

Verdict: LGTM with minor suggestions

This is a well-targeted fix that improves UX during RBF operations. The code is clean, follows existing patterns, and addresses a real timing issue. The suggestions above are mostly nice-to-haves for clarity and testing robustness.

Approve with optional follow-ups. The PR is ready to merge as-is, with the recommendations being good candidates for future improvements.

@ben-kaufman
ben-kaufman merged commit ab36651 into feat/onchain-eventsNov 27, 2025
5 checks passed
@ben-kaufman
ben-kaufman deleted the fix/rbf-display branch November 27, 2025 17:01
BitcoinErrorLog pushed a commit to BitcoinErrorLog/bitkit-ios that referenced this pull request Dec 15, 2025
BitcoinErrorLog pushed a commit to BitcoinErrorLog/bitkit-ios that referenced this pull request Feb 7, 2026
Sign up for freeto 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.

2 participants

@ben-kaufman@pwltr
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix RBF until events arrive - #247

Merged
ben-kaufman merged 1 commit into
feat/onchain-eventsfrom
fix/rbf-display
Nov 27, 2025
Merged

Fix RBF until events arrive#247
ben-kaufman merged 1 commit into
feat/onchain-eventsfrom
fix/rbf-display

Conversation

@ben-kaufman

Copy link
Copy Markdown
Contributor

Fix RBF until onchain events arrive

Screen.Recording.2025-11-27.at.11.18.08.AM.mov

@claude

claudeBot commented Nov 27, 2025

Copy link
Copy Markdown
Contributor

Pull Request Review: Fix RBF until events arrive

Summary

This PR addresses a timing issue in RBF (Replace-By-Fee) transaction handling. The change ensures that the original transaction remains visible in the UI with a boosting status until the replacement transaction is detected via onchain events, rather than immediately marking it as removed.

Code Quality and Architecture

Positive aspects:

  • Clean fix: The solution is minimal and focused, changing only what's necessary
  • Symmetric handling: CPFP already used isBoosted = true with boostTxIds.append(), and now RBF uses the same pattern temporarily
  • Proper cleanup: When the replacement event arrives, both doesExist = false and isBoosted = false are set (CoreService.swift:359)
  • Good logging: Clear log messages explain the state transitions

Changes align with AGENTS.md conventions:

  • Uses proper service layer patterns
  • Maintains existing Bitcoin/Lightning terminology
  • Follows the established activity tracking patterns

Logic Analysis

Before this PR:
In boostOnchainTransaction (RBF path), the code set onchainActivity.doesExist = false immediately

After this PR:
In boostOnchainTransaction (RBF path), the code sets onchainActivity.isBoosted = true (NOT adding to boostTxIds here - that happens in handleOnchainTransactionReplaced)

Why this works:

  1. User initiates RBF and original tx is marked with isBoosted = true
  2. UI shows Confirming in ~X min (boosted) status (ActivityItemView.swift:277-284)
  3. LDK fires onchainTransactionReplaced event
  4. Handler marks original as doesExist = false AND isBoosted = false (CoreService.swift:358-359)
  5. Replacement tx gets isBoosted = true and boostTxIds updated (CoreService.swift:404-405)

Potential Issues and Questions

1. Inconsistency with CPFP pattern
In CPFP (line 986-987), the code sets both isBoosted = true and appends to boostTxIds. In RBF (line 1003), it only sets isBoosted = true without adding to boostTxIds.

This appears intentional since CPFP parent tx still exists (so it makes sense to add the child txid immediately) while RBF original tx will be replaced (and the replacement gets tracked via the event handler). However, a clarifying comment would be helpful.

2. Race condition consideration
What happens if the onchainTransactionReplaced event arrives before the boostOnchainTransaction update completes? Both operations use ServiceQueue.background(.core), so they should be serialized. This is likely safe, but worth verifying in testing.

3. ActivityItemViewModel refresh logic
In ActivityItemViewModel.swift:77-90, there is logic that searches for RBF replacements by matching isBoosted = true, same txType, same value, and later timestamp. During the window where the original has isBoosted = true but the replacement has not arrived yet, this logic might incorrectly match the original to itself. The 3-attempt retry logic (line 95-99) suggests this was anticipated, which is good.

Performance Considerations

Positive:

  • No additional database queries
  • No blocking operations added
  • The removed log statement (AppViewModel.swift) reduces noise

Suggestion: The removed log was actually useful for debugging. Consider downgrading to .debug level instead of removing it entirely.

Security and Bitcoin Considerations

Secure:

  • No changes to actual RBF transaction creation
  • No changes to fee calculation
  • Proper state transitions maintain data integrity
  • Users get better feedback (transaction stays visible during boost)

Test Coverage

Existing tests:

  • TxBumpingTests.swift has comprehensive RBF testing (testBumpFeeByRbf)
  • Tests verify the RBF transaction is created and confirmed
  • Gap: Tests do not verify the isBoosted flag behavior or UI state transitions

Recommendation: Add a unit test to verify the isBoosted state transitions during RBF

UI/UX Impact

User experience improvement:

  • Users now see Confirming in ~X min (boosted) instead of transaction disappearing
  • Reduces confusion during the event propagation window
  • Status icon changes to hourglass with yellow accent

Edge case to verify: If the replacement event never arrives (network issue, node restart, etc.), the original stays in boosted state indefinitely. Is there a timeout or recovery mechanism?

Recommendations

  1. Add clarifying comment (optional but helpful) at CoreService.swift:1003
  2. Restore debug logging (optional) in AppViewModel.swift
  3. Add unit test for isBoosted state transitions during RBF (recommended)
  4. Manual testing of the edge cases mentioned above

Verdict: LGTM with minor suggestions

This is a well-targeted fix that improves UX during RBF operations. The code is clean, follows existing patterns, and addresses a real timing issue. The suggestions above are mostly nice-to-haves for clarity and testing robustness.

Approve with optional follow-ups. The PR is ready to merge as-is, with the recommendations being good candidates for future improvements.

@ben-kaufman
ben-kaufman merged commit ab36651 into feat/onchain-eventsNov 27, 2025
5 checks passed
@ben-kaufman
ben-kaufman deleted the fix/rbf-display branch November 27, 2025 17:01
BitcoinErrorLog pushed a commit to BitcoinErrorLog/bitkit-ios that referenced this pull request Dec 15, 2025
BitcoinErrorLog pushed a commit to BitcoinErrorLog/bitkit-ios that referenced this pull request Feb 7, 2026
Sign up for freeto 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.

2 participants

@ben-kaufman@pwltr
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix RBF until events arrive - #247

Merged
ben-kaufman merged 1 commit into
feat/onchain-eventsfrom
fix/rbf-display
Nov 27, 2025
Merged

Fix RBF until events arrive#247
ben-kaufman merged 1 commit into
feat/onchain-eventsfrom
fix/rbf-display

Conversation

@ben-kaufman

Copy link
Copy Markdown
Contributor

Fix RBF until onchain events arrive

Screen.Recording.2025-11-27.at.11.18.08.AM.mov

@claude

claudeBot commented Nov 27, 2025

Copy link
Copy Markdown
Contributor

Pull Request Review: Fix RBF until events arrive

Summary

This PR addresses a timing issue in RBF (Replace-By-Fee) transaction handling. The change ensures that the original transaction remains visible in the UI with a boosting status until the replacement transaction is detected via onchain events, rather than immediately marking it as removed.

Code Quality and Architecture

Positive aspects:

  • Clean fix: The solution is minimal and focused, changing only what's necessary
  • Symmetric handling: CPFP already used isBoosted = true with boostTxIds.append(), and now RBF uses the same pattern temporarily
  • Proper cleanup: When the replacement event arrives, both doesExist = false and isBoosted = false are set (CoreService.swift:359)
  • Good logging: Clear log messages explain the state transitions

Changes align with AGENTS.md conventions:

  • Uses proper service layer patterns
  • Maintains existing Bitcoin/Lightning terminology
  • Follows the established activity tracking patterns

Logic Analysis

Before this PR:
In boostOnchainTransaction (RBF path), the code set onchainActivity.doesExist = false immediately

After this PR:
In boostOnchainTransaction (RBF path), the code sets onchainActivity.isBoosted = true (NOT adding to boostTxIds here - that happens in handleOnchainTransactionReplaced)

Why this works:

  1. User initiates RBF and original tx is marked with isBoosted = true
  2. UI shows Confirming in ~X min (boosted) status (ActivityItemView.swift:277-284)
  3. LDK fires onchainTransactionReplaced event
  4. Handler marks original as doesExist = false AND isBoosted = false (CoreService.swift:358-359)
  5. Replacement tx gets isBoosted = true and boostTxIds updated (CoreService.swift:404-405)

Potential Issues and Questions

1. Inconsistency with CPFP pattern
In CPFP (line 986-987), the code sets both isBoosted = true and appends to boostTxIds. In RBF (line 1003), it only sets isBoosted = true without adding to boostTxIds.

This appears intentional since CPFP parent tx still exists (so it makes sense to add the child txid immediately) while RBF original tx will be replaced (and the replacement gets tracked via the event handler). However, a clarifying comment would be helpful.

2. Race condition consideration
What happens if the onchainTransactionReplaced event arrives before the boostOnchainTransaction update completes? Both operations use ServiceQueue.background(.core), so they should be serialized. This is likely safe, but worth verifying in testing.

3. ActivityItemViewModel refresh logic
In ActivityItemViewModel.swift:77-90, there is logic that searches for RBF replacements by matching isBoosted = true, same txType, same value, and later timestamp. During the window where the original has isBoosted = true but the replacement has not arrived yet, this logic might incorrectly match the original to itself. The 3-attempt retry logic (line 95-99) suggests this was anticipated, which is good.

Performance Considerations

Positive:

  • No additional database queries
  • No blocking operations added
  • The removed log statement (AppViewModel.swift) reduces noise

Suggestion: The removed log was actually useful for debugging. Consider downgrading to .debug level instead of removing it entirely.

Security and Bitcoin Considerations

Secure:

  • No changes to actual RBF transaction creation
  • No changes to fee calculation
  • Proper state transitions maintain data integrity
  • Users get better feedback (transaction stays visible during boost)

Test Coverage

Existing tests:

  • TxBumpingTests.swift has comprehensive RBF testing (testBumpFeeByRbf)
  • Tests verify the RBF transaction is created and confirmed
  • Gap: Tests do not verify the isBoosted flag behavior or UI state transitions

Recommendation: Add a unit test to verify the isBoosted state transitions during RBF

UI/UX Impact

User experience improvement:

  • Users now see Confirming in ~X min (boosted) instead of transaction disappearing
  • Reduces confusion during the event propagation window
  • Status icon changes to hourglass with yellow accent

Edge case to verify: If the replacement event never arrives (network issue, node restart, etc.), the original stays in boosted state indefinitely. Is there a timeout or recovery mechanism?

Recommendations

  1. Add clarifying comment (optional but helpful) at CoreService.swift:1003
  2. Restore debug logging (optional) in AppViewModel.swift
  3. Add unit test for isBoosted state transitions during RBF (recommended)
  4. Manual testing of the edge cases mentioned above

Verdict: LGTM with minor suggestions

This is a well-targeted fix that improves UX during RBF operations. The code is clean, follows existing patterns, and addresses a real timing issue. The suggestions above are mostly nice-to-haves for clarity and testing robustness.

Approve with optional follow-ups. The PR is ready to merge as-is, with the recommendations being good candidates for future improvements.

@ben-kaufman
ben-kaufman merged commit ab36651 into feat/onchain-eventsNov 27, 2025
5 checks passed
@ben-kaufman
ben-kaufman deleted the fix/rbf-display branch November 27, 2025 17:01
BitcoinErrorLog pushed a commit to BitcoinErrorLog/bitkit-ios that referenced this pull request Dec 15, 2025
BitcoinErrorLog pushed a commit to BitcoinErrorLog/bitkit-ios that referenced this pull request Feb 7, 2026
Sign up for freeto 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.

2 participants

@ben-kaufman@pwltr
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Fix RBF until events arrive - #247

Merged
ben-kaufman merged 1 commit into
feat/onchain-eventsfrom
fix/rbf-display
Nov 27, 2025
Merged

Fix RBF until events arrive#247
ben-kaufman merged 1 commit into
feat/onchain-eventsfrom
fix/rbf-display

Conversation

@ben-kaufman

Copy link
Copy Markdown
Contributor

Fix RBF until onchain events arrive

Screen.Recording.2025-11-27.at.11.18.08.AM.mov

@claude

claudeBot commented Nov 27, 2025

Copy link
Copy Markdown
Contributor

Pull Request Review: Fix RBF until events arrive

Summary

This PR addresses a timing issue in RBF (Replace-By-Fee) transaction handling. The change ensures that the original transaction remains visible in the UI with a boosting status until the replacement transaction is detected via onchain events, rather than immediately marking it as removed.

Code Quality and Architecture

Positive aspects:

  • Clean fix: The solution is minimal and focused, changing only what's necessary
  • Symmetric handling: CPFP already used isBoosted = true with boostTxIds.append(), and now RBF uses the same pattern temporarily
  • Proper cleanup: When the replacement event arrives, both doesExist = false and isBoosted = false are set (CoreService.swift:359)
  • Good logging: Clear log messages explain the state transitions

Changes align with AGENTS.md conventions:

  • Uses proper service layer patterns
  • Maintains existing Bitcoin/Lightning terminology
  • Follows the established activity tracking patterns

Logic Analysis

Before this PR:
In boostOnchainTransaction (RBF path), the code set onchainActivity.doesExist = false immediately

After this PR:
In boostOnchainTransaction (RBF path), the code sets onchainActivity.isBoosted = true (NOT adding to boostTxIds here - that happens in handleOnchainTransactionReplaced)

Why this works:

  1. User initiates RBF and original tx is marked with isBoosted = true
  2. UI shows Confirming in ~X min (boosted) status (ActivityItemView.swift:277-284)
  3. LDK fires onchainTransactionReplaced event
  4. Handler marks original as doesExist = false AND isBoosted = false (CoreService.swift:358-359)
  5. Replacement tx gets isBoosted = true and boostTxIds updated (CoreService.swift:404-405)

Potential Issues and Questions

1. Inconsistency with CPFP pattern
In CPFP (line 986-987), the code sets both isBoosted = true and appends to boostTxIds. In RBF (line 1003), it only sets isBoosted = true without adding to boostTxIds.

This appears intentional since CPFP parent tx still exists (so it makes sense to add the child txid immediately) while RBF original tx will be replaced (and the replacement gets tracked via the event handler). However, a clarifying comment would be helpful.

2. Race condition consideration
What happens if the onchainTransactionReplaced event arrives before the boostOnchainTransaction update completes? Both operations use ServiceQueue.background(.core), so they should be serialized. This is likely safe, but worth verifying in testing.

3. ActivityItemViewModel refresh logic
In ActivityItemViewModel.swift:77-90, there is logic that searches for RBF replacements by matching isBoosted = true, same txType, same value, and later timestamp. During the window where the original has isBoosted = true but the replacement has not arrived yet, this logic might incorrectly match the original to itself. The 3-attempt retry logic (line 95-99) suggests this was anticipated, which is good.

Performance Considerations

Positive:

  • No additional database queries
  • No blocking operations added
  • The removed log statement (AppViewModel.swift) reduces noise

Suggestion: The removed log was actually useful for debugging. Consider downgrading to .debug level instead of removing it entirely.

Security and Bitcoin Considerations

Secure:

  • No changes to actual RBF transaction creation
  • No changes to fee calculation
  • Proper state transitions maintain data integrity
  • Users get better feedback (transaction stays visible during boost)

Test Coverage

Existing tests:

  • TxBumpingTests.swift has comprehensive RBF testing (testBumpFeeByRbf)
  • Tests verify the RBF transaction is created and confirmed
  • Gap: Tests do not verify the isBoosted flag behavior or UI state transitions

Recommendation: Add a unit test to verify the isBoosted state transitions during RBF

UI/UX Impact

User experience improvement:

  • Users now see Confirming in ~X min (boosted) instead of transaction disappearing
  • Reduces confusion during the event propagation window
  • Status icon changes to hourglass with yellow accent

Edge case to verify: If the replacement event never arrives (network issue, node restart, etc.), the original stays in boosted state indefinitely. Is there a timeout or recovery mechanism?

Recommendations

  1. Add clarifying comment (optional but helpful) at CoreService.swift:1003
  2. Restore debug logging (optional) in AppViewModel.swift
  3. Add unit test for isBoosted state transitions during RBF (recommended)
  4. Manual testing of the edge cases mentioned above

Verdict: LGTM with minor suggestions

This is a well-targeted fix that improves UX during RBF operations. The code is clean, follows existing patterns, and addresses a real timing issue. The suggestions above are mostly nice-to-haves for clarity and testing robustness.

Approve with optional follow-ups. The PR is ready to merge as-is, with the recommendations being good candidates for future improvements.

@ben-kaufman
ben-kaufman merged commit ab36651 into feat/onchain-eventsNov 27, 2025
5 checks passed
@ben-kaufman
ben-kaufman deleted the fix/rbf-display branch November 27, 2025 17:01
BitcoinErrorLog pushed a commit to BitcoinErrorLog/bitkit-ios that referenced this pull request Dec 15, 2025
BitcoinErrorLog pushed a commit to BitcoinErrorLog/bitkit-ios that referenced this pull request Feb 7, 2026
Sign up for freeto 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.

2 participants

@ben-kaufman@pwltr
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix RBF until events arrive - #247

Merged
ben-kaufman merged 1 commit into
feat/onchain-eventsfrom
fix/rbf-display
Nov 27, 2025
Merged

Fix RBF until events arrive#247
ben-kaufman merged 1 commit into
feat/onchain-eventsfrom
fix/rbf-display

Conversation

@ben-kaufman

Copy link
Copy Markdown
Contributor

Fix RBF until onchain events arrive

Screen.Recording.2025-11-27.at.11.18.08.AM.mov

@claude

claudeBot commented Nov 27, 2025

Copy link
Copy Markdown
Contributor

Pull Request Review: Fix RBF until events arrive

Summary

This PR addresses a timing issue in RBF (Replace-By-Fee) transaction handling. The change ensures that the original transaction remains visible in the UI with a boosting status until the replacement transaction is detected via onchain events, rather than immediately marking it as removed.

Code Quality and Architecture

Positive aspects:

  • Clean fix: The solution is minimal and focused, changing only what's necessary
  • Symmetric handling: CPFP already used isBoosted = true with boostTxIds.append(), and now RBF uses the same pattern temporarily
  • Proper cleanup: When the replacement event arrives, both doesExist = false and isBoosted = false are set (CoreService.swift:359)
  • Good logging: Clear log messages explain the state transitions

Changes align with AGENTS.md conventions:

  • Uses proper service layer patterns
  • Maintains existing Bitcoin/Lightning terminology
  • Follows the established activity tracking patterns

Logic Analysis

Before this PR:
In boostOnchainTransaction (RBF path), the code set onchainActivity.doesExist = false immediately

After this PR:
In boostOnchainTransaction (RBF path), the code sets onchainActivity.isBoosted = true (NOT adding to boostTxIds here - that happens in handleOnchainTransactionReplaced)

Why this works:

  1. User initiates RBF and original tx is marked with isBoosted = true
  2. UI shows Confirming in ~X min (boosted) status (ActivityItemView.swift:277-284)
  3. LDK fires onchainTransactionReplaced event
  4. Handler marks original as doesExist = false AND isBoosted = false (CoreService.swift:358-359)
  5. Replacement tx gets isBoosted = true and boostTxIds updated (CoreService.swift:404-405)

Potential Issues and Questions

1. Inconsistency with CPFP pattern
In CPFP (line 986-987), the code sets both isBoosted = true and appends to boostTxIds. In RBF (line 1003), it only sets isBoosted = true without adding to boostTxIds.

This appears intentional since CPFP parent tx still exists (so it makes sense to add the child txid immediately) while RBF original tx will be replaced (and the replacement gets tracked via the event handler). However, a clarifying comment would be helpful.

2. Race condition consideration
What happens if the onchainTransactionReplaced event arrives before the boostOnchainTransaction update completes? Both operations use ServiceQueue.background(.core), so they should be serialized. This is likely safe, but worth verifying in testing.

3. ActivityItemViewModel refresh logic
In ActivityItemViewModel.swift:77-90, there is logic that searches for RBF replacements by matching isBoosted = true, same txType, same value, and later timestamp. During the window where the original has isBoosted = true but the replacement has not arrived yet, this logic might incorrectly match the original to itself. The 3-attempt retry logic (line 95-99) suggests this was anticipated, which is good.

Performance Considerations

Positive:

  • No additional database queries
  • No blocking operations added
  • The removed log statement (AppViewModel.swift) reduces noise

Suggestion: The removed log was actually useful for debugging. Consider downgrading to .debug level instead of removing it entirely.

Security and Bitcoin Considerations

Secure:

  • No changes to actual RBF transaction creation
  • No changes to fee calculation
  • Proper state transitions maintain data integrity
  • Users get better feedback (transaction stays visible during boost)

Test Coverage

Existing tests:

  • TxBumpingTests.swift has comprehensive RBF testing (testBumpFeeByRbf)
  • Tests verify the RBF transaction is created and confirmed
  • Gap: Tests do not verify the isBoosted flag behavior or UI state transitions

Recommendation: Add a unit test to verify the isBoosted state transitions during RBF

UI/UX Impact

User experience improvement:

  • Users now see Confirming in ~X min (boosted) instead of transaction disappearing
  • Reduces confusion during the event propagation window
  • Status icon changes to hourglass with yellow accent

Edge case to verify: If the replacement event never arrives (network issue, node restart, etc.), the original stays in boosted state indefinitely. Is there a timeout or recovery mechanism?

Recommendations

  1. Add clarifying comment (optional but helpful) at CoreService.swift:1003
  2. Restore debug logging (optional) in AppViewModel.swift
  3. Add unit test for isBoosted state transitions during RBF (recommended)
  4. Manual testing of the edge cases mentioned above

Verdict: LGTM with minor suggestions

This is a well-targeted fix that improves UX during RBF operations. The code is clean, follows existing patterns, and addresses a real timing issue. The suggestions above are mostly nice-to-haves for clarity and testing robustness.

Approve with optional follow-ups. The PR is ready to merge as-is, with the recommendations being good candidates for future improvements.

@ben-kaufman
ben-kaufman merged commit ab36651 into feat/onchain-eventsNov 27, 2025
5 checks passed
@ben-kaufman
ben-kaufman deleted the fix/rbf-display branch November 27, 2025 17:01
BitcoinErrorLog pushed a commit to BitcoinErrorLog/bitkit-ios that referenced this pull request Dec 15, 2025
BitcoinErrorLog pushed a commit to BitcoinErrorLog/bitkit-ios that referenced this pull request Feb 7, 2026
Sign up for freeto 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.

2 participants

@ben-kaufman@pwltr
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix RBF until events arrive - #247

Merged
ben-kaufman merged 1 commit into
feat/onchain-eventsfrom
fix/rbf-display
Nov 27, 2025
Merged

Fix RBF until events arrive#247
ben-kaufman merged 1 commit into
feat/onchain-eventsfrom
fix/rbf-display

Conversation

@ben-kaufman

Copy link
Copy Markdown
Contributor

Fix RBF until onchain events arrive

Screen.Recording.2025-11-27.at.11.18.08.AM.mov

@claude

claudeBot commented Nov 27, 2025

Copy link
Copy Markdown
Contributor

Pull Request Review: Fix RBF until events arrive

Summary

This PR addresses a timing issue in RBF (Replace-By-Fee) transaction handling. The change ensures that the original transaction remains visible in the UI with a boosting status until the replacement transaction is detected via onchain events, rather than immediately marking it as removed.

Code Quality and Architecture

Positive aspects:

  • Clean fix: The solution is minimal and focused, changing only what's necessary
  • Symmetric handling: CPFP already used isBoosted = true with boostTxIds.append(), and now RBF uses the same pattern temporarily
  • Proper cleanup: When the replacement event arrives, both doesExist = false and isBoosted = false are set (CoreService.swift:359)
  • Good logging: Clear log messages explain the state transitions

Changes align with AGENTS.md conventions:

  • Uses proper service layer patterns
  • Maintains existing Bitcoin/Lightning terminology
  • Follows the established activity tracking patterns

Logic Analysis

Before this PR:
In boostOnchainTransaction (RBF path), the code set onchainActivity.doesExist = false immediately

After this PR:
In boostOnchainTransaction (RBF path), the code sets onchainActivity.isBoosted = true (NOT adding to boostTxIds here - that happens in handleOnchainTransactionReplaced)

Why this works:

  1. User initiates RBF and original tx is marked with isBoosted = true
  2. UI shows Confirming in ~X min (boosted) status (ActivityItemView.swift:277-284)
  3. LDK fires onchainTransactionReplaced event
  4. Handler marks original as doesExist = false AND isBoosted = false (CoreService.swift:358-359)
  5. Replacement tx gets isBoosted = true and boostTxIds updated (CoreService.swift:404-405)

Potential Issues and Questions

1. Inconsistency with CPFP pattern
In CPFP (line 986-987), the code sets both isBoosted = true and appends to boostTxIds. In RBF (line 1003), it only sets isBoosted = true without adding to boostTxIds.

This appears intentional since CPFP parent tx still exists (so it makes sense to add the child txid immediately) while RBF original tx will be replaced (and the replacement gets tracked via the event handler). However, a clarifying comment would be helpful.

2. Race condition consideration
What happens if the onchainTransactionReplaced event arrives before the boostOnchainTransaction update completes? Both operations use ServiceQueue.background(.core), so they should be serialized. This is likely safe, but worth verifying in testing.

3. ActivityItemViewModel refresh logic
In ActivityItemViewModel.swift:77-90, there is logic that searches for RBF replacements by matching isBoosted = true, same txType, same value, and later timestamp. During the window where the original has isBoosted = true but the replacement has not arrived yet, this logic might incorrectly match the original to itself. The 3-attempt retry logic (line 95-99) suggests this was anticipated, which is good.

Performance Considerations

Positive:

  • No additional database queries
  • No blocking operations added
  • The removed log statement (AppViewModel.swift) reduces noise

Suggestion: The removed log was actually useful for debugging. Consider downgrading to .debug level instead of removing it entirely.

Security and Bitcoin Considerations

Secure:

  • No changes to actual RBF transaction creation
  • No changes to fee calculation
  • Proper state transitions maintain data integrity
  • Users get better feedback (transaction stays visible during boost)

Test Coverage

Existing tests:

  • TxBumpingTests.swift has comprehensive RBF testing (testBumpFeeByRbf)
  • Tests verify the RBF transaction is created and confirmed
  • Gap: Tests do not verify the isBoosted flag behavior or UI state transitions

Recommendation: Add a unit test to verify the isBoosted state transitions during RBF

UI/UX Impact

User experience improvement:

  • Users now see Confirming in ~X min (boosted) instead of transaction disappearing
  • Reduces confusion during the event propagation window
  • Status icon changes to hourglass with yellow accent

Edge case to verify: If the replacement event never arrives (network issue, node restart, etc.), the original stays in boosted state indefinitely. Is there a timeout or recovery mechanism?

Recommendations

  1. Add clarifying comment (optional but helpful) at CoreService.swift:1003
  2. Restore debug logging (optional) in AppViewModel.swift
  3. Add unit test for isBoosted state transitions during RBF (recommended)
  4. Manual testing of the edge cases mentioned above

Verdict: LGTM with minor suggestions

This is a well-targeted fix that improves UX during RBF operations. The code is clean, follows existing patterns, and addresses a real timing issue. The suggestions above are mostly nice-to-haves for clarity and testing robustness.

Approve with optional follow-ups. The PR is ready to merge as-is, with the recommendations being good candidates for future improvements.

@ben-kaufman
ben-kaufman merged commit ab36651 into feat/onchain-eventsNov 27, 2025
5 checks passed
@ben-kaufman
ben-kaufman deleted the fix/rbf-display branch November 27, 2025 17:01
BitcoinErrorLog pushed a commit to BitcoinErrorLog/bitkit-ios that referenced this pull request Dec 15, 2025
BitcoinErrorLog pushed a commit to BitcoinErrorLog/bitkit-ios that referenced this pull request Feb 7, 2026
Sign up for freeto 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.

2 participants

@ben-kaufman@pwltr
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Fix RBF until events arrive - #247

Merged
ben-kaufman merged 1 commit into
feat/onchain-eventsfrom
fix/rbf-display
Nov 27, 2025
Merged

Fix RBF until events arrive#247
ben-kaufman merged 1 commit into
feat/onchain-eventsfrom
fix/rbf-display

Conversation

@ben-kaufman

Copy link
Copy Markdown
Contributor

Fix RBF until onchain events arrive

Screen.Recording.2025-11-27.at.11.18.08.AM.mov

@claude

claudeBot commented Nov 27, 2025

Copy link
Copy Markdown
Contributor

Pull Request Review: Fix RBF until events arrive

Summary

This PR addresses a timing issue in RBF (Replace-By-Fee) transaction handling. The change ensures that the original transaction remains visible in the UI with a boosting status until the replacement transaction is detected via onchain events, rather than immediately marking it as removed.

Code Quality and Architecture

Positive aspects:

  • Clean fix: The solution is minimal and focused, changing only what's necessary
  • Symmetric handling: CPFP already used isBoosted = true with boostTxIds.append(), and now RBF uses the same pattern temporarily
  • Proper cleanup: When the replacement event arrives, both doesExist = false and isBoosted = false are set (CoreService.swift:359)
  • Good logging: Clear log messages explain the state transitions

Changes align with AGENTS.md conventions:

  • Uses proper service layer patterns
  • Maintains existing Bitcoin/Lightning terminology
  • Follows the established activity tracking patterns

Logic Analysis

Before this PR:
In boostOnchainTransaction (RBF path), the code set onchainActivity.doesExist = false immediately

After this PR:
In boostOnchainTransaction (RBF path), the code sets onchainActivity.isBoosted = true (NOT adding to boostTxIds here - that happens in handleOnchainTransactionReplaced)

Why this works:

  1. User initiates RBF and original tx is marked with isBoosted = true
  2. UI shows Confirming in ~X min (boosted) status (ActivityItemView.swift:277-284)
  3. LDK fires onchainTransactionReplaced event
  4. Handler marks original as doesExist = false AND isBoosted = false (CoreService.swift:358-359)
  5. Replacement tx gets isBoosted = true and boostTxIds updated (CoreService.swift:404-405)

Potential Issues and Questions

1. Inconsistency with CPFP pattern
In CPFP (line 986-987), the code sets both isBoosted = true and appends to boostTxIds. In RBF (line 1003), it only sets isBoosted = true without adding to boostTxIds.

This appears intentional since CPFP parent tx still exists (so it makes sense to add the child txid immediately) while RBF original tx will be replaced (and the replacement gets tracked via the event handler). However, a clarifying comment would be helpful.

2. Race condition consideration
What happens if the onchainTransactionReplaced event arrives before the boostOnchainTransaction update completes? Both operations use ServiceQueue.background(.core), so they should be serialized. This is likely safe, but worth verifying in testing.

3. ActivityItemViewModel refresh logic
In ActivityItemViewModel.swift:77-90, there is logic that searches for RBF replacements by matching isBoosted = true, same txType, same value, and later timestamp. During the window where the original has isBoosted = true but the replacement has not arrived yet, this logic might incorrectly match the original to itself. The 3-attempt retry logic (line 95-99) suggests this was anticipated, which is good.

Performance Considerations

Positive:

  • No additional database queries
  • No blocking operations added
  • The removed log statement (AppViewModel.swift) reduces noise

Suggestion: The removed log was actually useful for debugging. Consider downgrading to .debug level instead of removing it entirely.

Security and Bitcoin Considerations

Secure:

  • No changes to actual RBF transaction creation
  • No changes to fee calculation
  • Proper state transitions maintain data integrity
  • Users get better feedback (transaction stays visible during boost)

Test Coverage

Existing tests:

  • TxBumpingTests.swift has comprehensive RBF testing (testBumpFeeByRbf)
  • Tests verify the RBF transaction is created and confirmed
  • Gap: Tests do not verify the isBoosted flag behavior or UI state transitions

Recommendation: Add a unit test to verify the isBoosted state transitions during RBF

UI/UX Impact

User experience improvement:

  • Users now see Confirming in ~X min (boosted) instead of transaction disappearing
  • Reduces confusion during the event propagation window
  • Status icon changes to hourglass with yellow accent

Edge case to verify: If the replacement event never arrives (network issue, node restart, etc.), the original stays in boosted state indefinitely. Is there a timeout or recovery mechanism?

Recommendations

  1. Add clarifying comment (optional but helpful) at CoreService.swift:1003
  2. Restore debug logging (optional) in AppViewModel.swift
  3. Add unit test for isBoosted state transitions during RBF (recommended)
  4. Manual testing of the edge cases mentioned above

Verdict: LGTM with minor suggestions

This is a well-targeted fix that improves UX during RBF operations. The code is clean, follows existing patterns, and addresses a real timing issue. The suggestions above are mostly nice-to-haves for clarity and testing robustness.

Approve with optional follow-ups. The PR is ready to merge as-is, with the recommendations being good candidates for future improvements.

@ben-kaufman
ben-kaufman merged commit ab36651 into feat/onchain-eventsNov 27, 2025
5 checks passed
@ben-kaufman
ben-kaufman deleted the fix/rbf-display branch November 27, 2025 17:01
BitcoinErrorLog pushed a commit to BitcoinErrorLog/bitkit-ios that referenced this pull request Dec 15, 2025
BitcoinErrorLog pushed a commit to BitcoinErrorLog/bitkit-ios that referenced this pull request Feb 7, 2026
Sign up for freeto 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.

2 participants

@ben-kaufman@pwltr