2021-12-24 12:52:08 +00:00
import ' dart:async ' ;
import ' dart:convert ' ;
2023-07-12 23:20:11 +00:00
import ' dart:io ' ;
2022-01-12 13:20:43 +00:00
import ' dart:math ' ;
2023-11-15 23:12:23 +00:00
2024-03-04 19:42:57 +00:00
import ' package:bitcoin_base/bitcoin_base.dart ' ;
2023-10-06 00:38:39 +00:00
import ' package:cw_core/encryption_file_utils.dart ' ;
2021-12-24 12:52:08 +00:00
import ' package:bitcoin_flutter/bitcoin_flutter.dart ' as bitcoin ;
2024-03-04 19:42:57 +00:00
import ' package:bitcoin_base/bitcoin_base.dart ' as bitcoin_base ;
2023-11-15 23:12:23 +00:00
import ' package:collection/collection.dart ' ;
2024-04-08 14:54:58 +00:00
import ' package:cw_bitcoin/address_from_output.dart ' ;
2021-12-24 12:52:08 +00:00
import ' package:cw_bitcoin/bitcoin_address_record.dart ' ;
2024-03-29 18:51:34 +00:00
import ' package:cw_bitcoin/bitcoin_amount_format.dart ' ;
2021-12-24 12:52:08 +00:00
import ' package:cw_bitcoin/bitcoin_transaction_credentials.dart ' ;
import ' package:cw_bitcoin/bitcoin_transaction_priority.dart ' ;
import ' package:cw_bitcoin/bitcoin_unspent.dart ' ;
import ' package:cw_bitcoin/bitcoin_wallet_keys.dart ' ;
2023-11-15 23:12:23 +00:00
import ' package:cw_bitcoin/electrum.dart ' ;
import ' package:cw_bitcoin/electrum_balance.dart ' ;
import ' package:cw_bitcoin/electrum_transaction_history.dart ' ;
import ' package:cw_bitcoin/electrum_transaction_info.dart ' ;
import ' package:cw_bitcoin/electrum_wallet_addresses.dart ' ;
2024-03-29 18:51:34 +00:00
import ' package:cw_bitcoin/exceptions.dart ' ;
2024-03-04 19:42:57 +00:00
import ' package:cw_bitcoin/litecoin_network.dart ' ;
2021-12-24 12:52:08 +00:00
import ' package:cw_bitcoin/pending_bitcoin_transaction.dart ' ;
import ' package:cw_bitcoin/script_hash.dart ' ;
import ' package:cw_bitcoin/utils.dart ' ;
2023-11-15 23:12:23 +00:00
import ' package:cw_core/crypto_currency.dart ' ;
2021-12-24 12:52:08 +00:00
import ' package:cw_core/node.dart ' ;
2023-11-15 23:12:23 +00:00
import ' package:cw_core/pathForWallet.dart ' ;
import ' package:cw_core/pending_transaction.dart ' ;
2021-12-24 12:52:08 +00:00
import ' package:cw_core/sync_status.dart ' ;
2023-11-15 23:12:23 +00:00
import ' package:cw_core/transaction_direction.dart ' ;
2021-12-24 12:52:08 +00:00
import ' package:cw_core/transaction_priority.dart ' ;
2023-11-15 23:12:23 +00:00
import ' package:cw_core/unspent_coins_info.dart ' ;
2023-12-02 01:02:55 +00:00
import ' package:cw_core/utils/file.dart ' ;
2023-11-15 23:12:23 +00:00
import ' package:cw_core/wallet_base.dart ' ;
2021-12-24 12:52:08 +00:00
import ' package:cw_core/wallet_info.dart ' ;
2023-11-15 23:12:23 +00:00
import ' package:flutter/foundation.dart ' ;
import ' package:hive/hive.dart ' ;
import ' package:mobx/mobx.dart ' ;
import ' package:rxdart/subjects.dart ' ;
2024-03-04 19:42:57 +00:00
import ' package:http/http.dart ' as http ;
2021-12-24 12:52:08 +00:00
part ' electrum_wallet.g.dart ' ;
class ElectrumWallet = ElectrumWalletBase with _ $ElectrumWallet ;
2023-10-12 22:50:16 +00:00
abstract class ElectrumWalletBase
extends WalletBase < ElectrumBalance , ElectrumTransactionHistory , ElectrumTransactionInfo >
with Store {
2021-12-24 12:52:08 +00:00
ElectrumWalletBase (
2022-10-12 17:09:57 +00:00
{ required String password ,
2023-10-12 22:50:16 +00:00
required WalletInfo walletInfo ,
required Box < UnspentCoinsInfo > unspentCoinsInfo ,
required this . networkType ,
required this . mnemonic ,
required Uint8List seedBytes ,
2023-10-13 14:29:46 +00:00
required this . encryptionFileUtils ,
2024-04-30 01:16:48 +00:00
this . passphrase ,
2023-10-12 22:50:16 +00:00
List < BitcoinAddressRecord > ? initialAddresses ,
ElectrumClient ? electrumClient ,
ElectrumBalance ? initialBalance ,
CryptoCurrency ? currency } )
: hd = currency = = CryptoCurrency . bch
2023-11-15 23:12:23 +00:00
? bitcoinCashHDWallet ( seedBytes )
Bitcoin derivations (#1089)
* - Update and Fix Conflicts with main
* Add Balances for ERC20 tokens
* Fix conflicts with main
* Add erc20 abi json
* Add send erc20 tokens initial function
* add missing getHeightByDate in Haven [skip ci]
* Allow contacts and wallets from the same tag
* Add Shiba Inu icon
* Add send ERC-20 tokens initial flow
* Add missing import in generated file
* Add initial approach for transaction sending for ERC-20 tokens
* Refactor signing/sending transactions
* Add initial flow for transactions subscription
* Refactor signing/sending transactions
* Add home settings icon
* Fix conflicts with main
* Initial flow for home settings
* Add logic flow for adding erc20 tokens
* Fix initial UI
* Finalize UI for Tokens
* Integrate UI with Ethereum flow
* Add "Enable/Disable" feature for ERC20 tokens
* Add initial Erc20 tokens
* Add Sorting and Pin Native Token features
* Fix price sorting
* Sort tokens list as well when Sort criteria changes
* - Improve sorting balances flow
- Add initial add token from search bar flow
* Fix Accounts Popup UI
* Fix Pin native token
* Fix Enabling/Disabling tokens
Fix sorting by fiat once app is opened
Improve token availability mechanism
* Fix deleting token
Fix renaming tokens
* Fix issue with search
* Add more tokens
* - Fix scroll issue
- Add ERC20 tokens placeholder image in picker
* - Separate and organize default erc20 tokens
- Fix scrolling
- Add token placeholder images in picker
- Sort disabled tokens alphabetically
* Change BNB token initial availability [skip ci]
* Fix Conflicts with main
* Fix Conflicts with main
* Add Verse ERC20 token to the initial tokens list
* Add rename wallet to Ethereum
* Integrate EtherScan API for fetching address transactions
Generate Ethereum specific secrets in Ethereum package
* Adjust transactions fiat price for ERC20 tokens
* Free Up GitHub Actions Ubuntu Runner Disk Space
* Free Up GitHub Actions Ubuntu Runner Disk space (trial 2)
* Fix Transaction Fee display
* Save transaction history
* Enhance loading time for erc20 tokens transactions
* Minor Fixes and Enhancements
* Fix sending erc20
fix block explorer issue
* Fix int overflow
* Fix transaction amount conversions
* Minor: `slow` -> `Slow` [skip-ci]
* initial changes
* more base config stuff
* config changes
* successfully builds!
* save
* successfully add nano wallet
* save
* seed generation
* receive screen + node screen working
* tx history working and fiat fixes
* balance working
* derivation updates
* nano-unfinished
* sends working
* remove fees from send screen, send and receive transactions working
* fixes + auto receive incoming txs
* fix for scanning QR codes
* save
* update translations
* fixes
* more fixes
* more strings
* small fix
* fix github actions workflow
* potential fix
* potential fix
* ci/cd fix
* change rep working
* seed generation fixes
* fixes
* save
* change rep screen functional
* save
* banano changes
* fixes, start adding ui for PoW
* pow node changes
* update translations
* fix
* account changing barely working
* save
* disable account generation
* small fix
* save
* UI work
* save
* fixes after merge main
* fixes
* remove monero stuff, work on derivation ui
* lots of fixes + finish up seed derivation
* last minute fixes
* node related fixes
* more fixes
* small fix
* more fixes
* fixes
* pretty big refactor for pow, still some bugs
* finally works!
* get transactions after send
* fix
* merge conflict fixes
* save
* fix pow node showing up twice
* done
* initial changes
* small fix
* more merge fixes
* fixes
* more fixes
* fix
* save
* fix manage pow nodes setting appearing on other wallets
* fix contact bug
* fixes
* fiat fixes
* save
* save
* save
* save
* updates
* cleanup
* restore fix
* fixes
* remove deprecated alert
* fix
* small fix
* remove outdated warning
* electrum restore fixes
* fixes
* fixes
* fix
* derivation fixes
* nano fixes pt.1
* nano fixes pt.2
* bip39 fixes
* pownode refactor
* nodes pages fixes
* observer fix
* ssl fix
* remove old references
* remove unused imports
* code cleanup
* small fix
* small potential fix
* save
* derivation fixes
* deterministic fix
* fix pt.2
* derivation class fixes
* review fixes from nano that also apply here
* formatting
* stuff that should've stayed deleted
* post merge fixes
* remove problematic imports and duplicate changes
* Delete lib/nano/nano.dart
* move wallet restore page proxy code to the view model
* fix dashboard page indicators being the same color
* debatably better refactoring of derivationInfo, migration needed
* additional refactor improvements
* blanket comment some stuff out to narrow down this issue
* refactor fixes
* fix nano exchange
* fix , bug, i.e. replace , with . when making a nano transaction
* fix nano sending, update restore page wording, and other minor fixes
* write migration for existing bitcoin and nano wallets
* merge fixes
* minor fixes
* use default derivation type when restoring from qr code
* fixes for restoring
* fixes
* fixes
* merge fix
* Fix issues with Creating Electrum and Restoring Bip39
* updates & fixes
* Add missing case for no transactions BIP39 wallet restore
* Make the default BIP39 the 84 derivation path
* Add Samourai Deposit
* litecoin mnemonic error fix
* Bip39 passphrase support (#1412)
* save
* passphrase working
* fix for when loading wallets + translation update
* minor fix
* Fix Nano
* minor fix [skip ci]
---------
Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
* change error state seed conditions into throwables [skip ci]
* litecoin fixes
* Bip39 minor enhancements (#1416)
* minor enhancements
* rename bitcoin_derivations -> electrum_derivations
* Remove duplicate derivations
handle default case
* minor fix
* Enable passphrase for Litecoin
* obscure text of passphrase
---------
Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
Co-authored-by: Justin Ehrenhofer <justin.ehrenhofer@gmail.com>
Co-authored-by: fossephate <fosse@book.local>
2024-04-30 00:49:56 +00:00
: bitcoin . HDWallet . fromSeed ( seedBytes , network: networkType )
. derivePath ( walletInfo . derivationInfo ? . derivationPath ? ? " m/0'/0 " ) ,
2021-12-24 12:52:08 +00:00
syncStatus = NotConnectedSyncStatus ( ) ,
_password = password ,
_feeRates = < int > [ ] ,
_isTransactionUpdating = false ,
2024-01-23 05:15:24 +00:00
isEnabledAutoGenerateSubaddress = true ,
2022-10-12 17:09:57 +00:00
unspentCoins = [ ] ,
_scripthashesUpdateSubject = { } ,
2023-10-12 22:50:16 +00:00
balance = ObservableMap < CryptoCurrency , ElectrumBalance > . of ( currency ! = null
? {
2023-11-15 23:12:23 +00:00
currency:
initialBalance ? ? const ElectrumBalance ( confirmed: 0 , unconfirmed: 0 , frozen: 0 )
}
2023-10-12 22:50:16 +00:00
: { } ) ,
2022-10-12 17:09:57 +00:00
this . unspentCoinsInfo = unspentCoinsInfo ,
2024-03-21 02:51:57 +00:00
this . network = _getNetwork ( networkType , currency ) ,
2024-03-04 19:42:57 +00:00
this . isTestnet = networkType = = bitcoin . testnet ,
2021-12-24 12:52:08 +00:00
super ( walletInfo ) {
this . electrumClient = electrumClient ? ? ElectrumClient ( ) ;
this . walletInfo = walletInfo ;
transactionHistory =
2023-04-10 23:16:13 +00:00
ElectrumTransactionHistory (
walletInfo: walletInfo ,
password: password ,
encryptionFileUtils: encryptionFileUtils ) ;
2021-12-24 12:52:08 +00:00
}
2023-10-12 22:50:16 +00:00
static bitcoin . HDWallet bitcoinCashHDWallet ( Uint8List seedBytes ) = >
2023-11-15 23:12:23 +00:00
bitcoin . HDWallet . fromSeed ( seedBytes ) . derivePath ( " m/44'/145'/0'/0 " ) ;
2023-10-12 22:50:16 +00:00
2021-12-24 12:52:08 +00:00
static int estimatedTransactionSize ( int inputsCount , int outputsCounts ) = >
2023-12-13 14:43:26 +00:00
inputsCount * 68 + outputsCounts * 34 + 10 ;
2021-12-24 12:52:08 +00:00
final bitcoin . HDWallet hd ;
final String mnemonic ;
2023-04-10 23:16:13 +00:00
final EncryptionFileUtils encryptionFileUtils ;
Bitcoin derivations (#1089)
* - Update and Fix Conflicts with main
* Add Balances for ERC20 tokens
* Fix conflicts with main
* Add erc20 abi json
* Add send erc20 tokens initial function
* add missing getHeightByDate in Haven [skip ci]
* Allow contacts and wallets from the same tag
* Add Shiba Inu icon
* Add send ERC-20 tokens initial flow
* Add missing import in generated file
* Add initial approach for transaction sending for ERC-20 tokens
* Refactor signing/sending transactions
* Add initial flow for transactions subscription
* Refactor signing/sending transactions
* Add home settings icon
* Fix conflicts with main
* Initial flow for home settings
* Add logic flow for adding erc20 tokens
* Fix initial UI
* Finalize UI for Tokens
* Integrate UI with Ethereum flow
* Add "Enable/Disable" feature for ERC20 tokens
* Add initial Erc20 tokens
* Add Sorting and Pin Native Token features
* Fix price sorting
* Sort tokens list as well when Sort criteria changes
* - Improve sorting balances flow
- Add initial add token from search bar flow
* Fix Accounts Popup UI
* Fix Pin native token
* Fix Enabling/Disabling tokens
Fix sorting by fiat once app is opened
Improve token availability mechanism
* Fix deleting token
Fix renaming tokens
* Fix issue with search
* Add more tokens
* - Fix scroll issue
- Add ERC20 tokens placeholder image in picker
* - Separate and organize default erc20 tokens
- Fix scrolling
- Add token placeholder images in picker
- Sort disabled tokens alphabetically
* Change BNB token initial availability [skip ci]
* Fix Conflicts with main
* Fix Conflicts with main
* Add Verse ERC20 token to the initial tokens list
* Add rename wallet to Ethereum
* Integrate EtherScan API for fetching address transactions
Generate Ethereum specific secrets in Ethereum package
* Adjust transactions fiat price for ERC20 tokens
* Free Up GitHub Actions Ubuntu Runner Disk Space
* Free Up GitHub Actions Ubuntu Runner Disk space (trial 2)
* Fix Transaction Fee display
* Save transaction history
* Enhance loading time for erc20 tokens transactions
* Minor Fixes and Enhancements
* Fix sending erc20
fix block explorer issue
* Fix int overflow
* Fix transaction amount conversions
* Minor: `slow` -> `Slow` [skip-ci]
* initial changes
* more base config stuff
* config changes
* successfully builds!
* save
* successfully add nano wallet
* save
* seed generation
* receive screen + node screen working
* tx history working and fiat fixes
* balance working
* derivation updates
* nano-unfinished
* sends working
* remove fees from send screen, send and receive transactions working
* fixes + auto receive incoming txs
* fix for scanning QR codes
* save
* update translations
* fixes
* more fixes
* more strings
* small fix
* fix github actions workflow
* potential fix
* potential fix
* ci/cd fix
* change rep working
* seed generation fixes
* fixes
* save
* change rep screen functional
* save
* banano changes
* fixes, start adding ui for PoW
* pow node changes
* update translations
* fix
* account changing barely working
* save
* disable account generation
* small fix
* save
* UI work
* save
* fixes after merge main
* fixes
* remove monero stuff, work on derivation ui
* lots of fixes + finish up seed derivation
* last minute fixes
* node related fixes
* more fixes
* small fix
* more fixes
* fixes
* pretty big refactor for pow, still some bugs
* finally works!
* get transactions after send
* fix
* merge conflict fixes
* save
* fix pow node showing up twice
* done
* initial changes
* small fix
* more merge fixes
* fixes
* more fixes
* fix
* save
* fix manage pow nodes setting appearing on other wallets
* fix contact bug
* fixes
* fiat fixes
* save
* save
* save
* save
* updates
* cleanup
* restore fix
* fixes
* remove deprecated alert
* fix
* small fix
* remove outdated warning
* electrum restore fixes
* fixes
* fixes
* fix
* derivation fixes
* nano fixes pt.1
* nano fixes pt.2
* bip39 fixes
* pownode refactor
* nodes pages fixes
* observer fix
* ssl fix
* remove old references
* remove unused imports
* code cleanup
* small fix
* small potential fix
* save
* derivation fixes
* deterministic fix
* fix pt.2
* derivation class fixes
* review fixes from nano that also apply here
* formatting
* stuff that should've stayed deleted
* post merge fixes
* remove problematic imports and duplicate changes
* Delete lib/nano/nano.dart
* move wallet restore page proxy code to the view model
* fix dashboard page indicators being the same color
* debatably better refactoring of derivationInfo, migration needed
* additional refactor improvements
* blanket comment some stuff out to narrow down this issue
* refactor fixes
* fix nano exchange
* fix , bug, i.e. replace , with . when making a nano transaction
* fix nano sending, update restore page wording, and other minor fixes
* write migration for existing bitcoin and nano wallets
* merge fixes
* minor fixes
* use default derivation type when restoring from qr code
* fixes for restoring
* fixes
* fixes
* merge fix
* Fix issues with Creating Electrum and Restoring Bip39
* updates & fixes
* Add missing case for no transactions BIP39 wallet restore
* Make the default BIP39 the 84 derivation path
* Add Samourai Deposit
* litecoin mnemonic error fix
* Bip39 passphrase support (#1412)
* save
* passphrase working
* fix for when loading wallets + translation update
* minor fix
* Fix Nano
* minor fix [skip ci]
---------
Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
* change error state seed conditions into throwables [skip ci]
* litecoin fixes
* Bip39 minor enhancements (#1416)
* minor enhancements
* rename bitcoin_derivations -> electrum_derivations
* Remove duplicate derivations
handle default case
* minor fix
* Enable passphrase for Litecoin
* obscure text of passphrase
---------
Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
Co-authored-by: Justin Ehrenhofer <justin.ehrenhofer@gmail.com>
Co-authored-by: fossephate <fosse@book.local>
2024-04-30 00:49:56 +00:00
final String ? passphrase ;
2021-12-24 12:52:08 +00:00
2024-01-23 05:15:24 +00:00
@ override
@ observable
bool isEnabledAutoGenerateSubaddress ;
2022-10-12 17:09:57 +00:00
late ElectrumClient electrumClient ;
2021-12-24 12:52:08 +00:00
Box < UnspentCoinsInfo > unspentCoinsInfo ;
@ override
2022-10-12 17:09:57 +00:00
late ElectrumWalletAddresses walletAddresses ;
2021-12-24 12:52:08 +00:00
@ override
@ observable
2022-10-12 17:09:57 +00:00
late ObservableMap < CryptoCurrency , ElectrumBalance > balance ;
2021-12-24 12:52:08 +00:00
@ override
@ observable
SyncStatus syncStatus ;
2024-03-04 19:42:57 +00:00
List < String > get scriptHashes = > walletAddresses . addressesByReceiveType
. map ( ( addr ) = > scriptHash ( addr . address , network: network ) )
2021-12-24 12:52:08 +00:00
. toList ( ) ;
2024-03-04 19:42:57 +00:00
List < String > get publicScriptHashes = > walletAddresses . allAddresses
2023-10-12 22:50:16 +00:00
. where ( ( addr ) = > ! addr . isHidden )
2024-03-04 19:42:57 +00:00
. map ( ( addr ) = > scriptHash ( addr . address , network: network ) )
2023-10-12 22:50:16 +00:00
. toList ( ) ;
2021-12-24 12:52:08 +00:00
2022-10-12 17:09:57 +00:00
String get xpub = > hd . base58 ! ;
2021-12-24 12:52:08 +00:00
@ override
String get seed = > mnemonic ;
2023-03-30 22:33:59 +00:00
@ override
String get password = > _password ;
2021-12-24 12:52:08 +00:00
bitcoin . NetworkType networkType ;
2024-03-04 19:42:57 +00:00
BasedUtxoNetwork network ;
@ override
bool ? isTestnet ;
2021-12-24 12:52:08 +00:00
@ override
2023-10-12 22:50:16 +00:00
BitcoinWalletKeys get keys = >
BitcoinWalletKeys ( wif: hd . wif ! , privateKey: hd . privKey ! , publicKey: hd . pubKey ! ) ;
2021-12-24 12:52:08 +00:00
2022-07-19 14:29:28 +00:00
String _password ;
2021-12-24 12:52:08 +00:00
List < BitcoinUnspent > unspentCoins ;
List < int > _feeRates ;
2022-10-12 17:09:57 +00:00
Map < String , BehaviorSubject < Object > ? > _scripthashesUpdateSubject ;
2021-12-24 12:52:08 +00:00
bool _isTransactionUpdating ;
2023-06-16 01:14:01 +00:00
void Function ( FlutterErrorDetails ) ? _onError ;
2021-12-24 12:52:08 +00:00
Future < void > init ( ) async {
await walletAddresses . init ( ) ;
await transactionHistory . init ( ) ;
await save ( ) ;
}
@ action
@ override
Future < void > startSync ( ) async {
try {
2022-11-09 10:14:21 +00:00
syncStatus = AttemptingSyncStatus ( ) ;
2021-12-24 12:52:08 +00:00
await updateTransactions ( ) ;
_subscribeForUpdates ( ) ;
await updateUnspent ( ) ;
2023-04-20 13:46:41 +00:00
await updateBalance ( ) ;
2024-03-04 19:42:57 +00:00
_feeRates = await electrumClient . feeRates ( network: network ) ;
2021-12-24 12:52:08 +00:00
2023-10-12 22:50:16 +00:00
Timer . periodic (
const Duration ( minutes: 1 ) , ( timer ) async = > _feeRates = await electrumClient . feeRates ( ) ) ;
2021-12-24 12:52:08 +00:00
syncStatus = SyncedSyncStatus ( ) ;
2022-10-12 17:09:57 +00:00
} catch ( e , stacktrace ) {
print ( stacktrace ) ;
2021-12-24 12:52:08 +00:00
print ( e . toString ( ) ) ;
syncStatus = FailedSyncStatus ( ) ;
}
}
@ action
@ override
2022-10-12 17:09:57 +00:00
Future < void > connectToNode ( { required Node node } ) async {
2021-12-24 12:52:08 +00:00
try {
syncStatus = ConnectingSyncStatus ( ) ;
await electrumClient . connectToUri ( node . uri ) ;
electrumClient . onConnectionStatusChange = ( bool isConnected ) {
if ( ! isConnected ) {
syncStatus = LostConnectionSyncStatus ( ) ;
}
} ;
syncStatus = ConnectedSyncStatus ( ) ;
} catch ( e ) {
print ( e . toString ( ) ) ;
syncStatus = FailedSyncStatus ( ) ;
}
}
2024-04-08 14:54:58 +00:00
int get _dustAmount = > 546 ;
2024-03-29 18:51:34 +00:00
2024-04-08 14:54:58 +00:00
bool _isBelowDust ( int amount ) = > amount < = _dustAmount & & network ! = BitcoinNetwork . testnet ;
2024-03-29 18:51:34 +00:00
Future < EstimatedTxResult > estimateSendAllTx (
List < BitcoinOutput > outputs ,
int feeRate , {
String ? memo ,
int credentialsAmount = 0 ,
} ) async {
2024-03-04 19:42:57 +00:00
final utxos = < UtxoWithAddress > [ ] ;
List < ECPrivate > privateKeys = [ ] ;
2024-03-29 18:51:34 +00:00
int allInputsAmount = 0 ;
2021-12-24 12:52:08 +00:00
2024-04-26 19:29:31 +00:00
bool spendsUnconfirmedTX = false ;
2024-03-04 19:42:57 +00:00
for ( int i = 0 ; i < unspentCoins . length ; i + + ) {
final utx = unspentCoins [ i ] ;
2021-12-24 12:52:08 +00:00
2024-04-26 19:29:31 +00:00
if ( utx . isSending & & ! utx . isFrozen ) {
if ( ! spendsUnconfirmedTX ) spendsUnconfirmedTX = utx . confirmations = = 0 ;
2021-12-24 12:52:08 +00:00
allInputsAmount + = utx . value ;
2024-03-04 19:42:57 +00:00
2024-03-21 02:51:57 +00:00
final address = addressTypeFromStr ( utx . address , network ) ;
2024-03-04 19:42:57 +00:00
final privkey = generateECPrivate (
hd: utx . bitcoinAddressRecord . isHidden ? walletAddresses . sideHd : walletAddresses . mainHd ,
index: utx . bitcoinAddressRecord . index ,
network: network ) ;
privateKeys . add ( privkey ) ;
utxos . add (
UtxoWithAddress (
utxo: BitcoinUtxo (
txHash: utx . hash ,
value: BigInt . from ( utx . value ) ,
vout: utx . vout ,
scriptType: _getScriptType ( address ) ,
) ,
2024-03-29 18:51:34 +00:00
ownerDetails: UtxoAddressDetails (
publicKey: privkey . getPublic ( ) . toHex ( ) ,
address: address ,
) ,
2024-03-04 19:42:57 +00:00
) ,
) ;
2021-12-24 12:52:08 +00:00
}
}
2024-03-04 19:42:57 +00:00
if ( utxos . isEmpty ) {
2021-12-24 12:52:08 +00:00
throw BitcoinTransactionNoInputsException ( ) ;
}
2024-03-29 18:51:34 +00:00
int estimatedSize ;
if ( network is BitcoinCashNetwork ) {
estimatedSize = ForkedTransactionBuilder . estimateTransactionSize (
utxos: utxos ,
outputs: outputs ,
network: network as BitcoinCashNetwork ,
memo: memo ,
) ;
} else {
estimatedSize = BitcoinTransactionBuilder . estimateTransactionSize (
utxos: utxos ,
outputs: outputs ,
network: network ,
memo: memo ,
) ;
}
int fee = feeAmountWithFeeRate ( feeRate , 0 , 0 , size: estimatedSize ) ;
2021-12-24 12:52:08 +00:00
2024-03-29 18:51:34 +00:00
if ( fee = = 0 ) {
throw BitcoinTransactionNoFeeException ( ) ;
2024-03-04 19:42:57 +00:00
}
2021-12-24 12:52:08 +00:00
2024-03-29 18:51:34 +00:00
// Here, when sending all, the output amount equals to the input value - fee to fully spend every input on the transaction and have no amount left for change
int amount = allInputsAmount - fee ;
2022-07-28 17:03:16 +00:00
2024-04-26 19:29:31 +00:00
if ( amount < = 0 ) {
throw BitcoinTransactionWrongBalanceException ( ) ;
}
2024-03-29 18:51:34 +00:00
// Attempting to send less than the dust limit
if ( _isBelowDust ( amount ) ) {
throw BitcoinTransactionNoDustException ( ) ;
}
2021-12-24 12:52:08 +00:00
2024-03-29 18:51:34 +00:00
if ( credentialsAmount > 0 ) {
final amountLeftForFee = amount - credentialsAmount ;
if ( amountLeftForFee > 0 & & _isBelowDust ( amountLeftForFee ) ) {
amount - = amountLeftForFee ;
fee + = amountLeftForFee ;
2021-12-24 12:52:08 +00:00
}
2024-03-04 19:42:57 +00:00
}
2021-12-24 12:52:08 +00:00
2024-03-29 18:51:34 +00:00
outputs [ outputs . length - 1 ] =
BitcoinOutput ( address: outputs . last . address , value: BigInt . from ( amount ) ) ;
return EstimatedTxResult (
2024-03-28 12:41:11 +00:00
utxos: utxos ,
2024-03-29 18:51:34 +00:00
privateKeys: privateKeys ,
fee: fee ,
amount: amount ,
isSendAll: true ,
hasChange: false ,
2024-03-28 12:41:11 +00:00
memo: memo ,
2024-04-26 19:29:31 +00:00
spendsUnconfirmedTX: spendsUnconfirmedTX ,
2024-03-28 12:41:11 +00:00
) ;
2024-03-29 18:51:34 +00:00
}
2022-07-28 17:03:16 +00:00
2024-03-29 18:51:34 +00:00
Future < EstimatedTxResult > estimateTxForAmount (
int credentialsAmount ,
List < BitcoinOutput > outputs ,
int feeRate , {
int ? inputsCount ,
String ? memo ,
2024-04-26 19:29:31 +00:00
bool ? useUnconfirmed ,
2024-03-29 18:51:34 +00:00
} ) async {
final utxos = < UtxoWithAddress > [ ] ;
List < ECPrivate > privateKeys = [ ] ;
int allInputsAmount = 0 ;
2024-04-26 19:29:31 +00:00
bool spendsUnconfirmedTX = false ;
2021-12-24 12:52:08 +00:00
2024-03-29 18:51:34 +00:00
int leftAmount = credentialsAmount ;
2024-04-26 19:29:31 +00:00
final sendingCoins = unspentCoins . where ( ( utx ) = > utx . isSending & & ! utx . isFrozen ) . toList ( ) ;
final unconfirmedCoins = sendingCoins . where ( ( utx ) = > utx . confirmations = = 0 ) . toList ( ) ;
2021-12-24 12:52:08 +00:00
2024-03-29 18:51:34 +00:00
for ( int i = 0 ; i < sendingCoins . length ; i + + ) {
final utx = sendingCoins [ i ] ;
2022-07-28 17:03:16 +00:00
2024-04-26 19:29:31 +00:00
final isUncormirmed = utx . confirmations = = 0 ;
if ( useUnconfirmed ! = true & & isUncormirmed ) continue ;
if ( ! spendsUnconfirmedTX ) spendsUnconfirmedTX = isUncormirmed ;
2024-03-29 18:51:34 +00:00
allInputsAmount + = utx . value ;
leftAmount = leftAmount - utx . value ;
final address = addressTypeFromStr ( utx . address , network ) ;
final privkey = generateECPrivate (
hd: utx . bitcoinAddressRecord . isHidden ? walletAddresses . sideHd : walletAddresses . mainHd ,
index: utx . bitcoinAddressRecord . index ,
network: network ) ;
privateKeys . add ( privkey ) ;
utxos . add (
UtxoWithAddress (
utxo: BitcoinUtxo (
txHash: utx . hash ,
value: BigInt . from ( utx . value ) ,
vout: utx . vout ,
scriptType: _getScriptType ( address ) ,
) ,
ownerDetails: UtxoAddressDetails (
publicKey: privkey . getPublic ( ) . toHex ( ) ,
address: address ,
) ,
) ,
) ;
bool amountIsAcquired = leftAmount < = 0 ;
if ( ( inputsCount = = null & & amountIsAcquired ) | | inputsCount = = i + 1 ) {
break ;
2022-07-28 17:03:16 +00:00
}
2024-03-29 18:51:34 +00:00
}
if ( utxos . isEmpty ) {
throw BitcoinTransactionNoInputsException ( ) ;
}
final spendingAllCoins = sendingCoins . length = = utxos . length ;
2024-04-26 19:29:31 +00:00
final spendingAllConfirmedCoins =
! spendsUnconfirmedTX & & utxos . length = = sendingCoins . length - unconfirmedCoins . length ;
2024-03-29 18:51:34 +00:00
// How much is being spent - how much is being sent
int amountLeftForChangeAndFee = allInputsAmount - credentialsAmount ;
if ( amountLeftForChangeAndFee < = 0 ) {
2024-04-26 19:29:31 +00:00
if ( ! spendingAllCoins ) {
return estimateTxForAmount (
credentialsAmount ,
outputs ,
feeRate ,
inputsCount: utxos . length + 1 ,
memo: memo ,
useUnconfirmed: useUnconfirmed ? ? spendingAllConfirmedCoins ,
) ;
}
2024-03-29 18:51:34 +00:00
throw BitcoinTransactionWrongBalanceException ( ) ;
}
final changeAddress = await walletAddresses . getChangeAddress ( ) ;
final address = addressTypeFromStr ( changeAddress , network ) ;
outputs . add ( BitcoinOutput (
address: address ,
value: BigInt . from ( amountLeftForChangeAndFee ) ,
) ) ;
int estimatedSize ;
if ( network is BitcoinCashNetwork ) {
estimatedSize = ForkedTransactionBuilder . estimateTransactionSize (
utxos: utxos ,
outputs: outputs ,
network: network as BitcoinCashNetwork ,
memo: memo ,
) ;
2024-03-04 19:42:57 +00:00
} else {
2024-03-29 18:51:34 +00:00
estimatedSize = BitcoinTransactionBuilder . estimateTransactionSize (
utxos: utxos ,
outputs: outputs ,
network: network ,
memo: memo ,
) ;
}
int fee = feeAmountWithFeeRate ( feeRate , 0 , 0 , size: estimatedSize ) ;
if ( fee = = 0 ) {
throw BitcoinTransactionNoFeeException ( ) ;
}
int amount = credentialsAmount ;
final lastOutput = outputs . last ;
final amountLeftForChange = amountLeftForChangeAndFee - fee ;
if ( ! _isBelowDust ( amountLeftForChange ) ) {
// Here, lastOutput already is change, return the amount left without the fee to the user's address.
2024-03-04 19:42:57 +00:00
outputs [ outputs . length - 1 ] =
2024-03-29 18:51:34 +00:00
BitcoinOutput ( address: lastOutput . address , value: BigInt . from ( amountLeftForChange ) ) ;
} else {
// If has change that is lower than dust, will end up with tx rejected by network rules, so estimate again without the added change
outputs . removeLast ( ) ;
// Still has inputs to spend before failing
if ( ! spendingAllCoins ) {
return estimateTxForAmount (
credentialsAmount ,
outputs ,
feeRate ,
inputsCount: utxos . length + 1 ,
memo: memo ,
2024-04-26 19:29:31 +00:00
useUnconfirmed: useUnconfirmed ? ? spendingAllConfirmedCoins ,
2024-03-29 18:51:34 +00:00
) ;
}
final estimatedSendAll = await estimateSendAllTx (
outputs ,
feeRate ,
memo: memo ,
) ;
if ( estimatedSendAll . amount = = credentialsAmount ) {
return estimatedSendAll ;
}
// Estimate to user how much is needed to send to cover the fee
2024-04-08 14:54:58 +00:00
final maxAmountWithReturningChange = allInputsAmount - _dustAmount - fee - 1 ;
2024-03-29 18:51:34 +00:00
throw BitcoinTransactionNoDustOnChangeException (
bitcoinAmountToString ( amount: maxAmountWithReturningChange ) ,
bitcoinAmountToString ( amount: estimatedSendAll . amount ) ,
) ;
}
// Attempting to send less than the dust limit
if ( _isBelowDust ( amount ) ) {
throw BitcoinTransactionNoDustException ( ) ;
2021-12-24 12:52:08 +00:00
}
final totalAmount = amount + fee ;
2024-03-04 19:42:57 +00:00
if ( totalAmount > balance [ currency ] ! . confirmed ) {
2024-03-29 18:51:34 +00:00
throw BitcoinTransactionWrongBalanceException ( ) ;
2021-12-24 12:52:08 +00:00
}
2024-03-04 19:42:57 +00:00
if ( totalAmount > allInputsAmount ) {
2024-03-29 18:51:34 +00:00
if ( spendingAllCoins ) {
throw BitcoinTransactionWrongBalanceException ( ) ;
2024-03-04 19:42:57 +00:00
} else {
2024-03-29 18:51:34 +00:00
if ( amountLeftForChangeAndFee > fee ) {
2024-03-04 19:42:57 +00:00
outputs . removeLast ( ) ;
2021-12-24 12:52:08 +00:00
}
2024-03-04 19:42:57 +00:00
2024-03-29 18:51:34 +00:00
return estimateTxForAmount (
credentialsAmount ,
outputs ,
feeRate ,
inputsCount: utxos . length + 1 ,
memo: memo ,
2024-04-26 19:29:31 +00:00
useUnconfirmed: useUnconfirmed ? ? spendingAllConfirmedCoins ,
2024-03-29 18:51:34 +00:00
) ;
2021-12-24 12:52:08 +00:00
}
}
2024-03-28 12:41:11 +00:00
return EstimatedTxResult (
utxos: utxos ,
privateKeys: privateKeys ,
fee: fee ,
amount: amount ,
2024-03-29 18:51:34 +00:00
hasChange: true ,
isSendAll: false ,
2024-03-28 12:41:11 +00:00
memo: memo ,
2024-04-26 19:29:31 +00:00
spendsUnconfirmedTX: spendsUnconfirmedTX ,
2024-03-28 12:41:11 +00:00
) ;
2024-03-04 19:42:57 +00:00
}
2021-12-24 12:52:08 +00:00
2024-03-04 19:42:57 +00:00
@ override
Future < PendingTransaction > createTransaction ( Object credentials ) async {
try {
final outputs = < BitcoinOutput > [ ] ;
final transactionCredentials = credentials as BitcoinTransactionCredentials ;
final hasMultiDestination = transactionCredentials . outputs . length > 1 ;
final sendAll = ! hasMultiDestination & & transactionCredentials . outputs . first . sendAll ;
2024-03-29 18:51:34 +00:00
final memo = transactionCredentials . outputs . first . memo ;
2021-12-24 12:52:08 +00:00
2024-03-29 18:51:34 +00:00
int credentialsAmount = 0 ;
2021-12-24 12:52:08 +00:00
2024-03-04 19:42:57 +00:00
for ( final out in transactionCredentials . outputs ) {
2024-03-29 18:51:34 +00:00
final outputAmount = out . formattedCryptoAmount ! ;
2021-12-24 12:52:08 +00:00
2024-03-29 18:51:34 +00:00
if ( ! sendAll & & _isBelowDust ( outputAmount ) ) {
throw BitcoinTransactionNoDustException ( ) ;
}
2022-07-28 17:03:16 +00:00
2024-03-04 19:42:57 +00:00
if ( hasMultiDestination ) {
2024-03-29 18:51:34 +00:00
if ( out . sendAll ) {
throw BitcoinTransactionWrongBalanceException ( ) ;
2024-03-04 19:42:57 +00:00
}
2024-03-29 18:51:34 +00:00
}
2023-06-16 01:14:01 +00:00
2024-03-29 18:51:34 +00:00
credentialsAmount + = outputAmount ;
2021-12-24 12:52:08 +00:00
2024-03-29 18:51:34 +00:00
final address =
addressTypeFromStr ( out . isParsedAddress ? out . extractedAddress ! : out . address , network ) ;
if ( sendAll ) {
// The value will be changed after estimating the Tx size and deducting the fee from the total to be sent
outputs . add ( BitcoinOutput ( address: address , value: BigInt . from ( 0 ) ) ) ;
2024-03-04 19:42:57 +00:00
} else {
2024-03-29 18:51:34 +00:00
outputs . add ( BitcoinOutput ( address: address , value: BigInt . from ( outputAmount ) ) ) ;
2024-03-04 19:42:57 +00:00
}
}
2021-12-24 12:52:08 +00:00
2024-03-29 18:51:34 +00:00
final feeRateInt = transactionCredentials . feeRate ! = null
? transactionCredentials . feeRate !
: feeRate ( transactionCredentials . priority ! ) ;
EstimatedTxResult estimatedTx ;
if ( sendAll ) {
estimatedTx = await estimateSendAllTx (
outputs ,
feeRateInt ,
memo: memo ,
credentialsAmount: credentialsAmount ,
) ;
} else {
estimatedTx = await estimateTxForAmount (
credentialsAmount ,
outputs ,
feeRateInt ,
memo: memo ,
) ;
}
2021-12-24 12:52:08 +00:00
2024-03-29 18:51:34 +00:00
BasedBitcoinTransacationBuilder txb ;
if ( network is BitcoinCashNetwork ) {
txb = ForkedTransactionBuilder (
2024-03-04 19:42:57 +00:00
utxos: estimatedTx . utxos ,
outputs: outputs ,
fee: BigInt . from ( estimatedTx . fee ) ,
2024-03-29 18:51:34 +00:00
network: network ,
memo: estimatedTx . memo ,
outputOrdering: BitcoinOrdering . none ,
2024-04-26 19:29:31 +00:00
enableRBF: ! estimatedTx . spendsUnconfirmedTX ,
2024-03-29 18:51:34 +00:00
) ;
} else {
txb = BitcoinTransactionBuilder (
utxos: estimatedTx . utxos ,
outputs: outputs ,
fee: BigInt . from ( estimatedTx . fee ) ,
network: network ,
memo: estimatedTx . memo ,
outputOrdering: BitcoinOrdering . none ,
2024-04-26 19:29:31 +00:00
enableRBF: ! estimatedTx . spendsUnconfirmedTX ,
2024-03-29 18:51:34 +00:00
) ;
}
2024-03-04 19:42:57 +00:00
2024-04-01 13:31:14 +00:00
bool hasTaprootInputs = false ;
2024-03-04 19:42:57 +00:00
final transaction = txb . buildTransaction ( ( txDigest , utxo , publicKey , sighash ) {
final key = estimatedTx . privateKeys
. firstWhereOrNull ( ( element ) = > element . getPublic ( ) . toHex ( ) = = publicKey ) ;
2021-12-24 12:52:08 +00:00
2024-03-04 19:42:57 +00:00
if ( key = = null ) {
throw Exception ( " Cannot find private key " ) ;
}
if ( utxo . utxo . isP2tr ( ) ) {
2024-04-01 13:31:14 +00:00
hasTaprootInputs = true ;
2024-03-04 19:42:57 +00:00
return key . signTapRoot ( txDigest , sighash: sighash ) ;
} else {
return key . signInput ( txDigest , sigHash: sighash ) ;
}
2021-12-24 12:52:08 +00:00
} ) ;
2024-03-04 19:42:57 +00:00
2024-04-01 13:31:14 +00:00
return PendingBitcoinTransaction (
transaction ,
type ,
electrumClient: electrumClient ,
amount: estimatedTx . amount ,
fee: estimatedTx . fee ,
feeRate: feeRateInt . toString ( ) ,
network: network ,
hasChange: estimatedTx . hasChange ,
isSendAll: estimatedTx . isSendAll ,
hasTaprootInputs: hasTaprootInputs ,
) . . addListener ( ( transaction ) async {
2024-03-04 19:42:57 +00:00
transactionHistory . addOne ( transaction ) ;
await updateBalance ( ) ;
} ) ;
} catch ( e ) {
throw e ;
}
2021-12-24 12:52:08 +00:00
}
String toJSON ( ) = > json . encode ( {
2023-11-15 23:12:23 +00:00
' mnemonic ' : mnemonic ,
Bitcoin derivations (#1089)
* - Update and Fix Conflicts with main
* Add Balances for ERC20 tokens
* Fix conflicts with main
* Add erc20 abi json
* Add send erc20 tokens initial function
* add missing getHeightByDate in Haven [skip ci]
* Allow contacts and wallets from the same tag
* Add Shiba Inu icon
* Add send ERC-20 tokens initial flow
* Add missing import in generated file
* Add initial approach for transaction sending for ERC-20 tokens
* Refactor signing/sending transactions
* Add initial flow for transactions subscription
* Refactor signing/sending transactions
* Add home settings icon
* Fix conflicts with main
* Initial flow for home settings
* Add logic flow for adding erc20 tokens
* Fix initial UI
* Finalize UI for Tokens
* Integrate UI with Ethereum flow
* Add "Enable/Disable" feature for ERC20 tokens
* Add initial Erc20 tokens
* Add Sorting and Pin Native Token features
* Fix price sorting
* Sort tokens list as well when Sort criteria changes
* - Improve sorting balances flow
- Add initial add token from search bar flow
* Fix Accounts Popup UI
* Fix Pin native token
* Fix Enabling/Disabling tokens
Fix sorting by fiat once app is opened
Improve token availability mechanism
* Fix deleting token
Fix renaming tokens
* Fix issue with search
* Add more tokens
* - Fix scroll issue
- Add ERC20 tokens placeholder image in picker
* - Separate and organize default erc20 tokens
- Fix scrolling
- Add token placeholder images in picker
- Sort disabled tokens alphabetically
* Change BNB token initial availability [skip ci]
* Fix Conflicts with main
* Fix Conflicts with main
* Add Verse ERC20 token to the initial tokens list
* Add rename wallet to Ethereum
* Integrate EtherScan API for fetching address transactions
Generate Ethereum specific secrets in Ethereum package
* Adjust transactions fiat price for ERC20 tokens
* Free Up GitHub Actions Ubuntu Runner Disk Space
* Free Up GitHub Actions Ubuntu Runner Disk space (trial 2)
* Fix Transaction Fee display
* Save transaction history
* Enhance loading time for erc20 tokens transactions
* Minor Fixes and Enhancements
* Fix sending erc20
fix block explorer issue
* Fix int overflow
* Fix transaction amount conversions
* Minor: `slow` -> `Slow` [skip-ci]
* initial changes
* more base config stuff
* config changes
* successfully builds!
* save
* successfully add nano wallet
* save
* seed generation
* receive screen + node screen working
* tx history working and fiat fixes
* balance working
* derivation updates
* nano-unfinished
* sends working
* remove fees from send screen, send and receive transactions working
* fixes + auto receive incoming txs
* fix for scanning QR codes
* save
* update translations
* fixes
* more fixes
* more strings
* small fix
* fix github actions workflow
* potential fix
* potential fix
* ci/cd fix
* change rep working
* seed generation fixes
* fixes
* save
* change rep screen functional
* save
* banano changes
* fixes, start adding ui for PoW
* pow node changes
* update translations
* fix
* account changing barely working
* save
* disable account generation
* small fix
* save
* UI work
* save
* fixes after merge main
* fixes
* remove monero stuff, work on derivation ui
* lots of fixes + finish up seed derivation
* last minute fixes
* node related fixes
* more fixes
* small fix
* more fixes
* fixes
* pretty big refactor for pow, still some bugs
* finally works!
* get transactions after send
* fix
* merge conflict fixes
* save
* fix pow node showing up twice
* done
* initial changes
* small fix
* more merge fixes
* fixes
* more fixes
* fix
* save
* fix manage pow nodes setting appearing on other wallets
* fix contact bug
* fixes
* fiat fixes
* save
* save
* save
* save
* updates
* cleanup
* restore fix
* fixes
* remove deprecated alert
* fix
* small fix
* remove outdated warning
* electrum restore fixes
* fixes
* fixes
* fix
* derivation fixes
* nano fixes pt.1
* nano fixes pt.2
* bip39 fixes
* pownode refactor
* nodes pages fixes
* observer fix
* ssl fix
* remove old references
* remove unused imports
* code cleanup
* small fix
* small potential fix
* save
* derivation fixes
* deterministic fix
* fix pt.2
* derivation class fixes
* review fixes from nano that also apply here
* formatting
* stuff that should've stayed deleted
* post merge fixes
* remove problematic imports and duplicate changes
* Delete lib/nano/nano.dart
* move wallet restore page proxy code to the view model
* fix dashboard page indicators being the same color
* debatably better refactoring of derivationInfo, migration needed
* additional refactor improvements
* blanket comment some stuff out to narrow down this issue
* refactor fixes
* fix nano exchange
* fix , bug, i.e. replace , with . when making a nano transaction
* fix nano sending, update restore page wording, and other minor fixes
* write migration for existing bitcoin and nano wallets
* merge fixes
* minor fixes
* use default derivation type when restoring from qr code
* fixes for restoring
* fixes
* fixes
* merge fix
* Fix issues with Creating Electrum and Restoring Bip39
* updates & fixes
* Add missing case for no transactions BIP39 wallet restore
* Make the default BIP39 the 84 derivation path
* Add Samourai Deposit
* litecoin mnemonic error fix
* Bip39 passphrase support (#1412)
* save
* passphrase working
* fix for when loading wallets + translation update
* minor fix
* Fix Nano
* minor fix [skip ci]
---------
Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
* change error state seed conditions into throwables [skip ci]
* litecoin fixes
* Bip39 minor enhancements (#1416)
* minor enhancements
* rename bitcoin_derivations -> electrum_derivations
* Remove duplicate derivations
handle default case
* minor fix
* Enable passphrase for Litecoin
* obscure text of passphrase
---------
Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
Co-authored-by: Justin Ehrenhofer <justin.ehrenhofer@gmail.com>
Co-authored-by: fossephate <fosse@book.local>
2024-04-30 00:49:56 +00:00
' passphrase ' : passphrase ? ? ' ' ,
2024-03-04 19:42:57 +00:00
' account_index ' : walletAddresses . currentReceiveAddressIndexByType ,
' change_address_index ' : walletAddresses . currentChangeAddressIndexByType ,
' addresses ' : walletAddresses . allAddresses . map ( ( addr ) = > addr . toJSON ( ) ) . toList ( ) ,
' address_page_type ' : walletInfo . addressPageType = = null
? SegwitAddresType . p2wpkh . toString ( )
: walletInfo . addressPageType . toString ( ) ,
' balance ' : balance [ currency ] ? . toJSON ( ) ,
Bitcoin derivations (#1089)
* - Update and Fix Conflicts with main
* Add Balances for ERC20 tokens
* Fix conflicts with main
* Add erc20 abi json
* Add send erc20 tokens initial function
* add missing getHeightByDate in Haven [skip ci]
* Allow contacts and wallets from the same tag
* Add Shiba Inu icon
* Add send ERC-20 tokens initial flow
* Add missing import in generated file
* Add initial approach for transaction sending for ERC-20 tokens
* Refactor signing/sending transactions
* Add initial flow for transactions subscription
* Refactor signing/sending transactions
* Add home settings icon
* Fix conflicts with main
* Initial flow for home settings
* Add logic flow for adding erc20 tokens
* Fix initial UI
* Finalize UI for Tokens
* Integrate UI with Ethereum flow
* Add "Enable/Disable" feature for ERC20 tokens
* Add initial Erc20 tokens
* Add Sorting and Pin Native Token features
* Fix price sorting
* Sort tokens list as well when Sort criteria changes
* - Improve sorting balances flow
- Add initial add token from search bar flow
* Fix Accounts Popup UI
* Fix Pin native token
* Fix Enabling/Disabling tokens
Fix sorting by fiat once app is opened
Improve token availability mechanism
* Fix deleting token
Fix renaming tokens
* Fix issue with search
* Add more tokens
* - Fix scroll issue
- Add ERC20 tokens placeholder image in picker
* - Separate and organize default erc20 tokens
- Fix scrolling
- Add token placeholder images in picker
- Sort disabled tokens alphabetically
* Change BNB token initial availability [skip ci]
* Fix Conflicts with main
* Fix Conflicts with main
* Add Verse ERC20 token to the initial tokens list
* Add rename wallet to Ethereum
* Integrate EtherScan API for fetching address transactions
Generate Ethereum specific secrets in Ethereum package
* Adjust transactions fiat price for ERC20 tokens
* Free Up GitHub Actions Ubuntu Runner Disk Space
* Free Up GitHub Actions Ubuntu Runner Disk space (trial 2)
* Fix Transaction Fee display
* Save transaction history
* Enhance loading time for erc20 tokens transactions
* Minor Fixes and Enhancements
* Fix sending erc20
fix block explorer issue
* Fix int overflow
* Fix transaction amount conversions
* Minor: `slow` -> `Slow` [skip-ci]
* initial changes
* more base config stuff
* config changes
* successfully builds!
* save
* successfully add nano wallet
* save
* seed generation
* receive screen + node screen working
* tx history working and fiat fixes
* balance working
* derivation updates
* nano-unfinished
* sends working
* remove fees from send screen, send and receive transactions working
* fixes + auto receive incoming txs
* fix for scanning QR codes
* save
* update translations
* fixes
* more fixes
* more strings
* small fix
* fix github actions workflow
* potential fix
* potential fix
* ci/cd fix
* change rep working
* seed generation fixes
* fixes
* save
* change rep screen functional
* save
* banano changes
* fixes, start adding ui for PoW
* pow node changes
* update translations
* fix
* account changing barely working
* save
* disable account generation
* small fix
* save
* UI work
* save
* fixes after merge main
* fixes
* remove monero stuff, work on derivation ui
* lots of fixes + finish up seed derivation
* last minute fixes
* node related fixes
* more fixes
* small fix
* more fixes
* fixes
* pretty big refactor for pow, still some bugs
* finally works!
* get transactions after send
* fix
* merge conflict fixes
* save
* fix pow node showing up twice
* done
* initial changes
* small fix
* more merge fixes
* fixes
* more fixes
* fix
* save
* fix manage pow nodes setting appearing on other wallets
* fix contact bug
* fixes
* fiat fixes
* save
* save
* save
* save
* updates
* cleanup
* restore fix
* fixes
* remove deprecated alert
* fix
* small fix
* remove outdated warning
* electrum restore fixes
* fixes
* fixes
* fix
* derivation fixes
* nano fixes pt.1
* nano fixes pt.2
* bip39 fixes
* pownode refactor
* nodes pages fixes
* observer fix
* ssl fix
* remove old references
* remove unused imports
* code cleanup
* small fix
* small potential fix
* save
* derivation fixes
* deterministic fix
* fix pt.2
* derivation class fixes
* review fixes from nano that also apply here
* formatting
* stuff that should've stayed deleted
* post merge fixes
* remove problematic imports and duplicate changes
* Delete lib/nano/nano.dart
* move wallet restore page proxy code to the view model
* fix dashboard page indicators being the same color
* debatably better refactoring of derivationInfo, migration needed
* additional refactor improvements
* blanket comment some stuff out to narrow down this issue
* refactor fixes
* fix nano exchange
* fix , bug, i.e. replace , with . when making a nano transaction
* fix nano sending, update restore page wording, and other minor fixes
* write migration for existing bitcoin and nano wallets
* merge fixes
* minor fixes
* use default derivation type when restoring from qr code
* fixes for restoring
* fixes
* fixes
* merge fix
* Fix issues with Creating Electrum and Restoring Bip39
* updates & fixes
* Add missing case for no transactions BIP39 wallet restore
* Make the default BIP39 the 84 derivation path
* Add Samourai Deposit
* litecoin mnemonic error fix
* Bip39 passphrase support (#1412)
* save
* passphrase working
* fix for when loading wallets + translation update
* minor fix
* Fix Nano
* minor fix [skip ci]
---------
Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
* change error state seed conditions into throwables [skip ci]
* litecoin fixes
* Bip39 minor enhancements (#1416)
* minor enhancements
* rename bitcoin_derivations -> electrum_derivations
* Remove duplicate derivations
handle default case
* minor fix
* Enable passphrase for Litecoin
* obscure text of passphrase
---------
Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
Co-authored-by: Justin Ehrenhofer <justin.ehrenhofer@gmail.com>
Co-authored-by: fossephate <fosse@book.local>
2024-04-30 00:49:56 +00:00
' derivationTypeIndex ' : walletInfo . derivationInfo ? . derivationType ? . index ,
' derivationPath ' : walletInfo . derivationInfo ? . derivationPath ,
2023-11-15 23:12:23 +00:00
} ) ;
2021-12-24 12:52:08 +00:00
int feeRate ( TransactionPriority priority ) {
2022-01-13 14:48:42 +00:00
try {
if ( priority is BitcoinTransactionPriority ) {
return _feeRates [ priority . raw ] ;
}
2021-12-24 12:52:08 +00:00
2022-01-13 14:48:42 +00:00
return 0 ;
2023-10-12 22:50:16 +00:00
} catch ( _ ) {
2022-01-13 14:48:42 +00:00
return 0 ;
}
2021-12-24 12:52:08 +00:00
}
2024-03-29 18:51:34 +00:00
int feeAmountForPriority ( TransactionPriority priority , int inputsCount , int outputsCount ,
2024-03-04 19:42:57 +00:00
{ int ? size } ) = >
feeRate ( priority ) * ( size ? ? estimatedTransactionSize ( inputsCount , outputsCount ) ) ;
2021-12-24 12:52:08 +00:00
2024-03-04 19:42:57 +00:00
int feeAmountWithFeeRate ( int feeRate , int inputsCount , int outputsCount , { int ? size } ) = >
feeRate * ( size ? ? estimatedTransactionSize ( inputsCount , outputsCount ) ) ;
2022-07-28 17:03:16 +00:00
2021-12-24 12:52:08 +00:00
@ override
2024-03-04 19:42:57 +00:00
int calculateEstimatedFee ( TransactionPriority ? priority , int ? amount ,
{ int ? outputsCount , int ? size } ) {
2021-12-24 12:52:08 +00:00
if ( priority is BitcoinTransactionPriority ) {
2023-10-12 22:50:16 +00:00
return calculateEstimatedFeeWithFeeRate ( feeRate ( priority ) , amount ,
2024-03-04 19:42:57 +00:00
outputsCount: outputsCount , size: size ) ;
2022-07-28 17:03:16 +00:00
}
2021-12-24 12:52:08 +00:00
2022-07-28 17:03:16 +00:00
return 0 ;
}
2021-12-24 12:52:08 +00:00
2024-03-04 19:42:57 +00:00
int calculateEstimatedFeeWithFeeRate ( int feeRate , int ? amount , { int ? outputsCount , int ? size } ) {
if ( size ! = null ) {
return feeAmountWithFeeRate ( feeRate , 0 , 0 , size: size ) ;
}
2022-07-28 17:03:16 +00:00
int inputsCount = 0 ;
2021-12-24 12:52:08 +00:00
2022-07-28 17:03:16 +00:00
if ( amount ! = null ) {
int totalValue = 0 ;
for ( final input in unspentCoins ) {
if ( totalValue > = amount ) {
break ;
2021-12-24 12:52:08 +00:00
}
2022-07-28 17:03:16 +00:00
if ( input . isSending ) {
totalValue + = input . value ;
inputsCount + = 1 ;
2021-12-24 12:52:08 +00:00
}
}
2022-07-28 17:03:16 +00:00
if ( totalValue < amount ) return 0 ;
} else {
for ( final input in unspentCoins ) {
if ( input . isSending ) {
inputsCount + = 1 ;
}
}
2021-12-24 12:52:08 +00:00
}
2022-07-28 17:03:16 +00:00
// If send all, then we have no change value
final _outputsCount = outputsCount ? ? ( amount ! = null ? 2 : 1 ) ;
2023-10-12 22:50:16 +00:00
return feeAmountWithFeeRate ( feeRate , inputsCount , _outputsCount ) ;
2021-12-24 12:52:08 +00:00
}
@ override
Future < void > save ( ) async {
final path = await makePath ( ) ;
2023-04-10 23:16:13 +00:00
await encryptionFileUtils . write ( path: path , password: _password , data: toJSON ( ) ) ;
2021-12-24 12:52:08 +00:00
await transactionHistory . save ( ) ;
}
2023-08-04 17:01:49 +00:00
@ override
2023-07-12 23:20:11 +00:00
Future < void > renameWalletFiles ( String newWalletName ) async {
final currentWalletPath = await pathForWallet ( name: walletInfo . name , type: type ) ;
final currentWalletFile = File ( currentWalletPath ) ;
2023-10-12 22:50:16 +00:00
final currentDirPath = await pathForWalletDir ( name: walletInfo . name , type: type ) ;
2023-07-12 23:20:11 +00:00
final currentTransactionsFile = File ( ' $ currentDirPath / $ transactionsHistoryFileName ' ) ;
// Copies current wallet files into new wallet name's dir and files
if ( currentWalletFile . existsSync ( ) ) {
final newWalletPath = await pathForWallet ( name: newWalletName , type: type ) ;
await currentWalletFile . copy ( newWalletPath ) ;
}
if ( currentTransactionsFile . existsSync ( ) ) {
final newDirPath = await pathForWalletDir ( name: newWalletName , type: type ) ;
await currentTransactionsFile . copy ( ' $ newDirPath / $ transactionsHistoryFileName ' ) ;
}
// Delete old name's dir and files
await Directory ( currentDirPath ) . delete ( recursive: true ) ;
}
2022-07-19 14:29:28 +00:00
@ override
Future < void > changePassword ( String password ) async {
_password = password ;
await save ( ) ;
await transactionHistory . changePassword ( password ) ;
}
2021-12-24 12:52:08 +00:00
@ override
2022-10-12 17:09:57 +00:00
Future < void > rescan ( { required int height } ) async = > throw UnimplementedError ( ) ;
2021-12-24 12:52:08 +00:00
@ override
Future < void > close ( ) async {
try {
2022-10-12 17:09:57 +00:00
await electrumClient . close ( ) ;
2021-12-24 12:52:08 +00:00
} catch ( _ ) { }
}
2023-10-12 22:50:16 +00:00
Future < String > makePath ( ) async = > pathForWallet ( name: walletInfo . name , type: walletInfo . type ) ;
2021-12-24 12:52:08 +00:00
Future < void > updateUnspent ( ) async {
2024-03-04 19:42:57 +00:00
List < BitcoinUnspent > updatedUnspentCoins = [ ] ;
final addressesSet = walletAddresses . allAddresses . map ( ( addr ) = > addr . address ) . toSet ( ) ;
await Future . wait ( walletAddresses . allAddresses . map ( ( address ) = > electrumClient
. getListUnspentWithAddress ( address . address , network )
. then ( ( unspent ) = > Future . forEach < Map < String , dynamic > > ( unspent , ( unspent ) async {
2023-11-15 23:12:23 +00:00
try {
2024-03-04 19:42:57 +00:00
final coin = BitcoinUnspent . fromJSON ( address , unspent ) ;
final tx = await fetchTransactionInfo (
hash: coin . hash , height: 0 , myAddresses: addressesSet ) ;
coin . isChange = tx ? . direction = = TransactionDirection . outgoing ;
2024-04-26 19:29:31 +00:00
coin . confirmations = tx ? . confirmations ;
2024-03-04 19:42:57 +00:00
updatedUnspentCoins . add ( coin ) ;
} catch ( _ ) { }
} ) ) ) ) ;
unspentCoins = updatedUnspentCoins ;
2021-12-24 12:52:08 +00:00
if ( unspentCoinsInfo . isEmpty ) {
unspentCoins . forEach ( ( coin ) = > _addCoinInfo ( coin ) ) ;
return ;
}
if ( unspentCoins . isNotEmpty ) {
unspentCoins . forEach ( ( coin ) {
2024-03-04 19:42:57 +00:00
final coinInfoList = unspentCoinsInfo . values . where ( ( element ) = >
element . walletId . contains ( id ) & &
element . hash . contains ( coin . hash ) & &
element . vout = = coin . vout ) ;
2021-12-24 12:52:08 +00:00
if ( coinInfoList . isNotEmpty ) {
final coinInfo = coinInfoList . first ;
coin . isFrozen = coinInfo . isFrozen ;
coin . isSending = coinInfo . isSending ;
coin . note = coinInfo . note ;
2024-04-26 19:29:31 +00:00
coin . bitcoinAddressRecord . balance + = coinInfo . value ;
2021-12-24 12:52:08 +00:00
} else {
_addCoinInfo ( coin ) ;
}
} ) ;
}
await _refreshUnspentCoinsInfo ( ) ;
}
Future < void > _addCoinInfo ( BitcoinUnspent coin ) async {
final newInfo = UnspentCoinsInfo (
2023-10-12 22:50:16 +00:00
walletId: id ,
hash: coin . hash ,
isFrozen: coin . isFrozen ,
isSending: coin . isSending ,
noteRaw: coin . note ,
address: coin . bitcoinAddressRecord . address ,
value: coin . value ,
vout: coin . vout ,
2023-11-15 23:12:23 +00:00
isChange: coin . isChange ,
2021-12-24 12:52:08 +00:00
) ;
await unspentCoinsInfo . add ( newInfo ) ;
}
Future < void > _refreshUnspentCoinsInfo ( ) async {
try {
final List < dynamic > keys = < dynamic > [ ] ;
2023-10-12 22:50:16 +00:00
final currentWalletUnspentCoins =
2023-11-15 23:12:23 +00:00
unspentCoinsInfo . values . where ( ( element ) = > element . walletId . contains ( id ) ) ;
2021-12-24 12:52:08 +00:00
if ( currentWalletUnspentCoins . isNotEmpty ) {
currentWalletUnspentCoins . forEach ( ( element ) {
2024-03-04 19:42:57 +00:00
final existUnspentCoins = unspentCoins
. where ( ( coin ) = > element . hash . contains ( coin . hash ) & & element . vout = = coin . vout ) ;
2021-12-24 12:52:08 +00:00
2022-10-12 17:09:57 +00:00
if ( existUnspentCoins . isEmpty ) {
2021-12-24 12:52:08 +00:00
keys . add ( element . key ) ;
}
} ) ;
}
if ( keys . isNotEmpty ) {
await unspentCoinsInfo . deleteAll ( keys ) ;
}
} catch ( e ) {
print ( e . toString ( ) ) ;
}
}
2024-04-08 14:54:58 +00:00
Future < bool > canReplaceByFee ( String hash ) async {
final verboseTransaction = await electrumClient . getTransactionRaw ( hash: hash ) ;
final confirmations = verboseTransaction [ ' confirmations ' ] as int ? ? ? 0 ;
final transactionHex = verboseTransaction [ ' hex ' ] as String ? ;
if ( confirmations > 0 ) return false ;
if ( transactionHex = = null ) {
return false ;
}
final original = bitcoin . Transaction . fromHex ( transactionHex ) ;
return original . ins
. any ( ( element ) = > element . sequence ! = null & & element . sequence ! < 4294967293 ) ;
}
Future < bool > isChangeSufficientForFee ( String txId , int newFee ) async {
final bundle = await getTransactionExpanded ( hash: txId ) ;
final outputs = bundle . originalTransaction . outputs ;
final changeAddresses = walletAddresses . allAddresses . where ( ( element ) = > element . isHidden ) ;
// look for a change address in the outputs
final changeOutput = outputs . firstWhereOrNull ( ( output ) = > changeAddresses . any (
( element ) = > element . address = = addressFromOutputScript ( output . scriptPubKey , network ) ) ) ;
var allInputsAmount = 0 ;
for ( int i = 0 ; i < bundle . originalTransaction . inputs . length ; i + + ) {
final input = bundle . originalTransaction . inputs [ i ] ;
final inputTransaction = bundle . ins [ i ] ;
final vout = input . txIndex ;
final outTransaction = inputTransaction . outputs [ vout ] ;
allInputsAmount + = outTransaction . amount . toInt ( ) ;
}
int totalOutAmount = bundle . originalTransaction . outputs
. fold < int > ( 0 , ( previousValue , element ) = > previousValue + element . amount . toInt ( ) ) ;
var currentFee = allInputsAmount - totalOutAmount ;
int remainingFee = ( newFee - currentFee > 0 ) ? newFee - currentFee : newFee ;
return changeOutput ! = null & & changeOutput . amount . toInt ( ) - remainingFee > = 0 ;
}
Future < PendingBitcoinTransaction > replaceByFee ( String hash , int newFee ) async {
try {
final bundle = await getTransactionExpanded ( hash: hash ) ;
final utxos = < UtxoWithAddress > [ ] ;
List < ECPrivate > privateKeys = [ ] ;
var allInputsAmount = 0 ;
// Add inputs
for ( var i = 0 ; i < bundle . originalTransaction . inputs . length ; i + + ) {
final input = bundle . originalTransaction . inputs [ i ] ;
final inputTransaction = bundle . ins [ i ] ;
final vout = input . txIndex ;
final outTransaction = inputTransaction . outputs [ vout ] ;
final address = addressFromOutputScript ( outTransaction . scriptPubKey , network ) ;
allInputsAmount + = outTransaction . amount . toInt ( ) ;
final addressRecord =
walletAddresses . allAddresses . firstWhere ( ( element ) = > element . address = = address ) ;
final btcAddress = addressTypeFromStr ( addressRecord . address , network ) ;
final privkey = generateECPrivate (
hd: addressRecord . isHidden ? walletAddresses . sideHd : walletAddresses . mainHd ,
index: addressRecord . index ,
network: network ) ;
privateKeys . add ( privkey ) ;
utxos . add (
UtxoWithAddress (
utxo: BitcoinUtxo (
txHash: input . txId ,
value: outTransaction . amount ,
vout: vout ,
scriptType: _getScriptType ( btcAddress ) ,
) ,
ownerDetails:
UtxoAddressDetails ( publicKey: privkey . getPublic ( ) . toHex ( ) , address: btcAddress ) ,
) ,
) ;
}
int totalOutAmount = bundle . originalTransaction . outputs
. fold < int > ( 0 , ( previousValue , element ) = > previousValue + element . amount . toInt ( ) ) ;
var currentFee = allInputsAmount - totalOutAmount ;
int remainingFee = newFee - currentFee ;
final outputs = < BitcoinOutput > [ ] ;
// Add outputs and deduct the fees from it
for ( int i = bundle . originalTransaction . outputs . length - 1 ; i > = 0 ; i - - ) {
final out = bundle . originalTransaction . outputs [ i ] ;
final address = addressFromOutputScript ( out . scriptPubKey , network ) ;
final btcAddress = addressTypeFromStr ( address , network ) ;
int newAmount ;
if ( out . amount . toInt ( ) > = remainingFee ) {
newAmount = out . amount . toInt ( ) - remainingFee ;
remainingFee = 0 ;
// if new amount of output is less than dust amount, then don't add this output as well
if ( newAmount < = _dustAmount ) {
continue ;
}
} else {
remainingFee - = out . amount . toInt ( ) ;
continue ;
}
outputs . add ( BitcoinOutput ( address: btcAddress , value: BigInt . from ( newAmount ) ) ) ;
}
final changeAddresses = walletAddresses . allAddresses . where ( ( element ) = > element . isHidden ) ;
// look for a change address in the outputs
final changeOutput = outputs . firstWhereOrNull ( ( output ) = >
changeAddresses . any ( ( element ) = > element . address = = output . address . toAddress ( network ) ) ) ;
// deduct the change amount from the output amount
if ( changeOutput ! = null ) {
totalOutAmount - = changeOutput . value . toInt ( ) ;
}
final txb = BitcoinTransactionBuilder (
utxos: utxos ,
outputs: outputs ,
fee: BigInt . from ( newFee ) ,
network: network ,
enableRBF: true ,
) ;
final transaction = txb . buildTransaction ( ( txDigest , utxo , publicKey , sighash ) {
final key =
privateKeys . firstWhereOrNull ( ( element ) = > element . getPublic ( ) . toHex ( ) = = publicKey ) ;
if ( key = = null ) {
throw Exception ( " Cannot find private key " ) ;
}
if ( utxo . utxo . isP2tr ( ) ) {
return key . signTapRoot ( txDigest , sighash: sighash ) ;
} else {
return key . signInput ( txDigest , sigHash: sighash ) ;
}
} ) ;
return PendingBitcoinTransaction (
transaction ,
type ,
electrumClient: electrumClient ,
amount: totalOutAmount ,
fee: newFee ,
network: network ,
hasChange: changeOutput ! = null ,
feeRate: newFee . toString ( ) ,
) . . addListener ( ( transaction ) async {
transactionHistory . addOne ( transaction ) ;
await updateBalance ( ) ;
} ) ;
} catch ( e ) {
throw e ;
}
}
Future < ElectrumTransactionBundle > getTransactionExpanded ( { required String hash } ) async {
2024-03-04 19:42:57 +00:00
String transactionHex ;
int ? time ;
int confirmations = 0 ;
if ( network = = BitcoinNetwork . testnet ) {
// Testnet public electrum server does not support verbose transaction fetching
transactionHex = await electrumClient . getTransactionHex ( hash: hash ) ;
final status = json . decode (
( await http . get ( Uri . parse ( " https://blockstream.info/testnet/api/tx/ $ hash /status " ) ) ) . body ) ;
time = status [ " block_time " ] as int ? ;
final tip = await electrumClient . getCurrentBlockChainTip ( ) ? ? 0 ;
confirmations = tip - ( status [ " block_height " ] as int ? ? ? 0 ) ;
} else {
final verboseTransaction = await electrumClient . getTransactionRaw ( hash: hash ) ;
transactionHex = verboseTransaction [ ' hex ' ] as String ;
time = verboseTransaction [ ' time ' ] as int ? ;
confirmations = verboseTransaction [ ' confirmations ' ] as int ? ? ? 0 ;
}
final original = bitcoin_base . BtcTransaction . fromRaw ( transactionHex ) ;
final ins = < bitcoin_base . BtcTransaction > [ ] ;
for ( final vin in original . inputs ) {
final txHex = await electrumClient . getTransactionHex ( hash: vin . txId ) ;
final tx = bitcoin_base . BtcTransaction . fromRaw ( txHex ) ;
2022-01-17 15:33:51 +00:00
ins . add ( tx ) ;
}
2024-04-08 14:54:58 +00:00
return ElectrumTransactionBundle (
original ,
ins: ins ,
time: time ,
confirmations: confirmations ,
) ;
2022-01-17 15:33:51 +00:00
}
2022-10-17 20:55:41 +00:00
Future < ElectrumTransactionInfo ? > fetchTransactionInfo (
2024-03-04 19:42:57 +00:00
{ required String hash ,
required int height ,
required Set < String > myAddresses ,
bool ? retryOnFailure } ) async {
2023-10-12 22:50:16 +00:00
try {
2024-03-04 19:42:57 +00:00
return ElectrumTransactionInfo . fromElectrumBundle (
2024-04-08 14:54:58 +00:00
await getTransactionExpanded ( hash: hash ) , walletInfo . type , network ,
2024-03-04 19:42:57 +00:00
addresses: myAddresses , height: height ) ;
} catch ( e ) {
if ( e is FormatException & & retryOnFailure = = true ) {
await Future . delayed ( const Duration ( seconds: 2 ) ) ;
return fetchTransactionInfo ( hash: hash , height: height , myAddresses: myAddresses ) ;
}
2023-10-12 22:50:16 +00:00
return null ;
}
2021-12-24 12:52:08 +00:00
}
@ override
Future < Map < String , ElectrumTransactionInfo > > fetchTransactions ( ) async {
2024-01-23 05:15:24 +00:00
try {
2024-03-04 19:42:57 +00:00
final Map < String , ElectrumTransactionInfo > historiesWithDetails = { } ;
final addressesSet = walletAddresses . allAddresses . map ( ( addr ) = > addr . address ) . toSet ( ) ;
final currentHeight = await electrumClient . getCurrentBlockChainTip ( ) ? ? 0 ;
await Future . wait ( ADDRESS_TYPES . map ( ( type ) {
final addressesByType = walletAddresses . allAddresses . where ( ( addr ) = > addr . type = = type ) ;
return Future . wait ( addressesByType . map ( ( addressRecord ) async {
final history = await _fetchAddressHistory ( addressRecord , addressesSet , currentHeight ) ;
2024-04-26 16:18:35 +00:00
final balance = await electrumClient . getBalance ( addressRecord . scriptHash ! ) ;
2024-03-04 19:42:57 +00:00
if ( history . isNotEmpty ) {
addressRecord . txCount = history . length ;
2024-04-26 16:18:35 +00:00
addressRecord . balance = balance [ ' confirmed ' ] as int ? ? ? 0 ;
2024-03-04 19:42:57 +00:00
historiesWithDetails . addAll ( history ) ;
final matchedAddresses =
addressesByType . where ( ( addr ) = > addr . isHidden = = addressRecord . isHidden ) ;
final isLastUsedAddress =
history . isNotEmpty & & addressRecord . address = = matchedAddresses . last . address ;
if ( isLastUsedAddress ) {
await walletAddresses . discoverAddresses (
matchedAddresses . toList ( ) ,
addressRecord . isHidden ,
( address , addressesSet ) = >
_fetchAddressHistory ( address , addressesSet , currentHeight )
. then ( ( history ) = > history . isNotEmpty ? address . address : null ) ,
type: type ) ;
}
2024-01-23 05:15:24 +00:00
}
2024-03-04 19:42:57 +00:00
} ) ) ;
} ) ) ;
2024-01-23 05:15:24 +00:00
2024-03-04 19:42:57 +00:00
return historiesWithDetails ;
} catch ( e ) {
print ( e . toString ( ) ) ;
return { } ;
}
}
2024-01-23 05:15:24 +00:00
2024-03-04 19:42:57 +00:00
Future < Map < String , ElectrumTransactionInfo > > _fetchAddressHistory (
BitcoinAddressRecord addressRecord , Set < String > addressesSet , int currentHeight ) async {
try {
final Map < String , ElectrumTransactionInfo > historiesWithDetails = { } ;
final history = await electrumClient
. getHistory ( addressRecord . scriptHash ? ? addressRecord . updateScriptHash ( network ) ) ;
if ( history . isNotEmpty ) {
addressRecord . setAsUsed ( ) ;
await Future . wait ( history . map ( ( transaction ) async {
final txid = transaction [ ' tx_hash ' ] as String ;
final height = transaction [ ' height ' ] as int ;
final storedTx = transactionHistory . transactions [ txid ] ;
if ( storedTx ! = null ) {
if ( height > 0 ) {
storedTx . height = height ;
// the tx's block itself is the first confirmation so add 1
storedTx . confirmations = currentHeight - height + 1 ;
storedTx . isPending = storedTx . confirmations = = 0 ;
}
historiesWithDetails [ txid ] = storedTx ;
} else {
final tx = await fetchTransactionInfo (
hash: txid , height: height , myAddresses: addressesSet , retryOnFailure: true ) ;
if ( tx ! = null ) {
historiesWithDetails [ txid ] = tx ;
// Got a new transaction fetched, add it to the transaction history
// instead of waiting all to finish, and next time it will be faster
transactionHistory . addOne ( tx ) ;
await transactionHistory . save ( ) ;
}
}
2024-01-23 05:15:24 +00:00
return Future . value ( null ) ;
2024-03-04 19:42:57 +00:00
} ) ) ;
}
2024-01-23 05:15:24 +00:00
2024-03-04 19:42:57 +00:00
return historiesWithDetails ;
2024-01-23 05:15:24 +00:00
} catch ( e ) {
print ( e . toString ( ) ) ;
return { } ;
}
2021-12-24 12:52:08 +00:00
}
Future < void > updateTransactions ( ) async {
try {
if ( _isTransactionUpdating ) {
return ;
}
_isTransactionUpdating = true ;
2024-03-04 19:42:57 +00:00
await fetchTransactions ( ) ;
2022-01-24 12:04:23 +00:00
walletAddresses . updateReceiveAddresses ( ) ;
2021-12-24 12:52:08 +00:00
_isTransactionUpdating = false ;
2022-10-17 20:55:41 +00:00
} catch ( e , stacktrace ) {
print ( stacktrace ) ;
2021-12-24 12:52:08 +00:00
print ( e ) ;
_isTransactionUpdating = false ;
}
}
void _subscribeForUpdates ( ) {
scriptHashes . forEach ( ( sh ) async {
await _scripthashesUpdateSubject [ sh ] ? . close ( ) ;
_scripthashesUpdateSubject [ sh ] = electrumClient . scripthashUpdate ( sh ) ;
2022-01-17 15:33:51 +00:00
_scripthashesUpdateSubject [ sh ] ? . listen ( ( event ) async {
2021-12-24 12:52:08 +00:00
try {
await updateUnspent ( ) ;
2023-04-20 13:46:41 +00:00
await updateBalance ( ) ;
2021-12-24 12:52:08 +00:00
await updateTransactions ( ) ;
2023-06-16 01:14:01 +00:00
} catch ( e , s ) {
2021-12-24 12:52:08 +00:00
print ( e . toString ( ) ) ;
2023-06-16 01:14:01 +00:00
_onError ? . call ( FlutterErrorDetails (
exception: e ,
stack: s ,
library : this . runtimeType . toString ( ) ,
) ) ;
2021-12-24 12:52:08 +00:00
}
} ) ;
} ) ;
}
Future < ElectrumBalance > _fetchBalances ( ) async {
2024-03-04 19:42:57 +00:00
final addresses = walletAddresses . allAddresses . toList ( ) ;
2022-01-18 16:27:33 +00:00
final balanceFutures = < Future < Map < String , dynamic > > > [ ] ;
for ( var i = 0 ; i < addresses . length ; i + + ) {
2023-11-15 23:12:23 +00:00
final addressRecord = addresses [ i ] ;
2024-03-04 19:42:57 +00:00
final sh = scriptHash ( addressRecord . address , network: network ) ;
2022-01-18 16:27:33 +00:00
final balanceFuture = electrumClient . getBalance ( sh ) ;
balanceFutures . add ( balanceFuture ) ;
2023-04-20 13:46:41 +00:00
}
var totalFrozen = 0 ;
unspentCoinsInfo . values . forEach ( ( info ) {
unspentCoins . forEach ( ( element ) {
2023-10-12 22:50:16 +00:00
if ( element . hash = = info . hash & &
2024-03-04 19:42:57 +00:00
element . vout = = info . vout & &
2023-10-12 22:50:16 +00:00
info . isFrozen & &
element . bitcoinAddressRecord . address = = info . address & &
element . value = = info . value ) {
2023-04-20 13:46:41 +00:00
totalFrozen + = element . value ;
}
} ) ;
} ) ;
2022-01-18 16:27:33 +00:00
final balances = await Future . wait ( balanceFutures ) ;
var totalConfirmed = 0 ;
var totalUnconfirmed = 0 ;
for ( var i = 0 ; i < balances . length ; i + + ) {
final addressRecord = addresses [ i ] ;
final balance = balances [ i ] ;
2022-10-12 17:09:57 +00:00
final confirmed = balance [ ' confirmed ' ] as int ? ? ? 0 ;
final unconfirmed = balance [ ' unconfirmed ' ] as int ? ? ? 0 ;
2022-01-18 16:27:33 +00:00
totalConfirmed + = confirmed ;
totalUnconfirmed + = unconfirmed ;
if ( confirmed > 0 | | unconfirmed > 0 ) {
addressRecord . setAsUsed ( ) ;
}
}
2023-10-12 22:50:16 +00:00
return ElectrumBalance (
confirmed: totalConfirmed , unconfirmed: totalUnconfirmed , frozen: totalFrozen ) ;
2021-12-24 12:52:08 +00:00
}
2023-04-20 13:46:41 +00:00
Future < void > updateBalance ( ) async {
2022-03-30 15:57:04 +00:00
balance [ currency ] = await _fetchBalances ( ) ;
2021-12-24 12:52:08 +00:00
await save ( ) ;
}
2022-01-12 13:20:43 +00:00
String getChangeAddress ( ) {
const minCountOfHiddenAddresses = 5 ;
final random = Random ( ) ;
2024-03-04 19:42:57 +00:00
var addresses = walletAddresses . allAddresses . where ( ( addr ) = > addr . isHidden ) . toList ( ) ;
2022-01-12 13:20:43 +00:00
if ( addresses . length < minCountOfHiddenAddresses ) {
2024-03-04 19:42:57 +00:00
addresses = walletAddresses . allAddresses . toList ( ) ;
2022-01-12 13:20:43 +00:00
}
return addresses [ random . nextInt ( addresses . length ) ] . address ;
}
2023-06-16 01:14:01 +00:00
@ override
void setExceptionHandler ( void Function ( FlutterErrorDetails ) onError ) = > _onError = onError ;
2023-09-14 19:14:49 +00:00
@ override
String signMessage ( String message , { String ? address = null } ) {
final index = address ! = null
2024-03-04 19:42:57 +00:00
? walletAddresses . allAddresses . firstWhere ( ( element ) = > element . address = = address ) . index
2023-09-14 19:14:49 +00:00
: null ;
2023-12-08 14:05:52 +00:00
final HD = index = = null ? hd : hd . derive ( index ) ;
return base64Encode ( HD . signMessage ( message ) ) ;
2023-09-14 19:14:49 +00:00
}
2024-03-21 02:51:57 +00:00
static BasedUtxoNetwork _getNetwork ( bitcoin . NetworkType networkType , CryptoCurrency ? currency ) {
if ( networkType = = bitcoin . bitcoin & & currency = = CryptoCurrency . bch ) {
return BitcoinCashNetwork . mainnet ;
}
if ( networkType = = litecoinNetwork ) {
return LitecoinNetwork . mainnet ;
}
if ( networkType = = bitcoin . testnet ) {
return BitcoinNetwork . testnet ;
}
return BitcoinNetwork . mainnet ;
}
2021-12-24 12:52:08 +00:00
}
2024-03-04 19:42:57 +00:00
class EstimateTxParams {
EstimateTxParams (
{ required this . amount ,
required this . feeRate ,
required this . priority ,
required this . outputsCount ,
required this . size } ) ;
final int amount ;
final int feeRate ;
final TransactionPriority priority ;
final int outputsCount ;
final int size ;
}
class EstimatedTxResult {
2024-03-28 12:41:11 +00:00
EstimatedTxResult ( {
required this . utxos ,
required this . privateKeys ,
required this . fee ,
required this . amount ,
2024-03-29 18:51:34 +00:00
required this . hasChange ,
required this . isSendAll ,
2024-03-28 12:41:11 +00:00
this . memo ,
2024-04-26 19:29:31 +00:00
required this . spendsUnconfirmedTX ,
2024-03-28 12:41:11 +00:00
} ) ;
2024-03-04 19:42:57 +00:00
final List < UtxoWithAddress > utxos ;
final List < ECPrivate > privateKeys ;
final int fee ;
final int amount ;
2024-03-29 18:51:34 +00:00
final bool hasChange ;
final bool isSendAll ;
2024-03-28 12:41:11 +00:00
final String ? memo ;
2024-04-26 19:29:31 +00:00
final bool spendsUnconfirmedTX ;
2024-03-04 19:42:57 +00:00
}
2024-03-21 02:51:57 +00:00
BitcoinBaseAddress addressTypeFromStr ( String address , BasedUtxoNetwork network ) {
2024-03-29 18:51:34 +00:00
if ( network is BitcoinCashNetwork ) {
if ( ! address . startsWith ( " bitcoincash: " ) & &
( address . startsWith ( " q " ) | | address . startsWith ( " p " ) ) ) {
address = " bitcoincash: $ address " ;
}
return BitcoinCashAddress ( address ) . baseAddress ;
}
2024-03-04 19:42:57 +00:00
if ( P2pkhAddress . regex . hasMatch ( address ) ) {
return P2pkhAddress . fromAddress ( address: address , network: network ) ;
} else if ( P2shAddress . regex . hasMatch ( address ) ) {
return P2shAddress . fromAddress ( address: address , network: network ) ;
} else if ( P2wshAddress . regex . hasMatch ( address ) ) {
return P2wshAddress . fromAddress ( address: address , network: network ) ;
} else if ( P2trAddress . regex . hasMatch ( address ) ) {
return P2trAddress . fromAddress ( address: address , network: network ) ;
} else {
return P2wpkhAddress . fromAddress ( address: address , network: network ) ;
}
}
BitcoinAddressType _getScriptType ( BitcoinBaseAddress type ) {
if ( type is P2pkhAddress ) {
return P2pkhAddressType . p2pkh ;
} else if ( type is P2shAddress ) {
return P2shAddressType . p2wpkhInP2sh ;
} else if ( type is P2wshAddress ) {
return SegwitAddresType . p2wsh ;
} else if ( type is P2trAddress ) {
return SegwitAddresType . p2tr ;
} else {
return SegwitAddresType . p2wpkh ;
}
}