6 Commits

Author SHA1 Message Date
b5eda4f05a partial commit to fix wallet utils
this commit provide a lot of changes in will-settings including export to ics calendar file.
2026-04-27 10:03:05 -04:00
9204c90e4c WillException.init bug 2026-04-15 13:08:59 -04:00
6f44a3bb54 checkalive sulla finestra will 2026-04-10 16:34:27 -04:00
8f966a974a checkalive sulla finestra will 2026-04-10 16:08:33 -04:00
a59b5c47b6 checkalive sulla finestra will 2026-04-10 16:02:40 -04:00
56586ef0a7 default locktime as date or as intervall depending on value. 2026-04-09 16:45:47 -04:00
10 changed files with 876 additions and 1060 deletions

445
README.md
View File

@@ -1,443 +1,2 @@
# Bal Electrum Plugin # BalPlugin
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.

63
bal.py
View File

@@ -1,14 +1,14 @@
import os import os
from datetime import date, datetime, timedelta
import platform
# import random # import random
# import zipfile as zipfile_lib # import zipfile as zipfile_lib
from electrum import constants, json_db
from electrum import json_db
from electrum.logging import get_logger from electrum.logging import get_logger
from electrum.plugin import BasePlugin from electrum.plugin import BasePlugin
from electrum.transaction import tx_from_any from electrum.transaction import tx_from_any
_logger = get_logger(__name__)
def get_will_settings(x): def get_will_settings(x):
# print(x) # print(x)
pass pass
@@ -49,6 +49,12 @@ class BalConfig:
class BalPlugin(BasePlugin): class BalPlugin(BasePlugin):
_version=None _version=None
__version__ = "0.2.8" #AUTOMATICALLY GENERATED DO NOT EDIT __version__ = "0.2.8" #AUTOMATICALLY GENERATED DO NOT EDIT
default_app={
"Linux":"xdg-open",
"Window":"start",
"Darwin":"open"
}
chainname = constants.net.NET_NAME if constants.net.NET_NAME != "mainnet" else "bitcoin"
def version(self): def version(self):
if not self._version: if not self._version:
try: try:
@@ -100,6 +106,7 @@ class BalPlugin(BasePlugin):
self.HIDE_INVALIDATED = BalConfig(config, "bal_hide_invalidated", True) self.HIDE_INVALIDATED = BalConfig(config, "bal_hide_invalidated", True)
self.ALLOW_REPUSH = BalConfig(config, "bal_allow_repush", True) self.ALLOW_REPUSH = BalConfig(config, "bal_allow_repush", True)
self.FIRST_EXECUTION = BalConfig(config, "bal_first_execution", True) self.FIRST_EXECUTION = BalConfig(config, "bal_first_execution", True)
self.WELIST_SERVER = BalConfig(config,"bal_welist_server","https://welist.bitcoin-after.life/")
self.WILLEXECUTORS = BalConfig( self.WILLEXECUTORS = BalConfig(
config, config,
"bal_willexecutors", "bal_willexecutors",
@@ -122,18 +129,33 @@ class BalPlugin(BasePlugin):
"selected": True, "selected": True,
} }
}, },
"testnet4": {
"https://we.bitcoin-after.life": {
"base_fee": 100000,
"status": "New",
"info": "Bitcoin After Life Will Executor",
"address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7",
"selected": True,
}
},
"regtest": {
"https://we.bitcoin-after.life": {
"base_fee": 100000,
"status": "New",
"info": "Bitcoin After Life Will Executor",
"address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7",
"selected": True,
}
},
}, },
) )
self.WILL_SETTINGS = BalConfig( self.WILL_SETTINGS = BalConfig(
config, config,
"bal_will_settings", "bal_will_settings",
{ BalPlugin.default_will_settings(),
"baltx_fees": 100,
"threshold": "180d",
"locktime": "1y",
},
) )
self.system = platform.system()
self.CALENDAR_APP = BalConfig(config,"bal_open_app",self.default_app[self.system])
self._hide_invalidated = self.HIDE_INVALIDATED.get() self._hide_invalidated = self.HIDE_INVALIDATED.get()
self._hide_replaced = self.HIDE_REPLACED.get() self._hide_replaced = self.HIDE_REPLACED.get()
@@ -149,13 +171,22 @@ class BalPlugin(BasePlugin):
self.HIDE_REPLACED.set(self._hide_replaced) self.HIDE_REPLACED.set(self._hide_replaced)
def validate_will_settings(self, will_settings): def validate_will_settings(self, will_settings):
if int(will_settings.get("baltx_fees", 1)) < 1: defaults=BalPlugin.default_will_settings()
will_settings["baltx_fees"] = 1 if not will_settings:
will_settings=[]
if int(will_settings.get("baltx_fees", 0)) < 1:
will_settings["baltx_fees"] = defaults['baltx_fees']
if not will_settings.get("threshold"): if not will_settings.get("threshold"):
will_settings["threshold"] = "180d" will_settings["threshold"] = defaults['threshold']
if not will_settings.get("locktime"): if not will_settings.get("locktime"):
will_settings["locktime"] = "1y" will_settings["locktime"] = defaults['locktime']
return will_settings return will_settings
def default_will_settings(self): @staticmethod
return {"baltx_fees": 100, "threshold": "180d", "locktime": "1y"} def default_will_settings():
today = date.today()
dt = datetime(today.year, today.month, today.day, 0, 0, 0)
threshold =(dt + timedelta(days=180)).timestamp()
locktime =(dt + timedelta(days=365)).timestamp()
return {"baltx_fees": 100, "threshold": threshold, "locktime": locktime}

View File

@@ -30,12 +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 (
BitcoinException,
bfh, bfh,
read_json_file, read_json_file,
to_string, to_string,
@@ -45,7 +44,7 @@ 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
if TYPE_CHECKING: if TYPE_CHECKING:
from .simple_config import SimpleConfig from .simple_config import SimpleConfig
@@ -184,7 +183,7 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet):
tx.remove_signatures() tx.remove_signatures()
txid = tx.txid() txid = tx.txid()
if txid is None: if txid is None:
raise Exception("txid is none", tx) raise Exception(f"txid is none: {tx}")
tx.heirs = paid_heirs tx.heirs = paid_heirs
tx.my_locktime = locktime tx.my_locktime = locktime

979
qt.py

File diff suppressed because it is too large Load Diff

68
util.py
View File

@@ -1,15 +1,13 @@
import bisect import bisect
from datetime import datetime, timedelta from datetime import datetime, timedelta
from electrum.gui.qt.util import getSaveFileName
from electrum.i18n import _
from electrum.transaction import PartialTxOutput from electrum.transaction import PartialTxOutput
from electrum.util import FileExportFailed
LOCKTIME_THRESHOLD = 500000000 LOCKTIME_THRESHOLD = 500000000
class Util: class Util:
@staticmethod
def locktime_to_str(locktime): def locktime_to_str(locktime):
try: try:
locktime = int(locktime) locktime = int(locktime)
@@ -21,6 +19,7 @@ class Util:
pass pass
return str(locktime) return str(locktime)
@staticmethod
def str_to_locktime(locktime): def str_to_locktime(locktime):
try: try:
if locktime[-1] in ("y", "d", "b"): if locktime[-1] in ("y", "d", "b"):
@@ -33,6 +32,7 @@ class Util:
timestamp = dt_object.timestamp() timestamp = dt_object.timestamp()
return int(timestamp) return int(timestamp)
@staticmethod
def parse_locktime_string(locktime, w=None): def parse_locktime_string(locktime, w=None):
try: try:
return int(locktime) return int(locktime)
@@ -60,6 +60,7 @@ class Util:
pass pass
return 0 return 0
@staticmethod
def int_locktime(seconds=0, minutes=0, hours=0, days=0, blocks=0): def int_locktime(seconds=0, minutes=0, hours=0, days=0, blocks=0):
return int( return int(
seconds seconds
@@ -69,6 +70,7 @@ class Util:
+ blocks * 600 + blocks * 600
) )
@staticmethod
def encode_amount(amount, decimal_point): def encode_amount(amount, decimal_point):
if Util.is_perc(amount): if Util.is_perc(amount):
return amount return amount
@@ -78,6 +80,7 @@ class Util:
except Exception: except Exception:
return 0 return 0
@staticmethod
def decode_amount(amount, decimal_point): def decode_amount(amount, decimal_point):
if Util.is_perc(amount): if Util.is_perc(amount):
return amount return amount
@@ -88,12 +91,14 @@ class Util:
except Exception: except Exception:
return str(amount) return str(amount)
@staticmethod
def is_perc(value): def is_perc(value):
try: try:
return value[-1] == "%" return value[-1] == "%"
except Exception: except Exception:
return False return False
@staticmethod
def cmp_array(heira, heirb): def cmp_array(heira, heirb):
try: try:
if len(heira) != len(heirb): if len(heira) != len(heirb):
@@ -105,11 +110,13 @@ class Util:
except Exception: except Exception:
return False return False
@staticmethod
def cmp_heir(heira, heirb): def cmp_heir(heira, heirb):
if heira[0] == heirb[0] and heira[1] == heirb[1]: if heira[0] == heirb[0] and heira[1] == heirb[1]:
return True return True
return False return False
@staticmethod
def cmp_willexecutor(willexecutora, willexecutorb): def cmp_willexecutor(willexecutora, willexecutorb):
if willexecutora == willexecutorb: if willexecutora == willexecutorb:
return True return True
@@ -124,6 +131,7 @@ class Util:
return False return False
return False return False
@staticmethod
def search_heir_by_values(heirs, heir, values): def search_heir_by_values(heirs, heir, values):
for h, v in heirs.items(): for h, v in heirs.items():
found = False found = False
@@ -135,12 +143,14 @@ class Util:
return h return h
return False return False
@staticmethod
def cmp_heir_by_values(heira, heirb, values): def cmp_heir_by_values(heira, heirb, values):
for v in values: for v in values:
if heira[v] != heirb[v]: if heira[v] != heirb[v]:
return False return False
return True return True
@staticmethod
def cmp_heirs_by_values( def cmp_heirs_by_values(
heirsa, heirsb, values, exclude_willexecutors=False, reverse=True heirsa, heirsb, values, exclude_willexecutors=False, reverse=True
): ):
@@ -165,6 +175,7 @@ class Util:
else: else:
return True return True
@staticmethod
def cmp_heirs( def cmp_heirs(
heirsa, heirsa,
heirsb, heirsb,
@@ -187,6 +198,7 @@ class Util:
raise e raise e
return False return False
@staticmethod
def cmp_inputs(inputsa, inputsb): def cmp_inputs(inputsa, inputsb):
if len(inputsa) != len(inputsb): if len(inputsa) != len(inputsb):
return False return False
@@ -195,6 +207,7 @@ class Util:
return False return False
return True return True
@staticmethod
def cmp_outputs(outputsa, outputsb, willexecutor_output=None): def cmp_outputs(outputsa, outputsb, willexecutor_output=None):
if len(outputsa) != len(outputsb): if len(outputsa) != len(outputsb):
return False return False
@@ -204,6 +217,7 @@ class Util:
return False return False
return True return True
@staticmethod
def cmp_txs(txa, txb): def cmp_txs(txa, txb):
if not Util.cmp_inputs(txa.inputs(), txb.inputs()): if not Util.cmp_inputs(txa.inputs(), txb.inputs()):
return False return False
@@ -211,6 +225,7 @@ class Util:
return False return False
return True return True
@staticmethod
def get_value_amount(txa, txb): def get_value_amount(txa, txb):
outputsa = txa.outputs() outputsa = txa.outputs()
# outputsb = txb.outputs() # outputsb = txb.outputs()
@@ -229,6 +244,7 @@ class Util:
return value_amount return value_amount
@staticmethod
def chk_locktime(timestamp_to_check, block_height_to_check, locktime): def chk_locktime(timestamp_to_check, block_height_to_check, locktime):
# TODO BUG: WHAT HAPPEN AT THRESHOLD? # TODO BUG: WHAT HAPPEN AT THRESHOLD?
locktime = int(locktime) locktime = int(locktime)
@@ -239,6 +255,7 @@ class Util:
else: else:
return False return False
@staticmethod
def anticipate_locktime(locktime, blocks=0, hours=0, days=0): def anticipate_locktime(locktime, blocks=0, hours=0, days=0):
locktime = int(locktime) locktime = int(locktime)
out = 0 out = 0
@@ -255,6 +272,7 @@ class Util:
out = 1 out = 1
return out return out
@staticmethod
def cmp_locktime(locktimea, locktimeb): def cmp_locktime(locktimea, locktimeb):
if locktimea == locktimeb: if locktimea == locktimeb:
return 0 return 0
@@ -268,17 +286,20 @@ class Util:
else: else:
return int(locktimea) - (locktimeb) return int(locktimea) - (locktimeb)
@staticmethod
def get_lowest_valid_tx(available_utxos, will): def get_lowest_valid_tx(available_utxos, will):
will = sorted(will.items(), key=lambda x: x[1]["tx"].locktime) will = sorted(will.items(), key=lambda x: x[1]["tx"].locktime)
for txid, willitem in will.items(): for txid, willitem in will.items():
pass pass
@staticmethod
def get_locktimes(will): def get_locktimes(will):
locktimes = {} locktimes = {}
for txid, willitem in will.items(): for txid, willitem in will.items():
locktimes[willitem["tx"].locktime] = True locktimes[willitem["tx"].locktime] = True
return locktimes.keys() return locktimes.keys()
@staticmethod
def get_lowest_locktimes(locktimes): def get_lowest_locktimes(locktimes):
sorted_timestamp = [] sorted_timestamp = []
sorted_block = [] sorted_block = []
@@ -291,18 +312,22 @@ class Util:
return sorted(sorted_timestamp), sorted(sorted_block) return sorted(sorted_timestamp), sorted(sorted_block)
@staticmethod
def get_lowest_locktimes_from_will(will): def get_lowest_locktimes_from_will(will):
return Util.get_lowest_locktimes(Util.get_locktimes(will)) return Util.get_lowest_locktimes(Util.get_locktimes(will))
@staticmethod
def search_willtx_per_io(will, tx): def search_willtx_per_io(will, tx):
for wid, w in will.items(): for wid, w in will.items():
if Util.cmp_txs(w["tx"], tx["tx"]): if Util.cmp_txs(w["tx"], tx["tx"]):
return wid, w return wid, w
return None, None return None, None
@staticmethod
def invalidate_will(will): def invalidate_will(will):
raise Exception("not implemented") raise Exception("not implemented")
@staticmethod
def get_will_spent_utxos(will): def get_will_spent_utxos(will):
utxos = [] utxos = []
for txid, willitem in will.items(): for txid, willitem in will.items():
@@ -310,6 +335,7 @@ class Util:
return utxos return utxos
@staticmethod
def utxo_to_str(utxo): def utxo_to_str(utxo):
try: try:
return utxo.to_str() return utxo.to_str()
@@ -321,6 +347,7 @@ class Util:
pass pass
return str(utxo) return str(utxo)
@staticmethod
def cmp_utxo(utxoa, utxob): def cmp_utxo(utxoa, utxob):
utxoa = Util.utxo_to_str(utxoa) utxoa = Util.utxo_to_str(utxoa)
utxob = Util.utxo_to_str(utxob) utxob = Util.utxo_to_str(utxob)
@@ -329,21 +356,25 @@ class Util:
else: else:
return False return False
@staticmethod
def in_utxo(utxo, utxos): def in_utxo(utxo, utxos):
for s_u in utxos: for s_u in utxos:
if Util.cmp_utxo(s_u, utxo): if Util.cmp_utxo(s_u, utxo):
return True return True
return False return False
@staticmethod
def txid_in_utxo(txid, utxos): def txid_in_utxo(txid, utxos):
for s_u in utxos: for s_u in utxos:
if s_u.prevout.txid == txid: if s_u.prevout.txid == txid:
return True return True
return False return False
@staticmethod
def cmp_output(outputa, outputb): def cmp_output(outputa, outputb):
return outputa.address == outputb.address and outputa.value == outputb.value return outputa.address == outputb.address and outputa.value == outputb.value
@staticmethod
def in_output(output, outputs): def in_output(output, outputs):
for s_o in outputs: for s_o in outputs:
if Util.cmp_output(s_o, output): if Util.cmp_output(s_o, output):
@@ -355,6 +386,7 @@ class Util:
# return true false same amount different address # return true false same amount different address
# return false false different amount, different address not found # return false false different amount, different address not found
@staticmethod
def din_output(out, outputs): def din_output(out, outputs):
same_amount = [] same_amount = []
for s_o in outputs: for s_o in outputs:
@@ -370,6 +402,7 @@ class Util:
else: else:
return False, False return False, False
@staticmethod
def get_change_output(wallet, in_amount, out_amount, fee): def get_change_output(wallet, in_amount, out_amount, fee):
change_amount = int(in_amount - out_amount - fee) change_amount = int(in_amount - out_amount - fee)
if change_amount > wallet.dust_threshold(): if change_amount > wallet.dust_threshold():
@@ -380,6 +413,7 @@ class Util:
out.is_change = True out.is_change = True
return out return out
@staticmethod
def get_current_height(network): def get_current_height(network):
# if no network or not up to date, just set locktime to zero # if no network or not up to date, just set locktime to zero
if not network: if not network:
@@ -401,6 +435,7 @@ class Util:
height = min(chain_height, server_height) height = min(chain_height, server_height)
return height return height
@staticmethod
def print_var(var, name="", veryverbose=False): def print_var(var, name="", veryverbose=False):
print(f"---{name}---") print(f"---{name}---")
if var is not None: if var is not None:
@@ -435,6 +470,7 @@ class Util:
print(f"---end {name}---") print(f"---end {name}---")
@staticmethod
def print_utxo(utxo, name=""): def print_utxo(utxo, name=""):
print(f"---utxo-{name}---") print(f"---utxo-{name}---")
Util.print_var(utxo, name) Util.print_var(utxo, name)
@@ -446,36 +482,20 @@ class Util:
print("_TxInput__value_sats:", utxo._TxInput__value_sats) print("_TxInput__value_sats:", utxo._TxInput__value_sats)
print(f"---utxo-end {name}---") print(f"---utxo-end {name}---")
@staticmethod
def print_prevout(prevout, name=""): def print_prevout(prevout, name=""):
print(f"---prevout-{name}---") print(f"---prevout-{name}---")
Util.print_var(prevout, f"{name}-prevout") Util.print_var(prevout, f"{name}-prevout")
Util.print_var(prevout._asdict()) Util.print_var(prevout._asdict())
print(f"---prevout-end {name}---") print(f"---prevout-end {name}---")
def export_meta_gui(electrum_window, title, exporter):
filter_ = "All files (*)"
filename = getSaveFileName(
parent=electrum_window,
title=_("Select file to save your {}".format(title)),
filename="BALplugin_{}".format(title),
filter=filter_,
config=electrum_window.config,
)
if not filename:
return
try:
exporter(filename)
except FileExportFailed as e:
electrum_window.show_critical(str(e))
else:
electrum_window.show_message(
_("Your {0} were exported to '{1}'".format(title, str(filename)))
)
@staticmethod
def copy(dicto, dictfrom): def copy(dicto, dictfrom):
for k, v in dictfrom.items(): for k, v in dictfrom.items():
dicto[k] = v dicto[k] = v
@staticmethod
def fix_will_settings_tx_fees(will_settings): def fix_will_settings_tx_fees(will_settings):
tx_fees = will_settings.get("tx_fees", False) tx_fees = will_settings.get("tx_fees", False)
have_to_update = False have_to_update = False
@@ -485,6 +505,7 @@ class Util:
have_to_update = True have_to_update = True
return have_to_update return have_to_update
@staticmethod
def fix_will_tx_fees(will): def fix_will_tx_fees(will):
have_to_update = False have_to_update = False
for txid, willitem in will.items(): for txid, willitem in will.items():
@@ -495,15 +516,18 @@ class Util:
have_to_update = True have_to_update = True
return have_to_update return have_to_update
@staticmethod
def text_to_hex(text: str) -> str: def text_to_hex(text: str) -> str:
"""Convert text to hexadecimal string""" """Convert text to hexadecimal string"""
hex_string = text.encode('utf-8').hex() hex_string = text.encode('utf-8').hex()
return hex_string return hex_string
@staticmethod
def hex_to_text(hex_string: str) -> str: def hex_to_text(hex_string: str) -> str:
"""Convert hexadecimal string back to text (for verification)""" """Convert hexadecimal string back to text (for verification)"""
try: try:
return bytes.fromhex(hex_string).decode('utf-8') return bytes.fromhex(hex_string).decode('utf-8')
except Exception: except Exception:
return "Error: Invalid hex string" return "Error: Invalid hex string"

50
wallet_util/README.md Normal file
View File

@@ -0,0 +1,50 @@
## README
### Overview
This tool provides two entry points: a CLI script (bal_wallet_utils.py) and a Qt GUI script (bal_wallet_utils_qt.py) that operate against an Electrum source tree.
### Installation / Preparation
1. Copy both files into the Electrum project root (the folder that contains the Electrum source package):
- bal_wallet_utils.py
- bal_wallet_utils_qt.py
2. Activate the Electrum Python environment (the virtualenv used to run Electrum). Example (PowerShell, adjust path to your venv):
```
.\env\Scripts\Activate.ps1
```
or (cmd):
```
env\Scripts\activate.bat
```
### Running
- CLI version:
```
python bal_wallet_utils.py
```
- Qt GUI version:
```
python bal_wallet_utils_qt.py
```
### Building a Windows executable with PyInstaller
From the project root (with the Electrum environment active), you can build the Qt executable using PyInstaller. Example command (adjust the paths if your environment path differs):
```
pyinstaller.exe --onefile --noconsole --add-data "electrum\currencies.json;electrum" --add-data "electrum\bip39_wallet_formats.json;electrum" --add-data "electrum\lnwire\peer_wire.csv;electrum\lnwire" --add-data "electrum\lnwire\onion_wire.csv;electrum\lnwire" --add-binary "env/Lib/site-packages\electrum_ecc\libsecp256k1-6.dll;electrum_ecc" bal_wallet_utils_qt.py
```
Notes:
- Run the command from the project root so relative paths resolve correctly.
- On Windows the --add-data and --add-binary arguments use ";" to separate source and destination.
- If electrum expects additional data files or native DLLs, include them with additional --add-data / --add-binary flags.
- For debugging include --onedir first to inspect the created folder before using --onefile.
### Troubleshooting
- If PyInstaller is not found, run it via Python:
```
python -m PyInstaller <same arguments>
```
- If the frozen exe fails because DLLs or JSON files are missing, add those files explicitly with --add-data or --add-binary.
- Test the build on a clean Windows VM to ensure all runtime dependencies are included.
License and attribution: include your preferred license or attribution details here.

View File

@@ -1,10 +1,11 @@
#!env/bin/python3 #!env/bin/python3
from electrum.storage import WalletStorage
import json
from electrum.util import MyEncoder
import sys
import getpass import getpass
import json
import os import os
import sys
from electrum.storage import WalletStorage
from electrum.util import MyEncoder
default_fees = 100 default_fees = 100
@@ -26,8 +27,11 @@ def fix_will_settings_tx_fees(json_wallet):
def uninstall_bal(json_wallet): def uninstall_bal(json_wallet):
if "will_settings" in json_wallet:
del json_wallet["will_settings"] del json_wallet["will_settings"]
if "will" in json_wallet:
del json_wallet["will"] del json_wallet["will"]
if "heirs" in json_wallet:
del json_wallet["heirs"] del json_wallet["heirs"]
return True return True

View File

@@ -1,30 +1,31 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import sys
import os
import json import json
import os
import sys
from bal_wallet_utils import fix_will_settings_tx_fees, save, uninstall_bal
from electrum.storage import WalletStorage
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QApplication, QApplication,
QMainWindow, QFileDialog,
QVBoxLayout, QGroupBox,
QHBoxLayout, QHBoxLayout,
QLabel, QLabel,
QLineEdit, QLineEdit,
QMainWindow,
QPushButton, QPushButton,
QWidget,
QFileDialog,
QGroupBox,
QTextEdit, QTextEdit,
QVBoxLayout,
QWidget,
) )
from electrum.storage import WalletStorage
from bal_wallet_utils import fix_will_settings_tx_fees, uninstall_bal, save
class WalletUtilityGUI(QMainWindow): class WalletUtilityGUI(QMainWindow):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.initUI() self.init_ui()
def initUI(self): def init_ui(self):
self.setWindowTitle("BAL Wallet Utility") self.setWindowTitle("BAL Wallet Utility")
self.setFixedSize(500, 400) self.setFixedSize(500, 400)

62
will.py
View File

@@ -24,6 +24,7 @@ _logger = get_logger(__name__)
class Will: class Will:
@staticmethod
def get_children(will, willid): def get_children(will, willid):
out = [] out = []
for _id in will: for _id in will:
@@ -35,6 +36,7 @@ class Will:
return out return out
# build a tree with parent transactions # build a tree with parent transactions
@staticmethod
def add_willtree(will): def add_willtree(will):
for willid in will: for willid in will:
will[willid].children = Will.get_children(will, willid) will[willid].children = Will.get_children(will, willid)
@@ -43,14 +45,17 @@ class Will:
will[child[0]].father = willid will[child[0]].father = willid
# return a list of will sorted by locktime # return a list of will sorted by locktime
@staticmethod
def get_sorted_will(will): def get_sorted_will(will):
return sorted(will.items(), key=lambda x: x[1]["tx"].locktime) return sorted(will.items(), key=lambda x: x[1]["tx"].locktime)
@staticmethod
def only_valid(will): def only_valid(will):
for k, v in will.items(): for k, v in will.items():
if v.get_status("VALID"): if v.get_status("VALID"):
yield k yield k
@staticmethod
def search_equal_tx(will, tx, wid): def search_equal_tx(will, tx, wid):
for w in will: for w in will:
if w != wid and not tx.to_json() != will[w]["tx"].to_json(): if w != wid and not tx.to_json() != will[w]["tx"].to_json():
@@ -59,6 +64,7 @@ class Will:
return will[w]["tx"] return will[w]["tx"]
return False return False
@staticmethod
def get_tx_from_any(x): def get_tx_from_any(x):
try: try:
a = str(x) a = str(x)
@@ -69,6 +75,7 @@ class Will:
return x return x
@staticmethod
def add_info_from_will(will, wid, wallet): def add_info_from_will(will, wid, wallet):
if isinstance(will[wid].tx, str): if isinstance(will[wid].tx, str):
will[wid].tx = Will.get_tx_from_any(will[wid].tx) will[wid].tx = Will.get_tx_from_any(will[wid].tx)
@@ -89,7 +96,9 @@ class Will:
txin._TxInput__value_sats = change.value txin._TxInput__value_sats = change.value
txin._trusted_value_sats = change.value txin._trusted_value_sats = change.value
def normalize_will(will, wallet=None, others_inputs={}): @staticmethod
def normalize_will(will, wallet=None, others_inputs=None):
others_input = others_inputs if others_inputs is not None else {}
to_delete = [] to_delete = []
to_add = {} to_add = {}
# add info from wallet # add info from wallet
@@ -138,6 +147,7 @@ class Will:
if wid in will: if wid in will:
del will[wid] del will[wid]
@staticmethod
def new_input(txid, idx, change): def new_input(txid, idx, change):
prevout = TxOutpoint(txid=bfh(txid), out_idx=idx) prevout = TxOutpoint(txid=bfh(txid), out_idx=idx)
inp = PartialTxInput(prevout=prevout) inp = PartialTxInput(prevout=prevout)
@@ -148,16 +158,7 @@ class Will:
inp._TxInput__value_sats = change.value inp._TxInput__value_sats = change.value
return inp return inp
""" @staticmethod
in questa situazione sono presenti due transazioni con id differente(quindi transazioni differenti)
per prima cosa controllo il locktime
se il locktime della nuova transazione e' maggiore del locktime della vecchia transazione, allora
confronto gli eredi, per locktime se corrispondono controllo i willexecutor
se hanno la stessa url ma le fee vecchie sono superiori alle fee nuove, allora anticipare.
"""
def check_anticipate(ow: "WillItem", nw: "WillItem"): def check_anticipate(ow: "WillItem", nw: "WillItem"):
anticipate = Util.anticipate_locktime(ow.tx.locktime, days=1) anticipate = Util.anticipate_locktime(ow.tx.locktime, days=1)
if int(nw.tx.locktime) >= int(anticipate): if int(nw.tx.locktime) >= int(anticipate):
@@ -187,6 +188,7 @@ class Will:
return anticipate return anticipate
return 4294967295 + 1 return 4294967295 + 1
@staticmethod
def change_input(will, otxid, idx, change, others_inputs, to_delete, to_append): def change_input(will, otxid, idx, change, others_inputs, to_delete, to_append):
ow = will[otxid] ow = will[otxid]
ntxid = ow.tx.txid() ntxid = ow.tx.txid()
@@ -227,6 +229,7 @@ class Will:
to_append, to_append,
) )
@staticmethod
def get_all_inputs(will, only_valid=False): def get_all_inputs(will, only_valid=False):
all_inputs = {} all_inputs = {}
for w, wi in will.items(): for w, wi in will.items():
@@ -241,6 +244,7 @@ class Will:
all_inputs[prevout_str].append(inp) all_inputs[prevout_str].append(inp)
return all_inputs return all_inputs
@staticmethod
def get_all_inputs_min_locktime(all_inputs): def get_all_inputs_min_locktime(all_inputs):
all_inputs_min_locktime = {} all_inputs_min_locktime = {}
@@ -255,6 +259,7 @@ class Will:
return all_inputs_min_locktime return all_inputs_min_locktime
@staticmethod
def search_anticipate_rec(will, old_inputs): def search_anticipate_rec(will, old_inputs):
redo = False redo = False
to_delete = [] to_delete = []
@@ -294,6 +299,7 @@ class Will:
Will.search_anticipate_rec(will, old_inputs) Will.search_anticipate_rec(will, old_inputs)
@staticmethod
def update_will(old_will, new_will): def update_will(old_will, new_will):
all_old_inputs = Will.get_all_inputs(old_will, only_valid=True) all_old_inputs = Will.get_all_inputs(old_will, only_valid=True)
# all_inputs_min_locktime = Will.get_all_inputs_min_locktime(all_old_inputs) # all_inputs_min_locktime = Will.get_all_inputs_min_locktime(all_old_inputs)
@@ -320,6 +326,7 @@ class Will:
else: else:
continue continue
@staticmethod
def get_higher_input_for_tx(will): def get_higher_input_for_tx(will):
out = {} out = {}
for wid in will: for wid in will:
@@ -333,6 +340,7 @@ class Will:
out[inp.prevout.to_str()] = inp out[inp.prevout.to_str()] = inp
return out return out
@staticmethod
def invalidate_will(will, wallet, fees_per_byte): def invalidate_will(will, wallet, fees_per_byte):
will_only_valid = Will.only_valid_list(will) will_only_valid = Will.only_valid_list(will)
inputs = Will.get_all_inputs(will_only_valid) inputs = Will.get_all_inputs(will_only_valid)
@@ -384,11 +392,13 @@ class Will:
_logger.debug("len utxo_to_spend <=0") _logger.debug("len utxo_to_spend <=0")
pass pass
@staticmethod
def is_new(will): def is_new(will):
for wid, w in will.items(): for wid, w in will.items():
if w.get_status("VALID") and not w.get_status("COMPLETE"): if w.get_status("VALID") and not w.get_status("COMPLETE"):
return True return True
@staticmethod
def search_rai(all_inputs, all_utxos, will, wallet): def search_rai(all_inputs, all_utxos, will, wallet):
# will_only_valid = Will.only_valid_or_replaced_list(will) # will_only_valid = Will.only_valid_or_replaced_list(will)
for inp, ws in all_inputs.items(): for inp, ws in all_inputs.items():
@@ -422,20 +432,25 @@ class Will:
else: else:
pass pass
@staticmethod
def utxos_strs(utxos): def utxos_strs(utxos):
return [Util.utxo_to_str(u) for u in utxos] return [Util.utxo_to_str(u) for u in utxos]
def set_invalidate(wid, will=[]): @staticmethod
def set_invalidate(wid, will=None):
will = will if will is not None else {}
will[wid].set_status("INVALIDATED", True) will[wid].set_status("INVALIDATED", True)
if will[wid].children: if will[wid].children:
for c in will[wid].children.items(): for c in will[wid].children.items():
Will.set_invalidate(c[0], will) Will.set_invalidate(c[0], will)
@staticmethod
def check_tx_height(tx, wallet): def check_tx_height(tx, wallet):
info = wallet.get_tx_info(tx) info = wallet.get_tx_info(tx)
return info.tx_mined_status.height() return info.tx_mined_status.height()
# check if transactions are stil valid tecnically valid # check if transactions are stil valid tecnically valid
@staticmethod
def check_invalidated(willtree, utxos_list, wallet): def check_invalidated(willtree, utxos_list, wallet):
for wid, w in willtree.items(): for wid, w in willtree.items():
if ( if (
@@ -468,6 +483,7 @@ class Will:
# if wc.children: # if wc.children:
# Will.reflect_to_children(wc) # Will.reflect_to_children(wc)
@staticmethod
def check_amounts(heirs, willexecutors, all_utxos, timestamp_to_check, dust): def check_amounts(heirs, willexecutors, all_utxos, timestamp_to_check, dust):
fixed_heirs, fixed_amount, perc_heirs, perc_amount, fixed_amount_with_dust = ( fixed_heirs, fixed_amount, perc_heirs, perc_amount, fixed_amount_with_dust = (
heirs.fixed_percent_lists_amount(timestamp_to_check, dust, reverse=True) heirs.fixed_percent_lists_amount(timestamp_to_check, dust, reverse=True)
@@ -491,6 +507,7 @@ class Will:
f"Willexecutor{url} excess base fee({wex['base_fee']}), {fixed_amount} >={temp_balance}" f"Willexecutor{url} excess base fee({wex['base_fee']}), {fixed_amount} >={temp_balance}"
) )
@staticmethod
def check_will(will, all_utxos, wallet, block_to_check, timestamp_to_check): def check_will(will, all_utxos, wallet, block_to_check, timestamp_to_check):
Will.add_willtree(will) Will.add_willtree(will)
utxos_list = Will.utxos_strs(all_utxos) utxos_list = Will.utxos_strs(all_utxos)
@@ -507,18 +524,28 @@ class Will:
Will.search_rai(all_inputs, all_utxos, will, wallet) Will.search_rai(all_inputs, all_utxos, will, wallet)
@staticmethod
def get_min_locktime(will,default_value=None):
return min((v.tx.locktime for v in will.values() if v.get_status('VALID')), default=default_value)
@staticmethod
def is_will_valid( def is_will_valid(
will, will,
block_to_check, block_to_check,
timestamp_to_check, timestamp_to_check,
tx_fees, tx_fees,
all_utxos, all_utxos,
heirs={}, heirs=None,
willexecutors={}, willexecutors=None,
self_willexecutor=False, self_willexecutor=False,
wallet=False, wallet=False,
callback_not_valid_tx=None, callback_not_valid_tx=None,
): ):
heirs = heirs if heirs is not None else {}
willexecutors= willexecutors if willexecutors is not None else {}
Will.check_will(will, all_utxos, wallet, block_to_check, timestamp_to_check) Will.check_will(will, all_utxos, wallet, block_to_check, timestamp_to_check)
if heirs: if heirs:
if not Will.check_willexecutors_and_heirs( if not Will.check_willexecutors_and_heirs(
@@ -547,6 +574,7 @@ class Will:
_logger.info("will ok") _logger.info("will ok")
return True return True
@staticmethod
def check_will_expired(all_inputs_min_locktime, block_to_check, timestamp_to_check): def check_will_expired(all_inputs_min_locktime, block_to_check, timestamp_to_check):
_logger.info("check if some transaction is expired") _logger.info("check if some transaction is expired")
for prevout_str, wid in all_inputs_min_locktime.items(): for prevout_str, wid in all_inputs_min_locktime.items():
@@ -578,6 +606,7 @@ class Will:
# if not parentwill or not parentwill.get_status("VALID"): # if not parentwill or not parentwill.get_status("VALID"):
# w[1].set_status("INVALIDATED", True) # w[1].set_status("INVALIDATED", True)
@staticmethod
def only_valid_list(will): def only_valid_list(will):
out = {} out = {}
for wid, w in will.items(): for wid, w in will.items():
@@ -585,6 +614,7 @@ class Will:
out[wid] = w out[wid] = w
return out return out
@staticmethod
def only_valid_or_replaced_list(will): def only_valid_or_replaced_list(will):
out = [] out = []
for wid, w in will.items(): for wid, w in will.items():
@@ -593,6 +623,7 @@ class Will:
out.append(wid) out.append(wid)
return out return out
@staticmethod
def check_willexecutors_and_heirs( def check_willexecutors_and_heirs(
will, heirs, willexecutors, self_willexecutor, check_date, tx_fees will, heirs, willexecutors, self_willexecutor, check_date, tx_fees
): ):
@@ -606,7 +637,7 @@ class Will:
for wid in Will.only_valid_list(will): for wid in Will.only_valid_list(will):
w = will[wid] w = will[wid]
if w.tx_fees != tx_fees: if w.tx_fees != tx_fees:
raise TxFeesChangedException(f"{tx_fees}:", w.tx_fees) raise TxFeesChangedException(f"{tx_fees}: {w.tx_fees}")
for wheir in w.heirs: for wheir in w.heirs:
if not 'w!ll3x3c"' == wheir[:9]: if not 'w!ll3x3c"' == wheir[:9]:
their = will[wid].heirs[wheir] their = will[wid].heirs[wheir]
@@ -655,6 +686,7 @@ class Will:
return True return True
class WillItem(Logger): class WillItem(Logger):
STATUS_DEFAULT = { STATUS_DEFAULT = {
"ANTICIPATED": ["Anticipated", False], "ANTICIPATED": ["Anticipated", False],

View File

@@ -1,9 +1,8 @@
import json import json
from datetime import datetime
import time import time
from datetime import datetime
from aiohttp import ClientResponse from aiohttp import ClientResponse
from electrum import constants
from electrum.i18n import _ from electrum.i18n import _
from electrum.logging import get_logger from electrum.logging import get_logger
from electrum.network import Network from electrum.network import Network
@@ -14,11 +13,12 @@ DEFAULT_TIMEOUT = 5
_logger = get_logger(__name__) _logger = get_logger(__name__)
chainname = constants.net.NET_NAME if constants.net.NET_NAME != "mainnet" else "bitcoin" chainname = BalPlugin.chainname
class Willexecutors: class Willexecutors:
@staticmethod
def save(bal_plugin, willexecutors): def save(bal_plugin, willexecutors):
_logger.debug(f"save {willexecutors},{chainname}") _logger.debug(f"save {willexecutors},{chainname}")
aw = bal_plugin.WILLEXECUTORS.get() aw = bal_plugin.WILLEXECUTORS.get()
@@ -27,6 +27,7 @@ class Willexecutors:
_logger.debug(f"saved: {aw}") _logger.debug(f"saved: {aw}")
# bal_plugin.WILLEXECUTORS.set(willexecutors) # bal_plugin.WILLEXECUTORS.set(willexecutors)
@staticmethod
def get_willexecutors( def get_willexecutors(
bal_plugin, update=False, bal_window=False, force=False, task=True bal_plugin, update=False, bal_window=False, force=False, task=True
): ):
@@ -78,6 +79,7 @@ class Willexecutors:
) )
return w_sorted return w_sorted
@staticmethod
def is_selected(willexecutor, value=None): def is_selected(willexecutor, value=None):
if not willexecutor: if not willexecutor:
return False return False
@@ -89,6 +91,7 @@ class Willexecutors:
willexecutor["selected"] = False willexecutor["selected"] = False
return False return False
@staticmethod
def get_willexecutor_transactions(will, force=False): def get_willexecutor_transactions(will, force=False):
willexecutors = {} willexecutors = {}
for wid, willitem in will.items(): for wid, willitem in will.items():
@@ -124,6 +127,7 @@ class Willexecutors:
# willexecutors[url]["txs"], url # willexecutors[url]["txs"], url
# ) # )
@staticmethod
def send_request( def send_request(
method, url, data=None, *, timeout=10, handle_response=None, count_reply=0 method, url, data=None, *, timeout=10, handle_response=None, count_reply=0
): ):
@@ -177,12 +181,14 @@ class Willexecutors:
_logger.debug(f"--> {response}") _logger.debug(f"--> {response}")
return response return response
@staticmethod
def get_we_url_from_response(resp): def get_we_url_from_response(resp):
url_slices = str(resp.url).split("/") url_slices = str(resp.url).split("/")
if len(url_slices) > 2: if len(url_slices) > 2:
url_slices = url_slices[:-2] url_slices = url_slices[:-2]
return "/".join(url_slices) return "/".join(url_slices)
@staticmethod
async def handle_response(resp: ClientResponse): async def handle_response(resp: ClientResponse):
r = await resp.text() r = await resp.text()
try: try:
@@ -196,9 +202,11 @@ class Willexecutors:
pass pass
return r return r
@staticmethod
class AlreadyPresentException(Exception): class AlreadyPresentException(Exception):
pass pass
@staticmethod
def push_transactions_to_willexecutor(willexecutor): def push_transactions_to_willexecutor(willexecutor):
out = True out = True
try: try:
@@ -224,10 +232,12 @@ class Willexecutors:
return out return out
@staticmethod
def ping_servers(willexecutors): def ping_servers(willexecutors):
for url, we in willexecutors.items(): for url, we in willexecutors.items():
Willexecutors.get_info_task(url, we) Willexecutors.get_info_task(url, we)
@staticmethod
def get_info_task(url, willexecutor): def get_info_task(url, willexecutor):
w = None w = None
try: try:
@@ -248,7 +258,9 @@ class Willexecutors:
willexecutor["last_update"] = datetime.now().timestamp() willexecutor["last_update"] = datetime.now().timestamp()
return willexecutor return willexecutor
def initialize_willexecutor(willexecutor, url, status=None, old_willexecutor={}): @staticmethod
def initialize_willexecutor(willexecutor, url, status=None, old_willexecutor=None):
old_willexecutor=old_willexecutor if old_willexecutor is not None else {}
willexecutor["url"] = url willexecutor["url"] = url
if status is not None: if status is not None:
willexecutor["status"] = status willexecutor["status"] = status
@@ -260,11 +272,13 @@ class Willexecutors:
def download_list(old_willexecutors): @staticmethod
def download_list(old_willexecutors,welist_server):
try: try:
welist_server = welist_server if welist_server[-1] == '/' else welist_server+'/'
willexecutors = Willexecutors.send_request( willexecutors = Willexecutors.send_request(
"get", "get",
f"https://welist.bitcoin-after.life/data/{chainname}?page=0&limit=100", f"{welist_server}data/{chainname}?page=0&limit=100",
) )
# del willexecutors["status"] # del willexecutors["status"]
for w in willexecutors: for w in willexecutors:
@@ -280,6 +294,7 @@ class Willexecutors:
_logger.error(f"Failed to download willexecutors list: {e}") _logger.error(f"Failed to download willexecutors list: {e}")
return {} return {}
@staticmethod
def get_willexecutors_list_from_json(): def get_willexecutors_list_from_json():
try: try:
with open("willexecutors.json") as f: with open("willexecutors.json") as f:
@@ -294,6 +309,7 @@ class Willexecutors:
return {} return {}
@staticmethod
def check_transaction(txid, url): def check_transaction(txid, url):
_logger.debug(f"{url}:{txid}") _logger.debug(f"{url}:{txid}")
try: try:
@@ -305,53 +321,54 @@ class Willexecutors:
_logger.error(f"error contacting {url} for checking txs {e}") _logger.error(f"error contacting {url} for checking txs {e}")
raise e raise e
@staticmethod
def compute_id(willexecutor): def compute_id(willexecutor):
return "{}-{}".format(willexecutor.get("url"), willexecutor.get("chain")) return "{}-{}".format(willexecutor.get("url"), willexecutor.get("chain"))
class WillExecutor: #class WillExecutor:
def __init__( # def __init__(
self, # self,
url, # url,
base_fee, # base_fee,
chain, # chain,
info, # info,
version, # version,
status, # status,
is_selected=False, # is_selected=False,
promo_code="", # promo_code="",
): # ):
self.url = url # self.url = url
self.base_fee = base_fee # self.base_fee = base_fee
self.chain = chain # self.chain = chain
self.info = info # self.info = info
self.version = version # self.version = version
self.status = status # self.status = status
self.promo_code = promo_code # self.promo_code = promo_code
self.is_selected = is_selected # self.is_selected = is_selected
self.id = self.compute_id() # self.id = self.compute_id()
#
def from_dict(d): # def from_dict(d):
return WillExecutor( # return WillExecutor(
url=d.get("url", "http://localhost:8000"), # url=d.get("url", "http://localhost:8000"),
base_fee=d.get("base_fee", 1000), # base_fee=d.get("base_fee", 1000),
chain=d.get("chain", chainname), # chain=d.get("chain", chainname),
info=d.get("info", ""), # info=d.get("info", ""),
version=d.get("version", 0), # version=d.get("version", 0),
status=d.get("status", "Ko"), # status=d.get("status", "Ko"),
is_selected=d.get("is_selected", "False"), # is_selected=d.get("is_selected", "False"),
promo_code=d.get("promo_code", ""), # promo_code=d.get("promo_code", ""),
) # )
#
def to_dict(self): # def to_dict(self):
return { # return {
"url": self.url, # "url": self.url,
"base_fee": self.base_fee, # "base_fee": self.base_fee,
"chain": self.chain, # "chain": self.chain,
"info": self.info, # "info": self.info,
"version": self.version, # "version": self.version,
"promo_code": self.promo_code, # "promo_code": self.promo_code,
} # }
#
def compute_id(self): # def compute_id(self):
return f"{self.url}-{self.chain}" # return f"{self.url}-{self.chain}"