Add gift codes support - #473

Merged
jvsena42 merged 11 commits into
masterfrom
feat/gift-codes
Nov 24, 2025
Merged

Add gift codes support#473
jvsena42 merged 11 commits into
masterfrom
feat/gift-codes

Conversation

@ben-kaufman

@ben-kaufmanben-kaufman commented Nov 20, 2025

Copy link
Copy Markdown
Contributor

Added gift codes support.

In the demo:

  1. Redeem valid code, no open channels
  2. Try redeem the same code and fail
  3. Redeem a new code when already having a channel
  4. Try redeeming a code which doesn't exist
Screen.Recording.2025-11-19.at.9.42.46.PM.mov

Commands to test with:
npx uri-scheme open "bitkit://gift-ben19112025-3000" --android
npx uri-scheme open "bitkit://gift-notben19112025-3000" --android

Even when the app is closed, running one of the commands should make the app open and claim.
Can try a non existent code with:
npx uri-scheme open "bitkit://gift-x-3000" --android

Can try a code which has already been out of gifts with:
npx uri-scheme open "bitkit://gift-phil30102025-3000" --android

Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftErrorSheet.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated

@jvsena42jvsena42 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.

Just missing some open comments

OBS: On the android repo, the business logic is centered in the repositories.
The methods are exposed to the ViewModels with the return wrapped in a Result class

Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Fixed
Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Fixed

@jvsena42jvsena42 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.

Worked on tests ✅
Found one last issue

Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
jvsena42
jvsena42 previously approved these changes Nov 21, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
ovitrif
ovitrif previously approved these changes Nov 24, 2025

@ovitrifovitrif 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.

ACK & tested with all gift codes combinations.

pushed a few small edits.

Only having one uncertainty about the use of:

 lightningRepo.executeWhenNodeRunning(
operationName ="claimGiftCode",
waitTimeout = waitTimeout,
) {
Result.success(Unit)
}.getOrThrow()

If I understand correctly, we could change the body of BlocktankRepo.claimGiftCode so that everything that comes after this block could actually be passed inside the block.

Does it make sense to you?!

My understanding is that the purpose of the empty call with a simple Result success is because we want to wait for the node to init if not ready.

Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Outdated
@claude

claudeBot commented Nov 24, 2025

Copy link
Copy Markdown
Contributor

Pull Request Review: Gift Codes Support

Thank you for this comprehensive implementation of gift codes functionality! Overall, the code is well-structured and follows good practices. Below are my findings and suggestions:

✅ Strengths

  1. Clean Architecture: Good separation of concerns between UI (GiftSheet, GiftViewModel), repository (BlocktankRepo), and business logic layers
  2. Error Handling: Proper use of Result types and comprehensive error classification (Used, UsedUp, Error)
  3. UI/UX: Well-designed loading states and error screens with clear user feedback
  4. Code Reusability: Good refactoring of calculateRemoteBalance into a reusable extension function
  5. Type Safety: Good use of sealed classes for GiftRoute and GiftClaimResult

🐛 Potential Bugs & Issues

Critical

  1. Race Condition in GiftViewModel.initialize() (GiftViewModel.kt:47-58)

    • The isClaiming flag is checked before emitting Loading, but the flag is set inside claimGift()
    • If initialize() is called twice rapidly, both calls could pass the check and launch claimGift() concurrently
    • Recommendation: Set isClaiming = true before launching the coroutine, or use a mutex/atomic operation
  2. Missing Null Safety (BlocktankRepo.kt:465)

    • openedOrder.channel?.fundingTx?.id ?: orderId falls back to orderId, but the activity record expects a valid payment hash or transaction ID
    • If the channel opening fails but doesn't throw an exception, this could create invalid activity records
    • Recommendation: Add validation or throw an exception if fundingTx.id is null
  3. No Result Handling for giftPay() (BlocktankRepo.kt:446-448)

    • The giftPay() call doesn't await or check the result, so payment failures go undetected
    • Users might see a success message even if the payment failed
    • Recommendation: Await the result and handle potential failures appropriately

Moderate

  1. Fixed Delay May Be Insufficient (BlocktankRepo.kt:423)

    • The 2-second PEER_CONNECTION_DELAY_MS is a fixed delay that may not be reliable across all network conditions
    • Recommendation: Consider implementing a retry mechanism or checking peer connection status instead of a fixed delay
  2. Double LaunchedEffect Collection (GiftSheet.kt:39-43, 45-57)

    • Two separate LaunchedEffect(Unit) blocks collecting from different flows could lead to ordering issues
    • Recommendation: Combine into a single LaunchedEffect or use remember { } to ensure proper initialization order
  3. Missing Validation (BlocktankRepo.kt:416-417)

    • Gift code format isn't validated (e.g., does it need to match a pattern?)
    • Amount isn't checked against minimum/maximum thresholds
    • Recommendation: Add format validation and reasonable bounds checking

🔒 Security Concerns

  1. Gift Code in Activity Message (GiftViewModel.kt:108)

    • The gift code is stored in plaintext in the activity message field
    • If gift codes are meant to be secret or single-use, this could be a privacy concern
    • Recommendation: Consider whether gift codes need to be stored, or if they should be hashed/obfuscated
  2. No Rate Limiting

    • The implementation doesn't appear to have rate limiting on gift code attempts
    • An attacker could potentially brute-force gift codes
    • Recommendation: Consider implementing client-side rate limiting or exponential backoff on failures
  3. Error Message Information Disclosure (GiftViewModel.kt:133)

    • The error checking uses case-insensitive matching on full error messages, which could match unintended patterns
    • Recommendation: Use more specific error codes or structured error types instead of string matching

⚡ Performance Considerations

  1. Blocking UI Thread (GiftSheet.kt:30-31)

    • LaunchedEffect(sheet.code, sheet.amount) will re-initialize if these values change
    • Could cause multiple simultaneous claim attempts
    • Recommendation: Add a check to prevent re-initialization if already claiming
  2. StateFlow Buffer Size (GiftViewModel.kt:34, 37)

    • extraBufferCapacity = 1 means events can be dropped if not collected immediately
    • With sheet transitions, this could cause missed navigation events
    • Recommendation: Consider using replay = 1 or ensuring collection starts before events are emitted

🧪 Test Coverage

  1. No Tests for New Functionality

    • BlocktankRepoTest.kt only has 2 lines added (dependency injection)
    • No tests for:
      • claimGiftCode()
      • claimGiftCodeWithLiquidity()
      • claimGiftCodeWithoutLiquidity()
      • GiftViewModel logic
      • UI navigation flows
    • Recommendation: Add comprehensive unit tests for the new repository methods and ViewModel logic
  2. Integration Test Coverage

    • The deep link handling (bitkit://gift-...) isn't covered by tests
    • Recommendation: Add integration tests for the gift code URI scheme handling

📝 Code Quality Suggestions

  1. Magic Numbers (GiftViewModel.kt:144)

    • NODE_STARTUP_TIMEOUT_MS = 30_000L is defined but differs from TIMEOUT_GIFT_CODE = 30.seconds in BlocktankRepo
    • Recommendation: Consolidate timeout constants or document why they differ
  2. Inconsistent Error Handling Pattern (GiftViewModel.kt:115)

    • insertGiftActivity() uses .getOrThrow() which will crash if it fails, but claimGiftCode() returns a Result
    • Recommendation: Handle the Result properly or document why crashing is acceptable here
  3. Missing Documentation

    • The new public API methods lack KDoc comments explaining parameters, return values, and potential exceptions
    • Recommendation: Add documentation for claimGiftCode() and GiftClaimResult
  4. Suppress Annotation Justification (BlocktankRepo.kt:53)

    • The @Suppress("LongParameterList") annotation was added but this is a known issue with DI
    • Recommendation: This is acceptable for constructor injection, but consider if any parameters could be grouped into a configuration object

🎯 Minor Improvements

  1. String Concatenation (GiftViewModel.kt:133)

    • currentError.toString() + (currentError.message ?: "") could include the message twice
    • Recommendation: Use currentError.message ?: currentError.toString() instead
  2. Unused Import (BlocktankRepo.kt:489-490)

    • Consider converting SuccessWithLiquidity to a data object for Kotlin 1.9+ consistency
    • Currently mixing object and data class in the same sealed class hierarchy
  3. Constant Naming (GiftRoute.kt:4)

    • IMAGE_WIDTH_FRACTION is defined at file level but only used in UI files
    • Recommendation: Move to a shared UI constants file or make it private to the UI components

✨ Overall Assessment

This is a solid implementation with good architectural decisions. The main concerns are:

  • Race conditions in initialization (high priority)
  • Missing error handling for payment execution (high priority)
  • Lack of test coverage (high priority)
  • Some security considerations around rate limiting and data storage (medium priority)

The code follows Kotlin best practices and integrates well with the existing codebase. Once the critical issues are addressed, this will be a robust feature.


Review completed by Claude Code

@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@jvsena42
jvsena42 merged commit b68efb6 into masterNov 24, 2025
18 checks passed
@jvsena42
jvsena42 deleted the feat/gift-codes branch November 24, 2025 19:34
@jvsena42jvsena42 mentioned this pull request Jan 26, 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.

4 participants

@ben-kaufman@ovitrif@jvsena42@github-advanced-security
, '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

Add gift codes support - #473

Merged
jvsena42 merged 11 commits into
masterfrom
feat/gift-codes
Nov 24, 2025
Merged

Add gift codes support#473
jvsena42 merged 11 commits into
masterfrom
feat/gift-codes

Conversation

@ben-kaufman

@ben-kaufmanben-kaufman commented Nov 20, 2025

Copy link
Copy Markdown
Contributor

Added gift codes support.

In the demo:

  1. Redeem valid code, no open channels
  2. Try redeem the same code and fail
  3. Redeem a new code when already having a channel
  4. Try redeeming a code which doesn't exist
Screen.Recording.2025-11-19.at.9.42.46.PM.mov

Commands to test with:
npx uri-scheme open "bitkit://gift-ben19112025-3000" --android
npx uri-scheme open "bitkit://gift-notben19112025-3000" --android

Even when the app is closed, running one of the commands should make the app open and claim.
Can try a non existent code with:
npx uri-scheme open "bitkit://gift-x-3000" --android

Can try a code which has already been out of gifts with:
npx uri-scheme open "bitkit://gift-phil30102025-3000" --android

Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftErrorSheet.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated

@jvsena42jvsena42 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.

Just missing some open comments

OBS: On the android repo, the business logic is centered in the repositories.
The methods are exposed to the ViewModels with the return wrapped in a Result class

Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Fixed
Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Fixed

@jvsena42jvsena42 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.

Worked on tests ✅
Found one last issue

Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
jvsena42
jvsena42 previously approved these changes Nov 21, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
ovitrif
ovitrif previously approved these changes Nov 24, 2025

@ovitrifovitrif 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.

ACK & tested with all gift codes combinations.

pushed a few small edits.

Only having one uncertainty about the use of:

 lightningRepo.executeWhenNodeRunning(
operationName ="claimGiftCode",
waitTimeout = waitTimeout,
) {
Result.success(Unit)
}.getOrThrow()

If I understand correctly, we could change the body of BlocktankRepo.claimGiftCode so that everything that comes after this block could actually be passed inside the block.

Does it make sense to you?!

My understanding is that the purpose of the empty call with a simple Result success is because we want to wait for the node to init if not ready.

Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Outdated
@claude

claudeBot commented Nov 24, 2025

Copy link
Copy Markdown
Contributor

Pull Request Review: Gift Codes Support

Thank you for this comprehensive implementation of gift codes functionality! Overall, the code is well-structured and follows good practices. Below are my findings and suggestions:

✅ Strengths

  1. Clean Architecture: Good separation of concerns between UI (GiftSheet, GiftViewModel), repository (BlocktankRepo), and business logic layers
  2. Error Handling: Proper use of Result types and comprehensive error classification (Used, UsedUp, Error)
  3. UI/UX: Well-designed loading states and error screens with clear user feedback
  4. Code Reusability: Good refactoring of calculateRemoteBalance into a reusable extension function
  5. Type Safety: Good use of sealed classes for GiftRoute and GiftClaimResult

🐛 Potential Bugs & Issues

Critical

  1. Race Condition in GiftViewModel.initialize() (GiftViewModel.kt:47-58)

    • The isClaiming flag is checked before emitting Loading, but the flag is set inside claimGift()
    • If initialize() is called twice rapidly, both calls could pass the check and launch claimGift() concurrently
    • Recommendation: Set isClaiming = true before launching the coroutine, or use a mutex/atomic operation
  2. Missing Null Safety (BlocktankRepo.kt:465)

    • openedOrder.channel?.fundingTx?.id ?: orderId falls back to orderId, but the activity record expects a valid payment hash or transaction ID
    • If the channel opening fails but doesn't throw an exception, this could create invalid activity records
    • Recommendation: Add validation or throw an exception if fundingTx.id is null
  3. No Result Handling for giftPay() (BlocktankRepo.kt:446-448)

    • The giftPay() call doesn't await or check the result, so payment failures go undetected
    • Users might see a success message even if the payment failed
    • Recommendation: Await the result and handle potential failures appropriately

Moderate

  1. Fixed Delay May Be Insufficient (BlocktankRepo.kt:423)

    • The 2-second PEER_CONNECTION_DELAY_MS is a fixed delay that may not be reliable across all network conditions
    • Recommendation: Consider implementing a retry mechanism or checking peer connection status instead of a fixed delay
  2. Double LaunchedEffect Collection (GiftSheet.kt:39-43, 45-57)

    • Two separate LaunchedEffect(Unit) blocks collecting from different flows could lead to ordering issues
    • Recommendation: Combine into a single LaunchedEffect or use remember { } to ensure proper initialization order
  3. Missing Validation (BlocktankRepo.kt:416-417)

    • Gift code format isn't validated (e.g., does it need to match a pattern?)
    • Amount isn't checked against minimum/maximum thresholds
    • Recommendation: Add format validation and reasonable bounds checking

🔒 Security Concerns

  1. Gift Code in Activity Message (GiftViewModel.kt:108)

    • The gift code is stored in plaintext in the activity message field
    • If gift codes are meant to be secret or single-use, this could be a privacy concern
    • Recommendation: Consider whether gift codes need to be stored, or if they should be hashed/obfuscated
  2. No Rate Limiting

    • The implementation doesn't appear to have rate limiting on gift code attempts
    • An attacker could potentially brute-force gift codes
    • Recommendation: Consider implementing client-side rate limiting or exponential backoff on failures
  3. Error Message Information Disclosure (GiftViewModel.kt:133)

    • The error checking uses case-insensitive matching on full error messages, which could match unintended patterns
    • Recommendation: Use more specific error codes or structured error types instead of string matching

⚡ Performance Considerations

  1. Blocking UI Thread (GiftSheet.kt:30-31)

    • LaunchedEffect(sheet.code, sheet.amount) will re-initialize if these values change
    • Could cause multiple simultaneous claim attempts
    • Recommendation: Add a check to prevent re-initialization if already claiming
  2. StateFlow Buffer Size (GiftViewModel.kt:34, 37)

    • extraBufferCapacity = 1 means events can be dropped if not collected immediately
    • With sheet transitions, this could cause missed navigation events
    • Recommendation: Consider using replay = 1 or ensuring collection starts before events are emitted

🧪 Test Coverage

  1. No Tests for New Functionality

    • BlocktankRepoTest.kt only has 2 lines added (dependency injection)
    • No tests for:
      • claimGiftCode()
      • claimGiftCodeWithLiquidity()
      • claimGiftCodeWithoutLiquidity()
      • GiftViewModel logic
      • UI navigation flows
    • Recommendation: Add comprehensive unit tests for the new repository methods and ViewModel logic
  2. Integration Test Coverage

    • The deep link handling (bitkit://gift-...) isn't covered by tests
    • Recommendation: Add integration tests for the gift code URI scheme handling

📝 Code Quality Suggestions

  1. Magic Numbers (GiftViewModel.kt:144)

    • NODE_STARTUP_TIMEOUT_MS = 30_000L is defined but differs from TIMEOUT_GIFT_CODE = 30.seconds in BlocktankRepo
    • Recommendation: Consolidate timeout constants or document why they differ
  2. Inconsistent Error Handling Pattern (GiftViewModel.kt:115)

    • insertGiftActivity() uses .getOrThrow() which will crash if it fails, but claimGiftCode() returns a Result
    • Recommendation: Handle the Result properly or document why crashing is acceptable here
  3. Missing Documentation

    • The new public API methods lack KDoc comments explaining parameters, return values, and potential exceptions
    • Recommendation: Add documentation for claimGiftCode() and GiftClaimResult
  4. Suppress Annotation Justification (BlocktankRepo.kt:53)

    • The @Suppress("LongParameterList") annotation was added but this is a known issue with DI
    • Recommendation: This is acceptable for constructor injection, but consider if any parameters could be grouped into a configuration object

🎯 Minor Improvements

  1. String Concatenation (GiftViewModel.kt:133)

    • currentError.toString() + (currentError.message ?: "") could include the message twice
    • Recommendation: Use currentError.message ?: currentError.toString() instead
  2. Unused Import (BlocktankRepo.kt:489-490)

    • Consider converting SuccessWithLiquidity to a data object for Kotlin 1.9+ consistency
    • Currently mixing object and data class in the same sealed class hierarchy
  3. Constant Naming (GiftRoute.kt:4)

    • IMAGE_WIDTH_FRACTION is defined at file level but only used in UI files
    • Recommendation: Move to a shared UI constants file or make it private to the UI components

✨ Overall Assessment

This is a solid implementation with good architectural decisions. The main concerns are:

  • Race conditions in initialization (high priority)
  • Missing error handling for payment execution (high priority)
  • Lack of test coverage (high priority)
  • Some security considerations around rate limiting and data storage (medium priority)

The code follows Kotlin best practices and integrates well with the existing codebase. Once the critical issues are addressed, this will be a robust feature.


Review completed by Claude Code

@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@jvsena42
jvsena42 merged commit b68efb6 into masterNov 24, 2025
18 checks passed
@jvsena42
jvsena42 deleted the feat/gift-codes branch November 24, 2025 19:34
@jvsena42jvsena42 mentioned this pull request Jan 26, 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.

4 participants

@ben-kaufman@ovitrif@jvsena42@github-advanced-security
, '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

Add gift codes support - #473

Merged
jvsena42 merged 11 commits into
masterfrom
feat/gift-codes
Nov 24, 2025
Merged

Add gift codes support#473
jvsena42 merged 11 commits into
masterfrom
feat/gift-codes

Conversation

@ben-kaufman

@ben-kaufmanben-kaufman commented Nov 20, 2025

Copy link
Copy Markdown
Contributor

Added gift codes support.

In the demo:

  1. Redeem valid code, no open channels
  2. Try redeem the same code and fail
  3. Redeem a new code when already having a channel
  4. Try redeeming a code which doesn't exist
Screen.Recording.2025-11-19.at.9.42.46.PM.mov

Commands to test with:
npx uri-scheme open "bitkit://gift-ben19112025-3000" --android
npx uri-scheme open "bitkit://gift-notben19112025-3000" --android

Even when the app is closed, running one of the commands should make the app open and claim.
Can try a non existent code with:
npx uri-scheme open "bitkit://gift-x-3000" --android

Can try a code which has already been out of gifts with:
npx uri-scheme open "bitkit://gift-phil30102025-3000" --android

Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftErrorSheet.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated

@jvsena42jvsena42 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.

Just missing some open comments

OBS: On the android repo, the business logic is centered in the repositories.
The methods are exposed to the ViewModels with the return wrapped in a Result class

Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Fixed
Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Fixed

@jvsena42jvsena42 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.

Worked on tests ✅
Found one last issue

Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
jvsena42
jvsena42 previously approved these changes Nov 21, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
ovitrif
ovitrif previously approved these changes Nov 24, 2025

@ovitrifovitrif 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.

ACK & tested with all gift codes combinations.

pushed a few small edits.

Only having one uncertainty about the use of:

 lightningRepo.executeWhenNodeRunning(
operationName ="claimGiftCode",
waitTimeout = waitTimeout,
) {
Result.success(Unit)
}.getOrThrow()

If I understand correctly, we could change the body of BlocktankRepo.claimGiftCode so that everything that comes after this block could actually be passed inside the block.

Does it make sense to you?!

My understanding is that the purpose of the empty call with a simple Result success is because we want to wait for the node to init if not ready.

Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Outdated
@claude

claudeBot commented Nov 24, 2025

Copy link
Copy Markdown
Contributor

Pull Request Review: Gift Codes Support

Thank you for this comprehensive implementation of gift codes functionality! Overall, the code is well-structured and follows good practices. Below are my findings and suggestions:

✅ Strengths

  1. Clean Architecture: Good separation of concerns between UI (GiftSheet, GiftViewModel), repository (BlocktankRepo), and business logic layers
  2. Error Handling: Proper use of Result types and comprehensive error classification (Used, UsedUp, Error)
  3. UI/UX: Well-designed loading states and error screens with clear user feedback
  4. Code Reusability: Good refactoring of calculateRemoteBalance into a reusable extension function
  5. Type Safety: Good use of sealed classes for GiftRoute and GiftClaimResult

🐛 Potential Bugs & Issues

Critical

  1. Race Condition in GiftViewModel.initialize() (GiftViewModel.kt:47-58)

    • The isClaiming flag is checked before emitting Loading, but the flag is set inside claimGift()
    • If initialize() is called twice rapidly, both calls could pass the check and launch claimGift() concurrently
    • Recommendation: Set isClaiming = true before launching the coroutine, or use a mutex/atomic operation
  2. Missing Null Safety (BlocktankRepo.kt:465)

    • openedOrder.channel?.fundingTx?.id ?: orderId falls back to orderId, but the activity record expects a valid payment hash or transaction ID
    • If the channel opening fails but doesn't throw an exception, this could create invalid activity records
    • Recommendation: Add validation or throw an exception if fundingTx.id is null
  3. No Result Handling for giftPay() (BlocktankRepo.kt:446-448)

    • The giftPay() call doesn't await or check the result, so payment failures go undetected
    • Users might see a success message even if the payment failed
    • Recommendation: Await the result and handle potential failures appropriately

Moderate

  1. Fixed Delay May Be Insufficient (BlocktankRepo.kt:423)

    • The 2-second PEER_CONNECTION_DELAY_MS is a fixed delay that may not be reliable across all network conditions
    • Recommendation: Consider implementing a retry mechanism or checking peer connection status instead of a fixed delay
  2. Double LaunchedEffect Collection (GiftSheet.kt:39-43, 45-57)

    • Two separate LaunchedEffect(Unit) blocks collecting from different flows could lead to ordering issues
    • Recommendation: Combine into a single LaunchedEffect or use remember { } to ensure proper initialization order
  3. Missing Validation (BlocktankRepo.kt:416-417)

    • Gift code format isn't validated (e.g., does it need to match a pattern?)
    • Amount isn't checked against minimum/maximum thresholds
    • Recommendation: Add format validation and reasonable bounds checking

🔒 Security Concerns

  1. Gift Code in Activity Message (GiftViewModel.kt:108)

    • The gift code is stored in plaintext in the activity message field
    • If gift codes are meant to be secret or single-use, this could be a privacy concern
    • Recommendation: Consider whether gift codes need to be stored, or if they should be hashed/obfuscated
  2. No Rate Limiting

    • The implementation doesn't appear to have rate limiting on gift code attempts
    • An attacker could potentially brute-force gift codes
    • Recommendation: Consider implementing client-side rate limiting or exponential backoff on failures
  3. Error Message Information Disclosure (GiftViewModel.kt:133)

    • The error checking uses case-insensitive matching on full error messages, which could match unintended patterns
    • Recommendation: Use more specific error codes or structured error types instead of string matching

⚡ Performance Considerations

  1. Blocking UI Thread (GiftSheet.kt:30-31)

    • LaunchedEffect(sheet.code, sheet.amount) will re-initialize if these values change
    • Could cause multiple simultaneous claim attempts
    • Recommendation: Add a check to prevent re-initialization if already claiming
  2. StateFlow Buffer Size (GiftViewModel.kt:34, 37)

    • extraBufferCapacity = 1 means events can be dropped if not collected immediately
    • With sheet transitions, this could cause missed navigation events
    • Recommendation: Consider using replay = 1 or ensuring collection starts before events are emitted

🧪 Test Coverage

  1. No Tests for New Functionality

    • BlocktankRepoTest.kt only has 2 lines added (dependency injection)
    • No tests for:
      • claimGiftCode()
      • claimGiftCodeWithLiquidity()
      • claimGiftCodeWithoutLiquidity()
      • GiftViewModel logic
      • UI navigation flows
    • Recommendation: Add comprehensive unit tests for the new repository methods and ViewModel logic
  2. Integration Test Coverage

    • The deep link handling (bitkit://gift-...) isn't covered by tests
    • Recommendation: Add integration tests for the gift code URI scheme handling

📝 Code Quality Suggestions

  1. Magic Numbers (GiftViewModel.kt:144)

    • NODE_STARTUP_TIMEOUT_MS = 30_000L is defined but differs from TIMEOUT_GIFT_CODE = 30.seconds in BlocktankRepo
    • Recommendation: Consolidate timeout constants or document why they differ
  2. Inconsistent Error Handling Pattern (GiftViewModel.kt:115)

    • insertGiftActivity() uses .getOrThrow() which will crash if it fails, but claimGiftCode() returns a Result
    • Recommendation: Handle the Result properly or document why crashing is acceptable here
  3. Missing Documentation

    • The new public API methods lack KDoc comments explaining parameters, return values, and potential exceptions
    • Recommendation: Add documentation for claimGiftCode() and GiftClaimResult
  4. Suppress Annotation Justification (BlocktankRepo.kt:53)

    • The @Suppress("LongParameterList") annotation was added but this is a known issue with DI
    • Recommendation: This is acceptable for constructor injection, but consider if any parameters could be grouped into a configuration object

🎯 Minor Improvements

  1. String Concatenation (GiftViewModel.kt:133)

    • currentError.toString() + (currentError.message ?: "") could include the message twice
    • Recommendation: Use currentError.message ?: currentError.toString() instead
  2. Unused Import (BlocktankRepo.kt:489-490)

    • Consider converting SuccessWithLiquidity to a data object for Kotlin 1.9+ consistency
    • Currently mixing object and data class in the same sealed class hierarchy
  3. Constant Naming (GiftRoute.kt:4)

    • IMAGE_WIDTH_FRACTION is defined at file level but only used in UI files
    • Recommendation: Move to a shared UI constants file or make it private to the UI components

✨ Overall Assessment

This is a solid implementation with good architectural decisions. The main concerns are:

  • Race conditions in initialization (high priority)
  • Missing error handling for payment execution (high priority)
  • Lack of test coverage (high priority)
  • Some security considerations around rate limiting and data storage (medium priority)

The code follows Kotlin best practices and integrates well with the existing codebase. Once the critical issues are addressed, this will be a robust feature.


Review completed by Claude Code

@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@jvsena42
jvsena42 merged commit b68efb6 into masterNov 24, 2025
18 checks passed
@jvsena42
jvsena42 deleted the feat/gift-codes branch November 24, 2025 19:34
@jvsena42jvsena42 mentioned this pull request Jan 26, 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.

4 participants

@ben-kaufman@ovitrif@jvsena42@github-advanced-security
, '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

Add gift codes support - #473

Merged
jvsena42 merged 11 commits into
masterfrom
feat/gift-codes
Nov 24, 2025
Merged

Add gift codes support#473
jvsena42 merged 11 commits into
masterfrom
feat/gift-codes

Conversation

@ben-kaufman

@ben-kaufmanben-kaufman commented Nov 20, 2025

Copy link
Copy Markdown
Contributor

Added gift codes support.

In the demo:

  1. Redeem valid code, no open channels
  2. Try redeem the same code and fail
  3. Redeem a new code when already having a channel
  4. Try redeeming a code which doesn't exist
Screen.Recording.2025-11-19.at.9.42.46.PM.mov

Commands to test with:
npx uri-scheme open "bitkit://gift-ben19112025-3000" --android
npx uri-scheme open "bitkit://gift-notben19112025-3000" --android

Even when the app is closed, running one of the commands should make the app open and claim.
Can try a non existent code with:
npx uri-scheme open "bitkit://gift-x-3000" --android

Can try a code which has already been out of gifts with:
npx uri-scheme open "bitkit://gift-phil30102025-3000" --android

Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftErrorSheet.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated

@jvsena42jvsena42 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.

Just missing some open comments

OBS: On the android repo, the business logic is centered in the repositories.
The methods are exposed to the ViewModels with the return wrapped in a Result class

Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Fixed
Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Fixed

@jvsena42jvsena42 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.

Worked on tests ✅
Found one last issue

Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
jvsena42
jvsena42 previously approved these changes Nov 21, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
ovitrif
ovitrif previously approved these changes Nov 24, 2025

@ovitrifovitrif 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.

ACK & tested with all gift codes combinations.

pushed a few small edits.

Only having one uncertainty about the use of:

 lightningRepo.executeWhenNodeRunning(
operationName ="claimGiftCode",
waitTimeout = waitTimeout,
) {
Result.success(Unit)
}.getOrThrow()

If I understand correctly, we could change the body of BlocktankRepo.claimGiftCode so that everything that comes after this block could actually be passed inside the block.

Does it make sense to you?!

My understanding is that the purpose of the empty call with a simple Result success is because we want to wait for the node to init if not ready.

Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Outdated
@claude

claudeBot commented Nov 24, 2025

Copy link
Copy Markdown
Contributor

Pull Request Review: Gift Codes Support

Thank you for this comprehensive implementation of gift codes functionality! Overall, the code is well-structured and follows good practices. Below are my findings and suggestions:

✅ Strengths

  1. Clean Architecture: Good separation of concerns between UI (GiftSheet, GiftViewModel), repository (BlocktankRepo), and business logic layers
  2. Error Handling: Proper use of Result types and comprehensive error classification (Used, UsedUp, Error)
  3. UI/UX: Well-designed loading states and error screens with clear user feedback
  4. Code Reusability: Good refactoring of calculateRemoteBalance into a reusable extension function
  5. Type Safety: Good use of sealed classes for GiftRoute and GiftClaimResult

🐛 Potential Bugs & Issues

Critical

  1. Race Condition in GiftViewModel.initialize() (GiftViewModel.kt:47-58)

    • The isClaiming flag is checked before emitting Loading, but the flag is set inside claimGift()
    • If initialize() is called twice rapidly, both calls could pass the check and launch claimGift() concurrently
    • Recommendation: Set isClaiming = true before launching the coroutine, or use a mutex/atomic operation
  2. Missing Null Safety (BlocktankRepo.kt:465)

    • openedOrder.channel?.fundingTx?.id ?: orderId falls back to orderId, but the activity record expects a valid payment hash or transaction ID
    • If the channel opening fails but doesn't throw an exception, this could create invalid activity records
    • Recommendation: Add validation or throw an exception if fundingTx.id is null
  3. No Result Handling for giftPay() (BlocktankRepo.kt:446-448)

    • The giftPay() call doesn't await or check the result, so payment failures go undetected
    • Users might see a success message even if the payment failed
    • Recommendation: Await the result and handle potential failures appropriately

Moderate

  1. Fixed Delay May Be Insufficient (BlocktankRepo.kt:423)

    • The 2-second PEER_CONNECTION_DELAY_MS is a fixed delay that may not be reliable across all network conditions
    • Recommendation: Consider implementing a retry mechanism or checking peer connection status instead of a fixed delay
  2. Double LaunchedEffect Collection (GiftSheet.kt:39-43, 45-57)

    • Two separate LaunchedEffect(Unit) blocks collecting from different flows could lead to ordering issues
    • Recommendation: Combine into a single LaunchedEffect or use remember { } to ensure proper initialization order
  3. Missing Validation (BlocktankRepo.kt:416-417)

    • Gift code format isn't validated (e.g., does it need to match a pattern?)
    • Amount isn't checked against minimum/maximum thresholds
    • Recommendation: Add format validation and reasonable bounds checking

🔒 Security Concerns

  1. Gift Code in Activity Message (GiftViewModel.kt:108)

    • The gift code is stored in plaintext in the activity message field
    • If gift codes are meant to be secret or single-use, this could be a privacy concern
    • Recommendation: Consider whether gift codes need to be stored, or if they should be hashed/obfuscated
  2. No Rate Limiting

    • The implementation doesn't appear to have rate limiting on gift code attempts
    • An attacker could potentially brute-force gift codes
    • Recommendation: Consider implementing client-side rate limiting or exponential backoff on failures
  3. Error Message Information Disclosure (GiftViewModel.kt:133)

    • The error checking uses case-insensitive matching on full error messages, which could match unintended patterns
    • Recommendation: Use more specific error codes or structured error types instead of string matching

⚡ Performance Considerations

  1. Blocking UI Thread (GiftSheet.kt:30-31)

    • LaunchedEffect(sheet.code, sheet.amount) will re-initialize if these values change
    • Could cause multiple simultaneous claim attempts
    • Recommendation: Add a check to prevent re-initialization if already claiming
  2. StateFlow Buffer Size (GiftViewModel.kt:34, 37)

    • extraBufferCapacity = 1 means events can be dropped if not collected immediately
    • With sheet transitions, this could cause missed navigation events
    • Recommendation: Consider using replay = 1 or ensuring collection starts before events are emitted

🧪 Test Coverage

  1. No Tests for New Functionality

    • BlocktankRepoTest.kt only has 2 lines added (dependency injection)
    • No tests for:
      • claimGiftCode()
      • claimGiftCodeWithLiquidity()
      • claimGiftCodeWithoutLiquidity()
      • GiftViewModel logic
      • UI navigation flows
    • Recommendation: Add comprehensive unit tests for the new repository methods and ViewModel logic
  2. Integration Test Coverage

    • The deep link handling (bitkit://gift-...) isn't covered by tests
    • Recommendation: Add integration tests for the gift code URI scheme handling

📝 Code Quality Suggestions

  1. Magic Numbers (GiftViewModel.kt:144)

    • NODE_STARTUP_TIMEOUT_MS = 30_000L is defined but differs from TIMEOUT_GIFT_CODE = 30.seconds in BlocktankRepo
    • Recommendation: Consolidate timeout constants or document why they differ
  2. Inconsistent Error Handling Pattern (GiftViewModel.kt:115)

    • insertGiftActivity() uses .getOrThrow() which will crash if it fails, but claimGiftCode() returns a Result
    • Recommendation: Handle the Result properly or document why crashing is acceptable here
  3. Missing Documentation

    • The new public API methods lack KDoc comments explaining parameters, return values, and potential exceptions
    • Recommendation: Add documentation for claimGiftCode() and GiftClaimResult
  4. Suppress Annotation Justification (BlocktankRepo.kt:53)

    • The @Suppress("LongParameterList") annotation was added but this is a known issue with DI
    • Recommendation: This is acceptable for constructor injection, but consider if any parameters could be grouped into a configuration object

🎯 Minor Improvements

  1. String Concatenation (GiftViewModel.kt:133)

    • currentError.toString() + (currentError.message ?: "") could include the message twice
    • Recommendation: Use currentError.message ?: currentError.toString() instead
  2. Unused Import (BlocktankRepo.kt:489-490)

    • Consider converting SuccessWithLiquidity to a data object for Kotlin 1.9+ consistency
    • Currently mixing object and data class in the same sealed class hierarchy
  3. Constant Naming (GiftRoute.kt:4)

    • IMAGE_WIDTH_FRACTION is defined at file level but only used in UI files
    • Recommendation: Move to a shared UI constants file or make it private to the UI components

✨ Overall Assessment

This is a solid implementation with good architectural decisions. The main concerns are:

  • Race conditions in initialization (high priority)
  • Missing error handling for payment execution (high priority)
  • Lack of test coverage (high priority)
  • Some security considerations around rate limiting and data storage (medium priority)

The code follows Kotlin best practices and integrates well with the existing codebase. Once the critical issues are addressed, this will be a robust feature.


Review completed by Claude Code

@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@jvsena42
jvsena42 merged commit b68efb6 into masterNov 24, 2025
18 checks passed
@jvsena42
jvsena42 deleted the feat/gift-codes branch November 24, 2025 19:34
@jvsena42jvsena42 mentioned this pull request Jan 26, 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.

4 participants

@ben-kaufman@ovitrif@jvsena42@github-advanced-security
, '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

Add gift codes support - #473

Merged
jvsena42 merged 11 commits into
masterfrom
feat/gift-codes
Nov 24, 2025
Merged

Add gift codes support#473
jvsena42 merged 11 commits into
masterfrom
feat/gift-codes

Conversation

@ben-kaufman

@ben-kaufmanben-kaufman commented Nov 20, 2025

Copy link
Copy Markdown
Contributor

Added gift codes support.

In the demo:

  1. Redeem valid code, no open channels
  2. Try redeem the same code and fail
  3. Redeem a new code when already having a channel
  4. Try redeeming a code which doesn't exist
Screen.Recording.2025-11-19.at.9.42.46.PM.mov

Commands to test with:
npx uri-scheme open "bitkit://gift-ben19112025-3000" --android
npx uri-scheme open "bitkit://gift-notben19112025-3000" --android

Even when the app is closed, running one of the commands should make the app open and claim.
Can try a non existent code with:
npx uri-scheme open "bitkit://gift-x-3000" --android

Can try a code which has already been out of gifts with:
npx uri-scheme open "bitkit://gift-phil30102025-3000" --android

Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftErrorSheet.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated

@jvsena42jvsena42 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.

Just missing some open comments

OBS: On the android repo, the business logic is centered in the repositories.
The methods are exposed to the ViewModels with the return wrapped in a Result class

Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Fixed
Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Fixed

@jvsena42jvsena42 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.

Worked on tests ✅
Found one last issue

Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
jvsena42
jvsena42 previously approved these changes Nov 21, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
ovitrif
ovitrif previously approved these changes Nov 24, 2025

@ovitrifovitrif 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.

ACK & tested with all gift codes combinations.

pushed a few small edits.

Only having one uncertainty about the use of:

 lightningRepo.executeWhenNodeRunning(
operationName ="claimGiftCode",
waitTimeout = waitTimeout,
) {
Result.success(Unit)
}.getOrThrow()

If I understand correctly, we could change the body of BlocktankRepo.claimGiftCode so that everything that comes after this block could actually be passed inside the block.

Does it make sense to you?!

My understanding is that the purpose of the empty call with a simple Result success is because we want to wait for the node to init if not ready.

Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Outdated
@claude

claudeBot commented Nov 24, 2025

Copy link
Copy Markdown
Contributor

Pull Request Review: Gift Codes Support

Thank you for this comprehensive implementation of gift codes functionality! Overall, the code is well-structured and follows good practices. Below are my findings and suggestions:

✅ Strengths

  1. Clean Architecture: Good separation of concerns between UI (GiftSheet, GiftViewModel), repository (BlocktankRepo), and business logic layers
  2. Error Handling: Proper use of Result types and comprehensive error classification (Used, UsedUp, Error)
  3. UI/UX: Well-designed loading states and error screens with clear user feedback
  4. Code Reusability: Good refactoring of calculateRemoteBalance into a reusable extension function
  5. Type Safety: Good use of sealed classes for GiftRoute and GiftClaimResult

🐛 Potential Bugs & Issues

Critical

  1. Race Condition in GiftViewModel.initialize() (GiftViewModel.kt:47-58)

    • The isClaiming flag is checked before emitting Loading, but the flag is set inside claimGift()
    • If initialize() is called twice rapidly, both calls could pass the check and launch claimGift() concurrently
    • Recommendation: Set isClaiming = true before launching the coroutine, or use a mutex/atomic operation
  2. Missing Null Safety (BlocktankRepo.kt:465)

    • openedOrder.channel?.fundingTx?.id ?: orderId falls back to orderId, but the activity record expects a valid payment hash or transaction ID
    • If the channel opening fails but doesn't throw an exception, this could create invalid activity records
    • Recommendation: Add validation or throw an exception if fundingTx.id is null
  3. No Result Handling for giftPay() (BlocktankRepo.kt:446-448)

    • The giftPay() call doesn't await or check the result, so payment failures go undetected
    • Users might see a success message even if the payment failed
    • Recommendation: Await the result and handle potential failures appropriately

Moderate

  1. Fixed Delay May Be Insufficient (BlocktankRepo.kt:423)

    • The 2-second PEER_CONNECTION_DELAY_MS is a fixed delay that may not be reliable across all network conditions
    • Recommendation: Consider implementing a retry mechanism or checking peer connection status instead of a fixed delay
  2. Double LaunchedEffect Collection (GiftSheet.kt:39-43, 45-57)

    • Two separate LaunchedEffect(Unit) blocks collecting from different flows could lead to ordering issues
    • Recommendation: Combine into a single LaunchedEffect or use remember { } to ensure proper initialization order
  3. Missing Validation (BlocktankRepo.kt:416-417)

    • Gift code format isn't validated (e.g., does it need to match a pattern?)
    • Amount isn't checked against minimum/maximum thresholds
    • Recommendation: Add format validation and reasonable bounds checking

🔒 Security Concerns

  1. Gift Code in Activity Message (GiftViewModel.kt:108)

    • The gift code is stored in plaintext in the activity message field
    • If gift codes are meant to be secret or single-use, this could be a privacy concern
    • Recommendation: Consider whether gift codes need to be stored, or if they should be hashed/obfuscated
  2. No Rate Limiting

    • The implementation doesn't appear to have rate limiting on gift code attempts
    • An attacker could potentially brute-force gift codes
    • Recommendation: Consider implementing client-side rate limiting or exponential backoff on failures
  3. Error Message Information Disclosure (GiftViewModel.kt:133)

    • The error checking uses case-insensitive matching on full error messages, which could match unintended patterns
    • Recommendation: Use more specific error codes or structured error types instead of string matching

⚡ Performance Considerations

  1. Blocking UI Thread (GiftSheet.kt:30-31)

    • LaunchedEffect(sheet.code, sheet.amount) will re-initialize if these values change
    • Could cause multiple simultaneous claim attempts
    • Recommendation: Add a check to prevent re-initialization if already claiming
  2. StateFlow Buffer Size (GiftViewModel.kt:34, 37)

    • extraBufferCapacity = 1 means events can be dropped if not collected immediately
    • With sheet transitions, this could cause missed navigation events
    • Recommendation: Consider using replay = 1 or ensuring collection starts before events are emitted

🧪 Test Coverage

  1. No Tests for New Functionality

    • BlocktankRepoTest.kt only has 2 lines added (dependency injection)
    • No tests for:
      • claimGiftCode()
      • claimGiftCodeWithLiquidity()
      • claimGiftCodeWithoutLiquidity()
      • GiftViewModel logic
      • UI navigation flows
    • Recommendation: Add comprehensive unit tests for the new repository methods and ViewModel logic
  2. Integration Test Coverage

    • The deep link handling (bitkit://gift-...) isn't covered by tests
    • Recommendation: Add integration tests for the gift code URI scheme handling

📝 Code Quality Suggestions

  1. Magic Numbers (GiftViewModel.kt:144)

    • NODE_STARTUP_TIMEOUT_MS = 30_000L is defined but differs from TIMEOUT_GIFT_CODE = 30.seconds in BlocktankRepo
    • Recommendation: Consolidate timeout constants or document why they differ
  2. Inconsistent Error Handling Pattern (GiftViewModel.kt:115)

    • insertGiftActivity() uses .getOrThrow() which will crash if it fails, but claimGiftCode() returns a Result
    • Recommendation: Handle the Result properly or document why crashing is acceptable here
  3. Missing Documentation

    • The new public API methods lack KDoc comments explaining parameters, return values, and potential exceptions
    • Recommendation: Add documentation for claimGiftCode() and GiftClaimResult
  4. Suppress Annotation Justification (BlocktankRepo.kt:53)

    • The @Suppress("LongParameterList") annotation was added but this is a known issue with DI
    • Recommendation: This is acceptable for constructor injection, but consider if any parameters could be grouped into a configuration object

🎯 Minor Improvements

  1. String Concatenation (GiftViewModel.kt:133)

    • currentError.toString() + (currentError.message ?: "") could include the message twice
    • Recommendation: Use currentError.message ?: currentError.toString() instead
  2. Unused Import (BlocktankRepo.kt:489-490)

    • Consider converting SuccessWithLiquidity to a data object for Kotlin 1.9+ consistency
    • Currently mixing object and data class in the same sealed class hierarchy
  3. Constant Naming (GiftRoute.kt:4)

    • IMAGE_WIDTH_FRACTION is defined at file level but only used in UI files
    • Recommendation: Move to a shared UI constants file or make it private to the UI components

✨ Overall Assessment

This is a solid implementation with good architectural decisions. The main concerns are:

  • Race conditions in initialization (high priority)
  • Missing error handling for payment execution (high priority)
  • Lack of test coverage (high priority)
  • Some security considerations around rate limiting and data storage (medium priority)

The code follows Kotlin best practices and integrates well with the existing codebase. Once the critical issues are addressed, this will be a robust feature.


Review completed by Claude Code

@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@jvsena42
jvsena42 merged commit b68efb6 into masterNov 24, 2025
18 checks passed
@jvsena42
jvsena42 deleted the feat/gift-codes branch November 24, 2025 19:34
@jvsena42jvsena42 mentioned this pull request Jan 26, 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.

4 participants

@ben-kaufman@ovitrif@jvsena42@github-advanced-security
, '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

Add gift codes support - #473

Merged
jvsena42 merged 11 commits into
masterfrom
feat/gift-codes
Nov 24, 2025
Merged

Add gift codes support#473
jvsena42 merged 11 commits into
masterfrom
feat/gift-codes

Conversation

@ben-kaufman

@ben-kaufmanben-kaufman commented Nov 20, 2025

Copy link
Copy Markdown
Contributor

Added gift codes support.

In the demo:

  1. Redeem valid code, no open channels
  2. Try redeem the same code and fail
  3. Redeem a new code when already having a channel
  4. Try redeeming a code which doesn't exist
Screen.Recording.2025-11-19.at.9.42.46.PM.mov

Commands to test with:
npx uri-scheme open "bitkit://gift-ben19112025-3000" --android
npx uri-scheme open "bitkit://gift-notben19112025-3000" --android

Even when the app is closed, running one of the commands should make the app open and claim.
Can try a non existent code with:
npx uri-scheme open "bitkit://gift-x-3000" --android

Can try a code which has already been out of gifts with:
npx uri-scheme open "bitkit://gift-phil30102025-3000" --android

Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftErrorSheet.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated

@jvsena42jvsena42 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.

Just missing some open comments

OBS: On the android repo, the business logic is centered in the repositories.
The methods are exposed to the ViewModels with the return wrapped in a Result class

Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Fixed
Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Fixed

@jvsena42jvsena42 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.

Worked on tests ✅
Found one last issue

Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
jvsena42
jvsena42 previously approved these changes Nov 21, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
ovitrif
ovitrif previously approved these changes Nov 24, 2025

@ovitrifovitrif 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.

ACK & tested with all gift codes combinations.

pushed a few small edits.

Only having one uncertainty about the use of:

 lightningRepo.executeWhenNodeRunning(
operationName ="claimGiftCode",
waitTimeout = waitTimeout,
) {
Result.success(Unit)
}.getOrThrow()

If I understand correctly, we could change the body of BlocktankRepo.claimGiftCode so that everything that comes after this block could actually be passed inside the block.

Does it make sense to you?!

My understanding is that the purpose of the empty call with a simple Result success is because we want to wait for the node to init if not ready.

Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Outdated
@claude

claudeBot commented Nov 24, 2025

Copy link
Copy Markdown
Contributor

Pull Request Review: Gift Codes Support

Thank you for this comprehensive implementation of gift codes functionality! Overall, the code is well-structured and follows good practices. Below are my findings and suggestions:

✅ Strengths

  1. Clean Architecture: Good separation of concerns between UI (GiftSheet, GiftViewModel), repository (BlocktankRepo), and business logic layers
  2. Error Handling: Proper use of Result types and comprehensive error classification (Used, UsedUp, Error)
  3. UI/UX: Well-designed loading states and error screens with clear user feedback
  4. Code Reusability: Good refactoring of calculateRemoteBalance into a reusable extension function
  5. Type Safety: Good use of sealed classes for GiftRoute and GiftClaimResult

🐛 Potential Bugs & Issues

Critical

  1. Race Condition in GiftViewModel.initialize() (GiftViewModel.kt:47-58)

    • The isClaiming flag is checked before emitting Loading, but the flag is set inside claimGift()
    • If initialize() is called twice rapidly, both calls could pass the check and launch claimGift() concurrently
    • Recommendation: Set isClaiming = true before launching the coroutine, or use a mutex/atomic operation
  2. Missing Null Safety (BlocktankRepo.kt:465)

    • openedOrder.channel?.fundingTx?.id ?: orderId falls back to orderId, but the activity record expects a valid payment hash or transaction ID
    • If the channel opening fails but doesn't throw an exception, this could create invalid activity records
    • Recommendation: Add validation or throw an exception if fundingTx.id is null
  3. No Result Handling for giftPay() (BlocktankRepo.kt:446-448)

    • The giftPay() call doesn't await or check the result, so payment failures go undetected
    • Users might see a success message even if the payment failed
    • Recommendation: Await the result and handle potential failures appropriately

Moderate

  1. Fixed Delay May Be Insufficient (BlocktankRepo.kt:423)

    • The 2-second PEER_CONNECTION_DELAY_MS is a fixed delay that may not be reliable across all network conditions
    • Recommendation: Consider implementing a retry mechanism or checking peer connection status instead of a fixed delay
  2. Double LaunchedEffect Collection (GiftSheet.kt:39-43, 45-57)

    • Two separate LaunchedEffect(Unit) blocks collecting from different flows could lead to ordering issues
    • Recommendation: Combine into a single LaunchedEffect or use remember { } to ensure proper initialization order
  3. Missing Validation (BlocktankRepo.kt:416-417)

    • Gift code format isn't validated (e.g., does it need to match a pattern?)
    • Amount isn't checked against minimum/maximum thresholds
    • Recommendation: Add format validation and reasonable bounds checking

🔒 Security Concerns

  1. Gift Code in Activity Message (GiftViewModel.kt:108)

    • The gift code is stored in plaintext in the activity message field
    • If gift codes are meant to be secret or single-use, this could be a privacy concern
    • Recommendation: Consider whether gift codes need to be stored, or if they should be hashed/obfuscated
  2. No Rate Limiting

    • The implementation doesn't appear to have rate limiting on gift code attempts
    • An attacker could potentially brute-force gift codes
    • Recommendation: Consider implementing client-side rate limiting or exponential backoff on failures
  3. Error Message Information Disclosure (GiftViewModel.kt:133)

    • The error checking uses case-insensitive matching on full error messages, which could match unintended patterns
    • Recommendation: Use more specific error codes or structured error types instead of string matching

⚡ Performance Considerations

  1. Blocking UI Thread (GiftSheet.kt:30-31)

    • LaunchedEffect(sheet.code, sheet.amount) will re-initialize if these values change
    • Could cause multiple simultaneous claim attempts
    • Recommendation: Add a check to prevent re-initialization if already claiming
  2. StateFlow Buffer Size (GiftViewModel.kt:34, 37)

    • extraBufferCapacity = 1 means events can be dropped if not collected immediately
    • With sheet transitions, this could cause missed navigation events
    • Recommendation: Consider using replay = 1 or ensuring collection starts before events are emitted

🧪 Test Coverage

  1. No Tests for New Functionality

    • BlocktankRepoTest.kt only has 2 lines added (dependency injection)
    • No tests for:
      • claimGiftCode()
      • claimGiftCodeWithLiquidity()
      • claimGiftCodeWithoutLiquidity()
      • GiftViewModel logic
      • UI navigation flows
    • Recommendation: Add comprehensive unit tests for the new repository methods and ViewModel logic
  2. Integration Test Coverage

    • The deep link handling (bitkit://gift-...) isn't covered by tests
    • Recommendation: Add integration tests for the gift code URI scheme handling

📝 Code Quality Suggestions

  1. Magic Numbers (GiftViewModel.kt:144)

    • NODE_STARTUP_TIMEOUT_MS = 30_000L is defined but differs from TIMEOUT_GIFT_CODE = 30.seconds in BlocktankRepo
    • Recommendation: Consolidate timeout constants or document why they differ
  2. Inconsistent Error Handling Pattern (GiftViewModel.kt:115)

    • insertGiftActivity() uses .getOrThrow() which will crash if it fails, but claimGiftCode() returns a Result
    • Recommendation: Handle the Result properly or document why crashing is acceptable here
  3. Missing Documentation

    • The new public API methods lack KDoc comments explaining parameters, return values, and potential exceptions
    • Recommendation: Add documentation for claimGiftCode() and GiftClaimResult
  4. Suppress Annotation Justification (BlocktankRepo.kt:53)

    • The @Suppress("LongParameterList") annotation was added but this is a known issue with DI
    • Recommendation: This is acceptable for constructor injection, but consider if any parameters could be grouped into a configuration object

🎯 Minor Improvements

  1. String Concatenation (GiftViewModel.kt:133)

    • currentError.toString() + (currentError.message ?: "") could include the message twice
    • Recommendation: Use currentError.message ?: currentError.toString() instead
  2. Unused Import (BlocktankRepo.kt:489-490)

    • Consider converting SuccessWithLiquidity to a data object for Kotlin 1.9+ consistency
    • Currently mixing object and data class in the same sealed class hierarchy
  3. Constant Naming (GiftRoute.kt:4)

    • IMAGE_WIDTH_FRACTION is defined at file level but only used in UI files
    • Recommendation: Move to a shared UI constants file or make it private to the UI components

✨ Overall Assessment

This is a solid implementation with good architectural decisions. The main concerns are:

  • Race conditions in initialization (high priority)
  • Missing error handling for payment execution (high priority)
  • Lack of test coverage (high priority)
  • Some security considerations around rate limiting and data storage (medium priority)

The code follows Kotlin best practices and integrates well with the existing codebase. Once the critical issues are addressed, this will be a robust feature.


Review completed by Claude Code

@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@jvsena42
jvsena42 merged commit b68efb6 into masterNov 24, 2025
18 checks passed
@jvsena42
jvsena42 deleted the feat/gift-codes branch November 24, 2025 19:34
@jvsena42jvsena42 mentioned this pull request Jan 26, 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.

4 participants

@ben-kaufman@ovitrif@jvsena42@github-advanced-security
, '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

Add gift codes support - #473

Merged
jvsena42 merged 11 commits into
masterfrom
feat/gift-codes
Nov 24, 2025
Merged

Add gift codes support#473
jvsena42 merged 11 commits into
masterfrom
feat/gift-codes

Conversation

@ben-kaufman

@ben-kaufmanben-kaufman commented Nov 20, 2025

Copy link
Copy Markdown
Contributor

Added gift codes support.

In the demo:

  1. Redeem valid code, no open channels
  2. Try redeem the same code and fail
  3. Redeem a new code when already having a channel
  4. Try redeeming a code which doesn't exist
Screen.Recording.2025-11-19.at.9.42.46.PM.mov

Commands to test with:
npx uri-scheme open "bitkit://gift-ben19112025-3000" --android
npx uri-scheme open "bitkit://gift-notben19112025-3000" --android

Even when the app is closed, running one of the commands should make the app open and claim.
Can try a non existent code with:
npx uri-scheme open "bitkit://gift-x-3000" --android

Can try a code which has already been out of gifts with:
npx uri-scheme open "bitkit://gift-phil30102025-3000" --android

Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftErrorSheet.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated

@jvsena42jvsena42 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.

Just missing some open comments

OBS: On the android repo, the business logic is centered in the repositories.
The methods are exposed to the ViewModels with the return wrapped in a Result class

Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Fixed
Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Fixed

@jvsena42jvsena42 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.

Worked on tests ✅
Found one last issue

Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
jvsena42
jvsena42 previously approved these changes Nov 21, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
ovitrif
ovitrif previously approved these changes Nov 24, 2025

@ovitrifovitrif 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.

ACK & tested with all gift codes combinations.

pushed a few small edits.

Only having one uncertainty about the use of:

 lightningRepo.executeWhenNodeRunning(
operationName ="claimGiftCode",
waitTimeout = waitTimeout,
) {
Result.success(Unit)
}.getOrThrow()

If I understand correctly, we could change the body of BlocktankRepo.claimGiftCode so that everything that comes after this block could actually be passed inside the block.

Does it make sense to you?!

My understanding is that the purpose of the empty call with a simple Result success is because we want to wait for the node to init if not ready.

Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Outdated
@claude

claudeBot commented Nov 24, 2025

Copy link
Copy Markdown
Contributor

Pull Request Review: Gift Codes Support

Thank you for this comprehensive implementation of gift codes functionality! Overall, the code is well-structured and follows good practices. Below are my findings and suggestions:

✅ Strengths

  1. Clean Architecture: Good separation of concerns between UI (GiftSheet, GiftViewModel), repository (BlocktankRepo), and business logic layers
  2. Error Handling: Proper use of Result types and comprehensive error classification (Used, UsedUp, Error)
  3. UI/UX: Well-designed loading states and error screens with clear user feedback
  4. Code Reusability: Good refactoring of calculateRemoteBalance into a reusable extension function
  5. Type Safety: Good use of sealed classes for GiftRoute and GiftClaimResult

🐛 Potential Bugs & Issues

Critical

  1. Race Condition in GiftViewModel.initialize() (GiftViewModel.kt:47-58)

    • The isClaiming flag is checked before emitting Loading, but the flag is set inside claimGift()
    • If initialize() is called twice rapidly, both calls could pass the check and launch claimGift() concurrently
    • Recommendation: Set isClaiming = true before launching the coroutine, or use a mutex/atomic operation
  2. Missing Null Safety (BlocktankRepo.kt:465)

    • openedOrder.channel?.fundingTx?.id ?: orderId falls back to orderId, but the activity record expects a valid payment hash or transaction ID
    • If the channel opening fails but doesn't throw an exception, this could create invalid activity records
    • Recommendation: Add validation or throw an exception if fundingTx.id is null
  3. No Result Handling for giftPay() (BlocktankRepo.kt:446-448)

    • The giftPay() call doesn't await or check the result, so payment failures go undetected
    • Users might see a success message even if the payment failed
    • Recommendation: Await the result and handle potential failures appropriately

Moderate

  1. Fixed Delay May Be Insufficient (BlocktankRepo.kt:423)

    • The 2-second PEER_CONNECTION_DELAY_MS is a fixed delay that may not be reliable across all network conditions
    • Recommendation: Consider implementing a retry mechanism or checking peer connection status instead of a fixed delay
  2. Double LaunchedEffect Collection (GiftSheet.kt:39-43, 45-57)

    • Two separate LaunchedEffect(Unit) blocks collecting from different flows could lead to ordering issues
    • Recommendation: Combine into a single LaunchedEffect or use remember { } to ensure proper initialization order
  3. Missing Validation (BlocktankRepo.kt:416-417)

    • Gift code format isn't validated (e.g., does it need to match a pattern?)
    • Amount isn't checked against minimum/maximum thresholds
    • Recommendation: Add format validation and reasonable bounds checking

🔒 Security Concerns

  1. Gift Code in Activity Message (GiftViewModel.kt:108)

    • The gift code is stored in plaintext in the activity message field
    • If gift codes are meant to be secret or single-use, this could be a privacy concern
    • Recommendation: Consider whether gift codes need to be stored, or if they should be hashed/obfuscated
  2. No Rate Limiting

    • The implementation doesn't appear to have rate limiting on gift code attempts
    • An attacker could potentially brute-force gift codes
    • Recommendation: Consider implementing client-side rate limiting or exponential backoff on failures
  3. Error Message Information Disclosure (GiftViewModel.kt:133)

    • The error checking uses case-insensitive matching on full error messages, which could match unintended patterns
    • Recommendation: Use more specific error codes or structured error types instead of string matching

⚡ Performance Considerations

  1. Blocking UI Thread (GiftSheet.kt:30-31)

    • LaunchedEffect(sheet.code, sheet.amount) will re-initialize if these values change
    • Could cause multiple simultaneous claim attempts
    • Recommendation: Add a check to prevent re-initialization if already claiming
  2. StateFlow Buffer Size (GiftViewModel.kt:34, 37)

    • extraBufferCapacity = 1 means events can be dropped if not collected immediately
    • With sheet transitions, this could cause missed navigation events
    • Recommendation: Consider using replay = 1 or ensuring collection starts before events are emitted

🧪 Test Coverage

  1. No Tests for New Functionality

    • BlocktankRepoTest.kt only has 2 lines added (dependency injection)
    • No tests for:
      • claimGiftCode()
      • claimGiftCodeWithLiquidity()
      • claimGiftCodeWithoutLiquidity()
      • GiftViewModel logic
      • UI navigation flows
    • Recommendation: Add comprehensive unit tests for the new repository methods and ViewModel logic
  2. Integration Test Coverage

    • The deep link handling (bitkit://gift-...) isn't covered by tests
    • Recommendation: Add integration tests for the gift code URI scheme handling

📝 Code Quality Suggestions

  1. Magic Numbers (GiftViewModel.kt:144)

    • NODE_STARTUP_TIMEOUT_MS = 30_000L is defined but differs from TIMEOUT_GIFT_CODE = 30.seconds in BlocktankRepo
    • Recommendation: Consolidate timeout constants or document why they differ
  2. Inconsistent Error Handling Pattern (GiftViewModel.kt:115)

    • insertGiftActivity() uses .getOrThrow() which will crash if it fails, but claimGiftCode() returns a Result
    • Recommendation: Handle the Result properly or document why crashing is acceptable here
  3. Missing Documentation

    • The new public API methods lack KDoc comments explaining parameters, return values, and potential exceptions
    • Recommendation: Add documentation for claimGiftCode() and GiftClaimResult
  4. Suppress Annotation Justification (BlocktankRepo.kt:53)

    • The @Suppress("LongParameterList") annotation was added but this is a known issue with DI
    • Recommendation: This is acceptable for constructor injection, but consider if any parameters could be grouped into a configuration object

🎯 Minor Improvements

  1. String Concatenation (GiftViewModel.kt:133)

    • currentError.toString() + (currentError.message ?: "") could include the message twice
    • Recommendation: Use currentError.message ?: currentError.toString() instead
  2. Unused Import (BlocktankRepo.kt:489-490)

    • Consider converting SuccessWithLiquidity to a data object for Kotlin 1.9+ consistency
    • Currently mixing object and data class in the same sealed class hierarchy
  3. Constant Naming (GiftRoute.kt:4)

    • IMAGE_WIDTH_FRACTION is defined at file level but only used in UI files
    • Recommendation: Move to a shared UI constants file or make it private to the UI components

✨ Overall Assessment

This is a solid implementation with good architectural decisions. The main concerns are:

  • Race conditions in initialization (high priority)
  • Missing error handling for payment execution (high priority)
  • Lack of test coverage (high priority)
  • Some security considerations around rate limiting and data storage (medium priority)

The code follows Kotlin best practices and integrates well with the existing codebase. Once the critical issues are addressed, this will be a robust feature.


Review completed by Claude Code

@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@jvsena42
jvsena42 merged commit b68efb6 into masterNov 24, 2025
18 checks passed
@jvsena42
jvsena42 deleted the feat/gift-codes branch November 24, 2025 19:34
@jvsena42jvsena42 mentioned this pull request Jan 26, 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.

4 participants

@ben-kaufman@ovitrif@jvsena42@github-advanced-security
, '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

Add gift codes support - #473

Merged
jvsena42 merged 11 commits into
masterfrom
feat/gift-codes
Nov 24, 2025
Merged

Add gift codes support#473
jvsena42 merged 11 commits into
masterfrom
feat/gift-codes

Conversation

@ben-kaufman

@ben-kaufmanben-kaufman commented Nov 20, 2025

Copy link
Copy Markdown
Contributor

Added gift codes support.

In the demo:

  1. Redeem valid code, no open channels
  2. Try redeem the same code and fail
  3. Redeem a new code when already having a channel
  4. Try redeeming a code which doesn't exist
Screen.Recording.2025-11-19.at.9.42.46.PM.mov

Commands to test with:
npx uri-scheme open "bitkit://gift-ben19112025-3000" --android
npx uri-scheme open "bitkit://gift-notben19112025-3000" --android

Even when the app is closed, running one of the commands should make the app open and claim.
Can try a non existent code with:
npx uri-scheme open "bitkit://gift-x-3000" --android

Can try a code which has already been out of gifts with:
npx uri-scheme open "bitkit://gift-phil30102025-3000" --android

Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftErrorSheet.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated

@jvsena42jvsena42 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.

Just missing some open comments

OBS: On the android repo, the business logic is centered in the repositories.
The methods are exposed to the ViewModels with the return wrapped in a Result class

Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Fixed
Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Fixed

@jvsena42jvsena42 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.

Worked on tests ✅
Found one last issue

Comment threadapp/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt Outdated
jvsena42
jvsena42 previously approved these changes Nov 21, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
ovitrif
ovitrif previously approved these changes Nov 24, 2025

@ovitrifovitrif 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.

ACK & tested with all gift codes combinations.

pushed a few small edits.

Only having one uncertainty about the use of:

 lightningRepo.executeWhenNodeRunning(
operationName ="claimGiftCode",
waitTimeout = waitTimeout,
) {
Result.success(Unit)
}.getOrThrow()

If I understand correctly, we could change the body of BlocktankRepo.claimGiftCode so that everything that comes after this block could actually be passed inside the block.

Does it make sense to you?!

My understanding is that the purpose of the empty call with a simple Result success is because we want to wait for the node to init if not ready.

Comment threadapp/src/main/java/to/bitkit/repositories/BlocktankRepo.kt Outdated
@claude

claudeBot commented Nov 24, 2025

Copy link
Copy Markdown
Contributor

Pull Request Review: Gift Codes Support

Thank you for this comprehensive implementation of gift codes functionality! Overall, the code is well-structured and follows good practices. Below are my findings and suggestions:

✅ Strengths

  1. Clean Architecture: Good separation of concerns between UI (GiftSheet, GiftViewModel), repository (BlocktankRepo), and business logic layers
  2. Error Handling: Proper use of Result types and comprehensive error classification (Used, UsedUp, Error)
  3. UI/UX: Well-designed loading states and error screens with clear user feedback
  4. Code Reusability: Good refactoring of calculateRemoteBalance into a reusable extension function
  5. Type Safety: Good use of sealed classes for GiftRoute and GiftClaimResult

🐛 Potential Bugs & Issues

Critical

  1. Race Condition in GiftViewModel.initialize() (GiftViewModel.kt:47-58)

    • The isClaiming flag is checked before emitting Loading, but the flag is set inside claimGift()
    • If initialize() is called twice rapidly, both calls could pass the check and launch claimGift() concurrently
    • Recommendation: Set isClaiming = true before launching the coroutine, or use a mutex/atomic operation
  2. Missing Null Safety (BlocktankRepo.kt:465)

    • openedOrder.channel?.fundingTx?.id ?: orderId falls back to orderId, but the activity record expects a valid payment hash or transaction ID
    • If the channel opening fails but doesn't throw an exception, this could create invalid activity records
    • Recommendation: Add validation or throw an exception if fundingTx.id is null
  3. No Result Handling for giftPay() (BlocktankRepo.kt:446-448)

    • The giftPay() call doesn't await or check the result, so payment failures go undetected
    • Users might see a success message even if the payment failed
    • Recommendation: Await the result and handle potential failures appropriately

Moderate

  1. Fixed Delay May Be Insufficient (BlocktankRepo.kt:423)

    • The 2-second PEER_CONNECTION_DELAY_MS is a fixed delay that may not be reliable across all network conditions
    • Recommendation: Consider implementing a retry mechanism or checking peer connection status instead of a fixed delay
  2. Double LaunchedEffect Collection (GiftSheet.kt:39-43, 45-57)

    • Two separate LaunchedEffect(Unit) blocks collecting from different flows could lead to ordering issues
    • Recommendation: Combine into a single LaunchedEffect or use remember { } to ensure proper initialization order
  3. Missing Validation (BlocktankRepo.kt:416-417)

    • Gift code format isn't validated (e.g., does it need to match a pattern?)
    • Amount isn't checked against minimum/maximum thresholds
    • Recommendation: Add format validation and reasonable bounds checking

🔒 Security Concerns

  1. Gift Code in Activity Message (GiftViewModel.kt:108)

    • The gift code is stored in plaintext in the activity message field
    • If gift codes are meant to be secret or single-use, this could be a privacy concern
    • Recommendation: Consider whether gift codes need to be stored, or if they should be hashed/obfuscated
  2. No Rate Limiting

    • The implementation doesn't appear to have rate limiting on gift code attempts
    • An attacker could potentially brute-force gift codes
    • Recommendation: Consider implementing client-side rate limiting or exponential backoff on failures
  3. Error Message Information Disclosure (GiftViewModel.kt:133)

    • The error checking uses case-insensitive matching on full error messages, which could match unintended patterns
    • Recommendation: Use more specific error codes or structured error types instead of string matching

⚡ Performance Considerations

  1. Blocking UI Thread (GiftSheet.kt:30-31)

    • LaunchedEffect(sheet.code, sheet.amount) will re-initialize if these values change
    • Could cause multiple simultaneous claim attempts
    • Recommendation: Add a check to prevent re-initialization if already claiming
  2. StateFlow Buffer Size (GiftViewModel.kt:34, 37)

    • extraBufferCapacity = 1 means events can be dropped if not collected immediately
    • With sheet transitions, this could cause missed navigation events
    • Recommendation: Consider using replay = 1 or ensuring collection starts before events are emitted

🧪 Test Coverage

  1. No Tests for New Functionality

    • BlocktankRepoTest.kt only has 2 lines added (dependency injection)
    • No tests for:
      • claimGiftCode()
      • claimGiftCodeWithLiquidity()
      • claimGiftCodeWithoutLiquidity()
      • GiftViewModel logic
      • UI navigation flows
    • Recommendation: Add comprehensive unit tests for the new repository methods and ViewModel logic
  2. Integration Test Coverage

    • The deep link handling (bitkit://gift-...) isn't covered by tests
    • Recommendation: Add integration tests for the gift code URI scheme handling

📝 Code Quality Suggestions

  1. Magic Numbers (GiftViewModel.kt:144)

    • NODE_STARTUP_TIMEOUT_MS = 30_000L is defined but differs from TIMEOUT_GIFT_CODE = 30.seconds in BlocktankRepo
    • Recommendation: Consolidate timeout constants or document why they differ
  2. Inconsistent Error Handling Pattern (GiftViewModel.kt:115)

    • insertGiftActivity() uses .getOrThrow() which will crash if it fails, but claimGiftCode() returns a Result
    • Recommendation: Handle the Result properly or document why crashing is acceptable here
  3. Missing Documentation

    • The new public API methods lack KDoc comments explaining parameters, return values, and potential exceptions
    • Recommendation: Add documentation for claimGiftCode() and GiftClaimResult
  4. Suppress Annotation Justification (BlocktankRepo.kt:53)

    • The @Suppress("LongParameterList") annotation was added but this is a known issue with DI
    • Recommendation: This is acceptable for constructor injection, but consider if any parameters could be grouped into a configuration object

🎯 Minor Improvements

  1. String Concatenation (GiftViewModel.kt:133)

    • currentError.toString() + (currentError.message ?: "") could include the message twice
    • Recommendation: Use currentError.message ?: currentError.toString() instead
  2. Unused Import (BlocktankRepo.kt:489-490)

    • Consider converting SuccessWithLiquidity to a data object for Kotlin 1.9+ consistency
    • Currently mixing object and data class in the same sealed class hierarchy
  3. Constant Naming (GiftRoute.kt:4)

    • IMAGE_WIDTH_FRACTION is defined at file level but only used in UI files
    • Recommendation: Move to a shared UI constants file or make it private to the UI components

✨ Overall Assessment

This is a solid implementation with good architectural decisions. The main concerns are:

  • Race conditions in initialization (high priority)
  • Missing error handling for payment execution (high priority)
  • Lack of test coverage (high priority)
  • Some security considerations around rate limiting and data storage (medium priority)

The code follows Kotlin best practices and integrates well with the existing codebase. Once the critical issues are addressed, this will be a robust feature.


Review completed by Claude Code

@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@jvsena42
jvsena42 merged commit b68efb6 into masterNov 24, 2025
18 checks passed
@jvsena42
jvsena42 deleted the feat/gift-codes branch November 24, 2025 19:34
@jvsena42jvsena42 mentioned this pull request Jan 26, 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.

4 participants

@ben-kaufman@ovitrif@jvsena42@github-advanced-security