• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

1200wd / bitcoinlib / 13475769857

22 Feb 2025 07:06PM UTC coverage: 90.63% (+0.2%) from 90.459%
13475769857

push

github

Cryp Toon
Show exception when database error occurs

0 of 2 new or added lines in 1 file covered. (0.0%)

10 existing lines in 4 files now uncovered.

7951 of 8773 relevant lines covered (90.63%)

2.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.12
/bitcoinlib/wallets.py
1
# -*- coding: utf-8 -*-
2
#    BitcoinLib - Python Cryptocurrency Library
3
#    WALLETS - HD wallet Class for Key and Transaction management
4
#    © 2016 - 2024 June - 1200 Web Development <http://1200wd.com/>
5
#
6
#    This program is free software: you can redistribute it and/or modify
7
#    it under the terms of the GNU Affero General Public License as
8
#    published by the Free Software Foundation, either version 3 of the
9
#    License, or (at your option) any later version.
10
#
11
#    This program is distributed in the hope that it will be useful,
12
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
13
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
#    GNU Affero General Public License for more details.
15
#
16
#    You should have received a copy of the GNU Affero General Public License
17
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
#
19

20
import json
3✔
21
import random
3✔
22
from itertools import groupby
3✔
23
from operator import itemgetter
3✔
24
import numpy as np
3✔
25
import pickle
3✔
26
from datetime import timedelta
3✔
27
from bitcoinlib.db import *
3✔
28
from bitcoinlib.encoding import *
3✔
29
from bitcoinlib.keys import Address, BKeyError, HDKey, check_network_and_key, path_expand
3✔
30
from bitcoinlib.mnemonic import Mnemonic
3✔
31
from bitcoinlib.networks import Network
3✔
32
from bitcoinlib.values import Value, value_to_satoshi
3✔
33
from bitcoinlib.services.services import Service
3✔
34
from bitcoinlib.transactions import Input, Output, Transaction, get_unlocking_script_type, TransactionError
3✔
35
from bitcoinlib.scripts import Script
3✔
36
from sqlalchemy import func, or_
3✔
37

38
_logger = logging.getLogger(__name__)
3✔
39

40

41
class WalletError(Exception):
3✔
42
    """
43
    Handle Wallet class Exceptions
44

45
    """
46
    def __init__(self, msg=''):
3✔
47
        self.msg = msg
3✔
48
        _logger.error(msg)
3✔
49

50
    def __str__(self):
3✔
51
        return self.msg
3✔
52

53

54
def wallets_list(db_uri=None, include_cosigners=False, db_password=None):
3✔
55
    """
56
    List Wallets from database
57

58
    :param db_uri: URI of the database
59
    :type db_uri: str
60
    :param include_cosigners: Child wallets for multisig wallets are for internal use only and are skipped by default
61
    :type include_cosigners: bool
62
    :param db_password: Password to use for encrypted database. Requires the installation of sqlcipher (see documentation).
63
    :type db_password: str
64

65
    :return dict: Dictionary of wallets defined in database
66
    """
67

68
    session = Db(db_uri=db_uri, password=db_password).session
3✔
69
    wallets = session.query(DbWallet).order_by(DbWallet.id).all()
3✔
70
    wlst = []
3✔
71
    for w in wallets:
3✔
72
        if w.parent_id and not include_cosigners:
3✔
73
            continue
3✔
74
        wlst.append({
3✔
75
            'id': w.id,
76
            'name': w.name,
77
            'owner': w.owner,
78
            'network': w.network_name,
79
            'purpose': w.purpose,
80
            'scheme': w.scheme,
81
            'main_key_id': w.main_key_id,
82
            'parent_id': w.parent_id,
83
        })
84
    session.close()
3✔
85
    return wlst
3✔
86

87

88
def wallet_exists(wallet, db_uri=None, db_password=None):
3✔
89
    """
90
    Check if Wallets is defined in database
91

92
    :param wallet: Wallet ID as integer or Wallet Name as string
93
    :type wallet: int, str
94
    :param db_uri: URI of the database
95
    :type db_uri: str
96
    :param db_password: Password to use for encrypted database. Requires the installation of sqlcipher (see documentation).
97
    :type db_password: str
98

99
    :return bool: True if wallet exists otherwise False
100
    """
101

102
    if wallet in [x['name'] for x in wallets_list(db_uri, db_password=db_password)]:
3✔
103
        return True
3✔
104
    if isinstance(wallet, int) and wallet in [x['id'] for x in wallets_list(db_uri, db_password=db_password)]:
3✔
105
        return True
3✔
106
    return False
3✔
107

108

109
def wallet_create_or_open(
3✔
110
        name, keys='', owner='', network=None, account_id=0, purpose=None, scheme='bip32', sort_keys=True,
111
        password='', witness_type=None, encoding=None, multisig=None, sigs_required=None, cosigner_id=None,
112
        key_path=None, anti_fee_sniping=True, db_uri=None, db_cache_uri=None, db_password=None):
113
    """
114
    Create a wallet with specified options if it doesn't exist, otherwise just open
115

116
    Returns Wallet object
117

118
    See Wallets class create method for option documentation
119
    """
120
    if wallet_exists(name, db_uri=db_uri, db_password=db_password):
3✔
121
        if keys or owner or password or witness_type or key_path:
3✔
122
            _logger.warning("Opening existing wallet, extra options are ignored")
3✔
123
        return Wallet(name, db_uri=db_uri, db_cache_uri=db_cache_uri, db_password=db_password)
3✔
124
    else:
125
        return Wallet.create(name, keys, owner, network, account_id, purpose, scheme, sort_keys,
3✔
126
                             password, witness_type, encoding, multisig, sigs_required, cosigner_id,
127
                             key_path, anti_fee_sniping, db_uri=db_uri, db_cache_uri=db_cache_uri,
128
                             db_password=db_password)
129

130

131
def wallet_delete(wallet, db_uri=None, force=False, db_password=None):
3✔
132
    """
133
    Delete wallet and associated keys and transactions from the database. If wallet has unspent outputs it raises a
134
    WalletError exception unless 'force=True' is specified
135

136
    :param wallet: Wallet ID as integer or Wallet Name as string
137
    :type wallet: int, str
138
    :param db_uri: URI of the database
139
    :type db_uri: str
140
    :param force: If set to True wallet will be deleted even if unspent outputs are found. Default is False
141
    :type force: bool
142
    :param db_password: Password to use for encrypted database. Requires the installation of sqlcipher (see documentation).
143
    :type db_password: str
144

145
    :return int: Number of rows deleted, so 1 if successful
146
    """
147

148
    session = Db(db_uri=db_uri, password=db_password).session
3✔
149
    if isinstance(wallet, int) or wallet.isdigit():
3✔
150
        w = session.query(DbWallet).filter_by(id=wallet)
3✔
151
    else:
152
        w = session.query(DbWallet).filter_by(name=wallet)
3✔
153
    if not w or not w.first():
3✔
154
        session.close()
3✔
155
        raise WalletError("Wallet '%s' not found" % wallet)
3✔
156
    wallet_id = w.first().id
3✔
157

158
    # Delete co-signer wallets if this is a multisig wallet
159
    for cw in session.query(DbWallet).filter_by(parent_id=wallet_id).all():
3✔
160
        wallet_delete(cw.id, db_uri=db_uri, force=force)
3✔
161

162
    # Delete keys from this wallet and update transactions (remove key_id)
163
    ks = session.query(DbKey).filter_by(wallet_id=wallet_id)
3✔
164
    if bool([k for k in ks if k.balance and k.is_private]) and not force:
3✔
165
        session.close()
3✔
166
        raise WalletError("Wallet still has unspent outputs. Use 'force=True' to delete this wallet")
3✔
167
    k_ids = [k.id for k in ks]
3✔
168
    session.query(DbTransactionOutput).filter(DbTransactionOutput.key_id.in_(k_ids)).update(
3✔
169
        {DbTransactionOutput.key_id: None})
170
    session.query(DbTransactionInput).filter(DbTransactionInput.key_id.in_(k_ids)).update(
3✔
171
        {DbTransactionInput.key_id: None})
172
    session.query(DbKeyMultisigChildren).filter(DbKeyMultisigChildren.parent_id.in_(k_ids)).delete()
3✔
173
    session.query(DbKeyMultisigChildren).filter(DbKeyMultisigChildren.child_id.in_(k_ids)).delete()
3✔
174
    ks.delete()
3✔
175

176
    # Delete incomplete transactions from wallet
177
    txs = session.query(DbTransaction).filter_by(wallet_id=wallet_id, is_complete=False)
3✔
178
    for tx in txs:
3✔
179
        session.query(DbTransactionOutput).filter_by(transaction_id=tx.id).delete()
3✔
180
        session.query(DbTransactionInput).filter_by(transaction_id=tx.id).delete()
3✔
181
    txs.delete()
3✔
182

183
    # Unlink transactions from this wallet (remove wallet_id)
184
    session.query(DbTransaction).filter_by(wallet_id=wallet_id).update({DbTransaction.wallet_id: None})
3✔
185

186
    res = w.delete()
3✔
187
    session.commit()
3✔
188
    session.close()
3✔
189

190
    _logger.info("Wallet '%s' deleted" % wallet)
3✔
191

192
    return res
3✔
193

194

195
def wallet_empty(wallet, db_uri=None, db_password=None):
3✔
196
    """
197
    Remove all generated keys and transactions from wallet. Does not delete the wallet itself or the masterkey,
198
    so everything can be recreated.
199

200
    :param wallet: Wallet ID as integer or Wallet Name as string
201
    :type wallet: int, str
202
    :param db_uri: URI of the database
203
    :type db_uri: str
204
    :param db_password: Password to use for encrypted database. Requires the installation of sqlcipher (see documentation).
205
    :type db_password: str
206

207
    :return bool: True if successful
208
    """
209

210
    session = Db(db_uri=db_uri, password=db_password).session
3✔
211
    if isinstance(wallet, int) or wallet.isdigit():
3✔
212
        w = session.query(DbWallet).filter_by(id=wallet)
3✔
213
    else:
214
        w = session.query(DbWallet).filter_by(name=wallet)
3✔
215
    if not w or not w.first():
3✔
216
        session.close()
3✔
217
        raise WalletError("Wallet '%s' not found" % wallet)
3✔
218
    wallet_id = w.first().id
3✔
219

220
    # Delete keys from this wallet and update transactions (remove key_id)
221
    ks = session.query(DbKey).filter(DbKey.wallet_id == wallet_id, DbKey.parent_id != 0)
3✔
222
    for k in ks:
3✔
223
        session.query(DbTransactionOutput).filter_by(key_id=k.id).update({DbTransactionOutput.key_id: None})
3✔
224
        session.query(DbTransactionInput).filter_by(key_id=k.id).update({DbTransactionInput.key_id: None})
3✔
225
        session.query(DbKeyMultisigChildren).filter_by(parent_id=k.id).delete()
3✔
226
        session.query(DbKeyMultisigChildren).filter_by(child_id=k.id).delete()
3✔
227
    ks.delete()
3✔
228

229
    # Delete incomplete transactions from wallet
230
    txs = session.query(DbTransaction).filter_by(wallet_id=wallet_id, is_complete=False)
3✔
231
    for tx in txs:
3✔
232
        session.query(DbTransactionOutput).filter_by(transaction_id=tx.id).delete()
3✔
233
        session.query(DbTransactionInput).filter_by(transaction_id=tx.id).delete()
3✔
234
    txs.delete()
3✔
235

236
    # Unlink transactions from this wallet (remove wallet_id)
237
    session.query(DbTransaction).filter_by(wallet_id=wallet_id).update({DbTransaction.wallet_id: None})
3✔
238

239
    session.commit()
3✔
240
    session.close()
3✔
241

242
    _logger.info("All keys and transactions from wallet '%s' deleted" % wallet)
3✔
243

244
    return True
3✔
245

246

247
def wallet_delete_if_exists(wallet, db_uri=None, force=False, db_password=None):
3✔
248
    """
249
    Delete wallet and associated keys from the database. If wallet has unspent outputs it raises a WalletError exception
250
    unless 'force=True' is specified. If the wallet does not exist return False
251

252
    :param wallet: Wallet ID as integer or Wallet Name as string
253
    :type wallet: int, str
254
    :param db_uri: URI of the database
255
    :type db_uri: str
256
    :param force: If set to True wallet will be deleted even if unspent outputs are found. Default is False
257
    :type force: bool
258
    :param db_password: Password to use for encrypted database. Requires the installation of sqlcipher (see documentation).
259
    :type db_password: str
260

261
    :return int: Number of rows deleted, so 1 if successful
262
    """
263

264
    if wallet_exists(wallet, db_uri, db_password=db_password):
3✔
265
        return wallet_delete(wallet, db_uri, force, db_password=db_password)
3✔
266
    return False
3✔
267

268

269
def normalize_path(path):
3✔
270
    """
271
    Normalize BIP0044 key path for HD keys. Using single quotes for hardened keys
272

273
    >>> normalize_path("m/44h/2p/1'/0/100")
274
    "m/44'/2'/1'/0/100"
275

276
    :param path: BIP0044 key path
277
    :type path: str
278

279
    :return str: Normalized BIP0044 key path with single quotes
280
    """
281

282
    levels = path.split("/")
3✔
283
    npath = ""
3✔
284
    for level in levels:
3✔
285
        if not level:
3✔
286
            raise WalletError("Could not parse path. Index is empty.")
3✔
287
        nlevel = level
3✔
288
        if level[-1] in "'HhPp":
3✔
289
            nlevel = level[:-1] + "'"
3✔
290
        npath += nlevel + "/"
3✔
291
    if npath[-1] == "/":
3✔
292
        npath = npath[:-1]
3✔
293
    return npath
3✔
294

295

296
class WalletKey(object):
3✔
297
    """
298
    Used as attribute of Wallet class. Contains HDKey class, and adds extra wallet related information such as
299
    key ID, name, path and balance.
300

301
    All WalletKeys are stored in a database
302
    """
303

304
    @staticmethod
3✔
305
    def from_key(name, wallet_id, session, key, account_id=0, network=None, change=0, purpose=84, parent_id=0,
3✔
306
                 path='m', key_type=None, encoding=None, witness_type=DEFAULT_WITNESS_TYPE, multisig=False,
307
                 cosigner_id=None, new_key_id=None):
308
        """
309
        Create WalletKey from a HDKey object or key.
310

311
        Normally you don't need to call this method directly. Key creation is handled by the Wallet class.
312

313
        >>> w = wallet_create_or_open('hdwalletkey_test')
314
        >>> wif = 'xprv9s21ZrQH143K2mcs9jcK4EjALbu2z1N9qsMTUG1frmnXM3NNCSGR57yLhwTccfNCwdSQEDftgjCGm96P29wGGcbBsPqZH85iqpoHA7LrqVy'
315
        >>> wk = WalletKey.from_key('import_key', w.wallet_id, w.session, wif)
316
        >>> wk.address
317
        'bc1qukcgc3guzt0a27j7vdegtgwxsrv4khc4688ycs'
318
        >>> wk # doctest:+ELLIPSIS
319
        <WalletKey(key_id=..., name=import_key, wif=zprvAWgYBBk7JR8GjN16pTBZUQvAgYBvsFM9g6Pu33oScnYHTEzphkbYKFHckMNncUg3kug1jAs1c3uNXiKWTYmHs5xPc5EQSwihPGvZwGiZeKD, path=m)>
320

321
        :param name: New key name
322
        :type name: str
323
        :param wallet_id: ID of wallet where to store key
324
        :type wallet_id: int
325
        :param session: Required Sqlalchemy Session object
326
        :type session: sqlalchemy.orm.session.Session
327
        :param key: Optional key in any format accepted by the HDKey class
328
        :type key: str, int, byte, HDKey
329
        :param account_id: Account ID for specified key, default is 0
330
        :type account_id: int
331
        :param network: Network of specified key
332
        :type network: str
333
        :param change: Use 0 for normal key, and 1 for change key (for returned payments)
334
        :type change: int
335
        :param purpose: BIP0044 purpose field, default is 84
336
        :type purpose: int
337
        :param parent_id: Key ID of parent, default is 0 (no parent)
338
        :type parent_id: int
339
        :param path: BIP0044 path of given key, default is 'm' (masterkey)
340
        :type path: str
341
        :param key_type: Type of key, single or BIP44 type
342
        :type key_type: str
343
        :param encoding: Encoding used for address, i.e.: base58 or bech32. Default is base58
344
        :type encoding: str
345
        :param witness_type: Witness type used when creating transaction script: legacy, p2sh-segwit or segwit.
346
        :type witness_type: str
347
        :param multisig: Specify if key is part of multisig wallet, used for create keys and key representations such as WIF and addreses
348
        :type multisig: bool
349
        :param cosigner_id: Set this if you would like to create keys for other cosigners.
350
        :type cosigner_id: int
351
        :param new_key_id: Key ID in database (DbKey.id), use to directly insert key in database without checks and without commiting. Mainly for internal usage, to significantly increase speed when inserting multiple keys.
352
        :type new_key_id: int
353

354
        :return WalletKey: WalletKey object
355
        """
356

357
        key_is_address = False
3✔
358
        if isinstance(key, HDKey):
3✔
359
            k = key
3✔
360
            if network is None:
3✔
361
                network = k.network.name
3✔
362
            elif network != k.network.name:
3✔
363
                raise WalletError("Specified network and key network should be the same")
3✔
364
            witness_type = k.witness_type
3✔
365
        elif isinstance(key, Address):
3✔
366
            k = key
3✔
367
            key_is_address = True
3✔
368
            if network is None:
3✔
369
                network = k.network.name
3✔
370
            elif network != k.network.name:
3✔
371
                raise WalletError("Specified network and key network should be the same")
3✔
372
        else:
373
            if network is None:
3✔
374
                network = DEFAULT_NETWORK
3✔
375
            k = HDKey(import_key=key, network=network, witness_type=witness_type)
3✔
376
        if not encoding and witness_type:
3✔
377
            encoding = get_encoding_from_witness(witness_type)
3✔
378
        script_type = script_type_default(witness_type, multisig)
3✔
379

380
        if not new_key_id:
3✔
381
            key_id_max = session.query(func.max(DbKey.id)).scalar()
3✔
382
            new_key_id = key_id_max + 1 if key_id_max else None
3✔
383
            commit = True
3✔
384
        else:
385
            commit = False
3✔
386

387
        if not key_is_address:
3✔
388
            if key_type != 'single' and k.depth != len(path.split('/'))-1:
3✔
389
                if path == 'm' and k.depth > 1:
3✔
390
                    path = "M"
3✔
391
            address = k.address(encoding=encoding, script_type=script_type)
3✔
392

393
            keyexists = session.query(DbKey).\
3✔
394
                filter(DbKey.wallet_id == wallet_id,
395
                       DbKey.wif == k.wif(witness_type=witness_type, multisig=multisig, is_private=True)).first()
396
            if keyexists:
3✔
397
                _logger.warning("Key already exists in this wallet. Key ID: %d" % keyexists.id)
3✔
398
                return WalletKey(keyexists.id, session, k)
3✔
399

400
            if commit:
3✔
401
                wk = session.query(DbKey).filter(
3✔
402
                    DbKey.wallet_id == wallet_id,
403
                    or_(DbKey.public == k.public_byte,
404
                        DbKey.wif == k.wif(witness_type=witness_type, multisig=multisig, is_private=False),
405
                        DbKey.address == address)).first()
406
                if wk:
3✔
407
                    wk.wif = k.wif(witness_type=witness_type, multisig=multisig, is_private=True)
3✔
408
                    wk.is_private = True
3✔
409
                    wk.private = k.private_byte
3✔
410
                    wk.public = k.public_byte
3✔
411
                    wk.path = path
3✔
412
                    session.commit()
3✔
413
                    return WalletKey(wk.id, session, k)
3✔
414

415
            address_index = k.child_index % 0x80000000
3✔
416
            nk = DbKey(id=new_key_id, name=name[:80], wallet_id=wallet_id, public=k.public_byte, private=k.private_byte, purpose=purpose,
3✔
417
                       account_id=account_id, depth=k.depth, change=change, address_index=address_index,
418
                       wif=k.wif(witness_type=witness_type, multisig=multisig, is_private=True), address=address,
419
                       parent_id=parent_id, compressed=k.compressed, is_private=k.is_private, path=path,
420
                       key_type=key_type, network_name=network, encoding=encoding, cosigner_id=cosigner_id,
421
                       witness_type=witness_type)
422
        else:
423
            keyexists = session.query(DbKey).\
3✔
424
                filter(DbKey.wallet_id == wallet_id,
425
                       DbKey.address == k.address).first()
426
            if keyexists:
3✔
427
                _logger.warning("Key %s with ID %s already exists" % (k.address, keyexists.id))
3✔
428
                return WalletKey(keyexists.id, session, k)
3✔
429
            nk = DbKey(id=new_key_id, name=name[:80], wallet_id=wallet_id, purpose=purpose,
3✔
430
                       account_id=account_id, depth=k.depth, change=change, address=k.address,
431
                       parent_id=parent_id, compressed=k.compressed, is_private=False, path=path,
432
                       key_type=key_type, network_name=network, encoding=encoding, cosigner_id=cosigner_id,
433
                       witness_type=witness_type)
434

435
        if commit:
3✔
436
            session.merge(DbNetwork(name=network))
3✔
437
        session.add(nk)
3✔
438
        if commit:
3✔
439
            session.commit()
3✔
440
        return WalletKey(nk.id, session, k)
3✔
441

442
    def _commit(self):
3✔
443
        try:
3✔
444
            self.session.commit()
3✔
445
        except Exception:
×
446
            self.session.rollback()
×
447
            raise
×
448

449
    def __init__(self, key_id, session, hdkey_object=None):
3✔
450
        """
451
        Initialize WalletKey with specified ID, get information from database.
452

453
        :param key_id: ID of key as mentioned in database
454
        :type key_id: int
455
        :param session: Required Sqlalchemy Session object
456
        :type session: sqlalchemy.orm.session.Session
457
        :param hdkey_object: Optional HDKey object. Specify HDKey object if available for performance
458
        :type hdkey_object: HDKey
459

460
        """
461

462
        self.session = session
3✔
463
        wk = session.query(DbKey).filter_by(id=key_id).first()
3✔
464
        if wk:
3✔
465
            self._dbkey = wk
3✔
466
            self._hdkey_object = hdkey_object
3✔
467
            if hdkey_object and isinstance(hdkey_object, HDKey):
3✔
468
                assert(not wk.public or wk.public == hdkey_object.public_byte)
3✔
469
                assert(not wk.private or wk.private == hdkey_object.private_byte)
3✔
470
                self._hdkey_object = hdkey_object
3✔
471
            self.key_id = key_id
3✔
472
            self._name = wk.name
3✔
473
            self.wallet_id = wk.wallet_id
3✔
474
            self.key_public = None if not wk.public else wk.public
3✔
475
            self.key_private = None if not wk.private else wk.private
3✔
476
            self.account_id = wk.account_id
3✔
477
            self.change = wk.change
3✔
478
            self.address_index = wk.address_index
3✔
479
            self.wif = wk.wif
3✔
480
            self.address = wk.address
3✔
481
            self._balance = wk.balance
3✔
482
            self.purpose = wk.purpose
3✔
483
            self.parent_id = wk.parent_id
3✔
484
            self.is_private = wk.is_private
3✔
485
            self.path = wk.path
3✔
486
            self.wallet = wk.wallet
3✔
487
            self.network_name = wk.network_name
3✔
488
            if not self.network_name:
3✔
489
                self.network_name = wk.wallet.network_name
×
490
            self.network = Network(self.network_name)
3✔
491
            self.depth = wk.depth
3✔
492
            self.key_type = wk.key_type
3✔
493
            self.compressed = wk.compressed
3✔
494
            self.encoding = wk.encoding
3✔
495
            self.cosigner_id = wk.cosigner_id
3✔
496
            self.used = wk.used
3✔
497
            self.witness_type = wk.witness_type
3✔
498
        else:
499
            raise WalletError("Key with id %s not found" % key_id)
3✔
500

501
    def __del__(self):
3✔
502
        self.session.close()
3✔
503

504
    def __repr__(self):
505
        return "<WalletKey(key_id=%d, name=%s, wif=%s, path=%s)>" % (self.key_id, self.name, self.wif, self.path)
506

507
    @property
3✔
508
    def name(self):
3✔
509
        """
510
        Return name of wallet key
511

512
        :return str:
513
        """
514
        return self._name
3✔
515

516
    @name.setter
3✔
517
    def name(self, value):
3✔
518
        """
519
        Set key name, update in database
520

521
        :param value: Name for this key
522
        :type value: str
523

524
        :return str:
525
        """
526

527
        self._name = value
3✔
528
        self._dbkey.name = value
3✔
529
        self._commit()
3✔
530

531
    @property
3✔
532
    def keys_public(self):
3✔
533
        if self.key_type == 'multisig':
×
534
            return [k.public_byte for k in self.key()]
×
535
        else:
536
            return [self.key_public]
×
537

538
    @property
3✔
539
    def keys_private(self):
3✔
540
        if self.key_type == 'multisig':
×
541
            return [k.private_byte for k in self.key() if k.private_byte]
×
542
        else:
543
            return [self.key_private] if self.key_private else []
×
544

545
    def key(self):
3✔
546
        """
547
        Get HDKey object for current WalletKey
548

549
        :return HDKey, list of HDKey:
550
        """
551

552
        self._hdkey_object = None
3✔
553
        if self.key_type == 'multisig':
3✔
554
            self._hdkey_object = []
3✔
555
            for kc in self._dbkey.multisig_children:
3✔
556
                self._hdkey_object.append(HDKey.from_wif(kc.child_key.wif, network=kc.child_key.network_name,
3✔
557
                                                         compressed=self.compressed))
558
        if self._hdkey_object is None and self.wif:
3✔
559
            self._hdkey_object = HDKey.from_wif(self.wif, network=self.network_name, compressed=self.compressed)
3✔
560
        return self._hdkey_object
3✔
561

562
    def balance(self, as_string=False):
3✔
563
        """
564
        Get total value of unspent outputs
565

566
        :param as_string: Specify 'string' to return a string in currency format
567
        :type as_string: bool
568

569
        :return float, str: Key balance
570
        """
571

572
        if as_string:
3✔
573
            return Value.from_satoshi(self._balance, network=self.network).str_unit()
3✔
574
        else:
575
            return self._balance
3✔
576

577
    def public(self):
3✔
578
        """
579
        Return current key as public WalletKey object with all private information removed
580

581
        :return WalletKey:
582
        """
583
        pub_key = self
3✔
584
        pub_key.is_private = False
3✔
585
        pub_key.key_private = None
3✔
586
        if self.key():
3✔
587
            pub_key.wif = self.key().wif()
3✔
588
        if self._hdkey_object:
3✔
589
            self._hdkey_object = pub_key._hdkey_object.public()
3✔
590
        self._dbkey = None
3✔
591
        return pub_key
3✔
592

593
    def as_dict(self, include_private=False):
3✔
594
        """
595
        Return current key information as dictionary
596

597
        :param include_private: Include private key information in dictionary
598
        :type include_private: bool
599

600
        """
601

602
        kdict = {
3✔
603
            'id': self.key_id,
604
            'key_type': self.key_type,
605
            'network': self.network.name,
606
            'is_private': self.is_private,
607
            'name': self.name,
608
            'key_public': '' if not self.key_public else self.key_public.hex(),
609
            'account_id':  self.account_id,
610
            'parent_id': self.parent_id,
611
            'depth': self.depth,
612
            'change': self.change,
613
            'address_index': self.address_index,
614
            'address': self.address,
615
            'encoding': self.encoding,
616
            'path': self.path,
617
            'balance': self.balance(),
618
            'balance_str': self.balance(as_string=True)
619
        }
620
        if include_private:
3✔
621
            kdict.update({
3✔
622
                'key_private': self.key_private.hex(),
623
                'wif': self.wif,
624
            })
625
        return kdict
3✔
626

627

628
class WalletTransaction(Transaction):
3✔
629
    """
630
    Used as attribute of Wallet class. Child of Transaction object with extra reference to
631
    wallet and database object.
632

633
    All WalletTransaction items are stored in a database
634
    """
635

636
    def __init__(self, hdwallet, account_id=None, *args, **kwargs):
3✔
637
        """
638
        Initialize WalletTransaction object with reference to a Wallet object
639

640
        :param hdwallet: Wallet object, wallet name or ID
641
        :type hdWallet: HDwallet, str, int
642
        :param account_id: Account ID
643
        :type account_id: int
644
        :param args: Arguments for HDWallet parent class
645
        :type args: args
646
        :param kwargs: Keyword arguments for Wallet parent class
647
        :type kwargs: kwargs
648
        """
649

650
        assert isinstance(hdwallet, Wallet)
3✔
651
        self.hdwallet = hdwallet
3✔
652
        self.pushed = False
3✔
653
        self.error = None
3✔
654
        self.response_dict = None
3✔
655
        self.account_id = account_id
3✔
656
        if not account_id:
3✔
657
            self.account_id = self.hdwallet.default_account_id
3✔
658
        witness_type = 'legacy'
3✔
659
        if hdwallet.witness_type in ['segwit', 'p2sh-segwit']:
3✔
660
            witness_type = 'segwit'
3✔
661
        Transaction.__init__(self, witness_type=witness_type, *args, **kwargs)
3✔
662
        addresslist = hdwallet.addresslist()
3✔
663
        self.outgoing_tx = bool([i.address for i in self.inputs if i.address in addresslist])
3✔
664
        self.incoming_tx = bool([o.address for o in self.outputs if o.address in addresslist])
3✔
665

666
    def __repr__(self):
667
        return "<WalletTransaction(input_count=%d, output_count=%d, status=%s, network=%s)>" % \
668
               (len(self.inputs), len(self.outputs), self.status, self.network.name)
669

670
    def __deepcopy__(self, memo):
3✔
671
        cls = self.__class__
3✔
672
        result = cls.__new__(cls)
3✔
673
        memo[id(self)] = result
3✔
674
        self_dict = self.__dict__
3✔
675
        for k, v in self_dict.items():
3✔
676
            if k != 'hdwallet':
3✔
677
                setattr(result, k, deepcopy(v, memo))
3✔
678
        result.hdwallet = self.hdwallet
3✔
679
        return result
3✔
680

681
    @classmethod
3✔
682
    def from_transaction(cls, hdwallet, t):
3✔
683
        """
684
        Create WalletTransaction object from Transaction object
685

686
        :param hdwallet: Wallet object, wallet name or ID
687
        :type hdwallet: HDwallet, str, int
688
        :param t: Specify Transaction object
689
        :type t: Transaction
690

691
        :return WalletClass:
692
        """
693
        return cls(hdwallet=hdwallet, inputs=t.inputs, outputs=t.outputs, locktime=t.locktime, version=t.version,
3✔
694
                   network=t.network.name, fee=t.fee, fee_per_kb=t.fee_per_kb, size=t.size, txid=t.txid,
695
                   txhash=t.txhash, date=t.date, confirmations=t.confirmations, block_height=t.block_height,
696
                   block_hash=t.block_hash, input_total=t.input_total, output_total=t.output_total,
697
                   rawtx=t.rawtx, status=t.status, coinbase=t.coinbase, verified=t.verified, flag=t.flag)
698

699
    @classmethod
3✔
700
    def from_txid(cls, hdwallet, txid):
3✔
701
        """
702
        Read single transaction from database with given transaction ID / transaction hash
703

704
        :param hdwallet: Wallet object
705
        :type hdwallet: Wallet
706
        :param txid: Transaction hash as hexadecimal string
707
        :type txid: str, bytes
708

709
        :return WalletClass:
710

711
        """
712
        sess = hdwallet.session
3✔
713
        # If txid is unknown add it to database, else update
714
        db_tx_query = sess.query(DbTransaction). \
3✔
715
            filter(DbTransaction.wallet_id == hdwallet.wallet_id, DbTransaction.txid == to_bytes(txid))
716
        db_tx = db_tx_query.scalar()
3✔
717
        if not db_tx:
3✔
718
            return
3✔
719

720
        fee_per_kb = None
3✔
721
        if db_tx.fee and db_tx.size:
3✔
722
            fee_per_kb = int((db_tx.fee / db_tx.size) * 1000)
3✔
723
        network = Network(db_tx.network_name)
3✔
724

725
        inputs = []
3✔
726
        for inp in db_tx.inputs:
3✔
727
            sequence = 0xffffffff
3✔
728
            if inp.sequence:
3✔
729
                sequence = inp.sequence
3✔
730
            inp_keys = []
3✔
731
            if inp.key_id:
3✔
732
                key = hdwallet.key(inp.key_id)
3✔
733
                if key.key_type == 'multisig':
3✔
734
                    db_key = sess.query(DbKey).filter_by(id=key.key_id).scalar()
3✔
735
                    for ck in db_key.multisig_children:
3✔
736
                        inp_keys.append(ck.child_key.public.hex())
3✔
737
                else:
738
                    inp_keys = key.key()
3✔
739

740
            inputs.append(Input(
3✔
741
                prev_txid=inp.prev_txid, output_n=inp.output_n, keys=inp_keys, unlocking_script=inp.script,
742
                script_type=inp.script_type, sequence=sequence, index_n=inp.index_n, value=inp.value,
743
                double_spend=inp.double_spend, witness_type=inp.witness_type, network=network, address=inp.address,
744
                witnesses=inp.witnesses))
745

746
        outputs = []
3✔
747
        for out in db_tx.outputs:
3✔
748
            address = ''
3✔
749
            public_key = b''
3✔
750
            if out.key_id:
3✔
751
                key = hdwallet.key(out.key_id)
3✔
752
                address = key.address
3✔
753
                if key.key_type != 'multisig':
3✔
754
                    if key.key() and not isinstance(key.key(), Address):
3✔
755
                        public_key = key.key().public_hex
3✔
756
            outputs.append(Output(value=out.value, address=address, public_key=public_key,
3✔
757
                                  lock_script=out.script, spent=out.spent, output_n=out.output_n,
758
                                  script_type=out.script_type, network=network, change=out.is_change))
759

760
        return cls(hdwallet=hdwallet, inputs=inputs, outputs=outputs, locktime=db_tx.locktime,
3✔
761
                   version=db_tx.version, network=network, fee=db_tx.fee, fee_per_kb=fee_per_kb,
762
                   size=db_tx.size, txid=to_hexstring(txid), date=db_tx.date, confirmations=db_tx.confirmations,
763
                   block_height=db_tx.block_height, input_total=db_tx.input_total, output_total=db_tx.output_total,
764
                   rawtx=db_tx.raw, status=db_tx.status, coinbase=db_tx.coinbase,
765
                   verified=db_tx.verified)
766

767
    def to_transaction(self):
3✔
768
        return Transaction(self.inputs, self.outputs, self.locktime, self.version,
3✔
769
                           self.network.name, self.fee, self.fee_per_kb, self.size,
770
                           self.txid, self.txhash, self.date, self.confirmations,
771
                           self.block_height, self.block_hash, self.input_total,
772
                           self.output_total, self.rawtx, self.status, self.coinbase,
773
                           self.verified, self.witness_type, self.flag)
774

775
    def sign(self, keys=None, index_n=0, multisig_key_n=None, hash_type=SIGHASH_ALL, fail_on_unknown_key=False,
3✔
776
             replace_signatures=False):
777
        """
778
        Sign this transaction. Use existing keys from wallet or use keys argument for extra keys.
779

780
        :param keys: Extra private keys to sign the transaction
781
        :type keys: HDKey, str
782
        :param index_n: Transaction index_n to sign
783
        :type index_n: int
784
        :param multisig_key_n: Index number of key for multisig input for segwit transactions. Leave empty if not known. If not specified all possibilities will be checked
785
        :type multisig_key_n: int
786
        :param hash_type: Hashtype to use, default is SIGHASH_ALL
787
        :type hash_type: int
788
        :param fail_on_unknown_key: Method fails if public key from signature is not found in public key list
789
        :type fail_on_unknown_key: bool
790
        :param replace_signatures: Replace signature with new one if already signed.
791
        :type replace_signatures: bool
792

793
        :return None:
794
        """
795
        priv_key_list_arg = []
3✔
796
        if keys:
3✔
797
            key_paths = list(dict.fromkeys([ti.key_path for ti in self.inputs if ti.key_path[0] == 'm']))
3✔
798
            if not isinstance(keys, list):
3✔
799
                keys = [keys]
3✔
800
            for priv_key in keys:
3✔
801
                if not isinstance(priv_key, HDKey):
3✔
802
                    if isinstance(priv_key, str) and len(str(priv_key).split(' ')) > 4:
3✔
803
                        priv_key = HDKey.from_passphrase(priv_key, network=self.network)
3✔
804
                    else:
805
                        priv_key = HDKey(priv_key, network=self.network.name)
3✔
806
                priv_key_list_arg.append((None, priv_key))
3✔
807
                if key_paths and priv_key.depth == 0 and priv_key.key_type != "single":
3✔
808
                    for key_path in key_paths:
3✔
809
                        priv_key_list_arg.append((key_path, priv_key.subkey_for_path(key_path)))
3✔
810
        for ti in self.inputs:
3✔
811
            priv_key_list = []
3✔
812
            for (key_path, priv_key) in priv_key_list_arg:
3✔
813
                if (not key_path or key_path == ti.key_path) and priv_key not in priv_key_list:
3✔
814
                    priv_key_list.append(priv_key)
3✔
815
            priv_key_list += [k for k in ti.keys if k.is_private]
3✔
816
            Transaction.sign(self, priv_key_list, ti.index_n, multisig_key_n, hash_type, fail_on_unknown_key,
3✔
817
                             replace_signatures)
818
        self.verify()
3✔
819
        self.error = ""
3✔
820

821
    def send(self, broadcast=True):
3✔
822
        """
823
        Verify and push transaction to network. Update UTXO's in database after successful send
824

825
        :param broadcast: Verify transaction and broadcast, if set to False the transaction is verified but not broadcasted, i. Default is True
826
        :type broadcast: bool
827

828
        :return None:
829

830
        """
831

832
        self.error = None
3✔
833
        if not self.verified and not self.verify():
3✔
834
            self.error = "Cannot verify transaction"
3✔
835
            return None
3✔
836

837
        if not broadcast:
3✔
838
            return None
3✔
839

840
        srv = Service(network=self.network.name, wallet_name=self.hdwallet.name, providers=self.hdwallet.providers,
3✔
841
                      cache_uri=self.hdwallet.db_cache_uri)
842
        res = srv.sendrawtransaction(self.raw_hex())
3✔
843
        if not res:
3✔
844
            self.error = "Cannot send transaction. %s" % srv.errors
×
845
            return None
×
846
        if 'txid' in res:
3✔
847
            _logger.info("Successfully pushed transaction, result: %s" % res)
3✔
848
            self.txid = res['txid']
3✔
849
            self.status = 'unconfirmed'
3✔
850
            self.confirmations = 0
3✔
851
            self.pushed = True
3✔
852
            self.response_dict = srv.results
3✔
853
            self.store()
3✔
854

855
            # Update db: Update spent UTXO's, add transaction to database
856
            for inp in self.inputs:
3✔
857
                txid = inp.prev_txid
3✔
858
                utxos = self.hdwallet.session.query(DbTransactionOutput).join(DbTransaction).\
3✔
859
                    filter(DbTransaction.txid == txid,
860
                           DbTransactionOutput.output_n == inp.output_n_int,
861
                           DbTransactionOutput.spent.is_(False)).all()
862
                for u in utxos:
3✔
863
                    u.spent = True
3✔
864

865
            self.hdwallet._commit()
3✔
866
            self.hdwallet._balance_update(network=self.network.name)
3✔
867
            return None
3✔
868
        self.error = "Transaction not send, unknown response from service providers"
×
869

870
    def store(self):
3✔
871
        """
872
        Store this transaction to database
873

874
        :return int: Transaction index number
875
        """
876

877
        sess = self.hdwallet.session
3✔
878
        # If txid is unknown add it to database, else update
879
        db_tx_query = sess.query(DbTransaction). \
3✔
880
            filter(DbTransaction.wallet_id == self.hdwallet.wallet_id, DbTransaction.txid == bytes.fromhex(self.txid))
881
        db_tx = db_tx_query.scalar()
3✔
882
        if not db_tx:
3✔
883
            db_tx_query = sess.query(DbTransaction). \
3✔
884
                filter(DbTransaction.wallet_id.is_(None), DbTransaction.txid == bytes.fromhex(self.txid))
885
            db_tx = db_tx_query.first()
3✔
886
            if db_tx:
3✔
887
                db_tx.wallet_id = self.hdwallet.wallet_id
3✔
888

889
        if not db_tx:
3✔
890
            new_tx = DbTransaction(
3✔
891
                wallet_id=self.hdwallet.wallet_id, txid=bytes.fromhex(self.txid), block_height=self.block_height,
892
                size=self.size, confirmations=self.confirmations, date=self.date, fee=self.fee, status=self.status,
893
                input_total=self.input_total, output_total=self.output_total, network_name=self.network.name,
894
                raw=self.rawtx, verified=self.verified, account_id=self.account_id, locktime=self.locktime,
895
                version=self.version_int, coinbase=self.coinbase, index=self.index)
896
            sess.add(new_tx)
3✔
897
            self.hdwallet._commit()
3✔
898
            txidn = new_tx.id
3✔
899
        else:
900
            txidn = db_tx.id
3✔
901
            db_tx.block_height = self.block_height if self.block_height else db_tx.block_height
3✔
902
            db_tx.confirmations = self.confirmations if self.confirmations else db_tx.confirmations
3✔
903
            db_tx.date = self.date if self.date else db_tx.date
3✔
904
            db_tx.fee = self.fee if self.fee else db_tx.fee
3✔
905
            db_tx.status = self.status if self.status else db_tx.status
3✔
906
            db_tx.input_total = self.input_total if self.input_total else db_tx.input_total
3✔
907
            db_tx.output_total = self.output_total if self.output_total else db_tx.output_total
3✔
908
            db_tx.network_name = self.network.name if self.network.name else db_tx.network_name
3✔
909
            db_tx.raw = self.rawtx if self.rawtx else db_tx.raw
3✔
910
            db_tx.verified = self.verified
3✔
911
            db_tx.locktime = self.locktime
3✔
912
            self.hdwallet._commit()
3✔
913

914
        assert txidn
3✔
915
        for ti in self.inputs:
3✔
916
            tx_key = sess.query(DbKey).filter_by(wallet_id=self.hdwallet.wallet_id, address=ti.address).scalar()
3✔
917
            key_id = None
3✔
918
            if tx_key:
3✔
919
                key_id = tx_key.id
3✔
920
                tx_key.used = True
3✔
921
            tx_input = sess.query(DbTransactionInput). \
3✔
922
                filter_by(transaction_id=txidn, index_n=ti.index_n).scalar()
923
            if not tx_input:
3✔
924
                witnesses = int_to_varbyteint(len(ti.witnesses)) + b''.join([bytes(varstr(w)) for w in ti.witnesses])
3✔
925
                new_tx_item = DbTransactionInput(
3✔
926
                    transaction_id=txidn, output_n=ti.output_n_int, key_id=key_id, value=ti.value,
927
                    prev_txid=ti.prev_txid, index_n=ti.index_n, double_spend=ti.double_spend,
928
                    script=ti.unlocking_script, script_type=ti.script_type, witness_type=ti.witness_type,
929
                    sequence=ti.sequence, address=ti.address, witnesses=witnesses)
930
                sess.add(new_tx_item)
3✔
931
            elif key_id:
3✔
932
                tx_input.key_id = key_id
3✔
933
                if ti.value:
3✔
934
                    tx_input.value = ti.value
3✔
935
                if ti.prev_txid:
3✔
936
                    tx_input.prev_txid = ti.prev_txid
3✔
937
                if ti.unlocking_script:
3✔
938
                    tx_input.script = ti.unlocking_script
3✔
939

940
            self.hdwallet._commit()
3✔
941
        for to in self.outputs:
3✔
942
            tx_key = sess.query(DbKey).\
3✔
943
                filter_by(wallet_id=self.hdwallet.wallet_id, address=to.address).scalar()
944
            key_id = None
3✔
945
            if tx_key:
3✔
946
                key_id = tx_key.id
3✔
947
                tx_key.used = True
3✔
948
            spent = to.spent
3✔
949
            tx_output = sess.query(DbTransactionOutput). \
3✔
950
                filter_by(transaction_id=txidn, output_n=to.output_n).scalar()
951
            if not tx_output:
3✔
952
                new_tx_item = DbTransactionOutput(
3✔
953
                    transaction_id=txidn, output_n=to.output_n, key_id=key_id, address=to.address, value=to.value,
954
                    spent=spent, script=to.lock_script, script_type=to.script_type, is_change=to.change)
955
                sess.add(new_tx_item)
3✔
956
            elif key_id:
3✔
957
                tx_output.key_id = key_id
3✔
958
                tx_output.spent = spent if spent is not None else tx_output.spent
3✔
959
            self.hdwallet._commit()
3✔
960
        return txidn
3✔
961

962
    def info(self):
3✔
963
        """
964
        Print Wallet transaction information to standard output. Include send information.
965
        """
966

967
        Transaction.info(self)
3✔
968
        print("Pushed to network: %s" % self.pushed)
3✔
969
        print("Wallet: %s" % self.hdwallet.name)
3✔
970
        if self.error:
3✔
971
            print("Errors: %s" % self.error)
×
972
        print("\n")
3✔
973

974
    def export(self, skip_change=True):
3✔
975
        """
976
        Export this transaction as list of tuples in the following format:
977
            (transaction_date, transaction_hash, in/out, addresses_in, addresses_out, value, fee)
978

979
        A transaction with multiple inputs or outputs results in multiple tuples.
980

981
        :param skip_change: Do not include outputs to own wallet (default). Please note: So if this is set to True, then an internal transfer is not exported.
982
        :type skip_change: boolean
983

984
        :return list of tuple:
985
        """
986
        mut_list = []
3✔
987
        wlt_addresslist = self.hdwallet.addresslist()
3✔
988
        input_addresslist = [i.address for i in self.inputs]
3✔
989
        if self.outgoing_tx:
3✔
990
            fee_per_output = self.fee / len(self.outputs)
3✔
991
            for o in self.outputs:
3✔
992
                o_value = -o.value
3✔
993
                if o.address in wlt_addresslist:
3✔
994
                    if skip_change:
3✔
995
                        continue
3✔
996
                    elif self.incoming_tx:
×
997
                        o_value = 0
×
998
                mut_list.append((self.date, self.txid, 'out', input_addresslist, o.address, o_value, fee_per_output))
3✔
999
        else:
1000
            for o in self.outputs:
3✔
1001
                if o.address not in wlt_addresslist:
3✔
1002
                    continue
×
1003
                mut_list.append((self.date, self.txid, 'in', input_addresslist, o.address, o.value, 0))
3✔
1004
        return mut_list
3✔
1005

1006
    def save(self, filename=None):
3✔
1007
        """
1008
        Store transaction object as file, so it can be imported in bitcoinlib later with the :func:`load` method.
1009

1010
        :param filename: Location and name of file, leave empty to store transaction in bitcoinlib data directory: .bitcoinlib/<transaction_id.tx)
1011
        :type filename: str
1012

1013
        :return:
1014
        """
1015
        if not filename:
3✔
1016
            p = Path(BCL_DATA_DIR, '%s.tx' % self.txid)
3✔
1017
        else:
1018
            p = Path(filename)
3✔
1019
            if not p.parent or str(p.parent) == '.':
3✔
1020
                p = Path(BCL_DATA_DIR, filename)
3✔
1021
        f = p.open('wb')
3✔
1022
        t = self.to_transaction()
3✔
1023
        pickle.dump(t, f)
3✔
1024
        f.close()
3✔
1025

1026
    def delete(self):
3✔
1027
        """
1028
        Delete this transaction from database.
1029

1030
        WARNING: Results in incomplete wallets, transactions will NOT be automatically downloaded again when scanning or updating wallet. In normal situations only used to remove old unconfirmed transactions
1031

1032
        :return int: Number of deleted transactions
1033
        """
1034

1035
        session = self.hdwallet.session
3✔
1036
        txid = bytes.fromhex(self.txid)
3✔
1037
        tx_query = session.query(DbTransaction).filter_by(txid=txid)
3✔
1038
        tx = tx_query.scalar()
3✔
1039
        session.query(DbTransactionOutput).filter_by(transaction_id=tx.id).delete()
3✔
1040
        for inp in tx.inputs:
3✔
1041
            prev_utxos = session.query(DbTransactionOutput).join(DbTransaction).\
3✔
1042
                filter(DbTransaction.txid == inp.prev_txid, DbTransactionOutput.output_n == inp.output_n,
1043
                       DbTransactionOutput.spent.is_(True), DbTransaction.wallet_id == self.hdwallet.wallet_id).all()
1044
            for u in prev_utxos:
3✔
1045
                # Check if output is spent in another transaction
1046
                if session.query(DbTransactionInput).filter(DbTransactionInput.transaction_id ==
3✔
1047
                                                            inp.transaction_id).first():
1048
                    u.spent = False
3✔
1049
        session.query(DbTransactionInput).filter_by(transaction_id=tx.id).delete()
3✔
1050
        qr = session.query(DbKey).filter_by(latest_txid=txid)
3✔
1051
        qr.update({DbKey.latest_txid: None, DbKey.used: False})
3✔
1052
        res = tx_query.delete()
3✔
1053
        key = qr.scalar()
3✔
1054
        if key:
3✔
1055
            self.hdwallet._balance_update(key_id=key.id)
×
1056
        self.hdwallet._commit()
3✔
1057
        return res
3✔
1058

1059
    def bumpfee(self, fee=0, extra_fee=0, broadcast=False):
3✔
1060
        """
1061
        Increase fee for this transaction. If replace-by-fee is signaled in this transaction the fee can be
1062
        increased to speed up inclusion on the blockchain.
1063

1064
        If not fee or extra_fee is provided the extra fee will be increased by the formule you can find in the code
1065
        below using the BUMPFEE_DEFAULT_MULTIPLIER from the config settings.
1066

1067
        The extra fee will be deducted from change output. This method fails if there are not enough change outputs
1068
        to cover fees.
1069

1070
        If this transaction does not have enough inputs to cover extra fee, an extra wallet utxo will be aaded to
1071
        inputs if available.
1072

1073
        Previous broadcasted transaction will be removed from wallet with this replace-by-fee transaction and wallet
1074
        information updated.
1075

1076
        :param fee: New fee for this transaction
1077
        :type fee: int
1078
        :param extra_fee: Extra fee to add to current transaction fee
1079
        :type extra_fee: int
1080
        :param broadcast: Increase fee and directly broadcast transaction to the network
1081
        :type broadcast: bool
1082

1083
        :return None:
1084
        """
1085
        fees_not_provided = not (fee or extra_fee)
3✔
1086
        old_txid = self.txid
3✔
1087
        try:
3✔
1088
            super(WalletTransaction, self).bumpfee(fee, extra_fee)
3✔
1089
        except TransactionError as e:
3✔
1090
            if str(e) != "Not enough unspent outputs to bump transaction fee":
3✔
1091
                raise TransactionError(str(e))
×
1092
            else:
1093
                # Add extra input to cover fee
1094
                if fees_not_provided:
3✔
1095
                    extra_fee = int(self.fee * (0.03 ** BUMPFEE_DEFAULT_MULTIPLIER) +
3✔
1096
                              (self.vsize * BUMPFEE_DEFAULT_MULTIPLIER))
1097
                new_inp = self.add_input_from_wallet(amount_min=extra_fee)
3✔
1098
                # Add value of extra input to change output
1099
                change_outputs = [o for o in self.outputs if o.change]
3✔
1100
                if change_outputs:
3✔
1101
                    change_outputs[0].value += self.inputs[new_inp].value
×
1102
                else:
1103
                    self.add_output(self.inputs[new_inp].value, self.hdwallet.get_key().address, change=True)
3✔
1104
                    if fees_not_provided:
3✔
1105
                        extra_fee += 25 * BUMPFEE_DEFAULT_MULTIPLIER
3✔
1106
                super(WalletTransaction, self).bumpfee(fee, extra_fee)
3✔
1107
        # remove previous transaction and update wallet
1108
        if self.pushed:
3✔
1109
            self.hdwallet.transaction_delete(old_txid)
3✔
1110
        if broadcast:
3✔
1111
            self.send()
3✔
1112

1113
    def add_input_from_wallet(self, amount_min=0, key_id=None, min_confirms=1):
3✔
1114
        """
1115
        Add a new input from an utxo of this wallet. If not key_id is specified it adds the first input it finds with
1116
        the minimum amount and minimum confirms specified.
1117

1118
        WARNING: Change output and fees are not updated, so you risk overpaying fees!
1119

1120
        :param amount_min: Minimum value of new input
1121
        :type amount_min: int
1122
        :param key_id: Filter by this key id
1123
        :type key_id: int
1124
        :param min_confirms: Minimum confirms of utxo
1125
        :type min_confirms: int
1126

1127
        :return int: Index number of new input
1128
        """
1129
        if not amount_min:
3✔
1130
            amount_min = self.network.dust_amount
3✔
1131
        utxos = self.hdwallet.utxos(self.account_id, network=self.network.name, min_confirms=min_confirms,
3✔
1132
                                    key_id=key_id)
1133
        current_inputs = [(i.prev_txid.hex(), i.output_n_int) for i in self.inputs]
3✔
1134
        unused_inputs = [u for u in utxos
3✔
1135
                         if (u['txid'], u['output_n']) not in current_inputs and u['value'] >= amount_min]
1136
        if not unused_inputs:
3✔
1137
            raise TransactionError("Not enough unspent inputs found for transaction %s" %
3✔
1138
                                   self.txid)
1139
        # take first input
1140
        utxo = unused_inputs[0]
3✔
1141
        inp_keys, key = self.hdwallet._objects_by_key_id(utxo['key_id'])
3✔
1142
        unlock_script_type = get_unlocking_script_type(utxo['script_type'], self.witness_type,
3✔
1143
                                                       multisig=self.hdwallet.multisig)
1144
        return self.add_input(utxo['txid'], utxo['output_n'], keys=inp_keys, script_type=unlock_script_type,
3✔
1145
                              sigs_required=self.hdwallet.multisig_n_required, sort=self.hdwallet.sort_keys,
1146
                              compressed=key.compressed, value=utxo['value'], address=utxo['address'],
1147
                              witness_type=key.witness_type)
1148

1149
class Wallet(object):
3✔
1150
    """
1151
    Class to create and manage keys Using the BIP0044 Hierarchical Deterministic wallet definitions, so you can
1152
    use one Masterkey to generate as much child keys as you want in a structured manner.
1153

1154
    You can import keys in many format such as WIF or extended WIF, bytes, hexstring, seeds or private key integer.
1155
    For the Bitcoin network, Litecoin or any other network you define in the settings.
1156

1157
    Easily send and receive transactions. Compose transactions automatically or select unspent outputs.
1158

1159
    Each wallet name must be unique and can contain only one cointype and purpose, but practically unlimited
1160
    accounts and addresses.
1161
    """
1162

1163
    @classmethod
3✔
1164
    def _create(cls, name, key, owner, network, account_id, purpose, scheme, parent_id, sort_keys,
3✔
1165
                witness_type, encoding, multisig, sigs_required, cosigner_id, key_path,
1166
                anti_fee_sniping, db_uri, db_cache_uri, db_password):
1167

1168
        db = Db(db_uri, db_password)
3✔
1169
        session = db.session
3✔
1170
        if (db_uri is None or db_uri.startswith("sqlite")) and db_cache_uri is None:
3✔
1171
            db_cache_uri = DEFAULT_DATABASE_CACHE
3✔
1172
        elif not db_cache_uri:
3✔
1173
            db_cache_uri = db.db_uri
3✔
1174
        db_uri = db.db_uri
3✔
1175
        if session.query(DbWallet).filter_by(name=name).count():
3✔
1176
            raise WalletError("Wallet with name '%s' already exists" % name)
3✔
1177
        else:
1178
            _logger.info("Create new wallet '%s'" % name)
3✔
1179
        if not name:
3✔
1180
            raise WalletError("Please enter wallet name")
3✔
1181

1182
        if not isinstance(key_path, list):
3✔
1183
            key_path = key_path.split('/')
3✔
1184
        key_depth = 1 if not key_path else len(key_path) - 1
3✔
1185
        base_path = 'm'
3✔
1186
        if hasattr(key, 'depth'):
3✔
1187
            if key.depth is None:
3✔
1188
                key.depth = key_depth
3✔
1189
            if key.depth > 0:
3✔
1190
                hardened_keys = [x for x in key_path if x[-1:] == "'"]
3✔
1191
                if hardened_keys:
3✔
1192
                    depth_public_master = key_path.index(hardened_keys[-1])
3✔
1193
                    if depth_public_master != key.depth:
3✔
1194
                        raise WalletError("Depth of provided public master key %d does not correspond with key path "
3✔
1195
                                          "%s. Did you provide correct witness_type and multisig attribute?" %
1196
                                          (key.depth, key_path))
1197
                key_path = ['M'] + key_path[key.depth+1:]
3✔
1198
                base_path = 'M'
3✔
1199

1200
        if isinstance(key_path, list):
3✔
1201
            key_path = '/'.join(key_path)
3✔
1202
        session.merge(DbNetwork(name=network))
3✔
1203
        new_wallet = DbWallet(name=name, owner=owner, network_name=network, purpose=purpose, scheme=scheme,
3✔
1204
                              sort_keys=sort_keys, witness_type=witness_type, parent_id=parent_id, encoding=encoding,
1205
                              multisig=multisig, multisig_n_required=sigs_required, cosigner_id=cosigner_id,
1206
                              key_path=key_path, anti_fee_sniping=anti_fee_sniping)
1207
        session.add(new_wallet)
3✔
1208
        session.commit()
3✔
1209
        new_wallet_id = new_wallet.id
3✔
1210

1211
        if scheme == 'bip32' and multisig and parent_id is None:
3✔
1212
            w = cls(new_wallet_id, db_uri=db_uri, db_cache_uri=db_cache_uri)
3✔
1213
        elif scheme == 'bip32':
3✔
1214
            mk = WalletKey.from_key(key=key, name=name, session=session, wallet_id=new_wallet_id, network=network,
3✔
1215
                                    account_id=account_id, purpose=purpose, key_type='bip32', encoding=encoding,
1216
                                    witness_type=witness_type, multisig=multisig, path=base_path)
1217
            new_wallet.main_key_id = mk.key_id
3✔
1218
            session.commit()
3✔
1219

1220
            w = cls(new_wallet_id, db_uri=db_uri, db_cache_uri=db_cache_uri, main_key_object=mk.key())
3✔
1221
            w.key_for_path([], account_id=account_id, cosigner_id=cosigner_id, change=0, address_index=0)
3✔
1222
        else:  # scheme == 'single':
1223
            if not key:
3✔
1224
                key = HDKey(network=network, depth=key_depth)
3✔
1225
            mk = WalletKey.from_key(key=key, name=name, session=session, wallet_id=new_wallet_id, network=network,
3✔
1226
                                    account_id=account_id, purpose=purpose, key_type='single', encoding=encoding,
1227
                                    witness_type=witness_type, multisig=multisig)
1228
            new_wallet.main_key_id = mk.key_id
3✔
1229
            session.commit()
3✔
1230
            w = cls(new_wallet_id, db_uri=db_uri, db_cache_uri=db_cache_uri, main_key_object=mk.key())
3✔
1231

1232
        session.close()
3✔
1233
        return w
3✔
1234

1235
    def _commit(self):
3✔
1236
        try:
3✔
1237
            self.session.commit()
3✔
NEW
1238
        except Exception as e:
×
1239
            self.session.rollback()
×
NEW
1240
            raise WalletError("Could not commit to database, rollback performed! Database error: %s" % str(e))
×
1241

1242
    @classmethod
3✔
1243
    def create(cls, name, keys=None, owner='', network=None, account_id=0, purpose=0, scheme='bip32',
3✔
1244
               sort_keys=True, password='', witness_type=None, encoding=None, multisig=None, sigs_required=None,
1245
               cosigner_id=None, key_path=None, anti_fee_sniping=True, db_uri=None, db_cache_uri=None,
1246
               db_password=None):
1247
        """
1248
        Create Wallet and insert in database. Generate masterkey or import key when specified.
1249

1250
        When only a name is specified a legacy Wallet with a single masterkey is created with standard p2wpkh
1251
        scripts.
1252

1253
        >>> if wallet_delete_if_exists('create_legacy_wallet_test'): pass
1254
        >>> w = Wallet.create('create_legacy_wallet_test')
1255
        >>> w
1256
        <Wallet(name="create_legacy_wallet_test")>
1257

1258
        To create a multi signature wallet specify multiple keys (private or public) and provide the sigs_required
1259
        argument if it different then len(keys)
1260

1261
        >>> if wallet_delete_if_exists('create_legacy_multisig_wallet_test'): pass
1262
        >>> w = Wallet.create('create_legacy_multisig_wallet_test', keys=[HDKey(), HDKey().public()])
1263

1264
        To create a native segwit wallet use the option witness_type = 'segwit' and for old style addresses and p2sh
1265
        embedded segwit script us 'ps2h-segwit' as witness_type.
1266

1267
        >>> if wallet_delete_if_exists('create_segwit_wallet_test'): pass
1268
        >>> w = Wallet.create('create_segwit_wallet_test', witness_type='segwit')
1269

1270
        Use a masterkey WIF when creating a wallet:
1271

1272
        >>> wif = 'xprv9s21ZrQH143K3cxbMVswDTYgAc9CeXABQjCD9zmXCpXw4MxN93LanEARbBmV3utHZS9Db4FX1C1RbC5KSNAjQ5WNJ1dDBJ34PjfiSgRvS8x'
1273
        >>> if wallet_delete_if_exists('bitcoinlib_legacy_wallet_test', force=True): pass
1274
        >>> w = Wallet.create('bitcoinlib_legacy_wallet_test', wif)
1275
        >>> w
1276
        <Wallet(name="bitcoinlib_legacy_wallet_test")>
1277
        >>> # Add some test utxo data:
1278
        >>> if w.utxo_add('16QaHuFkfuebXGcYHmehRXBBX7RG9NbtLg', 100000000, '748799c9047321cb27a6320a827f1f69d767fe889c14bf11f27549638d566fe4', 0): pass
1279

1280
        Please mention account_id if you are using multiple accounts.
1281

1282
        :param name: Unique name of this Wallet
1283
        :type name: str
1284
        :param keys: Masterkey to or list of keys to use for this wallet. Will be automatically created if not specified. One or more keys are obligatory for multisig wallets. Can contain all key formats accepted by the HDKey object, a HDKey object or BIP39 passphrase
1285
        :type keys: str, bytes, int, HDKey, HDWalletKey, list of str, list of bytes, list of int, list of HDKey, list of HDWalletKey
1286
        :param owner: Wallet owner for your own reference
1287
        :type owner: str
1288
        :param network: Network name, use default if not specified
1289
        :type network: str
1290
        :param account_id: Account ID, default is 0
1291
        :type account_id: int
1292
        :param purpose: BIP43 purpose field, will be derived from witness_type and multisig by default
1293
        :type purpose: int
1294
        :param scheme: Key structure type, i.e. BIP32 or single
1295
        :type scheme: str
1296
        :param sort_keys: Sort keys according to BIP45 standard (used for multisig keys)
1297
        :type sort_keys: bool
1298
        :param password: Password to protect passphrase, only used if a passphrase is supplied in the 'key' argument.
1299
        :type password: str
1300
        :param witness_type: Specify witness type, default is 'segwit', for native segregated witness wallet. Use 'legacy' for an old-style wallets or 'p2sh-segwit' for legacy compatible wallets
1301
        :type witness_type: str
1302
        :param encoding: Encoding used for address generation: base58 or bech32. Default is derive from wallet and/or witness type
1303
        :type encoding: str
1304
        :param multisig: Multisig wallet or child of a multisig wallet, default is None / derive from number of keys.
1305
        :type multisig: bool
1306
        :param sigs_required: Number of signatures required for validation if using a multisignature wallet. For example 2 for 2-of-3 multisignature. Default is all keys must be signed
1307
        :type sigs_required: int
1308
        :param cosigner_id: Set this if wallet contains only public keys, more than one private key or if you would like to create keys for other cosigners. Note: provided keys of a multisig wallet are sorted if sort_keys = True (default) so if your provided key list is not sorted the wallet's cosigner_id may be different.
1309
        :type cosigner_id: int
1310
        :param key_path: Key path for multisig wallet, use to create your own non-standard key path. Key path must follow the following rules:
1311
            * Path start with masterkey (m) and end with change / address_index
1312
            * If accounts are used, the account level must be 3. I.e.: m/purpose/coin_type/account/
1313
            * All keys must be hardened, except for change, address_index or cosigner_id
1314
            * Max length of path is 8 levels
1315
        :type key_path: list, str
1316
        :param anti_fee_sniping: Set default locktime in transactions as current block height + 1  to avoid fee-sniping. Default is True, which will make the network more secure. You could disable it to avoid transaction fingerprinting.
1317
        :type anti_fee_sniping: boolean
1318
        :param db_uri: URI of the database for wallets, wallet transactions and keys
1319
        :type db_uri: str
1320
        :param db_cache_uri: URI of the cache database. If not specified  the default cache database is used when using sqlite, for other databasetypes the cache database is merged with the wallet database (db_uri)
1321
        :type db_cache_uri: str
1322
        :param db_password: Password to encrypt database. Requires the installation of sqlcipher (see documentation).
1323
        :type db_password: str
1324

1325
        :return Wallet:
1326
        """
1327

1328
        if multisig is None:
3✔
1329
            if keys and isinstance(keys, list) and len(keys) > 1:
3✔
1330
                multisig = True
3✔
1331
            else:
1332
                multisig = False
3✔
1333
        if scheme not in ['bip32', 'single']:
3✔
1334
            raise WalletError("Only bip32 or single key scheme's are supported at the moment")
3✔
1335
        if witness_type not in [None, 'legacy', 'p2sh-segwit', 'segwit']:
3✔
1336
            raise WalletError("Witness type %s not supported at the moment" % witness_type)
3✔
1337
        if name.isdigit():
3✔
1338
            raise WalletError("Wallet name '%s' invalid, please include letter characters" % name)
3✔
1339

1340
        if multisig:
3✔
1341
            if password:
3✔
1342
                raise WalletError("Password protected multisig wallets not supported")
3✔
1343
            if scheme != 'bip32':
3✔
1344
                raise WalletError("Multisig wallets should use bip32 scheme not %s" % scheme)
3✔
1345
            if sigs_required is None:
3✔
1346
                sigs_required = len(keys)
3✔
1347
            if sigs_required > len(keys):
3✔
1348
                raise WalletError("Number of keys required to sign is greater then number of keys provided")
3✔
1349
        elif not isinstance(keys, list):
3✔
1350
            keys = [keys]
3✔
1351
        if len(keys) > 15:
3✔
1352
            raise WalletError("Redeemscripts with more then 15 keys are non-standard and could result in "
3✔
1353
                              "locked up funds")
1354

1355
        hdkey_list = []
3✔
1356
        # if keys and isinstance(keys, list) and sort_keys:
1357
        #     keys.sort(key=lambda x: ('0' if isinstance(x, HDKey) else '1'))
1358
        for key in keys:
3✔
1359
            if isinstance(key, HDKey):
3✔
1360
                if network and network != key.network.name:
3✔
1361
                    raise WalletError("Network from key (%s) is different then specified network (%s)" %
3✔
1362
                                      (key.network.name, network))
1363
                network = key.network.name
3✔
1364
                if witness_type is None:
3✔
1365
                    witness_type = key.witness_type
3✔
1366
            elif key:
3✔
1367
                # If key consists of several words assume it is a passphrase and convert it to a HDKey object
1368
                if isinstance(key, str) and len(key.split(" ")) > 1:
3✔
1369
                    if not network:
3✔
1370
                        network = DEFAULT_NETWORK
3✔
1371
                    key = HDKey.from_seed(Mnemonic().to_seed(key, password), network=network, witness_type=witness_type)
3✔
1372
                else:
1373
                    try:
3✔
1374
                        if isinstance(key, WalletKey):
3✔
1375
                            key = key._hdkey_object
3✔
1376
                        else:
1377
                            key = HDKey(key, password=password, witness_type=witness_type, network=network)
3✔
1378
                    except BKeyError:
3✔
1379
                        try:
3✔
1380
                            scheme = 'single'
3✔
1381
                            key = Address.parse(key, encoding=encoding, network=network)
3✔
1382
                        except Exception:
3✔
1383
                            raise WalletError("Invalid key or address: %s" % key)
3✔
1384
                    if network is None:
3✔
1385
                        network = key.network.name
3✔
1386
                    if witness_type is None:
3✔
1387
                        witness_type = key.witness_type
3✔
1388
            hdkey_list.append(key)
3✔
1389

1390
        if network is None:
3✔
1391
            network = DEFAULT_NETWORK
3✔
1392
        if witness_type is None:
3✔
1393
            witness_type = DEFAULT_WITNESS_TYPE
3✔
1394
        if network in ['dogecoin', 'dogecoin_testnet'] and witness_type != 'legacy':
3✔
1395
            raise WalletError("Segwit is not supported for %s wallets" % network.capitalize())
×
1396
        elif network in ('dogecoin', 'dogecoin_testnet') and witness_type not in ('legacy', 'p2sh-segwit'):
3✔
1397
            raise WalletError("Pure segwit addresses are not supported for Dogecoin wallets. "
×
1398
                              "Please use p2sh-segwit instead")
1399

1400
        if not key_path:
3✔
1401
            if scheme == 'single':
3✔
1402
                key_path = ['m']
3✔
1403
                purpose = 0
3✔
1404
            else:
1405
                key_path, purpose, encoding = get_key_structure_data(witness_type, multisig, purpose, encoding)
3✔
1406
        else:
1407
            if purpose is None:
3✔
1408
                purpose = 0
3✔
1409
        if not encoding:
3✔
1410
            encoding = get_encoding_from_witness(witness_type)
3✔
1411

1412
        if multisig:
3✔
1413
            key = ''
3✔
1414
        else:
1415
            key = hdkey_list[0]
3✔
1416

1417
        main_key_path = key_path
3✔
1418
        if multisig:
3✔
1419
            if sort_keys:
3✔
1420
                # FIXME: Think of simple construction to distinct between key order and cosigner id, the solution below is a bit confusing
1421
                # cosigner_id_key = None if cosigner_id is None else hdkey_list[cosigner_id].public_byte
1422
                hdkey_list.sort(key=lambda x: x.public_byte)
3✔
1423
                # Update cosigner id if order of keys changed
1424
                # cosigner_id = cosigner_id if (cosigner_id is None or cosigner_id_key is None) else (
1425
                #     hdkey_list.index([k for k in hdkey_list if k.public_byte == cosigner_id_key][0]))
1426

1427
            cos_prv_lst = [hdkey_list.index(cw) for cw in hdkey_list if cw.is_private]
3✔
1428
            if cosigner_id is None:
3✔
1429
                if not cos_prv_lst:
3✔
1430
                    raise WalletError("This wallet does not contain any private keys, please specify cosigner_id for "
3✔
1431
                                      "this wallet")
1432
                elif len(cos_prv_lst) > 1:
3✔
1433
                    raise WalletError("This wallet contains more then 1 private key, please specify "
×
1434
                                      "cosigner_id for this wallet")
1435
                cosigner_id = 0 if not cos_prv_lst else cos_prv_lst[0]
3✔
1436
            if hdkey_list[cosigner_id].key_type == 'single':
3✔
1437
                main_key_path = 'm'
3✔
1438

1439
        hdpm = cls._create(name, key, owner=owner, network=network, account_id=account_id, purpose=purpose,
3✔
1440
                           scheme=scheme, parent_id=None, sort_keys=sort_keys, witness_type=witness_type,
1441
                           encoding=encoding, multisig=multisig, sigs_required=sigs_required, cosigner_id=cosigner_id,
1442
                           anti_fee_sniping=anti_fee_sniping, key_path=main_key_path, db_uri=db_uri,
1443
                           db_cache_uri=db_cache_uri, db_password=db_password)
1444

1445
        if multisig:
3✔
1446
            wlt_cos_id = 0
3✔
1447
            for cokey in hdkey_list:
3✔
1448
                if hdpm.network.name != cokey.network.name:
3✔
1449
                    raise WalletError("Network for key %s (%s) is different then network specified: %s/%s" %
×
1450
                                      (cokey.wif(is_private=False), cokey.network.name, network, hdpm.network.name))
1451
                scheme = 'bip32'
3✔
1452
                wn = name + '-cosigner-%d' % wlt_cos_id
3✔
1453
                c_key_path = key_path
3✔
1454
                if cokey.key_type == 'single':
3✔
1455
                    scheme = 'single'
3✔
1456
                    c_key_path = ['m']
3✔
1457
                w = cls._create(name=wn, key=cokey, owner=owner, network=network, account_id=account_id,
3✔
1458
                                purpose=hdpm.purpose, scheme=scheme, parent_id=hdpm.wallet_id, sort_keys=sort_keys,
1459
                                witness_type=hdpm.witness_type, encoding=encoding, multisig=True,
1460
                                sigs_required=None, cosigner_id=wlt_cos_id, key_path=c_key_path,
1461
                                anti_fee_sniping=anti_fee_sniping, db_uri=db_uri, db_cache_uri=db_cache_uri,
1462
                                db_password=db_password)
1463
                hdpm.cosigner.append(w)
3✔
1464
                wlt_cos_id += 1
3✔
1465
            # hdpm._dbwallet = hdpm.session.query(DbWallet).filter(DbWallet.id == hdpm.wallet_id)
1466
            # hdpm._dbwallet.update({DbWallet.cosigner_id: hdpm.cosigner_id})
1467
            # hdpm._dbwallet.update({DbWallet.key_path: hdpm.key_path})
1468
            # hdpm.session.commit()
1469

1470
        return hdpm
3✔
1471

1472
    def __enter__(self):
3✔
1473
        return self
3✔
1474

1475
    def __init__(self, wallet, db_uri=None, db_cache_uri=None, session=None, main_key_object=None, db_password=None):
3✔
1476
        """
1477
        Open a wallet with given ID or name
1478

1479
        :param wallet: Wallet name or ID
1480
        :type wallet: int, str
1481
        :param db_uri: URI of the database
1482
        :type db_uri: str
1483
        :param db_cache_uri: URI of the cache database. If not specified  the default cache database is used when using sqlite, for other databasetypes the cache database is merged with the wallet database (db_uri)
1484
        :type db_cache_uri: str
1485
        :param session: Sqlalchemy session
1486
        :type session: sqlalchemy.orm.session.Session
1487
        :param main_key_object: Pass main key object to save time
1488
        :type main_key_object: HDKey
1489
        """
1490

1491
        self._session = None
3✔
1492
        self._engine = None
3✔
1493
        if session:
3✔
1494
            self._session = session
×
1495
        self._db_password = db_password
3✔
1496
        self.db_uri = db_uri
3✔
1497
        self.db_cache_uri = db_cache_uri
3✔
1498
        if isinstance(wallet, int) or wallet.isdigit():
3✔
1499
            db_wlt = self.session.query(DbWallet).filter_by(id=wallet).scalar()
3✔
1500
        else:
1501
            db_wlt = self.session.query(DbWallet).filter_by(name=wallet).scalar()
3✔
1502
        if db_wlt:
3✔
1503
            self._dbwallet = db_wlt
3✔
1504
            self.wallet_id = db_wlt.id
3✔
1505
            self._name = db_wlt.name
3✔
1506
            self._owner = db_wlt.owner
3✔
1507
            self.network = Network(db_wlt.network_name)
3✔
1508
            self.purpose = db_wlt.purpose
3✔
1509
            self.scheme = db_wlt.scheme
3✔
1510
            self._balance = None
3✔
1511
            self._balances = []
3✔
1512
            self.main_key_id = db_wlt.main_key_id
3✔
1513
            self.main_key = None
3✔
1514
            self._default_account_id = db_wlt.default_account_id
3✔
1515
            self.multisig_n_required = db_wlt.multisig_n_required
3✔
1516
            co_sign_wallets = self.session.query(DbWallet).\
3✔
1517
                filter(DbWallet.parent_id == self.wallet_id).order_by(DbWallet.name).all()
1518
            self.cosigner = [Wallet(w.id, db_uri=db_uri, db_cache_uri=db_cache_uri) for w in co_sign_wallets]
3✔
1519
            self.sort_keys = db_wlt.sort_keys
3✔
1520
            if db_wlt.main_key_id:
3✔
1521
                self.main_key = WalletKey(self.main_key_id, session=self.session, hdkey_object=main_key_object)
3✔
1522
            if self._default_account_id is None:
3✔
1523
                self._default_account_id = 0
3✔
1524
                if self.main_key:
3✔
1525
                    self._default_account_id = self.main_key.account_id
3✔
1526
            _logger.info("Opening wallet '%s'" % self.name)
3✔
1527
            self._key_objects = {
3✔
1528
                self.main_key_id: self.main_key
1529
            }
1530
            self.providers = None
3✔
1531
            self.witness_type = db_wlt.witness_type
3✔
1532
            self.encoding = db_wlt.encoding
3✔
1533
            self.multisig = db_wlt.multisig
3✔
1534
            self.cosigner_id = db_wlt.cosigner_id
3✔
1535
            self.script_type = script_type_default(self.witness_type, self.multisig, locking_script=True)
3✔
1536
            self.key_path = [] if not db_wlt.key_path else db_wlt.key_path.split('/')
3✔
1537
            self.depth_public_master = 0
3✔
1538
            self.parent_id = db_wlt.parent_id
3✔
1539
            if self.main_key and self.main_key.depth > 0:
3✔
1540
                self.depth_public_master = self.main_key.depth
3✔
1541
                self.key_depth = self.depth_public_master + len(self.key_path) - 1
3✔
1542
            else:
1543
                hardened_keys = [x for x in self.key_path if x[-1:] == "'"]
3✔
1544
                if hardened_keys:
3✔
1545
                    self.depth_public_master = self.key_path.index(hardened_keys[-1])
3✔
1546
                self.key_depth = len(self.key_path) - 1
3✔
1547
            self.last_updated = None
3✔
1548
            self.anti_fee_sniping = db_wlt.anti_fee_sniping
3✔
1549
        else:
1550
            raise WalletError("Wallet '%s' not found, please specify correct wallet ID or name." % wallet)
3✔
1551

1552
    def __exit__(self, exception_type, exception_value, traceback):
3✔
1553
        try:
3✔
1554
            self.session.close()
3✔
1555
            self._engine.dispose()
3✔
1556
        except Exception:
×
1557
            pass
×
1558

1559
    def __del__(self):
3✔
1560
        try:
3✔
1561
            self.session.close()
3✔
1562
            self._engine.dispose()
3✔
1563
        except Exception:
×
1564
            pass
×
1565

1566
    def __repr__(self):
1567
        db_uri = '' if not self.db_uri else self.db_uri.split('?')[0]
1568
        if DEFAULT_DATABASE in db_uri:
1569
            return "<Wallet(name=\"%s\")>" % self.name
1570
        return "<Wallet(name=\"%s\", db_uri=\"%s\")>" % \
1571
               (self.name, db_uri)
1572

1573
    def __str__(self):
3✔
1574
        return self.name
×
1575

1576
    def _get_account_defaults(self, network=None, account_id=None, key_id=None):
3✔
1577
        """
1578
        Check parameter values for network and account ID, return defaults if no network or account ID is specified.
1579
        If a network is specified but no account ID this method returns the first account ID it finds.
1580

1581
        :param network: Network code, leave empty for default
1582
        :type network: str
1583
        :param account_id: Account ID, leave emtpy for default
1584
        :type account_id: int
1585
        :param key_id: Key ID to just update 1 key
1586
        :type key_id: int
1587

1588
        :return: network code, account ID and DbKey instance of account ID key
1589
        """
1590

1591
        if key_id:
3✔
1592
            kobj = self.key(key_id)
3✔
1593
            network = kobj.network_name
3✔
1594
            account_id = kobj.account_id
3✔
1595
        if network is None:
3✔
1596
            network = self.network.name
3✔
1597
        if account_id is None and network == self.network.name:
3✔
1598
            account_id = self.default_account_id
3✔
1599
        qr = self.session.query(DbKey).\
3✔
1600
            filter_by(wallet_id=self.wallet_id, purpose=self.purpose, depth=self.depth_public_master,
1601
                      network_name=network)
1602
        if account_id is not None:
3✔
1603
            qr = qr.filter_by(account_id=account_id)
3✔
1604
        acckey = qr.first()
3✔
1605
        if len(qr.all()) > 1 and "account'" in self.key_path:
3✔
1606
            _logger.warning("No account_id specified and more than one account found for this network %s. "
3✔
1607
                            "Using a random account" % network)
1608
        if account_id is None:
3✔
1609
            if acckey:
3✔
1610
                account_id = acckey.account_id
3✔
1611
            else:
1612
                account_id = 0
3✔
1613
        return network, account_id, acckey
3✔
1614

1615
    @property
3✔
1616
    def default_account_id(self):
3✔
1617
        return self._default_account_id
3✔
1618

1619
    @default_account_id.setter
3✔
1620
    def default_account_id(self, value):
3✔
1621
        self._default_account_id = value
3✔
1622
        self._dbwallet = self.session.query(DbWallet).filter(DbWallet.id == self.wallet_id). \
3✔
1623
            update({DbWallet.default_account_id: value})
1624
        self._commit()
3✔
1625

1626
    @property
3✔
1627
    def owner(self):
3✔
1628
        """
1629
        Get wallet Owner
1630

1631
        :return str:
1632
        """
1633

1634
        return self._owner
3✔
1635

1636
    @owner.setter
3✔
1637
    def owner(self, value):
3✔
1638
        """
1639
        Set wallet Owner in database
1640

1641
        :param value: Owner
1642
        :type value: str
1643

1644
        :return str:
1645
        """
1646

1647
        self._owner = value
3✔
1648
        self._dbwallet = self.session.query(DbWallet).filter(DbWallet.id == self.wallet_id).\
3✔
1649
            update({DbWallet.owner: value})
1650
        self._commit()
3✔
1651

1652
    @property
3✔
1653
    def name(self):
3✔
1654
        """
1655
        Get wallet name
1656

1657
        :return str:
1658
        """
1659

1660
        return self._name
3✔
1661

1662
    @name.setter
3✔
1663
    def name(self, value):
3✔
1664
        """
1665
        Set wallet name, update in database
1666

1667
        :param value: Name for this wallet
1668
        :type value: str
1669

1670
        :return str:
1671
        """
1672

1673
        if wallet_exists(value, db_uri=self.db_uri):
3✔
1674
            raise WalletError("Wallet with name '%s' already exists" % value)
3✔
1675
        self._name = value
3✔
1676
        self.session.query(DbWallet).filter(DbWallet.id == self.wallet_id).update({DbWallet.name: value})
3✔
1677
        self._commit()
3✔
1678

1679
    @property
3✔
1680
    def session(self):
3✔
1681
        if not self._session:
3✔
1682
            logger.info("Opening database session %s" % self.db_uri)
3✔
1683
            dbinit = Db(db_uri=self.db_uri, password=self._db_password)
3✔
1684
            self._session = dbinit.session
3✔
1685
            self._engine = dbinit.engine
3✔
1686
        return self._session
3✔
1687

1688
    def default_network_set(self, network):
3✔
1689
        if not isinstance(network, Network):
3✔
1690
            network = Network(network)
3✔
1691
        self.network = network
3✔
1692
        self.session.query(DbWallet).filter(DbWallet.id == self.wallet_id).\
3✔
1693
            update({DbWallet.network_name: network.name})
1694
        self._commit()
3✔
1695

1696
    def import_master_key(self, hdkey, name='Masterkey (imported)'):
3✔
1697
        """
1698
        Import (another) masterkey in this wallet
1699

1700
        :param hdkey: Private key
1701
        :type hdkey: HDKey, str
1702
        :param name: Key name of masterkey
1703
        :type name: str
1704

1705
        :return HDKey: Main key as HDKey object
1706
        """
1707

1708
        network, account_id, acckey = self._get_account_defaults()
3✔
1709
        if not isinstance(hdkey, HDKey):
3✔
1710
            hdkey = HDKey(hdkey)
3✔
1711
        if not isinstance(self.main_key, WalletKey):
3✔
1712
            raise WalletError("Main wallet key is not an WalletKey instance. Type %s" % type(self.main_key))
3✔
1713
        if not hdkey.is_private or hdkey.depth != 0:
3✔
1714
            raise WalletError("Please supply a valid private BIP32 master key with key depth 0")
3✔
1715
        if self.main_key.is_private:
3✔
1716
            raise WalletError("Main key is already a private key, cannot import key")
3✔
1717
        if (self.main_key.depth != 1 and self.main_key.depth != 3 and self.main_key.depth != 4) or \
3✔
1718
                self.main_key.key_type != 'bip32':
1719
            raise WalletError("Current main key is not a valid BIP32 public master key")
3✔
1720
        if not (self.network.name == self.main_key.network.name == hdkey.network.name):
3✔
1721
            raise WalletError("Network of Wallet class, main account key and the imported private key must use "
3✔
1722
                              "the same network")
1723
        if self.main_key.wif != hdkey.public_master().wif():
3✔
1724
            raise WalletError("This key does not correspond to current public master key")
3✔
1725

1726
        hdkey.key_type = 'bip32'
3✔
1727
        # ks = [k for k in WALLET_KEY_STRUCTURES if
1728
        #       k['witness_type'] == self.witness_type and k['multisig'] == self.multisig and k['purpose'] is not None]
1729
        # if len(ks) > 1:
1730
        #     raise WalletError("Please check definitions in WALLET_KEY_STRUCTURES. Multiple options found for "
1731
        #                       "witness_type - multisig combination")
1732
        # self.key_path = ks[0]['key_path']
1733
        self.key_path, _, _ = get_key_structure_data(self.witness_type, self.multisig)
3✔
1734
        self.main_key = WalletKey.from_key(
3✔
1735
            key=hdkey, name=name, session=self.session, wallet_id=self.wallet_id, network=network,
1736
            account_id=account_id, purpose=self.purpose, key_type='bip32', witness_type=self.witness_type)
1737
        self.main_key_id = self.main_key.key_id
3✔
1738
        self._key_objects.update({self.main_key_id: self.main_key})
3✔
1739
        self.session.query(DbWallet).filter(DbWallet.id == self.wallet_id).\
3✔
1740
            update({DbWallet.main_key_id: self.main_key_id})
1741

1742
        for key in self.keys(is_private=False):
3✔
1743
            kp = key.path.split("/")
3✔
1744
            if kp and kp[0] == 'M':
3✔
1745
                kp = self.key_path[:self.depth_public_master+1] + kp[1:]
3✔
1746
            self.key_for_path(kp, recreate=True)
3✔
1747
        self._commit()
3✔
1748
        return self.main_key
3✔
1749

1750
    def import_key(self, key, account_id=0, name='', network=None, purpose=84, key_type=None):
3✔
1751
        """
1752
        Add new single key to wallet.
1753

1754
        :param key: Key to import
1755
        :type key: str, bytes, int, HDKey, Address
1756
        :param account_id: Account ID. Default is last used or created account ID.
1757
        :type account_id: int
1758
        :param name: Specify name for key, leave empty for default
1759
        :type name: str
1760
        :param network: Network name, method will try to extract from key if not specified. Raises warning if network could not be detected
1761
        :type network: str
1762
        :param purpose: BIP44 definition used, default is 84 (segwit)
1763
        :type purpose: int
1764
        :param key_type: Key type of imported key, can be single. Unrelated to wallet, bip32, bip44 or master for new or extra master key import. Default is 'single'
1765
        :type key_type: str
1766

1767
        :return WalletKey:
1768
        """
1769

1770
        if self.scheme not in ['bip32', 'single']:
3✔
1771
            raise WalletError("Keys can only be imported to a BIP32 or single type wallet, create a new wallet "
×
1772
                              "instead")
1773
        if isinstance(key, (HDKey, Address)):
3✔
1774
            network = key.network.name
3✔
1775
            hdkey = key
3✔
1776
            if network not in self.network_list():
3✔
1777
                raise WalletError("Network %s not found in this wallet" % network)
×
1778
        else:
1779
            if isinstance(key, str) and len(key.split(" ")) > 1:
3✔
1780
                if network is None:
3✔
1781
                    network = self.network
3✔
1782
                hdkey = HDKey.from_seed(Mnemonic().to_seed(key), network=network)
3✔
1783
            else:
1784
                if network is None:
3✔
1785
                    network = check_network_and_key(key, default_network=self.network.name)
3✔
1786
                if network not in self.network_list():
3✔
1787
                    raise WalletError("Network %s not available in this wallet, please create an account for this "
3✔
1788
                                      "network first." % network)
1789
                hdkey = HDKey(key, network=network, key_type=key_type, witness_type=self.witness_type)
3✔
1790

1791
        if not self.multisig:
3✔
1792
            if self.main_key and self.main_key.depth == self.depth_public_master and \
3✔
1793
                    not isinstance(hdkey, Address) and hdkey.is_private and hdkey.depth == 0 and self.scheme == 'bip32':
1794
                return self.import_master_key(hdkey, name)
3✔
1795

1796
            if key_type is None:
3✔
1797
                hdkey.key_type = 'single'
3✔
1798
                key_type = 'single'
3✔
1799

1800
            ik_path = 'm'
3✔
1801
            if key_type == 'single':
3✔
1802
                # Create path for unrelated import keys
1803
                hdkey.depth = self.key_depth
3✔
1804
                last_import_key = self.session.query(DbKey).filter(DbKey.path.like("import_key_%")).\
3✔
1805
                    order_by(DbKey.path.desc()).first()
1806
                if last_import_key:
3✔
1807
                    ik_path = "import_key_" + str(int(last_import_key.path[-5:]) + 1).zfill(5)
3✔
1808
                else:
1809
                    ik_path = "import_key_00001"
3✔
1810
                if not name:
3✔
1811
                    name = ik_path
3✔
1812

1813
            mk = WalletKey.from_key(
3✔
1814
                key=hdkey, name=name, wallet_id=self.wallet_id, network=network, key_type=key_type,
1815
                account_id=account_id, purpose=purpose, session=self.session, path=ik_path,
1816
                witness_type=self.witness_type)
1817
            self._key_objects.update({mk.key_id: mk})
3✔
1818
            if mk.key_id == self.main_key.key_id:
3✔
1819
                self.main_key = mk
3✔
1820
            return mk
3✔
1821
        else:
1822
            account_key = hdkey.public_master(witness_type=self.witness_type, multisig=True).wif()
3✔
1823
            for w in self.cosigner:
3✔
1824
                if w.main_key.key().wif_public() == account_key:
3✔
1825
                    _logger.debug("Import new private cosigner key in this multisig wallet: %s" % account_key)
3✔
1826
                    return w.import_master_key(hdkey)
3✔
1827
            raise WalletError("Unknown key: Can only import a private key for a known public key in multisig wallets")
×
1828

1829
    def _new_key_multisig(self, public_keys, name, account_id, change, cosigner_id, network, address_index,
3✔
1830
                          witness_type):
1831
        if self.sort_keys:
3✔
1832
            public_keys.sort(key=lambda pubk: pubk.key_public)
3✔
1833
        public_key_list = [pubk.key_public for pubk in public_keys]
3✔
1834
        public_key_ids = [str(x.key_id) for x in public_keys]
3✔
1835

1836
        # todo: pass key object, reuse key objects
1837
        redeemscript = Script(script_types=['multisig'], keys=public_key_list,
3✔
1838
                              sigs_required=self.multisig_n_required).serialize()
1839
        script_type = 'p2sh' if witness_type == 'legacy' else \
3✔
1840
            ('p2sh_p2wsh' if witness_type == 'p2sh-segwit' else 'p2wsh')
1841
        address = Address(redeemscript, script_type=script_type, network=network, witness_type=witness_type)
3✔
1842
        already_found_key = self.session.query(DbKey).filter_by(wallet_id=self.wallet_id,
3✔
1843
                                                                 address=address.address).first()
1844
        if already_found_key:
3✔
1845
            return self.key(already_found_key.id)
3✔
1846
        path = [pubk.path for pubk in public_keys if pubk.wallet.cosigner_id == self.cosigner_id][0]
3✔
1847
        depth = self.cosigner[self.cosigner_id].main_key.depth + len(path.split("/")) - 1
3✔
1848
        if not name:
3✔
1849
            name = "Multisig Key " + '/'.join(public_key_ids)
3✔
1850

1851
        new_key_id = (self.session.query(func.max(DbKey.id)).scalar() or 0) + 1
3✔
1852
        multisig_key = DbKey(id=new_key_id,
3✔
1853
            name=name[:80], wallet_id=self.wallet_id, purpose=self.purpose, account_id=account_id,
1854
            depth=depth, change=change, address_index=address_index, parent_id=0, is_private=False, path=path,
1855
            public=address.hash_bytes, wif='multisig-%s' % address, address=address.address, cosigner_id=cosigner_id,
1856
            key_type='multisig', witness_type=witness_type, network_name=network)
1857
        self.session.add(multisig_key)
3✔
1858
        self._commit()
3✔
1859
        for child_id in public_key_ids:
3✔
1860
            self.session.add(DbKeyMultisigChildren(key_order=public_key_ids.index(child_id), parent_id=multisig_key.id,
3✔
1861
                                                    child_id=int(child_id)))
1862
        self._commit()
3✔
1863
        return self.key(multisig_key.id)
3✔
1864

1865
    def new_key(self, name='', account_id=None, change=0, cosigner_id=None, witness_type=None, network=None):
3✔
1866
        """
1867
        def new_key(self, name='', account_id=None, change=0, cosigner_id=None, witness_type=None, network=None):
1868

1869
        :param name: Key name. Does not have to be unique but if you use it at reference you might chooce to enforce this. If not specified 'Key #' with a unique sequence number will be used
1870
        :type name: str
1871
        :param account_id: Account ID. Default is last used or created account ID.
1872
        :type account_id: int
1873
        :param change: Change (1) or payments (0). Default is 0
1874
        :type change: int
1875
        :param cosigner_id: Cosigner ID for key path
1876
        :type cosigner_id: int
1877
        :param witness_type: Use to create key with different witness_type
1878
        :type witness_type: str
1879
        :param network: Network name. Leave empty for default network
1880
        :type network: str
1881

1882
        :return WalletKey:
1883
        """
1884
        return self.new_keys(name, account_id, change, cosigner_id, witness_type, 1, network)[0]
3✔
1885

1886
    def new_keys(self, name='', account_id=None, change=0, cosigner_id=None, witness_type=None,
3✔
1887
                number_of_keys=1, network=None):
1888
        """
1889
        Create a new HD Key derived from this wallet's masterkey. An account will be created for this wallet
1890
        with index 0 if there is no account defined yet.
1891

1892
        >>> w = Wallet('create_legacy_wallet_test')
1893
        >>> w.new_key('my key') # doctest:+ELLIPSIS
1894
        <WalletKey(key_id=..., name=my key, wif=..., path=m/84'/0'/0'/0/...)>
1895

1896
        :param name: Key name. Does not have to be unique but if you use it at reference you might chooce to enforce this. If not specified 'Key #' with a unique sequence number will be used
1897
        :type name: str
1898
        :param account_id: Account ID. Default is last used or created account ID.
1899
        :type account_id: int
1900
        :param change: Change (1) or payments (0). Default is 0
1901
        :type change: int
1902
        :param cosigner_id: Cosigner ID for key path
1903
        :type cosigner_id: int
1904
        :param witness_type: Use to create key with different witness_type
1905
        :type witness_type: str
1906
        :param number_of_keys: Number of keys to generate. Use positive integer
1907
        :type number_of_keys: int
1908
        :param network: Network name. Leave empty for default network
1909
        :type network: str
1910

1911
        :return list of WalletKey:
1912
        """
1913

1914
        if self.scheme == 'single':
3✔
1915
            return [self.main_key]
3✔
1916
        network, account_id, _ = self._get_account_defaults(network, account_id)
3✔
1917
        if network != self.network.name and "coin_type'" not in self.key_path:
3✔
1918
            raise WalletError("Multiple networks not supported by wallet key structure")
×
1919
        if self.multisig:
3✔
1920
            if not self.multisig_n_required:
3✔
1921
                raise WalletError("Multisig_n_required not set, cannot create new key")
×
1922
            if cosigner_id is None:
3✔
1923
                if self.cosigner_id is None:
3✔
1924
                    raise WalletError("Missing Cosigner ID value, cannot create new key")
×
1925
                cosigner_id = self.cosigner_id
3✔
1926
        witness_type = self.witness_type if not witness_type else witness_type
3✔
1927
        purpose = self.purpose
3✔
1928
        if witness_type != self.witness_type:
3✔
1929
            _, purpose, encoding = get_key_structure_data(witness_type, self.multisig)
3✔
1930

1931
        address_index = 0
3✔
1932
        if not((self.multisig and cosigner_id is not None and
3✔
1933
                (len(self.cosigner) > cosigner_id and self.cosigner[cosigner_id].key_path == 'm' or
1934
                 self.cosigner[cosigner_id].key_path == ['m']))):
1935
            prevkey = self.session.query(DbKey).\
3✔
1936
                filter_by(wallet_id=self.wallet_id, purpose=purpose, network_name=network, account_id=account_id,
1937
                          witness_type=witness_type, change=change, cosigner_id=cosigner_id, depth=self.key_depth).\
1938
                order_by(DbKey.address_index.desc()).first()
1939
            if prevkey:
3✔
1940
                address_index = prevkey.address_index + 1
3✔
1941

1942
        return self.keys_for_path([], name=name, account_id=account_id, witness_type=witness_type, network=network,
3✔
1943
                                 cosigner_id=cosigner_id, address_index=address_index, number_of_keys=number_of_keys,
1944
                                 change=change)
1945

1946
    def new_key_change(self, name='', account_id=None, witness_type=None, network=None):
3✔
1947
        """
1948
        Create new key to receive change for a transaction. Calls :func:`new_key` method with change=1.
1949

1950
        :param name: Key name. Default name is 'Change #' with an address index
1951
        :type name: str
1952
        :param account_id: Account ID. Default is last used or created account ID.
1953
        :type account_id: int
1954
        :param witness_type: Use to create key with different witness_type
1955
        :type witness_type: str
1956
        :param network: Network name. Leave empty for default network
1957
        :type network: str
1958

1959
        :return WalletKey:
1960
        """
1961

1962
        return self.new_key(name=name, account_id=account_id, witness_type=witness_type, network=network, change=1)
3✔
1963

1964
    def scan_key(self, key):
3✔
1965
        """
1966
        Scan for new transactions for specified wallet key and update wallet transactions
1967

1968
        :param key: The wallet key as object or index
1969
        :type key: WalletKey, int
1970

1971
        :return bool: New transactions found?
1972

1973
        """
1974
        if isinstance(key, int):
3✔
1975
            key = self.key(key)
×
1976
        txs_found = False
3✔
1977
        should_be_finished_count = 0
3✔
1978
        while True:
2✔
1979
            n_new = self.transactions_update(key_id=key.key_id)
3✔
1980
            if n_new and n_new < MAX_TRANSACTIONS:
3✔
1981
                if should_be_finished_count:
3✔
1982
                    _logger.info("Possible recursive loop detected in scan_key(%d): retry %d/5" %
×
1983
                                 (key.key_id, should_be_finished_count))
1984
                should_be_finished_count += 1
3✔
1985
            logger.info("Scanned key %d, %s Found %d new transactions" % (key.key_id, key.address, n_new))
3✔
1986
            if not n_new or should_be_finished_count > 5:
3✔
1987
                break
2✔
1988
            txs_found = True
3✔
1989
        return txs_found
3✔
1990

1991
    def scan(self, scan_gap_limit=5, account_id=None, change=None, rescan_used=False, network=None, keys_ignore=None):
3✔
1992
        """
1993
        Generate new addresses/keys and scan for new transactions using the Service providers. Updates all UTXO's and balances.
1994

1995
        Keep scanning for new transactions until no new transactions are found for 'scan_gap_limit' addresses. Only scan keys from default network and account unless another network or account is specified.
1996

1997
        Use the faster :func:`utxos_update` method if you are only interested in unspent outputs.
1998
        Use the :func:`transactions_update` method if you would like to manage the key creation yourself or if you want to scan a single key.
1999

2000
        :param scan_gap_limit: Amount of new keys and change keys (addresses) created for this wallet. Default is 5, so scanning stops if after 5 addresses no transaction are found.
2001
        :type scan_gap_limit: int
2002
        :param account_id: Account ID. Default is last used or created account ID.
2003
        :type account_id: int
2004
        :param change: Filter by change addresses. Set to True to include only change addresses, False to only include regular addresses. None (default) to disable filter and include both
2005
        :type change: bool
2006
        :param rescan_used: Rescan already used addressed. Default is False, so funds send to old addresses will be ignored by default.
2007
        :type rescan_used: bool
2008
        :param network: Network name. Leave empty for default network
2009
        :type network: str
2010
        :param keys_ignore: Id's of keys to ignore
2011
        :type keys_ignore: list of int
2012

2013
        :return:
2014
        """
2015

2016
        network, account_id, _ = self._get_account_defaults(network, account_id)
3✔
2017
        if self.scheme != 'bip32' and self.scheme != 'multisig' and scan_gap_limit < 2:
3✔
2018
            raise WalletError("The wallet scan() method is only available for BIP32 wallets")
×
2019
        if keys_ignore is None:
3✔
2020
            keys_ignore = []
3✔
2021

2022
        # Rescan used addresses
2023
        if rescan_used:
3✔
2024
            for key in self.keys_addresses(account_id=account_id, change=change, network=network, used=True):
×
2025
                self.scan_key(key.id)
×
2026

2027
        # Update already known transactions with known block height
2028
        self.transactions_update_confirmations()
3✔
2029

2030
        # Check unconfirmed transactions
2031
        db_txs = self.session.query(DbTransaction). \
3✔
2032
            filter(DbTransaction.wallet_id == self.wallet_id,
2033
                   DbTransaction.network_name == network, DbTransaction.confirmations == 0).all()
2034
        for db_tx in db_txs:
3✔
2035
            self.transactions_update_by_txids([db_tx.txid])
×
2036

2037
        # Scan each key address, stop when no new transactions are found after set scan gap limit
2038
        if change is None:
3✔
2039
            change_range = [0, 1]
3✔
2040
        else:
2041
            change_range = [change]
×
2042
        counter = 0
3✔
2043
        for chg in change_range:
3✔
2044
            while True:
2✔
2045
                if self.scheme == 'single':
3✔
2046
                    keys_to_scan = [self.key(k.id) for k in self.keys_addresses()[counter:counter+scan_gap_limit]]
3✔
2047
                    counter += scan_gap_limit
3✔
2048
                else:
2049
                    keys_to_scan = []
3✔
2050
                    for witness_type in self.witness_types(network=network):
3✔
2051
                        keys_to_scan += self.get_keys(account_id, witness_type, network,
3✔
2052
                                                     number_of_keys=scan_gap_limit, change=chg)
2053

2054
                n_highest_updated = 0
3✔
2055
                for key in keys_to_scan:
3✔
2056
                    if key.key_id in keys_ignore:
3✔
2057
                        continue
×
2058
                    keys_ignore.append(key.key_id)
3✔
2059
                    n_high_new = 0
3✔
2060
                    if self.scan_key(key):
3✔
2061
                        if not key.address_index:
3✔
2062
                            key.address_index = 0
3✔
2063
                        n_high_new = key.address_index + 1
3✔
2064
                    if n_high_new > n_highest_updated:
3✔
2065
                        n_highest_updated = n_high_new
3✔
2066
                if not n_highest_updated:
3✔
2067
                    break
3✔
2068

2069
    def _get_key(self, account_id=None, witness_type=None, network=None, cosigner_id=None, number_of_keys=1, change=0,
3✔
2070
                 as_list=False):
2071
        network, account_id, _ = self._get_account_defaults(network, account_id)
3✔
2072
        if cosigner_id is None:
3✔
2073
            cosigner_id = self.cosigner_id
3✔
2074
        elif cosigner_id > len(self.cosigner):
3✔
2075
            raise WalletError("Cosigner ID (%d) can not be greater then number of cosigners for this wallet (%d)" %
3✔
2076
                              (cosigner_id, len(self.cosigner)))
2077

2078
        witness_type = witness_type if witness_type else self.witness_type
3✔
2079
        last_used_qr = self.session.query(DbKey.id).\
3✔
2080
            filter_by(wallet_id=self.wallet_id, account_id=account_id, network_name=network, cosigner_id=cosigner_id,
2081
                      used=True, change=change, depth=self.key_depth, witness_type=witness_type).\
2082
            order_by(DbKey.id.desc()).first()
2083
        last_used_key_id = 0
3✔
2084
        if last_used_qr:
3✔
2085
            last_used_key_id = last_used_qr.id
3✔
2086
        dbkey = (self.session.query(DbKey.id).
3✔
2087
            filter_by(wallet_id=self.wallet_id, account_id=account_id, network_name=network, cosigner_id=cosigner_id,
2088
                      used=False, change=change, depth=self.key_depth, witness_type=witness_type).
2089
            filter(DbKey.id > last_used_key_id).
2090
            order_by(DbKey.id.asc()).all())
2091
        if self.scheme == 'single' and len(dbkey):
3✔
2092
            number_of_keys = len(dbkey) if number_of_keys > len(dbkey) else number_of_keys
×
2093
        key_list = [self.key(key_id[0]) for key_id in dbkey]
3✔
2094

2095
        if len(key_list) > number_of_keys:
3✔
2096
            key_list = key_list[:number_of_keys]
3✔
2097
        else:
2098
            new_keys = self.new_keys(account_id=account_id, change=change, cosigner_id=cosigner_id,
3✔
2099
                                     witness_type=witness_type, network=network,
2100
                                     number_of_keys=number_of_keys - len(key_list))
2101
            key_list += new_keys
3✔
2102

2103
        if as_list:
3✔
2104
            return key_list
3✔
2105
        else:
2106
            return key_list[0]
3✔
2107

2108
    def get_key(self, account_id=None, witness_type=None, network=None, cosigner_id=None, change=0):
3✔
2109
        """
2110
        Get an unused key / address or create a new one with :func:`new_key` if there are no unused keys.
2111
        Returns a key from this wallet which has no transactions linked to it.
2112

2113
        Use the get_keys() method to a list of unused keys. Calling the get_key() method repeatelly to receive a
2114
        list of key doesn't work: since the key is unused it would return the same result every time you call this
2115
        method.
2116

2117
        >>> w = Wallet('create_legacy_wallet_test')
2118
        >>> w.get_key() # doctest:+ELLIPSIS
2119
        <WalletKey(key_id=..., name=..., wif=..., path=m/84'/0'/0'/0/...)>
2120

2121
        :param account_id: Account ID. Default is last used or created account ID.
2122
        :type account_id: int
2123
        :param witness_type: Use to create key with specific witness_type
2124
        :type witness_type: str
2125
        :param network: Network name. Leave empty for default network
2126
        :type network: str
2127
        :param cosigner_id: Cosigner ID for key path
2128
        :type cosigner_id: int
2129
        :param change: Payment (0) or change key (1). Default is 0
2130
        :type change: int
2131

2132
        :return WalletKey:
2133
        """
2134
        return self._get_key(account_id, witness_type, network, cosigner_id, change=change, as_list=False)
3✔
2135

2136
    def get_keys(self, account_id=None, witness_type=None, network=None, cosigner_id=None, number_of_keys=1, change=0):
3✔
2137
        """
2138
        Get a list of unused keys / addresses or create a new ones with :func:`new_key` if there are no unused keys.
2139
        Returns a list of keys from this wallet which has no transactions linked to it.
2140

2141
        Use the get_key() method to get a single key.
2142

2143
        :param account_id: Account ID. Default is last used or created account ID.
2144
        :type account_id: int
2145
        :param witness_type: Use to create key with specific witness_type
2146
        :type witness_type: str
2147
        :param network: Network name. Leave empty for default network
2148
        :type network: str
2149
        :param cosigner_id: Cosigner ID for key path
2150
        :type cosigner_id: int
2151
        :param number_of_keys: Number of keys to return. Default is 1
2152
        :type number_of_keys: int
2153
        :param change: Payment (0) or change key (1). Default is 0
2154
        :type change: int
2155

2156
        :return list of WalletKey:
2157
        """
2158
        if self.scheme == 'single':
3✔
2159
            raise WalletError("Single wallet has only one (master)key. Use get_key() or main_key() method")
3✔
2160
        return self._get_key(account_id, witness_type, network, cosigner_id, number_of_keys, change, as_list=True)
3✔
2161

2162
    def get_key_change(self, account_id=None, witness_type=None, network=None):
3✔
2163
        """
2164
        Get a unused change key or create a new one if there are no unused keys.
2165
        Wrapper for the :func:`get_key` method
2166

2167
        :param account_id: Account ID. Default is last used or created account ID.
2168
        :type account_id: int
2169
        :param witness_type: Use to create key with specific witness_type
2170
        :type witness_type: str
2171
        :param network: Network name. Leave empty for default network
2172
        :type network: str
2173

2174
        :return WalletKey:
2175
        """
2176

2177
        return self._get_key(account_id, witness_type, network, change=1, as_list=False)
3✔
2178

2179
    def get_keys_change(self, account_id=None, witness_type=None, network=None, number_of_keys=1):
3✔
2180
        """
2181
        Get a unused change key or create a new one if there are no unused keys.
2182
        Wrapper for the :func:`get_key` method
2183

2184
        :param account_id: Account ID. Default is last used or created account ID.
2185
        :type account_id: int
2186
        :param witness_type: Use to create key with specific witness_type
2187
        :type witness_type: str
2188
        :param network: Network name. Leave empty for default network
2189
        :type network: str
2190
        :param number_of_keys: Number of keys to return. Default is 1
2191
        :type number_of_keys: int
2192

2193
        :return list of WalletKey:
2194
        """
2195

2196
        return self._get_key(account_id, witness_type, network, change=1, number_of_keys=number_of_keys, as_list=True)
×
2197

2198
    def new_account(self, name='', account_id=None, witness_type=None, network=None):
3✔
2199
        """
2200
        Create a new account with a child key for payments and 1 for change.
2201

2202
        An account key can only be created if wallet contains a masterkey.
2203

2204
        :param name: Account Name. If not specified "Account #" with the account_id will be used as name
2205
        :type name: str
2206
        :param account_id: Account ID. Default is last accounts ID + 1
2207
        :type account_id: int
2208
        :param witness_type: Use to create key with specific witness_type
2209
        :type witness_type: str
2210
        :param network: Network name. Leave empty for default network
2211
        :type network: str
2212

2213
        :return WalletKey:
2214
        """
2215

2216
        if self.scheme != 'bip32':
3✔
2217
            raise WalletError("We can only create new accounts for a wallet with a BIP32 key scheme")
×
2218
        if self.main_key and (self.main_key.depth != 0 or self.main_key.is_private is False):
3✔
2219
            raise WalletError("A master private key of depth 0 is needed to create new accounts (depth: %d)" %
×
2220
                              self.main_key.depth)
2221
        if "account'" not in self.key_path:
3✔
2222
            raise WalletError("Accounts are not supported for this wallet. Account level not found in key path %s" %
×
2223
                              self.key_path)
2224
        if network is None:
3✔
2225
            network = self.network.name
3✔
2226
        elif network != self.network.name and "coin_type'" not in self.key_path:
3✔
2227
            raise WalletError("Multiple networks not supported by wallet key structure")
×
2228

2229
        duplicate_cointypes = [Network(x).name for x in self.network_list() if Network(x).name != network and
3✔
2230
                               Network(x).bip44_cointype == Network(network).bip44_cointype]
2231
        if duplicate_cointypes:
3✔
2232
            raise WalletError("Can not create new account for network %s with same BIP44 cointype: %s" %
3✔
2233
                              (network, duplicate_cointypes))
2234

2235
        witness_type = witness_type if witness_type else self.witness_type
3✔
2236
        # Determine account_id and name
2237
        if account_id is None:
3✔
2238
            account_id = 0
3✔
2239
            qr = self.session.query(DbKey). \
3✔
2240
                filter_by(wallet_id=self.wallet_id, witness_type=witness_type, network_name=network). \
2241
                order_by(DbKey.account_id.desc()).first()
2242
            if qr:
3✔
2243
                account_id = qr.account_id + 1
3✔
2244
        if self.keys(account_id=account_id, depth=self.depth_public_master, witness_type=witness_type,
3✔
2245
                     network=network):
2246
            raise WalletError("Account with ID %d already exists for this wallet" % account_id)
3✔
2247

2248
        acckey = self.key_for_path([], level_offset=self.depth_public_master-self.key_depth, account_id=account_id,
3✔
2249
                                   name=name, witness_type=witness_type, network=network)
2250
        self.key_for_path([], witness_type=witness_type, network=network, account_id=account_id, change=0,
3✔
2251
                          address_index=0)
2252
        self.key_for_path([], witness_type=witness_type, network=network, account_id=account_id, change=1,
3✔
2253
                          address_index=0)
2254
        return acckey
3✔
2255

2256
    def path_expand(self, path, level_offset=None, account_id=None, cosigner_id=0, address_index=None, change=0,
3✔
2257
                    network=DEFAULT_NETWORK):
2258
        """
2259
        Create key path. Specify part of key path to expand to key path used in this wallet.
2260

2261
        >>> w = Wallet('create_legacy_wallet_test')
2262
        >>> w.path_expand([0,1200])
2263
        ['m', "84'", "0'", "0'", '0', '1200']
2264

2265
        >>> w = Wallet('create_legacy_multisig_wallet_test')
2266
        >>> w.path_expand([0,2], cosigner_id=1)
2267
        ['m', "48'", "0'", "0'", "2'", '0', '2']
2268

2269
        :param path: Part of path, for example [0, 2] for change=0 and address_index=2
2270
        :type path: list, str
2271
        :param level_offset: Just create part of path. For example -2 means create path with the last 2 items (change, address_index) or 1 will return the master key 'm'
2272
        :type level_offset: int
2273
        :param account_id: Account ID
2274
        :type account_id: int
2275
        :param cosigner_id: ID of cosigner
2276
        :type cosigner_id: int
2277
        :param address_index: Index of key, normally provided to 'path' argument
2278
        :type address_index: int
2279
        :param change: Change key = 1 or normal = 0, normally provided to 'path' argument
2280
        :type change: int
2281
        :param network: Network name. Leave empty for default network
2282
        :type network: str
2283

2284
        :return list:
2285
        """
2286
        network, account_id, _ = self._get_account_defaults(network, account_id)
3✔
2287
        return path_expand(path, self.key_path, level_offset, account_id=account_id, cosigner_id=cosigner_id,
3✔
2288
                           address_index=address_index, change=change, purpose=self.purpose,
2289
                           witness_type=self.witness_type, network=network)
2290

2291
    def key_for_path(self, path, level_offset=None, name=None, account_id=None, cosigner_id=None,
3✔
2292
                      address_index=0, change=0, witness_type=None, network=None, recreate=False):
2293
        """
2294
        Wrapper for the keys_for_path method. Returns a single wallet key.
2295

2296
        :param path: Part of key path, i.e. [0, 0] for [change=0, address_index=0]
2297
        :type path: list, str
2298
        :param level_offset: Just create part of path, when creating keys. For example -2 means create path with the last 2 items (change, address_index) or 1 will return the master key 'm'
2299
        :type level_offset: int
2300
        :param name: Specify key name for latest/highest key in structure
2301
        :type name: str
2302
        :param account_id: Account ID
2303
        :type account_id: int
2304
        :param cosigner_id: ID of cosigner
2305
        :type cosigner_id: int
2306
        :param address_index: Index of key, normally provided to 'path' argument
2307
        :type address_index: int
2308
        :param change: Change key = 1 or normal = 0, normally provided to 'path' argument
2309
        :type change: int
2310
        :param witness_type: Use to create key with different witness_type
2311
        :type witness_type: str
2312
        :param network: Network name. Leave empty for default network
2313
        :type network: str
2314
        :param recreate: Recreate key, even if already found in wallet. Can be used to update public key with private key info
2315
        :type recreate: bool
2316

2317
        :return WalletKey:
2318
        """
2319
        return self.keys_for_path(path, level_offset, name, account_id, cosigner_id, address_index, change,
3✔
2320
                                  witness_type, network, recreate, 1)[0]
2321

2322
    def keys_for_path(self, path, level_offset=None, name=None, account_id=None, cosigner_id=None,
3✔
2323
                      address_index=0, change=0, witness_type=None, network=None, recreate=False,
2324
                      number_of_keys=1):
2325
        """
2326
        Return key for specified path. Derive all wallet keys in path if they not already exists
2327

2328
        >>> w = wallet_create_or_open('key_for_path_example')
2329
        >>> key = w.key_for_path([0, 0])
2330
        >>> key.path
2331
        "m/84'/0'/0'/0/0"
2332

2333
        >>> w.key_for_path([], level_offset=-2).path
2334
        "m/84'/0'/0'"
2335

2336
        >>> w.key_for_path([], w.depth_public_master + 1).path
2337
        "m/84'/0'/0'"
2338

2339
        Arguments provided in 'path' take precedence over other arguments. The address_index argument is ignored:
2340
        >>> key = w.key_for_path([0, 10], address_index=1000)
2341
        >>> key.path
2342
        "m/84'/0'/0'/0/10"
2343
        >>> key.address_index
2344
        10
2345

2346
        :param path: Part of key path, i.e. [0, 0] for [change=0, address_index=0]
2347
        :type path: list, str
2348
        :param level_offset: Just create part of path, when creating keys. For example -2 means create path with the last 2 items (change, address_index) or 1 will return the master key 'm'
2349
        :type level_offset: int
2350
        :param name: Specify key name for latest/highest key in structure
2351
        :type name: str
2352
        :param account_id: Account ID
2353
        :type account_id: int
2354
        :param cosigner_id: ID of cosigner
2355
        :type cosigner_id: int
2356
        :param address_index: Index of key, normally provided to 'path' argument
2357
        :type address_index: int
2358
        :param change: Change key = 1 or normal = 0, normally provided to 'path' argument
2359
        :type change: int
2360
        :param witness_type: Use to create key with different witness_type
2361
        :type witness_type: str
2362
        :param network: Network name. Leave empty for default network
2363
        :type network: str
2364
        :param recreate: Recreate key, even if already found in wallet. Can be used to update public key with private key info
2365
        :type recreate: bool
2366
        :param number_of_keys: Number of keys to create, use to create keys in bulk fast
2367
        :type number_of_keys: int
2368

2369
        :return list of WalletKey:
2370
        """
2371

2372
        if number_of_keys == 0:
3✔
2373
            return []
3✔
2374
        network, account_id, _ = self._get_account_defaults(network, account_id)
3✔
2375
        cosigner_id = cosigner_id if cosigner_id is not None else self.cosigner_id
3✔
2376
        level_offset_key = level_offset
3✔
2377
        if level_offset and self.main_key and level_offset > 0:
3✔
2378
            level_offset_key = level_offset - self.main_key.depth
×
2379
        witness_type = witness_type if witness_type else self.witness_type
3✔
2380
        if ((not self.main_key or not self.main_key.is_private or self.main_key.depth != 0) and
3✔
2381
                self.witness_type != witness_type) and not self.multisig:
2382
            raise WalletError("This wallet has no private key, cannot use multiple witness types")
3✔
2383
        key_path = self.key_path
3✔
2384
        purpose = self.purpose
3✔
2385
        encoding = self.encoding
3✔
2386
        if witness_type != self.witness_type:
3✔
2387
            _, purpose, encoding = get_key_structure_data(witness_type, self.multisig)
3✔
2388
        if self.multisig and cosigner_id is not None and len(self.cosigner) > cosigner_id:
3✔
2389
            key_path = self.cosigner[cosigner_id].key_path
3✔
2390
        fullpath = path_expand(path, key_path, level_offset_key, account_id=account_id, cosigner_id=cosigner_id,
3✔
2391
                               purpose=purpose, address_index=address_index, change=change,
2392
                               witness_type=witness_type, network=network)
2393

2394
        if self.multisig and self.cosigner:
3✔
2395
            public_keys = []
3✔
2396
            for wlt in self.cosigner:
3✔
2397
                if wlt.scheme == 'single':
3✔
2398
                    wk = [wlt.main_key]
3✔
2399
                else:
2400
                    wk = wlt.keys_for_path(path, level_offset=level_offset, account_id=account_id, name=name,
3✔
2401
                                          cosigner_id=cosigner_id, network=network, recreate=recreate,
2402
                                          witness_type=witness_type, number_of_keys=number_of_keys, change=change,
2403
                                          address_index=address_index)
2404
                public_keys.append(wk)
3✔
2405
            keys_to_add = [public_keys]
3✔
2406
            if type(public_keys[0]) is list:
3✔
2407
                keys_to_add = list(zip(*public_keys))
3✔
2408
            new_ms_keys = []
3✔
2409
            for ms_key_cosigners in keys_to_add:
3✔
2410
                new_ms_keys.append(self._new_key_multisig(list(ms_key_cosigners), name, account_id, change, cosigner_id,
3✔
2411
                                                      network, address_index, witness_type))
2412
            return new_ms_keys if new_ms_keys else None
3✔
2413

2414
        # Check for closest ancestor in wallet
2415
        wpath = fullpath
3✔
2416
        if self.main_key.depth and fullpath and fullpath[0] != 'M':
3✔
2417
            wpath = ["M"] + fullpath[self.main_key.depth + 1:]
×
2418
        dbkey = None
3✔
2419
        while wpath and not dbkey:
3✔
2420
            qr = self.session.query(DbKey).filter_by(path=normalize_path('/'.join(wpath)), wallet_id=self.wallet_id)
3✔
2421
            if recreate:
3✔
2422
                qr = qr.filter_by(is_private=True)
3✔
2423
            dbkey = qr.first()
3✔
2424
            wpath = wpath[:-1]
3✔
2425
        if not dbkey:
3✔
2426
            _logger.warning("No master or public master key found in this wallet")
×
2427
            return None
×
2428
        else:
2429
            topkey = self.key(dbkey.id)
3✔
2430

2431
        if topkey.network != network and topkey.path.split('/') == fullpath:
3✔
2432
            raise WalletError("Cannot create new keys for network %s, no private masterkey found" % network)
3✔
2433

2434
        # Key already found in db, return key
2435
        if dbkey and dbkey.path == normalize_path('/'.join(fullpath)) and not recreate and number_of_keys == 1:
3✔
2436
            return [topkey]
3✔
2437
        else:
2438
            if dbkey and dbkey.path == normalize_path('/'.join(fullpath)) and not recreate and number_of_keys > 1:
3✔
2439
                new_keys = [topkey]
3✔
2440
            else:
2441
                # Create 1 or more keys add them to wallet
2442
                new_keys = []
3✔
2443

2444
            nkey = None
3✔
2445
            parent_id = topkey.key_id
3✔
2446
            ck = topkey.key()
3✔
2447
            ck.witness_type = witness_type
3✔
2448
            ck.encoding = encoding
3✔
2449
            newpath = topkey.path
3✔
2450
            n_items = len(str(dbkey.path).split('/'))
3✔
2451
            for lvl in fullpath[n_items:]:
3✔
2452
                ck = ck.subkey_for_path(lvl, network=network)
3✔
2453
                newpath += '/' + lvl
3✔
2454
                if not account_id:
3✔
2455
                    account_id = 0 if ("account'" not in self.key_path or
3✔
2456
                                       self.key_path.index("account'") >= len(fullpath)) \
2457
                        else int(fullpath[self.key_path.index("account'")][:-1])
2458
                change_pos = [self.key_path.index(chg) for chg in ["change", "change'"] if chg in self.key_path]
3✔
2459
                change = None if not change_pos or change_pos[0] >= len(fullpath) else (
3✔
2460
                    int(fullpath[change_pos[0]].strip("'")))
2461
                if name and len(fullpath) == len(newpath.split('/')):
3✔
2462
                    key_name = name
3✔
2463
                else:
2464
                    key_name = "%s %s" % (self.key_path[len(newpath.split('/'))-1], lvl)
3✔
2465
                    key_name = key_name.replace("'", "").replace("_", " ")
3✔
2466
                nkey = WalletKey.from_key(key=ck, name=key_name, wallet_id=self.wallet_id, account_id=account_id,
3✔
2467
                                        change=change, purpose=purpose, path=newpath, parent_id=parent_id,
2468
                                        encoding=encoding, witness_type=witness_type,
2469
                                        cosigner_id=cosigner_id, network=network, session=self.session)
2470
                self._key_objects.update({nkey.key_id: nkey})
3✔
2471
                parent_id = nkey.key_id
3✔
2472
            if nkey:
3✔
2473
                new_keys.append(nkey)
3✔
2474
            if len(new_keys) < number_of_keys:
3✔
2475
                parent_id = new_keys[0].parent_id
3✔
2476
                if parent_id not in self._key_objects:
3✔
2477
                    self.key(parent_id)
×
2478
                topkey = self._key_objects[new_keys[0].parent_id]
3✔
2479
                parent_key = topkey.key()
3✔
2480
                new_key_id = self.session.query(DbKey.id).order_by(DbKey.id.desc()).first()[0] + 1
3✔
2481
                hardened_child = False
3✔
2482
                if fullpath[-1].endswith("'"):
3✔
2483
                    hardened_child = True
×
2484
                keys_to_add = [str(k_id) for k_id in range(int(fullpath[-1].strip("'")) + len(new_keys),
3✔
2485
                                                           int(fullpath[-1].strip("'")) + number_of_keys)]
2486

2487
                for key_idx in keys_to_add:
3✔
2488
                    new_key_id += 1
3✔
2489
                    if hardened_child:
3✔
2490
                        key_idx = "%s'" % key_idx
×
2491
                    ck = parent_key.subkey_for_path(key_idx, network=network)
3✔
2492
                    key_name = 'address index %s' % key_idx.strip("'")
3✔
2493
                    newpath = '/'.join(newpath.split('/')[:-1] + [key_idx])
3✔
2494
                    new_keys.append(WalletKey.from_key(
3✔
2495
                        key=ck, name=key_name, wallet_id=self.wallet_id, account_id=account_id,
2496
                        change=change, purpose=purpose, path=newpath, parent_id=parent_id,
2497
                        encoding=encoding, witness_type=witness_type, new_key_id=new_key_id,
2498
                        cosigner_id=cosigner_id, network=network, session=self.session))
2499
                self.session.commit()
3✔
2500

2501
        return new_keys
3✔
2502

2503
    def last_address_index(self, account_id=None, cosigner_id=0, change=0, network=None):
3✔
2504
        """
2505
        Get last used address_index for this wallet
2506

2507
        :param account_id: Account ID
2508
        :type account_id: int
2509
        :param cosigner_id: ID of cosigner
2510
        :type cosigner_id: int
2511
        :param change: Change key = 1 or normal = 0, normally provided to 'path' argument
2512
        :type change: int
2513
        :param network: Network name. Leave empty for default network
2514
        :type network: str
2515

2516
        :return int:
2517
        """
2518
        network, account_id, _ = self._get_account_defaults(network, account_id)
3✔
2519
        last_address_index = 0
3✔
2520
        if not((self.multisig and cosigner_id is not None and
3✔
2521
                (len(self.cosigner) > cosigner_id and self.cosigner[cosigner_id].key_path == 'm' or
2522
                 self.cosigner[cosigner_id].key_path == ['m']))):
2523
            prevkey = self.session.query(DbKey).\
3✔
2524
                filter_by(wallet_id=self.wallet_id, purpose=self.purpose, network_name=network,
2525
                          account_id=account_id, witness_type=self.witness_type, change=change,
2526
                          cosigner_id=cosigner_id, depth=self.key_depth).\
2527
                order_by(DbKey.address_index.desc()).first()
2528
            if prevkey:
3✔
2529
                last_address_index = prevkey.address_index
3✔
2530
        return last_address_index
3✔
2531

2532
    def address_index(self, address_index, account_id=None, cosigner_id=None, change=0, network=None):
3✔
2533
        """
2534
        Get key with specified address_index from wallet. Always returns a key with the specified path, even if it is
2535
        not created yet in wallet. Wrapper for the :func:`key_for_path` method.
2536

2537
        To get an unused key / address use the :func:`get_key` method and to create a new key use the :func:`new_key`
2538
        method.
2539

2540
        :param address_index: address_index of specific key
2541
        :type address_index: int
2542
        :param account_id: Account ID
2543
        :type account_id: int
2544
        :param cosigner_id: ID of cosigner
2545
        :type cosigner_id: int
2546
        :param change: Change key = 1 or normal = 0, normally provided to 'path' argument
2547
        :type change: int
2548
        :param network: Network name. Leave empty for default network
2549
        :type network: str
2550

2551
        :return WalletKey:
2552
        """
2553
        network, account_id, _ = self._get_account_defaults(network, account_id)
3✔
2554
        if address_index > self.last_address_index(account_id, cosigner_id, change, network):
3✔
2555
            raise WalletError("Key with address_index %d not found in wallet. Please create key first" % address_index)
3✔
2556
        if account_id not in self.accounts():
3✔
2557
            raise WalletError("Account %d not found in wallet. Please create account first" % account_id)
3✔
2558
        return self.key_for_path([], address_index=address_index, account_id=account_id, cosigner_id=cosigner_id,
3✔
2559
                                 change=change, network=network)
2560

2561
    def keys(self, account_id=None, name=None, key_id=None, change=None, depth=None, used=None, is_private=None,
3✔
2562
             has_balance=None, is_active=None, witness_type=None, network=None, include_private=False, as_dict=False):
2563
        """
2564
        Search for keys in database. Include 0 or more of account_id, name, key_id, change and depth.
2565

2566
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
2567
        >>> all_wallet_keys = w.keys()
2568
        >>> w.keys(depth=0) # doctest:+ELLIPSIS
2569
        [<DbKey(id=..., name='bitcoinlib_legacy_wallet_test', wif='xprv9s21ZrQH143K3cxbMVswDTYgAc9CeXABQjCD9zmXCpXw4MxN93LanEARbBmV3utHZS9Db4FX1C1RbC5KSNAjQ5WNJ1dDBJ34PjfiSgRvS8x'>]
2570

2571
        Returns a list of DbKey object or dictionary object if as_dict is True
2572

2573
        :param account_id: Search for account ID
2574
        :type account_id: int
2575
        :param name: Search for Name
2576
        :type name: str
2577
        :param key_id: Search for Key ID
2578
        :type key_id: int
2579
        :param change: Search for Change
2580
        :type change: int
2581
        :param depth: Only include keys with this depth
2582
        :type depth: int
2583
        :param used: Only return used or unused keys
2584
        :type used: bool
2585
        :param is_private: Only return private keys
2586
        :type is_private: bool
2587
        :param has_balance: Only include keys with a balance or without a balance, default is both
2588
        :type has_balance: bool
2589
        :param is_active: Hide inactive keys. Only include active keys with either a balance or which are unused, default is None (show all)
2590
        :type is_active: bool
2591
        :param witness_type: Filter by witness_type
2592
        :type witness_type: str
2593
        :param network: Network name filter
2594
        :type network: str
2595
        :param include_private: Include private key information in dictionary
2596
        :type include_private: bool
2597
        :param as_dict: Return keys as dictionary objects. Default is False: DbKey objects
2598
        :type as_dict: bool
2599

2600
        :return list of DbKey: List of Keys
2601
        """
2602

2603
        qr = self.session.query(DbKey).filter_by(wallet_id=self.wallet_id).order_by(DbKey.id)
3✔
2604
        if network is not None:
3✔
2605
            qr = qr.filter(DbKey.network_name == network)
3✔
2606
        if witness_type is not None:
3✔
2607
            qr = qr.filter(DbKey.witness_type == witness_type)
3✔
2608
        if account_id is not None:
3✔
2609
            qr = qr.filter(DbKey.account_id == account_id)
3✔
2610
            if self.scheme == 'bip32' and depth is None:
3✔
2611
                qr = qr.filter(DbKey.depth >= 3)
3✔
2612
        if change is not None:
3✔
2613
            qr = qr.filter(DbKey.change == change)
3✔
2614
            if self.scheme == 'bip32' and depth is None:
3✔
2615
                qr = qr.filter(DbKey.depth > self.key_depth - 1)
×
2616
        if depth is not None:
3✔
2617
            qr = qr.filter(DbKey.depth == depth)
3✔
2618
        if name is not None:
3✔
2619
            qr = qr.filter(DbKey.name == name)
3✔
2620
        if key_id is not None:
3✔
2621
            qr = qr.filter(DbKey.id == key_id)
3✔
2622
            is_active = False
3✔
2623
        elif used is not None:
3✔
2624
            qr = qr.filter(DbKey.used == used)
×
2625
        if is_private is not None:
3✔
2626
            qr = qr.filter(DbKey.is_private == is_private)
3✔
2627
        if has_balance is True and is_active is True:
3✔
2628
            raise WalletError("Cannot use has_balance and is_active parameter together")
×
2629
        if has_balance is not None:
3✔
2630
            if has_balance:
×
2631
                qr = qr.filter(DbKey.balance != 0)
×
2632
            else:
2633
                qr = qr.filter(DbKey.balance == 0)
×
2634
        if is_active:  # Unused keys and keys with a balance
3✔
2635
            qr = qr.filter(or_(DbKey.balance != 0, DbKey.used.is_(False)))
3✔
2636
        keys = qr.order_by(DbKey.depth).all()
3✔
2637
        if as_dict:
3✔
2638
            keys = [x.__dict__ for x in keys]
3✔
2639
            keys2 = []
3✔
2640
            private_fields = []
3✔
2641
            if not include_private:
3✔
2642
                private_fields += ['private', 'wif']
3✔
2643
            for key in keys:
3✔
2644
                keys2.append({k: v for (k, v) in key.items()
3✔
2645
                              if k[:1] != '_' and k != 'wallet' and k not in private_fields})
2646
            return keys2
3✔
2647
        # qr.session.close()
2648
        qr.session.commit()
3✔
2649
        return keys
3✔
2650

2651
    def keys_networks(self, used=None, as_dict=False):
3✔
2652
        """
2653
        Get keys of defined networks for this wallet. Wrapper for the :func:`keys` method
2654

2655
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
2656
        >>> network_key = w.keys_networks()
2657
        >>> network_key[0].path
2658
        "m/44'/0'"
2659

2660
        :param used: Only return used or unused keys
2661
        :type used: bool
2662
        :param as_dict: Return as dictionary or DbKey object. Default is False: DbKey objects
2663
        :type as_dict: bool
2664

2665
        :return list of DbKey, list of dict:
2666

2667
        """
2668

2669
        if self.scheme != 'bip32':
3✔
2670
            raise WalletError("The 'keys_network' method can only be used with BIP32 type wallets")
×
2671
        try:
3✔
2672
            depth = self.key_path.index("coin_type'")
3✔
2673
        except ValueError:
3✔
2674
            return []
3✔
2675
        if self.multisig and self.cosigner:
3✔
2676
            _logger.warning("No network keys available for multisig wallet, use networks() method for list of networks")
×
2677
        return self.keys(depth=depth, used=used, as_dict=as_dict)
3✔
2678

2679
    def keys_accounts(self, account_id=None, network=DEFAULT_NETWORK, as_dict=False):
3✔
2680
        """
2681
        Get Database records of account key(s) with for current wallet. Wrapper for the :func:`keys` method.
2682

2683
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
2684
        >>> account_key = w.keys_accounts()
2685
        >>> account_key[0].path
2686
        "m/44'/0'/0'"
2687

2688
        Returns nothing if no account keys are available for instance in multisig or single account wallets. In this case use :func:`accounts` method instead.
2689

2690
        :param account_id: Search for Account ID
2691
        :type account_id: int
2692
        :param network: Network name filter
2693
        :type network: str
2694
        :param as_dict: Return as dictionary or DbKey object. Default is False: DbKey objects
2695
        :type as_dict: bool
2696

2697
        :return list of (DbKey, dict):
2698
        """
2699

2700
        return self.keys(account_id, depth=self.depth_public_master, network=network, as_dict=as_dict)
3✔
2701

2702
    def keys_addresses(self, account_id=None, used=None, is_active=None, change=None, network=None, depth=None,
3✔
2703
                       as_dict=False):
2704
        """
2705
        Get address keys of specified account_id for current wallet. Wrapper for the :func:`keys` methods.
2706

2707
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
2708
        >>> w.keys_addresses()[0].address
2709
        '16QaHuFkfuebXGcYHmehRXBBX7RG9NbtLg'
2710

2711
        :param account_id: Account ID
2712
        :type account_id: int
2713
        :param used: Only return used or unused keys
2714
        :type used: bool
2715
        :param is_active: Hide inactive keys. Only include active keys with either a balance or which are unused, default is True
2716
        :type is_active: bool
2717
        :param change: Search for Change
2718
        :type change: int
2719
        :param network: Network name filter
2720
        :type network: str
2721
        :param depth: Filter by key depth. Default for BIP44 and multisig is 5
2722
        :type depth: int
2723
        :param as_dict: Return as dictionary or DbKey object. Default is False: DbKey objects
2724
        :type as_dict: bool
2725

2726
        :return list of (DbKey, dict)
2727
        """
2728

2729
        if depth is None:
3✔
2730
            depth = self.key_depth
3✔
2731
        return self.keys(account_id, depth=depth, used=used, change=change, is_active=is_active, network=network,
3✔
2732
                         as_dict=as_dict)
2733

2734
    def keys_address_payment(self, account_id=None, used=None, network=None, as_dict=False):
3✔
2735
        """
2736
        Get payment addresses (change=0) of specified account_id for current wallet. Wrapper for the :func:`keys` methods.
2737

2738
        :param account_id: Account ID
2739
        :type account_id: int
2740
        :param used: Only return used or unused keys
2741
        :type used: bool
2742
        :param network: Network name filter
2743
        :type network: str
2744
        :param as_dict: Return as dictionary or DbKey object. Default is False: DbKey objects
2745
        :type as_dict: bool
2746

2747
        :return list of (DbKey, dict)
2748
        """
2749

2750
        return self.keys(account_id, depth=self.key_depth, change=0, used=used, network=network, as_dict=as_dict)
3✔
2751

2752
    def keys_address_change(self, account_id=None, used=None, network=None, as_dict=False):
3✔
2753
        """
2754
        Get payment addresses (change=1) of specified account_id for current wallet. Wrapper for the :func:`keys` methods.
2755

2756
        :param account_id: Account ID
2757
        :type account_id: int
2758
        :param used: Only return used or unused keys
2759
        :type used: bool
2760
        :param network: Network name filter
2761
        :type network: str
2762
        :param as_dict: Return as dictionary or DbKey object. Default is False: DbKey objects
2763
        :type as_dict: bool
2764

2765
        :return list of (DbKey, dict)
2766
        """
2767

2768
        return self.keys(account_id, depth=self.key_depth, change=1, used=used, network=network, as_dict=as_dict)
3✔
2769

2770
    def addresslist(self, account_id=None, used=None, network=None, change=None, depth=None, key_id=None):
3✔
2771
        """
2772
        Get list of addresses defined in current wallet. Wrapper for the :func:`keys` methods.
2773

2774
        Use :func:`keys_addresses` method to receive full key objects
2775

2776
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
2777
        >>> w.addresslist()[0]
2778
        '16QaHuFkfuebXGcYHmehRXBBX7RG9NbtLg'
2779

2780
        :param account_id: Account ID
2781
        :type account_id: int
2782
        :param used: Only return used or unused keys
2783
        :type used: bool, None
2784
        :param network: Network name filter
2785
        :type network: str
2786
        :param change: Only include change addresses or not. Default is None which returns both
2787
        :param depth: Filter by key depth. Default is None for standard key depth. Use -1 to show all keys
2788
        :type depth: int
2789
        :param key_id: Key ID to get address of just 1 key
2790
        :type key_id: int
2791

2792
        :return list of str: List of address strings
2793
        """
2794

2795
        addresslist = []
3✔
2796
        if depth is None:
3✔
2797
            depth = self.key_depth
3✔
2798
        elif depth == -1:
3✔
2799
            depth = None
3✔
2800
        for key in self.keys(account_id=account_id, depth=depth, used=used, network=network, change=change,
3✔
2801
                             key_id=key_id, is_active=False):
2802
            addresslist.append(key.address)
3✔
2803
        return addresslist
3✔
2804

2805
    def key(self, term):
3✔
2806
        """
2807
        Return single key with given ID or name as WalletKey object
2808

2809
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
2810
        >>> w.key('change 0').address
2811
        '1HabJXe8mTwXiMzUWW5KdpYbFWu3hvtsbF'
2812

2813
        :param term: Search term can be key ID, key address, key WIF or key name
2814
        :type term: int, str
2815

2816
        :return WalletKey: Single key as object
2817
        """
2818

2819
        dbkey = None
3✔
2820
        qr = self.session.query(DbKey).filter_by(wallet_id=self.wallet_id)
3✔
2821
        if isinstance(term, numbers.Number):
3✔
2822
            dbkey = qr.filter_by(id=term).scalar()
3✔
2823
        if not dbkey:
3✔
2824
            dbkey = qr.filter_by(address=term).first()
×
2825
        if not dbkey:
3✔
2826
            dbkey = qr.filter_by(wif=term).first()
×
2827
        if not dbkey:
3✔
2828
            dbkey = qr.filter_by(name=term).first()
×
2829
        if dbkey:
3✔
2830
            if dbkey.id in self._key_objects.keys():
3✔
2831
                return self._key_objects[dbkey.id]
3✔
2832
            else:
2833
                hdwltkey = WalletKey(key_id=dbkey.id, session=self.session)
3✔
2834
                self._key_objects.update({dbkey.id: hdwltkey})
3✔
2835
                return hdwltkey
3✔
2836
        else:
2837
            raise BKeyError("Key '%s' not found" % term)
×
2838

2839
    def account(self, account_id):
3✔
2840
        """
2841
        Returns wallet key of specific BIP44 account.
2842

2843
        Account keys have a BIP44 path depth of 3 and have the format m/purpose'/network'/account'
2844

2845
        I.e: Use account(0).key().wif_public() to get wallet's public master key
2846

2847
        :param account_id: ID of account. Default is 0
2848
        :type account_id: int
2849

2850
        :return WalletKey:
2851
        """
2852

2853
        if "account'" not in self.key_path:
3✔
2854
            raise WalletError("Accounts are not supported for this wallet. Account not found in key path %s" %
3✔
2855
                              self.key_path)
2856
        qr = self.session.query(DbKey).\
3✔
2857
            filter_by(wallet_id=self.wallet_id, purpose=self.purpose, network_name=self.network.name,
2858
                      account_id=account_id, depth=3).scalar()
2859
        if not qr:
3✔
2860
            raise WalletError("Account with ID %d not found in this wallet" % account_id)
3✔
2861
        key_id = qr.id
3✔
2862
        return self.key(key_id)
3✔
2863

2864
    def accounts(self, network=None):
3✔
2865
        """
2866
        Get list of accounts for this wallet
2867

2868
        :param network: Network name filter. Default filter is network of first main key
2869
        :type network: str
2870

2871
        :return list of integers: List of accounts IDs
2872
        """
2873

2874
        network, _, _ = self._get_account_defaults(network)
3✔
2875
        if self.multisig and self.cosigner:
3✔
2876
            if self.cosigner_id is None:
3✔
2877
                raise WalletError("Missing Cosigner ID value for this wallet, cannot fetch account ID")
×
2878
            accounts = [wk.account_id for wk in self.cosigner[self.cosigner_id].keys_accounts(network=network)]
3✔
2879
        else:
2880
            accounts = [wk.account_id for wk in self.keys_accounts(network=network)]
3✔
2881
        if not accounts:
3✔
2882
            accounts = [self.default_account_id]
×
2883
        return list(dict.fromkeys(accounts))
3✔
2884

2885
    def witness_types(self, account_id=None, network=None):
3✔
2886
        """
2887
        Get witness types in use by this wallet. For example 'legacy', 'segwit', 'p2sh-segwit'
2888

2889
        :param account_id: Account ID. Leave empty for default account
2890
        :type account_id: int
2891
        :param network: Network name filter. Default filter is DEFAULT_NETWORK
2892
        :type network: str
2893

2894
        :return list of str:
2895
        """
2896

2897
        qr = self.session.query(DbKey.witness_type).filter_by(wallet_id=self.wallet_id)
3✔
2898
        if network is not None:
3✔
2899
            qr = qr.filter(DbKey.network_name == network)
3✔
2900
        if account_id is not None:
3✔
2901
            qr = qr.filter(DbKey.account_id == account_id)
3✔
2902
        qr = qr.group_by(DbKey.witness_type).all()
3✔
2903
        return [x[0] for x in qr] if qr else [self.witness_type]
3✔
2904

2905
    def networks(self, as_dict=False):
3✔
2906
        """
2907
        Get list of networks used by this wallet
2908

2909
        :param as_dict: Return as dictionary or as Network objects, default is Network objects
2910
        :type as_dict: bool
2911

2912
        :return list of (Network, dict):
2913
        """
2914

2915
        nw_list = [self.network]
3✔
2916
        if self.multisig and self.cosigner:
3✔
2917
            keys_qr = self.session.query(DbKey.network_name).\
3✔
2918
                filter_by(wallet_id=self.wallet_id, depth=self.key_depth).\
2919
                group_by(DbKey.network_name).all()
2920
            nw_list += [Network(nw[0]) for nw in keys_qr]
3✔
2921
        elif self.main_key.key_type != 'single':
3✔
2922
            wks = self.keys_networks()
3✔
2923
            for wk in wks:
3✔
2924
                nw_list.append(Network(wk.network_name))
3✔
2925

2926
        networks = []
3✔
2927
        nw_list = list(dict.fromkeys(nw_list))
3✔
2928
        for nw in nw_list:
3✔
2929
            if as_dict:
3✔
2930
                nw = nw.__dict__
×
2931
                if '_sa_instance_state' in nw:
×
2932
                    del nw['_sa_instance_state']
×
2933
            networks.append(nw)
3✔
2934

2935
        return networks
3✔
2936

2937
    def network_list(self, field='name'):
3✔
2938
        """
2939
        Wrapper for :func:`networks` method, returns a flat list with currently used
2940
        networks for this wallet.
2941

2942
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
2943
        >>> w.network_list()
2944
        ['bitcoin']
2945

2946
        :return list of str:
2947
        """
2948

2949
        return [getattr(x, field) for x in self.networks()]
3✔
2950

2951
    def balance_update_from_serviceprovider(self, account_id=None, network=None):
3✔
2952
        """
2953
        Update balance of currents account addresses using default Service objects :func:`getbalance` method. Update total
2954
        wallet balance in database.
2955

2956
        Please Note: Does not update UTXO's or the balance per key! For this use the :func:`updatebalance` method
2957
        instead
2958

2959
        :param account_id: Account ID. Leave empty for default account
2960
        :type account_id: int
2961
        :param network: Network name. Leave empty for default network
2962
        :type network: str
2963

2964
        :return int: Total balance
2965
        """
2966

2967
        network, account_id, acckey = self._get_account_defaults(network, account_id)
3✔
2968
        srv = Service(network=network, wallet_name=self.name, providers=self.providers, cache_uri=self.db_cache_uri)
3✔
2969
        balance = srv.getbalance(self.addresslist(account_id=account_id, network=network))
3✔
2970
        if srv.results:
3✔
2971
            new_balance = {
3✔
2972
                'account_id': account_id,
2973
                'network': network,
2974
                'balance': balance
2975
            }
2976
            old_balance_item = [bi for bi in self._balances if bi['network'] == network and
3✔
2977
                                bi['account_id'] == account_id]
2978
            if old_balance_item:
3✔
2979
                item_n = self._balances.index(old_balance_item[0])
×
2980
                self._balances[item_n] = new_balance
×
2981
            else:
2982
                self._balances.append(new_balance)
3✔
2983
        return balance
3✔
2984

2985
    def balance(self, account_id=None, network=None, as_string=False):
3✔
2986
        """
2987
        Get total of unspent outputs
2988

2989
        :param account_id: Account ID filter
2990
        :type account_id: int
2991
        :param network: Network name. Leave empty for default network
2992
        :type network: str
2993
        :param as_string: Set True to return a string in currency format. Default returns float.
2994
        :type as_string: boolean
2995

2996
        :return float, str: Key balance
2997
        """
2998

2999
        self._balance_update(account_id, network)
3✔
3000
        network, account_id, _ = self._get_account_defaults(network, account_id)
3✔
3001

3002
        balance = 0
3✔
3003
        b_res = [b['balance'] for b in self._balances if b['account_id'] == account_id and b['network'] == network]
3✔
3004
        if len(b_res):
3✔
3005
            balance = b_res[0]
3✔
3006
        if as_string:
3✔
3007
            return Value.from_satoshi(balance, network=network).str_unit()
3✔
3008
        else:
3009
            return float(balance)
3✔
3010

3011
    def _balance_update(self, account_id=None, network=None, key_id=None, min_confirms=0):
3✔
3012
        """
3013
        Update balance from UTXO's in database. To get most recent balance use :func:`utxos_update` first.
3014

3015
        Also updates balance of wallet and keys in this wallet for the specified account or all accounts if
3016
        no account is specified.
3017

3018
        :param account_id: Account ID filter
3019
        :type account_id: int
3020
        :param network: Network name. Leave empty for default network
3021
        :type network: str
3022
        :param key_id: Key ID Filter
3023
        :type key_id: int
3024
        :param min_confirms: Minimal confirmations needed to include in balance (default = 0)
3025
        :type min_confirms: int
3026

3027
        :return: Updated balance
3028
        """
3029

3030
        qr = self.session.query(DbTransactionOutput, func.sum(DbTransactionOutput.value), DbTransaction.network_name,
3✔
3031
                                 DbTransaction.account_id).\
3032
            join(DbTransaction). \
3033
            filter(DbTransactionOutput.spent.is_(False),
3034
                   DbTransaction.wallet_id == self.wallet_id,
3035
                   DbTransaction.confirmations >= min_confirms)
3036
        if account_id is not None:
3✔
3037
            qr = qr.filter(DbTransaction.account_id == account_id)
3✔
3038
        if network is not None:
3✔
3039
            qr = qr.filter(DbTransaction.network_name == network)
3✔
3040
        if key_id is not None:
3✔
3041
            qr = qr.filter(DbTransactionOutput.key_id == key_id)
3✔
3042
        else:
3043
            qr = qr.filter(DbTransactionOutput.key_id.isnot(None))
3✔
3044
        utxos = qr.group_by(
3✔
3045
            DbTransactionOutput.key_id,
3046
            DbTransactionOutput.transaction_id,
3047
            DbTransactionOutput.output_n,
3048
            DbTransaction.network_name,
3049
            DbTransaction.account_id
3050
        ).all()
3051

3052
        key_values = [
3✔
3053
            {
3054
                'id': utxo[0].key_id,
3055
                'network': utxo[2],
3056
                'account_id': utxo[3],
3057
                'balance': utxo[1]
3058
            }
3059
            for utxo in utxos
3060
        ]
3061

3062
        grouper = itemgetter("id", "network", "account_id")
3✔
3063
        key_balance_list = []
3✔
3064
        for key, grp in groupby(sorted(key_values, key=grouper), grouper):
3✔
3065
            nw_acc_dict = dict(zip(["id", "network", "account_id"], key))
3✔
3066
            nw_acc_dict["balance"] = sum(item["balance"] for item in grp)
3✔
3067
            key_balance_list.append(nw_acc_dict)
3✔
3068

3069
        grouper = itemgetter("network", "account_id")
3✔
3070
        balance_list = []
3✔
3071
        for key, grp in groupby(sorted(key_balance_list, key=grouper), grouper):
3✔
3072
            nw_acc_dict = dict(zip(["network", "account_id"], key))
3✔
3073
            nw_acc_dict["balance"] = sum(item["balance"] for item in grp)
3✔
3074
            balance_list.append(nw_acc_dict)
3✔
3075

3076
        # Add keys with no UTXO's with 0 balance
3077
        for key in self.keys(account_id=account_id, network=network, key_id=key_id):
3✔
3078
            if key.id not in [utxo[0].key_id for utxo in utxos]:
3✔
3079
                key_balance_list.append({
3✔
3080
                    'id': key.id,
3081
                    'network': network,
3082
                    'account_id': key.account_id,
3083
                    'balance': 0
3084
                })
3085

3086
        if not key_id:
3✔
3087
            for bl in balance_list:
3✔
3088
                bl_item = [b for b in self._balances if
3✔
3089
                           b['network'] == bl['network'] and b['account_id'] == bl['account_id']]
3090
                if not bl_item:
3✔
3091
                    self._balances.append(bl)
3✔
3092
                    continue
3✔
3093
                lx = self._balances.index(bl_item[0])
3✔
3094
                self._balances[lx].update(bl)
3✔
3095

3096
        self._balance = sum([b['balance'] for b in balance_list if b['network'] == self.network.name])
3✔
3097

3098
        # Bulk update database
3099
        for kb in key_balance_list:
3✔
3100
            if kb['id'] in self._key_objects:
3✔
3101
                self._key_objects[kb['id']]._balance = kb['balance']
3✔
3102
        self.session.bulk_update_mappings(DbKey, key_balance_list)
3✔
3103
        self._commit()
3✔
3104
        _logger.info("Got balance for %d key(s)" % len(key_balance_list))
3✔
3105
        return self._balances
3✔
3106

3107
    def utxos_update(self, account_id=None, used=None, networks=None, key_id=None, depth=None, change=None,
3✔
3108
                     utxos=None, update_balance=True, max_utxos=MAX_TRANSACTIONS, rescan_all=True):
3109
        """
3110
        Update UTXO's (Unspent Outputs) for addresses/keys in this wallet using various Service providers.
3111

3112
        This method does not import transactions: use :func:`transactions_update` function or to look for new addresses use :func:`scan`.
3113

3114
        :param account_id: Account ID
3115
        :type account_id: int
3116
        :param used: Only check for UTXO for used or unused keys. Default is both
3117
        :type used: bool
3118
        :param networks: Network name filter as string or list of strings. Leave empty to update all used networks in wallet
3119
        :type networks: str, list
3120
        :param key_id: Key ID to just update 1 key
3121
        :type key_id: int
3122
        :param depth: Only update keys with this depth, default is depth 5 according to BIP0048 standard. Set depth to None to update all keys of this wallet.
3123
        :type depth: int
3124
        :param change: Only update change or normal keys, default is both (None)
3125
        :type change: int
3126
        :param utxos: List of unspent outputs in dictionary format specified below. For usage on an offline PC, you can import utxos with the utxos parameter as a list of dictionaries
3127
        :type utxos: list of dict.
3128

3129
        .. code-block:: json
3130

3131
            {
3132
              "address": "n2S9Czehjvdmpwd2YqekxuUC1Tz5ZdK3YN",
3133
              "script": "",
3134
              "confirmations": 10,
3135
              "output_n": 1,
3136
              "txid": "9df91f89a3eb4259ce04af66ad4caf3c9a297feea5e0b3bc506898b6728c5003",
3137
              "value": 8970937
3138
            }
3139

3140
        :param update_balance: Option to disable balance update after fetching UTXO's. Can be used when utxos_update method is called several times in a row. Default is True
3141
        :type update_balance: bool
3142
        :param max_utxos: Maximum number of UTXO's to update
3143
        :type max_utxos: int
3144
        :param rescan_all: Remove old utxo's and rescan wallet. Default is True. Set to False if you work with large utxo's sets. Value will be ignored if key_id is specified in your call
3145
        :type rescan_all: bool
3146

3147
        :return int: Number of new UTXO's added
3148
        """
3149

3150
        _, account_id, acckey = self._get_account_defaults('', account_id, key_id)
3✔
3151

3152
        single_key = None
3✔
3153
        if key_id:
3✔
3154
            single_key = self.session.query(DbKey).filter_by(id=key_id).scalar()
3✔
3155
            networks = [single_key.network_name]
3✔
3156
            account_id = single_key.account_id
3✔
3157
            rescan_all = False
3✔
3158
        if networks is None:
3✔
3159
            networks = self.network_list()
3✔
3160
        elif not isinstance(networks, list):
3✔
3161
            networks = [networks]
3✔
3162
        elif len(networks) != 1 and utxos is not None:
3✔
3163
            raise WalletError("Please specify maximum 1 network when passing utxo's")
×
3164

3165
        count_utxos = 0
3✔
3166
        for network in networks:
3✔
3167
            # Remove current UTXO's
3168
            if rescan_all:
3✔
3169
                cur_utxos = self.session.query(DbTransactionOutput). \
3✔
3170
                    join(DbTransaction). \
3171
                    filter(DbTransactionOutput.spent.is_(False),
3172
                           DbTransaction.account_id == account_id,
3173
                           DbTransaction.wallet_id == self.wallet_id,
3174
                           DbTransaction.network_name == network).all()
3175
                for u in cur_utxos:
3✔
3176
                    self.session.query(DbTransactionOutput).filter_by(
×
3177
                        transaction_id=u.transaction_id, output_n=u.output_n).update({DbTransactionOutput.spent: True})
3178
                self._commit()
3✔
3179

3180
            if account_id is None and not self.multisig:
3✔
3181
                accounts = self.accounts(network=network)
×
3182
            else:
3183
                accounts = [account_id]
3✔
3184
            for account_id in accounts:
3✔
3185
                if depth is None:
3✔
3186
                    depth = self.key_depth
3✔
3187
                if utxos is None:
3✔
3188
                    # Get all UTXO's for this wallet from default Service object
3189
                    addresslist = self.addresslist(account_id=account_id, used=used, network=network, key_id=key_id,
3✔
3190
                                                   change=change, depth=depth)
3191
                    random.shuffle(addresslist)
3✔
3192
                    srv = Service(network=network, wallet_name=self.name, providers=self.providers, cache_uri=self.db_cache_uri)
3✔
3193
                    utxos = []
3✔
3194
                    for address in addresslist:
3✔
3195
                        if rescan_all:
3✔
3196
                            last_txid = ''
3✔
3197
                        else:
3198
                            last_txid = self.utxo_last(address)
3✔
3199
                        new_utxos = srv.getutxos(address, after_txid=last_txid, limit=max_utxos)
3✔
3200
                        if new_utxos:
3✔
3201
                            utxos += new_utxos
3✔
3202
                        elif new_utxos is False:
3✔
3203
                            raise WalletError("No response from any service provider, could not update UTXO's. "
×
3204
                                              "Errors: %s" % srv.errors)
3205
                    if srv.complete:
3✔
3206
                        self.last_updated = datetime.now(timezone.utc)
×
3207
                    elif utxos and 'date' in utxos[-1:][0]:
3✔
3208
                        self.last_updated = utxos[-1:][0]['date']
3✔
3209

3210
                # If UTXO is new, add to database otherwise update depth (confirmation count)
3211
                for utxo in utxos:
3✔
3212
                    key = single_key
3✔
3213
                    if not single_key:
3✔
3214
                        key = self.session.query(DbKey).\
3✔
3215
                            filter_by(wallet_id=self.wallet_id, address=utxo['address']).scalar()
3216
                    if not key:
3✔
3217
                        raise WalletError("Key with address %s not found in this wallet" % utxo['address'])
×
3218
                    key.used = True
3✔
3219
                    status = 'unconfirmed'
3✔
3220
                    if utxo['confirmations']:
3✔
3221
                        status = 'confirmed'
3✔
3222

3223
                    # Update confirmations in db if utxo was already imported
3224
                    transaction_in_db = self.session.query(DbTransaction).\
3✔
3225
                        filter_by(wallet_id=self.wallet_id, txid=bytes.fromhex(utxo['txid']),
3226
                                  network_name=network)
3227
                    utxo_in_db = self.session.query(DbTransactionOutput).join(DbTransaction).\
3✔
3228
                        filter(DbTransaction.wallet_id == self.wallet_id,
3229
                               DbTransaction.txid == bytes.fromhex(utxo['txid']),
3230
                               DbTransactionOutput.output_n == utxo['output_n'])
3231
                    spent_in_db = self.session.query(DbTransactionInput).join(DbTransaction).\
3✔
3232
                        filter(DbTransaction.wallet_id == self.wallet_id,
3233
                               DbTransactionInput.prev_txid == bytes.fromhex(utxo['txid']),
3234
                               DbTransactionInput.output_n == utxo['output_n'])
3235
                    if utxo_in_db.count():
3✔
3236
                        utxo_record = utxo_in_db.scalar()
3✔
3237
                        if not utxo_record.key_id:
3✔
3238
                            count_utxos += 1
×
3239
                        utxo_record.key_id = key.id
3✔
3240
                        utxo_record.spent = bool(spent_in_db.count())
3✔
3241
                        if transaction_in_db.count():
3✔
3242
                            transaction_record = transaction_in_db.scalar()
3✔
3243
                            transaction_record.confirmations = utxo['confirmations']
3✔
3244
                            transaction_record.status = status
3✔
3245
                    else:
3246
                        # Add transaction if not exist and then add output
3247
                        if not transaction_in_db.count():
3✔
3248
                            block_height = None
3✔
3249
                            if block_height in utxo and utxo['block_height']:
3✔
3250
                                block_height = utxo['block_height']
×
3251
                            new_tx = DbTransaction(
3✔
3252
                                wallet_id=self.wallet_id, txid=bytes.fromhex(utxo['txid']), status=status,
3253
                                is_complete=False, block_height=block_height, account_id=account_id,
3254
                                confirmations=utxo['confirmations'], network_name=network)
3255
                            self.session.add(new_tx)
3✔
3256
                            # TODO: Get unique id before inserting to increase performance for large utxo-sets
3257
                            self._commit()
3✔
3258
                            tid = new_tx.id
3✔
3259
                        else:
3260
                            tid = transaction_in_db.scalar().id
3✔
3261

3262
                        script_type = script_type_default(self.witness_type, multisig=self.multisig,
3✔
3263
                                                          locking_script=True)
3264
                        new_utxo = DbTransactionOutput(transaction_id=tid,  output_n=utxo['output_n'],
3✔
3265
                                                       value=utxo['value'], key_id=key.id, address=utxo['address'],
3266
                                                       script=bytes.fromhex(utxo['script']),
3267
                                                       script_type=script_type,
3268
                                                       spent=bool(spent_in_db.count()))
3269
                        self.session.add(new_utxo)
3✔
3270
                        count_utxos += 1
3✔
3271

3272
                    self._commit()
3✔
3273

3274
                _logger.info("Got %d new UTXOs for account %s" % (count_utxos, account_id))
3✔
3275
                self._commit()
3✔
3276
                if update_balance:
3✔
3277
                    self._balance_update(account_id=account_id, network=network, key_id=key_id, min_confirms=0)
3✔
3278
                utxos = None
3✔
3279
        return count_utxos
3✔
3280

3281
    def utxos(self, account_id=None, network=None, min_confirms=0, key_id=None):
3✔
3282
        """
3283
        Get UTXO's (Unspent Outputs) from database. Use :func:`utxos_update` method first for updated values
3284

3285
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
3286
        >>> w.utxos()  # doctest:+SKIP
3287
        [{'value': 100000000, 'script': '', 'output_n': 0, 'transaction_id': ..., 'spent': False, 'script_type': 'p2pkh', 'key_id': ..., 'address': '16QaHuFkfuebXGcYHmehRXBBX7RG9NbtLg', 'confirmations': 0, 'txid': '748799c9047321cb27a6320a827f1f69d767fe889c14bf11f27549638d566fe4', 'network_name': 'bitcoin'}]
3288

3289
        :param account_id: Account ID
3290
        :type account_id: int
3291
        :param network: Network name. Leave empty for default network
3292
        :type network: str
3293
        :param min_confirms: Minimal confirmation needed to include in output list
3294
        :type min_confirms: int
3295
        :param key_id: Key ID or list of key IDs to filter utxo's for specific keys
3296
        :type key_id: int, list
3297

3298
        :return list: List of transactions
3299
        """
3300

3301
        first_key_id = key_id
3✔
3302
        if isinstance(key_id, list):
3✔
3303
            first_key_id = key_id[0]
3✔
3304
        network, account_id, acckey = self._get_account_defaults(network, account_id, first_key_id)
3✔
3305

3306
        qr = self.session.query(DbTransactionOutput, DbKey.address, DbTransaction.confirmations, DbTransaction.txid,
3✔
3307
                                 DbKey.network_name).\
3308
            join(DbTransaction).join(DbKey). \
3309
            filter(DbTransactionOutput.spent.is_(False),
3310
                   DbTransaction.account_id == account_id,
3311
                   DbTransaction.wallet_id == self.wallet_id,
3312
                   DbTransaction.network_name == network,
3313
                   DbTransaction.confirmations >= min_confirms)
3314
        if isinstance(key_id, int):
3✔
3315
            qr = qr.filter(DbKey.id == key_id)
×
3316
        elif isinstance(key_id, list):
3✔
3317
            qr = qr.filter(DbKey.id.in_(key_id))
3✔
3318
        utxos = qr.order_by(DbTransaction.confirmations.desc()).all()
3✔
3319
        res = []
3✔
3320
        for utxo in utxos:
3✔
3321
            u = utxo[0].__dict__
3✔
3322
            if '_sa_instance_state' in u:
3✔
3323
                del u['_sa_instance_state']
3✔
3324
            u['address'] = utxo[1]
3✔
3325
            u['confirmations'] = int(utxo[2])
3✔
3326
            u['txid'] = utxo[3].hex()
3✔
3327
            u['network_name'] = utxo[4]
3✔
3328
            res.append(u)
3✔
3329
        return res
3✔
3330

3331
    def utxo_add(self, address, value, txid, output_n, confirmations=1, script=''):
3✔
3332
        """
3333
        Add a single UTXO to the wallet database. To update all utxo's use :func:`utxos_update` method.
3334

3335
        Use this method for testing, offline wallets or if you wish to override standard method of retrieving UTXO's
3336

3337
        This method does not check if UTXO exists or is still spendable.
3338

3339
        :param address: Address of Unspent Output. Address should be available in wallet
3340
        :type address: str
3341
        :param value: Value of output in sathosis or smallest denominator for type of currency
3342
        :type value: int
3343
        :param txid: Transaction hash or previous output as hex-string
3344
        :type txid: str
3345
        :param output_n: Output number of previous transaction output
3346
        :type output_n: int
3347
        :param confirmations: Number of confirmations. Default is 0, unconfirmed
3348
        :type confirmations: int
3349
        :param script: Locking script of previous output as hex-string
3350
        :type script: str
3351

3352
        :return int: Number of new UTXO's added, so 1 if successful
3353
        """
3354

3355
        utxo = {
3✔
3356
            'address': address,
3357
            'script': script,
3358
            'confirmations': confirmations,
3359
            'output_n': output_n,
3360
            'txid': txid,
3361
            'value': value
3362
        }
3363
        return self.utxos_update(utxos=[utxo], rescan_all=False)
3✔
3364

3365
    def utxo_last(self, address):
3✔
3366
        """
3367
        Get transaction ID for latest utxo in database for given address
3368

3369
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
3370
        >>> w.utxo_last('16QaHuFkfuebXGcYHmehRXBBX7RG9NbtLg')
3371
        '748799c9047321cb27a6320a827f1f69d767fe889c14bf11f27549638d566fe4'
3372

3373
        :param address: The address
3374
        :type address: str
3375

3376
        :return str:
3377
        """
3378
        to = self.session.query(
3✔
3379
            DbTransaction.txid, DbTransaction.confirmations). \
3380
            join(DbTransactionOutput).join(DbKey). \
3381
            filter(DbKey.address == address, DbTransaction.wallet_id == self.wallet_id,
3382
                   DbTransactionOutput.spent.is_(False)). \
3383
            order_by(DbTransaction.confirmations).first()
3384
        return '' if not to else to[0].hex()
3✔
3385

3386
    def transactions_update_confirmations(self):
3✔
3387
        """
3388
        Update number of confirmations and status for transactions in database
3389

3390
        :return:
3391
        """
3392
        network = self.network.name
3✔
3393
        srv = Service(network=network, wallet_name=self.name, providers=self.providers, cache_uri=self.db_cache_uri)
3✔
3394
        blockcount = srv.blockcount()
3✔
3395
        self.session.query(DbTransaction).\
3✔
3396
            filter(DbTransaction.wallet_id == self.wallet_id,
3397
                   DbTransaction.network_name == network, DbTransaction.block_height > 0).\
3398
                update({DbTransaction.status: 'confirmed',
3399
                        DbTransaction.confirmations: (blockcount - DbTransaction.block_height) + 1})
3400
        self._commit()
3✔
3401

3402
    def transactions_update_by_txids(self, txids):
3✔
3403
        """
3404
        Update transaction or list or transaction for this wallet with provided transaction ID
3405

3406
        :param txids: Transaction ID, or list of transaction IDs
3407
        :type txids: str, list of str, bytes, list of bytes
3408

3409
        :return:
3410
        """
3411
        if not isinstance(txids, list):
3✔
3412
            txids = [txids]
×
3413
        txids = list(dict.fromkeys(txids))
3✔
3414

3415
        txs = []
3✔
3416
        srv = Service(network=self.network.name, wallet_name=self.name, providers=self.providers, cache_uri=self.db_cache_uri)
3✔
3417
        for txid in txids:
3✔
3418
            tx = srv.gettransaction(to_hexstring(txid))
3✔
3419
            if tx:
3✔
3420
                txs.append(tx)
3✔
3421

3422
        # TODO: Avoid duplicate code in this method and transaction_update()
3423
        utxo_set = set()
3✔
3424
        for t in txs:
3✔
3425
            wt = WalletTransaction.from_transaction(self, t)
3✔
3426
            wt.store()
3✔
3427
            utxos = [(ti.prev_txid.hex(), ti.output_n_int) for ti in wt.inputs]
3✔
3428
            utxo_set.update(utxos)
3✔
3429

3430
        for utxo in list(utxo_set):
3✔
3431
            tos = self.session.query(DbTransactionOutput).join(DbTransaction). \
3✔
3432
                filter(DbTransaction.txid == bytes.fromhex(utxo[0]), DbTransactionOutput.output_n == utxo[1],
3433
                       DbTransactionOutput.spent.is_(False)).all()
3434
            for u in tos:
3✔
3435
                u.spent = True
×
3436
        self._commit()
3✔
3437
        # self._balance_update(account_id=account_id, network=network, key_id=key_id)
3438

3439
    def transactions_update(self, account_id=None, used=None, network=None, key_id=None, depth=None, change=None,
3✔
3440
                            limit=MAX_TRANSACTIONS):
3441
        """
3442
        Update wallets transaction from service providers. Get all transactions for known keys in this wallet. The balances and unspent outputs (UTXO's) are updated as well. Only scan keys from default network and account unless another network or account is specified.
3443

3444
        Use the :func:`scan` method for automatic address generation/management, and use the :func:`utxos_update` method to only look for unspent outputs and balances.
3445

3446
        :param account_id: Account ID
3447
        :type account_id: int
3448
        :param used: Only update used or unused keys, specify None to update both. Default is None
3449
        :type used: bool, None
3450
        :param network: Network name. Leave empty for default network
3451
        :type network: str
3452
        :param key_id: Key ID to just update 1 key
3453
        :type key_id: int
3454
        :param depth: Only update keys with this depth, default is depth 5 according to BIP0048 standard. Set depth to None to update all keys of this wallet.
3455
        :type depth: int
3456
        :param change: Only update change or normal keys, default is both (None)
3457
        :type change: int
3458
        :param limit: Stop update after limit transactions to avoid timeouts with service providers. Default is MAX_TRANSACTIONS defined in config.py
3459
        :type limit: int
3460

3461
        :return bool: True if all transactions are updated
3462
        """
3463

3464
        network, account_id, acckey = self._get_account_defaults(network, account_id, key_id)
3✔
3465
        if depth is None:
3✔
3466
            depth = self.key_depth
3✔
3467

3468
        # Update number of confirmations and status for already known transactions
3469
        self.transactions_update_confirmations()
3✔
3470

3471
        srv = Service(network=network, wallet_name=self.name, providers=self.providers, cache_uri=self.db_cache_uri)
3✔
3472

3473
        # Get transactions for wallet's addresses
3474
        txs = []
3✔
3475
        addresslist = self.addresslist(
3✔
3476
            account_id=account_id, used=used, network=network, key_id=key_id, change=change, depth=depth)
3477
        last_updated = datetime.now(timezone.utc)
3✔
3478
        for address in addresslist:
3✔
3479
            txs += srv.gettransactions(address, limit=limit, after_txid=self.transaction_last(address))
3✔
3480
            if not srv.complete:
3✔
3481
                if txs and txs[-1].date and txs[-1].date < last_updated:
3✔
3482
                    last_updated = txs[-1].date
3✔
3483
            if txs and txs[-1].confirmations:
3✔
3484
                dbkey = self.session.query(DbKey).filter(DbKey.address == address, DbKey.wallet_id == self.wallet_id)
3✔
3485
                if not dbkey.update({DbKey.latest_txid: bytes.fromhex(txs[-1].txid)}):
3✔
3486
                    raise WalletError("Failed to update latest transaction id for key with address %s" % address)
×
3487
                self._commit()
3✔
3488
        if txs is False:
3✔
3489
            raise WalletError("No response from any service provider, could not update transactions")
×
3490

3491
        # Update Transaction outputs to get list of unspent outputs (UTXO's)
3492
        utxo_set = set()
3✔
3493
        for t in txs:
3✔
3494
            wt = WalletTransaction.from_transaction(self, t)
3✔
3495
            wt.store()
3✔
3496
            utxos = [(ti.prev_txid.hex(), ti.output_n_int) for ti in wt.inputs]
3✔
3497
            utxo_set.update(utxos)
3✔
3498
        for utxo in list(utxo_set):
3✔
3499
            tos = self.session.query(DbTransactionOutput).join(DbTransaction).\
3✔
3500
                filter(DbTransaction.txid == bytes.fromhex(utxo[0]), DbTransactionOutput.output_n == utxo[1],
3501
                       DbTransactionOutput.spent.is_(False), DbTransaction.wallet_id == self.wallet_id).all()
3502
            for u in tos:
3✔
3503
                u.spent = True
×
3504

3505
        self.last_updated = last_updated
3✔
3506
        self._commit()
3✔
3507
        self._balance_update(account_id=account_id, network=network, key_id=key_id)
3✔
3508

3509
        return len(txs)
3✔
3510

3511
    def transaction_last(self, address):
3✔
3512
        """
3513
        Get transaction ID for latest transaction in database for given address
3514

3515
        :param address: The address
3516
        :type address: str
3517

3518
        :return str:
3519
        """
3520
        txid = self.session.query(DbKey.latest_txid).\
3✔
3521
            filter(DbKey.address == address, DbKey.wallet_id == self.wallet_id).scalar()
3522
        return '' if not txid else txid.hex()
3✔
3523

3524
    def transactions(self, account_id=None, network=None, include_new=False, key_id=None, as_dict=False):
3✔
3525
        """
3526
        Get all known transactions input and outputs for this wallet.
3527

3528
        The transaction only includes the inputs and outputs related to this wallet. To get full transactions
3529
        use the :func:`transactions_full` method.
3530

3531
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
3532
        >>> w.transactions()
3533
        [<WalletTransaction(input_count=0, output_count=1, status=confirmed, network=bitcoin)>]
3534

3535
        :param account_id: Filter by Account ID. Leave empty for default account_id
3536
        :type account_id: int, None
3537
        :param network: Filter by network name. Leave empty for default network
3538
        :type network: str, None
3539
        :param include_new: Also include new and incomplete transactions in list. Default is False
3540
        :type include_new: bool
3541
        :param key_id: Filter by key ID
3542
        :type key_id: int, None
3543
        :param as_dict: Output as dictionary or WalletTransaction object
3544
        :type as_dict: bool
3545

3546
        :return list of WalletTransaction: List of WalletTransaction or transactions as dictionary
3547
        """
3548

3549
        network, account_id, acckey = self._get_account_defaults(network, account_id, key_id)
3✔
3550
        # Transaction inputs
3551
        qr = self.session.query(DbTransactionInput, DbTransactionInput.address, DbTransaction.confirmations,
3✔
3552
                                 DbTransaction.txid, DbTransaction.network_name, DbTransaction.status). \
3553
            join(DbTransaction).join(DbKey). \
3554
            filter(DbTransaction.account_id == account_id,
3555
                   DbTransaction.wallet_id == self.wallet_id,
3556
                   DbKey.wallet_id == self.wallet_id,
3557
                   DbTransaction.network_name == network)
3558
        if key_id is not None:
3✔
3559
            qr = qr.filter(DbTransactionInput.key_id == key_id)
×
3560
        if not include_new:
3✔
3561
            qr = qr.filter(or_(DbTransaction.status == 'confirmed', DbTransaction.status == 'unconfirmed'))
3✔
3562
        txs = qr.all()
3✔
3563
        # Transaction outputs
3564
        qr = self.session.query(DbTransactionOutput, DbTransactionOutput.address, DbTransaction.confirmations,
3✔
3565
                                 DbTransaction.txid, DbTransaction.network_name, DbTransaction.status). \
3566
            join(DbTransaction).join(DbKey). \
3567
            filter(DbTransaction.account_id == account_id,
3568
                   DbTransaction.wallet_id == self.wallet_id,
3569
                   DbKey.wallet_id == self.wallet_id,
3570
                   DbTransaction.network_name == network)
3571
        if key_id is not None:
3✔
3572
            qr = qr.filter(DbTransactionOutput.key_id == key_id)
×
3573
        if not include_new:
3✔
3574
            qr = qr.filter(or_(DbTransaction.status == 'confirmed', DbTransaction.status == 'unconfirmed'))
3✔
3575
        txs += qr.all()
3✔
3576

3577
        txs = sorted(txs, key=lambda k: (k[2], pow(10, 20)-k[0].transaction_id, k[3]), reverse=True)
3✔
3578
        res = []
3✔
3579
        txids = []
3✔
3580
        for tx in txs:
3✔
3581
            txid = tx[3].hex()
3✔
3582
            if as_dict:
3✔
3583
                u = tx[0].__dict__
3✔
3584
                u['block_height'] = tx[0].transaction.block_height
3✔
3585
                u['date'] = tx[0].transaction.date
3✔
3586
                if '_sa_instance_state' in u:
3✔
3587
                    del u['_sa_instance_state']
3✔
3588
                u['address'] = tx[1]
3✔
3589
                u['confirmations'] = None if tx[2] is None else int(tx[2])
3✔
3590
                u['txid'] = txid
3✔
3591
                u['network_name'] = tx[4]
3✔
3592
                u['status'] = tx[5]
3✔
3593
                if 'index_n' in u:
3✔
3594
                    u['is_output'] = True
×
3595
                    u['value'] = -u['value']
×
3596
                else:
3597
                    u['is_output'] = False
3✔
3598
            else:
3599
                if txid in txids:
3✔
3600
                    continue
3✔
3601
                txids.append(txid)
3✔
3602
                u = self.transaction(txid)
3✔
3603
            res.append(u)
3✔
3604
        return res
3✔
3605

3606
    def transactions_full(self, network=None, include_new=False, limit=0, offset=0):
3✔
3607
        """
3608
        Get all transactions of this wallet as WalletTransaction objects
3609

3610
        Use the :func:`transactions` method to only get the inputs and outputs transaction parts related to this wallet
3611

3612
        :param network: Filter by network name. Leave empty for default network
3613
        :type network: str
3614
        :param include_new: Also include new and incomplete transactions in list. Default is False
3615
        :type include_new: bool
3616
        :param limit: Maximum number of transactions to return. Combine with offset parameter to use as pagination
3617
        :type limit: int
3618
        :param offset: Number of transactions to skip
3619
        :type offset: int
3620

3621
        :return list of WalletTransaction:
3622
        """
3623
        network, _, _ = self._get_account_defaults(network)
3✔
3624
        qr = self.session.query(DbTransaction.txid, DbTransaction.network_name, DbTransaction.status). \
3✔
3625
            filter(DbTransaction.wallet_id == self.wallet_id,
3626
                   DbTransaction.network_name == network)
3627
        if not include_new:
3✔
3628
            qr = qr.filter(or_(DbTransaction.status == 'confirmed', DbTransaction.status == 'unconfirmed'))
3✔
3629
        txs = []
3✔
3630
        if limit:
3✔
3631
            qr = qr.limit(limit)
3✔
3632
        if offset:
3✔
3633
            qr = qr.offset(offset)
3✔
3634
        for tx in qr.all():
3✔
3635
            txs.append(self.transaction(tx[0].hex()))
3✔
3636
        return txs
3✔
3637

3638
    def transactions_export(self, account_id=None, network=None, include_new=False, key_id=None, skip_change=True):
3✔
3639
        """
3640
        Export wallets transactions as list of tuples with the following fields:
3641
            (transaction_date, transaction_hash, in/out, addresses_in, addresses_out, value, value_cumulative, fee)
3642

3643
        :param account_id: Filter by Account ID. Leave empty for default account_id
3644
        :type account_id: int, None
3645
        :param network: Filter by network name. Leave empty for default network
3646
        :type network: str, None
3647
        :param include_new: Also include new and incomplete transactions in list. Default is False
3648
        :type include_new: bool
3649
        :param key_id: Filter by key ID
3650
        :type key_id: int, None
3651
        :param skip_change: Do not include change outputs. Default is True
3652
        :type skip_change: bool
3653

3654
        :return list of tuple:
3655
        """
3656

3657
        txs_tuples = []
3✔
3658
        cumulative_value = 0
3✔
3659
        for t in self.transactions(account_id, network, include_new, key_id):
3✔
3660
            te = t.export(skip_change=skip_change)
3✔
3661

3662
            # When transaction is outgoing deduct fee from cumulative value
3663
            if t.outgoing_tx:
3✔
3664
                cumulative_value -= t.fee
×
3665

3666
            # Loop through all transaction inputs and outputs
3667
            for tei in te:
3✔
3668
                # Create string with  list of inputs addresses for incoming transactions, and outputs addresses
3669
                # for outgoing txs
3670
                addr_list_in = tei[3] if isinstance(tei[3], list) else [tei[3]]
3✔
3671
                addr_list_out = tei[4] if isinstance(tei[4], list) else [tei[4]]
3✔
3672
                cumulative_value += tei[5]
3✔
3673
                txs_tuples.append((tei[0], tei[1], tei[2], addr_list_in, addr_list_out, tei[5], cumulative_value,
3✔
3674
                                   tei[6]))
3675
        return txs_tuples
3✔
3676

3677
    def transaction(self, txid):
3✔
3678
        """
3679
        Get WalletTransaction object for given transaction ID (transaction hash)
3680

3681
        :param txid: Hexadecimal transaction hash
3682
        :type txid: str
3683

3684
        :return WalletTransaction:
3685
        """
3686
        return WalletTransaction.from_txid(self, txid)
3✔
3687

3688
    def transaction_spent(self, txid, output_n):
3✔
3689
        """
3690
        Check if transaction with given transaction ID and output_n is spent and return txid of spent transaction.
3691

3692
        Retrieves information from database, does not update transaction and does not check if transaction is spent with service providers.
3693

3694
        :param txid: Hexadecimal transaction hash
3695
        :type txid: str, bytes
3696
        :param output_n: Output n
3697
        :type output_n: int, bytes
3698

3699
        :return str: Transaction ID
3700
        """
3701
        txid = to_bytes(txid)
3✔
3702
        if isinstance(output_n, bytes):
3✔
3703
            output_n = int.from_bytes(output_n, 'big')
3✔
3704
        qr = self.session.query(DbTransactionInput, DbTransaction.confirmations,
3✔
3705
                                 DbTransaction.txid, DbTransaction.status). \
3706
            join(DbTransaction). \
3707
            filter(DbTransaction.wallet_id == self.wallet_id,
3708
                   DbTransactionInput.prev_txid == txid, DbTransactionInput.output_n == output_n).scalar()
3709
        if qr:
3✔
3710
            return qr.transaction.txid.hex()
3✔
3711

3712

3713
    # def update_transactions_from_block(block, network=None):
3714
    #     pass
3715

3716
    def transaction_delete(self, txid):
3✔
3717
        """
3718
        Remove specified transaction from wallet and update related transactions.
3719

3720
        :param txid: Transaction ID of transaction to remove
3721
        :type txid: str
3722

3723
        :return:
3724
        """
3725
        wt = self.transaction(txid)
3✔
3726
        if wt:
3✔
3727
            wt.delete()
3✔
3728
        else:
3729
            raise WalletError("Transaction %s not found in this wallet" % txid)
×
3730

3731
    def transactions_remove_unconfirmed(self, hours_old=0, account_id=None, network=None):
3✔
3732
        """
3733
        Removes all unconfirmed transactions from this wallet and updates related transactions / utxos.
3734

3735
        :param hours_old: Only delete unconfirmed transaction which are x hours old. You can also use decimals, ie: 0.5 for half an hour
3736
        :type hours_old: int, float
3737
        :param account_id: Filter by Account ID. Leave empty for default account_id
3738
        :type account_id: int, None
3739
        :param network: Filter by network name. Leave empty for default network
3740
        :type network: str, None
3741
        :return:
3742
        """
3743
        txs = self.transactions(account_id=account_id, network=network)
3✔
3744
        td = datetime.utcnow() - timedelta(hours=hours_old)
3✔
3745
        for tx in txs:
3✔
3746
            if not tx.confirmations and tx.date < td:
3✔
3747
                self.transaction_delete(tx.txid)
3✔
3748

3749
    def _objects_by_key_id(self, key_id):
3✔
3750
        self.session.expire_all()
3✔
3751
        key = self.session.query(DbKey).filter_by(id=key_id).scalar()
3✔
3752
        if not key:
3✔
3753
            raise WalletError("Key '%s' not found in this wallet" % key_id)
×
3754
        if key.key_type == 'multisig':
3✔
3755
            inp_keys = [HDKey.from_wif(ck.child_key.wif, network=ck.child_key.network_name) for ck in
3✔
3756
                        key.multisig_children]
3757
        elif key.key_type in ['bip32', 'single']:
3✔
3758
            if not key.wif:
3✔
3759
                raise WalletError("WIF of key is empty cannot create HDKey")
×
3760
            inp_keys = [HDKey.from_wif(key.wif, network=key.network_name)]
3✔
3761
        else:
3762
            raise WalletError("Input key type %s not supported" % key.key_type)
×
3763
        return inp_keys, key
3✔
3764

3765
    def select_inputs(self, amount, variance=None, input_key_id=None, account_id=None, network=None, min_confirms=1,
3✔
3766
                      max_utxos=None, return_input_obj=True, skip_dust_amounts=True):
3767
        """
3768
        Select available unspent transaction outputs (UTXO's) which can be used as inputs for a transaction for
3769
        the specified amount.
3770

3771
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
3772
        >>> w.select_inputs(50000000)
3773
        [<Input(prev_txid='748799c9047321cb27a6320a827f1f69d767fe889c14bf11f27549638d566fe4', output_n=0, address='16QaHuFkfuebXGcYHmehRXBBX7RG9NbtLg', index_n=0, type='sig_pubkey')>]
3774

3775
        :param amount: Total value of inputs in the smallest denominator (sathosi) to select
3776
        :type amount: int
3777
        :param variance: Allowed difference in total input value. Default is dust amount of selected network. Difference will be added to transaction fee.
3778
        :type variance: int
3779
        :param input_key_id: Limit UTXO's search for inputs to this key ID or list of key IDs. Only valid if no input array is specified
3780
        :type input_key_id: int, list
3781
        :param account_id: Account ID
3782
        :type account_id: int
3783
        :param network: Network name. Leave empty for default network
3784
        :type network: str
3785
        :param min_confirms: Minimal confirmation needed for an UTXO before it will be included in inputs. Default is 1 confirmation. Option is ignored if input_arr is provided.
3786
        :type min_confirms: int
3787
        :param max_utxos: Maximum number of UTXO's to use. Set to 1 for optimal privacy. Default is None: No maximum
3788
        :type max_utxos: int
3789
        :param return_input_obj: Return inputs as Input class object. Default is True
3790
        :type return_input_obj: bool
3791
        :param skip_dust_amounts: Do not include small amount to avoid dust inputs
3792
        :type skip_dust_amounts: bool
3793

3794
        :return: List of previous outputs
3795
        :rtype: list of DbTransactionOutput, list of Input
3796
        """
3797

3798
        network, account_id, _ = self._get_account_defaults(network, account_id)
3✔
3799
        dust_amount = Network(network).dust_amount
3✔
3800
        if variance is None:
3✔
3801
            variance = dust_amount
3✔
3802

3803
        utxo_query = self.session.query(DbTransactionOutput).join(DbTransaction).join(DbKey). \
3✔
3804
            filter(DbTransaction.wallet_id == self.wallet_id, DbTransaction.account_id == account_id,
3805
                   DbTransaction.network_name == network, DbKey.public != b'',
3806
                   DbTransactionOutput.spent.is_(False), DbTransaction.confirmations >= min_confirms)
3807
        if input_key_id:
3✔
3808
            if isinstance(input_key_id, int):
3✔
3809
                utxo_query = utxo_query.filter(DbKey.id == input_key_id)
3✔
3810
            else:
3811
                utxo_query = utxo_query.filter(DbKey.id.in_(input_key_id))
×
3812
        if skip_dust_amounts:
3✔
3813
            utxo_query = utxo_query.filter(DbTransactionOutput.value >= dust_amount)
3✔
3814
        utxo_query = utxo_query.order_by(DbTransaction.confirmations.desc())
3✔
3815
        try:
3✔
3816
            utxos = utxo_query.all()
3✔
3817
        except Exception as e:
×
3818
            self.session.close()
×
3819
            logger.warning("Error when querying database, retry: %s" % str(e))
×
3820
            utxos = utxo_query.all()
×
3821

3822
        if not utxos:
3✔
3823
            raise WalletError("Create transaction: No unspent transaction outputs found or no key available for UTXO's")
3✔
3824

3825
        # TODO: Find 1 or 2 UTXO's with exact amount +/- self.network.dust_amount
3826

3827
        # Try to find one utxo with exact amount
3828
        one_utxo = utxo_query.filter(DbTransactionOutput.spent.is_(False),
3✔
3829
                                     DbTransactionOutput.value >= amount,
3830
                                     DbTransactionOutput.value <= amount + variance).first()
3831
        selected_utxos = []
3✔
3832
        if one_utxo:
3✔
3833
            selected_utxos = [one_utxo]
3✔
3834
        else:
3835
            # Try to find one utxo with higher amount
3836
            one_utxo = utxo_query. \
3✔
3837
                filter(DbTransactionOutput.spent.is_(False), DbTransactionOutput.value >= amount).\
3838
                order_by(DbTransactionOutput.value).first()
3839
            if one_utxo:
3✔
3840
                selected_utxos = [one_utxo]
3✔
3841
            elif max_utxos and max_utxos <= 1:
3✔
3842
                _logger.info("No single UTXO found with requested amount, use higher 'max_utxo' setting to use "
3✔
3843
                             "multiple UTXO's")
3844
                return []
3✔
3845

3846
        # Otherwise compose of 2 or more lesser outputs
3847
        if not selected_utxos:
3✔
3848
            lessers = utxo_query. \
3✔
3849
                filter(DbTransactionOutput.spent.is_(False), DbTransactionOutput.value < amount).\
3850
                order_by(DbTransactionOutput.value.desc()).all()
3851
            total_amount = 0
3✔
3852
            selected_utxos = []
3✔
3853
            for utxo in lessers[:max_utxos]:
3✔
3854
                if total_amount < amount:
3✔
3855
                    selected_utxos.append(utxo)
3✔
3856
                    total_amount += utxo.value
3✔
3857
            if total_amount < amount:
3✔
3858
                return []
3✔
3859
        if not return_input_obj:
3✔
3860
            return selected_utxos
3✔
3861
        else:
3862
            inputs = []
3✔
3863
            for utxo in selected_utxos:
3✔
3864
                # amount_total_input += utxo.value
3865
                inp_keys, key = self._objects_by_key_id(utxo.key_id)
3✔
3866
                multisig = False if len(inp_keys) < 2 else True
3✔
3867
                script_type = get_unlocking_script_type(utxo.script_type, multisig=multisig)
3✔
3868
                inputs.append(Input(utxo.transaction.txid, utxo.output_n, keys=inp_keys, script_type=script_type,
3✔
3869
                              sigs_required=self.multisig_n_required, sort=self.sort_keys, address=key.address,
3870
                              compressed=key.compressed, value=utxo.value, network=key.network_name))
3871
            return inputs
3✔
3872

3873
    def transaction_create(self, output_arr, input_arr=None, input_key_id=None, account_id=None, network=None, fee=None,
3✔
3874
                           min_confirms=1, max_utxos=None, locktime=0, number_of_change_outputs=1,
3875
                           random_output_order=True, replace_by_fee=False):
3876
        """
3877
        Create new transaction with specified outputs.
3878

3879
        Inputs can be specified but if not provided they will be selected from wallets utxo's with :func:`select_inputs` method.
3880

3881
        Output array is a list of 1 or more addresses and amounts.
3882

3883
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
3884
        >>> t = w.transaction_create([('1J9GDZMKEr3ZTj8q6pwtMy4Arvt92FDBTb', 200000)])
3885
        >>> t
3886
        <WalletTransaction(input_count=1, output_count=2, status=new, network=bitcoin)>
3887
        >>> t.outputs # doctest:+ELLIPSIS
3888
        [<Output(value=..., address=..., type=p2pkh)>, <Output(value=..., address=..., type=p2pkh)>]
3889

3890
        :param output_arr: List of output as Output objects or tuples with address and amount. Must contain at least one item. Example: [('mxdLD8SAGS9fe2EeCXALDHcdTTbppMHp8N', 5000000)]
3891
        :type output_arr: list of Output, tuple
3892
        :param input_arr: List of inputs as Input objects or tuples with reference to a UTXO, a wallet key and value. The format is [(txid, output_n, key_ids, value, signatures, unlocking_script, address)]
3893
        :type input_arr: list of Input, tuple
3894
        :param input_key_id: Limit UTXO's search for inputs to this key_id. Only valid if no input array is specified
3895
        :type input_key_id: int
3896
        :param account_id: Account ID
3897
        :type account_id: int
3898
        :param network: Network name. Leave empty for default network
3899
        :type network: str
3900
        :param fee: Set fee manually, leave empty to calculate fees automatically. Set fees in the smallest currency  denominator, for example satoshi's if you are using bitcoins. You can also supply a string: 'low', 'normal' or 'high' to determine fees automatically.
3901
        :type fee: int, str
3902
        :param min_confirms: Minimal confirmation needed for an UTXO before it will be included in inputs. Default is 1 confirmation. Option is ignored if input_arr is provided.
3903
        :type min_confirms: int
3904
        :param max_utxos: Maximum number of UTXO's to use. Set to 1 for optimal privacy. Default is None: No maximum
3905
        :type max_utxos: int
3906
        :param locktime: Transaction level locktime. Locks the transaction until a specified block (value from 1 to 5 million) or until a certain time (Timestamp in seconds after 1-jan-1970). Default value is 0 for transactions without locktime
3907
        :type locktime: int
3908
        :param number_of_change_outputs: Number of change outputs to create when there is a change value. Default is 1. Use 0 for random number of outputs: between 1 and 5 depending on send and change amount        :type number_of_change_outputs: int
3909
        :type number_of_change_outputs: int
3910
        :param random_output_order: Shuffle order of transaction outputs to increase privacy. Default is True
3911
        :type random_output_order: bool
3912
        :param replace_by_fee: Signal replace by fee and allow to send a new transaction with higher fees. Sets sequence value to SEQUENCE_REPLACE_BY_FEE
3913
        :type replace_by_fee: bool
3914

3915
        :return WalletTransaction: object
3916
        """
3917

3918
        if not isinstance(output_arr, list):
3✔
3919
            raise WalletError("Output array must be a list of tuples with address and amount. "
3✔
3920
                              "Use 'send_to' method to send to one address")
3921
        if not network and output_arr:
3✔
3922
            if isinstance(output_arr[0], Output):
3✔
3923
                network = output_arr[0].network.name
×
3924
            elif isinstance(output_arr[0][1], str):
3✔
3925
                network = Value(output_arr[0][1]).network.name
3✔
3926
        network, account_id, acckey = self._get_account_defaults(network, account_id)
3✔
3927

3928
        if input_arr and max_utxos and len(input_arr) > max_utxos:
3✔
3929
            raise WalletError("Input array contains %d UTXO's but max_utxos=%d parameter specified" %
3✔
3930
                              (len(input_arr), max_utxos))
3931

3932
        # Create transaction and add outputs
3933
        amount_total_output = 0
3✔
3934
        transaction = WalletTransaction(hdwallet=self, account_id=account_id, network=network, locktime=locktime,
3✔
3935
                                        replace_by_fee=replace_by_fee)
3936
        transaction.outgoing_tx = True
3✔
3937
        for o in output_arr:
3✔
3938
            if isinstance(o, Output):
3✔
3939
                transaction.outputs.append(o)
3✔
3940
                amount_total_output += o.value
3✔
3941
            else:
3942
                value = value_to_satoshi(o[1], network=transaction.network)
3✔
3943
                amount_total_output += value
3✔
3944
                addr = o[0]
3✔
3945
                if isinstance(addr, WalletKey):
3✔
3946
                    addr = addr.key()
3✔
3947
                transaction.add_output(value, addr, change=False)
3✔
3948

3949
        srv = Service(network=network, wallet_name=self.name, providers=self.providers, cache_uri=self.db_cache_uri)
3✔
3950

3951
        if not locktime and self.anti_fee_sniping:
3✔
3952
            srv = Service(network=network, providers=self.providers, cache_uri=self.db_cache_uri)
3✔
3953
            blockcount = srv.blockcount()
3✔
3954
            if blockcount:
3✔
3955
                transaction.locktime = blockcount
3✔
3956

3957
        transaction.fee_per_kb = None
3✔
3958
        if isinstance(fee, int):
3✔
3959
            fee_estimate = fee
3✔
3960
        else:
3961
            n_blocks = 3
3✔
3962
            priority = ''
3✔
3963
            if isinstance(fee, str):
3✔
3964
                priority = fee
3✔
3965
            transaction.fee_per_kb = srv.estimatefee(blocks=n_blocks, priority=priority)
3✔
3966
            if not input_arr:
3✔
3967
                fee_estimate = int(transaction.estimate_size(number_of_change_outputs=number_of_change_outputs) /
3✔
3968
                                   1000.0 * transaction.fee_per_kb)
3969
            else:
3970
                fee_estimate = 0
3✔
3971
            if isinstance(fee, str):
3✔
3972
                fee = fee_estimate
3✔
3973

3974
        # Add inputs
3975
        sequence = 0xffffffff
3✔
3976
        if replace_by_fee:
3✔
3977
            sequence = SEQUENCE_REPLACE_BY_FEE
3✔
3978
        elif 0 < transaction.locktime < 0xffffffff:
3✔
3979
            sequence = SEQUENCE_ENABLE_LOCKTIME
3✔
3980
        amount_total_input = 0
3✔
3981
        if input_arr is None:
3✔
3982
            selected_utxos = self.select_inputs(amount_total_output + fee_estimate, transaction.network.dust_amount,
3✔
3983
                                                input_key_id, account_id, network, min_confirms, max_utxos, False)
3984
            if not selected_utxos:
3✔
3985
                raise WalletError("Not enough unspent transaction outputs found")
3✔
3986
            for utxo in selected_utxos:
3✔
3987
                amount_total_input += utxo.value
3✔
3988
                inp_keys, key = self._objects_by_key_id(utxo.key_id)
3✔
3989
                multisig = False if isinstance(inp_keys, list) and len(inp_keys) < 2 else True
3✔
3990
                witness_type = utxo.key.witness_type if utxo.key.witness_type else self.witness_type
3✔
3991
                unlock_script_type = get_unlocking_script_type(utxo.script_type, witness_type,
3✔
3992
                                                               multisig=multisig)
3993
                transaction.add_input(utxo.transaction.txid, utxo.output_n, keys=inp_keys,
3✔
3994
                                      script_type=unlock_script_type, sigs_required=self.multisig_n_required,
3995
                                      sort=self.sort_keys, compressed=key.compressed, value=utxo.value,
3996
                                      address=utxo.key.address, sequence=sequence,
3997
                                      key_path=utxo.key.path, witness_type=witness_type)
3998
                # FIXME: Missing locktime_cltv=locktime_cltv, locktime_csv=locktime_csv (?)
3999
        else:
4000
            for inp in input_arr:
3✔
4001
                locktime_cltv = None
3✔
4002
                locktime_csv = None
3✔
4003
                locking_script = None
3✔
4004
                unlocking_script_type = ''
3✔
4005
                if isinstance(inp, Input):
3✔
4006
                    prev_txid = inp.prev_txid
3✔
4007
                    output_n = inp.output_n
3✔
4008
                    key_id = None
3✔
4009
                    value = inp.value
3✔
4010
                    signatures = inp.signatures
3✔
4011
                    unlocking_script = inp.unlocking_script
3✔
4012
                    locking_script = inp.locking_script
3✔
4013
                    unlocking_script_type = inp.script_type
3✔
4014
                    address = inp.address
3✔
4015
                    sequence = inp.sequence
3✔
4016
                    locktime_cltv = inp.locktime_cltv
3✔
4017
                    locktime_csv = inp.locktime_csv
3✔
4018
                    witness_type = inp.witness_type
3✔
4019
                else:
4020
                    prev_txid = inp[0]
3✔
4021
                    output_n = inp[1]
3✔
4022
                    key_id = None if len(inp) <= 2 else inp[2]
3✔
4023
                    value = 0 if len(inp) <= 3 else inp[3]
3✔
4024
                    signatures = None if len(inp) <= 4 else inp[4]
3✔
4025
                    unlocking_script = b'' if len(inp) <= 5 else inp[5]
3✔
4026
                    address = '' if len(inp) <= 6 else inp[6]
3✔
4027
                    witness_type = self.witness_type
3✔
4028
                # Get key_ids, value from Db if not specified
4029
                if not (key_id and value and unlocking_script_type):
3✔
4030
                    if not isinstance(output_n, TYPE_INT):
3✔
4031
                        output_n = int.from_bytes(output_n, 'big')
3✔
4032
                    inp_utxo = self.session.query(DbTransactionOutput).join(DbTransaction). \
3✔
4033
                        filter(DbTransaction.wallet_id == self.wallet_id,
4034
                               DbTransaction.txid == to_bytes(prev_txid),
4035
                               DbTransactionOutput.output_n == output_n).first()
4036
                    if inp_utxo:
3✔
4037
                        key_id = inp_utxo.key_id
3✔
4038
                        value = inp_utxo.value
3✔
4039
                        address = inp_utxo.key.address
3✔
4040
                        unlocking_script_type = get_unlocking_script_type(inp_utxo.script_type, multisig=self.multisig)
3✔
4041
                        witness_type = inp_utxo.key.witness_type
3✔
4042
                    else:
4043
                        _logger.info("UTXO %s not found in this wallet. Please update UTXO's if this is not an "
3✔
4044
                                     "offline wallet" % to_hexstring(prev_txid))
4045
                        key_id = self.session.query(DbKey.id).\
3✔
4046
                            filter(DbKey.wallet_id == self.wallet_id, DbKey.address == address).scalar()
4047
                        if not key_id:
3✔
4048
                            raise WalletError("UTXO %s and key with address %s not found in this wallet" % (
3✔
4049
                                to_hexstring(prev_txid), address))
4050
                        if not value:
×
4051
                            raise WalletError("Input value is zero for address %s. Import or update UTXO's first "
×
4052
                                              "or import transaction as dictionary" % address)
4053

4054
                amount_total_input += value
3✔
4055
                inp_keys, key = self._objects_by_key_id(key_id)
3✔
4056
                transaction.add_input(prev_txid, output_n, keys=inp_keys, script_type=unlocking_script_type,
3✔
4057
                                      sigs_required=self.multisig_n_required, sort=self.sort_keys,
4058
                                      compressed=key.compressed, value=value, signatures=signatures,
4059
                                      unlocking_script=unlocking_script, address=address,
4060
                                      locking_script=locking_script,
4061
                                      sequence=sequence, locktime_cltv=locktime_cltv, locktime_csv=locktime_csv,
4062
                                      witness_type=witness_type, key_path=key.path)
4063
        # Calculate fees
4064
        transaction.fee = fee
3✔
4065
        fee_per_output = None
3✔
4066
        transaction.size = transaction.estimate_size(number_of_change_outputs=number_of_change_outputs)
3✔
4067
        if fee is None:
3✔
4068
            if not input_arr:
3✔
4069
                if not transaction.fee_per_kb:
3✔
4070
                    transaction.fee_per_kb = srv.estimatefee()
×
4071
                if transaction.fee_per_kb < transaction.network.fee_min:
3✔
4072
                    transaction.fee_per_kb = transaction.network.fee_min
×
4073
                transaction.fee = int((transaction.size / 1000.0) * transaction.fee_per_kb)
3✔
4074
                fee_per_output = int((50 / 1000.0) * transaction.fee_per_kb)
3✔
4075
            else:
4076
                if amount_total_output and amount_total_input:
3✔
4077
                    fee = False
3✔
4078
                else:
4079
                    transaction.fee = 0
×
4080

4081
        if fee is False:
3✔
4082
            transaction.change = 0
3✔
4083
            transaction.fee = int(amount_total_input - amount_total_output)
3✔
4084
        else:
4085
            transaction.change = int(amount_total_input - (amount_total_output + transaction.fee))
3✔
4086

4087
        # Skip change if amount is smaller than the dust limit or estimated fee
4088
        if (fee_per_output and transaction.change < fee_per_output) or transaction.change <= transaction.network.dust_amount:
3✔
4089
            transaction.fee += transaction.change
3✔
4090
            transaction.change = 0
3✔
4091
        if transaction.change < 0:
3✔
4092
            raise WalletError("Total amount of outputs is greater then total amount of inputs")
×
4093
        if transaction.change:
3✔
4094
            min_output_value = transaction.network.dust_amount * 2 + transaction.network.fee_min * 4
3✔
4095
            if transaction.fee and transaction.size:
3✔
4096
                if not transaction.fee_per_kb:
3✔
4097
                    transaction.fee_per_kb = int((transaction.fee * 1000.0) / transaction.vsize)
3✔
4098
                min_output_value = transaction.fee_per_kb + transaction.network.fee_min * 4 + \
3✔
4099
                                   transaction.network.dust_amount
4100

4101
            if number_of_change_outputs == 0:
3✔
4102
                if transaction.change < amount_total_output / 10 or transaction.change < min_output_value * 8:
3✔
4103
                    number_of_change_outputs = 1
×
4104
                elif transaction.change / 10 > amount_total_output:
3✔
4105
                    number_of_change_outputs = random.randint(2, 5)
×
4106
                else:
4107
                    number_of_change_outputs = random.randint(1, 3)
3✔
4108
                    # Prefer 1 and 2 as number of change outputs
4109
                    if number_of_change_outputs == 3:
3✔
4110
                        number_of_change_outputs = random.randint(3, 4)
1✔
4111
                transaction.size = transaction.estimate_size(number_of_change_outputs=number_of_change_outputs)
3✔
4112

4113
            average_change = transaction.change // number_of_change_outputs
3✔
4114
            if number_of_change_outputs > 1 and average_change < min_output_value:
3✔
4115
                raise WalletError("Not enough funds to create multiple change outputs. Try less change outputs "
×
4116
                                  "or lower fees")
4117

4118
            if self.scheme == 'single':
3✔
4119
                change_keys = [self.get_key(account_id, self.witness_type, network, change=1)]
3✔
4120
            else:
4121
                change_keys = self.get_keys(account_id, self.witness_type, network, change=1,
3✔
4122
                                            number_of_keys=number_of_change_outputs)
4123

4124
            if number_of_change_outputs > 1:
3✔
4125
                rand_prop = transaction.change - number_of_change_outputs * min_output_value
3✔
4126
                change_amounts = list(((np.random.dirichlet(np.ones(number_of_change_outputs), size=1)[0] *
3✔
4127
                                        rand_prop) + min_output_value).astype(int))
4128
                # Fix rounding problems / small amount differences
4129
                diffs = transaction.change - sum(change_amounts)
3✔
4130
                for idx, co in enumerate(change_amounts):
3✔
4131
                    if co - diffs > min_output_value:
3✔
4132
                        change_amounts[idx] += change_amounts.index(co) + diffs
3✔
4133
                        break
3✔
4134
            else:
4135
                change_amounts = [transaction.change]
3✔
4136

4137
            for idx, ck in enumerate(change_keys):
3✔
4138
                on = transaction.add_output(change_amounts[idx], ck.address, encoding=self.encoding, change=True)
3✔
4139
                transaction.outputs[on].key_id = ck.key_id
3✔
4140

4141
        # Shuffle output order to increase privacy
4142
        if random_output_order:
3✔
4143
            transaction.shuffle()
3✔
4144

4145
        # Check tx values
4146
        transaction.input_total = sum([i.value for i in transaction.inputs])
3✔
4147
        transaction.output_total = sum([o.value for o in transaction.outputs])
3✔
4148
        if transaction.input_total != transaction.fee + transaction.output_total:
3✔
4149
            raise WalletError("Sum of inputs values is not equal to sum of outputs values plus fees")
×
4150

4151
        transaction.txid = transaction.signature_hash()[::-1].hex()
3✔
4152
        if not transaction.fee_per_kb:
3✔
4153
            transaction.fee_per_kb = int((transaction.fee * 1000.0) / transaction.vsize)
3✔
4154
        if transaction.fee_per_kb < transaction.network.fee_min:
3✔
4155
            raise WalletError("Fee per kB of %d is lower then minimal network fee of %d" %
3✔
4156
                              (transaction.fee_per_kb, transaction.network.fee_min))
4157
        elif transaction.fee_per_kb > transaction.network.fee_max:
3✔
4158
            raise WalletError("Fee per kB of %d is higher then maximum network fee of %d" %
3✔
4159
                              (transaction.fee_per_kb, transaction.network.fee_max))
4160

4161
        return transaction
3✔
4162

4163
    def transaction_import(self, t):
3✔
4164
        """
4165
        Import a Transaction into this wallet. Link inputs to wallet keys if possible and return WalletTransaction
4166
        object. Only imports Transaction objects or dictionaries, use
4167
        :func:`transaction_import_raw` method to import a raw transaction.
4168

4169
        :param t: A Transaction object or dictionary
4170
        :type t: Transaction, dict
4171

4172
        :return WalletTransaction:
4173

4174
        """
4175
        if isinstance(t, Transaction):
3✔
4176
            rt = self.transaction_create(t.outputs, t.inputs, fee=t.fee, network=t.network.name,
3✔
4177
                                         random_output_order=False)
4178
            rt.block_height = t.block_height
3✔
4179
            rt.confirmations = t.confirmations
3✔
4180
            rt.witness_type = t.witness_type
3✔
4181
            rt.date = t.date
3✔
4182
            rt.txid = t.txid
3✔
4183
            rt.txhash = t.txhash
3✔
4184
            rt.locktime = t.locktime
3✔
4185
            rt.version = t.version
3✔
4186
            rt.version_int = t.version_int
3✔
4187
            rt.block_hash = t.block_hash
3✔
4188
            rt.rawtx = t.rawtx
3✔
4189
            rt.coinbase = t.coinbase
3✔
4190
            rt.flag = t.flag
3✔
4191
            rt.size = t.size
3✔
4192
            if not t.size:
3✔
4193
                rt.size = len(t.raw())
×
4194
            rt.vsize = t.vsize
3✔
4195
            if not t.vsize:
3✔
4196
                rt.vsize = rt.size
×
4197
            rt.fee_per_kb = int((rt.fee / float(rt.vsize)) * 1000)
3✔
4198
        elif isinstance(t, dict):
3✔
4199
            input_arr = []
3✔
4200
            for i in t['inputs']:
3✔
4201
                signatures = [bytes.fromhex(sig) for sig in i['signatures']]
3✔
4202
                script = b'' if 'script' not in i else i['script']
3✔
4203
                address = '' if 'address' not in i else i['address']
3✔
4204
                input_arr.append((i['prev_txid'], i['output_n'], None, int(i['value']), signatures, script,
3✔
4205
                                  address))
4206
            output_arr = []
3✔
4207
            for o in t['outputs']:
3✔
4208
                output_arr.append((o['address'], int(o['value'])))
3✔
4209
            rt = self.transaction_create(output_arr, input_arr, fee=t['fee'], network=t['network'],
3✔
4210
                                         random_output_order=False)
4211
            rt.block_height = t['block_height']
3✔
4212
            rt.confirmations = t['confirmations']
3✔
4213
            rt.witness_type = t['witness_type']
3✔
4214
            rt.date = t['date']
3✔
4215
            rt.txid = t['txid']
3✔
4216
            rt.txhash = t['txhash']
3✔
4217
            rt.locktime = t['locktime']
3✔
4218
            rt.version = t['version'].to_bytes(4, 'big')
3✔
4219
            rt.version_int = t['version']
3✔
4220
            rt.block_hash = t['block_hash']
3✔
4221
            rt.rawtx = t['raw']
3✔
4222
            rt.coinbase = t['coinbase']
3✔
4223
            rt.flag = t['flag']
3✔
4224
            rt.size = t['size']
3✔
4225
            if not t['size']:
3✔
4226
                rt.size = len(rt.raw())
×
4227
            rt.vsize = t['vsize']
3✔
4228
            if not rt.vsize:
3✔
4229
                rt.vsize = rt.size
×
4230
            rt.fee_per_kb = int((rt.fee / float(rt.vsize)) * 1000)
3✔
4231
        else:
4232
            raise WalletError("Import transaction must be of type Transaction or dict")
×
4233
        rt.verify()
3✔
4234
        return rt
3✔
4235

4236
    def transaction_import_raw(self, rawtx, network=None):
3✔
4237
        """
4238
        Import a raw transaction. Link inputs to wallet keys if possible and return WalletTransaction object
4239

4240
        :param rawtx: Raw transaction
4241
        :type rawtx: str, bytes
4242
        :param network: Network name. Leave empty for default network
4243
        :type network: str
4244

4245
        :return WalletTransaction:
4246
        """
4247

4248
        if network is None:
3✔
4249
            network = self.network.name
3✔
4250
        if isinstance(rawtx, str):
3✔
4251
            rawtx = bytes.fromhex(rawtx)
×
4252
        t_import = Transaction.parse_bytes(rawtx, network=network)
3✔
4253
        rt = self.transaction_create(t_import.outputs, t_import.inputs, network=network, locktime=t_import.locktime,
3✔
4254
                                     random_output_order=False)
4255
        rt.version_int = t_import.version_int
3✔
4256
        rt.version = t_import.version
3✔
4257
        rt.verify()
3✔
4258
        rt.size = len(rawtx)
3✔
4259
        rt.calc_weight_units()
3✔
4260
        rt.fee_per_kb = int((rt.fee / float(rt.vsize)) * 1000)
3✔
4261
        return rt
3✔
4262

4263
    def send(self, output_arr, input_arr=None, input_key_id=None, account_id=None, network=None, fee=None,
3✔
4264
             min_confirms=1, priv_keys=None, max_utxos=None, locktime=0, broadcast=False, number_of_change_outputs=1,
4265
             random_output_order=True, replace_by_fee=False):
4266
        """
4267
        Create a new transaction with specified outputs and push it to the network.
4268
        Inputs can be specified but if not provided they will be selected from wallets utxo's
4269
        Output array is a list of 1 or more addresses and amounts.
4270

4271
        Uses the :func:`transaction_create` method to create a new transaction, and uses a random service client to send the transaction.
4272

4273
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
4274
        >>> t = w.send([('1J9GDZMKEr3ZTj8q6pwtMy4Arvt92FDBTb', 200000)])
4275
        >>> t
4276
        <WalletTransaction(input_count=1, output_count=2, status=new, network=bitcoin)>
4277
        >>> t.outputs # doctest:+ELLIPSIS
4278
        [<Output(value=..., address=..., type=p2pkh)>, <Output(value=..., address=..., type=p2pkh)>]
4279

4280
        :param output_arr: List of output tuples with address and amount. Must contain at least one item. Example: [('mxdLD8SAGS9fe2EeCXALDHcdTTbppMHp8N', 5000000)]. Address can be an address string, Address object, HDKey object or WalletKey object
4281
        :type output_arr: list
4282
        :param input_arr: List of inputs tuples with reference to a UTXO, a wallet key and value. The format is [(txid, output_n, key_id, value)]
4283
        :type input_arr: list
4284
        :param input_key_id: Limit UTXO's search for inputs to this key ID or list of key IDs. Only valid if no input array is specified
4285
        :type input_key_id: int, list
4286
        :param account_id: Account ID
4287
        :type account_id: int
4288
        :param network: Network name. Leave empty for default network
4289
        :type network: str
4290
        :param fee: Set fee manually, leave empty to calculate fees automatically. Set fees in the smallest currency  denominator, for example satoshi's if you are using bitcoins. You can also supply a string: 'low', 'normal' or 'high' to determine fees automatically.
4291
        :type fee: int, str
4292
        :param min_confirms: Minimal confirmation needed for an UTXO before it will be included in inputs. Default is 1. Option is ignored if input_arr is provided.
4293
        :type min_confirms: int
4294
        :param priv_keys: Specify extra private key if not available in this wallet
4295
        :type priv_keys: HDKey, list
4296
        :param max_utxos: Maximum number of UTXO's to use. Set to 1 for optimal privacy. Default is None: No maximum
4297
        :type max_utxos: int
4298
        :param locktime: Transaction level locktime. Locks the transaction until a specified block (value from 1 to 5 million) or until a certain time (Timestamp in seconds after 1-jan-1970). Default value is 0 for transactions without locktime
4299
        :type locktime: int
4300
        :param broadcast: Just return the transaction object and do not send it when broadcast = False. Default is False
4301
        :type broadcast: bool
4302
        :param number_of_change_outputs: Number of change outputs to create when there is a change value. Default is 1. Use 0 for random number of outputs: between 1 and 5 depending on send and change amount
4303
        :type number_of_change_outputs: int
4304
        :param random_output_order: Shuffle order of transaction outputs to increase privacy. Default is True
4305
        :type random_output_order: bool
4306
        :param replace_by_fee: Signal replace by fee and allow to send a new transaction with higher fees. Sets sequence value to SEQUENCE_REPLACE_BY_FEE
4307
        :type replace_by_fee: bool
4308

4309
        :return WalletTransaction:
4310
        """
4311

4312
        if input_arr and max_utxos and len(input_arr) > max_utxos:
3✔
4313
            raise WalletError("Input array contains %d UTXO's but max_utxos=%d parameter specified" %
×
4314
                              (len(input_arr), max_utxos))
4315

4316
        transaction = self.transaction_create(output_arr, input_arr, input_key_id, account_id, network, fee,
3✔
4317
                                              min_confirms, max_utxos, locktime, number_of_change_outputs,
4318
                                              random_output_order, replace_by_fee)
4319
        transaction.sign(priv_keys)
3✔
4320
        # Calculate exact fees and update change output if necessary
4321
        if fee is None and transaction.fee_per_kb and transaction.change:
3✔
4322
            fee_exact = transaction.calculate_fee()
3✔
4323
            # Recreate transaction if fee estimation more than 10% off
4324
            if fee_exact != self.network.fee_min and fee_exact != self.network.fee_max and \
3✔
4325
                    fee_exact and abs((float(transaction.fee) - float(fee_exact)) / float(fee_exact)) > 0.10:
4326
                _logger.info("Transaction fee not correctly estimated (est.: %d, real: %d). "
×
4327
                             "Recreate transaction with correct fee" % (transaction.fee, fee_exact))
4328
                transaction = self.transaction_create(output_arr, input_arr, input_key_id, account_id, network,
×
4329
                                                      fee_exact, min_confirms, max_utxos, locktime,
4330
                                                      number_of_change_outputs, random_output_order,
4331
                                                      replace_by_fee)
4332
                transaction.sign(priv_keys)
×
4333

4334
        transaction.rawtx = transaction.raw()
3✔
4335
        transaction.size = len(transaction.rawtx)
3✔
4336
        transaction.calc_weight_units()
3✔
4337
        transaction.fee_per_kb = int(float(transaction.fee) / float(transaction.vsize) * 1000)
3✔
4338
        transaction.txid = transaction.signature_hash()[::-1].hex()
3✔
4339
        transaction.send(broadcast)
3✔
4340
        return transaction
3✔
4341

4342
    def send_to(self, to_address, amount, input_key_id=None, account_id=None, network=None, fee=None, min_confirms=1,
3✔
4343
                priv_keys=None, locktime=0, broadcast=False, number_of_change_outputs=1, random_output_order=True,
4344
                replace_by_fee=False):
4345
        """
4346
        Create transaction and send it with default Service objects :func:`services.sendrawtransaction` method.
4347

4348
        Wrapper for wallet :func:`send` method.
4349

4350
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
4351
        >>> t = w.send_to('1J9GDZMKEr3ZTj8q6pwtMy4Arvt92FDBTb', 200000)
4352
        >>> t
4353
        <WalletTransaction(input_count=1, output_count=2, status=new, network=bitcoin)>
4354
        >>> t.outputs # doctest:+ELLIPSIS
4355
        [<Output(value=..., address=..., type=p2pkh)>, <Output(value=..., address=..., type=p2pkh)>]
4356

4357
        :param to_address: Single output address as string Address object, HDKey object or WalletKey object
4358
        :type to_address: str, Address, HDKey, WalletKey
4359
        :param amount: Output is the smallest denominator for this network (ie: Satoshi's for Bitcoin), as Value object or value string as accepted by Value class
4360
        :type amount: int, str, Value
4361
        :param input_key_id: Limit UTXO's search for inputs to this key ID or list of key IDs. Only valid if no input array is specified
4362
        :type input_key_id: int, list
4363
        :param account_id: Account ID, default is last used
4364
        :type account_id: int
4365
        :param network: Network name. Leave empty for default network
4366
        :type network: str
4367
        :param fee: Set fee manually, leave empty to calculate fees automatically. Set fees in the smallest currency  denominator, for example satoshi's if you are using bitcoins. You can also supply a string: 'low', 'normal' or 'high' to determine fees automatically.
4368
        :type fee: int, str
4369
        :param min_confirms: Minimal confirmation needed for an UTXO before it will be included in inputs. Default is 1. Option is ignored if input_arr is provided.
4370
        :type min_confirms: int
4371
        :param priv_keys: Specify extra private key if not available in this wallet
4372
        :type priv_keys: HDKey, list
4373
        :param locktime: Transaction level locktime. Locks the transaction until a specified block (value from 1 to 5 million) or until a certain time (Timestamp in seconds after 1-jan-1970). Default value is 0 for transactions without locktime
4374
        :type locktime: int
4375
        :param broadcast: Just return the transaction object and do not send it when broadcast = False. Default is False
4376
        :type broadcast: bool
4377
        :param number_of_change_outputs: Number of change outputs to create when there is a change value. Default is 1. Use 0 for random number of outputs: between 1 and 5 depending on send and change amount
4378
        :type number_of_change_outputs: int
4379
        :param random_output_order: Shuffle order of transaction outputs to increase privacy. Default is True
4380
        :type random_output_order: bool
4381
        :param replace_by_fee: Signal replace by fee and allow to send a new transaction with higher fees. Sets sequence value to SEQUENCE_REPLACE_BY_FEE
4382
        :type replace_by_fee: bool
4383

4384
        :return WalletTransaction:
4385
        """
4386

4387
        outputs = [(to_address, amount)]
3✔
4388
        return self.send(outputs, input_key_id=input_key_id, account_id=account_id, network=network, fee=fee,
3✔
4389
                         min_confirms=min_confirms, priv_keys=priv_keys, locktime=locktime, broadcast=broadcast,
4390
                         number_of_change_outputs=number_of_change_outputs, random_output_order=random_output_order,
4391
                         replace_by_fee=replace_by_fee)
4392

4393
    def sweep(self, to_address, account_id=None, input_key_id=None, network=None, max_utxos=999, min_confirms=1,
3✔
4394
              fee_per_kb=None, fee=None, locktime=0, broadcast=False, replace_by_fee=False):
4395
        """
4396
        Sweep all unspent transaction outputs (UTXO's) and send them to one or more output addresses.
4397

4398
        Wrapper for the :func:`send` method.
4399

4400
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
4401
        >>> t = w.sweep('1J9GDZMKEr3ZTj8q6pwtMy4Arvt92FDBTb')
4402
        >>> t
4403
        <WalletTransaction(input_count=1, output_count=1, status=new, network=bitcoin)>
4404
        >>> t.outputs # doctest:+ELLIPSIS
4405
        [<Output(value=..., address=1J9GDZMKEr3ZTj8q6pwtMy4Arvt92FDBTb, type=p2pkh)>]
4406

4407
        Output to multiple addresses
4408

4409
        >>> to_list = [('1J9GDZMKEr3ZTj8q6pwtMy4Arvt92FDBTb', 100000), (w.get_key(), 0)]
4410
        >>> w.sweep(to_list)
4411
        <WalletTransaction(input_count=1, output_count=2, status=new, network=bitcoin)>
4412

4413
        :param to_address: Single output address or list of outputs in format [(<adddress>, <amount>)]. If you specify a list of outputs, use amount value = 0 to indicate a change output
4414
        :type to_address: str, list
4415
        :param account_id: Wallet's account ID
4416
        :type account_id: int
4417
        :param input_key_id: Limit sweep to UTXO's with this key ID or list of key IDs
4418
        :type input_key_id: int, list
4419
        :param network: Network name. Leave empty for default network
4420
        :type network: str
4421
        :param max_utxos: Limit maximum number of outputs to use. Default is 999
4422
        :type max_utxos: int
4423
        :param min_confirms: Minimal confirmations needed to include utxo
4424
        :type min_confirms: int
4425
        :param fee_per_kb: Fee per kilobyte transaction size, leave empty to get estimated fee costs from Service provider. This option is ignored when the 'fee' option is specified
4426
        :type fee_per_kb: int
4427
        :param fee: Total transaction fee in the smallest denominator (i.e. satoshis). Leave empty to get estimated fee from service providers. You can also supply a string: 'low', 'normal' or 'high' to determine fees automatically.
4428
        :type fee: int, str
4429
        :param locktime: Transaction level locktime. Locks the transaction until a specified block (value from 1 to 5 million) or until a certain time (Timestamp in seconds after 1-jan-1970). Default value is 0 for transactions without locktime
4430
        :type locktime: int
4431
        :param broadcast: Just return the transaction object and do not send it when broadcast = False. Default is False
4432
        :type broadcast: bool
4433
        :param replace_by_fee: Signal replace by fee and allow to send a new transaction with higher fees. Sets sequence value to SEQUENCE_REPLACE_BY_FEE
4434
        :type replace_by_fee: bool
4435

4436
        :return WalletTransaction:
4437
        """
4438

4439
        network, account_id, acckey = self._get_account_defaults(network, account_id)
3✔
4440

4441
        utxos = self.utxos(account_id=account_id, network=network, min_confirms=min_confirms, key_id=input_key_id)
3✔
4442
        utxos = utxos[0:max_utxos]
3✔
4443
        input_arr = []
3✔
4444
        total_amount = 0
3✔
4445

4446
        if not utxos:
3✔
4447
            raise WalletError("Cannot sweep wallet, no UTXO's found")
3✔
4448
        for utxo in utxos:
3✔
4449
            # Skip dust transactions to avoid forced address reuse
4450
            if utxo['value'] <= self.network.dust_amount:
3✔
4451
                continue
3✔
4452
            input_arr.append((utxo['txid'], utxo['output_n'], utxo['key_id'], utxo['value']))
3✔
4453
            total_amount += utxo['value']
3✔
4454
        srv = Service(network=network, wallet_name=self.name, providers=self.providers, cache_uri=self.db_cache_uri)
3✔
4455

4456
        fee_modifier = 1 if self.witness_type == 'legacy' else 0.6
3✔
4457
        if isinstance(fee, str):
3✔
4458
            fee_per_kb = srv.estimatefee(priority=fee)
3✔
4459
            fee = None
3✔
4460
        if not fee:
3✔
4461
            if fee_per_kb is None:
3✔
4462
                fee_per_kb = srv.estimatefee()
3✔
4463
            n_outputs = 1 if not isinstance(to_address, list) else len(to_address)
3✔
4464
            tr_size = 125 + (len(input_arr) * (77 + self.multisig_n_required * 72)) + n_outputs * 30
3✔
4465
            fee = int(100 + ((tr_size / 1000.0) * fee_per_kb * fee_modifier))
3✔
4466

4467
        if total_amount - fee <= self.network.dust_amount:
3✔
4468
            raise WalletError("Amount to send is smaller then dust amount: %s" % (total_amount - fee))
3✔
4469

4470
        if isinstance(to_address, str):
3✔
4471
            to_list = [(to_address, total_amount - fee)]
3✔
4472
        else:
4473
            to_list = []
3✔
4474
            for o in to_address:
3✔
4475
                if o[1] == 0:
3✔
4476
                    o_amount = total_amount - sum([x[1] for x in to_list]) - fee
3✔
4477
                    if o_amount > 0:
3✔
4478
                        to_list.append((o[0], o_amount))
3✔
4479
                else:
4480
                    to_list.append(o)
3✔
4481

4482
        if sum(x[1] for x in to_list) + fee != total_amount:
3✔
4483
            raise WalletError("Total amount of outputs does not match total input amount. If you specify a list of "
×
4484
                              "outputs, use amount value = 0 to indicate a change/rest output")
4485

4486
        return self.send(to_list, input_arr, network=network, fee=fee, min_confirms=min_confirms, locktime=locktime,
3✔
4487
                         broadcast=broadcast, replace_by_fee=replace_by_fee)
4488

4489
    def wif(self, is_private=False, account_id=0):
3✔
4490
        """
4491
        Return Wallet Import Format string for master private or public key which can be used to import key and
4492
        recreate wallet in other software.
4493

4494
        A list of keys will be exported for a multisig wallet.
4495

4496
        :param is_private: Export public or private key, default is False
4497
        :type is_private: bool
4498
        :param account_id: Account ID of key to export
4499
        :type account_id: bool
4500

4501
        :return list, str:
4502
        """
4503
        if not self.multisig or not self.cosigner:
3✔
4504
            if is_private and self.main_key:
3✔
4505
                return self.main_key.wif
3✔
4506
            else:
4507
                return self.public_master(account_id=account_id).key().\
3✔
4508
                    wif(is_private=is_private, witness_type=self.witness_type, multisig=self.multisig)
4509
        else:
4510
            wiflist = []
3✔
4511
            for cs in self.cosigner:
3✔
4512
                wiflist.append(cs.wif(is_private=is_private))
3✔
4513
            return wiflist
3✔
4514

4515
    def public_master(self, account_id=None, name=None, as_private=False, witness_type=None, network=None):
3✔
4516
        """
4517
        Return public master key(s) for this wallet. Use to import in other wallets to sign transactions or create keys.
4518

4519
        For a multisig wallet all public master keys are return as list.
4520

4521
        Returns private key information if available and as_private is True is specified
4522

4523
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
4524
        >>> w.public_master().wif
4525
        'xpub6D2qEr8Z8WYKKns2xZYyyvvRviPh1NKt1kfHwwfiTxJwj7peReEJt3iXoWWsr8tXWTsejDjMfAezM53KVFVkSZzA5i2pNy3otprtYUvh4u1'
4526

4527
        :param account_id: Account ID of key to export
4528
        :type account_id: int
4529
        :param name: Optional name for account key
4530
        :type name: str
4531
        :param as_private: Export public or private key, default is False
4532
        :type as_private: bool
4533
        :param witness_type: Witness type, leave empty for default witness_type
4534
        :type witness_type: str
4535
        :param network: Network name. Leave empty for default network
4536
        :type network: str
4537

4538
        :return list of WalletKey, WalletKey:
4539
        """
4540
        if self.main_key and self.main_key.key_type == 'single':
3✔
4541
            key = self.main_key
3✔
4542
            return key if as_private else key.public()
3✔
4543
        elif not self.cosigner:
3✔
4544
            witness_type = witness_type if witness_type else self.witness_type
3✔
4545
            depth = -self.key_depth + self.depth_public_master
3✔
4546
            key = self.key_for_path([], depth, name=name, account_id=account_id, network=network,
3✔
4547
                                    cosigner_id=self.cosigner_id, witness_type=witness_type)
4548
            return key if as_private else key.public()
3✔
4549
        else:
4550
            pm_list = []
3✔
4551
            for cs in self.cosigner:
3✔
4552
                pm_list.append(cs.public_master(account_id, name, as_private, network))
3✔
4553
            return pm_list
3✔
4554

4555
    def transaction_load(self, txid=None, filename=None):
3✔
4556
        """
4557
        Load transaction object from file which has been stored with the :func:`Transaction.save` method.
4558

4559
        Specify transaction ID or filename.
4560

4561
        :param txid: Transaction ID. Transaction object will be read from .bitcoinlib datadir
4562
        :type txid: str
4563
        :param filename: Name of transaction object file
4564
        :type filename: str
4565

4566
        :return Transaction:
4567
        """
4568
        if not filename and not txid:
3✔
4569
            raise WalletError("Please supply filename or txid")
×
4570
        elif not filename and txid:
3✔
4571
            p = Path(BCL_DATA_DIR, '%s.tx' % txid)
3✔
4572
        else:
4573
            p = Path(filename)
3✔
4574
            if not p.parent or str(p.parent) == '.':
3✔
4575
                p = Path(BCL_DATA_DIR, filename)
3✔
4576
        f = p.open('rb')
3✔
4577
        t = pickle.load(f)
3✔
4578
        f.close()
3✔
4579
        return self.transaction_import(t)
3✔
4580

4581
    def info(self, detail=3):
3✔
4582
        """
4583
        Prints wallet information to standard output
4584

4585
        :param detail: Level of detail to show. Specify a number between 0 and 5, with 0 low detail and 5 highest detail
4586
        :type detail: int
4587
        """
4588

4589
        print("=== WALLET ===")
3✔
4590
        print(" ID                             %s" % self.wallet_id)
3✔
4591
        print(" Name                           %s" % self.name)
3✔
4592
        print(" Owner                          %s" % self.owner)
3✔
4593
        print(" Scheme                         %s" % self.scheme)
3✔
4594
        print(" Multisig                       %s" % self.multisig)
3✔
4595
        if self.multisig:
3✔
4596
            print(" Multisig Wallet IDs            %s" % str([w.wallet_id for w in self.cosigner]).strip('[]'))
3✔
4597
            print(" Cosigner ID                    %s" % self.cosigner_id)
3✔
4598
        print(" Witness type                   %s" % self.witness_type)
3✔
4599
        print(" Main network                   %s" % self.network.name)
3✔
4600
        print(" Latest update                  %s" % self.last_updated)
3✔
4601

4602
        if self.multisig:
3✔
4603
            print("\n= Multisig Public Master Keys =")
3✔
4604
            for cs in self.cosigner:
3✔
4605
                print("%5s %3s %-70s %-6s %-8s %s" %
3✔
4606
                      (cs.cosigner_id, cs.main_key.key_id, cs.wif(is_private=False), cs.scheme,
4607
                       "main" if cs.main_key.is_private else "cosigner",
4608
                       '*' if cs.cosigner_id == self.cosigner_id else ''))
4609

4610
            print("For main keys a private master key is available in this wallet to sign transactions. "
3✔
4611
                  "* cosigner key for this wallet")
4612

4613
        if detail and self.main_key:
3✔
4614
            print("\n= Wallet Master Key =")
3✔
4615
            print(" ID                             %s" % self.main_key_id)
3✔
4616
            print(" Private                        %s" % self.main_key.is_private)
3✔
4617
            print(" Depth                          %s" % self.main_key.depth)
3✔
4618

4619
        balances = self._balance_update()
3✔
4620
        if detail > 1:
3✔
4621
            for nw in self.networks():
3✔
4622
                print("\n- NETWORK: %s -" % nw.name)
3✔
4623
                print("- - Keys")
3✔
4624
                if detail < 4:
3✔
4625
                    ds = [self.key_depth]
3✔
4626
                elif detail < 5:
3✔
4627
                    if self.purpose == 45:
×
4628
                        ds = [0, self.key_depth]
×
4629
                    else:
4630
                        ds = [0, self.depth_public_master, self.key_depth]
×
4631
                else:
4632
                    ds = range(8)
3✔
4633
                for d in ds:
3✔
4634
                    is_active = True
3✔
4635
                    if detail > 3:
3✔
4636
                        is_active = False
3✔
4637
                    for key in self.keys(depth=d, network=nw.name, is_active=is_active):
3✔
4638
                        print("%5s %-28s %-45s %-25s %25s" %
3✔
4639
                              (key.id, key.path, key.address, key.name,
4640
                               Value.from_satoshi(key.balance, network=nw).str_unit(currency_repr='symbol')))
4641

4642
                if detail > 2:
3✔
4643
                    include_new = False
3✔
4644
                    if detail > 3:
3✔
4645
                        include_new = True
3✔
4646
                    accounts = self.accounts(network=nw.name)
3✔
4647
                    if not accounts:
3✔
4648
                        accounts = [0]
×
4649
                    for account_id in accounts:
3✔
4650
                        txs = self.transactions(include_new=include_new, account_id=account_id, network=nw.name,
3✔
4651
                                                as_dict=True)
4652
                        print("\n- - Transactions Account %d (%d)" % (account_id, len(txs)))
3✔
4653
                        for tx in txs:
3✔
4654
                            spent = " "
×
4655
                            address = tx['address']
×
4656
                            if not tx['address']:
×
4657
                                address = 'nulldata'
×
4658
                            elif 'spent' in tx and tx['spent'] is False:
×
4659
                                spent = "U"
×
4660
                            status = ""
×
4661
                            if tx['status'] not in ['confirmed', 'unconfirmed']:
×
4662
                                status = tx['status']
×
4663
                            print("%64s %43s %8d %21s %s %s" % (tx['txid'], address, tx['confirmations'],
×
4664
                                                                Value.from_satoshi(tx['value'], network=nw).str_unit(
4665
                                                                    currency_repr='symbol'),
4666
                                                                spent, status))
4667

4668
        print("\n= Balance Totals (includes unconfirmed) =")
3✔
4669
        for na_balance in balances:
3✔
4670
            print("%-20s %-20s %20s" % (na_balance['network'], "(Account %s)" % na_balance['account_id'],
×
4671
                                        Value.from_satoshi(na_balance['balance'], network=na_balance['network']).
4672
                                        str_unit(currency_repr='symbol')))
4673
        print("\n")
3✔
4674

4675
    def as_dict(self, include_private=False):
3✔
4676
        """
4677
        Return wallet information in dictionary format
4678

4679
        :param include_private: Include private key information in dictionary
4680
        :type include_private: bool
4681

4682
        :return dict:
4683
        """
4684

4685
        keys = []
3✔
4686
        transactions = []
3✔
4687
        for netw in self.networks():
3✔
4688
            for key in self.keys(network=netw.name, include_private=include_private, as_dict=True):
3✔
4689
                keys.append(key)
3✔
4690

4691
            if self.multisig:
3✔
4692
                for t in self.transactions(include_new=True, account_id=0, network=netw.name):
3✔
4693
                    transactions.append(t.as_dict())
×
4694
            else:
4695
                accounts = self.accounts(network=netw.name)
3✔
4696
                if not accounts:
3✔
4697
                    accounts = [0]
×
4698
                for account_id in accounts:
3✔
4699
                    for t in self.transactions(include_new=True, account_id=account_id, network=netw.name):
3✔
4700
                        transactions.append(t.as_dict())
×
4701

4702
        return {
3✔
4703
            'wallet_id': self.wallet_id,
4704
            'name': self.name,
4705
            'owner': self._owner,
4706
            'scheme': self.scheme,
4707
            'witness_type': self.witness_type,
4708
            'main_network': self.network.name,
4709
            'main_balance': self.balance(),
4710
            'main_balance_str': self.balance(as_string=True),
4711
            'balances': self._balances,
4712
            'default_account_id': self.default_account_id,
4713
            'multisig_n_required': self.multisig_n_required,
4714
            'cosigner_wallet_ids': [w.wallet_id for w in self.cosigner],
4715
            'cosigner_public_masters': [w.public_master().key().wif() for w in self.cosigner],
4716
            'sort_keys': self.sort_keys,
4717
            'main_key_id': self.main_key_id,
4718
            'encoding': self.encoding,
4719
            'keys': keys,
4720
            'transactions': transactions,
4721
        }
4722

4723
    def as_json(self, include_private=False):
3✔
4724
        """
4725
        Get current key as json formatted string
4726

4727
        :param include_private: Include private key information in JSON
4728
        :type include_private: bool
4729

4730
        :return str:
4731
        """
4732
        adict = self.as_dict(include_private=include_private)
3✔
4733
        return json.dumps(adict, indent=4, default=str)
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc