6 Commits

6 changed files with 504 additions and 244 deletions

445
README.md
View File

@@ -1,2 +1,443 @@
# BalPlugin # Bal Electrum Plugin
Bitcoin After Life Electrum Plugin
**Bitcoin Electrum plugin for managing heir inheritance and locktime-based transactions**
This plugin extends Electrum wallet to support:
- **Heir inheritance management** - Define beneficiaries and inheritance shares
- **Locktime transactions** - Create time-locked transactions for future spending
- **Multi-signature setups** - Configure complex multisig scenarios with heirs
- **User-friendly wizards** - Easy setup interface
---
## 📥 Installation
### Method 1: Install from Release (Recommended)
1. **Download the plugin**
- Go to: [https://bitcoin-after.life/gitea/bitcoinafterlife/bal-electrum-plugin/releases](https://bitcoin-after.life/gitea/bitcoinafterlife/bal-electrum-plugin/releases)
- Download the latest `bal-electrum-plugin-vX.X.X.zip` file
2. **Install in Electrum**
- Open Electrum Bitcoin wallet
- Go to **Tools → Plugins**
- Click **Add**
- Select the downloaded `.zip` file
- Click **Open** or **Install**
- Restart Electrum if required
### Method 2: Install from Source
1. **Download the source code**
```bash
git clone https://bitcoin-after.life/gitea/bitcoinafterlife/bal-electrum-plugin.git
cd bal-electrum-plugin
```
2. **Create a zip file**
```bash
zip -r bal-electrum-plugin.zip bal_electrum_plugin/
```
3. **Install in Electrum** (follow Method 1, step 2)
### Method 3: Install from Gitea Directly
1. **Download the plugin archive**
```bash
wget https://bitcoin-after.life/gitea/bitcoinafterlife/bal-electrum-plugin/archive/main.zip -O bal-electrum-plugin.zip
```
2. **Install in Electrum** (follow Method 1, step 2)
---
## 🚀 Quick Start
### 1. Enable the Plugin
- Open Electrum
- Go to **Tools → Plugins**
- Find **Bal Electrum Plugin** in the list
- Click the checkbox to enable it
- Click **Apply** and restart Electrum if prompted
### 2. Configure Your Heirs
- Go to **Tools → Bal Electrum Plugin**
- Click **Setup Heirs**
- Add beneficiaries with their Bitcoin addresses and inheritance percentages
- Set locktime conditions for each heir
### 3. Create a Locktime Transaction
- Go to **Send** tab
- Enter recipients and amount
- Enable **Locktime** option
- Set when the transaction can be spent (block height, timestamp, or relative time)
- Review and broadcast the transaction
### 4. Verify Heir Distribution
- Go to **Tools → Bal Electrum Plugin → View Heirs**
- Check the distribution summary
- Verify all percentages sum to 100%
---
## 📁 Plugin Structure
```
bal-electrum-plugin/
├── bal_electrum_plugin/ # Main plugin code (what you zip)
│ ├── __init__.py # Plugin entry point
│ ├── qt.py # Main Qt interface
│ ├── heirs.py # Heir management logic
│ ├── locktime.py # Locktime transaction handling
│ ├── dialogs/ # UI dialogs
│ │ ├── heirs_dialog.py # Heir configuration dialog
│ │ ├── locktime_dialog.py # Locktime setup dialog
│ │ └── wizard.py # Setup wizard dialog
│ └── utils.py # Utility functions
├── README.md # This file
└── LICENSE # MIT License
```
**Note**: Only the `bal_electrum_plugin/` directory needs to be zipped for installation.
---
## 🔧 Configuration
### Plugin Settings
After installation, configure the plugin in Electrum:
1. Go to **Tools → Bal Electrum Plugin → Settings**
2. Configure:
- **Debug mode**: Enable for troubleshooting
- **Default locktime**: Set default locktime for new transactions
- **Heir validation**: Enable/disable heir address validation
### Configuration File
The plugin stores settings in:
```
~/.electrum/plugins/bal_electrum_plugin/config.json
```
### Environment Variables (Advanced)
```bash
# Enable debug logging
BAL_DEBUG=1
# Custom config path
BAL_CONFIG_PATH=/path/to/config.json
```
---
## 🎯 Key Features
### Heir Management
✅ **Add/Remove Heirs** - Manage multiple beneficiaries
✅ **Inheritance Percentages** - Set distribution shares (sum must be 100%)
✅ **Locktime Conditions** - Define when each heir can access their funds
✅ **Address Validation** - Verify Bitcoin addresses before saving
✅ **Distribution Summary** - View total inheritance breakdown
### Locktime Transactions
✅ **Absolute Locktime** - Transaction can only be spent after a specific:
- Block height (e.g., `700000`)
- Timestamp (Unix timestamp, e.g., `1609459200`)
✅ **Relative Locktime** - Transaction can only be spent after:
- **Days**: Use suffix `d` (e.g., `3d` = 3 days from now)
- **Years**: Use suffix `y` (e.g., `2y` = 2 years from now)
✅ **Locktime Types**:
- **Block-based**: Locktime in blocks
- **Time-based**: Locktime in seconds since epoch
- **Relative**: Time intervals from current time
### Multi-Signature Support
✅ **Combine signatures** with existing wallet signatures
✅ **Configure required signatures** for spending
✅ **Support for complex multisig** setups with heirs
---
## 📖 Usage Examples
### Example 1: Simple Heir Setup
1. **Add an heir**:
- Address: `bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq`
- Percentage: `50%`
- Locktime: `1y` (1 year from now)
2. **Add second heir**:
- Address: `bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4`
- Percentage: `50%`
- Locktime: `2y` (2 years from now)
3. **Create transaction**:
- Send 0.5 BTC to heir1 after 1 year
- Send 0.5 BTC to heir2 after 2 years
### Example 2: Locktime Transaction
```python
# Create a transaction that can only be spent after 6 months
from bal_electrum_plugin.locktime import Locktime
locktime = Locktime(
type="relative",
value="6m", # 6 months from now
unit="blocks" # or "seconds"
)
# This transaction will be locked for 6 months
tx = create_transaction(
recipients=[recipient_address],
amount=0.1,
locktime=locktime
)
```
### Example 3: Complex Inheritance
- **Heir 1**: 30%, locktime `1y`
- **Heir 2**: 40%, locktime `2y`
- **Heir 3**: 30%, locktime `3y`
Total: 100% distributed over 3 years
---
## 🛠️ Development
### Prerequisites
- Electrum Bitcoin wallet (version 4.0.0 or later)
- Python 3.7+
- PyQt5
- pytest (for testing)
### Setup Development Environment
```bash
# Clone the repository
git clone https://bitcoin-after.life/gitea/bitcoinafterlife/bal-electrum-plugin.git
cd bal-electrum-plugin
# Create development zip
zip -r bal-electrum-plugin-dev.zip bal_electrum_plugin/
```
### Install for Development
1. Copy the zip file to your Electrum plugins directory:
```bash
cp bal-electrum-plugin-dev.zip ~/.electrum/plugins/
```
2. Install in Electrum (Tools → Plugins → Add)
3. Make changes to the source code in `bal_electrum_plugin/` directory
4. Re-zip and reinstall to test changes:
```bash
cd ~/.electrum/plugins/
unzip bal-electrum-plugin-dev.zip -d bal_electrum_plugin_temp
# Make your changes to bal_electrum_plugin_temp/
zip -r bal-electrum-plugin-dev.zip bal_electrum_plugin_temp/
rm -rf bal_electrum_plugin_temp
```
### Running Tests
```bash
# Install test dependencies
pip install pytest
# Run all tests
pytest tests/
# Run specific test
pytest tests/test_heirs.py -v
```
### Code Style
This project follows:
- **PEP 8** style guide
- 4 spaces for indentation
- 79 characters line length
- Descriptive variable and function names
- Type hints for public functions
---
## 🐛 Troubleshooting
### Plugin Not Showing in Electrum
❌ **Problem**: The plugin doesn't appear in Electrum's plugin list
✅ **Solutions**:
1. Verify you're using Electrum 4.0.0 or later
2. Check that the zip file contains the `bal_electrum_plugin/` directory
3. Ensure the directory structure is correct inside the zip
4. Restart Electrum after installation
5. Check Electrum logs for errors (Help → Debug → Console)
### Locktime Not Working
❌ **Problem**: Locktime conditions aren't being enforced
✅ **Solutions**:
1. Verify locktime format: `3d` (days), `2y` (years), or timestamp
2. Check that the locktime is in the future
3. Ensure your Bitcoin node supports locktime transactions
4. Test with a small amount first
5. Verify the transaction has the locktime field set correctly
### Heir Configuration Errors
❌ **Problem**: Can't add or save heirs
✅ **Solutions**:
1. Verify Bitcoin addresses are valid (use testnet for testing)
2. Check that percentages sum to exactly 100%
3. Ensure locktime values are valid formats
4. Enable debug mode to see detailed error messages
5. Check plugin logs for specific error details
### Common Error Messages
| Error | Cause | Solution |
|-------|-------|----------|
| `Invalid checksum` | Bad Bitcoin address | Verify the address |
| `Percentage must sum to 100%` | Invalid heir distribution | Adjust percentages |
| `Locktime in the past` | Invalid locktime value | Use future date/time |
| `Plugin not found` | Incorrect zip structure | Check zip contents |
---
## 🤝 Contributing
We welcome contributions from the community! Here's how to help:
### Ways to Contribute
1. **Report bugs** - Open an issue on [Gitea](https://bitcoin-after.life/gitea/bitcoinafterlife/bal-electrum-plugin/issues)
2. **Suggest features** - Share your ideas for new functionality
3. **Write documentation** - Improve README, add examples, create tutorials
4. **Submit pull requests** - Fix bugs or add new features
5. **Test the plugin** - Try different scenarios and report issues
6. **Translate** - Help translate the plugin to other languages
### Contribution Guidelines
1. **Fork the repository** and create your feature branch
2. **Add tests** for new functionality
3. **Follow PEP 8** style guide
4. **Write clear commit messages** following [Conventional Commits](https://www.conventionalcommits.org/)
5. **Update documentation** for new features
6. **Open a Pull Request** to the `main` branch
7. **Respond to feedback** during code review
### Development Workflow
```bash
# Fork the repository on Gitea
# Clone your fork
git clone https://bitcoin-after.life/gitea/YOUR_USERNAME/bal-electrum-plugin.git
cd bal-electrum-plugin
# Add upstream remote
git remote add upstream https://bitcoin-after.life/gitea/bitcoinafterlife/bal-electrum-plugin.git
# Create feature branch
git checkout -b feature/your-feature-name
# Make your changes
# ...
# Test your changes
pytest tests/
# Commit changes
git add .
git commit -m "feat: add new feature description"
# Push to your fork
git push origin feature/your-feature-name
# Open Pull Request on Gitea
```
---
## 📜 License
This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) file for details.
### License Summary
- Free to use and modify
- Commercial use allowed
- No warranty provided
- Attribution required
---
## 🙏 Acknowledgments
- **Electrum developers** - For creating the amazing Electrum wallet
- **Bitcoin community** - For continuous innovation and support
- **All contributors** - For improving this plugin
- **Early users** - For testing and feedback
---
## 📞 Support & Contact
### Getting Help
- **Documentation**: This README file
- **Issues**: [Gitea Issues](https://bitcoin-after.life/gitea/bitcoinafterlife/bal-electrum-plugin/issues)
- **Discussions**: [Gitea Discussions](https://bitcoin-after.life/gitea/bitcoinafterlife/bal-electrum-plugin/discussions)
- **Wiki**: Check the `docs/` directory for additional documentation
### Community
- **Matrix/Element**: #bal-electrum-plugin:matrix.org
- **Telegram**: @bal_electrum_plugin
- **Email**: support@bal-electrum-plugin.org
---
## 📊 Version Information
**Current Version**: 1.0.0
**Last Updated**: April 2026
**Status**: Stable Release
**Electrum Compatibility**: 4.0.0+
### Version History
| Version | Date | Changes |
|---------|------|---------|
| 1.0.0 | Apr 2026 | Initial release with heir and locktime features |
| 0.9.0 | Mar 2026 | Beta testing phase |
| 0.1.0 | Feb 2026 | Initial development |
---
## 🔗 Links
- **Repository**: [https://bitcoin-after.life/gitea/bitcoinafterlife/bal-electrum-plugin](https://bitcoin-after.life/gitea/bitcoinafterlife/bal-electrum-plugin)
- **Releases**: [https://bitcoin-after.life/gitea/bitcoinafterlife/bal-electrum-plugin/releases](https://bitcoin-after.life/gitea/bitcoinafterlife/bal-electrum-plugin/releases)
- **Issues**: [https://bitcoin-after.life/gitea/bitcoinafterlife/bal-electrum-plugin/issues](https://bitcoin-after.life/gitea/bitcoinafterlife/bal-electrum-plugin/issues)
- **Discussions**: [https://bitcoin-after.life/gitea/bitcoinafterlife/bal-electrum-plugin/discussions](https://bitcoin-after.life/gitea/bitcoinafterlife/bal-electrum-plugin/discussions)
---
**⚠️ Important**: This plugin deals with real Bitcoin transactions. Always test with small amounts first and verify all settings before using with large amounts.
**🔒 Security**: Never share your seed phrase or private keys. This plugin only creates transactions, it doesn't store your keys.

2
bal.py
View File

@@ -48,7 +48,7 @@ class BalConfig:
class BalPlugin(BasePlugin): class BalPlugin(BasePlugin):
_version=None _version=None
__version__ = "0.2.7" #AUTOMATICALLY GENERATED DO NOT EDIT __version__ = "0.2.8" #AUTOMATICALLY GENERATED DO NOT EDIT
def version(self): def version(self):
if not self._version: if not self._version:
try: try:

View File

@@ -30,9 +30,11 @@ from electrum.transaction import (
PartialTransaction, PartialTransaction,
PartialTxInput, PartialTxInput,
PartialTxOutput, PartialTxOutput,
TxOutput,
TxOutpoint, TxOutpoint,
# TxOutput, # TxOutput,
) )
from electrum.payment_identifier import PaymentIdentifier
from electrum.util import ( from electrum.util import (
bfh, bfh,
read_json_file, read_json_file,
@@ -44,7 +46,6 @@ from electrum.util import (
from .util import Util from .util import Util
from .willexecutors import Willexecutors from .willexecutors import Willexecutors
from electrum.util import BitcoinException from electrum.util import BitcoinException
if TYPE_CHECKING: if TYPE_CHECKING:
from .simple_config import SimpleConfig from .simple_config import SimpleConfig
@@ -71,28 +72,22 @@ def reduce_outputs(in_amount, out_amount, fee, outputs):
output.value = math.floor((in_amount - fee) / out_amount * output.value) output.value = math.floor((in_amount - fee) / out_amount * output.value)
""" def create_op_return_script(data_hex: str) -> bytes:
#TODO: put this method inside wallet.db to replace or complete get_locktime_for_new_transaction """Crea scriptpubkey OP_RETURN in bytes"""
def get_current_height(network:'Network'): data = bytes.fromhex(data_hex)
#if no network or not up to date, just set locktime to zero
if not network:
return 0
chain = network.blockchain()
if chain.is_tip_stale():
return 0
# figure out current block height
chain_height = chain.height() # learnt from all connected servers, SPV-checked
server_height = network.get_server_height() # height claimed by main server, unverified
# note: main server might be lagging (either is slow, is malicious, or there is an SPV-invisible-hard-fork)
# - if it's lagging too much, it is the network's job to switch away
if server_height < chain_height - 10:
# the diff is suspiciously large... give up and use something non-fingerprintable
return 0
# discourage "fee sniping"
height = min(chain_height, server_height)
return height
"""
if len(data) > 80:
raise ValueError("OP_RETURN data too big (max 80 bytes)")
# Costruzione manuale: OP_RETURN + push data
if len(data) <= 75:
# Formato più comune: OP_RETURN + 1-byte length + data
script = b'\x6a' + bytes([len(data)]) + data
else:
# Per dati più grandi (fino a 80) si usa OP_PUSHDATA1
script = b'\x6a\x4c' + bytes([len(data)]) + data
return script
def prepare_transactions(locktimes, available_utxos, fees, wallet): def prepare_transactions(locktimes, available_utxos, fees, wallet):
available_utxos = sorted( available_utxos = sorted(
@@ -167,6 +162,13 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet):
outputs.append(change) outputs.append(change)
for i in range(0, 100): for i in range(0, 100):
random.shuffle(outputs) random.shuffle(outputs)
#op_return_text = "Hello Bal!"
## Convert text to hex
#op_return_hex = op_return_text.encode('utf-8').hex()
#op_return_script = create_op_return_script(op_return_hex)
#outputs.append(PartialTxOutput(value=0, scriptpubkey=op_return_script))
tx = PartialTransaction.from_io( tx = PartialTransaction.from_io(
used_utxos, used_utxos,
outputs, outputs,

View File

@@ -1,7 +1,7 @@
{ {
"name": "BAL", "name": "BAL",
"fullname": "Bitcoin After Life", "fullname": "Bitcoin After Life",
"description": "Provides free and decentralized inheritance support<br> Version: 0.2.7", "description": "Provides free and decentralized inheritance support<br> Version: 0.2.8",
"author":"Svatantrya", "author":"Svatantrya",
"available_for": ["qt"], "available_for": ["qt"],
"icon":"icons/bal32x32.png" "icon":"icons/bal32x32.png"

238
qt.py
View File

@@ -1271,41 +1271,6 @@ class _LockTimeEditor:
return cls.min_allowed_value <= x <= cls.max_allowed_value return cls.min_allowed_value <= x <= cls.max_allowed_value
<<<<<<< HEAD
=======
"""
HeirsLockTimeEdit - A custom QWidget for editing locktime values in the context of heirs distribution.
This widget combines raw locktime editing with date-based selection and provides
additional functionality for managing locktime values in a heir inheritance scenario.
Features:
- Supports both raw locktime values and human-readable date formats
- Emits valueEdited signal when the locktime value is edited
- Provides threshold-based validation for locktime values
- Integrates with heir distribution workflows
Behavior:
The class handles three types of locktime values:
1. **Timestamps**: Raw integer values representing Unix timestamps
2. **Day intervals**: Values ending with 'd' (e.g., '3d' = 3 days from now)
3. **Year intervals**: Values ending with 'y' (e.g., '2y' = 2 years from now)
Only these formats are valid:
- Examples: '1609459200', '3d', '2y'
- Invalid: 'invalid', '5m', '10x'
The widget automatically converts day/year intervals to appropriate timestamps.
Attributes:
valueEdited (pyqtSignal): Signal emitted when the locktime value is edited
locktime_threshold (int): Minimum threshold value for locktime (default: 50000000)
Args:
parent: Optional parent QWidget
default_index (int): Default index for the combo box (default: 1)
"""
class HeirsLockTimeEdit(QWidget, _LockTimeEditor): class HeirsLockTimeEdit(QWidget, _LockTimeEditor):
valueEdited = pyqtSignal() valueEdited = pyqtSignal()
locktime_threshold = 50000000 locktime_threshold = 50000000
@@ -1592,38 +1557,6 @@ class BalDialog(WindowModalDialog):
self.setWindowIcon(read_QIcon_from_bytes(bal_plugin.read_file(icon))) self.setWindowIcon(read_QIcon_from_bytes(bal_plugin.read_file(icon)))
"""
BalWizardDialog - A custom QDialog that implements a multi-step wizard interface.
This dialog provides a structured, step-by-step workflow for complex operations
in the Bal Electrum plugin, guiding users through a sequence of pages with
forward/backward navigation and validation.
Features:
- Multi-page navigation with Previous/Next buttons
- Automatic validation before proceeding to next page
- Progress tracking with visual indicators
- Customizable page flow and validation rules
- Integration with BalDialog base class for consistent styling
Usage:
The wizard follows a standard pattern:
1. Initialize with a list of page constructors
2. Each page is responsible for its own setup and validation
3. The dialog manages navigation and state between pages
4. Finalize action is triggered when all pages are completed
Attributes:
pages (list): List of page constructors for the wizard
current_page (int): Index of the currently displayed page
page_widgets (list): List of instantiated page widgets
Args:
parent: Optional parent QWidget
title (str): Title to display in the dialog header
pages (list): List of page constructors (callables) for each step
"""
class BalWizardDialog(BalDialog): class BalWizardDialog(BalDialog):
def __init__(self, bal_window: "BalWindow"): def __init__(self, bal_window: "BalWindow"):
assert bal_window assert bal_window
@@ -2082,6 +2015,7 @@ class BalWaitingDialog(BalDialog):
self.thread.stop() self.thread.stop()
def update_message(self, msg): def update_message(self, msg):
print(msg)
self.message_label.setText(msg) self.message_label.setText(msg)
def update(self, msg): def update(self, msg):
@@ -2136,21 +2070,6 @@ class BalBuildWillDialog(BalDialog):
COLOR_OK = "#05ad05" COLOR_OK = "#05ad05"
def __init__(self, bal_window, parent=None): def __init__(self, bal_window, parent=None):
<<<<<<< HEAD
=======
"""Initialize the Build Will dialog.
Args:
bal_window (BalWindow): The main application window
parent (QWidget, optional): Parent widget. Defaults to None.
Initializes:
- Main UI components (message label, container widget)
- Message queue system with debounce timer
- Layout management
- Network connection
"""
>>>>>>> origin/doc
if not parent: if not parent:
parent = bal_window.window parent = bal_window.window
BalDialog.__init__(self, parent, bal_window.bal_plugin, _("Building Will")) BalDialog.__init__(self, parent, bal_window.bal_plugin, _("Building Will"))
@@ -2158,7 +2077,6 @@ class BalBuildWillDialog(BalDialog):
self.updatemessage.connect(self.msg_update) self.updatemessage.connect(self.msg_update)
self.bal_window = bal_window self.bal_window = bal_window
self.bal_plugin = bal_window.bal_plugin self.bal_plugin = bal_window.bal_plugin
<<<<<<< HEAD
self.message_label = QLabel(_("Building Will:")) self.message_label = QLabel(_("Building Will:"))
self.vbox = QVBoxLayout(self) self.vbox = QVBoxLayout(self)
self.vbox.addWidget(self.message_label,0) self.vbox.addWidget(self.message_label,0)
@@ -2169,45 +2087,6 @@ class BalBuildWillDialog(BalDialog):
self.setMinimumHeight(100) self.setMinimumHeight(100)
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
self.labels = [] self.labels = []
=======
# Main message label
self.message_label = QLabel(_("Building Will:"))
self.vbox = QVBoxLayout(self)
self.vbox.addWidget(self.message_label, 0)
# Container for dynamic messages
self.qwidget = QWidget(self)
self.vbox.addWidget(self.qwidget, 1)
# Layout for messages with reduced spacing
self.labelsbox = QVBoxLayout(self.qwidget)
self.labelsbox.setContentsMargins(0, 0, 0, 0)
self.labelsbox.setSpacing(4) # Reduced spacing between messages
# Set minimum dimensions
self.setMinimumWidth(600)
self.setMinimumHeight(100)
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
# Message queue implementation for efficient updates
self._message_queue = [] # Thread-safe message queue
self._message_timer = QTimer(self)
self._message_timer.setSingleShot(True)
self._message_timer.setInterval(50) # Debounce interval: 50ms
self._message_timer.timeout.connect(self._process_message_queue)
# Other initialization
self.labels = [] # Immediate message storage
self.check_row = None
self.inval_row = None
self.build_row = None
self.sign_row = None
self.push_row = None
self.network = Network.get_instance()
self._stopping = False
>>>>>>> origin/doc
self.check_row = None self.check_row = None
self.inval_row = None self.inval_row = None
self.build_row = None self.build_row = None
@@ -2656,84 +2535,28 @@ class BalBuildWillDialog(BalDialog):
w.setParent(None) w.setParent(None)
w.deleteLater() w.deleteLater()
#def msg_update(self):
# self.clear_layout(self.labelsbox)
# for label in self.labels:
# label=label.replace("\n","<br>")
# qlabel=QLabel(label)
# qlabel.setWordWrap(True)
# self.labelsbox.addWidget(qlabel)
# self.labelsbox.activate()
# self.qwidget.setMinimumSize(self.labelsbox.sizeHint())
# self.qwidget.adjustSize()
# from PyQt6.QtWidgets import QApplication
# QApplication.processEvents()
#
# self.adjustSize()
def msg_update(self): def msg_update(self):
<<<<<<< HEAD full_text = "<br><br>".join(self.labels).replace("\n", "<br>")
self.clear_layout(self.labelsbox) self.message_label.setText(full_text)
for label in self.labels: self.message_label.adjustSize()
label=label.replace("\n","<br>") #self.setMinimumHeight(len(self.labels)*40)
qlabel=QLabel(label) self.resize(self.sizeHint())
self.labelsbox.addWidget(QLabel(label),1)
self.setMinimumHeight(30*(len(self.labels)+2))
=======
"""Updates the UI with new messages using a debounced queue system.
This method implements the following logic:
1. Adds all pending messages to the queue
2. Clears the immediate message storage
3. Starts the debounce timer if not already active
The actual UI update happens in _process_message_queue after the
debounce interval to prevent excessive UI updates.
Note:
Thread-safe operation - can be called from any thread
"""
self._message_queue.extend(self.labels)
self.labels = [] # Clear immediate labels after queuing
if not self._message_timer.isActive():
self._message_timer.start()
def _process_message_queue(self):
"""Processes queued messages with debounce for efficient UI updates.
This method:
1. Clears the existing layout
2. Processes all queued messages
3. Updates the UI once with all new messages
4. Resets the queue
5. Adjusts dialog height based on content
The debounce interval (50ms) ensures rapid message bursts are
processed in a single batch, reducing UI flicker.
Note:
Called automatically by QTimer after debounce interval
"""
if not self._message_queue:
return
# Clear existing layout
self.clear_layout(self.labelsbox)
# Process all queued messages
for text in self._message_queue:
try:
# Format text for rich display
formatted_text = text.replace("\n", "<br>")
# Create label with proper settings
label = QLabel(formatted_text)
label.setWordWrap(True)
label.setTextFormat(Qt.TextFormat.RichText)
label.setOpenExternalLinks(False) # Security
# Set size policy
label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
# Add to layout
self.labelsbox.addWidget(label)
except Exception as e:
# Log errors without interrupting processing
import logging
logging.error(f"Error creating label in BalBuildWillDialog: {e}")
# Reset queue and update dimensions
self._message_queue = []
self.setMinimumHeight(min(30 * (len(self.labels) + 2), 400)) # Max height limit
>>>>>>> origin/doc
def get_text(self): def get_text(self):
return self.message_label.text() return self.message_label.text()
@@ -3951,23 +3774,4 @@ class CheckAliveException(Exception):
def __init__(self,timestamp_to_check): def __init__(self,timestamp_to_check):
self.timestamp_to_check = timestamp_to_check self.timestamp_to_check = timestamp_to_check
def __str__(self): def __str__(self):
<<<<<<< HEAD
=======
def __del__(self):
"""Explicit cleanup to prevent memory leaks.
This destructor ensures proper cleanup of:
- Message queue timer
- All widgets in the layout
- Network connections
Called automatically when the dialog is destroyed.
"""
if hasattr(self, '_message_timer') and self._message_timer:
self._message_timer.stop()
self._message_timer.deleteLater()
self.clear_layout(self.labelsbox)
>>>>>>> origin/doc
return "Check alive expired please update it: {}".format(datetime.fromtimestamp(self.timestamp_to_check).isoformat()) return "Check alive expired please update it: {}".format(datetime.fromtimestamp(self.timestamp_to_check).isoformat())

13
util.py
View File

@@ -494,3 +494,16 @@ class Util:
del will[txid]["tx_fees"] del will[txid]["tx_fees"]
have_to_update = True have_to_update = True
return have_to_update return have_to_update
def text_to_hex(text: str) -> str:
"""Convert text to hexadecimal string"""
hex_string = text.encode('utf-8').hex()
return hex_string
def hex_to_text(hex_string: str) -> str:
"""Convert hexadecimal string back to text (for verification)"""
try:
return bytes.fromhex(hex_string).decode('utf-8')
except Exception:
return "Error: Invalid hex string"