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

1200wd / bitcoinlib / 19944567212

04 Dec 2025 09:29PM UTC coverage: 90.921% (-0.02%) from 90.944%
19944567212

push

github

Cryp Toon
RELEASE 0.7.6 - MESSAGE SIGNING AND DETERMINISTIC SIGNATURES

8042 of 8845 relevant lines covered (90.92%)

1.81 hits per line

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

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

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

40

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

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

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

53

54
def wallets_list(db_uri=None, include_cosigners=False, db_password=None):
2✔
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
2✔
69
    wallets = session.query(DbWallet).order_by(DbWallet.id).all()
2✔
70
    wlst = []
2✔
71
    for w in wallets:
2✔
72
        if w.parent_id and not include_cosigners:
2✔
73
            continue
2✔
74
        wlst.append({
2✔
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()
2✔
85
    return wlst
2✔
86

87

88
def wallet_exists(wallet, db_uri=None, db_password=None):
2✔
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)]:
2✔
103
        return True
2✔
104
    if isinstance(wallet, int) and wallet in [x['id'] for x in wallets_list(db_uri, db_password=db_password)]:
2✔
105
        return True
2✔
106
    return False
2✔
107

108

109
def wallet_create_or_open(
2✔
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):
2✔
121
        if keys or owner or password or witness_type or key_path:
2✔
122
            _logger.warning("Opening existing wallet, extra options are ignored")
2✔
123
        return Wallet(name, db_uri=db_uri, db_cache_uri=db_cache_uri, db_password=db_password)
2✔
124
    else:
125
        return Wallet.create(name, keys, owner, network, account_id, purpose, scheme, sort_keys,
2✔
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):
2✔
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
2✔
149
    if isinstance(wallet, int) or wallet.isdigit():
2✔
150
        w = session.query(DbWallet).filter_by(id=wallet)
2✔
151
    else:
152
        w = session.query(DbWallet).filter_by(name=wallet)
2✔
153
    if not w or not w.first():
2✔
154
        session.close()
2✔
155
        raise WalletError("Wallet '%s' not found" % wallet)
2✔
156
    wallet_id = w.first().id
2✔
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():
2✔
160
        wallet_delete(cw.id, db_uri=db_uri, force=force)
2✔
161

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

176
    # Delete incomplete transactions from wallet
177
    txs = session.query(DbTransaction).filter_by(wallet_id=wallet_id, is_complete=False)
2✔
178
    for tx in txs:
2✔
179
        session.query(DbTransactionOutput).filter_by(transaction_id=tx.id).delete()
2✔
180
        session.query(DbTransactionInput).filter_by(transaction_id=tx.id).delete()
2✔
181
    txs.delete()
2✔
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})
2✔
185

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

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

192
    return res
2✔
193

194

195
def wallet_empty(wallet, db_uri=None, db_password=None):
2✔
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
2✔
211
    if isinstance(wallet, int) or wallet.isdigit():
2✔
212
        w = session.query(DbWallet).filter_by(id=wallet)
2✔
213
    else:
214
        w = session.query(DbWallet).filter_by(name=wallet)
2✔
215
    if not w or not w.first():
2✔
216
        session.close()
2✔
217
        raise WalletError("Wallet '%s' not found" % wallet)
2✔
218
    wallet_id = w.first().id
2✔
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)
2✔
222
    for k in ks:
2✔
223
        session.query(DbTransactionOutput).filter_by(key_id=k.id).update({DbTransactionOutput.key_id: None})
2✔
224
        session.query(DbTransactionInput).filter_by(key_id=k.id).update({DbTransactionInput.key_id: None})
2✔
225
        session.query(DbKeyMultisigChildren).filter_by(parent_id=k.id).delete()
2✔
226
        session.query(DbKeyMultisigChildren).filter_by(child_id=k.id).delete()
2✔
227
    ks.delete()
2✔
228

229
    # Delete incomplete transactions from wallet
230
    txs = session.query(DbTransaction).filter_by(wallet_id=wallet_id, is_complete=False)
2✔
231
    for tx in txs:
2✔
232
        session.query(DbTransactionOutput).filter_by(transaction_id=tx.id).delete()
2✔
233
        session.query(DbTransactionInput).filter_by(transaction_id=tx.id).delete()
2✔
234
    txs.delete()
2✔
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})
2✔
238

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

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

244
    return True
2✔
245

246

247
def wallet_delete_if_exists(wallet, db_uri=None, force=False, db_password=None):
2✔
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):
2✔
265
        return wallet_delete(wallet, db_uri, force, db_password=db_password)
2✔
266
    return False
2✔
267

268

269
def normalize_path(path):
2✔
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("/")
2✔
283
    npath = ""
2✔
284
    for level in levels:
2✔
285
        if not level:
2✔
286
            raise WalletError("Could not parse path. Index is empty.")
2✔
287
        nlevel = level
2✔
288
        if level[-1] in "'HhPp":
2✔
289
            nlevel = level[:-1] + "'"
2✔
290
        npath += nlevel + "/"
2✔
291
    if npath[-1] == "/":
2✔
292
        npath = npath[:-1]
2✔
293
    return npath
2✔
294

295

296
class WalletKey(object):
2✔
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
2✔
305
    def from_key(name, wallet_id, session, key, account_id=0, network=None, change=0, purpose=84, parent_id=0,
2✔
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
2✔
358
        if isinstance(key, HDKey):
2✔
359
            k = key
2✔
360
            if network is None:
2✔
361
                network = k.network.name
2✔
362
            elif network != k.network.name:
2✔
363
                raise WalletError("Specified network and key network should be the same")
2✔
364
            witness_type = k.witness_type
2✔
365
        elif isinstance(key, Address):
2✔
366
            k = key
2✔
367
            key_is_address = True
2✔
368
            if network is None:
2✔
369
                network = k.network.name
2✔
370
            elif network != k.network.name:
2✔
371
                raise WalletError("Specified network and key network should be the same")
2✔
372
        else:
373
            if network is None:
2✔
374
                network = DEFAULT_NETWORK
2✔
375
            k = HDKey(import_key=key, network=network, witness_type=witness_type)
2✔
376
        if not encoding and witness_type:
2✔
377
            encoding = get_encoding_from_witness(witness_type)
2✔
378
        script_type = script_type_default(witness_type, multisig)
2✔
379

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

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

393
            keyexists = session.query(DbKey).\
2✔
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:
2✔
397
                _logger.warning("Key already exists in this wallet. Key ID: %d" % keyexists.id)
2✔
398
                return WalletKey(keyexists.id, session, k)
2✔
399

400
            if commit:
2✔
401
                wk = session.query(DbKey).filter(
2✔
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:
2✔
407
                    wk.wif = k.wif(witness_type=witness_type, multisig=multisig, is_private=True)
2✔
408
                    wk.is_private = True
2✔
409
                    wk.private = k.private_byte
2✔
410
                    wk.public = k.public_byte
2✔
411
                    wk.path = path
2✔
412
                    session.commit()
2✔
413
                    return WalletKey(wk.id, session, k)
2✔
414

415
            address_index = k.child_index % 0x80000000
2✔
416
            nk = DbKey(id=new_key_id, name=name[:80], wallet_id=wallet_id, public=k.public_byte, private=k.private_byte, purpose=purpose,
2✔
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).\
2✔
424
                filter(DbKey.wallet_id == wallet_id,
425
                       DbKey.address == k.address).first()
426
            if keyexists:
2✔
427
                _logger.warning("Key %s with ID %s already exists" % (k.address, keyexists.id))
2✔
428
                return WalletKey(keyexists.id, session, k)
2✔
429
            nk = DbKey(id=new_key_id, name=name[:80], wallet_id=wallet_id, purpose=purpose,
2✔
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:
2✔
436
            session.merge(DbNetwork(name=network))
2✔
437
        session.add(nk)
2✔
438
        if commit:
2✔
439
            session.commit()
2✔
440
        return WalletKey(nk.id, session, k)
2✔
441

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

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

501
    def __del__(self):
2✔
502
        self.session.close()
2✔
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
2✔
508
    def name(self):
2✔
509
        """
510
        Return name of wallet key
511

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

516
    @name.setter
2✔
517
    def name(self, value):
2✔
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
2✔
528
        self._dbkey.name = value
2✔
529
        self._commit()
2✔
530

531
    @property
2✔
532
    def keys_public(self):
2✔
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
2✔
539
    def keys_private(self):
2✔
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):
2✔
546
        """
547
        Get HDKey object for current WalletKey
548

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

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

562
    def balance(self, as_string=False):
2✔
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:
2✔
573
            return Value.from_satoshi(self._balance, network=self.network).str_unit()
2✔
574
        else:
575
            return self._balance
2✔
576

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

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

593
    def sign_message(self, message, use_rfc6979=True, k=None, hash_type=SIGHASH_ALL, force_canonical=False):
2✔
594
        """
595
        Sign message with this wallet key
596

597
        :param message: Message to be signed. Must be unhashed and in bytes format.
598
        :type message: bytes, hexstring
599
        :param use_rfc6979: Use deterministic value for k nonce to derive k from txid/message according to RFC6979 standard. Default is True, set to False to use random k
600
        :type use_rfc6979: bool
601
        :param k: Provide own k. Only use for testing or if you know what you are doing. Providing wrong value for k can result in leaking your private key!
602
        :type k: int
603
        :param hash_type: Specific hash type, default is SIGHASH_ALL
604
        :type hash_type: int
605
        :param force_canonical: Some wallets require a canonical s value, so you could set this to True
606
        :type force_canonical: bool
607

608
        :return Signature:
609
        """
610
        return self.key().sign_message(message, use_rfc6979, k, hash_type, force_canonical)
2✔
611

612
    def verify_message(self, message, signature):
2✔
613
        """
614
        Verify if provided message is signed by signature for this wallet key.
615

616
        :param message: Message to verify. Must be unhashed and in bytes format.
617
        :type message: bytes, hexstring
618
        :param signature: signature as Signature object
619
        :type signature: Signature
620

621
        :return bool:
622
        """
623
        return self.key().verify_message(message, signature)
2✔
624

625
    def as_dict(self, include_private=False):
2✔
626
        """
627
        Return current key information as dictionary
628

629
        :param include_private: Include private key information in dictionary
630
        :type include_private: bool
631

632
        """
633

634
        kdict = {
2✔
635
            'id': self.key_id,
636
            'key_type': self.key_type,
637
            'network': self.network.name,
638
            'is_private': self.is_private,
639
            'name': self.name,
640
            'key_public': '' if not self.key_public else self.key_public.hex(),
641
            'account_id':  self.account_id,
642
            'parent_id': self.parent_id,
643
            'depth': self.depth,
644
            'change': self.change,
645
            'address_index': self.address_index,
646
            'address': self.address,
647
            'encoding': self.encoding,
648
            'path': self.path,
649
            'balance': self.balance(),
650
            'balance_str': self.balance(as_string=True)
651
        }
652
        if include_private:
2✔
653
            kdict.update({
2✔
654
                'key_private': self.key_private.hex(),
655
                'wif': self.wif,
656
            })
657
        return kdict
2✔
658

659

660
class WalletTransaction(Transaction):
2✔
661
    """
662
    Used as attribute of Wallet class. Child of Transaction object with extra reference to
663
    wallet and database object.
664

665
    All WalletTransaction items are stored in a database
666
    """
667

668
    def __init__(self, hdwallet, account_id=None, *args, **kwargs):
2✔
669
        """
670
        Initialize WalletTransaction object with reference to a Wallet object
671

672
        :param hdwallet: Wallet object, wallet name or ID
673
        :type hdWallet: HDwallet, str, int
674
        :param account_id: Account ID
675
        :type account_id: int
676
        :param args: Arguments for HDWallet parent class
677
        :type args: args
678
        :param kwargs: Keyword arguments for Wallet parent class
679
        :type kwargs: kwargs
680
        """
681

682
        assert isinstance(hdwallet, Wallet)
2✔
683
        self.hdwallet = hdwallet
2✔
684
        self.pushed = False
2✔
685
        self.error = None
2✔
686
        self.response_dict = None
2✔
687
        self.account_id = account_id
2✔
688
        if not account_id:
2✔
689
            self.account_id = self.hdwallet.default_account_id
2✔
690
        witness_type = 'legacy'
2✔
691
        if hdwallet.witness_type in ['segwit', 'p2sh-segwit']:
2✔
692
            witness_type = 'segwit'
2✔
693
        Transaction.__init__(self, witness_type=witness_type, *args, **kwargs)
2✔
694
        addresslist = hdwallet.addresslist()
2✔
695
        self.outgoing_tx = bool([i.address for i in self.inputs if i.address in addresslist])
2✔
696
        self.incoming_tx = bool([o.address for o in self.outputs if o.address in addresslist])
2✔
697

698
    def __repr__(self):
699
        return "<WalletTransaction(input_count=%d, output_count=%d, status=%s, network=%s)>" % \
700
               (len(self.inputs), len(self.outputs), self.status, self.network.name)
701

702
    def __deepcopy__(self, memo):
2✔
703
        cls = self.__class__
2✔
704
        result = cls.__new__(cls)
2✔
705
        memo[id(self)] = result
2✔
706
        self_dict = self.__dict__
2✔
707
        for k, v in self_dict.items():
2✔
708
            if k != 'hdwallet':
2✔
709
                setattr(result, k, deepcopy(v, memo))
2✔
710
        result.hdwallet = self.hdwallet
2✔
711
        return result
2✔
712

713
    @classmethod
2✔
714
    def from_transaction(cls, hdwallet, t):
2✔
715
        """
716
        Create WalletTransaction object from Transaction object
717

718
        :param hdwallet: Wallet object, wallet name or ID
719
        :type hdwallet: HDwallet, str, int
720
        :param t: Specify Transaction object
721
        :type t: Transaction
722

723
        :return WalletClass:
724
        """
725
        return cls(hdwallet=hdwallet, inputs=t.inputs, outputs=t.outputs, locktime=t.locktime, version=t.version,
2✔
726
                   network=t.network.name, fee=t.fee, fee_per_kb=t.fee_per_kb, size=t.size, txid=t.txid,
727
                   txhash=t.txhash, date=t.date, confirmations=t.confirmations, block_height=t.block_height,
728
                   block_hash=t.block_hash, input_total=t.input_total, output_total=t.output_total,
729
                   rawtx=t.rawtx, status=t.status, coinbase=t.coinbase, verified=t.verified, flag=t.flag)
730

731
    @classmethod
2✔
732
    def from_txid(cls, hdwallet, txid):
2✔
733
        """
734
        Read single transaction from database with given transaction ID / transaction hash
735

736
        :param hdwallet: Wallet object
737
        :type hdwallet: Wallet
738
        :param txid: Transaction hash as hexadecimal string
739
        :type txid: str, bytes
740

741
        :return WalletClass:
742

743
        """
744
        sess = hdwallet.session
2✔
745
        # If txid is unknown add it to database, else update
746
        db_tx_query = sess.query(DbTransaction). \
2✔
747
            filter(DbTransaction.wallet_id == hdwallet.wallet_id, DbTransaction.txid == to_bytes(txid))
748
        db_tx = db_tx_query.scalar()
2✔
749
        if not db_tx:
2✔
750
            return
2✔
751

752
        fee_per_kb = None
2✔
753
        if db_tx.fee and db_tx.size:
2✔
754
            fee_per_kb = round((db_tx.fee / db_tx.size) * 1000)
2✔
755
        network = Network(db_tx.network_name)
2✔
756

757
        inputs = []
2✔
758
        for inp in db_tx.inputs:
2✔
759
            sequence = 0xffffffff
2✔
760
            if inp.sequence:
2✔
761
                sequence = inp.sequence
2✔
762
            inp_keys = []
2✔
763
            if inp.key_id:
2✔
764
                key = hdwallet.key(inp.key_id)
2✔
765
                if key.key_type == 'multisig':
2✔
766
                    db_key = sess.query(DbKey).filter_by(id=key.key_id).scalar()
2✔
767
                    for ck in db_key.multisig_children:
2✔
768
                        inp_keys.append(ck.child_key.public.hex())
2✔
769
                else:
770
                    inp_keys = key.key()
2✔
771

772
            inputs.append(Input(
2✔
773
                prev_txid=inp.prev_txid, output_n=inp.output_n, keys=inp_keys, unlocking_script=inp.script,
774
                script_type=inp.script_type, sequence=sequence, index_n=inp.index_n, value=inp.value,
775
                double_spend=inp.double_spend, witness_type=inp.witness_type, network=network, address=inp.address,
776
                witnesses=inp.witnesses))
777

778
        outputs = []
2✔
779
        for out in db_tx.outputs:
2✔
780
            address = ''
2✔
781
            public_key = b''
2✔
782
            if out.key_id:
2✔
783
                key = hdwallet.key(out.key_id)
2✔
784
                address = key.address
2✔
785
                if key.key_type != 'multisig':
2✔
786
                    if key.key() and not isinstance(key.key(), Address):
2✔
787
                        public_key = key.key().public_hex
2✔
788
            outputs.append(Output(value=out.value, address=address, public_key=public_key,
2✔
789
                                  lock_script=out.script, spent=out.spent, output_n=out.output_n,
790
                                  script_type=out.script_type, network=network, change=out.is_change))
791

792
        return cls(hdwallet=hdwallet, inputs=inputs, outputs=outputs, locktime=db_tx.locktime,
2✔
793
                   version=db_tx.version, network=network, fee=db_tx.fee, fee_per_kb=fee_per_kb,
794
                   size=db_tx.size, txid=to_hexstring(txid), date=db_tx.date, confirmations=db_tx.confirmations,
795
                   block_height=db_tx.block_height, input_total=db_tx.input_total, output_total=db_tx.output_total,
796
                   rawtx=db_tx.raw, status=db_tx.status, coinbase=db_tx.coinbase,
797
                   verified=db_tx.verified)
798

799
    def to_transaction(self):
2✔
800
        return Transaction(self.inputs, self.outputs, self.locktime, self.version,
2✔
801
                           self.network.name, self.fee, self.fee_per_kb, self.size,
802
                           self.txid, self.txhash, self.date, self.confirmations,
803
                           self.block_height, self.block_hash, self.input_total,
804
                           self.output_total, self.rawtx, self.status, self.coinbase,
805
                           self.verified, self.witness_type, self.flag)
806

807
    def sign(self, keys=None, index_n=0, multisig_key_n=None, hash_type=SIGHASH_ALL, fail_on_unknown_key=False,
2✔
808
             replace_signatures=False):
809
        """
810
        Sign this transaction. Use existing keys from wallet or use keys argument for extra keys.
811

812
        :param keys: Extra private keys to sign the transaction
813
        :type keys: HDKey, str
814
        :param index_n: Transaction index_n to sign
815
        :type index_n: int
816
        :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
817
        :type multisig_key_n: int
818
        :param hash_type: Hashtype to use, default is SIGHASH_ALL
819
        :type hash_type: int
820
        :param fail_on_unknown_key: Method fails if public key from signature is not found in public key list
821
        :type fail_on_unknown_key: bool
822
        :param replace_signatures: Replace signature with new one if already signed.
823
        :type replace_signatures: bool
824

825
        :return None:
826
        """
827
        priv_key_list_arg = []
2✔
828
        if keys:
2✔
829
            key_paths = list(dict.fromkeys([ti.key_path for ti in self.inputs if ti.key_path[0] == 'm']))
2✔
830
            if not isinstance(keys, list):
2✔
831
                keys = [keys]
2✔
832
            for priv_key in keys:
2✔
833
                if not isinstance(priv_key, HDKey):
2✔
834
                    if isinstance(priv_key, str) and len(str(priv_key).split(' ')) > 4:
2✔
835
                        priv_key = HDKey.from_passphrase(priv_key, network=self.network)
2✔
836
                    else:
837
                        priv_key = HDKey(priv_key, network=self.network.name)
2✔
838
                priv_key_list_arg.append((None, priv_key))
2✔
839
                if key_paths and priv_key.depth == 0 and priv_key.key_type != "single":
2✔
840
                    for key_path in key_paths:
2✔
841
                        priv_key_list_arg.append((key_path, priv_key.key_for_path(key_path)))
2✔
842
        for ti in self.inputs:
2✔
843
            priv_key_list = []
2✔
844
            for (key_path, priv_key) in priv_key_list_arg:
2✔
845
                if (not key_path or key_path == ti.key_path) and priv_key not in priv_key_list:
2✔
846
                    priv_key_list.append(priv_key)
2✔
847
            priv_key_list += [k for k in ti.keys if k.is_private]
2✔
848
            Transaction.sign(self, priv_key_list, ti.index_n, multisig_key_n, hash_type, fail_on_unknown_key,
2✔
849
                             replace_signatures)
850
        self.verify()
2✔
851
        self.error = ""
2✔
852

853
    def send(self, broadcast=True):
2✔
854
        """
855
        Verify and push transaction to network. Update UTXO's in database after successful send
856

857
        :param broadcast: Verify transaction and broadcast, if set to False the transaction is verified but not broadcasted, i. Default is True
858
        :type broadcast: bool
859

860
        :return None:
861

862
        """
863

864
        self.error = None
2✔
865
        if not self.verified and not self.verify():
2✔
866
            self.error = "Cannot verify transaction"
2✔
867
            return None
2✔
868

869
        if not broadcast:
2✔
870
            return None
2✔
871

872
        srv = Service(network=self.network.name, wallet_name=self.hdwallet.name, providers=self.hdwallet.providers,
2✔
873
                      cache_uri=self.hdwallet.db_cache_uri, strict=self.hdwallet.strict)
874
        res = srv.sendrawtransaction(self.raw_hex())
2✔
875
        if not res:
2✔
876
            self.error = "Cannot send transaction. %s" % srv.errors
×
877
            return None
×
878
        if 'txid' in res:
2✔
879
            _logger.info("Successfully pushed transaction, result: %s" % res)
2✔
880
            self.txid = res['txid']
2✔
881
            self.status = 'unconfirmed'
2✔
882
            self.confirmations = 0
2✔
883
            self.pushed = True
2✔
884
            self.response_dict = srv.results
2✔
885
            self.store()
2✔
886

887
            # Update db: Update spent UTXO's, add transaction to database
888
            for inp in self.inputs:
2✔
889
                txid = inp.prev_txid
2✔
890
                utxos = self.hdwallet.session.query(DbTransactionOutput).join(DbTransaction).\
2✔
891
                    filter(DbTransaction.txid == txid,
892
                           DbTransactionOutput.output_n == inp.output_n_int,
893
                           DbTransactionOutput.spent.is_(False)).all()
894
                for u in utxos:
2✔
895
                    u.spent = True
2✔
896

897
            self.hdwallet._commit()
2✔
898
            self.hdwallet._balance_update(network=self.network.name)
2✔
899
            return None
2✔
900
        self.error = "Transaction not send, unknown response from service providers"
×
901

902
    def store(self):
2✔
903
        """
904
        Store this transaction to database
905

906
        :return int: Transaction index number
907
        """
908

909
        sess = self.hdwallet.session
2✔
910
        # If txid is unknown add it to database, else update
911
        db_tx_query = sess.query(DbTransaction). \
2✔
912
            filter(DbTransaction.wallet_id == self.hdwallet.wallet_id, DbTransaction.txid == bytes.fromhex(self.txid))
913
        db_tx = db_tx_query.scalar()
2✔
914
        if not db_tx:
2✔
915
            db_tx_query = sess.query(DbTransaction). \
2✔
916
                filter(DbTransaction.wallet_id.is_(None), DbTransaction.txid == bytes.fromhex(self.txid))
917
            db_tx = db_tx_query.first()
2✔
918
            if db_tx:
2✔
919
                db_tx.wallet_id = self.hdwallet.wallet_id
2✔
920

921
        if not db_tx:
2✔
922
            new_tx = DbTransaction(
2✔
923
                wallet_id=self.hdwallet.wallet_id, txid=bytes.fromhex(self.txid), block_height=self.block_height,
924
                size=self.size, confirmations=self.confirmations, date=self.date, fee=self.fee, status=self.status,
925
                input_total=self.input_total, output_total=self.output_total, network_name=self.network.name,
926
                raw=self.rawtx, verified=self.verified, account_id=self.account_id, locktime=self.locktime,
927
                version=self.version_int, coinbase=self.coinbase, index=self.index)
928
            sess.add(new_tx)
2✔
929
            self.hdwallet._commit()
2✔
930
            txidn = new_tx.id
2✔
931
        else:
932
            txidn = db_tx.id
2✔
933
            db_tx.block_height = self.block_height if self.block_height else db_tx.block_height
2✔
934
            db_tx.confirmations = self.confirmations if self.confirmations else db_tx.confirmations
2✔
935
            db_tx.date = self.date if self.date else db_tx.date
2✔
936
            db_tx.fee = self.fee if self.fee else db_tx.fee
2✔
937
            db_tx.status = self.status if self.status else db_tx.status
2✔
938
            db_tx.input_total = self.input_total if self.input_total else db_tx.input_total
2✔
939
            db_tx.output_total = self.output_total if self.output_total else db_tx.output_total
2✔
940
            db_tx.network_name = self.network.name if self.network.name else db_tx.network_name
2✔
941
            db_tx.raw = self.rawtx if self.rawtx else db_tx.raw
2✔
942
            db_tx.verified = self.verified
2✔
943
            db_tx.locktime = self.locktime
2✔
944
            self.hdwallet._commit()
2✔
945

946
        assert txidn
2✔
947
        for ti in self.inputs:
2✔
948
            tx_key = sess.query(DbKey).filter_by(wallet_id=self.hdwallet.wallet_id, address=ti.address).scalar()
2✔
949
            key_id = None
2✔
950
            if tx_key:
2✔
951
                key_id = tx_key.id
2✔
952
                tx_key.used = True
2✔
953
            tx_input = sess.query(DbTransactionInput). \
2✔
954
                filter_by(transaction_id=txidn, index_n=ti.index_n).scalar()
955
            if not tx_input:
2✔
956
                witnesses = int_to_varbyteint(len(ti.witnesses)) + b''.join([bytes(varstr(w)) for w in ti.witnesses])
2✔
957
                new_tx_item = DbTransactionInput(
2✔
958
                    transaction_id=txidn, output_n=ti.output_n_int, key_id=key_id, value=ti.value,
959
                    prev_txid=ti.prev_txid, index_n=ti.index_n, double_spend=ti.double_spend,
960
                    script=ti.unlocking_script, script_type=ti.script_type, witness_type=ti.witness_type,
961
                    sequence=ti.sequence, address=ti.address, witnesses=witnesses)
962
                sess.add(new_tx_item)
2✔
963
            elif key_id:
2✔
964
                tx_input.key_id = key_id
2✔
965
                if ti.value:
2✔
966
                    tx_input.value = ti.value
2✔
967
                if ti.prev_txid:
2✔
968
                    tx_input.prev_txid = ti.prev_txid
2✔
969
                if ti.unlocking_script:
2✔
970
                    tx_input.script = ti.unlocking_script
2✔
971

972
            self.hdwallet._commit()
2✔
973
        for to in self.outputs:
2✔
974
            tx_key = sess.query(DbKey).\
2✔
975
                filter_by(wallet_id=self.hdwallet.wallet_id, address=to.address).scalar()
976
            key_id = None
2✔
977
            if tx_key:
2✔
978
                key_id = tx_key.id
2✔
979
                tx_key.used = True
2✔
980
            spent = to.spent
2✔
981
            tx_output = sess.query(DbTransactionOutput). \
2✔
982
                filter_by(transaction_id=txidn, output_n=to.output_n).scalar()
983
            if not tx_output:
2✔
984
                new_tx_item = DbTransactionOutput(
2✔
985
                    transaction_id=txidn, output_n=to.output_n, key_id=key_id, address=to.address, value=to.value,
986
                    spent=spent, script=to.lock_script, script_type=to.script_type, is_change=to.change)
987
                sess.add(new_tx_item)
2✔
988
            elif key_id:
2✔
989
                tx_output.key_id = key_id
2✔
990
                tx_output.spent = spent if spent is not None else tx_output.spent
2✔
991
            self.hdwallet._commit()
2✔
992
        return txidn
2✔
993

994
    def info(self):
2✔
995
        """
996
        Print Wallet transaction information to standard output. Include send information.
997
        """
998

999
        Transaction.info(self)
2✔
1000
        print(f"Pushed to network: %s" % self.pushed)
2✔
1001
        print(f"Wallet: {self.hdwallet.name}")
2✔
1002
        if self.error:
2✔
1003
            print(f"Errors: {self.error}")
×
1004
        print("\n")
2✔
1005

1006
    def export(self, skip_change=True):
2✔
1007
        """
1008
        Export this transaction as list of tuples in the following format:
1009
            (transaction_date, transaction_hash, in/out, addresses_in, addresses_out, value, fee)
1010

1011
        A transaction with multiple inputs or outputs results in multiple tuples.
1012

1013
        :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.
1014
        :type skip_change: boolean
1015

1016
        :return list of tuple:
1017
        """
1018
        mut_list = []
2✔
1019
        wlt_addresslist = self.hdwallet.addresslist()
2✔
1020
        input_addresslist = [i.address for i in self.inputs]
2✔
1021
        if self.outgoing_tx:
2✔
1022
            fee_per_output = self.fee / len(self.outputs)
2✔
1023
            for o in self.outputs:
2✔
1024
                o_value = -o.value
2✔
1025
                if o.address in wlt_addresslist:
2✔
1026
                    if skip_change:
2✔
1027
                        continue
2✔
1028
                    elif self.incoming_tx:
×
1029
                        o_value = 0
×
1030
                mut_list.append((self.date, self.txid, 'out', input_addresslist, o.address, o_value, fee_per_output))
2✔
1031
        else:
1032
            for o in self.outputs:
2✔
1033
                if o.address not in wlt_addresslist:
2✔
1034
                    continue
×
1035
                mut_list.append((self.date, self.txid, 'in', input_addresslist, o.address, o.value, 0))
2✔
1036
        return mut_list
2✔
1037

1038
    def save(self, filename=None):
2✔
1039
        """
1040
        Store transaction object as file, so it can be imported in bitcoinlib later with the :func:`load` method.
1041

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

1045
        :return:
1046
        """
1047
        if not filename:
2✔
1048
            p = Path(BCL_DATA_DIR, '%s.tx' % self.txid)
2✔
1049
        else:
1050
            p = Path(filename)
2✔
1051
            if not p.parent or str(p.parent) == '.':
2✔
1052
                p = Path(BCL_DATA_DIR, filename)
2✔
1053
        f = p.open('wb')
2✔
1054
        t = self.to_transaction()
2✔
1055
        pickle.dump(t, f)
2✔
1056
        f.close()
2✔
1057

1058
    def delete(self):
2✔
1059
        """
1060
        Delete this transaction from database.
1061

1062
        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
1063

1064
        :return int: Number of deleted transactions
1065
        """
1066

1067
        session = self.hdwallet.session
2✔
1068
        txid = bytes.fromhex(self.txid)
2✔
1069
        tx_query = session.query(DbTransaction).filter_by(txid=txid)
2✔
1070
        tx = tx_query.scalar()
2✔
1071
        session.query(DbTransactionOutput).filter_by(transaction_id=tx.id).delete()
2✔
1072
        for inp in tx.inputs:
2✔
1073
            prev_utxos = session.query(DbTransactionOutput).join(DbTransaction).\
2✔
1074
                filter(DbTransaction.txid == inp.prev_txid, DbTransactionOutput.output_n == inp.output_n,
1075
                       DbTransactionOutput.spent.is_(True), DbTransaction.wallet_id == self.hdwallet.wallet_id).all()
1076
            for u in prev_utxos:
2✔
1077
                # Check if output is spent in another transaction
1078
                if session.query(DbTransactionInput).filter(DbTransactionInput.transaction_id ==
2✔
1079
                                                            inp.transaction_id).first():
1080
                    u.spent = False
2✔
1081
        session.query(DbTransactionInput).filter_by(transaction_id=tx.id).delete()
2✔
1082
        qr = session.query(DbKey).filter_by(latest_txid=txid)
2✔
1083
        qr.update({DbKey.latest_txid: None, DbKey.used: False})
2✔
1084
        res = tx_query.delete()
2✔
1085
        key = qr.scalar()
2✔
1086
        if key:
2✔
1087
            self.hdwallet._balance_update(key_id=key.id)
×
1088
        self.hdwallet._commit()
2✔
1089
        return res
2✔
1090

1091
    def bumpfee(self, fee=0, extra_fee=0, broadcast=False):
2✔
1092
        """
1093
        Increase fee for this transaction. If replace-by-fee is signaled in this transaction the fee can be
1094
        increased to speed up inclusion on the blockchain.
1095

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

1099
        The extra fee will be deducted from change output. This method fails if there are not enough change outputs
1100
        to cover fees.
1101

1102
        If this transaction does not have enough inputs to cover extra fee, an extra wallet utxo will be aaded to
1103
        inputs if available.
1104

1105
        Previous broadcasted transaction will be removed from wallet with this replace-by-fee transaction and wallet
1106
        information updated.
1107

1108
        :param fee: New fee for this transaction
1109
        :type fee: int
1110
        :param extra_fee: Extra fee to add to current transaction fee
1111
        :type extra_fee: int
1112
        :param broadcast: Increase fee and directly broadcast transaction to the network
1113
        :type broadcast: bool
1114

1115
        :return None:
1116
        """
1117
        fees_not_provided = not (fee or extra_fee)
2✔
1118
        old_txid = self.txid
2✔
1119
        try:
2✔
1120
            super(WalletTransaction, self).bumpfee(fee, extra_fee)
2✔
1121
        except TransactionError as e:
2✔
1122
            if str(e) != "Not enough unspent outputs to bump transaction fee":
2✔
1123
                raise TransactionError(str(e))
×
1124
            else:
1125
                # Add extra input to cover fee
1126
                if fees_not_provided:
2✔
1127
                    extra_fee = int(self.fee * (0.03 ** BUMPFEE_DEFAULT_MULTIPLIER) +
2✔
1128
                              (self.vsize * BUMPFEE_DEFAULT_MULTIPLIER))
1129
                new_inp = self.add_input_from_wallet(amount_min=extra_fee)
2✔
1130
                # Add value of extra input to change output
1131
                change_outputs = [o for o in self.outputs if o.change]
2✔
1132
                if change_outputs:
2✔
1133
                    change_outputs[0].value += self.inputs[new_inp].value
×
1134
                else:
1135
                    self.add_output(self.inputs[new_inp].value, self.hdwallet.get_key().address, change=True)
2✔
1136
                    if fees_not_provided:
2✔
1137
                        extra_fee += 25 * BUMPFEE_DEFAULT_MULTIPLIER
2✔
1138
                super(WalletTransaction, self).bumpfee(fee, extra_fee)
2✔
1139
        # remove previous transaction and update wallet
1140
        if self.pushed:
2✔
1141
            self.hdwallet.transaction_delete(old_txid)
2✔
1142
        if broadcast:
2✔
1143
            self.send()
2✔
1144

1145
    def add_input_from_wallet(self, amount_min=0, key_id=None, min_confirms=1):
2✔
1146
        """
1147
        Add a new input from an utxo of this wallet. If not key_id is specified it adds the first input it finds with
1148
        the minimum amount and minimum confirms specified.
1149

1150
        WARNING: Change output and fees are not updated, so you risk overpaying fees!
1151

1152
        :param amount_min: Minimum value of new input
1153
        :type amount_min: int
1154
        :param key_id: Filter by this key id
1155
        :type key_id: int
1156
        :param min_confirms: Minimum confirms of utxo
1157
        :type min_confirms: int
1158

1159
        :return int: Index number of new input
1160
        """
1161
        if not amount_min:
2✔
1162
            amount_min = self.network.dust_amount
2✔
1163
        utxos = self.hdwallet.utxos(self.account_id, network=self.network.name, min_confirms=min_confirms,
2✔
1164
                                    key_id=key_id)
1165
        current_inputs = [(i.prev_txid.hex(), i.output_n_int) for i in self.inputs]
2✔
1166
        unused_inputs = [u for u in utxos
2✔
1167
                         if (u['txid'], u['output_n']) not in current_inputs and u['value'] >= amount_min]
1168
        if not unused_inputs:
2✔
1169
            raise TransactionError("Not enough unspent inputs found for transaction %s" %
2✔
1170
                                   self.txid)
1171
        # take first input
1172
        utxo = unused_inputs[0]
2✔
1173
        inp_keys, key = self.hdwallet._objects_by_key_id(utxo['key_id'])
2✔
1174
        unlock_script_type = get_unlocking_script_type(utxo['script_type'], self.witness_type,
2✔
1175
                                                       multisig=self.hdwallet.multisig)
1176
        return self.add_input(utxo['txid'], utxo['output_n'], keys=inp_keys, script_type=unlock_script_type,
2✔
1177
                              sigs_required=self.hdwallet.multisig_n_required, sort=self.hdwallet.sort_keys,
1178
                              compressed=key.compressed, value=utxo['value'], address=utxo['address'],
1179
                              witness_type=key.witness_type)
1180

1181

1182
class Wallet(object):
2✔
1183
    """
1184
    Class to create and manage keys Using the BIP0044 Hierarchical Deterministic wallet definitions, so you can
1185
    use one Masterkey to generate as much child keys as you want in a structured manner.
1186

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

1190
    Easily send and receive transactions. Compose transactions automatically or select unspent outputs.
1191

1192
    Each wallet name must be unique and can contain only one cointype and purpose, but practically unlimited
1193
    accounts and addresses.
1194
    """
1195

1196
    @classmethod
2✔
1197
    def _create(cls, name, key, owner, network, account_id, purpose, scheme, parent_id, sort_keys,
2✔
1198
                witness_type, encoding, multisig, sigs_required, cosigner_id, key_path,
1199
                anti_fee_sniping, db_uri, db_cache_uri, db_password):
1200

1201
        db = Db(db_uri, db_password)
2✔
1202
        session = db.session
2✔
1203
        if (db_uri is None or db_uri.startswith("sqlite")) and db_cache_uri is None:
2✔
1204
            db_cache_uri = DEFAULT_DATABASE_CACHE
2✔
1205
        elif not db_cache_uri:
2✔
1206
            db_cache_uri = db.db_uri
2✔
1207
        db_uri = db.db_uri
2✔
1208
        if session.query(DbWallet).filter_by(name=name).count():
2✔
1209
            raise WalletError("Wallet with name '%s' already exists" % name)
2✔
1210
        else:
1211
            _logger.info("Create new wallet '%s'" % name)
2✔
1212
        if not name:
2✔
1213
            raise WalletError("Please enter wallet name")
2✔
1214

1215
        if not isinstance(key_path, list):
2✔
1216
            key_path = key_path.split('/')
2✔
1217
        key_depth = 1 if not key_path else len(key_path) - 1
2✔
1218
        base_path = 'm'
2✔
1219
        if hasattr(key, 'depth'):
2✔
1220
            if key.depth is None:
2✔
1221
                key.depth = key_depth
2✔
1222
            if key.depth > 0:
2✔
1223
                hardened_keys = [x for x in key_path if x[-1:] == "'"]
2✔
1224
                if hardened_keys:
2✔
1225
                    depth_public_master = key_path.index(hardened_keys[-1])
2✔
1226
                    if depth_public_master != key.depth:
2✔
1227
                        raise WalletError("Depth of provided public master key %d does not correspond with key path "
2✔
1228
                                          "%s. Did you provide correct witness_type and multisig attribute?" %
1229
                                          (key.depth, key_path))
1230
                key_path = ['M'] + key_path[key.depth+1:]
2✔
1231
                base_path = 'M'
2✔
1232

1233
        if isinstance(key_path, list):
2✔
1234
            key_path = '/'.join(key_path)
2✔
1235
        session.merge(DbNetwork(name=network))
2✔
1236
        new_wallet = DbWallet(name=name, owner=owner, network_name=network, purpose=purpose, scheme=scheme,
2✔
1237
                              sort_keys=sort_keys, witness_type=witness_type, parent_id=parent_id, encoding=encoding,
1238
                              multisig=multisig, multisig_n_required=sigs_required, cosigner_id=cosigner_id,
1239
                              key_path=key_path, anti_fee_sniping=anti_fee_sniping)
1240
        session.add(new_wallet)
2✔
1241
        session.commit()
2✔
1242
        new_wallet_id = new_wallet.id
2✔
1243

1244
        if scheme == 'bip32' and multisig and parent_id is None:
2✔
1245
            w = cls(new_wallet_id, db_uri=db_uri, db_cache_uri=db_cache_uri)
2✔
1246
        elif scheme == 'bip32':
2✔
1247
            mk = WalletKey.from_key(key=key, name=name, session=session, wallet_id=new_wallet_id, network=network,
2✔
1248
                                    account_id=account_id, purpose=purpose, key_type='bip32', encoding=encoding,
1249
                                    witness_type=witness_type, multisig=multisig, path=base_path)
1250
            new_wallet.main_key_id = mk.key_id
2✔
1251
            session.commit()
2✔
1252

1253
            w = cls(new_wallet_id, db_uri=db_uri, db_cache_uri=db_cache_uri, main_key_object=mk.key())
2✔
1254
            w.key_for_path([], account_id=account_id, cosigner_id=cosigner_id, change=0, address_index=0)
2✔
1255
        else:  # scheme == 'single':
1256
            if not key:
2✔
1257
                key = HDKey(network=network, depth=key_depth)
2✔
1258
            mk = WalletKey.from_key(key=key, name=name, session=session, wallet_id=new_wallet_id, network=network,
2✔
1259
                                    account_id=account_id, purpose=purpose, key_type='single', encoding=encoding,
1260
                                    witness_type=witness_type, multisig=multisig)
1261
            new_wallet.main_key_id = mk.key_id
2✔
1262
            session.commit()
2✔
1263
            w = cls(new_wallet_id, db_uri=db_uri, db_cache_uri=db_cache_uri, main_key_object=mk.key())
2✔
1264

1265
        session.close()
2✔
1266
        return w
2✔
1267

1268
    def _commit(self):
2✔
1269
        try:
2✔
1270
            self.session.commit()
2✔
1271
        except Exception as e:
×
1272
            self.session.rollback()
×
1273
            raise WalletError("Could not commit to database, rollback performed! Database error: %s" % str(e))
×
1274

1275
    @classmethod
2✔
1276
    def create(cls, name, keys=None, owner='', network=None, account_id=0, purpose=0, scheme='bip32',
2✔
1277
               sort_keys=True, password='', witness_type=None, encoding=None, multisig=None, sigs_required=None,
1278
               cosigner_id=None, key_path=None, anti_fee_sniping=True, db_uri=None, db_cache_uri=None,
1279
               db_password=None):
1280
        """
1281
        Create Wallet and insert in database. Generate masterkey or import key when specified.
1282

1283
        When only a name is specified a legacy Wallet with a single masterkey is created with standard p2wpkh
1284
        scripts.
1285

1286
        >>> if wallet_delete_if_exists('create_legacy_wallet_test'): pass
1287
        >>> w = Wallet.create('create_legacy_wallet_test')
1288
        >>> w
1289
        <Wallet(name="create_legacy_wallet_test")>
1290

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

1294
        >>> if wallet_delete_if_exists('create_legacy_multisig_wallet_test'): pass
1295
        >>> w = Wallet.create('create_legacy_multisig_wallet_test', keys=[HDKey(), HDKey().public()])
1296

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

1300
        >>> if wallet_delete_if_exists('create_segwit_wallet_test'): pass
1301
        >>> w = Wallet.create('create_segwit_wallet_test', witness_type='segwit')
1302

1303
        Use a masterkey WIF when creating a wallet:
1304

1305
        >>> wif = 'xprv9s21ZrQH143K3cxbMVswDTYgAc9CeXABQjCD9zmXCpXw4MxN93LanEARbBmV3utHZS9Db4FX1C1RbC5KSNAjQ5WNJ1dDBJ34PjfiSgRvS8x'
1306
        >>> if wallet_delete_if_exists('bitcoinlib_legacy_wallet_test', force=True): pass
1307
        >>> w = Wallet.create('bitcoinlib_legacy_wallet_test', wif)
1308
        >>> w
1309
        <Wallet(name="bitcoinlib_legacy_wallet_test")>
1310
        >>> # Add some test utxo data:
1311
        >>> if w.utxo_add('16QaHuFkfuebXGcYHmehRXBBX7RG9NbtLg', 100000000, '748799c9047321cb27a6320a827f1f69d767fe889c14bf11f27549638d566fe4', 0): pass
1312

1313
        Please mention account_id if you are using multiple accounts.
1314

1315
        :param name: Unique name of this Wallet
1316
        :type name: str
1317
        :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
1318
        :type keys: str, bytes, int, HDKey, HDWalletKey, list of str, list of bytes, list of int, list of HDKey, list of HDWalletKey
1319
        :param owner: Wallet owner for your own reference
1320
        :type owner: str
1321
        :param network: Network name, use default if not specified
1322
        :type network: str
1323
        :param account_id: Account ID, default is 0
1324
        :type account_id: int
1325
        :param purpose: BIP43 purpose field, will be derived from witness_type and multisig by default
1326
        :type purpose: int
1327
        :param scheme: Key structure type, i.e. BIP32 or single
1328
        :type scheme: str
1329
        :param sort_keys: Sort keys according to BIP45 standard (used for multisig keys)
1330
        :type sort_keys: bool
1331
        :param password: Password to protect passphrase, only used if a passphrase is supplied in the 'key' argument.
1332
        :type password: str
1333
        :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
1334
        :type witness_type: str
1335
        :param encoding: Encoding used for address generation: base58 or bech32. Default is derive from wallet and/or witness type
1336
        :type encoding: str
1337
        :param multisig: Multisig wallet or child of a multisig wallet, default is None / derive from number of keys.
1338
        :type multisig: bool
1339
        :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
1340
        :type sigs_required: int
1341
        :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.
1342
        :type cosigner_id: int
1343
        :param key_path: Key path for multisig wallet, use to create your own non-standard key path. Key path must follow the following rules:
1344
            * Path start with masterkey (m) and end with change / address_index
1345
            * If accounts are used, the account level must be 3. I.e.: m/purpose/coin_type/account/
1346
            * All keys must be hardened, except for change, address_index or cosigner_id
1347
            * Max length of path is 8 levels
1348
        :type key_path: list, str
1349
        :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.
1350
        :type anti_fee_sniping: boolean
1351
        :param db_uri: URI of the database for wallets, wallet transactions and keys
1352
        :type db_uri: str
1353
        :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)
1354
        :type db_cache_uri: str
1355
        :param db_password: Password to encrypt database. Requires the installation of sqlcipher (see documentation).
1356
        :type db_password: str
1357

1358
        :return Wallet:
1359
        """
1360

1361
        if multisig is None:
2✔
1362
            if keys and isinstance(keys, list) and len(keys) > 1:
2✔
1363
                multisig = True
2✔
1364
            else:
1365
                multisig = False
2✔
1366
        if scheme not in ['bip32', 'single']:
2✔
1367
            raise WalletError("Only bip32 or single key scheme's are supported at the moment")
2✔
1368
        if witness_type not in [None, 'legacy', 'p2sh-segwit', 'segwit']:
2✔
1369
            raise WalletError("Witness type %s not supported at the moment" % witness_type)
2✔
1370
        if name.isdigit():
2✔
1371
            raise WalletError("Wallet name '%s' invalid, please include letter characters" % name)
2✔
1372

1373
        if multisig:
2✔
1374
            if password:
2✔
1375
                raise WalletError("Password protected multisig wallets not supported")
2✔
1376
            if scheme != 'bip32':
2✔
1377
                raise WalletError("Multisig wallets should use bip32 scheme not %s" % scheme)
2✔
1378
            if sigs_required is None:
2✔
1379
                sigs_required = len(keys)
2✔
1380
            if sigs_required > len(keys):
2✔
1381
                raise WalletError("Number of keys required to sign is greater then number of keys provided")
2✔
1382
        elif not isinstance(keys, list):
2✔
1383
            keys = [keys]
2✔
1384
        if len(keys) > 15:
2✔
1385
            raise WalletError("Redeemscripts with more then 15 keys are non-standard and could result in "
2✔
1386
                              "locked up funds")
1387

1388
        hdkey_list = []
2✔
1389
        # if keys and isinstance(keys, list) and sort_keys:
1390
        #     keys.sort(key=lambda x: ('0' if isinstance(x, HDKey) else '1'))
1391
        for key in keys:
2✔
1392
            if isinstance(key, HDKey):
2✔
1393
                if network and network != key.network.name:
2✔
1394
                    raise WalletError("Network from key (%s) is different then specified network (%s)" %
2✔
1395
                                      (key.network.name, network))
1396
                network = key.network.name
2✔
1397
                if witness_type is None:
2✔
1398
                    witness_type = key.witness_type
2✔
1399
            elif key:
2✔
1400
                # If key consists of several words assume it is a passphrase and convert it to a HDKey object
1401
                if isinstance(key, str) and len(key.split(" ")) > 1:
2✔
1402
                    if not network:
2✔
1403
                        network = DEFAULT_NETWORK
2✔
1404
                    key = HDKey.from_seed(Mnemonic().to_seed(key, password), network=network, witness_type=witness_type)
2✔
1405
                else:
1406
                    try:
2✔
1407
                        if isinstance(key, WalletKey):
2✔
1408
                            key = key._hdkey_object
2✔
1409
                        else:
1410
                            key = HDKey(key, password=password, witness_type=witness_type, network=network)
2✔
1411
                    except BKeyError:
2✔
1412
                        try:
2✔
1413
                            scheme = 'single'
2✔
1414
                            key = Address.parse(key, encoding=encoding, network=network)
2✔
1415
                        except Exception:
2✔
1416
                            raise WalletError("Invalid key or address: %s" % key)
2✔
1417
                    if network is None:
2✔
1418
                        network = key.network.name
2✔
1419
                    if witness_type is None:
2✔
1420
                        witness_type = key.witness_type
2✔
1421
            hdkey_list.append(key)
2✔
1422

1423
        if network is None:
2✔
1424
            network = DEFAULT_NETWORK
2✔
1425
        if witness_type is None:
2✔
1426
            witness_type = DEFAULT_WITNESS_TYPE
2✔
1427
        if network in ['dogecoin', 'dogecoin_testnet'] and witness_type != 'legacy':
2✔
1428
            raise WalletError("Segwit is not supported for %s wallets" % network.capitalize())
×
1429
        elif network in ('dogecoin', 'dogecoin_testnet') and witness_type not in ('legacy', 'p2sh-segwit'):
2✔
1430
            raise WalletError("Pure segwit addresses are not supported for Dogecoin wallets. "
×
1431
                              "Please use p2sh-segwit instead")
1432

1433
        if not key_path:
2✔
1434
            if scheme == 'single':
2✔
1435
                key_path = ['m']
2✔
1436
                purpose = 0
2✔
1437
            else:
1438
                key_path, purpose, encoding = get_key_structure_data(witness_type, multisig, purpose, encoding)
2✔
1439
        else:
1440
            if purpose is None:
2✔
1441
                purpose = 0
2✔
1442
        if not encoding:
2✔
1443
            encoding = get_encoding_from_witness(witness_type)
2✔
1444

1445
        if multisig:
2✔
1446
            key = ''
2✔
1447
        else:
1448
            key = hdkey_list[0]
2✔
1449

1450
        main_key_path = key_path
2✔
1451
        if multisig:
2✔
1452
            if sort_keys:
2✔
1453
                # FIXME: Think of simple construction to distinct between key order and cosigner id, the solution below is a bit confusing
1454
                # cosigner_id_key = None if cosigner_id is None else hdkey_list[cosigner_id].public_byte
1455
                hdkey_list.sort(key=lambda x: x.public_byte)
2✔
1456
                # Update cosigner id if order of keys changed
1457
                # cosigner_id = cosigner_id if (cosigner_id is None or cosigner_id_key is None) else (
1458
                #     hdkey_list.index([k for k in hdkey_list if k.public_byte == cosigner_id_key][0]))
1459

1460
            cos_prv_lst = [hdkey_list.index(cw) for cw in hdkey_list if cw.is_private]
2✔
1461
            if cosigner_id is None:
2✔
1462
                if not cos_prv_lst:
2✔
1463
                    raise WalletError("This wallet does not contain any private keys, please specify cosigner_id for "
2✔
1464
                                      "this wallet")
1465
                elif len(cos_prv_lst) > 1:
2✔
1466
                    raise WalletError("This wallet contains more then 1 private key, please specify "
×
1467
                                      "cosigner_id for this wallet")
1468
                cosigner_id = 0 if not cos_prv_lst else cos_prv_lst[0]
2✔
1469
            if hdkey_list[cosigner_id].key_type == 'single':
2✔
1470
                main_key_path = 'm'
2✔
1471

1472
        hdpm = cls._create(name, key, owner=owner, network=network, account_id=account_id, purpose=purpose,
2✔
1473
                           scheme=scheme, parent_id=None, sort_keys=sort_keys, witness_type=witness_type,
1474
                           encoding=encoding, multisig=multisig, sigs_required=sigs_required, cosigner_id=cosigner_id,
1475
                           anti_fee_sniping=anti_fee_sniping, key_path=main_key_path, db_uri=db_uri,
1476
                           db_cache_uri=db_cache_uri, db_password=db_password)
1477

1478
        if multisig:
2✔
1479
            wlt_cos_id = 0
2✔
1480
            for cokey in hdkey_list:
2✔
1481
                if hdpm.network.name != cokey.network.name:
2✔
1482
                    raise WalletError("Network for key %s (%s) is different then network specified: %s/%s" %
×
1483
                                      (cokey.wif(is_private=False), cokey.network.name, network, hdpm.network.name))
1484
                scheme = 'bip32'
2✔
1485
                wn = name + '-cosigner-%d' % wlt_cos_id
2✔
1486
                c_key_path = key_path
2✔
1487
                if cokey.key_type == 'single':
2✔
1488
                    scheme = 'single'
2✔
1489
                    c_key_path = ['m']
2✔
1490
                w = cls._create(name=wn, key=cokey, owner=owner, network=network, account_id=account_id,
2✔
1491
                                purpose=hdpm.purpose, scheme=scheme, parent_id=hdpm.wallet_id, sort_keys=sort_keys,
1492
                                witness_type=hdpm.witness_type, encoding=encoding, multisig=True,
1493
                                sigs_required=None, cosigner_id=wlt_cos_id, key_path=c_key_path,
1494
                                anti_fee_sniping=anti_fee_sniping, db_uri=db_uri, db_cache_uri=db_cache_uri,
1495
                                db_password=db_password)
1496
                hdpm.cosigner.append(w)
2✔
1497
                wlt_cos_id += 1
2✔
1498
            # hdpm._dbwallet = hdpm.session.query(DbWallet).filter(DbWallet.id == hdpm.wallet_id)
1499
            # hdpm._dbwallet.update({DbWallet.cosigner_id: hdpm.cosigner_id})
1500
            # hdpm._dbwallet.update({DbWallet.key_path: hdpm.key_path})
1501
            # hdpm.session.commit()
1502

1503
        return hdpm
2✔
1504

1505
    def __enter__(self):
2✔
1506
        return self
2✔
1507

1508
    def __init__(self, wallet, db_uri=None, db_cache_uri=None, session=None, main_key_object=None, db_password=None):
2✔
1509
        """
1510
        Open a wallet with given ID or name
1511

1512
        :param wallet: Wallet name or ID
1513
        :type wallet: int, str
1514
        :param db_uri: URI of the database
1515
        :type db_uri: str
1516
        :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)
1517
        :type db_cache_uri: str
1518
        :param session: Sqlalchemy session
1519
        :type session: sqlalchemy.orm.session.Session
1520
        :param main_key_object: Pass main key object to save time
1521
        :type main_key_object: HDKey
1522
        """
1523

1524
        self._session = None
2✔
1525
        self._engine = None
2✔
1526
        if session:
2✔
1527
            self._session = session
×
1528
        self._db_password = db_password
2✔
1529
        self.db_uri = db_uri
2✔
1530
        self.db_cache_uri = db_cache_uri
2✔
1531
        if isinstance(wallet, int) or wallet.isdigit():
2✔
1532
            db_wlt = self.session.query(DbWallet).filter_by(id=wallet).scalar()
2✔
1533
        else:
1534
            db_wlt = self.session.query(DbWallet).filter_by(name=wallet).scalar()
2✔
1535
        if db_wlt:
2✔
1536
            self._dbwallet = db_wlt
2✔
1537
            self.wallet_id = db_wlt.id
2✔
1538
            self._name = db_wlt.name
2✔
1539
            self._owner = db_wlt.owner
2✔
1540
            self.network = Network(db_wlt.network_name)
2✔
1541
            self.purpose = db_wlt.purpose
2✔
1542
            self.scheme = db_wlt.scheme
2✔
1543
            self._balance = None
2✔
1544
            self._balances = []
2✔
1545
            self.main_key_id = db_wlt.main_key_id
2✔
1546
            self.main_key = None
2✔
1547
            self._default_account_id = db_wlt.default_account_id
2✔
1548
            self.multisig_n_required = db_wlt.multisig_n_required
2✔
1549
            co_sign_wallets = self.session.query(DbWallet).\
2✔
1550
                filter(DbWallet.parent_id == self.wallet_id).order_by(DbWallet.name).all()
1551
            self.cosigner = [Wallet(w.id, db_uri=db_uri, db_cache_uri=db_cache_uri) for w in co_sign_wallets]
2✔
1552
            self.sort_keys = db_wlt.sort_keys
2✔
1553
            if db_wlt.main_key_id:
2✔
1554
                self.main_key = WalletKey(self.main_key_id, session=self.session, hdkey_object=main_key_object)
2✔
1555
            if self._default_account_id is None:
2✔
1556
                self._default_account_id = 0
2✔
1557
                if self.main_key:
2✔
1558
                    self._default_account_id = self.main_key.account_id
2✔
1559
            _logger.info("Opening wallet '%s'" % self.name)
2✔
1560
            self._key_objects = {
2✔
1561
                self.main_key_id: self.main_key
1562
            }
1563
            self.providers = None
2✔
1564
            self.witness_type = db_wlt.witness_type
2✔
1565
            self.encoding = db_wlt.encoding
2✔
1566
            self.multisig = db_wlt.multisig
2✔
1567
            self.cosigner_id = db_wlt.cosigner_id
2✔
1568
            self.script_type = script_type_default(self.witness_type, self.multisig, locking_script=True)
2✔
1569
            self.key_path = [] if not db_wlt.key_path else db_wlt.key_path.split('/')
2✔
1570
            self.depth_public_master = 0
2✔
1571
            self.parent_id = db_wlt.parent_id
2✔
1572
            if self.main_key and self.main_key.depth > 0:
2✔
1573
                self.depth_public_master = self.main_key.depth
2✔
1574
                self.key_depth = self.depth_public_master + len(self.key_path) - 1
2✔
1575
            else:
1576
                hardened_keys = [x for x in self.key_path if x[-1:] == "'"]
2✔
1577
                if hardened_keys:
2✔
1578
                    self.depth_public_master = self.key_path.index(hardened_keys[-1])
2✔
1579
                self.key_depth = len(self.key_path) - 1
2✔
1580
            self.last_updated = None
2✔
1581
            self.anti_fee_sniping = db_wlt.anti_fee_sniping
2✔
1582
            self.strict = True
2✔
1583
        else:
1584
            raise WalletError("Wallet '%s' not found, please specify correct wallet ID or name." % wallet)
2✔
1585

1586
    def __exit__(self, exception_type, exception_value, traceback):
2✔
1587
        try:
2✔
1588
            self.session.close()
2✔
1589
            self._engine.dispose()
2✔
1590
        except Exception:
×
1591
            pass
×
1592

1593
    def __del__(self):
2✔
1594
        try:
2✔
1595
            self.session.close()
2✔
1596
            self._engine.dispose()
2✔
1597
        except Exception:
×
1598
            pass
×
1599

1600
    def __repr__(self):
1601
        db_uri = '' if not self.db_uri else self.db_uri.split('?')[0]
1602
        if DEFAULT_DATABASE in db_uri:
1603
            return "<Wallet(name=\"%s\")>" % self.name
1604
        return "<Wallet(name=\"%s\", db_uri=\"%s\")>" % \
1605
               (self.name, db_uri)
1606

1607
    def __str__(self):
2✔
1608
        return self.name
×
1609

1610
    def _get_account_defaults(self, network=None, account_id=None, key_id=None):
2✔
1611
        """
1612
        Check parameter values for network and account ID, return defaults if no network or account ID is specified.
1613
        If a network is specified but no account ID this method returns the first account ID it finds.
1614

1615
        :param network: Network code, leave empty for default
1616
        :type network: str
1617
        :param account_id: Account ID, leave emtpy for default
1618
        :type account_id: int
1619
        :param key_id: Key ID to just update 1 key
1620
        :type key_id: int
1621

1622
        :return: network code, account ID and DbKey instance of account ID key
1623
        """
1624

1625
        if key_id:
2✔
1626
            kobj = self.key(key_id)
2✔
1627
            network = kobj.network_name
2✔
1628
            account_id = kobj.account_id
2✔
1629
        if network is None:
2✔
1630
            network = self.network.name
2✔
1631
        if account_id is None and network == self.network.name:
2✔
1632
            account_id = self.default_account_id
2✔
1633
        qr = self.session.query(DbKey).\
2✔
1634
            filter_by(wallet_id=self.wallet_id, purpose=self.purpose, depth=self.depth_public_master,
1635
                      network_name=network)
1636
        if account_id is not None:
2✔
1637
            qr = qr.filter_by(account_id=account_id)
2✔
1638
        acckey = qr.first()
2✔
1639
        if len(qr.all()) > 1 and "account'" in self.key_path:
2✔
1640
            _logger.warning("No account_id specified and more than one account found for this network %s. "
2✔
1641
                            "Using a random account" % network)
1642
        if account_id is None:
2✔
1643
            if acckey:
2✔
1644
                account_id = acckey.account_id
2✔
1645
            else:
1646
                account_id = 0
2✔
1647
        return network, account_id, acckey
2✔
1648

1649
    @property
2✔
1650
    def default_account_id(self):
2✔
1651
        return self._default_account_id
2✔
1652

1653
    @default_account_id.setter
2✔
1654
    def default_account_id(self, value):
2✔
1655
        self._default_account_id = value
2✔
1656
        self._dbwallet = self.session.query(DbWallet).filter(DbWallet.id == self.wallet_id). \
2✔
1657
            update({DbWallet.default_account_id: value})
1658
        self._commit()
2✔
1659

1660
    @property
2✔
1661
    def owner(self):
2✔
1662
        """
1663
        Get wallet Owner
1664

1665
        :return str:
1666
        """
1667

1668
        return self._owner
2✔
1669

1670
    @owner.setter
2✔
1671
    def owner(self, value):
2✔
1672
        """
1673
        Set wallet Owner in database
1674

1675
        :param value: Owner
1676
        :type value: str
1677

1678
        :return str:
1679
        """
1680

1681
        self._owner = value
2✔
1682
        self._dbwallet = self.session.query(DbWallet).filter(DbWallet.id == self.wallet_id).\
2✔
1683
            update({DbWallet.owner: value})
1684
        self._commit()
2✔
1685

1686
    @property
2✔
1687
    def name(self):
2✔
1688
        """
1689
        Get wallet name
1690

1691
        :return str:
1692
        """
1693

1694
        return self._name
2✔
1695

1696
    @name.setter
2✔
1697
    def name(self, value):
2✔
1698
        """
1699
        Set wallet name, update in database
1700

1701
        :param value: Name for this wallet
1702
        :type value: str
1703

1704
        :return str:
1705
        """
1706

1707
        if wallet_exists(value, db_uri=self.db_uri):
2✔
1708
            raise WalletError("Wallet with name '%s' already exists" % value)
2✔
1709
        self._name = value
2✔
1710
        self.session.query(DbWallet).filter(DbWallet.id == self.wallet_id).update({DbWallet.name: value})
2✔
1711
        self._commit()
2✔
1712

1713
    @property
2✔
1714
    def session(self):
2✔
1715
        if not self._session:
2✔
1716
            logger.info("Opening database session %s" % self.db_uri)
2✔
1717
            dbinit = Db(db_uri=self.db_uri, password=self._db_password)
2✔
1718
            self._session = dbinit.session
2✔
1719
            self._engine = dbinit.engine
2✔
1720
        return self._session
2✔
1721

1722
    def default_network_set(self, network):
2✔
1723
        if not isinstance(network, Network):
2✔
1724
            network = Network(network)
2✔
1725
        self.network = network
2✔
1726
        self.session.query(DbWallet).filter(DbWallet.id == self.wallet_id).\
2✔
1727
            update({DbWallet.network_name: network.name})
1728
        self._commit()
2✔
1729

1730
    def import_master_key(self, hdkey, name='Masterkey (imported)'):
2✔
1731
        """
1732
        Import (another) masterkey in this wallet
1733

1734
        :param hdkey: Private key
1735
        :type hdkey: HDKey, str
1736
        :param name: Key name of masterkey
1737
        :type name: str
1738

1739
        :return HDKey: Main key as HDKey object
1740
        """
1741

1742
        network, account_id, acckey = self._get_account_defaults()
2✔
1743
        if not isinstance(hdkey, HDKey):
2✔
1744
            hdkey = HDKey(hdkey)
2✔
1745
        if not isinstance(self.main_key, WalletKey):
2✔
1746
            raise WalletError("Main wallet key is not an WalletKey instance. Type %s" % type(self.main_key))
2✔
1747
        if not hdkey.is_private or hdkey.depth != 0:
2✔
1748
            raise WalletError("Please supply a valid private BIP32 master key with key depth 0")
2✔
1749
        if self.main_key.is_private:
2✔
1750
            raise WalletError("Main key is already a private key, cannot import key")
2✔
1751
        if (self.main_key.depth != 1 and self.main_key.depth != 3 and self.main_key.depth != 4) or \
2✔
1752
                self.main_key.key_type != 'bip32':
1753
            raise WalletError("Current main key is not a valid BIP32 public master key")
2✔
1754
        if not (self.network.name == self.main_key.network.name == hdkey.network.name):
2✔
1755
            raise WalletError("Network of Wallet class, main account key and the imported private key must use "
2✔
1756
                              "the same network")
1757
        if self.main_key.wif != hdkey.public_master().wif():
2✔
1758
            raise WalletError("This key does not correspond to current public master key")
2✔
1759

1760
        hdkey.key_type = 'bip32'
2✔
1761
        # ks = [k for k in WALLET_KEY_STRUCTURES if
1762
        #       k['witness_type'] == self.witness_type and k['multisig'] == self.multisig and k['purpose'] is not None]
1763
        # if len(ks) > 1:
1764
        #     raise WalletError("Please check definitions in WALLET_KEY_STRUCTURES. Multiple options found for "
1765
        #                       "witness_type - multisig combination")
1766
        # self.key_path = ks[0]['key_path']
1767
        self.key_path, _, _ = get_key_structure_data(self.witness_type, self.multisig)
2✔
1768
        self.main_key = WalletKey.from_key(
2✔
1769
            key=hdkey, name=name, session=self.session, wallet_id=self.wallet_id, network=network,
1770
            account_id=account_id, purpose=self.purpose, key_type='bip32', witness_type=self.witness_type)
1771
        self.main_key_id = self.main_key.key_id
2✔
1772
        self._key_objects.update({self.main_key_id: self.main_key})
2✔
1773
        self.session.query(DbWallet).filter(DbWallet.id == self.wallet_id).\
2✔
1774
            update({DbWallet.main_key_id: self.main_key_id})
1775

1776
        for key in self.keys(is_private=False, as_dict=True):
2✔
1777
            kp = key['path'].split("/")
2✔
1778
            if kp and kp[0] == 'M':
2✔
1779
                kp = self.key_path[:self.depth_public_master+1] + kp[1:]
2✔
1780
            self.key_for_path(kp, recreate=True)
2✔
1781
        self._commit()
2✔
1782
        return self.main_key
2✔
1783

1784
    def import_key(self, key, account_id=0, name='', network=None, purpose=84, key_type=None):
2✔
1785
        """
1786
        Add new single key to wallet.
1787

1788
        :param key: Key to import
1789
        :type key: str, bytes, int, HDKey, Address
1790
        :param account_id: Account ID. Default is last used or created account ID.
1791
        :type account_id: int
1792
        :param name: Specify name for key, leave empty for default
1793
        :type name: str
1794
        :param network: Network name, method will try to extract from key if not specified. Raises warning if network could not be detected
1795
        :type network: str
1796
        :param purpose: BIP44 definition used, default is 84 (segwit)
1797
        :type purpose: int
1798
        :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'
1799
        :type key_type: str
1800

1801
        :return WalletKey:
1802
        """
1803

1804
        if self.scheme not in ['bip32', 'single']:
2✔
1805
            raise WalletError("Keys can only be imported to a BIP32 or single type wallet, create a new wallet "
×
1806
                              "instead")
1807
        if isinstance(key, (HDKey, Address)):
2✔
1808
            network = key.network.name
2✔
1809
            hdkey = key
2✔
1810
            if network not in self.network_list():
2✔
1811
                raise WalletError("Network %s not found in this wallet" % network)
×
1812
        else:
1813
            if isinstance(key, str) and len(key.split(" ")) > 1:
2✔
1814
                if network is None:
2✔
1815
                    network = self.network
2✔
1816
                hdkey = HDKey.from_seed(Mnemonic().to_seed(key), network=network)
2✔
1817
            else:
1818
                if network is None:
2✔
1819
                    network = check_network_and_key(key, default_network=self.network.name)
2✔
1820
                if network not in self.network_list():
2✔
1821
                    raise WalletError("Network %s not available in this wallet, please create an account for this "
2✔
1822
                                      "network first." % network)
1823
                hdkey = HDKey(key, network=network, key_type=key_type, witness_type=self.witness_type)
2✔
1824

1825
        if not self.multisig:
2✔
1826
            if self.main_key and self.main_key.depth == self.depth_public_master and \
2✔
1827
                    not isinstance(hdkey, Address) and hdkey.is_private and hdkey.depth == 0 and self.scheme == 'bip32':
1828
                return self.import_master_key(hdkey, name)
2✔
1829

1830
            if key_type is None:
2✔
1831
                hdkey.key_type = 'single'
2✔
1832
                key_type = 'single'
2✔
1833

1834
            ik_path = 'm'
2✔
1835
            if key_type == 'single':
2✔
1836
                # Create path for unrelated import keys
1837
                hdkey.depth = self.key_depth
2✔
1838
                last_import_key = self.session.query(DbKey).filter(DbKey.path.like("import_key_%")).\
2✔
1839
                    order_by(DbKey.path.desc()).first()
1840
                if last_import_key:
2✔
1841
                    ik_path = "import_key_" + str(int(last_import_key.path[-5:]) + 1).zfill(5)
2✔
1842
                else:
1843
                    ik_path = "import_key_00001"
2✔
1844
                if not name:
2✔
1845
                    name = ik_path
2✔
1846

1847
            mk = WalletKey.from_key(
2✔
1848
                key=hdkey, name=name, wallet_id=self.wallet_id, network=network, key_type=key_type,
1849
                account_id=account_id, purpose=purpose, session=self.session, path=ik_path,
1850
                witness_type=self.witness_type)
1851
            self._key_objects.update({mk.key_id: mk})
2✔
1852
            if mk.key_id == self.main_key.key_id:
2✔
1853
                self.main_key = mk
2✔
1854
            return mk
2✔
1855
        else:
1856
            account_key = hdkey.public_master(witness_type=self.witness_type, multisig=True).wif()
2✔
1857
            for w in self.cosigner:
2✔
1858
                if w.main_key.key().wif_public() == account_key:
2✔
1859
                    _logger.debug("Import new private cosigner key in this multisig wallet: %s" % account_key)
2✔
1860
                    return w.import_master_key(hdkey)
2✔
1861
            raise WalletError("Unknown key: Can only import a private key for a known public key in multisig wallets")
×
1862

1863
    def _new_key_multisig(self, public_keys, name, account_id, change, cosigner_id, network, address_index,
2✔
1864
                          witness_type):
1865
        if self.sort_keys:
2✔
1866
            public_keys.sort(key=lambda pubk: pubk.key_public)
2✔
1867
        public_key_list = [pubk.key_public for pubk in public_keys]
2✔
1868
        public_key_ids = [str(x.key_id) for x in public_keys]
2✔
1869

1870
        # todo: pass key object, reuse key objects
1871
        redeemscript = Script(script_types=['multisig'], keys=public_key_list,
2✔
1872
                              sigs_required=self.multisig_n_required).serialize()
1873
        script_type = 'p2sh' if witness_type == 'legacy' else \
2✔
1874
            ('p2sh_p2wsh' if witness_type == 'p2sh-segwit' else 'p2wsh')
1875
        address = Address(redeemscript, script_type=script_type, network=network, witness_type=witness_type)
2✔
1876
        already_found_key = self.session.query(DbKey).filter_by(wallet_id=self.wallet_id,
2✔
1877
                                                                 address=address.address).first()
1878
        if already_found_key:
2✔
1879
            return self.key(already_found_key.id)
2✔
1880
        path = [pubk.path for pubk in public_keys if pubk.wallet.cosigner_id == self.cosigner_id][0]
2✔
1881
        depth = self.cosigner[self.cosigner_id].main_key.depth + len(path.split("/")) - 1
2✔
1882
        if not name:
2✔
1883
            name = "Multisig Key " + '/'.join(public_key_ids)
2✔
1884

1885
        new_key_id = (self.session.query(func.max(DbKey.id)).scalar() or 0) + 1
2✔
1886
        multisig_key = DbKey(id=new_key_id,
2✔
1887
            name=name[:80], wallet_id=self.wallet_id, purpose=self.purpose, account_id=account_id,
1888
            depth=depth, change=change, address_index=address_index, parent_id=0, is_private=False, path=path,
1889
            public=address.hash_bytes, wif='multisig-%s' % address, address=address.address, cosigner_id=cosigner_id,
1890
            key_type='multisig', witness_type=witness_type, network_name=network)
1891
        self.session.add(multisig_key)
2✔
1892
        self._commit()
2✔
1893
        for child_id in public_key_ids:
2✔
1894
            self.session.add(DbKeyMultisigChildren(key_order=public_key_ids.index(child_id), parent_id=multisig_key.id,
2✔
1895
                                                    child_id=int(child_id)))
1896
        self._commit()
2✔
1897
        return self.key(multisig_key.id)
2✔
1898

1899
    def new_key(self, name='', account_id=None, change=0, cosigner_id=None, witness_type=None, network=None):
2✔
1900
        """
1901
        def new_key(self, name='', account_id=None, change=0, cosigner_id=None, witness_type=None, network=None):
1902

1903
        :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
1904
        :type name: str
1905
        :param account_id: Account ID. Default is last used or created account ID.
1906
        :type account_id: int
1907
        :param change: Change (1) or payments (0). Default is 0
1908
        :type change: int
1909
        :param cosigner_id: Cosigner ID for key path
1910
        :type cosigner_id: int
1911
        :param witness_type: Use to create key with different witness_type
1912
        :type witness_type: str
1913
        :param network: Network name. Leave empty for default network
1914
        :type network: str
1915

1916
        :return WalletKey:
1917
        """
1918
        return self.new_keys(name, account_id, change, cosigner_id, witness_type, 1, network)[0]
2✔
1919

1920
    def new_keys(self, name='', account_id=None, change=0, cosigner_id=None, witness_type=None,
2✔
1921
                number_of_keys=1, network=None):
1922
        """
1923
        Create a new HD Key derived from this wallet's masterkey. An account will be created for this wallet
1924
        with index 0 if there is no account defined yet.
1925

1926
        >>> w = Wallet('create_legacy_wallet_test')
1927
        >>> w.new_key('my key') # doctest:+ELLIPSIS
1928
        <WalletKey(key_id=..., name=my key, wif=..., path=m/84'/0'/0'/0/...)>
1929

1930
        :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
1931
        :type name: str
1932
        :param account_id: Account ID. Default is last used or created account ID.
1933
        :type account_id: int
1934
        :param change: Change (1) or payments (0). Default is 0
1935
        :type change: int
1936
        :param cosigner_id: Cosigner ID for key path
1937
        :type cosigner_id: int
1938
        :param witness_type: Use to create key with different witness_type
1939
        :type witness_type: str
1940
        :param number_of_keys: Number of keys to generate. Use positive integer
1941
        :type number_of_keys: int
1942
        :param network: Network name. Leave empty for default network
1943
        :type network: str
1944

1945
        :return list of WalletKey:
1946
        """
1947

1948
        if self.scheme == 'single':
2✔
1949
            return [self.main_key]
2✔
1950
        network, account_id, _ = self._get_account_defaults(network, account_id)
2✔
1951
        if network != self.network.name and "coin_type'" not in self.key_path:
2✔
1952
            raise WalletError("Multiple networks not supported by wallet key structure")
×
1953
        if self.multisig:
2✔
1954
            if not self.multisig_n_required:
2✔
1955
                raise WalletError("Multisig_n_required not set, cannot create new key")
×
1956
            if cosigner_id is None:
2✔
1957
                if self.cosigner_id is None:
2✔
1958
                    raise WalletError("Missing Cosigner ID value, cannot create new key")
×
1959
                cosigner_id = self.cosigner_id
2✔
1960
        witness_type = self.witness_type if not witness_type else witness_type
2✔
1961
        purpose = self.purpose
2✔
1962
        if witness_type != self.witness_type:
2✔
1963
            _, purpose, encoding = get_key_structure_data(witness_type, self.multisig)
2✔
1964

1965
        address_index = 0
2✔
1966
        if not((self.multisig and cosigner_id is not None and
2✔
1967
                (len(self.cosigner) > cosigner_id and self.cosigner[cosigner_id].key_path == 'm' or
1968
                 self.cosigner[cosigner_id].key_path == ['m']))):
1969
            prevkey = self.session.query(DbKey).\
2✔
1970
                filter_by(wallet_id=self.wallet_id, purpose=purpose, network_name=network, account_id=account_id,
1971
                          witness_type=witness_type, change=change, cosigner_id=cosigner_id, depth=self.key_depth).\
1972
                order_by(DbKey.address_index.desc()).first()
1973
            if prevkey:
2✔
1974
                address_index = prevkey.address_index + 1
2✔
1975

1976
        return self.keys_for_path([], name=name, account_id=account_id, witness_type=witness_type, network=network,
2✔
1977
                                 cosigner_id=cosigner_id, address_index=address_index, number_of_keys=number_of_keys,
1978
                                 change=change)
1979

1980
    def new_key_change(self, name='', account_id=None, witness_type=None, network=None):
2✔
1981
        """
1982
        Create new key to receive change for a transaction. Calls :func:`new_key` method with change=1.
1983

1984
        :param name: Key name. Default name is 'Change #' with an address index
1985
        :type name: str
1986
        :param account_id: Account ID. Default is last used or created account ID.
1987
        :type account_id: int
1988
        :param witness_type: Use to create key with different witness_type
1989
        :type witness_type: str
1990
        :param network: Network name. Leave empty for default network
1991
        :type network: str
1992

1993
        :return WalletKey:
1994
        """
1995

1996
        return self.new_key(name=name, account_id=account_id, witness_type=witness_type, network=network, change=1)
2✔
1997

1998
    def scan_key(self, key):
2✔
1999
        """
2000
        Scan for new transactions for specified wallet key and update wallet transactions
2001

2002
        :param key: The wallet key as object or index
2003
        :type key: WalletKey, int
2004

2005
        :return bool: New transactions found?
2006

2007
        """
2008
        if isinstance(key, int):
2✔
2009
            key = self.key(key)
×
2010
        txs_found = False
2✔
2011
        should_be_finished_count = 0
2✔
2012
        while True:
1✔
2013
            n_new = self.transactions_update(key_id=key.key_id)
2✔
2014
            if n_new and n_new < MAX_TRANSACTIONS:
2✔
2015
                if should_be_finished_count:
2✔
2016
                    _logger.info("Possible recursive loop detected in scan_key(%d): retry %d/5" %
×
2017
                                 (key.key_id, should_be_finished_count))
2018
                should_be_finished_count += 1
2✔
2019
            logger.info("Scanned key %d, %s Found %d new transactions" % (key.key_id, key.address, n_new))
2✔
2020
            if not n_new or should_be_finished_count > 5:
2✔
2021
                break
1✔
2022
            txs_found = True
2✔
2023
        return txs_found
2✔
2024

2025
    def scan(self, scan_gap_limit=5, account_id=None, change=None, rescan_used=False, network=None, keys_ignore=None):
2✔
2026
        """
2027
        Generate new addresses/keys and scan for new transactions using the Service providers. Updates all UTXO's and balances.
2028

2029
        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.
2030

2031
        Use the faster :func:`utxos_update` method if you are only interested in unspent outputs.
2032
        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.
2033

2034
        :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.
2035
        :type scan_gap_limit: int
2036
        :param account_id: Account ID. Default is last used or created account ID.
2037
        :type account_id: int
2038
        :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
2039
        :type change: bool
2040
        :param rescan_used: Rescan already used addressed. Default is False, so funds send to old addresses will be ignored by default.
2041
        :type rescan_used: bool
2042
        :param network: Network name. Leave empty for default network
2043
        :type network: str
2044
        :param keys_ignore: Id's of keys to ignore
2045
        :type keys_ignore: list of int
2046

2047
        :return:
2048
        """
2049

2050
        network, account_id, _ = self._get_account_defaults(network, account_id)
2✔
2051
        if self.scheme != 'bip32' and self.scheme != 'multisig' and scan_gap_limit < 2:
2✔
2052
            raise WalletError("The wallet scan() method is only available for BIP32 wallets")
×
2053
        if keys_ignore is None:
2✔
2054
            keys_ignore = []
2✔
2055

2056
        # Rescan used addresses
2057
        if rescan_used:
2✔
2058
            for key in self.keys_addresses(account_id=account_id, change=change, network=network, used=True):
×
2059
                self.scan_key(key.id)
×
2060

2061
        # Update already known transactions with known block height
2062
        self.transactions_update_confirmations()
2✔
2063

2064
        # Check unconfirmed transactions
2065
        db_txs = self.session.query(DbTransaction). \
2✔
2066
            filter(DbTransaction.wallet_id == self.wallet_id,
2067
                   DbTransaction.network_name == network, DbTransaction.confirmations == 0).all()
2068
        for db_tx in db_txs:
2✔
2069
            self.transactions_update_by_txids([db_tx.txid])
×
2070

2071
        # Scan each key address, stop when no new transactions are found after set scan gap limit
2072
        if change is None:
2✔
2073
            change_range = [0, 1]
2✔
2074
        else:
2075
            change_range = [change]
×
2076
        counter = 0
2✔
2077
        for chg in change_range:
2✔
2078
            while True:
1✔
2079
                if self.scheme == 'single':
2✔
2080
                    keys_to_scan = [self.key(k.id) for k in self.keys_addresses()[counter:counter+scan_gap_limit]]
2✔
2081
                    counter += scan_gap_limit
2✔
2082
                else:
2083
                    keys_to_scan = []
2✔
2084
                    for witness_type in self.witness_types(network=network):
2✔
2085
                        keys_to_scan += self.get_keys(account_id, witness_type, network,
2✔
2086
                                                     number_of_keys=scan_gap_limit, change=chg)
2087

2088
                n_highest_updated = 0
2✔
2089
                for key in keys_to_scan:
2✔
2090
                    if key.key_id in keys_ignore:
2✔
2091
                        continue
×
2092
                    keys_ignore.append(key.key_id)
2✔
2093
                    n_high_new = 0
2✔
2094
                    if self.scan_key(key):
2✔
2095
                        if not key.address_index:
2✔
2096
                            key.address_index = 0
2✔
2097
                        n_high_new = key.address_index + 1
2✔
2098
                    if n_high_new > n_highest_updated:
2✔
2099
                        n_highest_updated = n_high_new
2✔
2100
                if not n_highest_updated:
2✔
2101
                    break
2✔
2102

2103
    def _get_key(self, account_id=None, witness_type=None, network=None, cosigner_id=None, number_of_keys=1, change=0,
2✔
2104
                 as_list=False):
2105
        network, account_id, _ = self._get_account_defaults(network, account_id)
2✔
2106
        if cosigner_id is None:
2✔
2107
            cosigner_id = self.cosigner_id
2✔
2108
        elif cosigner_id > len(self.cosigner):
2✔
2109
            raise WalletError("Cosigner ID (%d) can not be greater then number of cosigners for this wallet (%d)" %
2✔
2110
                              (cosigner_id, len(self.cosigner)))
2111

2112
        witness_type = witness_type if witness_type else self.witness_type
2✔
2113
        last_used_qr = self.session.query(DbKey.id).\
2✔
2114
            filter_by(wallet_id=self.wallet_id, account_id=account_id, network_name=network, cosigner_id=cosigner_id,
2115
                      used=True, change=change, depth=self.key_depth, witness_type=witness_type).\
2116
            order_by(DbKey.id.desc()).first()
2117
        last_used_key_id = 0
2✔
2118
        if last_used_qr:
2✔
2119
            last_used_key_id = last_used_qr.id
2✔
2120
        dbkey = (self.session.query(DbKey.id).
2✔
2121
            filter_by(wallet_id=self.wallet_id, account_id=account_id, network_name=network, cosigner_id=cosigner_id,
2122
                      used=False, change=change, depth=self.key_depth, witness_type=witness_type).
2123
            filter(DbKey.id > last_used_key_id).
2124
            order_by(DbKey.id.asc()).all())
2125
        if self.scheme == 'single' and len(dbkey):
2✔
2126
            number_of_keys = len(dbkey) if number_of_keys > len(dbkey) else number_of_keys
×
2127
        key_list = [self.key(key_id[0]) for key_id in dbkey]
2✔
2128

2129
        if len(key_list) > number_of_keys:
2✔
2130
            key_list = key_list[:number_of_keys]
2✔
2131
        else:
2132
            new_keys = self.new_keys(account_id=account_id, change=change, cosigner_id=cosigner_id,
2✔
2133
                                     witness_type=witness_type, network=network,
2134
                                     number_of_keys=number_of_keys - len(key_list))
2135
            key_list += new_keys
2✔
2136

2137
        if as_list:
2✔
2138
            return key_list
2✔
2139
        else:
2140
            return key_list[0]
2✔
2141

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

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

2151
        >>> w = Wallet('create_legacy_wallet_test')
2152
        >>> w.get_key() # doctest:+ELLIPSIS
2153
        <WalletKey(key_id=..., name=..., wif=..., path=m/84'/0'/0'/0/...)>
2154

2155
        :param account_id: Account ID. Default is last used or created account ID.
2156
        :type account_id: int
2157
        :param witness_type: Use to create key with specific witness_type
2158
        :type witness_type: str
2159
        :param network: Network name. Leave empty for default network
2160
        :type network: str
2161
        :param cosigner_id: Cosigner ID for key path
2162
        :type cosigner_id: int
2163
        :param change: Payment (0) or change key (1). Default is 0
2164
        :type change: int
2165

2166
        :return WalletKey:
2167
        """
2168
        return self._get_key(account_id, witness_type, network, cosigner_id, change=change, as_list=False)
2✔
2169

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

2175
        Use the get_key() method to get a single key.
2176

2177
        :param account_id: Account ID. Default is last used or created account ID.
2178
        :type account_id: int
2179
        :param witness_type: Use to create key with specific witness_type
2180
        :type witness_type: str
2181
        :param network: Network name. Leave empty for default network
2182
        :type network: str
2183
        :param cosigner_id: Cosigner ID for key path
2184
        :type cosigner_id: int
2185
        :param number_of_keys: Number of keys to return. Default is 1
2186
        :type number_of_keys: int
2187
        :param change: Payment (0) or change key (1). Default is 0
2188
        :type change: int
2189

2190
        :return list of WalletKey:
2191
        """
2192
        if self.scheme == 'single':
2✔
2193
            raise WalletError("Single wallet has only one (master)key. Use get_key() or main_key() method")
2✔
2194
        return self._get_key(account_id, witness_type, network, cosigner_id, number_of_keys, change, as_list=True)
2✔
2195

2196
    def get_key_change(self, account_id=None, witness_type=None, network=None):
2✔
2197
        """
2198
        Get a unused change key or create a new one if there are no unused keys.
2199
        Wrapper for the :func:`get_key` method
2200

2201
        :param account_id: Account ID. Default is last used or created account ID.
2202
        :type account_id: int
2203
        :param witness_type: Use to create key with specific witness_type
2204
        :type witness_type: str
2205
        :param network: Network name. Leave empty for default network
2206
        :type network: str
2207

2208
        :return WalletKey:
2209
        """
2210

2211
        return self._get_key(account_id, witness_type, network, change=1, as_list=False)
2✔
2212

2213
    def get_keys_change(self, account_id=None, witness_type=None, network=None, number_of_keys=1):
2✔
2214
        """
2215
        Get a unused change key or create a new one if there are no unused keys.
2216
        Wrapper for the :func:`get_key` method
2217

2218
        :param account_id: Account ID. Default is last used or created account ID.
2219
        :type account_id: int
2220
        :param witness_type: Use to create key with specific witness_type
2221
        :type witness_type: str
2222
        :param network: Network name. Leave empty for default network
2223
        :type network: str
2224
        :param number_of_keys: Number of keys to return. Default is 1
2225
        :type number_of_keys: int
2226

2227
        :return list of WalletKey:
2228
        """
2229

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

2232
    def new_account(self, name='', account_id=None, witness_type=None, network=None):
2✔
2233
        """
2234
        Create a new account with a child key for payments and 1 for change.
2235

2236
        An account key can only be created if wallet contains a masterkey.
2237

2238
        :param name: Account Name. If not specified "Account #" with the account_id will be used as name
2239
        :type name: str
2240
        :param account_id: Account ID. Default is last accounts ID + 1
2241
        :type account_id: int
2242
        :param witness_type: Use to create key with specific witness_type
2243
        :type witness_type: str
2244
        :param network: Network name. Leave empty for default network
2245
        :type network: str
2246

2247
        :return WalletKey:
2248
        """
2249

2250
        if self.scheme != 'bip32':
2✔
2251
            raise WalletError("We can only create new accounts for a wallet with a BIP32 key scheme")
×
2252
        if self.main_key and (self.main_key.depth != 0 or self.main_key.is_private is False):
2✔
2253
            raise WalletError("A master private key of depth 0 is needed to create new accounts (depth: %d)" %
×
2254
                              self.main_key.depth)
2255
        if "account'" not in self.key_path:
2✔
2256
            raise WalletError("Accounts are not supported for this wallet. Account level not found in key path %s" %
×
2257
                              self.key_path)
2258
        if network is None:
2✔
2259
            network = self.network.name
2✔
2260
        elif network != self.network.name and "coin_type'" not in self.key_path:
2✔
2261
            raise WalletError("Multiple networks not supported by wallet key structure")
×
2262

2263
        duplicate_cointypes = [Network(x).name for x in self.network_list() if Network(x).name != network and
2✔
2264
                               Network(x).bip44_cointype == Network(network).bip44_cointype]
2265
        if duplicate_cointypes:
2✔
2266
            raise WalletError("Can not create new account for network %s with same BIP44 cointype: %s" %
2✔
2267
                              (network, duplicate_cointypes))
2268

2269
        witness_type = witness_type if witness_type else self.witness_type
2✔
2270
        # Determine account_id and name
2271
        if account_id is None:
2✔
2272
            account_id = 0
2✔
2273
            qr = self.session.query(DbKey). \
2✔
2274
                filter_by(wallet_id=self.wallet_id, witness_type=witness_type, network_name=network). \
2275
                order_by(DbKey.account_id.desc()).first()
2276
            if qr:
2✔
2277
                account_id = qr.account_id + 1
2✔
2278
        if self.keys(account_id=account_id, depth=self.depth_public_master, witness_type=witness_type,
2✔
2279
                     network=network):
2280
            raise WalletError("Account with ID %d already exists for this wallet" % account_id)
2✔
2281

2282
        acckey = self.key_for_path([], level_offset=self.depth_public_master-self.key_depth, account_id=account_id,
2✔
2283
                                   name=name, witness_type=witness_type, network=network)
2284
        self.key_for_path([], witness_type=witness_type, network=network, account_id=account_id, change=0,
2✔
2285
                          address_index=0)
2286
        self.key_for_path([], witness_type=witness_type, network=network, account_id=account_id, change=1,
2✔
2287
                          address_index=0)
2288
        return acckey
2✔
2289

2290
    def path_expand(self, path, level_offset=None, account_id=None, cosigner_id=0, address_index=None, change=0,
2✔
2291
                    network=DEFAULT_NETWORK):
2292
        """
2293
        Create key path. Specify part of key path to expand to key path used in this wallet.
2294

2295
        >>> w = Wallet('create_legacy_wallet_test')
2296
        >>> w.path_expand([0,1200])
2297
        ['m', "84'", "0'", "0'", '0', '1200']
2298

2299
        >>> w = Wallet('create_legacy_multisig_wallet_test')
2300
        >>> w.path_expand([0,2], cosigner_id=1)
2301
        ['m', "48'", "0'", "0'", "2'", '0', '2']
2302

2303
        :param path: Part of path, for example [0, 2] for change=0 and address_index=2
2304
        :type path: list, str
2305
        :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'
2306
        :type level_offset: int
2307
        :param account_id: Account ID
2308
        :type account_id: int
2309
        :param cosigner_id: ID of cosigner
2310
        :type cosigner_id: int
2311
        :param address_index: Index of key, normally provided to 'path' argument
2312
        :type address_index: int
2313
        :param change: Change key = 1 or normal = 0, normally provided to 'path' argument
2314
        :type change: int
2315
        :param network: Network name. Leave empty for default network
2316
        :type network: str
2317

2318
        :return list:
2319
        """
2320
        network, account_id, _ = self._get_account_defaults(network, account_id)
2✔
2321
        return path_expand(path, self.key_path, level_offset, account_id=account_id, cosigner_id=cosigner_id,
2✔
2322
                           address_index=address_index, change=change, purpose=self.purpose,
2323
                           witness_type=self.witness_type, network=network)
2324

2325
    def key_for_path(self, path, level_offset=None, name=None, account_id=None, cosigner_id=None,
2✔
2326
                      address_index=0, change=0, witness_type=None, network=None, recreate=False):
2327
        """
2328
        Wrapper for the keys_for_path method. Returns a single wallet key.
2329

2330
        :param path: Part of key path, i.e. [0, 0] for [change=0, address_index=0]
2331
        :type path: list, str
2332
        :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'
2333
        :type level_offset: int
2334
        :param name: Specify key name for latest/highest key in structure
2335
        :type name: str
2336
        :param account_id: Account ID
2337
        :type account_id: int
2338
        :param cosigner_id: ID of cosigner
2339
        :type cosigner_id: int
2340
        :param address_index: Index of key, normally provided to 'path' argument
2341
        :type address_index: int
2342
        :param change: Change key = 1 or normal = 0, normally provided to 'path' argument
2343
        :type change: int
2344
        :param witness_type: Use to create key with different witness_type
2345
        :type witness_type: str
2346
        :param network: Network name. Leave empty for default network
2347
        :type network: str
2348
        :param recreate: Recreate key, even if already found in wallet. Can be used to update public key with private key info
2349
        :type recreate: bool
2350

2351
        :return WalletKey:
2352
        """
2353
        return self.keys_for_path(path, level_offset, name, account_id, cosigner_id, address_index, change,
2✔
2354
                                  witness_type, network, recreate, 1)[0]
2355

2356
    def keys_for_path(self, path, level_offset=None, name=None, account_id=None, cosigner_id=None,
2✔
2357
                      address_index=0, change=0, witness_type=None, network=None, recreate=False,
2358
                      number_of_keys=1):
2359
        """
2360
        Return key for specified path. Derive all wallet keys in path if they not already exists
2361

2362
        >>> w = wallet_create_or_open('key_for_path_example')
2363
        >>> key = w.key_for_path([0, 0])
2364
        >>> key.path
2365
        "m/84'/0'/0'/0/0"
2366

2367
        >>> w.key_for_path([], level_offset=-2).path
2368
        "m/84'/0'/0'"
2369

2370
        >>> w.key_for_path([], w.depth_public_master + 1).path
2371
        "m/84'/0'/0'"
2372

2373
        Arguments provided in 'path' take precedence over other arguments. The address_index argument is ignored:
2374
        >>> key = w.key_for_path([0, 10], address_index=1000)
2375
        >>> key.path
2376
        "m/84'/0'/0'/0/10"
2377
        >>> key.address_index
2378
        10
2379

2380
        :param path: Part of key path, i.e. [0, 0] for [change=0, address_index=0]
2381
        :type path: list, str
2382
        :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'
2383
        :type level_offset: int
2384
        :param name: Specify key name for latest/highest key in structure
2385
        :type name: str
2386
        :param account_id: Account ID
2387
        :type account_id: int
2388
        :param cosigner_id: ID of cosigner
2389
        :type cosigner_id: int
2390
        :param address_index: Index of key, normally provided to 'path' argument
2391
        :type address_index: int
2392
        :param change: Change key = 1 or normal = 0, normally provided to 'path' argument
2393
        :type change: int
2394
        :param witness_type: Use to create key with different witness_type
2395
        :type witness_type: str
2396
        :param network: Network name. Leave empty for default network
2397
        :type network: str
2398
        :param recreate: Recreate key, even if already found in wallet. Can be used to update public key with private key info
2399
        :type recreate: bool
2400
        :param number_of_keys: Number of keys to create, use to create keys in bulk fast
2401
        :type number_of_keys: int
2402

2403
        :return list of WalletKey:
2404
        """
2405

2406
        if number_of_keys == 0:
2✔
2407
            return []
2✔
2408
        network, account_id, _ = self._get_account_defaults(network, account_id)
2✔
2409
        cosigner_id = cosigner_id if cosigner_id is not None else self.cosigner_id
2✔
2410
        level_offset_key = level_offset
2✔
2411
        if level_offset and self.main_key and level_offset > 0:
2✔
2412
            level_offset_key = level_offset - self.main_key.depth
×
2413
        witness_type = witness_type if witness_type else self.witness_type
2✔
2414
        if ((not self.main_key or not self.main_key.is_private or self.main_key.depth != 0) and
2✔
2415
                self.witness_type != witness_type) and not self.multisig:
2416
            raise WalletError("This wallet has no private key, cannot use multiple witness types")
2✔
2417
        key_path = self.key_path
2✔
2418
        purpose = self.purpose
2✔
2419
        encoding = self.encoding
2✔
2420
        if witness_type != self.witness_type:
2✔
2421
            _, purpose, encoding = get_key_structure_data(witness_type, self.multisig)
2✔
2422
        if self.multisig and cosigner_id is not None and len(self.cosigner) > cosigner_id:
2✔
2423
            key_path = self.cosigner[cosigner_id].key_path
2✔
2424
        fullpath = path_expand(path, key_path, level_offset_key, account_id=account_id, cosigner_id=cosigner_id,
2✔
2425
                               purpose=purpose, address_index=address_index, change=change,
2426
                               witness_type=witness_type, network=network)
2427

2428
        if self.multisig and self.cosigner:
2✔
2429
            public_keys = []
2✔
2430
            for wlt in self.cosigner:
2✔
2431
                if wlt.scheme == 'single':
2✔
2432
                    wk = [wlt.main_key]
2✔
2433
                else:
2434
                    wk = wlt.keys_for_path(path, level_offset=level_offset, account_id=account_id, name=name,
2✔
2435
                                          cosigner_id=cosigner_id, network=network, recreate=recreate,
2436
                                          witness_type=witness_type, number_of_keys=number_of_keys, change=change,
2437
                                          address_index=address_index)
2438
                public_keys.append(wk)
2✔
2439
            keys_to_add = [public_keys]
2✔
2440
            if type(public_keys[0]) is list:
2✔
2441
                keys_to_add = list(zip(*public_keys))
2✔
2442
            new_ms_keys = []
2✔
2443
            for ms_key_cosigners in keys_to_add:
2✔
2444
                new_ms_keys.append(self._new_key_multisig(list(ms_key_cosigners), name, account_id, change, cosigner_id,
2✔
2445
                                                      network, address_index, witness_type))
2446
            return new_ms_keys if new_ms_keys else None
2✔
2447

2448
        # Check for closest ancestor in wallet
2449
        wpath = fullpath
2✔
2450
        if self.main_key.depth and fullpath and fullpath[0] != 'M':
2✔
2451
            wpath = ["M"] + fullpath[self.main_key.depth + 1:]
×
2452
        dbkey = None
2✔
2453
        while wpath and not dbkey:
2✔
2454
            qr = self.session.query(DbKey).filter_by(path=normalize_path('/'.join(wpath)), wallet_id=self.wallet_id)
2✔
2455
            if recreate:
2✔
2456
                qr = qr.filter_by(is_private=True)
2✔
2457
            dbkey = qr.first()
2✔
2458
            wpath = wpath[:-1]
2✔
2459
        if not dbkey:
2✔
2460
            _logger.warning("No master or public master key found in this wallet")
×
2461
            return None
×
2462
        else:
2463
            topkey = self.key(dbkey.id)
2✔
2464

2465
        if topkey.network != network and topkey.path.split('/') == fullpath:
2✔
2466
            raise WalletError("Cannot create new keys for network %s, no private masterkey found" % network)
2✔
2467

2468
        # Key already found in db, return key
2469
        if dbkey and dbkey.path == normalize_path('/'.join(fullpath)) and not recreate and number_of_keys == 1:
2✔
2470
            return [topkey]
2✔
2471
        else:
2472
            if dbkey and dbkey.path == normalize_path('/'.join(fullpath)) and not recreate and number_of_keys > 1:
2✔
2473
                new_keys = [topkey]
2✔
2474
            else:
2475
                # Create 1 or more keys add them to wallet
2476
                new_keys = []
2✔
2477

2478
            nkey = None
2✔
2479
            parent_id = topkey.key_id
2✔
2480
            ck = topkey.key()
2✔
2481
            ck.witness_type = witness_type
2✔
2482
            ck.encoding = encoding
2✔
2483
            newpath = topkey.path
2✔
2484
            n_items = len(str(dbkey.path).split('/'))
2✔
2485
            for lvl in fullpath[n_items:]:
2✔
2486
                ck = ck.key_for_path(lvl, network=network)
2✔
2487
                newpath += '/' + lvl
2✔
2488
                if not account_id:
2✔
2489
                    account_id = 0 if ("account'" not in self.key_path or
2✔
2490
                                       self.key_path.index("account'") >= len(fullpath)) \
2491
                        else int(fullpath[self.key_path.index("account'")][:-1])
2492
                change_pos = [self.key_path.index(chg) for chg in ["change", "change'"] if chg in self.key_path]
2✔
2493
                change = None if not change_pos or change_pos[0] >= len(fullpath) else (
2✔
2494
                    int(fullpath[change_pos[0]].strip("'")))
2495
                if name and len(fullpath) == len(newpath.split('/')):
2✔
2496
                    key_name = name
2✔
2497
                else:
2498
                    key_name = "%s %s" % (self.key_path[len(newpath.split('/'))-1], lvl)
2✔
2499
                    key_name = key_name.replace("'", "").replace("_", " ")
2✔
2500
                nkey = WalletKey.from_key(key=ck, name=key_name, wallet_id=self.wallet_id, account_id=account_id,
2✔
2501
                                        change=change, purpose=purpose, path=newpath, parent_id=parent_id,
2502
                                        encoding=encoding, witness_type=witness_type,
2503
                                        cosigner_id=cosigner_id, network=network, session=self.session)
2504
                self._key_objects.update({nkey.key_id: nkey})
2✔
2505
                parent_id = nkey.key_id
2✔
2506
            if nkey:
2✔
2507
                new_keys.append(nkey)
2✔
2508
            if len(new_keys) < number_of_keys:
2✔
2509
                parent_id = new_keys[0].parent_id
2✔
2510
                if parent_id not in self._key_objects:
2✔
2511
                    self.key(parent_id)
×
2512
                topkey = self._key_objects[new_keys[0].parent_id]
2✔
2513
                parent_key = topkey.key()
2✔
2514
                new_key_id = self.session.query(DbKey.id).order_by(DbKey.id.desc()).first()[0] + 1
2✔
2515
                hardened_child = False
2✔
2516
                if fullpath[-1].endswith("'"):
2✔
2517
                    hardened_child = True
×
2518
                keys_to_add = [str(k_id) for k_id in range(int(fullpath[-1].strip("'")) + len(new_keys),
2✔
2519
                                                           int(fullpath[-1].strip("'")) + number_of_keys)]
2520

2521
                for key_idx in keys_to_add:
2✔
2522
                    new_key_id += 1
2✔
2523
                    if hardened_child:
2✔
2524
                        key_idx = "%s'" % key_idx
×
2525
                    ck = parent_key.key_for_path(key_idx, network=network)
2✔
2526
                    key_name = 'address index %s' % key_idx.strip("'")
2✔
2527
                    newpath = '/'.join(newpath.split('/')[:-1] + [key_idx])
2✔
2528
                    new_keys.append(WalletKey.from_key(
2✔
2529
                        key=ck, name=key_name, wallet_id=self.wallet_id, account_id=account_id,
2530
                        change=change, purpose=purpose, path=newpath, parent_id=parent_id,
2531
                        encoding=encoding, witness_type=witness_type, new_key_id=new_key_id,
2532
                        cosigner_id=cosigner_id, network=network, session=self.session))
2533
                self.session.commit()
2✔
2534

2535
        return new_keys
2✔
2536

2537
    def last_address_index(self, account_id=None, cosigner_id=0, change=0, network=None):
2✔
2538
        """
2539
        Get last used address_index for this wallet
2540

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

2550
        :return int:
2551
        """
2552
        network, account_id, _ = self._get_account_defaults(network, account_id)
2✔
2553
        last_address_index = 0
2✔
2554
        if not((self.multisig and cosigner_id is not None and
2✔
2555
                (len(self.cosigner) > cosigner_id and self.cosigner[cosigner_id].key_path == 'm' or
2556
                 self.cosigner[cosigner_id].key_path == ['m']))):
2557
            prevkey = self.session.query(DbKey).\
2✔
2558
                filter_by(wallet_id=self.wallet_id, purpose=self.purpose, network_name=network,
2559
                          account_id=account_id, witness_type=self.witness_type, change=change,
2560
                          cosigner_id=cosigner_id, depth=self.key_depth).\
2561
                order_by(DbKey.address_index.desc()).first()
2562
            if prevkey:
2✔
2563
                last_address_index = prevkey.address_index
2✔
2564
        return last_address_index
2✔
2565

2566
    def address_index(self, address_index, account_id=None, cosigner_id=None, change=0, network=None):
2✔
2567
        """
2568
        Get key with specified address_index from wallet. Always returns a key with the specified path, even if it is
2569
        not created yet in wallet. Wrapper for the :func:`key_for_path` method.
2570

2571
        To get an unused key / address use the :func:`get_key` method and to create a new key use the :func:`new_key`
2572
        method.
2573

2574
        :param address_index: address_index of specific key
2575
        :type address_index: int
2576
        :param account_id: Account ID
2577
        :type account_id: int
2578
        :param cosigner_id: ID of cosigner
2579
        :type cosigner_id: int
2580
        :param change: Change key = 1 or normal = 0, normally provided to 'path' argument
2581
        :type change: int
2582
        :param network: Network name. Leave empty for default network
2583
        :type network: str
2584

2585
        :return WalletKey:
2586
        """
2587
        network, account_id, _ = self._get_account_defaults(network, account_id)
2✔
2588
        if address_index > self.last_address_index(account_id, cosigner_id, change, network):
2✔
2589
            raise WalletError("Key with address_index %d not found in wallet. Please create key first" % address_index)
2✔
2590
        if account_id not in self.accounts():
2✔
2591
            raise WalletError("Account %d not found in wallet. Please create account first" % account_id)
2✔
2592
        return self.key_for_path([], address_index=address_index, account_id=account_id, cosigner_id=cosigner_id,
2✔
2593
                                 change=change, network=network)
2594

2595
    def keys(self, account_id=None, name=None, key_id=None, change=None, depth=None, used=None, is_private=None,
2✔
2596
             has_balance=None, is_active=None, witness_type=None, network=None, include_private=False, as_dict=False):
2597
        """
2598
        Search for keys in database. Include 0 or more of account_id, name, key_id, change and depth.
2599

2600
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
2601
        >>> all_wallet_keys = w.keys()
2602
        >>> w.keys(depth=0) # doctest:+ELLIPSIS
2603
        [<DbKey(id=..., name='bitcoinlib_legacy_wallet_test', wif='xprv9s21ZrQH143K3cxbMVswDTYgAc9CeXABQjCD9zmXCpXw4MxN93LanEARbBmV3utHZS9Db4FX1C1RbC5KSNAjQ5WNJ1dDBJ34PjfiSgRvS8x'>]
2604

2605
        Returns a list of DbKey object or dictionary object if as_dict is True
2606

2607
        :param account_id: Search for account ID
2608
        :type account_id: int
2609
        :param name: Search for Name
2610
        :type name: str
2611
        :param key_id: Search for Key ID
2612
        :type key_id: int
2613
        :param change: Search for Change
2614
        :type change: int
2615
        :param depth: Only include keys with this depth
2616
        :type depth: int
2617
        :param used: Only return used or unused keys
2618
        :type used: bool
2619
        :param is_private: Only return private keys
2620
        :type is_private: bool
2621
        :param has_balance: Only include keys with a balance or without a balance, default is both
2622
        :type has_balance: bool
2623
        :param is_active: Hide inactive keys. Only include active keys with either a balance or which are unused, default is None (show all)
2624
        :type is_active: bool
2625
        :param witness_type: Filter by witness_type
2626
        :type witness_type: str
2627
        :param network: Network name filter
2628
        :type network: str
2629
        :param include_private: Include private key information in dictionary
2630
        :type include_private: bool
2631
        :param as_dict: Return keys as dictionary objects. Default is False: DbKey objects
2632
        :type as_dict: bool
2633

2634
        :return list of DbKey: List of Keys
2635
        """
2636

2637
        qr = self.session.query(DbKey).filter_by(wallet_id=self.wallet_id).order_by(DbKey.id)
2✔
2638
        if network is not None:
2✔
2639
            qr = qr.filter(DbKey.network_name == network)
2✔
2640
        if witness_type is not None:
2✔
2641
            qr = qr.filter(DbKey.witness_type == witness_type)
2✔
2642
        if account_id is not None:
2✔
2643
            qr = qr.filter(DbKey.account_id == account_id)
2✔
2644
            if self.scheme == 'bip32' and depth is None:
2✔
2645
                qr = qr.filter(DbKey.depth >= 3)
2✔
2646
        if change is not None:
2✔
2647
            qr = qr.filter(DbKey.change == change)
2✔
2648
            if self.scheme == 'bip32' and depth is None:
2✔
2649
                qr = qr.filter(DbKey.depth > self.key_depth - 1)
×
2650
        if depth is not None:
2✔
2651
            qr = qr.filter(DbKey.depth == depth)
2✔
2652
        if name is not None:
2✔
2653
            qr = qr.filter(DbKey.name == name)
2✔
2654
        if key_id is not None:
2✔
2655
            qr = qr.filter(DbKey.id == key_id)
2✔
2656
            is_active = False
2✔
2657
        elif used is not None:
2✔
2658
            qr = qr.filter(DbKey.used == used)
×
2659
        if is_private is not None:
2✔
2660
            qr = qr.filter(DbKey.is_private == is_private)
2✔
2661
        if has_balance is True and is_active is True:
2✔
2662
            raise WalletError("Cannot use has_balance and is_active parameter together")
×
2663
        if has_balance is not None:
2✔
2664
            if has_balance:
×
2665
                qr = qr.filter(DbKey.balance != 0)
×
2666
            else:
2667
                qr = qr.filter(DbKey.balance == 0)
×
2668
        if is_active:  # Unused keys and keys with a balance
2✔
2669
            qr = qr.filter(or_(DbKey.balance != 0, DbKey.used.is_(False)))
2✔
2670
        keys = qr.order_by(DbKey.depth).all()
2✔
2671
        if as_dict:
2✔
2672
            keys = [x.__dict__ for x in keys]
2✔
2673
            keys2 = []
2✔
2674
            private_fields = []
2✔
2675
            if not include_private:
2✔
2676
                private_fields += ['private', 'wif']
2✔
2677
            for key in keys:
2✔
2678
                keys2.append({k: v for (k, v) in key.items()
2✔
2679
                              if k[:1] != '_' and k != 'wallet' and k not in private_fields})
2680
            return keys2
2✔
2681
        return keys
2✔
2682

2683
    def keys_networks(self, used=None, as_dict=False):
2✔
2684
        """
2685
        Get keys of defined networks for this wallet. Wrapper for the :func:`keys` method
2686

2687
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
2688
        >>> network_key = w.keys_networks()
2689
        >>> network_key[0].path
2690
        "m/44'/0'"
2691

2692
        :param used: Only return used or unused keys
2693
        :type used: bool
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, list of dict:
2698

2699
        """
2700

2701
        if self.scheme != 'bip32':
2✔
2702
            raise WalletError("The 'keys_network' method can only be used with BIP32 type wallets")
×
2703
        try:
2✔
2704
            depth = self.key_path.index("coin_type'")
2✔
2705
        except ValueError:
2✔
2706
            return []
2✔
2707
        if self.multisig and self.cosigner:
2✔
2708
            _logger.warning("No network keys available for multisig wallet, use networks() method for list of networks")
×
2709
        return self.keys(depth=depth, used=used, as_dict=as_dict)
2✔
2710

2711
    def keys_accounts(self, account_id=None, network=DEFAULT_NETWORK, as_dict=False):
2✔
2712
        """
2713
        Get Database records of account key(s) with for current wallet. Wrapper for the :func:`keys` method.
2714

2715
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
2716
        >>> account_key = w.keys_accounts()
2717
        >>> account_key[0].path
2718
        "m/44'/0'/0'"
2719

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

2722
        :param account_id: Search for Account ID
2723
        :type account_id: int
2724
        :param network: Network name filter
2725
        :type network: str
2726
        :param as_dict: Return as dictionary or DbKey object. Default is False: DbKey objects
2727
        :type as_dict: bool
2728

2729
        :return list of (DbKey, dict):
2730
        """
2731

2732
        return self.keys(account_id, depth=self.depth_public_master, network=network, as_dict=as_dict)
2✔
2733

2734
    def keys_addresses(self, account_id=None, used=None, is_active=None, change=None, network=None, depth=None,
2✔
2735
                       as_dict=False):
2736
        """
2737
        Get address keys of specified account_id for current wallet. Wrapper for the :func:`keys` methods.
2738

2739
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
2740
        >>> w.keys_addresses()[0].address
2741
        '16QaHuFkfuebXGcYHmehRXBBX7RG9NbtLg'
2742

2743
        :param account_id: Account ID
2744
        :type account_id: int
2745
        :param used: Only return used or unused keys
2746
        :type used: bool
2747
        :param is_active: Hide inactive keys. Only include active keys with either a balance or which are unused, default is True
2748
        :type is_active: bool
2749
        :param change: Search for Change
2750
        :type change: int
2751
        :param network: Network name filter
2752
        :type network: str
2753
        :param depth: Filter by key depth. Default for BIP44 and multisig is 5
2754
        :type depth: int
2755
        :param as_dict: Return as dictionary or DbKey object. Default is False: DbKey objects
2756
        :type as_dict: bool
2757

2758
        :return list of (DbKey, dict)
2759
        """
2760

2761
        if depth is None:
2✔
2762
            depth = self.key_depth
2✔
2763
        return self.keys(account_id, depth=depth, used=used, change=change, is_active=is_active, network=network,
2✔
2764
                         as_dict=as_dict)
2765

2766
    def keys_address_payment(self, account_id=None, used=None, network=None, as_dict=False):
2✔
2767
        """
2768
        Get payment addresses (change=0) of specified account_id for current wallet. Wrapper for the :func:`keys` methods.
2769

2770
        :param account_id: Account ID
2771
        :type account_id: int
2772
        :param used: Only return used or unused keys
2773
        :type used: bool
2774
        :param network: Network name filter
2775
        :type network: str
2776
        :param as_dict: Return as dictionary or DbKey object. Default is False: DbKey objects
2777
        :type as_dict: bool
2778

2779
        :return list of (DbKey, dict)
2780
        """
2781

2782
        return self.keys(account_id, depth=self.key_depth, change=0, used=used, network=network, as_dict=as_dict)
2✔
2783

2784
    def keys_address_change(self, account_id=None, used=None, network=None, as_dict=False):
2✔
2785
        """
2786
        Get payment addresses (change=1) of specified account_id for current wallet. Wrapper for the :func:`keys` methods.
2787

2788
        :param account_id: Account ID
2789
        :type account_id: int
2790
        :param used: Only return used or unused keys
2791
        :type used: bool
2792
        :param network: Network name filter
2793
        :type network: str
2794
        :param as_dict: Return as dictionary or DbKey object. Default is False: DbKey objects
2795
        :type as_dict: bool
2796

2797
        :return list of (DbKey, dict)
2798
        """
2799

2800
        return self.keys(account_id, depth=self.key_depth, change=1, used=used, network=network, as_dict=as_dict)
2✔
2801

2802
    def addresslist(self, account_id=None, used=None, network=None, change=None, depth=None, key_id=None):
2✔
2803
        """
2804
        Get list of addresses defined in current wallet. Wrapper for the :func:`keys` methods.
2805

2806
        Use :func:`keys_addresses` method to receive full key objects
2807

2808
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
2809
        >>> w.addresslist()[0]
2810
        '16QaHuFkfuebXGcYHmehRXBBX7RG9NbtLg'
2811

2812
        :param account_id: Account ID
2813
        :type account_id: int
2814
        :param used: Only return used or unused keys
2815
        :type used: bool, None
2816
        :param network: Network name filter
2817
        :type network: str
2818
        :param change: Only include change addresses or not. Default is None which returns both
2819
        :param depth: Filter by key depth. Default is None for standard key depth. Use -1 to show all keys
2820
        :type depth: int
2821
        :param key_id: Key ID to get address of just 1 key
2822
        :type key_id: int
2823

2824
        :return list of str: List of address strings
2825
        """
2826

2827
        addresslist = []
2✔
2828
        if depth is None:
2✔
2829
            depth = self.key_depth
2✔
2830
        elif depth == -1:
2✔
2831
            depth = None
2✔
2832
        for key in self.keys(account_id=account_id, depth=depth, used=used, network=network, change=change,
2✔
2833
                             key_id=key_id, is_active=False):
2834
            addresslist.append(key.address)
2✔
2835
        return addresslist
2✔
2836

2837
    def key(self, term):
2✔
2838
        """
2839
        Return single key with given ID or name as WalletKey object
2840

2841
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
2842
        >>> w.key('change 0').address
2843
        '1HabJXe8mTwXiMzUWW5KdpYbFWu3hvtsbF'
2844

2845
        :param term: Search term can be key ID, key address, key WIF or key name
2846
        :type term: int, str
2847

2848
        :return WalletKey: Single key as object
2849
        """
2850

2851
        dbkey = None
2✔
2852
        qr = self.session.query(DbKey).filter_by(wallet_id=self.wallet_id)
2✔
2853
        if isinstance(term, numbers.Number):
2✔
2854
            dbkey = qr.filter_by(id=term).scalar()
2✔
2855
        if not dbkey:
2✔
2856
            dbkey = qr.filter_by(address=term).first()
×
2857
        if not dbkey:
2✔
2858
            dbkey = qr.filter_by(wif=term).first()
×
2859
        if not dbkey:
2✔
2860
            dbkey = qr.filter_by(name=term).first()
×
2861
        if dbkey:
2✔
2862
            if dbkey.id in self._key_objects.keys():
2✔
2863
                return self._key_objects[dbkey.id]
2✔
2864
            else:
2865
                hdwltkey = WalletKey(key_id=dbkey.id, session=self.session)
2✔
2866
                self._key_objects.update({dbkey.id: hdwltkey})
2✔
2867
                return hdwltkey
2✔
2868
        else:
2869
            raise BKeyError("Key '%s' not found" % term)
×
2870

2871
    def account(self, account_id):
2✔
2872
        """
2873
        Returns wallet key of specific BIP44 account.
2874

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

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

2879
        :param account_id: ID of account. Default is 0
2880
        :type account_id: int
2881

2882
        :return WalletKey:
2883
        """
2884

2885
        if "account'" not in self.key_path:
2✔
2886
            raise WalletError("Accounts are not supported for this wallet. Account not found in key path %s" %
2✔
2887
                              self.key_path)
2888
        qr = self.session.query(DbKey).\
2✔
2889
            filter_by(wallet_id=self.wallet_id, purpose=self.purpose, network_name=self.network.name,
2890
                      account_id=account_id, depth=3).scalar()
2891
        if not qr:
2✔
2892
            raise WalletError("Account with ID %d not found in this wallet" % account_id)
2✔
2893
        key_id = qr.id
2✔
2894
        return self.key(key_id)
2✔
2895

2896
    def accounts(self, network=None):
2✔
2897
        """
2898
        Get list of accounts for this wallet
2899

2900
        :param network: Network name filter. Default filter is network of first main key
2901
        :type network: str
2902

2903
        :return list of integers: List of accounts IDs
2904
        """
2905

2906
        network, _, _ = self._get_account_defaults(network)
2✔
2907
        if self.multisig and self.cosigner:
2✔
2908
            if self.cosigner_id is None:
2✔
2909
                raise WalletError("Missing Cosigner ID value for this wallet, cannot fetch account ID")
×
2910
            accounts = [wk.account_id for wk in self.cosigner[self.cosigner_id].keys_accounts(network=network)]
2✔
2911
        else:
2912
            accounts = [wk.account_id for wk in self.keys_accounts(network=network)]
2✔
2913
        if not accounts:
2✔
2914
            accounts = [self.default_account_id]
×
2915
        return list(dict.fromkeys(accounts))
2✔
2916

2917
    def witness_types(self, account_id=None, network=None):
2✔
2918
        """
2919
        Get witness types in use by this wallet. For example 'legacy', 'segwit', 'p2sh-segwit'
2920

2921
        :param account_id: Account ID. Leave empty for default account
2922
        :type account_id: int
2923
        :param network: Network name filter. Default filter is DEFAULT_NETWORK
2924
        :type network: str
2925

2926
        :return list of str:
2927
        """
2928

2929
        qr = self.session.query(DbKey.witness_type).filter_by(wallet_id=self.wallet_id)
2✔
2930
        if network is not None:
2✔
2931
            qr = qr.filter(DbKey.network_name == network)
2✔
2932
        if account_id is not None:
2✔
2933
            qr = qr.filter(DbKey.account_id == account_id)
2✔
2934
        qr = qr.group_by(DbKey.witness_type).all()
2✔
2935
        return [x[0] for x in qr] if qr else [self.witness_type]
2✔
2936

2937
    def networks(self, as_dict=False):
2✔
2938
        """
2939
        Get list of networks used by this wallet
2940

2941
        :param as_dict: Return as dictionary or as Network objects, default is Network objects
2942
        :type as_dict: bool
2943

2944
        :return list of (Network, dict):
2945
        """
2946

2947
        nw_list = [self.network]
2✔
2948
        if self.multisig and self.cosigner:
2✔
2949
            keys_qr = self.session.query(DbKey.network_name).\
2✔
2950
                filter_by(wallet_id=self.wallet_id, depth=self.key_depth).\
2951
                group_by(DbKey.network_name).all()
2952
            nw_list += [Network(nw[0]) for nw in keys_qr]
2✔
2953
        elif self.main_key.key_type != 'single':
2✔
2954
            wks = self.keys_networks()
2✔
2955
            for wk in wks:
2✔
2956
                nw_list.append(Network(wk.network_name))
2✔
2957

2958
        networks = []
2✔
2959
        nw_list = list(dict.fromkeys(nw_list))
2✔
2960
        for nw in nw_list:
2✔
2961
            if as_dict:
2✔
2962
                nw = nw.__dict__
×
2963
                if '_sa_instance_state' in nw:
×
2964
                    del nw['_sa_instance_state']
×
2965
            networks.append(nw)
2✔
2966

2967
        return networks
2✔
2968

2969
    def network_list(self, field='name'):
2✔
2970
        """
2971
        Wrapper for :func:`networks` method, returns a flat list with currently used
2972
        networks for this wallet.
2973

2974
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
2975
        >>> w.network_list()
2976
        ['bitcoin']
2977

2978
        :return list of str:
2979
        """
2980

2981
        return [getattr(x, field) for x in self.networks()]
2✔
2982

2983
    def balance_update_from_serviceprovider(self, account_id=None, network=None):
2✔
2984
        """
2985
        Update balance of currents account addresses using default Service objects :func:`getbalance` method. Update total
2986
        wallet balance in database.
2987

2988
        Please Note: Does not update UTXO's or the balance per key! For this use the :func:`updatebalance` method
2989
        instead
2990

2991
        :param account_id: Account ID. Leave empty for default account
2992
        :type account_id: int
2993
        :param network: Network name. Leave empty for default network
2994
        :type network: str
2995

2996
        :return int: Total balance
2997
        """
2998

2999
        network, account_id, acckey = self._get_account_defaults(network, account_id)
2✔
3000
        srv = Service(network=network, wallet_name=self.name, providers=self.providers, cache_uri=self.db_cache_uri,
2✔
3001
                      strict=self.strict)
3002
        balance = srv.getbalance(self.addresslist(account_id=account_id, network=network))
2✔
3003
        if srv.results:
2✔
3004
            new_balance = {
2✔
3005
                'account_id': account_id,
3006
                'network': network,
3007
                'balance': balance
3008
            }
3009
            old_balance_item = [bi for bi in self._balances if bi['network'] == network and
2✔
3010
                                bi['account_id'] == account_id]
3011
            if old_balance_item:
2✔
3012
                item_n = self._balances.index(old_balance_item[0])
×
3013
                self._balances[item_n] = new_balance
×
3014
            else:
3015
                self._balances.append(new_balance)
2✔
3016
        return balance
2✔
3017

3018
    def balance(self, account_id=None, network=None, as_string=False):
2✔
3019
        """
3020
        Get total of unspent outputs
3021

3022
        :param account_id: Account ID filter
3023
        :type account_id: int
3024
        :param network: Network name. Leave empty for default network
3025
        :type network: str
3026
        :param as_string: Set True to return a string in currency format. Default returns float.
3027
        :type as_string: boolean
3028

3029
        :return float, str: Key balance
3030
        """
3031

3032
        self._balance_update(account_id, network)
2✔
3033
        network, account_id, _ = self._get_account_defaults(network, account_id)
2✔
3034

3035
        balance = 0
2✔
3036
        b_res = [b['balance'] for b in self._balances if b['account_id'] == account_id and b['network'] == network]
2✔
3037
        if len(b_res):
2✔
3038
            balance = b_res[0]
2✔
3039
        if as_string:
2✔
3040
            return Value.from_satoshi(balance, network=network).str_unit()
2✔
3041
        else:
3042
            return round(balance)
2✔
3043

3044
    def _balance_update(self, account_id=None, network=None, key_id=None, min_confirms=0):
2✔
3045
        """
3046
        Update balance from UTXO's in database. To get most recent balance use :func:`utxos_update` first.
3047

3048
        Also updates balance of wallet and keys in this wallet for the specified account or all accounts if
3049
        no account is specified.
3050

3051
        :param account_id: Account ID filter
3052
        :type account_id: int
3053
        :param network: Network name. Leave empty for default network
3054
        :type network: str
3055
        :param key_id: Key ID Filter
3056
        :type key_id: int
3057
        :param min_confirms: Minimal confirmations needed to include in balance (default = 0)
3058
        :type min_confirms: int
3059

3060
        :return: Updated balance
3061
        """
3062

3063
        qr = self.session.query(DbTransactionOutput, func.sum(DbTransactionOutput.value), DbTransaction.network_name,
2✔
3064
                                 DbTransaction.account_id).\
3065
            join(DbTransaction). \
3066
            filter(DbTransactionOutput.spent.is_(False),
3067
                   DbTransaction.wallet_id == self.wallet_id,
3068
                   DbTransaction.confirmations >= min_confirms)
3069
        if account_id is not None:
2✔
3070
            qr = qr.filter(DbTransaction.account_id == account_id)
2✔
3071
        if network is not None:
2✔
3072
            qr = qr.filter(DbTransaction.network_name == network)
2✔
3073
        if key_id is not None:
2✔
3074
            qr = qr.filter(DbTransactionOutput.key_id == key_id)
2✔
3075
        else:
3076
            qr = qr.filter(DbTransactionOutput.key_id.isnot(None))
2✔
3077
        utxos = qr.group_by(
2✔
3078
            DbTransactionOutput.key_id,
3079
            DbTransactionOutput.transaction_id,
3080
            DbTransactionOutput.output_n,
3081
            DbTransaction.network_name,
3082
            DbTransaction.account_id
3083
        ).all()
3084

3085
        key_values = [
2✔
3086
            {
3087
                'id': utxo[0].key_id,
3088
                'network': utxo[2],
3089
                'account_id': utxo[3],
3090
                'balance': utxo[1]
3091
            }
3092
            for utxo in utxos
3093
        ]
3094

3095
        grouper = itemgetter("id", "network", "account_id")
2✔
3096
        key_balance_list = []
2✔
3097
        for key, grp in groupby(sorted(key_values, key=grouper), grouper):
2✔
3098
            nw_acc_dict = dict(zip(["id", "network", "account_id"], key))
2✔
3099
            nw_acc_dict["balance"] = sum(item["balance"] for item in grp)
2✔
3100
            key_balance_list.append(nw_acc_dict)
2✔
3101

3102
        grouper = itemgetter("network", "account_id")
2✔
3103
        balance_list = []
2✔
3104
        for key, grp in groupby(sorted(key_balance_list, key=grouper), grouper):
2✔
3105
            nw_acc_dict = dict(zip(["network", "account_id"], key))
2✔
3106
            nw_acc_dict["balance"] = sum(item["balance"] for item in grp)
2✔
3107
            balance_list.append(nw_acc_dict)
2✔
3108

3109
        # Add keys with no UTXO's with 0 balance
3110
        for key in self.keys(account_id=account_id, network=network, key_id=key_id):
2✔
3111
            if key.id not in [utxo[0].key_id for utxo in utxos]:
2✔
3112
                key_balance_list.append({
2✔
3113
                    'id': key.id,
3114
                    'network': network,
3115
                    'account_id': key.account_id,
3116
                    'balance': 0
3117
                })
3118

3119
        if not key_id:
2✔
3120
            for bl in balance_list:
2✔
3121
                bl_item = [b for b in self._balances if
2✔
3122
                           b['network'] == bl['network'] and b['account_id'] == bl['account_id']]
3123
                if not bl_item:
2✔
3124
                    self._balances.append(bl)
2✔
3125
                    continue
2✔
3126
                lx = self._balances.index(bl_item[0])
2✔
3127
                self._balances[lx].update(bl)
2✔
3128

3129
        self._balance = sum([b['balance'] for b in balance_list if b['network'] == self.network.name])
2✔
3130

3131
        # Bulk update database
3132
        for kb in key_balance_list:
2✔
3133
            if kb['id'] in self._key_objects:
2✔
3134
                self._key_objects[kb['id']]._balance = kb['balance']
2✔
3135
        self.session.bulk_update_mappings(DbKey, key_balance_list)
2✔
3136
        self._commit()
2✔
3137
        _logger.info("Got balance for %d key(s)" % len(key_balance_list))
2✔
3138
        return self._balances
2✔
3139

3140
    def utxos_update(self, account_id=None, used=None, networks=None, key_id=None, depth=None, change=None,
2✔
3141
                     utxos=None, update_balance=True, max_utxos=MAX_TRANSACTIONS, rescan_all=True):
3142
        """
3143
        Update UTXO's (Unspent Outputs) for addresses/keys in this wallet using various Service providers.
3144

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

3147
        :param account_id: Account ID
3148
        :type account_id: int
3149
        :param used: Only check for UTXO for used or unused keys. Default is both
3150
        :type used: bool
3151
        :param networks: Network name filter as string or list of strings. Leave empty to update all used networks in wallet
3152
        :type networks: str, list
3153
        :param key_id: Key ID to just update 1 key
3154
        :type key_id: int
3155
        :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.
3156
        :type depth: int
3157
        :param change: Only update change or normal keys, default is both (None)
3158
        :type change: int
3159
        :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
3160
        :type utxos: list of dict.
3161

3162
        .. code-block:: json
3163

3164
            {
3165
              "address": "n2S9Czehjvdmpwd2YqekxuUC1Tz5ZdK3YN",
3166
              "script": "",
3167
              "confirmations": 10,
3168
              "output_n": 1,
3169
              "txid": "9df91f89a3eb4259ce04af66ad4caf3c9a297feea5e0b3bc506898b6728c5003",
3170
              "value": 8970937
3171
            }
3172

3173
        :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
3174
        :type update_balance: bool
3175
        :param max_utxos: Maximum number of UTXO's to update
3176
        :type max_utxos: int
3177
        :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
3178
        :type rescan_all: bool
3179

3180
        :return int: Number of new UTXO's added
3181
        """
3182

3183
        _, account_id, acckey = self._get_account_defaults('', account_id, key_id)
2✔
3184

3185
        single_key = None
2✔
3186
        if key_id:
2✔
3187
            single_key = self.session.query(DbKey).filter_by(id=key_id).scalar()
2✔
3188
            networks = [single_key.network_name]
2✔
3189
            account_id = single_key.account_id
2✔
3190
            rescan_all = False
2✔
3191
        if networks is None:
2✔
3192
            networks = self.network_list()
2✔
3193
        elif not isinstance(networks, list):
2✔
3194
            networks = [networks]
2✔
3195
        elif len(networks) != 1 and utxos is not None:
2✔
3196
            raise WalletError("Please specify maximum 1 network when passing utxo's")
×
3197

3198
        count_utxos = 0
2✔
3199
        for network in networks:
2✔
3200
            # Remove current UTXO's
3201
            if rescan_all:
2✔
3202
                cur_utxos = self.session.query(DbTransactionOutput). \
2✔
3203
                    join(DbTransaction). \
3204
                    filter(DbTransactionOutput.spent.is_(False),
3205
                           DbTransaction.account_id == account_id,
3206
                           DbTransaction.wallet_id == self.wallet_id,
3207
                           DbTransaction.network_name == network).all()
3208
                for u in cur_utxos:
2✔
3209
                    self.session.query(DbTransactionOutput).filter_by(
×
3210
                        transaction_id=u.transaction_id, output_n=u.output_n).update({DbTransactionOutput.spent: True})
3211
                self._commit()
2✔
3212

3213
            if account_id is None and not self.multisig:
2✔
3214
                accounts = self.accounts(network=network)
×
3215
            else:
3216
                accounts = [account_id]
2✔
3217
            for account_id in accounts:
2✔
3218
                if depth is None:
2✔
3219
                    depth = self.key_depth
2✔
3220
                if utxos is None:
2✔
3221
                    # Get all UTXO's for this wallet from default Service object
3222
                    addresslist = self.addresslist(account_id=account_id, used=used, network=network, key_id=key_id,
2✔
3223
                                                   change=change, depth=depth)
3224
                    random.shuffle(addresslist)
2✔
3225
                    srv = Service(network=network, wallet_name=self.name, providers=self.providers,
2✔
3226
                                  cache_uri=self.db_cache_uri, strict=self.strict)
3227
                    utxos = []
2✔
3228
                    for address in addresslist:
2✔
3229
                        if rescan_all:
2✔
3230
                            last_txid = ''
2✔
3231
                        else:
3232
                            last_txid = self.utxo_last(address)
2✔
3233
                        new_utxos = srv.getutxos(address, after_txid=last_txid, limit=max_utxos)
2✔
3234
                        if new_utxos:
2✔
3235
                            utxos += new_utxos
2✔
3236
                        elif new_utxos is False:
2✔
3237
                            raise WalletError("No response from any service provider, could not update UTXO's. "
×
3238
                                              "Errors: %s" % srv.errors)
3239
                    if srv.complete:
2✔
3240
                        self.last_updated = datetime.now(timezone.utc)
×
3241
                    elif utxos and 'date' in utxos[-1:][0]:
2✔
3242
                        self.last_updated = utxos[-1:][0]['date']
2✔
3243

3244
                # If UTXO is new, add to database otherwise update depth (confirmation count)
3245
                for utxo in utxos:
2✔
3246
                    key = single_key
2✔
3247
                    if not single_key:
2✔
3248
                        key = self.session.query(DbKey).\
2✔
3249
                            filter_by(wallet_id=self.wallet_id, address=utxo['address']).scalar()
3250
                    if not key:
2✔
3251
                        raise WalletError("Key with address %s not found in this wallet" % utxo['address'])
×
3252
                    key.used = True
2✔
3253
                    status = 'unconfirmed'
2✔
3254
                    if utxo['confirmations']:
2✔
3255
                        status = 'confirmed'
2✔
3256

3257
                    # Update confirmations in db if utxo was already imported
3258
                    transaction_in_db = self.session.query(DbTransaction).\
2✔
3259
                        filter_by(wallet_id=self.wallet_id, txid=bytes.fromhex(utxo['txid']),
3260
                                  network_name=network)
3261
                    utxo_in_db = self.session.query(DbTransactionOutput).join(DbTransaction).\
2✔
3262
                        filter(DbTransaction.wallet_id == self.wallet_id,
3263
                               DbTransaction.txid == bytes.fromhex(utxo['txid']),
3264
                               DbTransactionOutput.output_n == utxo['output_n'])
3265
                    spent_in_db = self.session.query(DbTransactionInput).join(DbTransaction).\
2✔
3266
                        filter(DbTransaction.wallet_id == self.wallet_id,
3267
                               DbTransactionInput.prev_txid == bytes.fromhex(utxo['txid']),
3268
                               DbTransactionInput.output_n == utxo['output_n'])
3269
                    if utxo_in_db.count():
2✔
3270
                        utxo_record = utxo_in_db.scalar()
2✔
3271
                        if not utxo_record.key_id:
2✔
3272
                            count_utxos += 1
×
3273
                        utxo_record.key_id = key.id
2✔
3274
                        utxo_record.spent = bool(spent_in_db.count())
2✔
3275
                        if transaction_in_db.count():
2✔
3276
                            transaction_record = transaction_in_db.scalar()
2✔
3277
                            transaction_record.confirmations = utxo['confirmations']
2✔
3278
                            transaction_record.status = status
2✔
3279
                    else:
3280
                        # Add transaction if not exist and then add output
3281
                        if not transaction_in_db.count():
2✔
3282
                            block_height = None
2✔
3283
                            if block_height in utxo and utxo['block_height']:
2✔
3284
                                block_height = utxo['block_height']
×
3285
                            new_tx = DbTransaction(
2✔
3286
                                wallet_id=self.wallet_id, txid=bytes.fromhex(utxo['txid']), status=status,
3287
                                is_complete=False, block_height=block_height, account_id=account_id,
3288
                                confirmations=utxo['confirmations'], network_name=network)
3289
                            self.session.add(new_tx)
2✔
3290
                            # TODO: Get unique id before inserting to increase performance for large utxo-sets
3291
                            self._commit()
2✔
3292
                            tid = new_tx.id
2✔
3293
                        else:
3294
                            tid = transaction_in_db.scalar().id
2✔
3295

3296
                        script_type = script_type_default(self.witness_type, multisig=self.multisig,
2✔
3297
                                                          locking_script=True)
3298
                        new_utxo = DbTransactionOutput(transaction_id=tid,  output_n=utxo['output_n'],
2✔
3299
                                                       value=utxo['value'], key_id=key.id, address=utxo['address'],
3300
                                                       script=bytes.fromhex(utxo['script']),
3301
                                                       script_type=script_type,
3302
                                                       spent=bool(spent_in_db.count()))
3303
                        self.session.add(new_utxo)
2✔
3304
                        count_utxos += 1
2✔
3305

3306
                    self._commit()
2✔
3307

3308
                _logger.info("Got %d new UTXOs for account %s" % (count_utxos, account_id))
2✔
3309
                self._commit()
2✔
3310
                if update_balance:
2✔
3311
                    self._balance_update(account_id=account_id, network=network, key_id=key_id, min_confirms=0)
2✔
3312
                utxos = None
2✔
3313
        return count_utxos
2✔
3314

3315
    def utxos(self, account_id=None, network=None, min_confirms=0, key_id=None):
2✔
3316
        """
3317
        Get UTXO's (Unspent Outputs) from database. Use :func:`utxos_update` method first for updated values
3318

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

3323
        :param account_id: Account ID
3324
        :type account_id: int
3325
        :param network: Network name. Leave empty for default network
3326
        :type network: str
3327
        :param min_confirms: Minimal confirmation needed to include in output list
3328
        :type min_confirms: int
3329
        :param key_id: Key ID or list of key IDs to filter utxo's for specific keys
3330
        :type key_id: int, list
3331

3332
        :return list: List of transactions
3333
        """
3334

3335
        first_key_id = key_id
2✔
3336
        if isinstance(key_id, list):
2✔
3337
            first_key_id = key_id[0]
2✔
3338
        network, account_id, acckey = self._get_account_defaults(network, account_id, first_key_id)
2✔
3339

3340
        qr = self.session.query(DbTransactionOutput, DbKey.address, DbTransaction.confirmations, DbTransaction.txid,
2✔
3341
                                 DbKey.network_name).\
3342
            join(DbTransaction).join(DbKey). \
3343
            filter(DbTransactionOutput.spent.is_(False),
3344
                   DbTransaction.account_id == account_id,
3345
                   DbTransaction.wallet_id == self.wallet_id,
3346
                   DbTransaction.network_name == network,
3347
                   DbTransaction.confirmations >= min_confirms)
3348
        if isinstance(key_id, int):
2✔
3349
            qr = qr.filter(DbKey.id == key_id)
×
3350
        elif isinstance(key_id, list):
2✔
3351
            qr = qr.filter(DbKey.id.in_(key_id))
2✔
3352
        utxos = qr.order_by(DbTransaction.confirmations.desc()).all()
2✔
3353
        res = []
2✔
3354
        for utxo in utxos:
2✔
3355
            u = utxo[0].__dict__
2✔
3356
            # if '_sa_instance_state' in u:
3357
            #     del u['_sa_instance_state']
3358
            u['address'] = utxo[1]
2✔
3359
            u['confirmations'] = int(utxo[2])
2✔
3360
            u['txid'] = utxo[3].hex()
2✔
3361
            u['network_name'] = utxo[4]
2✔
3362
            res.append(u)
2✔
3363
        return res
2✔
3364

3365
    def utxo_add(self, address, value, txid, output_n, confirmations=1, script=''):
2✔
3366
        """
3367
        Add a single UTXO to the wallet database. To update all utxo's use :func:`utxos_update` method.
3368

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

3371
        This method does not check if UTXO exists or is still spendable.
3372

3373
        :param address: Address of Unspent Output. Address should be available in wallet
3374
        :type address: str
3375
        :param value: Value of output in sathosis or smallest denominator for type of currency
3376
        :type value: int
3377
        :param txid: Transaction hash or previous output as hex-string
3378
        :type txid: str
3379
        :param output_n: Output number of previous transaction output
3380
        :type output_n: int
3381
        :param confirmations: Number of confirmations. Default is 0, unconfirmed
3382
        :type confirmations: int
3383
        :param script: Locking script of previous output as hex-string
3384
        :type script: str
3385

3386
        :return int: Number of new UTXO's added, so 1 if successful
3387
        """
3388

3389
        utxo = {
2✔
3390
            'address': address,
3391
            'script': script,
3392
            'confirmations': confirmations,
3393
            'output_n': output_n,
3394
            'txid': txid,
3395
            'value': value
3396
        }
3397
        return self.utxos_update(utxos=[utxo], rescan_all=False)
2✔
3398

3399
    def utxo_last(self, address):
2✔
3400
        """
3401
        Get transaction ID for latest utxo in database for given address
3402

3403
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
3404
        >>> w.utxo_last('16QaHuFkfuebXGcYHmehRXBBX7RG9NbtLg')
3405
        '748799c9047321cb27a6320a827f1f69d767fe889c14bf11f27549638d566fe4'
3406

3407
        :param address: The address
3408
        :type address: str
3409

3410
        :return str:
3411
        """
3412
        to = self.session.query(
2✔
3413
            DbTransaction.txid, DbTransaction.confirmations). \
3414
            join(DbTransactionOutput).join(DbKey). \
3415
            filter(DbKey.address == address, DbTransaction.wallet_id == self.wallet_id,
3416
                   DbTransactionOutput.spent.is_(False)). \
3417
            order_by(DbTransaction.confirmations).first()
3418
        return '' if not to else to[0].hex()
2✔
3419

3420
    def transactions_update_confirmations(self):
2✔
3421
        """
3422
        Update number of confirmations and status for transactions in database
3423

3424
        :return:
3425
        """
3426
        network = self.network.name
2✔
3427
        srv = Service(network=network, wallet_name=self.name, providers=self.providers, cache_uri=self.db_cache_uri,
2✔
3428
                      strict=self.strict)
3429
        blockcount = srv.blockcount()
2✔
3430
        self.session.query(DbTransaction).\
2✔
3431
            filter(DbTransaction.wallet_id == self.wallet_id,
3432
                   DbTransaction.network_name == network, DbTransaction.block_height > 0).\
3433
                update({DbTransaction.status: 'confirmed',
3434
                        DbTransaction.confirmations: (blockcount - DbTransaction.block_height) + 1})
3435
        self._commit()
2✔
3436

3437
    def transactions_update_by_txids(self, txids):
2✔
3438
        """
3439
        Update transaction or list or transaction for this wallet with provided transaction ID
3440

3441
        :param txids: Transaction ID, or list of transaction IDs
3442
        :type txids: str, list of str, bytes, list of bytes
3443

3444
        :return:
3445
        """
3446
        if not isinstance(txids, list):
2✔
3447
            txids = [txids]
×
3448
        txids = list(dict.fromkeys(txids))
2✔
3449

3450
        txs = []
2✔
3451
        srv = Service(network=self.network.name, wallet_name=self.name, providers=self.providers,
2✔
3452
                      cache_uri=self.db_cache_uri, strict=self.strict)
3453
        for txid in txids:
2✔
3454
            tx = srv.gettransaction(to_hexstring(txid))
2✔
3455
            if tx:
2✔
3456
                txs.append(tx)
2✔
3457

3458
        # TODO: Avoid duplicate code in this method and transaction_update()
3459
        utxo_set = set()
2✔
3460
        for t in txs:
2✔
3461
            wt = WalletTransaction.from_transaction(self, t)
2✔
3462
            wt.store()
2✔
3463
            utxos = [(ti.prev_txid.hex(), ti.output_n_int) for ti in wt.inputs]
2✔
3464
            utxo_set.update(utxos)
2✔
3465

3466
        for utxo in list(utxo_set):
2✔
3467
            tos = self.session.query(DbTransactionOutput).join(DbTransaction). \
2✔
3468
                filter(DbTransaction.txid == bytes.fromhex(utxo[0]), DbTransactionOutput.output_n == utxo[1],
3469
                       DbTransactionOutput.spent.is_(False)).all()
3470
            for u in tos:
2✔
3471
                u.spent = True
×
3472
        self._commit()
2✔
3473
        # self._balance_update(account_id=account_id, network=network, key_id=key_id)
3474

3475
    def transactions_update(self, account_id=None, used=None, network=None, key_id=None, depth=None, change=None,
2✔
3476
                            limit=MAX_TRANSACTIONS):
3477
        """
3478
        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.
3479

3480
        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.
3481

3482
        :param account_id: Account ID
3483
        :type account_id: int
3484
        :param used: Only update used or unused keys, specify None to update both. Default is None
3485
        :type used: bool, None
3486
        :param network: Network name. Leave empty for default network
3487
        :type network: str
3488
        :param key_id: Key ID to just update 1 key
3489
        :type key_id: int
3490
        :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.
3491
        :type depth: int
3492
        :param change: Only update change or normal keys, default is both (None)
3493
        :type change: int
3494
        :param limit: Stop update after limit transactions to avoid timeouts with service providers. Default is MAX_TRANSACTIONS defined in config.py
3495
        :type limit: int
3496

3497
        :return bool: True if all transactions are updated
3498
        """
3499

3500
        network, account_id, acckey = self._get_account_defaults(network, account_id, key_id)
2✔
3501
        if depth is None:
2✔
3502
            depth = self.key_depth
2✔
3503

3504
        # Update number of confirmations and status for already known transactions
3505
        self.transactions_update_confirmations()
2✔
3506

3507
        srv = Service(network=network, wallet_name=self.name, providers=self.providers, cache_uri=self.db_cache_uri,
2✔
3508
                      strict=self.strict)
3509

3510
        # Get transactions for wallet's addresses
3511
        txs = []
2✔
3512
        addresslist = self.addresslist(
2✔
3513
            account_id=account_id, used=used, network=network, key_id=key_id, change=change, depth=depth)
3514
        last_updated = datetime.now(timezone.utc)
2✔
3515
        for address in addresslist:
2✔
3516
            txs += srv.gettransactions(address, limit=limit, after_txid=self.transaction_last(address))
2✔
3517
            if not srv.complete:
2✔
3518
                if txs and txs[-1].date and txs[-1].date < last_updated:
2✔
3519
                    last_updated = txs[-1].date
2✔
3520
            if txs and txs[-1].confirmations:
2✔
3521
                dbkey = self.session.query(DbKey).filter(DbKey.address == address, DbKey.wallet_id == self.wallet_id)
2✔
3522
                if not dbkey.update({DbKey.latest_txid: bytes.fromhex(txs[-1].txid)}):
2✔
3523
                    raise WalletError("Failed to update latest transaction id for key with address %s" % address)
×
3524
                self._commit()
2✔
3525
        if txs is False:
2✔
3526
            raise WalletError("No response from any service provider, could not update transactions")
×
3527

3528
        # Update Transaction outputs to get list of unspent outputs (UTXO's)
3529
        utxo_set = set()
2✔
3530
        for t in txs:
2✔
3531
            wt = WalletTransaction.from_transaction(self, t)
2✔
3532
            wt.store()
2✔
3533
            utxos = [(ti.prev_txid.hex(), ti.output_n_int) for ti in wt.inputs]
2✔
3534
            utxo_set.update(utxos)
2✔
3535
        for utxo in list(utxo_set):
2✔
3536
            tos = self.session.query(DbTransactionOutput).join(DbTransaction).\
2✔
3537
                filter(DbTransaction.txid == bytes.fromhex(utxo[0]), DbTransactionOutput.output_n == utxo[1],
3538
                       DbTransactionOutput.spent.is_(False), DbTransaction.wallet_id == self.wallet_id).all()
3539
            for u in tos:
2✔
3540
                u.spent = True
×
3541

3542
        self.last_updated = last_updated
2✔
3543
        self._commit()
2✔
3544
        self._balance_update(account_id=account_id, network=network, key_id=key_id)
2✔
3545

3546
        return len(txs)
2✔
3547

3548
    def transaction_last(self, address):
2✔
3549
        """
3550
        Get transaction ID for latest transaction in database for given address
3551

3552
        :param address: The address
3553
        :type address: str
3554

3555
        :return str:
3556
        """
3557
        txid = self.session.query(DbKey.latest_txid).\
2✔
3558
            filter(DbKey.address == address, DbKey.wallet_id == self.wallet_id).scalar()
3559
        return '' if not txid else txid.hex()
2✔
3560

3561
    def transactions(self, account_id=None, network=None, include_new=False, key_id=None, as_dict=False):
2✔
3562
        """
3563
        Get all known transactions input and outputs for this wallet.
3564

3565
        The transaction only includes the inputs and outputs related to this wallet. To get full transactions
3566
        use the :func:`transactions_full` method.
3567

3568
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
3569
        >>> w.transactions()
3570
        [<WalletTransaction(input_count=0, output_count=1, status=confirmed, network=bitcoin)>]
3571

3572
        :param account_id: Filter by Account ID. Leave empty for default account_id
3573
        :type account_id: int, None
3574
        :param network: Filter by network name. Leave empty for default network
3575
        :type network: str, None
3576
        :param include_new: Also include new and incomplete transactions in list. Default is False
3577
        :type include_new: bool
3578
        :param key_id: Filter by key ID
3579
        :type key_id: int, None
3580
        :param as_dict: Output as dictionary or WalletTransaction object
3581
        :type as_dict: bool
3582

3583
        :return list of WalletTransaction: List of WalletTransaction or transactions as dictionary
3584
        """
3585

3586
        network, account_id, acckey = self._get_account_defaults(network, account_id, key_id)
2✔
3587
        # Transaction inputs
3588
        qr = self.session.query(DbTransactionInput, DbTransactionInput.address, DbTransaction.confirmations,
2✔
3589
                                 DbTransaction.txid, DbTransaction.network_name, DbTransaction.status). \
3590
            join(DbTransaction).join(DbKey). \
3591
            filter(DbTransaction.account_id == account_id,
3592
                   DbTransaction.wallet_id == self.wallet_id,
3593
                   DbKey.wallet_id == self.wallet_id,
3594
                   DbTransaction.network_name == network)
3595
        if key_id is not None:
2✔
3596
            qr = qr.filter(DbTransactionInput.key_id == key_id)
×
3597
        if not include_new:
2✔
3598
            qr = qr.filter(or_(DbTransaction.status == 'confirmed', DbTransaction.status == 'unconfirmed'))
2✔
3599
        txs = qr.all()
2✔
3600
        # Transaction outputs
3601
        qr = self.session.query(DbTransactionOutput, DbTransactionOutput.address, DbTransaction.confirmations,
2✔
3602
                                 DbTransaction.txid, DbTransaction.network_name, DbTransaction.status). \
3603
            join(DbTransaction).join(DbKey). \
3604
            filter(DbTransaction.account_id == account_id,
3605
                   DbTransaction.wallet_id == self.wallet_id,
3606
                   DbKey.wallet_id == self.wallet_id,
3607
                   DbTransaction.network_name == network)
3608
        if key_id is not None:
2✔
3609
            qr = qr.filter(DbTransactionOutput.key_id == key_id)
×
3610
        if not include_new:
2✔
3611
            qr = qr.filter(or_(DbTransaction.status == 'confirmed', DbTransaction.status == 'unconfirmed'))
2✔
3612
        txs += qr.all()
2✔
3613

3614
        txs = sorted(txs, key=lambda k: (k[2], pow(10, 20)-k[0].transaction_id, k[3]), reverse=True)
2✔
3615
        res = []
2✔
3616
        txids = []
2✔
3617
        for tx in txs:
2✔
3618
            txid = tx[3].hex()
2✔
3619
            if as_dict:
2✔
3620
                u = tx[0].__dict__
2✔
3621
                u['block_height'] = tx[0].transaction.block_height
2✔
3622
                u['date'] = tx[0].transaction.date
2✔
3623
                if '_sa_instance_state' in u:
2✔
3624
                    del u['_sa_instance_state']
2✔
3625
                u['address'] = tx[1]
2✔
3626
                u['confirmations'] = None if tx[2] is None else int(tx[2])
2✔
3627
                u['txid'] = txid
2✔
3628
                u['network_name'] = tx[4]
2✔
3629
                u['status'] = tx[5]
2✔
3630
                if 'index_n' in u:
2✔
3631
                    u['is_output'] = True
×
3632
                    u['value'] = -u['value']
×
3633
                else:
3634
                    u['is_output'] = False
2✔
3635
            else:
3636
                if txid in txids:
2✔
3637
                    continue
2✔
3638
                txids.append(txid)
2✔
3639
                u = self.transaction(txid)
2✔
3640
            res.append(u)
2✔
3641
        return res
2✔
3642

3643
    def transactions_full(self, network=None, include_new=False, limit=0, offset=0):
2✔
3644
        """
3645
        Get all transactions of this wallet as WalletTransaction objects
3646

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

3649
        :param network: Filter by network name. Leave empty for default network
3650
        :type network: str
3651
        :param include_new: Also include new and incomplete transactions in list. Default is False
3652
        :type include_new: bool
3653
        :param limit: Maximum number of transactions to return. Combine with offset parameter to use as pagination
3654
        :type limit: int
3655
        :param offset: Number of transactions to skip
3656
        :type offset: int
3657

3658
        :return list of WalletTransaction:
3659
        """
3660
        network, _, _ = self._get_account_defaults(network)
2✔
3661
        qr = self.session.query(DbTransaction.txid, DbTransaction.network_name, DbTransaction.status). \
2✔
3662
            filter(DbTransaction.wallet_id == self.wallet_id,
3663
                   DbTransaction.network_name == network)
3664
        if not include_new:
2✔
3665
            qr = qr.filter(or_(DbTransaction.status == 'confirmed', DbTransaction.status == 'unconfirmed'))
2✔
3666
        txs = []
2✔
3667
        if limit:
2✔
3668
            qr = qr.limit(limit)
2✔
3669
        if offset:
2✔
3670
            qr = qr.offset(offset)
2✔
3671
        for tx in qr.all():
2✔
3672
            txs.append(self.transaction(tx[0].hex()))
2✔
3673
        return txs
2✔
3674

3675
    def transactions_export(self, account_id=None, network=None, include_new=False, key_id=None, skip_change=True):
2✔
3676
        """
3677
        Export wallets transactions as list of tuples with the following fields:
3678
            (transaction_date, transaction_hash, in/out, addresses_in, addresses_out, value, value_cumulative, fee)
3679

3680
        :param account_id: Filter by Account ID. Leave empty for default account_id
3681
        :type account_id: int, None
3682
        :param network: Filter by network name. Leave empty for default network
3683
        :type network: str, None
3684
        :param include_new: Also include new and incomplete transactions in list. Default is False
3685
        :type include_new: bool
3686
        :param key_id: Filter by key ID
3687
        :type key_id: int, None
3688
        :param skip_change: Do not include change outputs. Default is True
3689
        :type skip_change: bool
3690

3691
        :return list of tuple:
3692
        """
3693

3694
        txs_tuples = []
2✔
3695
        cumulative_value = 0
2✔
3696
        for t in self.transactions(account_id, network, include_new, key_id):
2✔
3697
            te = t.export(skip_change=skip_change)
2✔
3698

3699
            # When transaction is outgoing deduct fee from cumulative value
3700
            if t.outgoing_tx:
2✔
3701
                cumulative_value -= t.fee
×
3702

3703
            # Loop through all transaction inputs and outputs
3704
            for tei in te:
2✔
3705
                # Create string with  list of inputs addresses for incoming transactions, and outputs addresses
3706
                # for outgoing txs
3707
                addr_list_in = tei[3] if isinstance(tei[3], list) else [tei[3]]
2✔
3708
                addr_list_out = tei[4] if isinstance(tei[4], list) else [tei[4]]
2✔
3709
                cumulative_value += tei[5]
2✔
3710
                txs_tuples.append((tei[0], tei[1], tei[2], addr_list_in, addr_list_out, tei[5], cumulative_value,
2✔
3711
                                   tei[6]))
3712
        return txs_tuples
2✔
3713

3714
    def transaction(self, txid):
2✔
3715
        """
3716
        Get WalletTransaction object for given transaction ID (transaction hash)
3717

3718
        :param txid: Hexadecimal transaction hash
3719
        :type txid: str
3720

3721
        :return WalletTransaction:
3722
        """
3723
        return WalletTransaction.from_txid(self, txid)
2✔
3724

3725
    def transaction_spent(self, txid, output_n):
2✔
3726
        """
3727
        Check if transaction with given transaction ID and output_n is spent and return txid of spent transaction.
3728

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

3731
        :param txid: Hexadecimal transaction hash
3732
        :type txid: str, bytes
3733
        :param output_n: Output n
3734
        :type output_n: int, bytes
3735

3736
        :return str: Transaction ID
3737
        """
3738
        txid = to_bytes(txid)
2✔
3739
        if isinstance(output_n, bytes):
2✔
3740
            output_n = int.from_bytes(output_n, 'big')
2✔
3741
        qr = self.session.query(DbTransactionInput, DbTransaction.confirmations,
2✔
3742
                                 DbTransaction.txid, DbTransaction.status). \
3743
            join(DbTransaction). \
3744
            filter(DbTransaction.wallet_id == self.wallet_id,
3745
                   DbTransactionInput.prev_txid == txid, DbTransactionInput.output_n == output_n).scalar()
3746
        if qr:
2✔
3747
            return qr.transaction.txid.hex()
2✔
3748

3749

3750
    # def update_transactions_from_block(block, network=None):
3751
    #     pass
3752

3753
    def transaction_delete(self, txid):
2✔
3754
        """
3755
        Remove specified transaction from wallet and update related transactions.
3756

3757
        :param txid: Transaction ID of transaction to remove
3758
        :type txid: str
3759

3760
        :return:
3761
        """
3762
        wt = self.transaction(txid)
2✔
3763
        if wt:
2✔
3764
            wt.delete()
2✔
3765
        else:
3766
            raise WalletError("Transaction %s not found in this wallet" % txid)
×
3767

3768
    def transactions_remove_unconfirmed(self, hours_old=0, account_id=None, network=None):
2✔
3769
        """
3770
        Removes all unconfirmed transactions from this wallet and updates related transactions / utxos.
3771

3772
        :param hours_old: Only delete unconfirmed transaction which are x hours old. You can also use decimals, ie: 0.5 for half an hour
3773
        :type hours_old: int, float
3774
        :param account_id: Filter by Account ID. Leave empty for default account_id
3775
        :type account_id: int, None
3776
        :param network: Filter by network name. Leave empty for default network
3777
        :type network: str, None
3778
        :return:
3779
        """
3780
        txs = self.transactions(account_id=account_id, network=network)
2✔
3781
        td = datetime.now(timezone.utc).replace(tzinfo=None) - timedelta(hours=hours_old)
2✔
3782
        for tx in txs:
2✔
3783
            if not tx.confirmations and tx.date < td:
2✔
3784
                self.transaction_delete(tx.txid)
2✔
3785

3786
    def _objects_by_key_id(self, key_id):
2✔
3787
        self.session.expire_all()
2✔
3788
        key = self.session.query(DbKey).filter_by(id=key_id).scalar()
2✔
3789
        if not key:
2✔
3790
            raise WalletError("Key '%s' not found in this wallet" % key_id)
×
3791
        if key.key_type == 'multisig':
2✔
3792
            inp_keys = [HDKey.from_wif(ck.child_key.wif, network=ck.child_key.network_name, compressed=key.compressed)
2✔
3793
                        for ck in key.multisig_children]
3794
        elif key.key_type in ['bip32', 'single']:
2✔
3795
            if not key.wif:
2✔
3796
                raise WalletError("WIF of key is empty cannot create HDKey")
×
3797
            inp_keys = [HDKey.from_wif(key.wif, network=key.network_name, compressed=key.compressed)]
2✔
3798
        else:
3799
            raise WalletError("Input key type %s not supported" % key.key_type)
×
3800
        return inp_keys, key
2✔
3801

3802
    def select_inputs(self, amount, variance=None, input_key_id=None, account_id=None, network=None, min_confirms=1,
2✔
3803
                      max_utxos=None, return_input_obj=True, skip_dust_amounts=True):
3804
        """
3805
        Select available unspent transaction outputs (UTXO's) which can be used as inputs for a transaction for
3806
        the specified amount.
3807

3808
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
3809
        >>> w.select_inputs(50000000)
3810
        [<Input(prev_txid='748799c9047321cb27a6320a827f1f69d767fe889c14bf11f27549638d566fe4', output_n=0, address='16QaHuFkfuebXGcYHmehRXBBX7RG9NbtLg', index_n=0, type='sig_pubkey')>]
3811

3812
        :param amount: Total value of inputs in the smallest denominator (sathosi) to select
3813
        :type amount: int
3814
        :param variance: Allowed difference in total input value. Default is dust amount of selected network. Difference will be added to transaction fee.
3815
        :type variance: int
3816
        :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
3817
        :type input_key_id: int, list
3818
        :param account_id: Account ID
3819
        :type account_id: int
3820
        :param network: Network name. Leave empty for default network
3821
        :type network: str
3822
        :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.
3823
        :type min_confirms: int
3824
        :param max_utxos: Maximum number of UTXO's to use. Set to 1 for optimal privacy. Default is None: No maximum
3825
        :type max_utxos: int
3826
        :param return_input_obj: Return inputs as Input class object. Default is True
3827
        :type return_input_obj: bool
3828
        :param skip_dust_amounts: Do not include small amount to avoid dust inputs
3829
        :type skip_dust_amounts: bool
3830

3831
        :return: List of previous outputs
3832
        :rtype: list of DbTransactionOutput, list of Input
3833
        """
3834

3835
        network, account_id, _ = self._get_account_defaults(network, account_id)
2✔
3836
        dust_amount = Network(network).dust_amount
2✔
3837
        if variance is None:
2✔
3838
            variance = dust_amount
2✔
3839

3840
        utxo_query = self.session.query(DbTransactionOutput).join(DbTransaction).join(DbKey). \
2✔
3841
            filter(DbTransaction.wallet_id == self.wallet_id, DbTransaction.account_id == account_id,
3842
                   DbTransaction.network_name == network, DbKey.public != b'',
3843
                   DbTransactionOutput.spent.is_(False), DbTransaction.confirmations >= min_confirms)
3844
        if input_key_id:
2✔
3845
            if isinstance(input_key_id, int):
2✔
3846
                utxo_query = utxo_query.filter(DbKey.id == input_key_id)
2✔
3847
            else:
3848
                utxo_query = utxo_query.filter(DbKey.id.in_(input_key_id))
×
3849
        if skip_dust_amounts:
2✔
3850
            utxo_query = utxo_query.filter(DbTransactionOutput.value >= dust_amount)
2✔
3851
        utxo_query = utxo_query.order_by(DbTransaction.confirmations.desc())
2✔
3852
        try:
2✔
3853
            utxos = utxo_query.all()
2✔
3854
        except Exception as e:
×
3855
            self.session.close()
×
3856
            logger.warning("Error when querying database, retry: %s" % str(e))
×
3857
            utxos = utxo_query.all()
×
3858

3859
        if not utxos:
2✔
3860
            raise WalletError("Create transaction: No unspent transaction outputs found or no key available for UTXO's")
2✔
3861

3862
        # Try to find one utxo with exact amount
3863
        one_utxo = utxo_query.filter(DbTransactionOutput.spent.is_(False),
2✔
3864
                                     DbTransactionOutput.value >= amount,
3865
                                     DbTransactionOutput.value <= amount + variance).first()
3866
        selected_utxos = []
2✔
3867
        if one_utxo:
2✔
3868
            selected_utxos = [one_utxo]
2✔
3869
        else:
3870
            # Try to find one utxo with higher amount
3871
            one_utxo = utxo_query. \
2✔
3872
                filter(DbTransactionOutput.spent.is_(False), DbTransactionOutput.value >= amount).\
3873
                order_by(DbTransactionOutput.value).first()
3874
            if one_utxo:
2✔
3875
                selected_utxos = [one_utxo]
2✔
3876

3877
        # Otherwise compose of 2 or more lesser outputs
3878
        if not selected_utxos:
2✔
3879
            if max_utxos and max_utxos <= 1:
2✔
3880
                _logger.info("No single UTXO found with requested amount, use higher 'max_utxo' setting to use "
2✔
3881
                             "multiple UTXO's")
3882
                return []
2✔
3883
            lessers = utxo_query. \
2✔
3884
                filter(DbTransactionOutput.spent.is_(False), DbTransactionOutput.value < amount).\
3885
                order_by(DbTransactionOutput.value.desc()).all()
3886
            utxo_values = [l.value for l in lessers]
2✔
3887
            result = []
2✔
3888
            for r in range(2, (max_utxos or len(utxo_values)) + 1):
2✔
3889
                for combo in combinations(utxo_values, r):
2✔
3890
                    if sum(combo) >= amount:
2✔
3891
                        result.append(combo)
2✔
3892
            if result:
2✔
3893
                for value in result[0]:
2✔
3894
                    utxo = [u for u in lessers if u.value == value][0]
2✔
3895
                    selected_utxos.append(utxo)
2✔
3896
                    lessers.remove(utxo)
2✔
3897

3898
        if not return_input_obj:
2✔
3899
            return selected_utxos
2✔
3900
        else:
3901
            inputs = []
2✔
3902
            for utxo in selected_utxos:
2✔
3903
                # amount_total_input += utxo.value
3904
                inp_keys, key = self._objects_by_key_id(utxo.key_id)
2✔
3905
                multisig = False if len(inp_keys) < 2 else True
2✔
3906
                script_type = get_unlocking_script_type(utxo.script_type, multisig=multisig)
2✔
3907
                inputs.append(Input(utxo.transaction.txid, utxo.output_n, keys=inp_keys, script_type=script_type,
2✔
3908
                              sigs_required=self.multisig_n_required, sort=self.sort_keys, address=key.address,
3909
                              compressed=key.compressed, value=utxo.value, network=key.network_name))
3910
            return inputs
2✔
3911

3912
    def transaction_create(self, output_arr, input_arr=None, input_key_id=None, account_id=None, network=None, fee=None,
2✔
3913
                           min_confirms=1, max_utxos=None, locktime=0, number_of_change_outputs=1,
3914
                           random_output_order=True, replace_by_fee=False):
3915
        """
3916
        Create new transaction with specified outputs.
3917

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

3920
        Output array is a list of 1 or more addresses and amounts.
3921

3922
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
3923
        >>> t = w.transaction_create([('1J9GDZMKEr3ZTj8q6pwtMy4Arvt92FDBTb', 200000)])
3924
        >>> t
3925
        <WalletTransaction(input_count=1, output_count=2, status=new, network=bitcoin)>
3926
        >>> t.outputs # doctest:+ELLIPSIS
3927
        [<Output(value=..., address=..., type=p2pkh)>, <Output(value=..., address=..., type=p2pkh)>]
3928

3929
        :param output_arr: List of output as Output objects or tuples with address and amount. Must contain at least one item. Example: [('mxdLD8SAGS9fe2EeCXALDHcdTTbppMHp8N', 5000000)]
3930
        :type output_arr: list of Output, tuple
3931
        :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)]
3932
        :type input_arr: list of Input, tuple
3933
        :param input_key_id: Limit UTXO's search for inputs to this key_id. Only valid if no input array is specified
3934
        :type input_key_id: int
3935
        :param account_id: Account ID
3936
        :type account_id: int
3937
        :param network: Network name. Leave empty for default network
3938
        :type network: str
3939
        :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.
3940
        :type fee: int, str
3941
        :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.
3942
        :type min_confirms: int
3943
        :param max_utxos: Maximum number of UTXO's to use. Set to 1 for optimal privacy. Default is None: No maximum
3944
        :type max_utxos: int
3945
        :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
3946
        :type locktime: int
3947
        :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
3948
        :type number_of_change_outputs: int
3949
        :param random_output_order: Shuffle order of transaction outputs to increase privacy. Default is True
3950
        :type random_output_order: bool
3951
        :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
3952
        :type replace_by_fee: bool
3953

3954
        :return WalletTransaction: object
3955
        """
3956

3957
        if not isinstance(output_arr, list):
2✔
3958
            raise WalletError("Output array must be a list of tuples with address and amount. "
2✔
3959
                              "Use 'send_to' method to send to one address")
3960
        if not network and output_arr:
2✔
3961
            if isinstance(output_arr[0], Output):
2✔
3962
                network = output_arr[0].network.name
×
3963
            elif isinstance(output_arr[0][1], str):
2✔
3964
                network = Value(output_arr[0][1]).network.name
2✔
3965
        network, account_id, acckey = self._get_account_defaults(network, account_id)
2✔
3966

3967
        if input_arr and max_utxos and len(input_arr) > max_utxos:
2✔
3968
            raise WalletError("Input array contains %d UTXO's but max_utxos=%d parameter specified" %
2✔
3969
                              (len(input_arr), max_utxos))
3970

3971
        # Create transaction and add outputs
3972
        amount_total_output = 0
2✔
3973
        transaction = WalletTransaction(hdwallet=self, account_id=account_id, network=network, locktime=locktime,
2✔
3974
                                        replace_by_fee=replace_by_fee)
3975
        transaction.outgoing_tx = True
2✔
3976
        for o in output_arr:
2✔
3977
            if isinstance(o, Output):
2✔
3978
                transaction.outputs.append(o)
2✔
3979
                amount_total_output += o.value
2✔
3980
            else:
3981
                value = value_to_satoshi(o[1], network=transaction.network)
2✔
3982
                amount_total_output += value
2✔
3983
                addr = o[0]
2✔
3984
                if isinstance(addr, WalletKey):
2✔
3985
                    addr = addr.key()
2✔
3986
                transaction.add_output(value, addr, change=False)
2✔
3987

3988
        srv = Service(network=network, wallet_name=self.name, providers=self.providers, cache_uri=self.db_cache_uri,
2✔
3989
                      strict=self.strict)
3990

3991
        if not locktime and self.anti_fee_sniping:
2✔
3992
            srv = Service(network=network, providers=self.providers, cache_uri=self.db_cache_uri, strict=self.strict)
2✔
3993
            blockcount = srv.blockcount()
2✔
3994
            if blockcount:
2✔
3995
                transaction.locktime = blockcount
2✔
3996

3997
        transaction.fee_per_kb = None
2✔
3998
        if isinstance(fee, int):
2✔
3999
            fee_estimate = fee
2✔
4000
        else:
4001
            n_blocks = 3
2✔
4002
            priority = ''
2✔
4003
            if isinstance(fee, str):
2✔
4004
                priority = fee
2✔
4005
            transaction.fee_per_kb = srv.estimatefee(blocks=n_blocks, priority=priority)
2✔
4006
            if not input_arr:
2✔
4007
                fee_estimate = round(transaction.estimate_size(number_of_change_outputs=number_of_change_outputs) /
2✔
4008
                                   1000.0 * transaction.fee_per_kb)
4009
            else:
4010
                fee_estimate = 0
2✔
4011
            if isinstance(fee, str):
2✔
4012
                fee = fee_estimate
2✔
4013

4014
        # Add inputs
4015
        sequence = 0xffffffff
2✔
4016
        if replace_by_fee:
2✔
4017
            sequence = SEQUENCE_REPLACE_BY_FEE
2✔
4018
        elif 0 < transaction.locktime < 0xffffffff:
2✔
4019
            sequence = SEQUENCE_ENABLE_LOCKTIME
2✔
4020
        amount_total_input = 0
2✔
4021
        if input_arr is None:
2✔
4022
            selected_utxos = self.select_inputs(amount_total_output + fee_estimate, transaction.network.dust_amount,
2✔
4023
                                                input_key_id, account_id, network, min_confirms, max_utxos, False)
4024
            if not selected_utxos:
2✔
4025
                raise WalletError("Not enough unspent transaction outputs found")
2✔
4026
            for utxo in selected_utxos:
2✔
4027
                amount_total_input += utxo.value
2✔
4028
                inp_keys, key = self._objects_by_key_id(utxo.key_id)
2✔
4029
                multisig = False if isinstance(inp_keys, list) and len(inp_keys) < 2 else True
2✔
4030
                witness_type = utxo.key.witness_type if utxo.key.witness_type else self.witness_type
2✔
4031
                unlock_script_type = get_unlocking_script_type(utxo.script_type, witness_type,
2✔
4032
                                                               multisig=multisig)
4033
                transaction.add_input(utxo.transaction.txid, utxo.output_n, keys=inp_keys,
2✔
4034
                                      script_type=unlock_script_type, sigs_required=self.multisig_n_required,
4035
                                      sort=self.sort_keys, compressed=key.compressed, value=utxo.value,
4036
                                      address=utxo.key.address, sequence=sequence,
4037
                                      key_path=utxo.key.path, witness_type=witness_type)
4038
                # FIXME: Missing locktime_cltv=locktime_cltv, locktime_csv=locktime_csv (?)
4039
        else:
4040
            for inp in input_arr:
2✔
4041
                locktime_cltv = None
2✔
4042
                locktime_csv = None
2✔
4043
                locking_script = None
2✔
4044
                unlocking_script_type = ''
2✔
4045
                if isinstance(inp, Input):
2✔
4046
                    prev_txid = inp.prev_txid
2✔
4047
                    output_n = inp.output_n
2✔
4048
                    key_id = None
2✔
4049
                    value = inp.value
2✔
4050
                    signatures = inp.signatures
2✔
4051
                    unlocking_script = inp.unlocking_script
2✔
4052
                    locking_script = inp.locking_script
2✔
4053
                    unlocking_script_type = inp.script_type
2✔
4054
                    address = inp.address
2✔
4055
                    sequence = inp.sequence
2✔
4056
                    locktime_cltv = inp.locktime_cltv
2✔
4057
                    locktime_csv = inp.locktime_csv
2✔
4058
                    witness_type = inp.witness_type
2✔
4059
                else:
4060
                    prev_txid = inp[0]
2✔
4061
                    output_n = inp[1]
2✔
4062
                    key_id = None if len(inp) <= 2 else inp[2]
2✔
4063
                    value = 0 if len(inp) <= 3 else inp[3]
2✔
4064
                    signatures = None if len(inp) <= 4 else inp[4]
2✔
4065
                    unlocking_script = b'' if len(inp) <= 5 else inp[5]
2✔
4066
                    address = '' if len(inp) <= 6 else inp[6]
2✔
4067
                    witness_type = self.witness_type
2✔
4068
                # Get key_ids, value from Db if not specified
4069
                if not (key_id and value and unlocking_script_type):
2✔
4070
                    if not isinstance(output_n, TYPE_INT):
2✔
4071
                        output_n = int.from_bytes(output_n, 'big')
2✔
4072
                    inp_utxo = self.session.query(DbTransactionOutput).join(DbTransaction). \
2✔
4073
                        filter(DbTransaction.wallet_id == self.wallet_id,
4074
                               DbTransaction.txid == to_bytes(prev_txid),
4075
                               DbTransactionOutput.output_n == output_n).first()
4076
                    if inp_utxo:
2✔
4077
                        key_id = inp_utxo.key_id
2✔
4078
                        value = inp_utxo.value
2✔
4079
                        address = inp_utxo.key.address
2✔
4080
                        unlocking_script_type = get_unlocking_script_type(inp_utxo.script_type, multisig=self.multisig)
2✔
4081
                        witness_type = inp_utxo.key.witness_type
2✔
4082
                    else:
4083
                        _logger.info("UTXO %s not found in this wallet. Please update UTXO's if this is not an "
2✔
4084
                                     "offline wallet" % to_hexstring(prev_txid))
4085
                        key_id = self.session.query(DbKey.id).\
2✔
4086
                            filter(DbKey.wallet_id == self.wallet_id, DbKey.address == address).scalar()
4087
                        if not key_id:
2✔
4088
                            raise WalletError("UTXO %s and key with address %s not found in this wallet" % (
2✔
4089
                                to_hexstring(prev_txid), address))
4090
                        if not value:
×
4091
                            raise WalletError("Input value is zero for address %s. Import or update UTXO's first "
×
4092
                                              "or import transaction as dictionary" % address)
4093

4094
                amount_total_input += value
2✔
4095
                inp_keys, key = self._objects_by_key_id(key_id)
2✔
4096
                transaction.add_input(prev_txid, output_n, keys=inp_keys, script_type=unlocking_script_type,
2✔
4097
                                      sigs_required=self.multisig_n_required, sort=self.sort_keys,
4098
                                      compressed=key.compressed, value=value, signatures=signatures,
4099
                                      unlocking_script=unlocking_script, address=address,
4100
                                      locking_script=locking_script,
4101
                                      sequence=sequence, locktime_cltv=locktime_cltv, locktime_csv=locktime_csv,
4102
                                      witness_type=witness_type, key_path=key.path)
4103
        # Calculate fees
4104
        transaction.fee = fee
2✔
4105
        fee_per_output = None
2✔
4106
        transaction.size = transaction.estimate_size(number_of_change_outputs=number_of_change_outputs)
2✔
4107
        if fee is None:
2✔
4108
            if not input_arr:
2✔
4109
                if not transaction.fee_per_kb:
2✔
4110
                    transaction.fee_per_kb = srv.estimatefee()
×
4111
                if transaction.fee_per_kb < transaction.network.fee_min:
2✔
4112
                    transaction.fee_per_kb = transaction.network.fee_min
×
4113
                transaction.fee = round((transaction.size / 1000.0) * transaction.fee_per_kb)
2✔
4114
                fee_per_output = round((50 / 1000.0) * transaction.fee_per_kb)
2✔
4115
            else:
4116
                if amount_total_output and amount_total_input:
2✔
4117
                    fee = False
2✔
4118
                else:
4119
                    transaction.fee = 0
×
4120

4121
        if fee is False:
2✔
4122
            transaction.change = 0
2✔
4123
            transaction.fee = round(amount_total_input - amount_total_output)
2✔
4124
        else:
4125
            transaction.change = round(amount_total_input - (amount_total_output + transaction.fee))
2✔
4126

4127
        # Skip change if amount is smaller than the dust limit or estimated fee
4128
        if (fee_per_output and transaction.change < fee_per_output) or transaction.change <= transaction.network.dust_amount:
2✔
4129
            transaction.fee += transaction.change
2✔
4130
            transaction.change = 0
2✔
4131
        if transaction.change < 0:
2✔
4132
            raise WalletError("Total amount of outputs is greater then total amount of inputs")
×
4133
        if transaction.change:
2✔
4134
            min_output_value = transaction.network.dust_amount * 2 + transaction.network.fee_min * 4
2✔
4135
            if transaction.fee and transaction.size:
2✔
4136
                if not transaction.fee_per_kb:
2✔
4137
                    transaction.fee_per_kb = round((transaction.fee * 1000.0) / transaction.vsize)
2✔
4138
                min_output_value = transaction.fee_per_kb + transaction.network.fee_min * 4 + \
2✔
4139
                                   transaction.network.dust_amount
4140

4141
            if number_of_change_outputs == 0:
2✔
4142
                if transaction.change < amount_total_output / 10 or transaction.change < min_output_value * 8:
2✔
4143
                    number_of_change_outputs = 1
×
4144
                elif transaction.change / 10 > amount_total_output:
2✔
4145
                    number_of_change_outputs = random.randint(2, 5)
×
4146
                else:
4147
                    number_of_change_outputs = random.randint(1, 3)
2✔
4148
                    # Prefer 1 and 2 as number of change outputs
4149
                    if number_of_change_outputs == 3:
2✔
4150
                        number_of_change_outputs = random.randint(3, 4)
2✔
4151
                transaction.size = transaction.estimate_size(number_of_change_outputs=number_of_change_outputs)
2✔
4152

4153
            average_change = transaction.change // number_of_change_outputs
2✔
4154
            if number_of_change_outputs > 1 and average_change < min_output_value:
2✔
4155
                raise WalletError("Not enough funds to create multiple change outputs. Try less change outputs "
×
4156
                                  "or lower fees")
4157

4158
            if self.scheme == 'single':
2✔
4159
                change_keys = [self.get_key(account_id, self.witness_type, network, change=1)]
2✔
4160
            else:
4161
                change_keys = self.get_keys(account_id, self.witness_type, network, change=1,
2✔
4162
                                            number_of_keys=number_of_change_outputs)
4163

4164
            if number_of_change_outputs > 1:
2✔
4165
                rand_prop = transaction.change - number_of_change_outputs * min_output_value
2✔
4166
                change_amounts = list(((np.random.dirichlet(np.ones(number_of_change_outputs), size=1)[0] *
2✔
4167
                                        rand_prop) + min_output_value).astype(int))
4168
                # Fix rounding problems / small amount differences
4169
                diffs = transaction.change - sum(change_amounts)
2✔
4170
                for idx, co in enumerate(change_amounts):
2✔
4171
                    if co - diffs > min_output_value:
2✔
4172
                        change_amounts[idx] += change_amounts.index(co) + diffs
2✔
4173
                        break
2✔
4174
            else:
4175
                change_amounts = [transaction.change]
2✔
4176

4177
            for idx, ck in enumerate(change_keys):
2✔
4178
                on = transaction.add_output(change_amounts[idx], ck.address, encoding=self.encoding, change=True)
2✔
4179
                transaction.outputs[on].key_id = ck.key_id
2✔
4180

4181
        # Shuffle output order to increase privacy
4182
        if random_output_order:
2✔
4183
            transaction.shuffle()
2✔
4184

4185
        # Check tx values
4186
        transaction.input_total = sum([i.value for i in transaction.inputs])
2✔
4187
        transaction.output_total = sum([o.value for o in transaction.outputs])
2✔
4188
        if transaction.input_total != transaction.fee + transaction.output_total:
2✔
4189
            raise WalletError("Sum of inputs values is not equal to sum of outputs values plus fees")
×
4190

4191
        transaction.txid = transaction.signature_hash()[::-1].hex()
2✔
4192
        if not transaction.fee_per_kb:
2✔
4193
            transaction.fee_per_kb = round((transaction.fee * 1000.0) / transaction.vsize)
2✔
4194
        if transaction.fee_per_kb < transaction.network.fee_min:
2✔
4195
            raise WalletError("Fee per kB of %d is lower then minimal network fee of %d" %
2✔
4196
                              (transaction.fee_per_kb, transaction.network.fee_min))
4197
        elif transaction.fee_per_kb > transaction.network.fee_max:
2✔
4198
            raise WalletError("Fee per kB of %d is higher then maximum network fee of %d" %
2✔
4199
                              (transaction.fee_per_kb, transaction.network.fee_max))
4200

4201
        return transaction
2✔
4202

4203
    def transaction_import(self, t):
2✔
4204
        """
4205
        Import a Transaction into this wallet. Link inputs to wallet keys if possible and return WalletTransaction
4206
        object. Only imports Transaction objects or dictionaries, use
4207
        :func:`transaction_import_raw` method to import a raw transaction.
4208

4209
        :param t: A Transaction object or dictionary
4210
        :type t: Transaction, dict
4211

4212
        :return WalletTransaction:
4213

4214
        """
4215
        if isinstance(t, Transaction):
2✔
4216
            rt = self.transaction_create(t.outputs, t.inputs, fee=t.fee, network=t.network.name,
2✔
4217
                                         random_output_order=False)
4218
            rt.block_height = t.block_height
2✔
4219
            rt.confirmations = t.confirmations
2✔
4220
            rt.witness_type = t.witness_type
2✔
4221
            rt.date = t.date
2✔
4222
            rt.txid = t.txid
2✔
4223
            rt.txhash = t.txhash
2✔
4224
            rt.locktime = t.locktime
2✔
4225
            rt.version = t.version
2✔
4226
            rt.version_int = t.version_int
2✔
4227
            rt.block_hash = t.block_hash
2✔
4228
            rt.rawtx = t.rawtx
2✔
4229
            rt.coinbase = t.coinbase
2✔
4230
            rt.flag = t.flag
2✔
4231
            rt.size = t.size
2✔
4232
            if not t.size:
2✔
4233
                rt.size = len(t.raw())
×
4234
            rt.vsize = t.vsize
2✔
4235
            if not t.vsize:
2✔
4236
                rt.vsize = rt.size
×
4237
            rt.fee_per_kb = int((rt.fee / float(rt.vsize)) * 1000)
2✔
4238
        elif isinstance(t, dict):
2✔
4239
            input_arr = []
2✔
4240
            for i in t['inputs']:
2✔
4241
                signatures = [bytes.fromhex(sig) for sig in i['signatures']]
2✔
4242
                script = b'' if 'script' not in i else i['script']
2✔
4243
                address = '' if 'address' not in i else i['address']
2✔
4244
                input_arr.append((i['prev_txid'], i['output_n'], None, int(i['value']), signatures, script,
2✔
4245
                                  address))
4246
            output_arr = []
2✔
4247
            for o in t['outputs']:
2✔
4248
                output_arr.append((o['address'], int(o['value'])))
2✔
4249
            rt = self.transaction_create(output_arr, input_arr, fee=t['fee'], network=t['network'],
2✔
4250
                                         random_output_order=False)
4251
            rt.block_height = t['block_height']
2✔
4252
            rt.confirmations = t['confirmations']
2✔
4253
            rt.witness_type = t['witness_type']
2✔
4254
            rt.date = t['date']
2✔
4255
            rt.txid = t['txid']
2✔
4256
            rt.txhash = t['txhash']
2✔
4257
            rt.locktime = t['locktime']
2✔
4258
            rt.version = t['version'].to_bytes(4, 'big')
2✔
4259
            rt.version_int = t['version']
2✔
4260
            rt.block_hash = t['block_hash']
2✔
4261
            rt.rawtx = t['raw']
2✔
4262
            rt.coinbase = t['coinbase']
2✔
4263
            rt.flag = t['flag']
2✔
4264
            rt.size = t['size']
2✔
4265
            if not t['size']:
2✔
4266
                rt.size = len(rt.raw())
×
4267
            rt.vsize = t['vsize']
2✔
4268
            if not rt.vsize:
2✔
4269
                rt.vsize = rt.size
×
4270
            rt.fee_per_kb = int((rt.fee / float(rt.vsize)) * 1000)
2✔
4271
        else:
4272
            raise WalletError("Import transaction must be of type Transaction or dict")
×
4273
        rt.verify()
2✔
4274
        return rt
2✔
4275

4276
    def transaction_import_raw(self, rawtx, network=None):
2✔
4277
        """
4278
        Import a raw transaction. Link inputs to wallet keys if possible and return WalletTransaction object
4279

4280
        :param rawtx: Raw transaction
4281
        :type rawtx: str, bytes
4282
        :param network: Network name. Leave empty for default network
4283
        :type network: str
4284

4285
        :return WalletTransaction:
4286
        """
4287

4288
        if network is None:
2✔
4289
            network = self.network.name
2✔
4290
        if isinstance(rawtx, str):
2✔
4291
            rawtx = bytes.fromhex(rawtx)
×
4292
        t_import = Transaction.parse_bytes(rawtx, network=network)
2✔
4293
        rt = self.transaction_create(t_import.outputs, t_import.inputs, network=network, locktime=t_import.locktime,
2✔
4294
                                     random_output_order=False)
4295
        rt.version_int = t_import.version_int
2✔
4296
        rt.version = t_import.version
2✔
4297
        rt.verify()
2✔
4298
        rt.size = len(rawtx)
2✔
4299
        rt.calc_weight_units()
2✔
4300
        rt.fee_per_kb = int((rt.fee / float(rt.vsize)) * 1000)
2✔
4301
        return rt
2✔
4302

4303
    def send(self, output_arr, input_arr=None, input_key_id=None, account_id=None, network=None, fee=None,
2✔
4304
             min_confirms=1, priv_keys=None, max_utxos=None, locktime=0, broadcast=False, number_of_change_outputs=1,
4305
             random_output_order=True, replace_by_fee=False):
4306
        """
4307
        Create a new transaction with specified outputs and push it to the network.
4308
        Inputs can be specified but if not provided they will be selected from wallets utxo's
4309
        Output array is a list of 1 or more addresses and amounts.
4310

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

4313
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
4314
        >>> t = w.send([('1J9GDZMKEr3ZTj8q6pwtMy4Arvt92FDBTb', 200000)])
4315
        >>> t
4316
        <WalletTransaction(input_count=1, output_count=2, status=new, network=bitcoin)>
4317
        >>> t.outputs # doctest:+ELLIPSIS
4318
        [<Output(value=..., address=..., type=p2pkh)>, <Output(value=..., address=..., type=p2pkh)>]
4319

4320
        :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
4321
        :type output_arr: list
4322
        :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)]
4323
        :type input_arr: list
4324
        :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
4325
        :type input_key_id: int, list
4326
        :param account_id: Account ID
4327
        :type account_id: int
4328
        :param network: Network name. Leave empty for default network
4329
        :type network: str
4330
        :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.
4331
        :type fee: int, str
4332
        :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.
4333
        :type min_confirms: int
4334
        :param priv_keys: Specify extra private key if not available in this wallet
4335
        :type priv_keys: HDKey, list
4336
        :param max_utxos: Maximum number of UTXO's to use. Set to 1 for optimal privacy. Default is None: No maximum
4337
        :type max_utxos: int
4338
        :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
4339
        :type locktime: int
4340
        :param broadcast: Just return the transaction object and do not send it when broadcast = False. Default is False
4341
        :type broadcast: bool
4342
        :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
4343
        :type number_of_change_outputs: int
4344
        :param random_output_order: Shuffle order of transaction outputs to increase privacy. Default is True
4345
        :type random_output_order: bool
4346
        :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
4347
        :type replace_by_fee: bool
4348

4349
        :return WalletTransaction:
4350
        """
4351

4352
        if input_arr and max_utxos and len(input_arr) > max_utxos:
2✔
4353
            raise WalletError("Input array contains %d UTXO's but max_utxos=%d parameter specified" %
×
4354
                              (len(input_arr), max_utxos))
4355

4356
        transaction = self.transaction_create(output_arr, input_arr, input_key_id, account_id, network, fee,
2✔
4357
                                              min_confirms, max_utxos, locktime, number_of_change_outputs,
4358
                                              random_output_order, replace_by_fee)
4359
        transaction.sign(priv_keys)
2✔
4360
        # Calculate exact fees and update change output if necessary
4361
        if fee is None and transaction.fee_per_kb and transaction.change:
2✔
4362
            fee_exact = transaction.calculate_fee()
2✔
4363
            # Recreate transaction if fee estimation more than 10% off
4364
            if fee_exact != self.network.fee_min and fee_exact != self.network.fee_max and \
2✔
4365
                    fee_exact and abs((float(transaction.fee) - float(fee_exact)) / float(fee_exact)) > 0.10:
4366
                _logger.info("Transaction fee not correctly estimated (est.: %d, real: %d). "
×
4367
                             "Recreate transaction with correct fee" % (transaction.fee, fee_exact))
4368
                transaction = self.transaction_create(output_arr, input_arr, input_key_id, account_id, network,
×
4369
                                                      fee_exact, min_confirms, max_utxos, locktime,
4370
                                                      number_of_change_outputs, random_output_order,
4371
                                                      replace_by_fee)
4372
                transaction.sign(priv_keys)
×
4373

4374
        transaction.rawtx = transaction.raw()
2✔
4375
        transaction.size = len(transaction.rawtx)
2✔
4376
        transaction.calc_weight_units()
2✔
4377
        transaction.fee_per_kb = round(float(transaction.fee) / float(transaction.vsize) * 1000)
2✔
4378
        transaction.txid = transaction.signature_hash()[::-1].hex()
2✔
4379
        transaction.send(broadcast)
2✔
4380
        return transaction
2✔
4381

4382
    def send_to(self, to_address, amount, input_key_id=None, account_id=None, network=None, fee=None, min_confirms=1,
2✔
4383
                priv_keys=None, locktime=0, broadcast=False, number_of_change_outputs=1, random_output_order=True,
4384
                replace_by_fee=False):
4385
        """
4386
        Create transaction and send it with default Service objects :func:`services.sendrawtransaction` method.
4387

4388
        Wrapper for wallet :func:`send` method.
4389

4390
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
4391
        >>> t = w.send_to('1J9GDZMKEr3ZTj8q6pwtMy4Arvt92FDBTb', 200000)
4392
        >>> t
4393
        <WalletTransaction(input_count=1, output_count=2, status=new, network=bitcoin)>
4394
        >>> t.outputs # doctest:+ELLIPSIS
4395
        [<Output(value=..., address=..., type=p2pkh)>, <Output(value=..., address=..., type=p2pkh)>]
4396

4397
        :param to_address: Single output address as string Address object, HDKey object or WalletKey object
4398
        :type to_address: str, Address, HDKey, WalletKey
4399
        :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
4400
        :type amount: int, str, Value
4401
        :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
4402
        :type input_key_id: int, list
4403
        :param account_id: Account ID, default is last used
4404
        :type account_id: int
4405
        :param network: Network name. Leave empty for default network
4406
        :type network: str
4407
        :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.
4408
        :type fee: int, str
4409
        :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.
4410
        :type min_confirms: int
4411
        :param priv_keys: Specify extra private key if not available in this wallet
4412
        :type priv_keys: HDKey, list
4413
        :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
4414
        :type locktime: int
4415
        :param broadcast: Just return the transaction object and do not send it when broadcast = False. Default is False
4416
        :type broadcast: bool
4417
        :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
4418
        :type number_of_change_outputs: int
4419
        :param random_output_order: Shuffle order of transaction outputs to increase privacy. Default is True
4420
        :type random_output_order: bool
4421
        :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
4422
        :type replace_by_fee: bool
4423

4424
        :return WalletTransaction:
4425
        """
4426

4427
        outputs = [(to_address, amount)]
2✔
4428
        return self.send(outputs, input_key_id=input_key_id, account_id=account_id, network=network, fee=fee,
2✔
4429
                         min_confirms=min_confirms, priv_keys=priv_keys, locktime=locktime, broadcast=broadcast,
4430
                         number_of_change_outputs=number_of_change_outputs, random_output_order=random_output_order,
4431
                         replace_by_fee=replace_by_fee)
4432

4433
    def sweep(self, to_address, account_id=None, input_key_id=None, network=None, max_utxos=999, min_confirms=1,
2✔
4434
              fee_per_kb=None, fee=None, locktime=0, broadcast=False, replace_by_fee=False):
4435
        """
4436
        Sweep all unspent transaction outputs (UTXO's) and send them to one or more output addresses.
4437

4438
        Wrapper for the :func:`send` method.
4439

4440
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
4441
        >>> t = w.sweep('1J9GDZMKEr3ZTj8q6pwtMy4Arvt92FDBTb')
4442
        >>> t
4443
        <WalletTransaction(input_count=1, output_count=1, status=new, network=bitcoin)>
4444
        >>> t.outputs # doctest:+ELLIPSIS
4445
        [<Output(value=..., address=1J9GDZMKEr3ZTj8q6pwtMy4Arvt92FDBTb, type=p2pkh)>]
4446

4447
        Output to multiple addresses
4448

4449
        >>> to_list = [('1J9GDZMKEr3ZTj8q6pwtMy4Arvt92FDBTb', 100000), (w.get_key(), 0)]
4450
        >>> w.sweep(to_list)
4451
        <WalletTransaction(input_count=1, output_count=2, status=new, network=bitcoin)>
4452

4453
        :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
4454
        :type to_address: str, list
4455
        :param account_id: Wallet's account ID
4456
        :type account_id: int
4457
        :param input_key_id: Limit sweep to UTXO's with this key ID or list of key IDs
4458
        :type input_key_id: int, list
4459
        :param network: Network name. Leave empty for default network
4460
        :type network: str
4461
        :param max_utxos: Limit maximum number of outputs to use. Default is 999
4462
        :type max_utxos: int
4463
        :param min_confirms: Minimal confirmations needed to include utxo
4464
        :type min_confirms: int
4465
        :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
4466
        :type fee_per_kb: int
4467
        :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.
4468
        :type fee: int, str
4469
        :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
4470
        :type locktime: int
4471
        :param broadcast: Just return the transaction object and do not send it when broadcast = False. Default is False
4472
        :type broadcast: bool
4473
        :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
4474
        :type replace_by_fee: bool
4475

4476
        :return WalletTransaction:
4477
        """
4478

4479
        network, account_id, acckey = self._get_account_defaults(network, account_id)
2✔
4480

4481
        utxos = self.utxos(account_id=account_id, network=network, min_confirms=min_confirms, key_id=input_key_id)
2✔
4482
        utxos = utxos[0:max_utxos]
2✔
4483
        input_arr = []
2✔
4484
        total_amount = 0
2✔
4485

4486
        if not utxos:
2✔
4487
            raise WalletError("Cannot sweep wallet, no UTXO's found")
2✔
4488
        for utxo in utxos:
2✔
4489
            # Skip dust transactions to avoid forced address reuse
4490
            if utxo['value'] <= self.network.dust_amount:
2✔
4491
                continue
2✔
4492
            input_arr.append((utxo['txid'], utxo['output_n'], utxo['key_id'], utxo['value']))
2✔
4493
            total_amount += utxo['value']
2✔
4494
        srv = Service(network=network, wallet_name=self.name, providers=self.providers, cache_uri=self.db_cache_uri,
2✔
4495
                      strict=self.strict)
4496

4497
        fee_modifier = 1 if self.witness_type == 'legacy' else 0.6
2✔
4498
        if isinstance(fee, str):
2✔
4499
            fee_per_kb = srv.estimatefee(priority=fee)
2✔
4500
            fee = None
2✔
4501
        if not fee:
2✔
4502
            if fee_per_kb is None:
2✔
4503
                fee_per_kb = srv.estimatefee()
2✔
4504
            n_outputs = 1 if not isinstance(to_address, list) else len(to_address)
2✔
4505
            tr_size = 125 + (len(input_arr) * (77 + self.multisig_n_required * 72)) + n_outputs * 30
2✔
4506
            fee = int(100 + ((tr_size / 1000.0) * fee_per_kb * fee_modifier))
2✔
4507

4508
        if total_amount - fee <= self.network.dust_amount:
2✔
4509
            raise WalletError("Amount to send is smaller then dust amount: %s" % (total_amount - fee))
2✔
4510

4511
        if isinstance(to_address, str):
2✔
4512
            to_list = [(to_address, total_amount - fee)]
2✔
4513
        else:
4514
            to_list = []
2✔
4515
            for o in to_address:
2✔
4516
                if o[1] == 0:
2✔
4517
                    o_amount = total_amount - sum([x[1] for x in to_list]) - fee
2✔
4518
                    if o_amount > 0:
2✔
4519
                        to_list.append((o[0], o_amount))
2✔
4520
                else:
4521
                    to_list.append(o)
2✔
4522

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

4527
        return self.send(to_list, input_arr, network=network, fee=fee, min_confirms=min_confirms, locktime=locktime,
2✔
4528
                         broadcast=broadcast, replace_by_fee=replace_by_fee)
4529

4530
    def wif(self, is_private=False, account_id=0):
2✔
4531
        """
4532
        Return Wallet Import Format string for master private or public key which can be used to import key and
4533
        recreate wallet in other software.
4534

4535
        A list of keys will be exported for a multisig wallet.
4536

4537
        :param is_private: Export public or private key, default is False
4538
        :type is_private: bool
4539
        :param account_id: Account ID of key to export
4540
        :type account_id: bool
4541

4542
        :return list, str:
4543
        """
4544
        if not self.multisig or not self.cosigner:
2✔
4545
            if is_private and self.main_key:
2✔
4546
                return self.main_key.wif
2✔
4547
            else:
4548
                return self.public_master(account_id=account_id).key().\
2✔
4549
                    wif(is_private=is_private, witness_type=self.witness_type, multisig=self.multisig)
4550
        else:
4551
            wiflist = []
2✔
4552
            for cs in self.cosigner:
2✔
4553
                wiflist.append(cs.wif(is_private=is_private))
2✔
4554
            return wiflist
2✔
4555

4556
    def public_master(self, account_id=None, name=None, as_private=False, witness_type=None, network=None):
2✔
4557
        """
4558
        Return public master key(s) for this wallet. Use to import in other wallets to sign transactions or create keys.
4559

4560
        For a multisig wallet all public master keys are return as list.
4561

4562
        Returns private key information if available and as_private is True is specified
4563

4564
        >>> w = Wallet('bitcoinlib_legacy_wallet_test')
4565
        >>> w.public_master().wif
4566
        'xpub6D2qEr8Z8WYKKns2xZYyyvvRviPh1NKt1kfHwwfiTxJwj7peReEJt3iXoWWsr8tXWTsejDjMfAezM53KVFVkSZzA5i2pNy3otprtYUvh4u1'
4567

4568
        :param account_id: Account ID of key to export
4569
        :type account_id: int
4570
        :param name: Optional name for account key
4571
        :type name: str
4572
        :param as_private: Export public or private key, default is False
4573
        :type as_private: bool
4574
        :param witness_type: Witness type, leave empty for default witness_type
4575
        :type witness_type: str
4576
        :param network: Network name. Leave empty for default network
4577
        :type network: str
4578

4579
        :return list of WalletKey, WalletKey:
4580
        """
4581
        if self.main_key and self.main_key.key_type == 'single':
2✔
4582
            key = self.main_key
2✔
4583
            return key if as_private else key.public()
2✔
4584
        elif not self.cosigner:
2✔
4585
            witness_type = witness_type if witness_type else self.witness_type
2✔
4586
            depth = -self.key_depth + self.depth_public_master
2✔
4587
            key = self.key_for_path([], depth, name=name, account_id=account_id, network=network,
2✔
4588
                                    cosigner_id=self.cosigner_id, witness_type=witness_type)
4589
            return key if as_private else key.public()
2✔
4590
        else:
4591
            pm_list = []
2✔
4592
            for cs in self.cosigner:
2✔
4593
                pm_list.append(cs.public_master(account_id, name, as_private, network))
2✔
4594
            return pm_list
2✔
4595

4596
    def transaction_load(self, txid=None, filename=None):
2✔
4597
        """
4598
        Load transaction object from file which has been stored with the :func:`Transaction.save` method.
4599

4600
        Specify transaction ID or filename.
4601

4602
        :param txid: Transaction ID. Transaction object will be read from .bitcoinlib datadir
4603
        :type txid: str
4604
        :param filename: Name of transaction object file
4605
        :type filename: str
4606

4607
        :return Transaction:
4608
        """
4609
        if not filename and not txid:
2✔
4610
            raise WalletError("Please supply filename or txid")
×
4611
        elif not filename and txid:
2✔
4612
            p = Path(BCL_DATA_DIR, '%s.tx' % txid)
2✔
4613
        else:
4614
            p = Path(filename)
2✔
4615
            if not p.parent or str(p.parent) == '.':
2✔
4616
                p = Path(BCL_DATA_DIR, filename)
2✔
4617
        f = p.open('rb')
2✔
4618
        t = pickle.load(f)
2✔
4619
        f.close()
2✔
4620
        return self.transaction_import(t)
2✔
4621

4622
    def sign_message(self, message, key_term=None, use_rfc6979=True, k=None, hash_type=SIGHASH_ALL,
2✔
4623
                     force_canonical=False):
4624
        """
4625
        Sign message with this wallet and provided key. If no key ID is provided the message will be signed with
4626
        first available key.
4627

4628
        :param message: Message to be signed. Must be unhashed and in bytes format.
4629
        :type message: bytes, hexstring
4630
        :param key_term: Key ID or address of signing key. Search term can be key ID, key address, key WIF or key name
4631
        :type key_term: int, str
4632
        :param use_rfc6979: Use deterministic value for k nonce to derive k from txid/message according to RFC6979 standard. Default is True, set to False to use random k
4633
        :type use_rfc6979: bool
4634
        :param k: Provide own k. Only use for testing or if you know what you are doing. Providing wrong value for k can result in leaking your private key!
4635
        :type k: int
4636
        :param hash_type: Specific hash type, default is SIGHASH_ALL
4637
        :type hash_type: int
4638
        :param force_canonical: Some wallets require a canonical s value, so you could set this to True
4639
        :type force_canonical: bool
4640

4641
        :return Signature:
4642
        """
4643
        if key_term:
2✔
4644
            wk = self.key(key_term)
×
4645
        else:
4646
            wk = self.get_key()
2✔
4647

4648
        return wk.sign_message(message, use_rfc6979, k, hash_type, force_canonical)
2✔
4649

4650
    def verify_message(self, message, signature, key_term=None):
2✔
4651
        """
4652
        Verify if provided message is signed by provided signature and matches a public key from this wallet.
4653

4654
        If no key_term is provided it will check the signature against all known keys for this wallet. This can be
4655
        slow for larger wallets.
4656

4657
        :param message: Message to verify. Must be unhashed and in bytes format.
4658
        :type message: bytes, hexstring
4659
        :param signature: signature as Signature object
4660
        :type signature: Signature
4661
        :param key_term: Key ID or address of verifying key. Search term can be key ID, key address, key WIF or key name
4662
        :type key_term: int, str
4663

4664
        :return bool:
4665
        """
4666
        if key_term:
2✔
4667
            wk = self.key(key_term)
×
4668
            return wk.verify_message(message, signature)
×
4669
        else:
4670
            db_wks = self.keys_addresses()
2✔
4671
            for db_wk in db_wks:
2✔
4672
                wk = WalletKey(key_id=db_wk.id, session=self.session)
2✔
4673
                if wk.verify_message(message, signature):
2✔
4674
                    return True
2✔
4675
        return False
×
4676

4677
    def info(self, detail=3):
2✔
4678
        """
4679
        Prints wallet information to standard output
4680

4681
        :param detail: Level of detail to show. Specify a number between 0 and 5, with 0 low detail and 5 highest detail
4682
        :type detail: int
4683
        """
4684

4685
        print("=== WALLET ===")
2✔
4686
        print(f" ID                             {self.wallet_id}")
2✔
4687
        print(f" Name                           {self.name}")
2✔
4688
        print(f" Owner                          {self.owner}")
2✔
4689
        print(f" Scheme                         {self.scheme}")
2✔
4690
        print(f" Multisig                       {self.multisig}")
2✔
4691
        if self.multisig:
2✔
4692
            print(f" Multisig Wallet IDs            {str([w.wallet_id for w in self.cosigner]).strip('[]')}")
2✔
4693
            print(f" Cosigner ID                    {self.cosigner_id}")
2✔
4694
        print(f" Witness type                   {self.witness_type}")
2✔
4695
        print(f" Main network                   {self.network.name}")
2✔
4696
        print(f" Latest update                  {self.last_updated}")
2✔
4697

4698
        if self.multisig:
2✔
4699
            print("\n= Multisig Public Master Keys =")
2✔
4700
            for cs in self.cosigner:
2✔
4701
                print(f'{cs.cosigner_id:>5} {cs.main_key.key_id:>3} {cs.wif(is_private=False):<70} {cs.scheme:<6} '
2✔
4702
                      f'{"main" if cs.main_key.is_private else "cosigner":>8}'
4703
                      f'{"*" if cs.cosigner_id == self.cosigner_id else ""}')
4704
            print("For main keys a private master key is available in this wallet to sign transactions. "
2✔
4705
                  "* cosigner key for this wallet")
4706

4707
        if detail and self.main_key:
2✔
4708
            print("\n= Wallet Master Key =")
2✔
4709
            print(f" ID                             {self.main_key_id}")
2✔
4710
            print(f" Private                        {self.main_key.is_private}")
2✔
4711
            print(f" Depth                          {self.main_key.depth}")
2✔
4712

4713
        balances = self._balance_update()
2✔
4714
        if detail > 1:
2✔
4715
            for nw in self.networks():
2✔
4716
                print(f"\n- NETWORK: {nw.name} -")
2✔
4717
                print(f"- - Keys")
2✔
4718
                if detail < 4:
2✔
4719
                    ds = [self.key_depth]
2✔
4720
                elif detail < 5:
2✔
4721
                    if self.purpose == 45:
×
4722
                        ds = [0, self.key_depth]
×
4723
                    else:
4724
                        ds = [0, self.depth_public_master, self.key_depth]
×
4725
                else:
4726
                    ds = range(8)
2✔
4727
                for d in ds:
2✔
4728
                    is_active = True
2✔
4729
                    if detail > 3:
2✔
4730
                        is_active = False
2✔
4731
                    for key in self.keys(depth=d, network=nw.name, is_active=is_active):
2✔
4732
                        print(f"{str(key.id):>5} {key.path:<28} {key.address:<45} {key.name:<25} "
2✔
4733
                              f"{Value.from_satoshi(key.balance, network=nw).str_unit(currency_repr='symbol'):>25}")
4734

4735
                if detail > 2:
2✔
4736
                    include_new = False
2✔
4737
                    if detail > 3:
2✔
4738
                        include_new = True
2✔
4739
                    accounts = self.accounts(network=nw.name)
2✔
4740
                    if not accounts:
2✔
4741
                        accounts = [0]
×
4742
                    for account_id in accounts:
2✔
4743
                        txs = self.transactions(include_new=include_new, account_id=account_id, network=nw.name,
2✔
4744
                                                as_dict=True)
4745
                        print("\n- - Transactions Account %d (%d)" % (account_id, len(txs)))
2✔
4746
                        for tx in txs:
2✔
4747
                            spent = " "
2✔
4748
                            address = tx['address']
2✔
4749
                            if not tx['address']:
2✔
4750
                                address = 'nulldata'
×
4751
                            elif 'spent' in tx and tx['spent'] is False:
2✔
4752
                                spent = "U"
2✔
4753
                            status = ""
2✔
4754
                            if tx['status'] not in ['confirmed', 'unconfirmed']:
2✔
4755
                                status = tx['status']
×
4756
                            print(f"{tx['txid']:>64} {address:>43} {tx['confirmations']:>8}"
2✔
4757
                                  f"{Value.from_satoshi(tx['value'], network=nw).str_unit(currency_repr='symbol'):>21}"
4758
                                  f" {spent} {status}")
4759

4760
        print("\n= Balance Totals (includes unconfirmed) =")
2✔
4761
        for na_balance in balances:
2✔
4762
            account_str = f"(Account {na_balance['account_id']})"
2✔
4763
            value_str = Value.from_satoshi(na_balance['balance'],
2✔
4764
                                           network=na_balance['network']).str_unit(currency_repr='symbol')
4765
            print(f"{na_balance['network']:<20} {account_str:<20} {value_str:>20}")
2✔
4766
        print("\n")
2✔
4767

4768
    def as_dict(self, include_private=False):
2✔
4769
        """
4770
        Return wallet information in dictionary format
4771

4772
        :param include_private: Include private key information in dictionary
4773
        :type include_private: bool
4774

4775
        :return dict:
4776
        """
4777

4778
        keys = []
2✔
4779
        transactions = []
2✔
4780
        for netw in self.networks():
2✔
4781
            for key in self.keys(network=netw.name, include_private=include_private, as_dict=True):
2✔
4782
                keys.append(key)
2✔
4783

4784
            if self.multisig:
2✔
4785
                for t in self.transactions(include_new=True, account_id=0, network=netw.name):
2✔
4786
                    transactions.append(t.as_dict())
×
4787
            else:
4788
                accounts = self.accounts(network=netw.name)
2✔
4789
                if not accounts:
2✔
4790
                    accounts = [0]
×
4791
                for account_id in accounts:
2✔
4792
                    for t in self.transactions(include_new=True, account_id=account_id, network=netw.name):
2✔
4793
                        transactions.append(t.as_dict())
×
4794

4795
        return {
2✔
4796
            'wallet_id': self.wallet_id,
4797
            'name': self.name,
4798
            'owner': self._owner,
4799
            'scheme': self.scheme,
4800
            'witness_type': self.witness_type,
4801
            'main_network': self.network.name,
4802
            'main_balance': self.balance(),
4803
            'main_balance_str': self.balance(as_string=True),
4804
            'balances': self._balances,
4805
            'default_account_id': self.default_account_id,
4806
            'multisig_n_required': self.multisig_n_required,
4807
            'cosigner_wallet_ids': [w.wallet_id for w in self.cosigner],
4808
            'cosigner_public_masters': [w.public_master().key().wif() for w in self.cosigner],
4809
            'sort_keys': self.sort_keys,
4810
            'main_key_id': self.main_key_id,
4811
            'encoding': self.encoding,
4812
            'keys': keys,
4813
            'transactions': transactions,
4814
        }
4815

4816
    def as_json(self, include_private=False):
2✔
4817
        """
4818
        Get current key as json formatted string
4819

4820
        :param include_private: Include private key information in JSON
4821
        :type include_private: bool
4822

4823
        :return str:
4824
        """
4825
        adict = self.as_dict(include_private=include_private)
2✔
4826
        return json.dumps(adict, indent=4, default=str)
2✔
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