Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
23ba5b0
Release v2.1.6: Balance card UI, smart send button, and co-signing fixes
Dec 31, 2025
6d286ef
chore: Update v2.1.6 release date in changelog
Dec 31, 2025
3a94b54
docker+builder+optimizations
Dec 31, 2025
a32ac57
docker+builder+optimizations
Jan 1, 2026
5215726
docker+builder+optimizations
Jan 1, 2026
0f3c175
docker+builder+optimizations
Jan 2, 2026
59e1356
docker+builder+optimizations
Jan 2, 2026
0aa4f3d
docker+builder+optimizations
Jan 2, 2026
9ce387b
docker+builder+optimizations
Jan 2, 2026
9d84cb2
docker+builder+optimizations
Jan 2, 2026
13586bd
docker+builder+optimizations
Jan 2, 2026
31da6a7
docker+builder+optimizations
Jan 2, 2026
3e63a13
docker-builder-optimizations
Jan 2, 2026
519bdbe
docker-builder-optimizations
Jan 2, 2026
1929e4e
docker-builder-optimizations
Jan 2, 2026
56d6f99
docker-builder-optimizations
Jan 2, 2026
c3c7ada
docker-builder-optimizations
Jan 2, 2026
02953c3
docker-builder-optimizations
Jan 2, 2026
558e963
docker-cross-optimizations
Jan 2, 2026
c4bba91
docker-builder-optimizations
Jan 2, 2026
4fce148
docker-builder-optimizations
Jan 3, 2026
9b498a7
docker-builder-optimizations
Jan 4, 2026
e4b4234
docker-builder-optimizations
Jan 4, 2026
66252b7
docker-builder-optimizations
Jan 4, 2026
62ea82b
docker-builder-optimizations
Jan 4, 2026
4eea86e
docker-optimizations+ui-optimizations+watch-only-alignments
Jan 5, 2026
697437e
checkpoint
Jan 5, 2026
da9b646
checkpoint-ui
Jan 5, 2026
4b512fb
feat: Add multiple address display with amounts in transaction details
Jan 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Build outputs
android/app/build/
android/build/
android/.gradle/
android/app/release/
ios/build/
ios/Pods/
ios/*.xcworkspace/xcuserdata/
ios/*.xcodeproj/xcuserdata/

# IDE
.vscode/
.idea/
*.swp
*.swo
*~

# OS
.DS_Store
Thumbs.db

# Git
.git/
.gitignore
.gitattributes

# Docker
Dockerfile
.dockerignore
docker-apk-builder.sh

# Documentation
*.md
!BBMTLib/README.md
!BBMTLib/RECOVER.md
!BBMTLib/NOSTR_MESSAGE_ENCRYPTION_FLOW.md

# Test files
__tests__/
*.test.tsx
*.test.ts
*.spec.ts
*.spec.tsx

# CI/CD
.github/
.gitlab-ci.yml
.travis.yml

# Misc
.env
.env.local
*.log
build.log

# Docker inline cache directory (should not be in build context)
.docker-cache/

# Already built artifacts
*.apk
*.aab
*.aar
*.jar
*.xcframework
BBMTLib/tss.aar
BBMTLib/tss-sources.jar
BBMTLib/Tss.xcframework/

# Fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/

# Release artifacts
android/app-release.apk.sha256
android/app-release.apk.sha256.asc
android/mapping.txt

6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -382,4 +382,8 @@ PR_SUMMARY.md
PR_README.md

# third_party
third_party/
third_party/

# Docker cache
.docker-cache/
build.log
86 changes: 70 additions & 16 deletions BBMTLib/tss/nostrtransport/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"os"
"runtime/debug"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -285,14 +286,18 @@ func (c *Client) Publish(ctx context.Context, event *Event) error {

fmt.Fprintf(os.Stderr, "BBMTLog: Client.Publish - signed event, PubKey (hex)=%s, tags=%v\n", event.PubKey, event.Tags)

// Use all valid relays, not just initially connected ones
// Resiliency: Use ALL valid relays (not just initially connected ones)
// The pool will handle connections - if a relay isn't connected yet, it will try to connect
// This ensures we publish to all relays, including those that connected in background
// This is critical for resiliency across multiple relays
relaysToUse := c.validRelays
if len(relaysToUse) == 0 {
// Fallback to urls if validRelays not set (backward compatibility)
relaysToUse = c.urls
}
if len(relaysToUse) == 0 {
return errors.New("no relays configured for publishing")
}
results := c.pool.PublishMany(ctx, relaysToUse, *event)
totalRelays := len(relaysToUse)

Expand All @@ -301,6 +306,18 @@ func (c *Client) Publish(ctx context.Context, event *Event) error {
errorCh := make(chan error, 1)

go func() {
defer func() {
if r := recover(); r != nil {
errMsg := fmt.Sprintf("PANIC in Client.Publish goroutine: %v", r)
fmt.Fprintf(os.Stderr, "BBMTLog: %s\n", errMsg)
fmt.Fprintf(os.Stderr, "BBMTLog: Stack trace: %s\n", string(debug.Stack()))
select {
case errorCh <- fmt.Errorf("internal error (panic): %v", r):
default:
}
}
}()

var successCount int
var failureCount int
var allErrors []error
Expand Down Expand Up @@ -353,13 +370,20 @@ func (c *Client) Publish(ctx context.Context, event *Event) error {
}
return
}
// Safely extract relay URL to avoid nil pointer dereference
var relayURL string
if res.Relay != nil {
relayURL = res.Relay.URL
} else {
relayURL = "<unknown>"
}
if res.Error != nil {
failureCount++
allErrors = append(allErrors, res.Error)
fmt.Fprintf(os.Stderr, "BBMTLog: Client.Publish - relay %s error: %v (%d/%d failed)\n", res.Relay, res.Error, failureCount, totalRelays)
fmt.Fprintf(os.Stderr, "BBMTLog: Client.Publish - relay %s error: %v (%d/%d failed)\n", relayURL, res.Error, failureCount, totalRelays)
} else {
successCount++
fmt.Fprintf(os.Stderr, "BBMTLog: Client.Publish - relay %s success (%d/%d succeeded)\n", res.Relay, successCount, totalRelays)
fmt.Fprintf(os.Stderr, "BBMTLog: Client.Publish - relay %s success (%d/%d succeeded)\n", relayURL, successCount, totalRelays)
// Return immediately on first success (non-blocking)
if successCount == 1 {
select {
Expand Down Expand Up @@ -532,6 +556,9 @@ func (c *Client) Subscribe(ctx context.Context, filter Filter) (<-chan *Event, e
}

// PublishWrap publishes a pre-signed gift wrap event (kind:1059)
// Resiliency policy: Publishes to ALL valid relays in parallel, returns immediately on first success,
// continues publishing to other relays in background. Only fails if ALL relays fail.
// This ensures co-signing messages are delivered even if some relays are down or slow.
func (c *Client) PublishWrap(ctx context.Context, wrap *Event) error {
if wrap == nil {
return errors.New("nil wrap event")
Expand All @@ -553,32 +580,50 @@ func (c *Client) PublishWrap(ctx context.Context, wrap *Event) error {
wrap.CreatedAt = nostr.Now()
}

// Use all valid relays, not just initially connected ones
// Resiliency: Use ALL valid relays (not just initially connected ones)
// The pool will handle connections - if a relay isn't connected yet, it will try to connect
// This ensures we publish to all relays, including those that connected in background
// This is critical for co-signing resiliency across multiple relays
relaysToUse := c.validRelays
if len(relaysToUse) == 0 {
// Fallback to urls if validRelays not set (backward compatibility)
relaysToUse = c.urls
}
if len(relaysToUse) == 0 {
return errors.New("no relays configured for publishing")
}
results := c.pool.PublishMany(ctx, relaysToUse, *wrap)
totalRelays := len(relaysToUse)

// Track results in background goroutine - return immediately on first success
// Resiliency: Track results in background goroutine - return immediately on first success
// This allows co-signing to proceed quickly while other relays continue publishing in background
// Only fails if ALL relays fail, ensuring maximum resiliency
successCh := make(chan bool, 1)
errorCh := make(chan error, 1)

go func() {
defer func() {
if r := recover(); r != nil {
errMsg := fmt.Sprintf("PANIC in Client.PublishWrap goroutine: %v", r)
fmt.Fprintf(os.Stderr, "BBMTLog: %s\n", errMsg)
fmt.Fprintf(os.Stderr, "BBMTLog: Stack trace: %s\n", string(debug.Stack()))
select {
case errorCh <- fmt.Errorf("internal error (panic): %v", r):
default:
}
}
}()

var successCount int
var failureCount int
var allErrors []error

for {
select {
case <-ctx.Done():
// Context cancelled - check if we had any successes
// Context cancelled - check if we had any successes (resilient: partial success is still success)
if successCount > 0 {
fmt.Fprintf(os.Stderr, "BBMTLog: Client.PublishWrap - context cancelled but %d/%d relays succeeded\n", successCount, totalRelays)
fmt.Fprintf(os.Stderr, "BBMTLog: Client.PublishWrap - context cancelled but %d/%d relays succeeded (resilient)\n", successCount, totalRelays)
select {
case successCh <- true:
default:
Expand All @@ -601,6 +646,7 @@ func (c *Client) PublishWrap(ctx context.Context, wrap *Event) error {
if !ok {
// All relays have responded
if successCount > 0 {
// Resilient: At least one relay succeeded, operation is successful
if failureCount > 0 {
fmt.Fprintf(os.Stderr, "BBMTLog: Client.PublishWrap - %d/%d relays succeeded, %d failed (resilient)\n", successCount, totalRelays, failureCount)
} else {
Expand All @@ -612,7 +658,7 @@ func (c *Client) PublishWrap(ctx context.Context, wrap *Event) error {
default:
}
} else {
// All relays failed
// All relays failed - this is the only failure case
if len(allErrors) > 0 {
fmt.Fprintf(os.Stderr, "BBMTLog: Client.PublishWrap - all %d relays failed\n", totalRelays)
select {
Expand All @@ -628,14 +674,22 @@ func (c *Client) PublishWrap(ctx context.Context, wrap *Event) error {
}
return
}
// Safely extract relay URL to avoid nil pointer dereference
var relayURL string
if res.Relay != nil {
relayURL = res.Relay.URL
} else {
relayURL = "<unknown>"
}
if res.Error != nil {
failureCount++
allErrors = append(allErrors, res.Error)
fmt.Fprintf(os.Stderr, "BBMTLog: Client.PublishWrap - relay %s error: %v (%d/%d failed)\n", res.Relay, res.Error, failureCount, totalRelays)
fmt.Fprintf(os.Stderr, "BBMTLog: Client.PublishWrap - relay %s error: %v (%d/%d failed)\n", relayURL, res.Error, failureCount, totalRelays)
} else {
successCount++
fmt.Fprintf(os.Stderr, "BBMTLog: Client.PublishWrap - relay %s success (%d/%d succeeded)\n", res.Relay, successCount, totalRelays)
// Return immediately on first success (non-blocking)
fmt.Fprintf(os.Stderr, "BBMTLog: Client.PublishWrap - relay %s success (%d/%d succeeded)\n", relayURL, successCount, totalRelays)
// Resilient: Return immediately on first success (non-blocking)
// Other relays continue publishing in background for redundancy
if successCount == 1 {
select {
case successCh <- true:
Expand All @@ -648,17 +702,17 @@ func (c *Client) PublishWrap(ctx context.Context, wrap *Event) error {
}
}()

// Wait for first success or all failures
// Wait for first success or all failures (resiliency: succeed if ANY relay succeeds)
select {
case <-successCh:
// At least one relay succeeded - return immediately
// Other relays continue publishing in background
// Resilient: At least one relay succeeded - return immediately
// Other relays continue publishing in background for redundancy
return nil
case err := <-errorCh:
// All relays failed
// Only fails if ALL relays failed
return err
case <-ctx.Done():
// Context cancelled - check if we got any success
// Context cancelled - check if we got any success (resilient: partial success is still success)
select {
case <-successCh:
return nil
Expand Down
80 changes: 51 additions & 29 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,65 @@
# Changelog

## [Unreleased]
## [2.1.7] - 2026-01-05

### Added
- **Legacy Wallet Migration Modal**: New modal appears for users with legacy wallets, advising migration to new wallet setup for better PSBT compatibility
- Non-dismissible modal with friendly messaging
- "Do not remind me again" checkbox option
- Modal flag automatically resets on new wallet import (if wallet is legacy)
- Standalone `LegacyWalletModal` component for reusability
- **Docker Build System**: Complete Docker-based build infrastructure for Android APK compilation with cross-platform support
- **Enhanced QR Code for Send Bitcoin**: QR codes now include address type, derivation path, and network to prevent session timeouts between devices
- **From Address Display**: Transaction details now show the source (from) address in pairing screens
- **Watch-Only Wallet Export**: Streamlined to output descriptors only (removed xpub/tpub export)
- **Multiple Address Display in Transactions**: Transaction details now show all recipient addresses for sent transactions and all sender addresses for received transactions
- Sent transactions with multiple outputs (e.g., PSBT transactions) display all recipient addresses with individual amounts
- Received transactions with multiple inputs display all sender addresses with the received output amount
- Each address shows its BTC amount and fiat equivalent
- Transaction list shows count indicator for multiple recipients: "To: address... (+2 more)"

### Changed
- **Docker Build System**: Moved Docker scripts to organized `docker/scripts/` directory
- **Android Build Configuration**: Enhanced build system with Docker-specific Gradle settings and improved ProGuard rules

### Fixed
- **Nostr Transport Panic Recovery**: Enhanced panic recovery in co-signing operations with better error handling
- **Legacy Wallet Migration Modal**: New modal appears for users with legacy wallets, advising migration to new wallet setup
- **Network Reset on Wallet Import**: Network always resets to mainnet when importing a keyshare to ensure clean state
- **Balance Display Styling**: Balance rows (BTC and USD) now have transparent background while maintaining tap-to-hide functionality
- **Button Alignment**: Send and Receive buttons now vertically align with Device and Address Type buttons above for consistent spacing
- **QR Scanner Subtitle**: Updated subtitle text to "Point camera to Sending Device QR" for clarity
- **Address Flickering Issue**: Fixed address changing/flickering after lock/unlock by making UserContext the single source of truth
- **Session Timeout Fix for QR Code Scanning**: Fixed session timeouts when scanning send Bitcoin QR codes from second device
- **TransactionList Loading State**: Fixed infinite "Loading..." state and network errors when restoring wallet for the first time

## [2.1.6] - 2025-12-31

### Added
- **Balance Card in Send Modal**: New prominent balance card displayed above amount input in Send Bitcoin modal
- Shows available balance in BTC and fiat currency
- Integrated "Max" button for quick balance selection
- Clean, professional UI with card styling
- **Smart Balance Check for Send Button**: Automatic balance refresh when clicking Send with zero balance
- Prevents modal from opening prematurely when balance hasn't loaded
- Shows loading spinner on Send button during balance check
- Automatically opens modal if balance is found, or shows alert if truly zero
- Prevents multiple rapid clicks with button disable state
- 5-second timeout with graceful error handling

### Fixed
- **Address Flickering Issue**: Fixed address changing/flickering after lock/unlock by making UserContext the single source of truth for addresses
- UserContext now properly derives network-specific btcPub for both mainnet and testnet
- WalletHome prioritizes userActiveAddress from UserContext over local state
- Eliminated race conditions in address derivation
- **Network State Management**: Improved network state consistency across the app
- Network reset on import ensures proper address derivation
- All contexts and providers properly synchronized with network changes
- **Cache Clearing**: Comprehensive cache clearing on wallet setup and import screens
- LocalCache cleared on ShowcaseScreen (import)
- LocalCache cleared on MobilesPairing and MobileNostrPairing (setup mode only)
- Stale btcPub removed from EncryptedStorage
- WalletService cache cleared for fresh state
- **Co-signing Go Panic Recovery**: Fixed potential panic crashes in Nostr transport layer during co-signing
- Added panic recovery with stack trace logging in `Client.Publish` goroutine
- Improved nil pointer safety when extracting relay URLs
- Better error messages for debugging relay connection issues
- Enhanced resiliency for co-signing message delivery across multiple relays
- **Send Button Balance Race Condition**: Fixed issue where Send button would show "Insufficient Balance" alert even when balance was still loading
- Eliminates flickering and need to click Send button multiple times
- Better UX with immediate feedback during balance check

### Changed
- **Send Modal UI Enhancement**: Improved balance visibility and Max button placement
- Balance card replaces inline "Max" text link
- More prominent balance display with better visual hierarchy
- Updated QR scanner icon to use scan-icon.png for consistency

### Technical Details
- **UserContext**: Enhanced refresh() to derive separate btcPub values for mainnet and testnet
- **WalletHome**: Updated to use UserContext as primary address source, with local state as fallback
- **ShowcaseScreen**: Added network reset to mainnet on keyshare import using setActiveNetwork()
- **Cache Management**: Added useEffect hooks to clear all cache on wallet setup/import screens
- **Styles**: Updated balanceRowWithMargin to use transparent background
- **Button Layout**: Applied flexOneMinWidthZero and partyGap styles to action buttons for consistent alignment
- **WalletHome.tsx**: Added `checkBalanceForSend()` function for dedicated balance fetching
- **SendBitcoinModal.tsx**: New balance card component with integrated Max button
- **client.go**: Enhanced panic recovery and error handling in Nostr publish operations
- **Error Handling**: Improved timeout and error recovery for balance checks

## [2.1.4] - 2025-12-30

Expand All @@ -52,8 +76,6 @@
- **Android navigation bar overlap**: Fixed bottom navigation bar overlapping system navigation on Android devices (e.g., Samsung)
- **Message delivery reliability**: Improved handling of messages sent just before subscription starts

---

## [2.1.3] - 2025-12-20

### Added
Expand Down
Loading