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

handshake-org / hsd / 12791301410

15 Jan 2025 03:12PM UTC coverage: 71.261% (+0.02%) from 71.24%
12791301410

push

github

nodech
Merge PR #918 from 'nodech/wallet-doc-updates'

8059 of 13158 branches covered (61.25%)

Branch coverage included in aggregate %.

91 of 94 new or added lines in 6 files covered. (96.81%)

8 existing lines in 4 files now uncovered.

25735 of 34265 relevant lines covered (75.11%)

34481.61 hits per line

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

80.81
/lib/wallet/wallet.js
1
/*!
2
 * wallet.js - wallet object for hsd
3
 * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License).
4
 * https://github.com/handshake-org/hsd
5
 */
6

7
'use strict';
8

9
const assert = require('bsert');
1✔
10
const EventEmitter = require('events');
1✔
11
const {Lock} = require('bmutex');
1✔
12
const base58 = require('bcrypto/lib/encoding/base58');
1✔
13
const bio = require('bufio');
1✔
14
const blake2b = require('bcrypto/lib/blake2b');
1✔
15
const cleanse = require('bcrypto/lib/cleanse');
1✔
16
const TXDB = require('./txdb');
1✔
17
const Path = require('./path');
1✔
18
const common = require('./common');
1✔
19
const Address = require('../primitives/address');
1✔
20
const MTX = require('../primitives/mtx');
1✔
21
const Script = require('../script/script');
1✔
22
const CoinView = require('../coins/coinview');
1✔
23
const WalletCoinView = require('./walletcoinview');
1✔
24
const WalletKey = require('./walletkey');
1✔
25
const HDPrivateKey = require('../hd/private');
1✔
26
const HDPublicKey = require('../hd/public');
1✔
27
const Mnemonic = require('../hd/mnemonic');
1✔
28
const HD = require('../hd/hd');
1✔
29
const Output = require('../primitives/output');
1✔
30
const Account = require('./account');
1✔
31
const MasterKey = require('./masterkey');
1✔
32
const policy = require('../protocol/policy');
1✔
33
const consensus = require('../protocol/consensus');
1✔
34
const rules = require('../covenants/rules');
1✔
35
const {Resource} = require('../dns/resource');
1✔
36
const Claim = require('../primitives/claim');
1✔
37
const reserved = require('../covenants/reserved');
1✔
38
const {ownership} = require('../covenants/ownership');
1✔
39
const {states} = require('../covenants/namestate');
1✔
40
const {types} = rules;
1✔
41
const {BufferSet} = require('buffer-map');
1✔
42
const Coin = require('../primitives/coin');
1✔
43
const Outpoint = require('../primitives/outpoint');
1✔
44

45
/** @typedef {import('bdb').DB} DB */
46
/** @typedef {ReturnType<DB['batch']>} Batch */
47
/** @typedef {import('../types').Base58String} Base58String */
48
/** @typedef {import('../types').Hash} Hash */
49
/** @typedef {import('../types').Amount} Amount */
50
/** @typedef {import('../types').Rate} Rate */
51
/** @typedef {import('../covenants/namestate')} NameState */
52
/** @typedef {import('../primitives/tx')} TX */
53
/** @typedef {import('./records').BlockMeta} BlockMeta */
54
/** @typedef {import('./records').TXRecord} TXRecord */
55
/** @typedef {import('./txdb').BlockExtraInfo} BlockExtraInfo */
56
/** @typedef {import('./txdb').Details} Details */
57
/** @typedef {import('./txdb').Credit} Credit */
58
/** @typedef {import('./txdb').Balance} Balance */
59
/** @typedef {import('./txdb').BlindBid} BlindBid */
60
/** @typedef {import('./txdb').BidReveal} BidReveal */
61
/** @typedef {import('./txdb').BlindValue} BlindValue */
62
/** @typedef {import('./txdb').BlockRecord} BlockRecord */
63
/** @typedef {import('./walletdb')} WalletDB */
64

65
/*
66
 * Constants
67
 */
68

69
const EMPTY = Buffer.alloc(0);
1✔
70

71
/**
72
 * @typedef {Object} AddResult
73
 * @property {Details} details
74
 * @property {WalletKey[]} derived
75
 */
76

77
/**
78
 * Wallet
79
 * @alias module:wallet.Wallet
80
 * @extends EventEmitter
81
 */
82

83
class Wallet extends EventEmitter {
84
  /**
85
   * Create a wallet.
86
   * @constructor
87
   * @param {WalletDB} wdb
88
   * @param {Object} options
89
   */
90

91
  constructor(wdb, options) {
92
    super();
564✔
93

94
    assert(wdb, 'WDB required.');
564✔
95

96
    this.wdb = wdb;
564✔
97
    this.db = wdb.db;
564✔
98
    this.network = wdb.network;
564✔
99
    this.logger = wdb.logger;
564✔
100
    this.writeLock = new Lock();
564✔
101
    this.fundLock = new Lock();
564✔
102

103
    this.wid = 0;
564✔
104
    /** @type {String|null} */
105
    this.id = null;
564✔
106
    this.watchOnly = false;
564✔
107
    this.accountDepth = 0;
564✔
108
    this.token = consensus.ZERO_HASH;
564✔
109
    this.tokenDepth = 0;
564✔
110
    this.master = new MasterKey();
564✔
111

112
    this.txdb = new TXDB(this.wdb);
564✔
113

114
    this.maxAncestors = policy.MEMPOOL_MAX_ANCESTORS;
564✔
115
    this.absurdFactor = policy.ABSURD_FEE_FACTOR;
564✔
116

117
    if (options)
564!
118
      this.fromOptions(options);
×
119
  }
120

121
  /**
122
   * Inject properties from options object.
123
   * @param {Object} options
124
   */
125

126
  fromOptions(options) {
127
    if (!options)
532!
128
      return this;
×
129

130
    let key = options.master;
532✔
131
    let mnemonic = options.mnemonic;
532✔
132
    let id, token;
133

134
    if (key) {
532✔
135
      if (typeof key === 'string')
12✔
136
        key = HDPrivateKey.fromBase58(key, this.network);
10✔
137

138
      assert(HDPrivateKey.isHDPrivateKey(key),
12✔
139
        'Must create wallet with hd private key.');
140
    } else {
141
      if (typeof mnemonic === 'string')
520✔
142
        mnemonic = new Mnemonic({ phrase: mnemonic });
4✔
143

144
      if (!mnemonic)
520✔
145
        mnemonic = new Mnemonic({ language: options.language });
309✔
146

147
      key = HDPrivateKey.fromMnemonic(mnemonic, options.bip39Passphrase);
520✔
148
    }
149

150
    this.master.fromKey(key, mnemonic);
531✔
151

152
    if (options.wid != null) {
531✔
153
      assert((options.wid >>> 0) === options.wid);
3✔
154
      this.wid = options.wid;
1✔
155
    }
156

157
    if (options.id) {
529✔
158
      assert(common.isName(options.id), 'Bad wallet ID.');
411✔
159
      id = options.id;
404✔
160
    }
161

162
    if (options.watchOnly != null) {
522✔
163
      assert(typeof options.watchOnly === 'boolean');
7✔
164
      this.watchOnly = options.watchOnly;
6✔
165
    }
166

167
    if (options.accountDepth != null) {
521✔
168
      assert((options.accountDepth >>> 0) === options.accountDepth);
6✔
169
      this.accountDepth = options.accountDepth;
4✔
170
    }
171

172
    if (options.token) {
519✔
173
      assert(Buffer.isBuffer(options.token));
2✔
174
      assert(options.token.length === 32);
2✔
175
      token = options.token;
1✔
176
    }
177

178
    if (options.tokenDepth != null) {
518✔
179
      assert((options.tokenDepth >>> 0) === options.tokenDepth);
3✔
180
      this.tokenDepth = options.tokenDepth;
1✔
181
    }
182

183
    if (options.maxAncestors != null) {
516!
184
      assert((options.maxAncestors >>> 0) === options.maxAncestors);
×
185
      this.maxAncestors = options.maxAncestors;
×
186
    }
187

188
    if (options.absurdFactor != null) {
516!
189
      assert((options.absurdFactor >>> 0) === options.absurdFactor);
×
190
      this.absurdFactor = options.absurdFactor;
×
191
    }
192

193
    if (!id)
516✔
194
      id = this.getID();
112✔
195

196
    if (!token)
516✔
197
      token = this.getToken(this.tokenDepth);
515✔
198

199
    this.id = id;
516✔
200
    this.token = token;
516✔
201

202
    return this;
516✔
203
  }
204

205
  /**
206
   * Instantiate wallet from options.
207
   * @param {WalletDB} wdb
208
   * @param {Object} options
209
   * @returns {Wallet}
210
   */
211

212
  static fromOptions(wdb, options) {
213
    return new this(wdb).fromOptions(options);
532✔
214
  }
215

216
  /**
217
   * Attempt to intialize the wallet (generating
218
   * the first addresses along with the lookahead
219
   * addresses). Called automatically from the
220
   * walletdb.
221
   * @param {Object} options
222
   * @param {(String|Buffer)?} [passphrase]
223
   * @returns {Promise}
224
   */
225

226
  async init(options, passphrase) {
227
    if (passphrase)
514✔
228
      await this.master.encrypt(passphrase);
7✔
229

230
    const account = await this._createAccount(options, passphrase);
514✔
231
    assert(account);
508✔
232

233
    await this.txdb.open(this);
508✔
234

235
    this.logger.info('Wallet initialized (%s).', this.id);
508✔
236
  }
237

238
  /**
239
   * Open wallet (done after retrieval).
240
   * @returns {Promise}
241
   */
242

243
  async open() {
244
    const account = await this.getAccount(0);
30✔
245

246
    if (!account)
28!
247
      throw new Error('Default account not found.');
×
248

249
    await this.txdb.open(this);
28✔
250
    this.logger.info('Wallet opened (%s).', this.id);
28✔
251
  }
252

253
  /**
254
   * Close the wallet, unregister with the database.
255
   * @returns {Promise}
256
   */
257

258
  async destroy() {
259
    const unlock1 = await this.writeLock.lock();
513✔
260
    const unlock2 = await this.fundLock.lock();
513✔
261
    try {
513✔
262
      await this.master.destroy();
513✔
263
      this.writeLock.destroy();
513✔
264
      this.fundLock.destroy();
513✔
265
    } finally {
266
      unlock2();
513✔
267
      unlock1();
513✔
268
    }
269
  }
270

271
  /**
272
   * Add a public account key to the wallet (multisig).
273
   * Saves the key in the wallet database.
274
   * @param {(Number|String)} acct
275
   * @param {HDPublicKey} key
276
   * @returns {Promise<Boolean>}
277
   */
278

279
  async addSharedKey(acct, key) {
280
    const unlock = await this.writeLock.lock();
11✔
281
    try {
11✔
282
      return await this._addSharedKey(acct, key);
11✔
283
    } finally {
284
      unlock();
11✔
285
    }
286
  }
287

288
  /**
289
   * Add a public account key to the wallet without a lock.
290
   * @private
291
   * @param {(Number|String)} acct
292
   * @param {HDPublicKey} key
293
   * @returns {Promise<Boolean>}
294
   */
295

296
  async _addSharedKey(acct, key) {
297
    const account = await this.getAccount(acct);
11✔
298

299
    if (!account)
11!
300
      throw new Error('Account not found.');
×
301

302
    const b = this.db.batch();
11✔
303
    const result = await account.addSharedKey(b, key);
11✔
304
    await b.write();
11✔
305

306
    return result;
11✔
307
  }
308

309
  /**
310
   * Remove a public account key from the wallet (multisig).
311
   * @param {(Number|String)} acct
312
   * @param {HDPublicKey} key
313
   * @returns {Promise<Boolean>}
314
   */
315

316
  async removeSharedKey(acct, key) {
317
    const unlock = await this.writeLock.lock();
×
318
    try {
×
319
      return await this._removeSharedKey(acct, key);
×
320
    } finally {
321
      unlock();
×
322
    }
323
  }
324

325
  /**
326
   * Remove a public account key from the wallet (multisig).
327
   * @private
328
   * @param {(Number|String)} acct
329
   * @param {HDPublicKey} key
330
   * @returns {Promise<Boolean>}
331
   */
332

333
  async _removeSharedKey(acct, key) {
334
    const account = await this.getAccount(acct);
×
335

336
    if (!account)
×
337
      throw new Error('Account not found.');
×
338

339
    const b = this.db.batch();
×
NEW
340
    const result = account.removeSharedKey(b, key);
×
341
    await b.write();
×
342

343
    return result;
×
344
  }
345

346
  /**
347
   * Change or set master key's passphrase.
348
   * @param {String|Buffer} passphrase
349
   * @param {String|Buffer} old
350
   * @returns {Promise}
351
   */
352

353
  async setPassphrase(passphrase, old) {
354
    if (old != null)
×
355
      await this.decrypt(old);
×
356

357
    await this.encrypt(passphrase);
×
358
  }
359

360
  /**
361
   * Encrypt the wallet permanently.
362
   * @param {String|Buffer} passphrase
363
   * @returns {Promise}
364
   */
365

366
  async encrypt(passphrase) {
367
    const unlock = await this.writeLock.lock();
1✔
368
    try {
1✔
369
      return await this._encrypt(passphrase);
1✔
370
    } finally {
371
      unlock();
1✔
372
    }
373
  }
374

375
  /**
376
   * Encrypt the wallet permanently, without a lock.
377
   * @private
378
   * @param {String|Buffer} passphrase
379
   * @returns {Promise}
380
   */
381

382
  async _encrypt(passphrase) {
383
    const key = await this.master.encrypt(passphrase, true);
1✔
384
    const b = this.db.batch();
1✔
385

386
    try {
1✔
387
      await this.wdb.encryptKeys(b, this.wid, key);
1✔
388
    } finally {
389
      cleanse(key);
1✔
390
    }
391

392
    this.save(b);
1✔
393

394
    await b.write();
1✔
395
  }
396

397
  /**
398
   * Decrypt the wallet permanently.
399
   * @param {String|Buffer} passphrase
400
   * @returns {Promise}
401
   */
402

403
  async decrypt(passphrase) {
404
    const unlock = await this.writeLock.lock();
1✔
405
    try {
1✔
406
      return await this._decrypt(passphrase);
1✔
407
    } finally {
408
      unlock();
1✔
409
    }
410
  }
411

412
  /**
413
   * Decrypt the wallet permanently, without a lock.
414
   * @private
415
   * @param {String|Buffer} passphrase
416
   * @returns {Promise}
417
   */
418

419
  async _decrypt(passphrase) {
420
    const key = await this.master.decrypt(passphrase, true);
1✔
421
    const b = this.db.batch();
1✔
422

423
    try {
1✔
424
      await this.wdb.decryptKeys(b, this.wid, key);
1✔
425
    } finally {
426
      cleanse(key);
1✔
427
    }
428

429
    this.save(b);
1✔
430

431
    await b.write();
1✔
432
  }
433

434
  /**
435
   * Generate a new token.
436
   * @param {(String|Buffer)?} passphrase
437
   * @returns {Promise<Buffer>}
438
   */
439

440
  async retoken(passphrase) {
441
    const unlock = await this.writeLock.lock();
×
442
    try {
×
443
      return await this._retoken(passphrase);
×
444
    } finally {
445
      unlock();
×
446
    }
447
  }
448

449
  /**
450
   * Generate a new token without a lock.
451
   * @private
452
   * @param {(String|Buffer)?} passphrase
453
   * @returns {Promise<Buffer>}
454
   */
455

456
  async _retoken(passphrase) {
457
    if (passphrase)
×
458
      await this.unlock(passphrase);
×
459

460
    this.tokenDepth += 1;
×
461
    this.token = this.getToken(this.tokenDepth);
×
462

463
    const b = this.db.batch();
×
464
    this.save(b);
×
465

466
    await b.write();
×
467

468
    return this.token;
×
469
  }
470

471
  /**
472
   * Rename the wallet.
473
   * @param {String} id
474
   * @returns {Promise}
475
   */
476

477
  async rename(id) {
478
    const unlock = await this.writeLock.lock();
1✔
479
    try {
1✔
480
      return await this.wdb.rename(this, id);
1✔
481
    } finally {
482
      unlock();
1✔
483
    }
484
  }
485

486
  /**
487
   * Rename account.
488
   * @param {String} acct
489
   * @param {String} name
490
   * @returns {Promise}
491
   */
492

493
  async renameAccount(acct, name) {
494
    const unlock = await this.writeLock.lock();
×
495
    try {
×
496
      return await this._renameAccount(acct, name);
×
497
    } finally {
498
      unlock();
×
499
    }
500
  }
501

502
  /**
503
   * Rename account without a lock.
504
   * @private
505
   * @param {String} acct
506
   * @param {String} name
507
   * @returns {Promise}
508
   */
509

510
  async _renameAccount(acct, name) {
511
    if (!common.isName(name))
×
512
      throw new Error('Bad account name.');
×
513

514
    const account = await this.getAccount(acct);
×
515

516
    if (!account)
×
517
      throw new Error('Account not found.');
×
518

519
    if (account.accountIndex === 0)
×
520
      throw new Error('Cannot rename default account.');
×
521

522
    if (await this.hasAccount(name))
×
523
      throw new Error('Account name not available.');
×
524

525
    const b = this.db.batch();
×
526

527
    this.wdb.renameAccount(b, account, name);
×
528

529
    await b.write();
×
530
  }
531

532
  /**
533
   * Lock the wallet, destroy decrypted key.
534
   */
535

536
  async lock() {
537
    const unlock1 = await this.writeLock.lock();
29✔
538
    const unlock2 = await this.fundLock.lock();
29✔
539
    try {
29✔
540
      await this.master.lock();
29✔
541
    } finally {
542
      unlock2();
29✔
543
      unlock1();
29✔
544
    }
545
  }
546

547
  /**
548
   * Unlock the key for `timeout` seconds.
549
   * @param {Buffer|String} passphrase
550
   * @param {Number?} [timeout=60]
551
   */
552

553
  unlock(passphrase, timeout) {
554
    return this.master.unlock(passphrase, timeout);
2,734✔
555
  }
556

557
  /**
558
   * Generate the wallet ID if none was passed in.
559
   * It is represented as BLAKE2b(m/44->public|magic, 20)
560
   * converted to an "address" with a prefix
561
   * of `0x03be04` (`WLT` in base58).
562
   * @private
563
   * @returns {Base58String}
564
   */
565

566
  getID() {
567
    assert(this.master.key, 'Cannot derive id.');
112✔
568

569
    const key = this.master.key.derive(44);
112✔
570

571
    const bw = bio.write(37);
112✔
572
    bw.writeBytes(key.publicKey);
112✔
573
    bw.writeU32(this.network.magic);
112✔
574

575
    const hash = blake2b.digest(bw.render(), 20);
112✔
576

577
    const b58 = bio.write(23);
112✔
578
    b58.writeU8(0x03);
112✔
579
    b58.writeU8(0xbe);
112✔
580
    b58.writeU8(0x04);
112✔
581
    b58.writeBytes(hash);
112✔
582

583
    return base58.encode(b58.render());
112✔
584
  }
585

586
  /**
587
   * Generate the wallet api key if none was passed in.
588
   * It is represented as BLAKE2b(m/44'->private|nonce).
589
   * @private
590
   * @param {Number} nonce
591
   * @returns {Buffer}
592
   */
593

594
  getToken(nonce) {
595
    if (!this.master.key)
519!
596
      throw new Error('Cannot derive token.');
×
597

598
    const key = this.master.key.derive(44, true);
519✔
599

600
    const bw = bio.write(36);
519✔
601
    bw.writeBytes(key.privateKey);
519✔
602
    bw.writeU32(nonce);
519✔
603

604
    return blake2b.digest(bw.render());
518✔
605
  }
606

607
  /**
608
   * Create an account. Requires passphrase if master key is encrypted.
609
   * @param {Object} options - See {@link Account} options.
610
   * @param {(String|Buffer)?} [passphrase]
611
   * @returns {Promise<Account>}
612
   */
613

614
  async createAccount(options, passphrase) {
615
    const unlock = await this.writeLock.lock();
55✔
616
    try {
55✔
617
      return await this._createAccount(options, passphrase);
55✔
618
    } finally {
619
      unlock();
55✔
620
    }
621
  }
622

623
  /**
624
   * Create an account without a lock.
625
   * @param {Object} options - See {@link Account} options.
626
   * @param {(String|Buffer)?} [passphrase]
627
   * @returns {Promise<Account>}
628
   */
629

630
  async _createAccount(options, passphrase) {
631
    let name = options.name;
569✔
632

633
    if (!name)
569✔
634
      name = this.accountDepth.toString(10);
518✔
635

636
    if (await this.hasAccount(name))
569!
637
      throw new Error('Account already exists.');
×
638

639
    await this.unlock(passphrase);
569✔
640

641
    let key;
642
    if (this.watchOnly) {
569✔
643
      key = options.accountKey;
6✔
644

645
      if (typeof key === 'string')
6✔
646
        key = HDPublicKey.fromBase58(key, this.network);
2✔
647

648
      if (!HDPublicKey.isHDPublicKey(key))
6✔
649
        throw new Error('Must add HD public keys to watch only wallet.');
2✔
650
    } else {
651
      assert(this.master.key);
563✔
652
      const type = this.network.keyPrefix.coinType;
563✔
653
      key = this.master.key.deriveAccount(44, type, this.accountDepth);
563✔
654
      key = key.toPublic();
563✔
655
    }
656

657
    const opt = {
567✔
658
      wid: this.wid,
659
      id: this.id,
660
      name: this.accountDepth === 0 ? 'default' : name,
567✔
661
      watchOnly: this.watchOnly,
662
      accountKey: key,
663
      accountIndex: this.accountDepth,
664
      type: options.type,
665
      m: options.m,
666
      n: options.n,
667
      keys: options.keys,
668
      lookahead: options.lookahead
669
    };
670

671
    const b = this.db.batch();
567✔
672

673
    const account = Account.fromOptions(this.wdb, opt);
567✔
674

675
    await account.init(b);
563✔
676

677
    this.logger.info('Created account %s/%s/%d.',
563✔
678
      account.id,
679
      account.name,
680
      account.accountIndex);
681

682
    this.accountDepth += 1;
563✔
683
    this.save(b);
563✔
684

685
    if (this.accountDepth === 1)
563✔
686
      this.increment(b);
506✔
687

688
    await b.write();
563✔
689

690
    return account;
563✔
691
  }
692

693
  /**
694
   * Modify an account. Requires passphrase if master key is encrypted.
695
   * @param {String|Number} acct
696
   * @param {Object} options
697
   * @param {String} [passphrase]
698
   * @returns {Promise<Account>}
699
   */
700

701
  async modifyAccount(acct, options, passphrase) {
702
    const unlock = await this.writeLock.lock();
3✔
703
    try {
3✔
704
      return await this._modifyAccount(acct, options, passphrase);
3✔
705
    } finally {
706
      unlock();
3✔
707
    }
708
  }
709

710
  /**
711
   * Create an account without a lock.
712
   * @param {String|Number} acct
713
   * @param {Object} options
714
   * @param {(String|Buffer)?} [passphrase]
715
   * @returns {Promise<Account>}
716
   */
717

718
  async _modifyAccount(acct, options, passphrase) {
719
    if (!await this.hasAccount(acct))
3!
720
      throw new Error(`Account ${acct} does not exist.`);
×
721

722
    await this.unlock(passphrase);
3✔
723

724
    const account = await this.getAccount(acct);
3✔
725
    assert(account);
3✔
726

727
    const b = this.db.batch();
3✔
728

729
    if (options.lookahead != null)
3!
730
      await account.setLookahead(b, options.lookahead);
3✔
731

732
    await b.write();
3✔
733

734
    return account;
3✔
735
  }
736

737
  /**
738
   * Ensure an account. Requires passphrase if master key is encrypted.
739
   * @param {Object} options - See {@link Account} options.
740
   * @param {(String|Buffer)?} [passphrase]
741
   * @returns {Promise<Account>}
742
   */
743

744
  async ensureAccount(options, passphrase) {
745
    const name = options.name;
×
746
    const account = await this.getAccount(name);
×
747

748
    if (account)
×
749
      return account;
×
750

751
    return this.createAccount(options, passphrase);
×
752
  }
753

754
  /**
755
   * List account names and indexes from the db.
756
   * @returns {Promise<String[]>} - Returns Array.
757
   */
758

759
  getAccounts() {
760
    return this.wdb.getAccounts(this.wid);
1✔
761
  }
762

763
  /**
764
   * Get all wallet address hashes.
765
   * @param {(String|Number)?} acct
766
   * @returns {Promise<Hash[]>}
767
   */
768

769
  getAddressHashes(acct) {
770
    if (acct != null)
×
771
      return this.getAccountHashes(acct);
×
772
    return this.wdb.getWalletHashes(this.wid);
×
773
  }
774

775
  /**
776
   * Get all account address hashes.
777
   * @param {String|Number} acct
778
   * @returns {Promise<Hash[]>} - Returns Array.
779
   * @throws on non-existent account
780
   */
781

782
  async getAccountHashes(acct) {
783
    const index = await this.getAccountIndex(acct);
1✔
784

785
    if (index === -1)
1!
786
      throw new Error('Account not found.');
×
787

788
    return this.wdb.getAccountHashes(this.wid, index);
1✔
789
  }
790

791
  /**
792
   * Retrieve an account from the database.
793
   * @param {Number|String} acct
794
   * @returns {Promise<Account|null>}
795
   */
796

797
  async getAccount(acct) {
798
    const index = await this.getAccountIndex(acct);
53,367✔
799

800
    if (index === -1)
53,367!
801
      return null;
×
802

803
    const account = await this.wdb.getAccount(this.wid, index);
53,367✔
804

805
    if (!account)
53,365!
806
      return null;
×
807

808
    account.wid = this.wid;
53,365✔
809
    account.id = this.id;
53,365✔
810
    account.watchOnly = this.watchOnly;
53,365✔
811

812
    return account;
53,365✔
813
  }
814

815
  /**
816
   * Lookup the corresponding account name's index.
817
   * @param {String|Number} acct - Account name/index.
818
   * @returns {Promise<Number>}
819
   */
820

821
  async getAccountIndex(acct) {
822
    if (acct == null)
60,807!
823
      return -1;
×
824

825
    if (typeof acct === 'number')
60,807✔
826
      return acct;
53,064✔
827

828
    return this.wdb.getAccountIndex(this.wid, acct);
7,743✔
829
  }
830

831
  /**
832
   * Lookup the corresponding account name's index.
833
   * @param {(String|Number)?} [acct] - Account name/index.
834
   * @returns {Promise<Number>}
835
   * @throws on non-existent account
836
   */
837

838
  async ensureIndex(acct) {
839
    if (acct == null || acct === -1)
8,050✔
840
      return -1;
4,944✔
841

842
    const index = await this.getAccountIndex(acct);
3,106✔
843

844
    if (index === -1)
3,106✔
845
      throw new Error('Account not found.');
1✔
846

847
    return index;
3,105✔
848
  }
849

850
  /**
851
   * Lookup the corresponding account index's name.
852
   * @param {(String|Number)} index - Account index.
853
   * @returns {Promise<String|null>}
854
   */
855

856
  async getAccountName(index) {
857
    if (typeof index === 'string')
1!
858
      return index;
1✔
859

860
    return this.wdb.getAccountName(this.wid, index);
×
861
  }
862

863
  /**
864
   * Test whether an account exists.
865
   * @param {Number|String} acct
866
   * @returns {Promise<Boolean>}
867
   */
868

869
  async hasAccount(acct) {
870
    const index = await this.getAccountIndex(acct);
572✔
871

872
    if (index === -1)
572✔
873
      return false;
569✔
874

875
    return this.wdb.hasAccount(this.wid, index);
3✔
876
  }
877

878
  /**
879
   * Create a new receiving address (increments receiveDepth).
880
   * @param {(Number|String)?} acct
881
   * @returns {Promise<WalletKey>}
882
   */
883

884
  createReceive(acct = 0) {
2✔
885
    return this.createKey(acct, 0);
3,668✔
886
  }
887

888
  /**
889
   * Create a new change address (increments changeDepth).
890
   * @param {(Number|String)?} acct
891
   * @returns {Promise<WalletKey>}
892
   */
893

894
  createChange(acct = 0) {
20✔
895
    return this.createKey(acct, 1);
22✔
896
  }
897

898
  /**
899
   * Create a new address (increments depth).
900
   * @param {(Number|String)?} acct
901
   * @param {Number} branch
902
   * @returns {Promise<WalletKey>}
903
   */
904

905
  async createKey(acct, branch) {
906
    const unlock = await this.writeLock.lock();
3,690✔
907
    try {
3,690✔
908
      return await this._createKey(acct, branch);
3,690✔
909
    } finally {
910
      unlock();
3,690✔
911
    }
912
  }
913

914
  /**
915
   * Create a new address (increments depth) without a lock.
916
   * @private
917
   * @param {(Number|String)?} acct
918
   * @param {Number} branch
919
   * @returns {Promise<WalletKey>}
920
   */
921

922
  async _createKey(acct, branch) {
923
    const account = await this.getAccount(acct);
3,690✔
924

925
    if (!account)
3,690!
926
      throw new Error('Account not found.');
×
927

928
    const b = this.db.batch();
3,690✔
929
    const key = await account.createKey(b, branch);
3,690✔
930
    await b.write();
3,690✔
931

932
    return key;
3,690✔
933
  }
934

935
  /**
936
   * Save the wallet to the database. Necessary
937
   * when address depth and keys change.
938
   * @param {Batch} b
939
   * @returns {void}
940
   */
941

942
  save(b) {
943
    return this.wdb.save(b, this);
565✔
944
  }
945

946
  /**
947
   * Increment the wid depth.
948
   * @param {Batch} b
949
   * @returns {void}
950
   */
951

952
  increment(b) {
953
    return this.wdb.increment(b, this.wid);
506✔
954
  }
955

956
  /**
957
   * Test whether the wallet possesses an address.
958
   * @param {Address|Hash} address
959
   * @returns {Promise<Boolean>}
960
   */
961

962
  async hasAddress(address) {
963
    const hash = Address.getHash(address);
44✔
964
    const path = await this.getPath(hash);
44✔
965
    return path != null;
44✔
966
  }
967

968
  /**
969
   * Get path by address hash.
970
   * @param {Address|Hash} address
971
   * @returns {Promise<Path|null>}
972
   */
973

974
  async getPath(address) {
975
    const hash = Address.getHash(address);
27,910✔
976
    return this.wdb.getPath(this.wid, hash);
27,910✔
977
  }
978

979
  /**
980
   * Get path by address hash (without account name).
981
   * @private
982
   * @param {Address|Hash} address
983
   * @returns {Promise<Path|null>}
984
   */
985

986
  async readPath(address) {
987
    const hash = Address.getHash(address);
46,963✔
988
    return this.wdb.readPath(this.wid, hash);
46,963✔
989
  }
990

991
  /**
992
   * Test whether the wallet contains a path.
993
   * @param {Address|Hash} address
994
   * @returns {Promise<Boolean>}
995
   */
996

997
  async hasPath(address) {
998
    const hash = Address.getHash(address);
×
999
    return this.wdb.hasPath(this.wid, hash);
×
1000
  }
1001

1002
  /**
1003
   * Get all wallet paths.
1004
   * @param {(String|Number)?} acct
1005
   * @returns {Promise<Path[]>}
1006
   */
1007

1008
  async getPaths(acct) {
1009
    if (acct != null)
3✔
1010
      return this.getAccountPaths(acct);
1✔
1011

1012
    return this.wdb.getWalletPaths(this.wid);
2✔
1013
  }
1014

1015
  /**
1016
   * Get all account paths.
1017
   * @param {String|Number} acct
1018
   * @returns {Promise<Path[]>}
1019
   */
1020

1021
  async getAccountPaths(acct) {
1022
    const index = await this.getAccountIndex(acct);
1✔
1023

1024
    if (index === -1)
1!
1025
      throw new Error('Account not found.');
×
1026

1027
    const hashes = await this.getAccountHashes(index);
1✔
1028
    const name = await this.getAccountName(acct);
1✔
1029

1030
    assert(name);
1✔
1031

1032
    const result = [];
1✔
1033

1034
    for (const hash of hashes) {
1✔
1035
      const path = await this.readPath(hash);
442✔
1036

1037
      assert(path);
442✔
1038
      assert(path.account === index);
442✔
1039

1040
      path.name = name;
442✔
1041

1042
      result.push(path);
442✔
1043
    }
1044

1045
    return result;
1✔
1046
  }
1047

1048
  /**
1049
   * Import a keyring (will not exist on derivation chain).
1050
   * Rescanning must be invoked manually.
1051
   * @param {(String|Number)?} acct
1052
   * @param {WalletKey} ring
1053
   * @param {(String|Buffer)?} passphrase
1054
   * @returns {Promise}
1055
   */
1056

1057
  async importKey(acct, ring, passphrase) {
1058
    const unlock = await this.writeLock.lock();
4✔
1059
    try {
4✔
1060
      return await this._importKey(acct, ring, passphrase);
4✔
1061
    } finally {
1062
      unlock();
4✔
1063
    }
1064
  }
1065

1066
  /**
1067
   * Import a keyring (will not exist on derivation chain) without a lock.
1068
   * @private
1069
   * @param {(String|Number)?} acct
1070
   * @param {WalletKey} ring
1071
   * @param {(String|Buffer)?} passphrase
1072
   * @returns {Promise}
1073
   */
1074

1075
  async _importKey(acct, ring, passphrase) {
1076
    if (!this.watchOnly) {
4✔
1077
      if (!ring.privateKey)
3!
1078
        throw new Error('Cannot import pubkey into non watch-only wallet.');
×
1079
    } else {
1080
      if (ring.privateKey)
1!
1081
        throw new Error('Cannot import privkey into watch-only wallet.');
×
1082
    }
1083

1084
    const hash = ring.getHash();
4✔
1085

1086
    if (await this.getPath(hash))
4!
1087
      throw new Error('Key already exists.');
×
1088

1089
    const account = await this.getAccount(acct);
4✔
1090

1091
    if (!account)
4!
1092
      throw new Error('Account not found.');
×
1093

1094
    if (account.type !== Account.types.PUBKEYHASH)
4!
1095
      throw new Error('Cannot import into non-pkh account.');
×
1096

1097
    await this.unlock(passphrase);
4✔
1098

1099
    const key = WalletKey.fromRing(account, ring);
4✔
1100
    const path = key.toPath();
4✔
1101

1102
    if (this.master.encrypted) {
4✔
1103
      path.data = this.master.encipher(path.data, path.hash);
1✔
1104
      assert(path.data);
1✔
1105
      path.encrypted = true;
1✔
1106
    }
1107

1108
    const b = this.db.batch();
4✔
1109
    await account.savePath(b, path);
4✔
1110
    await b.write();
4✔
1111
  }
1112

1113
  /**
1114
   * Import a keyring (will not exist on derivation chain).
1115
   * Rescanning must be invoked manually.
1116
   * @param {(String|Number)?} acct
1117
   * @param {Address} address
1118
   * @returns {Promise}
1119
   */
1120

1121
  async importAddress(acct, address) {
1122
    const unlock = await this.writeLock.lock();
1✔
1123
    try {
1✔
1124
      return await this._importAddress(acct, address);
1✔
1125
    } finally {
1126
      unlock();
1✔
1127
    }
1128
  }
1129

1130
  /**
1131
   * Import a keyring (will not exist on derivation chain) without a lock.
1132
   * @private
1133
   * @param {(String|Number)?} acct
1134
   * @param {Address} address
1135
   * @returns {Promise}
1136
   */
1137

1138
  async _importAddress(acct, address) {
1139
    if (!this.watchOnly)
1!
1140
      throw new Error('Cannot import address into non watch-only wallet.');
×
1141

1142
    if (await this.getPath(address))
1!
1143
      throw new Error('Address already exists.');
×
1144

1145
    const account = await this.getAccount(acct);
1✔
1146

1147
    if (!account)
1!
1148
      throw new Error('Account not found.');
×
1149

1150
    if (account.type !== Account.types.PUBKEYHASH)
1!
1151
      throw new Error('Cannot import into non-pkh account.');
×
1152

1153
    const path = Path.fromAddress(account, address);
1✔
1154

1155
    const b = this.db.batch();
1✔
1156
    await account.savePath(b, path);
1✔
1157
    await b.write();
1✔
1158
  }
1159

1160
  /**
1161
   * Import a name.
1162
   * Rescanning must be invoked manually.
1163
   * @param {String} name
1164
   * @returns {Promise}
1165
   */
1166

1167
  async importName(name) {
1168
    const unlock = await this.writeLock.lock();
8✔
1169
    try {
8✔
1170
      return await this._importName(name);
8✔
1171
    } finally {
1172
      unlock();
8✔
1173
    }
1174
  }
1175

1176
  /**
1177
   * Import a name without a lock.
1178
   * @private
1179
   * @param {String} name
1180
   * @returns {Promise}
1181
   */
1182

1183
  async _importName(name) {
1184
    const nameHash = rules.hashName(name);
8✔
1185

1186
    if (await this.txdb.hasNameState(nameHash))
8✔
1187
      throw new Error('Name already exists.');
2✔
1188

1189
    const b = this.db.batch();
6✔
1190
    await this.wdb.addNameMap(b, nameHash, this.wid);
6✔
1191
    await b.write();
6✔
1192
  }
1193

1194
  /**
1195
   * Fill a transaction with inputs, estimate
1196
   * transaction size, calculate fee, and add a change output.
1197
   * @see MTX#selectCoins
1198
   * @see MTX#fill
1199
   * @param {MTX} mtx - _Must_ be a mutable transaction.
1200
   * @param {Object} [options]
1201
   * @param {(String|Number)?} options.account - If no account is
1202
   * specified, coins from the entire wallet will be filled.
1203
   * @param {String?} options.selection - Coin selection priority. Can
1204
   * be `age`, `random`, or `all`. (default=age).
1205
   * @param {Boolean} options.round - Whether to round to the nearest
1206
   * kilobyte for fee calculation.
1207
   * See {@link TX#getMinFee} vs. {@link TX#getRoundFee}.
1208
   * @param {Rate} options.rate - Rate used for fee calculation.
1209
   * @param {Boolean} options.confirmed - Select only confirmed coins.
1210
   * @param {Boolean} options.free - Do not apply a fee if the
1211
   * transaction priority is high enough to be considered free.
1212
   * @param {Amount?} options.hardFee - Use a hard fee rather than
1213
   * calculating one.
1214
   * @param {Number|Boolean} options.subtractFee - Whether to subtract the
1215
   * fee from existing outputs rather than adding more inputs.
1216
   * @param {Boolean} [force]
1217
   */
1218

1219
  async fund(mtx, options, force) {
1220
    const unlock = await this.fundLock.lock(force);
19✔
1221
    try {
19✔
1222
      return await this.fill(mtx, options);
19✔
1223
    } finally {
1224
      unlock();
19✔
1225
    }
1226
  }
1227

1228
  /**
1229
   * Fill a transaction with inputs without a lock.
1230
   * @private
1231
   * @see MTX#selectCoins
1232
   * @see MTX#fill
1233
   * @param {MTX} mtx
1234
   * @param {Object} [options]
1235
   */
1236

1237
  async fill(mtx, options) {
1238
    if (!options)
2,147✔
1239
      options = {};
510✔
1240

1241
    const acct = options.account || 0;
2,147✔
1242
    const change = await this.changeAddress(acct);
2,147✔
1243

1244
    if (!change)
2,147!
1245
      throw new Error('Account not found.');
×
1246

1247
    let rate = options.rate;
2,147✔
1248
    if (rate == null)
2,147✔
1249
      rate = await this.wdb.estimateFee(options.blocks);
2,127✔
1250

1251
    let coins = options.coins || [];
2,147✔
1252
    assert(Array.isArray(coins));
2,147✔
1253
    if (options.smart) {
2,147✔
1254
      const smartCoins = await this.getSmartCoins(options.account);
1✔
1255
      coins = coins.concat(smartCoins);
1✔
1256
    } else {
1257
      let availableCoins = await this.getCoins(options.account);
2,146✔
1258
      availableCoins = this.txdb.filterLocked(availableCoins);
2,146✔
1259
      coins = coins.concat(availableCoins);
2,146✔
1260
    }
1261

1262
    await mtx.fund(coins, {
2,147✔
1263
      selection: options.selection,
1264
      round: options.round,
1265
      depth: options.depth,
1266
      hardFee: options.hardFee,
1267
      subtractFee: options.subtractFee,
1268
      subtractIndex: options.subtractIndex,
1269
      changeAddress: change,
1270
      height: this.wdb.height,
1271
      coinbaseMaturity: this.network.coinbaseMaturity,
1272
      rate: rate,
1273
      maxFee: options.maxFee,
1274
      estimate: prev => this.estimateSize(prev)
10✔
1275
    });
1276
  }
1277

1278
  /**
1279
   * Get public keys at index based on
1280
   * address and value for nonce generation
1281
   * @param {Address} address
1282
   * @param {Amount} value
1283
   * @returns {Promise<Buffer[]>} public keys
1284
   */
1285

1286
  async _getNoncePublicKeys(address, value) {
1287
    const path = await this.getPath(address.hash);
2,960✔
1288

1289
    if (!path)
2,960!
1290
      throw new Error('Account not found.');
×
1291

1292
    const account = await this.getAccount(path.account);
2,960✔
1293

1294
    if (!account)
2,960!
1295
      throw new Error('Account not found.');
×
1296

1297
    const hi = (value * (1 / 0x100000000)) >>> 0;
2,960✔
1298
    const lo = value >>> 0;
2,960✔
1299
    const index = (hi ^ lo) & 0x7fffffff;
2,960✔
1300

1301
    const publicKeys = [];
2,960✔
1302
    for (const accountKey of [account.accountKey, ...account.keys])
2,960✔
1303
      publicKeys.push(accountKey.derive(index).publicKey);
2,968✔
1304

1305
    // Use smallest public key
1306
    publicKeys.sort(Buffer.compare);
2,960✔
1307

1308
    return publicKeys;
2,960✔
1309
  }
1310

1311
  /**
1312
   * Generate nonce deterministically
1313
   * based on address (smallest pubkey),
1314
   * name hash, and bid value.
1315
   * @param {Buffer} nameHash
1316
   * @param {Address} address
1317
   * @param {Amount} value
1318
   * @returns {Promise<Buffer>}
1319
   */
1320

1321
  async generateNonce(nameHash, address, value) {
1322
    const publicKeys = await this._getNoncePublicKeys(address, value);
2,947✔
1323
    return blake2b.multi(address.hash, publicKeys[0], nameHash);
2,947✔
1324
  }
1325

1326
  /**
1327
   * Generate nonces deterministically
1328
   * for all keys (in multisig).
1329
   * @param {Buffer} nameHash
1330
   * @param {Address} address
1331
   * @param {Amount} value
1332
   * @returns {Promise<Buffer[]>}
1333
   */
1334

1335
  async generateNonces(nameHash, address, value) {
1336
    const publicKeys = await this._getNoncePublicKeys(address, value);
13✔
1337

1338
    // Generate nonces for all public keys
1339
    const nonces = [];
13✔
1340
    for (const publicKey of publicKeys)
13✔
1341
      nonces.push(blake2b.multi(address.hash, publicKey, nameHash));
17✔
1342

1343
    return nonces;
13✔
1344
  }
1345

1346
  /**
1347
   * Generate nonce & blind, save nonce.
1348
   * @param {Buffer} nameHash
1349
   * @param {Address} address
1350
   * @param {Amount} value
1351
   * @returns {Promise<Buffer>}
1352
   */
1353

1354
  async generateBlind(nameHash, address, value) {
1355
    const nonce = await this.generateNonce(nameHash, address, value);
2,944✔
1356
    const blind = rules.blind(value, nonce);
2,944✔
1357

1358
    await this.txdb.saveBlind(blind, {value, nonce});
2,944✔
1359
    return blind;
2,944✔
1360
  }
1361

1362
  /**
1363
   * Generate all nonces & blinds, save nonces.
1364
   * @param {Buffer} nameHash
1365
   * @param {Address} address
1366
   * @param {Amount} value
1367
   * @returns {Promise<Buffer[]>}
1368
   */
1369

1370
  async generateBlinds(nameHash, address, value) {
1371
    const nonces = await this.generateNonces(nameHash, address, value);
6✔
1372

1373
    const blinds = [];
6✔
1374
    for (const nonce of nonces) {
6✔
1375
      const blind = rules.blind(value, nonce);
8✔
1376
      await this.txdb.saveBlind(blind, {value, nonce});
8✔
1377
      blinds.push(blind);
8✔
1378
    }
1379

1380
    return blinds;
6✔
1381
  }
1382

1383
  /**
1384
   * Make a claim MTX.
1385
   * @param {String} name
1386
   * @param {Object?} [options]
1387
   * @returns {Promise<Object>}
1388
   */
1389

1390
  async _createClaim(name, options) {
1391
    if (options == null)
22✔
1392
      options = {};
1✔
1393

1394
    assert(typeof name === 'string');
22✔
1395
    assert(options && typeof options === 'object');
22✔
1396

1397
    if (!rules.verifyName(name))
22!
1398
      throw new Error('Invalid name.');
×
1399

1400
    const rawName = Buffer.from(name, 'ascii');
22✔
1401
    const nameHash = rules.hashName(rawName);
22✔
1402
    const height = this.wdb.height + 1;
22✔
1403
    const network = this.network;
22✔
1404

1405
    // TODO: Handle expired behavior.
1406
    if (!rules.isReserved(nameHash, height, network))
22!
1407
      throw new Error('Name is not reserved.');
×
1408

1409
    // Must get this from chain (not walletDB) in case
1410
    // this name has already been claimed by an attacker
1411
    // and we are trying to replace that claim.
1412
    const ns = await this.wdb.getNameStatus(nameHash);
22✔
1413

1414
    if (!await this.wdb.isAvailable(nameHash))
22!
1415
      throw new Error('Name is not available.');
×
1416

1417
    const item = reserved.get(nameHash);
22✔
1418
    assert(item);
22✔
1419

1420
    let rate = options.rate;
22✔
1421
    if (rate == null)
22!
1422
      rate = await this.wdb.estimateFee(options.blocks);
22✔
1423

1424
    let size = 5 << 10;
22✔
1425
    let vsize = size / consensus.WITNESS_SCALE_FACTOR | 0;
22✔
1426
    let proof = null;
22✔
1427

1428
    try {
22✔
1429
      proof = await ownership.prove(item.target, true);
22✔
1430
    } catch (e) {
1431
      ;
1432
    }
1433

1434
    if (proof) {
22!
1435
      const zones = proof.zones;
22✔
1436
      const zone = zones.length >= 2
22!
1437
        ? zones[zones.length - 1]
1438
        : null;
1439

1440
      let added = 0;
22✔
1441

1442
      // TXT record.
1443
      added += item.target.length; // rrname
22✔
1444
      added += 10; // header
22✔
1445
      added += 1; // txt size
22✔
1446
      added += 200; // max string size
22✔
1447

1448
      // RRSIG record size.
1449
      if (!zone || zone.claim.length === 0) {
22!
1450
        added += item.target.length; // rrname
×
1451
        added += 10; // header
×
1452
        added += 275; // avg rsa sig size
×
1453
      }
1454

1455
      const claim = Claim.fromProof(proof);
22✔
1456

1457
      size = claim.getSize() + added;
22✔
1458

1459
      added /= consensus.WITNESS_SCALE_FACTOR;
22✔
1460
      added |= 0;
22✔
1461

1462
      vsize = claim.getVirtualSize() + added;
22✔
1463
    }
1464

1465
    let minFee = options.fee;
22✔
1466

1467
    if (minFee == null)
22✔
1468
      minFee = policy.getMinFee(vsize, rate);
21✔
1469

1470
    if (this.wdb.height < 1)
22!
1471
      throw new Error('Chain too immature for name claim.');
×
1472

1473
    let commitHeight = 1;
22✔
1474
    if (ns && ns.claimed)
22✔
1475
      commitHeight = ns.claimed + 1;
3✔
1476

1477
    const commitHash = (await this.wdb.getBlock(commitHeight)).hash;
22✔
1478

1479
    let fee = Math.min(item.value, minFee);
22✔
1480

1481
    if (ns && !ns.owner.isNull()) {
22✔
1482
      const coin = await this.wdb.getCoin(ns.owner.hash, ns.owner.index);
3✔
1483
      assert(coin, 'Coin not found for name owner.');
3✔
1484
      fee = item.value - coin.value;
2✔
1485
    }
1486

1487
    const acct = options.account || 0;
21✔
1488
    const address = await this.receiveAddress(acct);
21✔
1489

1490
    const txt = ownership.createData(address,
21✔
1491
                                     fee,
1492
                                     commitHash,
1493
                                     commitHeight,
1494
                                     network);
1495

1496
    return {
21✔
1497
      name,
1498
      proof,
1499
      target: item.target,
1500
      value: item.value,
1501
      size,
1502
      fee,
1503
      address,
1504
      txt
1505
    };
1506
  }
1507

1508
  /**
1509
   * Create and send a claim MTX.
1510
   * @param {String} name
1511
   * @param {Object} options
1512
   * @returns {Promise<Object>}
1513
   */
1514

1515
  async createClaim(name, options) {
1516
    const unlock = await this.fundLock.lock();
1✔
1517
    try {
1✔
1518
      return await this._createClaim(name, options);
1✔
1519
    } finally {
1520
      unlock();
1✔
1521
    }
1522
  }
1523

1524
  /**
1525
   * Make a claim proof.
1526
   * @param {String} name
1527
   * @param {Object?} [options]
1528
   * @returns {Promise<Claim>}
1529
   */
1530

1531
  async makeFakeClaim(name, options) {
1532
    if (options == null)
21✔
1533
      options = {};
20✔
1534

1535
    assert(typeof name === 'string');
21✔
1536

1537
    if (!rules.verifyName(name))
21!
1538
      throw new Error('Invalid name.');
×
1539

1540
    const rawName = Buffer.from(name, 'ascii');
21✔
1541
    const nameHash = rules.hashName(rawName);
21✔
1542
    const height = this.wdb.height + 1;
21✔
1543
    const network = this.network;
21✔
1544

1545
    // TODO: Handle expired behavior.
1546
    if (!rules.isReserved(nameHash, height, network))
21!
1547
      throw new Error('Name is not reserved.');
×
1548

1549
    const {proof, txt} = await this._createClaim(name, options);
21✔
1550

1551
    if (!proof)
20!
1552
      throw new Error('Could not resolve name.');
×
1553

1554
    proof.addData([txt]);
20✔
1555

1556
    const data = proof.getData(this.network);
20✔
1557

1558
    if (!data)
20!
1559
      throw new Error(`No valid DNS commitment found for ${name}.`);
×
1560

1561
    return Claim.fromProof(proof);
20✔
1562
  }
1563

1564
  /**
1565
   * Create and send a claim proof.
1566
   * @param {String} name
1567
   * @param {Object} options
1568
   */
1569

1570
  async _sendFakeClaim(name, options) {
1571
    const claim = await this.makeFakeClaim(name, options);
5✔
1572
    await this.wdb.sendClaim(claim);
4✔
1573
    return claim;
4✔
1574
  }
1575

1576
  /**
1577
   * Create and send a claim proof.
1578
   * @param {String} name
1579
   * @param {Object} options
1580
   */
1581

1582
  async sendFakeClaim(name, options) {
1583
    const unlock = await this.fundLock.lock();
5✔
1584
    try {
5✔
1585
      return await this._sendFakeClaim(name, options);
5✔
1586
    } finally {
1587
      unlock();
5✔
1588
    }
1589
  }
1590

1591
  /**
1592
   * Make a claim proof.
1593
   * @param {String} name
1594
   * @param {Object} options
1595
   * @returns {Promise<Claim>}
1596
   */
1597

1598
  async makeClaim(name, options) {
1599
    if (options == null)
×
1600
      options = {};
×
1601

1602
    assert(typeof name === 'string');
×
1603

1604
    if (!rules.verifyName(name))
×
1605
      throw new Error(`Invalid name: ${name}.`);
×
1606

1607
    const rawName = Buffer.from(name, 'ascii');
×
1608
    const nameHash = rules.hashName(rawName);
×
1609
    const height = this.wdb.height + 1;
×
1610
    const network = this.network;
×
1611

1612
    // TODO: Handle expired behavior.
1613
    if (!rules.isReserved(nameHash, height, network))
×
1614
      throw new Error(`Name is not reserved: ${name}.`);
×
1615

1616
    const ns = await this.getNameState(nameHash);
×
1617

1618
    if (ns) {
×
1619
      if (!ns.isExpired(height, network))
×
1620
        throw new Error(`Name already claimed: ${name}.`);
×
1621
    } else {
1622
      if (!await this.wdb.isAvailable(nameHash))
×
1623
        throw new Error(`Name is not available: ${name}.`);
×
1624
    }
1625

1626
    const item = reserved.get(nameHash);
×
1627
    assert(item);
×
1628

1629
    const proof = await ownership.prove(item.target);
×
1630
    const data = proof.getData(this.network);
×
1631

1632
    if (!data)
×
1633
      throw new Error(`No valid DNS commitment found for ${name}.`);
×
1634

1635
    return Claim.fromProof(proof);
×
1636
  }
1637

1638
  /**
1639
   * Create and send a claim proof.
1640
   * @param {String} name
1641
   * @param {Object} options
1642
   * @returns {Promise<Claim>}
1643
   */
1644

1645
  async _sendClaim(name, options) {
1646
    const claim = await this.makeClaim(name, options);
×
1647
    await this.wdb.sendClaim(claim);
×
1648
    return claim;
×
1649
  }
1650

1651
  /**
1652
   * Create and send a claim proof.
1653
   * @param {String} name
1654
   * @param {Object} options
1655
   * @returns {Promise<Claim>}
1656
   */
1657

1658
  async sendClaim(name, options) {
1659
    const unlock = await this.fundLock.lock();
×
1660
    try {
×
1661
      return await this._sendClaim(name, options);
×
1662
    } finally {
1663
      unlock();
×
1664
    }
1665
  }
1666

1667
  /**
1668
   * Make a open MTX.
1669
   * @param {String} name
1670
   * @param {Number|String} acct
1671
   * @param {MTX?} [mtx]
1672
   * @returns {Promise<MTX>}
1673
   */
1674

1675
  async makeOpen(name, acct, mtx) {
1676
    assert(typeof name === 'string');
1,354✔
1677
    assert((acct >>> 0) === acct || typeof acct === 'string');
1,354✔
1678

1679
    if (!rules.verifyName(name))
1,354!
1680
      throw new Error(`Invalid name: ${name}.`);
×
1681

1682
    const rawName = Buffer.from(name, 'ascii');
1,354✔
1683
    const nameHash = rules.hashName(rawName);
1,354✔
1684
    const height = this.wdb.height + 1;
1,354✔
1685
    const network = this.network;
1,354✔
1686
    const {icannlockup} = this.wdb.options;
1,354✔
1687

1688
    // TODO: Handle expired behavior.
1689
    if (rules.isReserved(nameHash, height, network))
1,354✔
1690
      throw new Error(`Name is reserved: ${name}.`);
3✔
1691

1692
    if (icannlockup && rules.isLockedUp(nameHash, height, network))
1,351!
1693
      throw new Error(`Name is locked up: ${name}.`);
×
1694

1695
    if (!rules.hasRollout(nameHash, height, network))
1,351!
1696
      throw new Error(`Name not yet available: ${name}.`);
×
1697

1698
    let ns = await this.getNameState(nameHash);
1,351✔
1699

1700
    if (!ns)
1,351✔
1701
      ns = await this.wdb.getNameStatus(nameHash);
1,343✔
1702

1703
    ns.maybeExpire(height, network);
1,351✔
1704

1705
    const start = ns.height;
1,351✔
1706

1707
    if (!ns.isOpening(height, network))
1,351✔
1708
      throw new Error(`Name is not available: ${name}.`);
1✔
1709

1710
    if (start !== 0 && start !== height)
1,350✔
1711
      throw new Error(`Name is already opening: ${name}.`);
1✔
1712

1713
    const addr = await this.receiveAddress(acct);
1,349✔
1714

1715
    const output = new Output();
1,349✔
1716
    output.address = addr;
1,349✔
1717
    output.value = 0;
1,349✔
1718
    output.covenant.setOpen(nameHash, rawName);
1,349✔
1719

1720
    if (!mtx)
1,349✔
1721
      mtx = new MTX();
128✔
1722

1723
    mtx.outputs.push(output);
1,349✔
1724

1725
    if (await this.txdb.isDoubleOpen(mtx))
1,349✔
1726
      throw new Error(`Already sent an open for: ${name}.`);
2✔
1727

1728
    return mtx;
1,347✔
1729
  }
1730

1731
  /**
1732
   * Create and finalize an open
1733
   * MTX without a lock.
1734
   * @param {String} name
1735
   * @param {Object} options
1736
   * @returns {Promise<MTX>}
1737
   */
1738

1739
  async _createOpen(name, options) {
1740
    const acct = options ? options.account || 0 : 0;
132✔
1741
    const mtx = await this.makeOpen(name, acct);
132✔
1742
    await this.fill(mtx, options);
126✔
1743
    return this.finalize(mtx, options);
125✔
1744
  }
1745

1746
  /**
1747
   * Create and finalize an open
1748
   * MTX with a lock.
1749
   * @param {String} name
1750
   * @param {Object} options
1751
   * @returns {Promise<MTX>}
1752
   */
1753

1754
  async createOpen(name, options) {
1755
    const unlock = await this.fundLock.lock();
28✔
1756
    try {
28✔
1757
      return await this._createOpen(name, options);
28✔
1758
    } finally {
1759
      unlock();
28✔
1760
    }
1761
  }
1762

1763
  /**
1764
   * Create and send an open
1765
   * MTX without a lock.
1766
   * @param {String} name
1767
   * @param {Object} options
1768
   * @returns {Promise<TX>}
1769
   */
1770

1771
  async _sendOpen(name, options) {
1772
    const passphrase = options ? options.passphrase : null;
104✔
1773
    const mtx = await this._createOpen(name, options);
104✔
1774
    return this.sendMTX(mtx, passphrase);
101✔
1775
  }
1776

1777
  /**
1778
   * Create and send an open
1779
   * MTX with a lock.
1780
   * @param {String} name
1781
   * @param {Object} options
1782
   * @returns {Promise<TX>}
1783
   */
1784

1785
  async sendOpen(name, options) {
1786
    const unlock = await this.fundLock.lock();
104✔
1787
    try {
104✔
1788
      return await this._sendOpen(name, options);
104✔
1789
    } finally {
1790
      unlock();
104✔
1791
    }
1792
  }
1793

1794
  /**
1795
   * Make a bid MTX.
1796
   * @param {String} name
1797
   * @param {Number} value
1798
   * @param {Number} lockup
1799
   * @param {Number|String} acct
1800
   * @param {MTX?} [mtx]
1801
   * @param {Address?} [addr]
1802
   * @returns {Promise<MTX>}
1803
   */
1804

1805
  async makeBid(name, value, lockup, acct, mtx, addr) {
1806
    assert(typeof name === 'string');
2,945✔
1807
    assert(Number.isSafeInteger(value) && value >= 0);
2,945✔
1808
    assert(Number.isSafeInteger(lockup) && lockup >= 0);
2,945✔
1809
    assert((acct >>> 0) === acct || typeof acct === 'string');
2,945✔
1810
    assert(addr == null || addr instanceof Address);
2,945✔
1811

1812
    if (!rules.verifyName(name))
2,945!
1813
      throw new Error(`Invalid name: ${name}.`);
×
1814

1815
    const rawName = Buffer.from(name, 'ascii');
2,945✔
1816
    const nameHash = rules.hashName(rawName);
2,945✔
1817
    const height = this.wdb.height + 1;
2,945✔
1818
    const network = this.network;
2,945✔
1819

1820
    let ns = await this.getNameState(nameHash);
2,945✔
1821

1822
    if (!ns)
2,945✔
1823
      ns = await this.wdb.getNameStatus(nameHash);
163✔
1824

1825
    ns.maybeExpire(height, network);
2,945✔
1826

1827
    const start = ns.height;
2,945✔
1828

1829
    if (ns.isOpening(height, network))
2,945✔
1830
      throw new Error(`Name has not reached the bidding phase yet: ${name}.`);
2✔
1831

1832
    if (!ns.isBidding(height, network))
2,943!
1833
      throw new Error(`Name is not available: ${name}.`);
×
1834

1835
    if (value > lockup)
2,943!
1836
      throw new Error(
×
1837
        `Bid (${value}) exceeds lockup value (${lockup}): ${name}.`
1838
      );
1839

1840
    if (!addr)
2,943✔
1841
      addr = await this.receiveAddress(acct);
213✔
1842

1843
    const blind = await this.generateBlind(nameHash, addr, value);
2,943✔
1844

1845
    const output = new Output();
2,943✔
1846
    output.address = addr;
2,943✔
1847
    output.value = lockup;
2,943✔
1848
    output.covenant.setBid(nameHash, start, rawName, blind);
2,943✔
1849

1850
    if (!mtx)
2,943✔
1851
      mtx = new MTX();
213✔
1852
    mtx.outputs.push(output);
2,943✔
1853

1854
    return mtx;
2,943✔
1855
  }
1856

1857
  /**
1858
   * Create and finalize a bid
1859
   * MTX without a lock.
1860
   * @param {String} name
1861
   * @param {Number} value
1862
   * @param {Number} lockup
1863
   * @param {Object} options
1864
   * @returns {Promise<MTX>}
1865
   */
1866

1867
  async _createBid(name, value, lockup, options) {
1868
    const acct = options ? options.account || 0 : 0;
213✔
1869
    const mtx = await this.makeBid(name, value, lockup, acct);
213✔
1870
    await this.fill(mtx, options);
212✔
1871
    return this.finalize(mtx, options);
212✔
1872
  }
1873

1874
  /**
1875
   * Create and finalize a bid
1876
   * MTX with a lock.
1877
   * @param {String} name
1878
   * @param {Number} value
1879
   * @param {Number} lockup
1880
   * @param {Object} options
1881
   * @returns {Promise<MTX>}
1882
   */
1883

1884
  async createBid(name, value, lockup, options) {
1885
    const unlock = await this.fundLock.lock();
8✔
1886
    try {
8✔
1887
      return await this._createBid(name, value, lockup, options);
8✔
1888
    } finally {
1889
      unlock();
8✔
1890
    }
1891
  }
1892

1893
  /**
1894
   * Create and send a bid MTX.
1895
   * @param {String} name
1896
   * @param {Number} value
1897
   * @param {Number} lockup
1898
   * @param {Object} options
1899
   */
1900

1901
  async _sendBid(name, value, lockup, options) {
1902
    const passphrase = options ? options.passphrase : null;
203✔
1903
    const mtx = await this._createBid(name, value, lockup, options);
203✔
1904
    return this.sendMTX(mtx, passphrase);
202✔
1905
  }
1906

1907
  /**
1908
   * Create and send a bid MTX.
1909
   * @param {String} name
1910
   * @param {Number} value
1911
   * @param {Number} lockup
1912
   * @param {Object} options
1913
   * @returns {Promise<TX>}
1914
   */
1915

1916
  async sendBid(name, value, lockup, options) {
1917
    const unlock = await this.fundLock.lock();
203✔
1918
    try {
203✔
1919
      return await this._sendBid(name, value, lockup, options);
203✔
1920
    } finally {
1921
      unlock();
203✔
1922
    }
1923
  }
1924

1925
  /**
1926
   * @typedef {Object} CreateAuctionResults
1927
   * @param {MTX} bid
1928
   * @param {MTX} reveal
1929
   */
1930

1931
  /**
1932
   * Create and finalize a bid & a reveal (in advance)
1933
   * MTX with a lock.
1934
   * @param {String} name
1935
   * @param {Number} value
1936
   * @param {Number} lockup
1937
   * @param {Object} options
1938
   * @returns {Promise<CreateAuctionResults>}
1939
   */
1940

1941
  async createAuctionTXs(name, value, lockup, options) {
1942
    const unlock = await this.fundLock.lock();
2✔
1943
    try {
2✔
1944
      return await this._createAuctionTXs(name, value, lockup, options);
2✔
1945
    } finally {
1946
      unlock();
2✔
1947
    }
1948
  }
1949

1950
  /**
1951
   * Create and finalize a bid & a reveal (in advance)
1952
   * MTX without a lock.
1953
   * @param {String} name
1954
   * @param {Number} value
1955
   * @param {Number} lockup
1956
   * @param {Object} options
1957
   * @returns {Promise<CreateAuctionResults>}
1958
   */
1959

1960
  async _createAuctionTXs(name, value, lockup, options) {
1961
    const bid = await this._createBid(name, value, lockup, options);
2✔
1962

1963
    const bidOuputIndex = bid.outputs.findIndex(o => o.covenant.isBid());
2✔
1964
    const bidOutput = bid.outputs[bidOuputIndex];
2✔
1965
    const bidCoin = Coin.fromTX(bid, bidOuputIndex, -1);
2✔
1966

1967
    // Prepare the data needed to make the reveal in advance
1968
    const nameHash = bidOutput.covenant.getHash(0);
2✔
1969
    const height = bidOutput.covenant.getU32(1);
2✔
1970

1971
    const coins = [];
2✔
1972
    coins.push(bidCoin);
2✔
1973

1974
    const blind = bidOutput.covenant.getHash(3);
2✔
1975
    const bv = await this.getBlind(blind);
2✔
1976
    if (!bv)
2!
1977
      throw new Error(`Blind value not found for name: ${name}.`);
×
1978
    const { nonce } = bv;
2✔
1979

1980
    const reveal = new MTX();
2✔
1981
    const output = new Output();
2✔
1982
    output.address = bidCoin.address;
2✔
1983
    output.value = value;
2✔
1984
    output.covenant.setReveal(nameHash, height, nonce);
2✔
1985

1986
    reveal.addOutpoint(Outpoint.fromTX(bid, bidOuputIndex));
2✔
1987
    reveal.outputs.push(output);
2✔
1988

1989
    await this.fill(reveal, { ...options, coins: coins });
2✔
1990
    assert(
2✔
1991
      reveal.inputs.length === 1,
1992
      'Pre-signed REVEAL must not require additional inputs'
1993
    );
1994

1995
    const finalReveal = await this.finalize(reveal, options);
2✔
1996
    return { bid, reveal: finalReveal };
2✔
1997
  }
1998

1999
  /**
2000
   * Make a reveal MTX.
2001
   * @param {String} name
2002
   * @param {(Number|String)?} [acct]
2003
   * @param {MTX?} [mtx]
2004
   * @returns {Promise<MTX>}
2005
   */
2006

2007
  async makeReveal(name, acct, mtx) {
2008
    assert(typeof name === 'string');
277✔
2009

2010
    let acctno;
2011

2012
    if (acct != null) {
277✔
2013
      assert((acct >>> 0) === acct || typeof acct === 'string');
115✔
2014
      acctno = await this.getAccountIndex(acct);
115✔
2015
    }
2016

2017
    if (!rules.verifyName(name))
277✔
2018
      throw new Error(`Invalid name: ${name}.`);
1✔
2019

2020
    const rawName = Buffer.from(name, 'ascii');
276✔
2021
    const nameHash = rules.hashName(rawName);
276✔
2022
    const ns = await this.getNameState(nameHash);
276✔
2023
    const height = this.wdb.height + 1;
276✔
2024
    const network = this.network;
276✔
2025

2026
    if (!ns)
276!
2027
      throw new Error(`Auction not found: ${name}.`);
×
2028

2029
    ns.maybeExpire(height, network);
276✔
2030

2031
    const state = ns.state(height, network);
276✔
2032

2033
    if (state < states.REVEAL)
276!
2034
      throw new Error(`Cannot reveal yet: ${name}.`);
×
2035

2036
    if (state > states.REVEAL)
276!
2037
      throw new Error(`Reveal period has passed: ${name}.`);
×
2038

2039
    const bids = await this.getBids(nameHash);
276✔
2040

2041
    if (!mtx)
276✔
2042
      mtx = new MTX();
174✔
2043

2044
    let pushed = 0;
276✔
2045
    for (const {prevout, own} of bids) {
276✔
2046
      if (!own)
536✔
2047
        continue;
209✔
2048

2049
      const {hash, index} = prevout;
327✔
2050
      const coin = await this.getUnspentCoin(hash, index);
327✔
2051

2052
      if (!coin)
327✔
2053
        continue;
8✔
2054

2055
      if (acctno != null) {
319✔
2056
        if (!await this.txdb.hasCoinByAccount(acctno, hash, index))
126✔
2057
          continue;
1✔
2058
      }
2059

2060
      // Is local?
2061
      if (coin.height < ns.height)
318!
2062
        continue;
×
2063

2064
      const blind = coin.covenant.getHash(3);
318✔
2065
      const bv = await this.getBlind(blind);
318✔
2066

2067
      if (!bv) {
318✔
2068
        this.logger.warning(`Blind value not found for name: ${name}.`);
3✔
2069
        continue;
3✔
2070
      }
2071

2072
      const {value, nonce} = bv;
315✔
2073

2074
      const output = new Output();
315✔
2075
      output.address = coin.address;
315✔
2076
      output.value = value;
315✔
2077
      output.covenant.setReveal(nameHash, ns.height, nonce);
315✔
2078

2079
      mtx.addOutpoint(prevout);
315✔
2080
      mtx.outputs.push(output);
315✔
2081
      pushed++;
315✔
2082
    }
2083

2084
    if (pushed === 0)
276✔
2085
      throw new Error(`No bids to reveal for name: ${name}.`);
4✔
2086

2087
    return mtx;
272✔
2088
  }
2089

2090
  /**
2091
   * Create and finalize a reveal
2092
   * MTX without a lock.
2093
   * @param {String} name
2094
   * @param {Object} options
2095
   * @returns {Promise<MTX>}
2096
   */
2097

2098
  async _createReveal(name, options) {
2099
    const acct = options ? options.account : null;
174✔
2100
    const mtx = await this.makeReveal(name, acct);
174✔
2101
    await this.fill(mtx, options);
170✔
2102
    return this.finalize(mtx, options);
170✔
2103
  }
2104

2105
  /**
2106
   * Create and finalize a reveal
2107
   * MTX with a lock.
2108
   * @param {String} name
2109
   * @param {Object} options
2110
   * @returns {Promise<MTX>}
2111
   */
2112

2113
  async createReveal(name, options) {
2114
    const unlock = await this.fundLock.lock();
19✔
2115
    try {
19✔
2116
      return await this._createReveal(name, options);
19✔
2117
    } finally {
2118
      unlock();
19✔
2119
    }
2120
  }
2121

2122
  /**
2123
   * Create and send a reveal MTX.
2124
   * @param {String} name
2125
   * @param {Object} options
2126
   * @returns {Promise<TX>}
2127
   */
2128

2129
  async _sendReveal(name, options) {
2130
    const passphrase = options ? options.passphrase : null;
155✔
2131
    const mtx = await this._createReveal(name, options);
155✔
2132
    return this.sendMTX(mtx, passphrase);
152✔
2133
  }
2134

2135
  /**
2136
   * Create and send a bid MTX.
2137
   * @param {String} name
2138
   * @param {Object} options
2139
   * @returns {Promise<TX>}
2140
   */
2141

2142
  async sendReveal(name, options) {
2143
    const unlock = await this.fundLock.lock();
155✔
2144
    try {
155✔
2145
      return await this._sendReveal(name, options);
155✔
2146
    } finally {
2147
      unlock();
155✔
2148
    }
2149
  }
2150

2151
  /**
2152
   * Make a reveal MTX.
2153
   * @param {MTX?} [mtx]
2154
   * @param {Number?} [witnessSize]
2155
   * @returns {Promise<MTX>}
2156
   */
2157

2158
  async makeRevealAll(mtx, witnessSize) {
2159
    const height = this.wdb.height + 1;
41✔
2160
    const network = this.network;
41✔
2161
    const bids = await this.getBids();
41✔
2162

2163
    if (!mtx)
41✔
2164
      mtx = new MTX();
9✔
2165
    else
2166
      assert(witnessSize, 'Witness size required for batch size estimation.');
32✔
2167

2168
    let pushed = 0;
41✔
2169
    for (const {nameHash, prevout, own} of bids) {
41✔
2170
      if (!own)
61,672✔
2171
        continue;
8✔
2172

2173
      const ns = await this.getNameState(nameHash);
61,664✔
2174
      const name = ns.name;
61,664✔
2175

2176
      if (!ns)
61,664!
2177
        continue;
×
2178

2179
      ns.maybeExpire(height, network);
61,664✔
2180

2181
      if (!ns.isReveal(height, network))
61,664✔
2182
        continue;
53,955✔
2183

2184
      const {hash, index} = prevout;
7,709✔
2185
      const coin = await this.getUnspentCoin(hash, index);
7,709✔
2186

2187
      if (!coin)
7,709✔
2188
        continue;
3,974✔
2189

2190
      // Is local?
2191
      if (coin.height < ns.height)
3,735!
2192
        continue;
×
2193

2194
      const blind = coin.covenant.getHash(3);
3,735✔
2195
      const bv = await this.getBlind(blind);
3,735✔
2196

2197
      if (!bv) {
3,735!
2198
        this.logger.warning(`Blind value not found for name: ${name}.`);
×
2199
        continue;
×
2200
      }
2201

2202
      const {value, nonce} = bv;
3,735✔
2203

2204
      const output = new Output();
3,735✔
2205
      output.address = coin.address;
3,735✔
2206
      output.value = value;
3,735✔
2207
      output.covenant.setReveal(nameHash, ns.height, nonce);
3,735✔
2208

2209
      mtx.addOutpoint(prevout);
3,735✔
2210
      mtx.outputs.push(output);
3,735✔
2211

2212
      // Keep batches below policy size limit
2213
      if (this.isOversizedBatch(mtx, witnessSize)) {
3,735✔
2214
        mtx.inputs.pop();
4✔
2215
        mtx.outputs.pop();
4✔
2216
        break;
4✔
2217
      }
2218

2219
      pushed++;
3,731✔
2220
    }
2221

2222
    // Ignore in batches
2223
    if (pushed === 0 && !witnessSize)
41!
2224
      throw new Error('No bids to reveal.');
×
2225

2226
    return mtx;
41✔
2227
  }
2228

2229
  /**
2230
   * Create and finalize a reveal all
2231
   * MTX without a lock.
2232
   * @param {Object} options
2233
   * @returns {Promise<MTX>}
2234
   */
2235

2236
  async _createRevealAll(options) {
2237
    const mtx = await this.makeRevealAll();
9✔
2238
    await this.fill(mtx, options);
9✔
2239
    return this.finalize(mtx, options);
9✔
2240
  }
2241

2242
  /**
2243
   * Create and finalize a reveal all
2244
   * MTX with a lock.
2245
   * @param {Object} options
2246
   * @returns {Promise<MTX>}
2247
   */
2248

2249
  async createRevealAll(options) {
2250
    const unlock = await this.fundLock.lock();
1✔
2251
    try {
1✔
2252
      return await this._createRevealAll(options);
1✔
2253
    } finally {
2254
      unlock();
1✔
2255
    }
2256
  }
2257

2258
  /**
2259
   * Create and send a reveal all MTX.
2260
   * @param {Object} options
2261
   * @returns {Promise<TX>}
2262
   */
2263

2264
  async _sendRevealAll(options) {
2265
    const passphrase = options ? options.passphrase : null;
8✔
2266
    const mtx = await this._createRevealAll(options);
8✔
2267
    return this.sendMTX(mtx, passphrase);
8✔
2268
  }
2269

2270
  /**
2271
   * Create and send a bid MTX.
2272
   * @param {Object} options
2273
   * @returns {Promise<TX>}
2274
   */
2275

2276
  async sendRevealAll(options) {
2277
    const unlock = await this.fundLock.lock();
8✔
2278
    try {
8✔
2279
      return await this._sendRevealAll(options);
8✔
2280
    } finally {
2281
      unlock();
8✔
2282
    }
2283
  }
2284

2285
  /**
2286
   * Make a redeem MTX.
2287
   * @param {String} name
2288
   * @param {(Number|String)?} [acct]
2289
   * @param {MTX?} [mtx]
2290
   * @returns {Promise<MTX>}
2291
   */
2292

2293
  async makeRedeem(name, acct, mtx) {
2294
    assert(typeof name === 'string');
38✔
2295

2296
    if (!rules.verifyName(name))
38!
2297
      throw new Error(`Invalid name: ${name}.`);
×
2298

2299
    let acctno;
2300

2301
    if (acct != null) {
38✔
2302
      assert((acct >>> 0) === acct || typeof acct === 'string');
25✔
2303
      acctno = await this.getAccountIndex(acct);
25✔
2304
    }
2305

2306
    const rawName = Buffer.from(name, 'ascii');
38✔
2307
    const nameHash = rules.hashName(rawName);
38✔
2308
    const ns = await this.getNameState(nameHash);
38✔
2309
    const height = this.wdb.height + 1;
38✔
2310
    const network = this.network;
38✔
2311

2312
    if (!ns)
38!
2313
      throw new Error(`Auction not found: ${name}.`);
×
2314

2315
    if (ns.isExpired(height, network))
38!
2316
      throw new Error(`Name has expired: ${name}.`);
×
2317

2318
    if (!ns.isRedeemable(height, network))
38!
2319
      throw new Error(`Auction is not yet closed: ${name}.`);
×
2320

2321
    const reveals = await this.txdb.getReveals(nameHash);
38✔
2322

2323
    if (!mtx)
38✔
2324
      mtx = new MTX();
25✔
2325

2326
    let pushed = 0;
38✔
2327
    for (const {prevout, own} of reveals) {
38✔
2328
      const {hash, index} = prevout;
80✔
2329

2330
      if (!own)
80✔
2331
        continue;
17✔
2332

2333
      // Winner can not redeem
2334
      if (prevout.equals(ns.owner))
63✔
2335
        continue;
19✔
2336

2337
      const coin = await this.getUnspentCoin(hash, index);
44✔
2338

2339
      if (!coin)
44✔
2340
        continue;
5✔
2341

2342
      if (acctno != null) {
39✔
2343
        if (!await this.txdb.hasCoinByAccount(acctno, hash, index))
25✔
2344
          continue;
1✔
2345
      }
2346

2347
      // Is local?
2348
      if (coin.height < ns.height)
38!
2349
        continue;
×
2350

2351
      mtx.addOutpoint(prevout);
38✔
2352

2353
      const output = new Output();
38✔
2354
      output.address = coin.address;
38✔
2355
      output.value = coin.value;
38✔
2356
      output.covenant.setRedeem(nameHash, ns.height);
38✔
2357

2358
      mtx.outputs.push(output);
38✔
2359
      pushed++;
38✔
2360
    }
2361

2362
    if (pushed === 0)
38✔
2363
      throw new Error(`No reveals to redeem for name: ${name}.`);
4✔
2364

2365
    return mtx;
34✔
2366
  }
2367

2368
  /**
2369
   * Create and finalize a redeem
2370
   * MTX without a lock.
2371
   * @param {String} name
2372
   * @param {Object} options
2373
   * @returns {Promise<MTX>}
2374
   */
2375

2376
  async _createRedeem(name, options) {
2377
    const acct = options ? options.account : null;
25✔
2378
    const mtx = await this.makeRedeem(name, acct);
25✔
2379
    await this.fill(mtx, options);
21✔
2380
    return this.finalize(mtx, options);
21✔
2381
  }
2382

2383
  /**
2384
   * Create and finalize a redeem
2385
   * MTX with a lock.
2386
   * @param {String} name
2387
   * @param {Object} options
2388
   * @returns {Promise<MTX>}
2389
   */
2390

2391
  async createRedeem(name, options) {
2392
    const unlock = await this.fundLock.lock();
2✔
2393
    try {
2✔
2394
      return await this._createRedeem(name, options);
2✔
2395
    } finally {
2396
      unlock();
2✔
2397
    }
2398
  }
2399

2400
  /**
2401
   * Create and send a redeem
2402
   * MTX without a lock.
2403
   * @param {String} name
2404
   * @param {Object} options
2405
   * @returns {Promise<TX>}
2406
   */
2407

2408
  async _sendRedeem(name, options) {
2409
    const passphrase = options ? options.passphrase : null;
23✔
2410
    const mtx = await this._createRedeem(name, options);
23✔
2411
    return this.sendMTX(mtx, passphrase);
19✔
2412
  }
2413

2414
  /**
2415
   * Create and send a redeem
2416
   * MTX with a lock.
2417
   * @param {String} name
2418
   * @param {Object} options
2419
   * @returns {Promise<TX>}
2420
   */
2421

2422
  async sendRedeem(name, options) {
2423
    const unlock = await this.fundLock.lock();
23✔
2424
    try {
23✔
2425
      return await this._sendRedeem(name, options);
23✔
2426
    } finally {
2427
      unlock();
23✔
2428
    }
2429
  }
2430

2431
  /**
2432
   * Make a redeem MTX.
2433
   * @param {MTX?} [mtx]
2434
   * @param {Number?} [witnessSize]
2435
   * @returns {Promise<MTX>}
2436
   */
2437

2438
  async makeRedeemAll(mtx, witnessSize) {
2439
    const height = this.wdb.height + 1;
37✔
2440
    const network = this.network;
37✔
2441
    const reveals = await this.txdb.getReveals();
37✔
2442

2443
    if (!mtx)
37✔
2444
      mtx = new MTX();
6✔
2445
    else
2446
      assert(witnessSize, 'Witness size required for batch size estimation.');
31✔
2447

2448
    let pushed = 0;
37✔
2449
    for (const {nameHash, prevout, own} of reveals) {
37✔
2450
      const {hash, index} = prevout;
56,855✔
2451

2452
      const ns = await this.getNameState(nameHash);
56,855✔
2453

2454
      if (!ns)
56,855!
2455
        continue;
×
2456

2457
      if (ns.isExpired(height, network))
56,855✔
2458
        continue;
14,250✔
2459

2460
      if (!ns.isRedeemable(height, network))
42,605!
2461
        continue;
×
2462

2463
      if (!own)
42,605✔
2464
        continue;
3✔
2465

2466
      if (prevout.equals(ns.owner))
42,602✔
2467
        continue;
2,361✔
2468

2469
      const coin = await this.getUnspentCoin(hash, index);
40,241✔
2470

2471
      if (!coin)
40,241✔
2472
        continue;
37,923✔
2473

2474
      // Is local?
2475
      if (coin.height < ns.height)
2,318!
2476
        continue;
×
2477

2478
      const output = new Output();
2,318✔
2479
      output.address = coin.address;
2,318✔
2480
      output.value = coin.value;
2,318✔
2481
      output.covenant.setRedeem(nameHash, ns.height);
2,318✔
2482

2483
      mtx.addOutpoint(prevout);
2,318✔
2484
      mtx.outputs.push(output);
2,318✔
2485

2486
      // Keep batches below policy size limit
2487
      if (this.isOversizedBatch(mtx, witnessSize)) {
2,318✔
2488
        mtx.inputs.pop();
2✔
2489
        mtx.outputs.pop();
2✔
2490
        break;
2✔
2491
      }
2492

2493
      pushed++;
2,316✔
2494
    }
2495

2496
    // Ignore in batches
2497
    if (pushed === 0 && !witnessSize)
37!
2498
      throw new Error('No reveals to redeem.');
×
2499

2500
    return mtx;
37✔
2501
  }
2502

2503
  /**
2504
   * Create and finalize a redeem
2505
   * all MTX without a lock.
2506
   * @param {Object} options
2507
   * @returns {Promise<MTX>}
2508
   */
2509

2510
  async _createRedeemAll(options) {
2511
    const mtx = await this.makeRedeemAll();
6✔
2512
    await this.fill(mtx, options);
6✔
2513
    return this.finalize(mtx, options);
6✔
2514
  }
2515

2516
  /**
2517
   * Create and finalize a redeem
2518
   * all MTX with a lock.
2519
   * @param {Object} options
2520
   * @returns {Promise<MTX>}
2521
   */
2522

2523
  async createRedeemAll(options) {
2524
    const unlock = await this.fundLock.lock();
1✔
2525
    try {
1✔
2526
      return await this._createRedeemAll(options);
1✔
2527
    } finally {
2528
      unlock();
1✔
2529
    }
2530
  }
2531

2532
  /**
2533
   * Create and send a redeem all
2534
   * MTX without a lock.
2535
   * @param {Object} options
2536
   * @returns {Promise<TX>}
2537
   */
2538

2539
  async _sendRedeemAll(options) {
2540
    const passphrase = options ? options.passphrase : null;
5✔
2541
    const mtx = await this._createRedeemAll(options);
5✔
2542
    return this.sendMTX(mtx, passphrase);
5✔
2543
  }
2544

2545
  /**
2546
   * Create and send a redeem all
2547
   * MTX with a lock.
2548
   * @param {Object} options
2549
   * @returns {Promise<TX>}
2550
   */
2551

2552
  async sendRedeemAll(options) {
2553
    const unlock = await this.fundLock.lock();
5✔
2554
    try {
5✔
2555
      return await this._sendRedeemAll(options);
5✔
2556
    } finally {
2557
      unlock();
5✔
2558
    }
2559
  }
2560

2561
  /**
2562
   * Make a register MTX.
2563
   * @private
2564
   * @param {String} name
2565
   * @param {Resource?} resource
2566
   * @param {MTX?} [mtx]
2567
   * @returns {Promise<MTX>}
2568
   */
2569

2570
  async _makeRegister(name, resource, mtx) {
2571
    assert(typeof name === 'string');
930✔
2572
    assert(!resource || (resource instanceof Resource));
930✔
2573

2574
    if (!rules.verifyName(name))
930!
2575
      throw new Error(`Invalid name: ${name}.`);
×
2576

2577
    const rawName = Buffer.from(name, 'ascii');
930✔
2578
    const nameHash = rules.hashName(rawName);
930✔
2579
    const ns = await this.getNameState(nameHash);
930✔
2580
    const height = this.wdb.height + 1;
930✔
2581
    const network = this.network;
930✔
2582

2583
    if (!ns)
930!
2584
      throw new Error(`Auction not found: ${name}.`);
×
2585

2586
    const {hash, index} = ns.owner;
930✔
2587
    const credit = await this.getCredit(hash, index);
930✔
2588

2589
    if (!credit)
930!
2590
      throw new Error(`Wallet did not win the auction: ${name}.`);
×
2591

2592
    if (credit.spent)
930!
2593
      throw new Error(`Credit is already pending for: ${name}.`);
×
2594

2595
    if (ns.isExpired(height, network))
930!
2596
      throw new Error(`Name has expired: ${name}.`);
×
2597

2598
    const coin = credit.coin;
930✔
2599

2600
    // Is local?
2601
    if (coin.height < ns.height)
930!
2602
      throw new Error(`Wallet did not win the auction: ${name}.`);
×
2603

2604
    if (!coin.covenant.isReveal() && !coin.covenant.isClaim())
930!
2605
      throw new Error(`Name is not in REVEAL or CLAIM state: ${name}.`);
×
2606

2607
    if (coin.covenant.isClaim()) {
930✔
2608
      if (height < coin.height + network.coinbaseMaturity)
2!
2609
        throw new Error(`Claim is not yet mature: ${name}.`);
×
2610
    }
2611

2612
    if (!ns.isClosed(height, network))
930!
2613
      throw new Error(`Auction is not yet closed: ${name}.`);
×
2614

2615
    const output = new Output();
930✔
2616
    output.address = coin.address;
930✔
2617
    output.value = ns.value;
930✔
2618

2619
    let rawResource = EMPTY;
930✔
2620

2621
    if (resource) {
930!
2622
      const raw = resource.encode();
930✔
2623

2624
      if (raw.length > rules.MAX_RESOURCE_SIZE)
930!
2625
        throw new Error(
×
2626
          `Resource size (${raw.length}) exceeds maximum `+
2627
          `(${rules.MAX_RESOURCE_SIZE}) for name: ${name}.`
2628
        );
2629

2630
      rawResource = raw;
930✔
2631
    }
2632

2633
    const blockHash = await this.wdb.getRenewalBlock();
930✔
2634

2635
    output.covenant.setRegister(nameHash, ns.height, rawResource, blockHash);
930✔
2636

2637
    if (!mtx)
930✔
2638
      mtx = new MTX();
39✔
2639
    mtx.addOutpoint(ns.owner);
930✔
2640
    mtx.outputs.push(output);
930✔
2641

2642
    return mtx;
930✔
2643
  }
2644

2645
  /**
2646
   * Make an update MTX.
2647
   * @param {String} name
2648
   * @param {Resource} resource
2649
   * @param {(Number|String)?} acct
2650
   * @param {MTX?} [mtx]
2651
   * @returns {Promise<MTX>}
2652
   */
2653

2654
  async makeUpdate(name, resource, acct, mtx) {
2655
    assert(typeof name === 'string');
1,562✔
2656
    assert(resource instanceof Resource);
1,562✔
2657

2658
    if (!rules.verifyName(name))
1,562!
2659
      throw new Error('Invalid name.');
×
2660

2661
    let acctno;
2662

2663
    if (acct != null) {
1,562✔
2664
      assert((acct >>> 0) === acct || typeof acct === 'string');
1,516✔
2665
      acctno = await this.getAccountIndex(acct);
1,516✔
2666
    }
2667

2668
    const rawName = Buffer.from(name, 'ascii');
1,562✔
2669
    const nameHash = rules.hashName(rawName);
1,562✔
2670
    const ns = await this.getNameState(nameHash);
1,562✔
2671
    const height = this.wdb.height + 1;
1,562✔
2672
    const network = this.network;
1,562✔
2673

2674
    if (!ns)
1,562!
2675
      throw new Error(`Auction not found: ${name}.`);
×
2676

2677
    const {hash, index} = ns.owner;
1,562✔
2678
    const credit = await this.getCredit(hash, index);
1,562✔
2679

2680
    if (!credit)
1,562!
2681
      throw new Error(`Wallet does not own name: ${name}.`);
×
2682

2683
    if (credit.spent)
1,562✔
2684
      throw new Error(`Credit is already pending for: ${name}.`);
2✔
2685

2686
    if (acctno != null) {
1,560✔
2687
      if (!await this.txdb.hasCoinByAccount(acctno, hash, index))
1,516✔
2688
        throw new Error(`Account does not own name: ${name}.`);
1✔
2689
    }
2690

2691
    const coin = credit.coin;
1,559✔
2692

2693
    if (coin.covenant.isReveal() || coin.covenant.isClaim())
1,559✔
2694
      return this._makeRegister(name, resource, mtx);
930✔
2695

2696
    if (ns.isExpired(height, network))
629!
2697
      throw new Error(`Name has expired: ${name}.`);
×
2698

2699
    // Is local?
2700
    if (coin.height < ns.height)
629!
2701
      throw new Error(`Wallet does not own name: ${name}.`);
×
2702

2703
    if (!ns.isClosed(height, network))
629!
2704
      throw new Error(`Auction is not yet closed: ${name}.`);
×
2705

2706
    if (!coin.covenant.isRegister()
629!
2707
        && !coin.covenant.isUpdate()
2708
        && !coin.covenant.isRenew()
2709
        && !coin.covenant.isFinalize()) {
2710
      throw new Error(`Name is not registered: ${name}.`);
×
2711
    }
2712

2713
    const raw = resource.encode();
629✔
2714

2715
    if (raw.length > rules.MAX_RESOURCE_SIZE)
629!
2716
      throw new Error(`Resource exceeds maximum size: ${name}.`);
×
2717

2718
    const output = new Output();
629✔
2719
    output.address = coin.address;
629✔
2720
    output.value = coin.value;
629✔
2721
    output.covenant.setUpdate(nameHash, ns.height, raw);
629✔
2722

2723
    if (!mtx)
629✔
2724
      mtx = new MTX();
16✔
2725
    mtx.addOutpoint(ns.owner);
629✔
2726
    mtx.outputs.push(output);
629✔
2727

2728
    return mtx;
629✔
2729
  }
2730

2731
  /**
2732
   * Create and finalize an update
2733
   * MTX without a lock.
2734
   * @param {String} name
2735
   * @param {Resource} resource
2736
   * @param {Object} options
2737
   * @returns {Promise<MTX>}
2738
   */
2739

2740
  async _createUpdate(name, resource, options) {
2741
    const acct = options ? options.account : null;
58✔
2742
    const mtx = await this.makeUpdate(name, resource, acct);
58✔
2743
    await this.fill(mtx, options);
55✔
2744
    return this.finalize(mtx, options);
55✔
2745
  }
2746

2747
  /**
2748
   * Create and finalize an update
2749
   * MTX with a lock.
2750
   * @param {String} name
2751
   * @param {Resource} resource
2752
   * @param {Object} options
2753
   * @returns {Promise<MTX>}
2754
   */
2755

2756
  async createUpdate(name, resource, options) {
2757
    const unlock = await this.fundLock.lock();
2✔
2758
    try {
2✔
2759
      return await this._createUpdate(name, resource, options);
2✔
2760
    } finally {
2761
      unlock();
2✔
2762
    }
2763
  }
2764

2765
  /**
2766
   * Create and send an update
2767
   * MTX without a lock.
2768
   * @param {String} name
2769
   * @param {Resource} resource
2770
   * @param {Object} options
2771
   * @returns {Promise<TX>}
2772
   */
2773

2774
  async _sendUpdate(name, resource, options) {
2775
    const passphrase = options ? options.passphrase : null;
56✔
2776
    const mtx = await this._createUpdate(name, resource, options);
56✔
2777
    return this.sendMTX(mtx, passphrase);
53✔
2778
  }
2779

2780
  /**
2781
   * Create and send an update
2782
   * MTX with a lock.
2783
   * @param {String} name
2784
   * @param {Resource} resource
2785
   * @param {Object} options
2786
   * @returns {Promise<TX>}
2787
   */
2788

2789
  async sendUpdate(name, resource, options) {
2790
    const unlock = await this.fundLock.lock();
56✔
2791
    try {
56✔
2792
      return await this._sendUpdate(name, resource, options);
56✔
2793
    } finally {
2794
      unlock();
56✔
2795
    }
2796
  }
2797

2798
  /**
2799
   * Make a renewal MTX.
2800
   * @private
2801
   * @param {String} name
2802
   * @param {(Number|String)?} acct
2803
   * @param {MTX?} [mtx]
2804
   * @returns {Promise<MTX>}
2805
   */
2806

2807
  async makeRenewal(name, acct, mtx) {
2808
    assert(typeof name === 'string');
628✔
2809

2810
    if (!rules.verifyName(name))
628!
2811
      throw new Error(`Invalid name: ${name}.`);
×
2812

2813
    let acctno;
2814

2815
    if (acct != null) {
628✔
2816
      assert((acct >>> 0) === acct || typeof acct === 'string');
617✔
2817
      acctno = await this.getAccountIndex(acct);
617✔
2818
    }
2819

2820
    const rawName = Buffer.from(name, 'ascii');
628✔
2821
    const nameHash = rules.hashName(rawName);
628✔
2822
    const ns = await this.getNameState(nameHash);
628✔
2823
    const height = this.wdb.height + 1;
628✔
2824
    const network = this.network;
628✔
2825

2826
    if (!ns)
628!
2827
      throw new Error(`Auction not found: ${name}.`);
×
2828

2829
    const {hash, index} = ns.owner;
628✔
2830
    const credit = await this.getCredit(hash, index);
628✔
2831

2832
    if (!credit)
628!
2833
      throw new Error(`Wallet does not own name: ${name}.`);
×
2834

2835
    if (credit.spent) {
628✔
2836
      throw new Error(`Credit is already pending for: ${name}.`);
1✔
2837
    }
2838

2839
    if (ns.isExpired(height, network))
627!
2840
      throw new Error(`Name has expired: ${name}.`);
×
2841

2842
    const coin = credit.coin;
627✔
2843

2844
    // Is local?
2845
    if (coin.height < ns.height)
627!
2846
      throw new Error(`Wallet does not own name: ${name}.`);
×
2847

2848
    if (acctno != null) {
627✔
2849
      if (!await this.txdb.hasCoinByAccount(acctno, hash, index))
617✔
2850
        throw new Error(`Account does not own name: ${name}.`);
1✔
2851
    }
2852

2853
    if (!ns.isClosed(height, network))
626!
2854
      throw new Error(`Auction is not yet closed: ${name}.`);
×
2855

2856
    if (!coin.covenant.isRegister()
626!
2857
        && !coin.covenant.isUpdate()
2858
        && !coin.covenant.isRenew()
2859
        && !coin.covenant.isFinalize()) {
2860
      throw new Error(`Name is not registered: ${name}.`);
×
2861
    }
2862

2863
    if (height < ns.renewal + network.names.treeInterval)
626✔
2864
      throw new Error(`Can not renew yet: ${name}.`);
1✔
2865

2866
    const output = new Output();
625✔
2867
    output.address = coin.address;
625✔
2868
    output.value = coin.value;
625✔
2869
    const blockHash = await this.wdb.getRenewalBlock();
625✔
2870
    output.covenant.setRenew(nameHash, ns.height, blockHash);
625✔
2871

2872
    if (!mtx)
625✔
2873
      mtx = new MTX();
10✔
2874
    mtx.addOutpoint(ns.owner);
625✔
2875
    mtx.outputs.push(output);
625✔
2876

2877
    return mtx;
625✔
2878
  }
2879

2880
  /**
2881
   * Make a renewal MTX for all expiring names.
2882
   * @param {MTX?} mtx
2883
   * @param {Number?} witnessSize
2884
   * @returns {Promise<MTX>}
2885
   */
2886

2887
  async makeRenewalAll(mtx, witnessSize) {
2888
    // Only allowed in makeBatch
2889
    assert(mtx, 'Batch MTX required for makeRenewalAll.');
26✔
2890
    assert(witnessSize, 'Witness size required for batch size estimation.');
26✔
2891
    const height = this.wdb.height + 1;
26✔
2892
    const network = this.network;
26✔
2893
    const names = await this.getNames();
26✔
2894

2895
    let expiring = [];
26✔
2896
    for (const ns of names) {
26✔
2897
      // Easiest check is for expiring time, do that first
2898
      if (ns.isExpired(height, network))
20,826✔
2899
        continue;
19✔
2900

2901
      // TODO: Should this factor of 8 be user-configurable?
2902
      // About 90 days on main (1.75 years after REGISTER)
2903
      // 625 blocks on regtest (4375 blocks after REGISTER)
2904
      const blocksLeft = (ns.renewal + network.names.renewalWindow) - height;
20,807✔
2905
      if (blocksLeft >= network.names.renewalWindow / 8)
20,807✔
2906
        continue;
17,207✔
2907

2908
      if (height < ns.renewal + network.names.treeInterval)
3,600!
2909
        continue; // Can not renew yet
×
2910

2911
      // Now do the db lookups to see if we own the name
2912
      const {hash, index} = ns.owner;
3,600✔
2913
      const coin = await this.getUnspentCoin(hash, index);
3,600✔
2914
      if (!coin)
3,600!
2915
        continue;
×
2916

2917
      if (!coin.covenant.isRegister()
3,600!
2918
          && !coin.covenant.isUpdate()
2919
          && !coin.covenant.isRenew()
2920
          && !coin.covenant.isFinalize()) {
2921
        continue; // Name is not yet registered
×
2922
      }
2923

2924
      expiring.push({ns, coin});
3,600✔
2925
    }
2926

2927
    // Ignore in batches
2928
    if (!expiring.length)
26✔
2929
      return mtx;
18✔
2930

2931
    // Sort by urgency, oldest/lowest renewal heights go first
2932
    expiring.sort((a, b) => {
8✔
2933
      return a.ns.renewal - b.ns.renewal;
20,646✔
2934
    });
2935

2936
    // TODO: Should this factor of 6 be user-configurable?
2937
    // Enforce consensus limit per block at a maxmium
2938
    expiring = expiring.slice(0, consensus.MAX_BLOCK_RENEWALS / 6);
8✔
2939

2940
    const renewalBlock = await this.wdb.getRenewalBlock();
8✔
2941
    for (const {ns, coin} of expiring) {
8✔
2942
      const output = new Output();
800✔
2943
      output.address = coin.address;
800✔
2944
      output.value = coin.value;
800✔
2945
      output.covenant.setRenew(ns.nameHash, ns.height, renewalBlock);
800✔
2946

2947
      mtx.addOutpoint(new Outpoint(coin.hash, coin.index));
800✔
2948
      mtx.outputs.push(output);
800✔
2949

2950
      // Keep batches below policy size limit
2951
      if (this.isOversizedBatch(mtx, witnessSize)) {
800!
2952
        mtx.inputs.pop();
×
2953
        mtx.outputs.pop();
×
2954
        break;
×
2955
      }
2956
    }
2957

2958
    return mtx;
8✔
2959
  }
2960

2961
  /**
2962
   * Create and finalize a renewal
2963
   * MTX without a lock.
2964
   * @param {String} name
2965
   * @param {Object} options
2966
   * @returns {Promise<MTX>}
2967
   */
2968

2969
  async _createRenewal(name, options) {
2970
    const acct = options ? options.account : null;
13✔
2971
    const mtx = await this.makeRenewal(name, acct);
13✔
2972
    await this.fill(mtx, options);
10✔
2973
    return this.finalize(mtx, options);
10✔
2974
  }
2975

2976
  /**
2977
   * Create and finalize a renewal
2978
   * MTX with a lock.
2979
   * @param {String} name
2980
   * @param {Object} options
2981
   * @returns {Promise<MTX>}
2982
   */
2983

2984
  async createRenewal(name, options) {
2985
    const unlock = await this.fundLock.lock();
1✔
2986
    try {
1✔
2987
      return await this._createRenewal(name, options);
1✔
2988
    } finally {
2989
      unlock();
1✔
2990
    }
2991
  }
2992

2993
  /**
2994
   * Create and send a renewal
2995
   * MTX without a lock.
2996
   * @param {String} name
2997
   * @param {Object} options
2998
   * @returns {Promise<TX>}
2999
   */
3000

3001
  async _sendRenewal(name, options) {
3002
    const passphrase = options ? options.passphrase : null;
12✔
3003
    const mtx = await this._createRenewal(name, options);
12✔
3004
    return this.sendMTX(mtx, passphrase);
9✔
3005
  }
3006

3007
  /**
3008
   * Create and send a renewal
3009
   * MTX with a lock.
3010
   * @param {String} name
3011
   * @param {Object} options
3012
   * @returns {Promise<TX>}
3013
   */
3014

3015
  async sendRenewal(name, options) {
3016
    const unlock = await this.fundLock.lock();
12✔
3017
    try {
12✔
3018
      return await this._sendRenewal(name, options);
12✔
3019
    } finally {
3020
      unlock();
12✔
3021
    }
3022
  }
3023

3024
  /**
3025
   * Make a transfer MTX.
3026
   * @param {String} name
3027
   * @param {Address} address
3028
   * @param {(Number|String)?} acct
3029
   * @param {MTX?} [mtx]
3030
   * @returns {Promise<MTX>}
3031
   */
3032

3033
  async makeTransfer(name, address, acct, mtx) {
3034
    assert(typeof name === 'string');
1,469✔
3035
    assert(address instanceof Address);
1,469✔
3036

3037
    if (!rules.verifyName(name))
1,469!
3038
      throw new Error(`Invalid name: ${name}.`);
×
3039

3040
    let acctno;
3041

3042
    if (acct != null) {
1,469✔
3043
      assert((acct >>> 0) === acct || typeof acct === 'string');
1,442✔
3044
      acctno = await this.getAccountIndex(acct);
1,442✔
3045
    }
3046

3047
    const rawName = Buffer.from(name, 'ascii');
1,469✔
3048
    const nameHash = rules.hashName(rawName);
1,469✔
3049
    const ns = await this.getNameState(nameHash);
1,469✔
3050
    const height = this.wdb.height + 1;
1,469✔
3051
    const network = this.network;
1,469✔
3052

3053
    if (!ns)
1,469✔
3054
      throw new Error(`Auction not found: ${name}.`);
1✔
3055

3056
    const {hash, index} = ns.owner;
1,468✔
3057
    const credit = await this.getCredit(hash, index);
1,468✔
3058

3059
    if (!credit)
1,468✔
3060
      throw new Error(`Wallet does not own name: ${name}.`);
1✔
3061

3062
    if (credit.spent)
1,467✔
3063
      throw new Error(`Credit is already pending for: ${name}.`);
1✔
3064

3065
    if (ns.isExpired(height, network))
1,466!
3066
      throw new Error(`Name has expired: ${name}.`);
×
3067

3068
    const coin = credit.coin;
1,466✔
3069

3070
    // Is local?
3071
    if (coin.height < ns.height)
1,466!
3072
      throw new Error(`Wallet does not own name: ${name}.`);
×
3073

3074
    if (acctno != null) {
1,466✔
3075
      if (!await this.txdb.hasCoinByAccount(acctno, hash, index))
1,442✔
3076
        throw new Error(`Account does not own name: ${name}.`);
1✔
3077
    }
3078

3079
    if (!ns.isClosed(height, network))
1,465!
3080
      throw new Error(`Auction is not yet closed: ${name}.`);
×
3081

3082
    if (coin.covenant.isTransfer())
1,465✔
3083
      throw new Error(`Name is already being transferred: ${name}.`);
1✔
3084

3085
    if (!coin.covenant.isRegister()
1,464✔
3086
        && !coin.covenant.isUpdate()
3087
        && !coin.covenant.isRenew()
3088
        && !coin.covenant.isFinalize()) {
3089
      throw new Error(`Name is not registered: ${name}.`);
1✔
3090
    }
3091

3092
    const output = new Output();
1,463✔
3093
    output.address = coin.address;
1,463✔
3094
    output.value = coin.value;
1,463✔
3095
    output.covenant.setTransfer(nameHash, ns.height, address);
1,463✔
3096

3097
    if (!mtx)
1,463✔
3098
      mtx = new MTX();
23✔
3099
    mtx.addOutpoint(ns.owner);
1,463✔
3100
    mtx.outputs.push(output);
1,463✔
3101

3102
    return mtx;
1,463✔
3103
  }
3104

3105
  /**
3106
   * Create and finalize a transfer
3107
   * MTX without a lock.
3108
   * @param {String} name
3109
   * @param {Address} address
3110
   * @param {Object} options
3111
   * @returns {Promise<MTX>}
3112
   */
3113

3114
  async _createTransfer(name, address, options) {
3115
    const acct = options ? options.account : null;
29✔
3116
    const mtx = await this.makeTransfer(name, address, acct);
29✔
3117
    await this.fill(mtx, options);
23✔
3118
    return this.finalize(mtx, options);
23✔
3119
  }
3120

3121
  /**
3122
   * Create and finalize a transfer
3123
   * MTX with a lock.
3124
   * @param {String} name
3125
   * @param {Address} address
3126
   * @param {Object} options
3127
   * @returns {Promise<MTX>}
3128
   */
3129

3130
  async createTransfer(name, address, options) {
3131
    const unlock = await this.fundLock.lock();
2✔
3132
    try {
2✔
3133
      return await this._createTransfer(name, address, options);
2✔
3134
    } finally {
3135
      unlock();
2✔
3136
    }
3137
  }
3138

3139
  /**
3140
   * Create and send a transfer
3141
   * MTX without a lock.
3142
   * @param {String} name
3143
   * @param {Address} address
3144
   * @param {Object} options
3145
   * @returns {Promise<TX>}
3146
   */
3147

3148
  async _sendTransfer(name, address, options) {
3149
    const passphrase = options ? options.passphrase : null;
27✔
3150
    const mtx = await this._createTransfer(
27✔
3151
      name,
3152
      address,
3153
      options
3154
    );
3155
    return this.sendMTX(mtx, passphrase);
21✔
3156
  }
3157

3158
  /**
3159
   * Create and send a transfer
3160
   * MTX with a lock.
3161
   * @param {String} name
3162
   * @param {Address} address
3163
   * @param {Object} options
3164
   * @returns {Promise<TX>}
3165
   */
3166

3167
  async sendTransfer(name, address, options) {
3168
    const unlock = await this.fundLock.lock();
27✔
3169
    try {
27✔
3170
      return await this._sendTransfer(name, address, options);
27✔
3171
    } finally {
3172
      unlock();
27✔
3173
    }
3174
  }
3175

3176
  /**
3177
   * Make a transfer-cancelling MTX.
3178
   * @private
3179
   * @param {String} name
3180
   * @param {(Number|String)?} acct
3181
   * @param {MTX?} [mtx]
3182
   * @returns {Promise<MTX>}
3183
   */
3184

3185
  async makeCancel(name, acct, mtx) {
3186
    assert(typeof name === 'string');
9✔
3187

3188
    if (!rules.verifyName(name))
9!
3189
      throw new Error(`Invalid name: ${name}.`);
×
3190

3191
    let acctno;
3192

3193
    if (acct != null) {
9✔
3194
      assert((acct >>> 0) === acct || typeof acct === 'string');
3✔
3195
      acctno = await this.getAccountIndex(acct);
3✔
3196
    }
3197

3198
    const rawName = Buffer.from(name, 'ascii');
9✔
3199
    const nameHash = rules.hashName(rawName);
9✔
3200
    const ns = await this.getNameState(nameHash);
9✔
3201
    const height = this.wdb.height + 1;
9✔
3202
    const network = this.network;
9✔
3203

3204
    if (!ns)
9!
3205
      throw new Error(`Auction not found: ${name}.`);
×
3206

3207
    const {hash, index} = ns.owner;
9✔
3208
    const coin = await this.getCoin(hash, index);
9✔
3209

3210
    if (!coin)
9!
3211
      throw new Error(`Wallet does not own name: ${name}.`);
×
3212

3213
    if (ns.isExpired(height, network))
9!
3214
      throw new Error(`Name has expired: ${name}.`);
×
3215

3216
    // Is local?
3217
    if (coin.height < ns.height)
9!
3218
      throw new Error(`Wallet does not own name: ${name}.`);
×
3219

3220
    if (acctno != null) {
9✔
3221
      if (!await this.txdb.hasCoinByAccount(acctno, hash, index))
3✔
3222
        throw new Error(`Account does not own name: ${name}.`);
1✔
3223
    }
3224

3225
    if (!ns.isClosed(height, network))
8!
3226
      throw new Error(`Auction is not yet closed: ${name}.`);
×
3227

3228
    if (!coin.covenant.isTransfer())
8!
3229
      throw new Error(`Name is not being transferred: ${name}.`);
×
3230

3231
    const output = new Output();
8✔
3232
    output.address = coin.address;
8✔
3233
    output.value = coin.value;
8✔
3234
    output.covenant.setUpdate(nameHash, ns.height, EMPTY);
8✔
3235

3236
    if (!mtx)
8✔
3237
      mtx = new MTX();
7✔
3238
    mtx.addOutpoint(ns.owner);
8✔
3239
    mtx.outputs.push(output);
8✔
3240

3241
    return mtx;
8✔
3242
  }
3243

3244
  /**
3245
   * Create and finalize a cancel
3246
   * MTX without a lock.
3247
   * @param {String} name
3248
   * @param {Object} options
3249
   * @returns {Promise<MTX>}
3250
   */
3251

3252
  async _createCancel(name, options) {
3253
    const acct = options ? options.account : null;
8✔
3254
    const mtx = await this.makeCancel(name, acct);
8✔
3255
    await this.fill(mtx, options);
7✔
3256
    return this.finalize(mtx, options);
7✔
3257
  }
3258

3259
  /**
3260
   * Create and finalize a cancel
3261
   * MTX with a lock.
3262
   * @param {String} name
3263
   * @param {Object} options
3264
   * @returns {Promise<MTX>}
3265
   */
3266

3267
  async createCancel(name, options) {
3268
    const unlock = await this.fundLock.lock();
1✔
3269
    try {
1✔
3270
      return await this._createCancel(name, options);
1✔
3271
    } finally {
3272
      unlock();
1✔
3273
    }
3274
  }
3275

3276
  /**
3277
   * Create and send a cancel
3278
   * MTX without a lock.
3279
   * @param {String} name
3280
   * @param {Object} options
3281
   * @returns {Promise<TX>}
3282
   */
3283

3284
  async _sendCancel(name, options) {
3285
    const passphrase = options ? options.passphrase : null;
7✔
3286
    const mtx = await this._createCancel(name, options);
7✔
3287
    return this.sendMTX(mtx, passphrase);
6✔
3288
  }
3289

3290
  /**
3291
   * Create and send a cancel
3292
   * MTX with a lock.
3293
   * @param {String} name
3294
   * @param {Object} options
3295
   * @returns {Promise<TX>}
3296
   */
3297

3298
  async sendCancel(name, options) {
3299
    const unlock = await this.fundLock.lock();
7✔
3300
    try {
7✔
3301
      return await this._sendCancel(name, options);
7✔
3302
    } finally {
3303
      unlock();
7✔
3304
    }
3305
  }
3306

3307
  /**
3308
   * Make a transfer-finalizing MTX.
3309
   * @private
3310
   * @param {String} name
3311
   * @param {(Number|String)?} acct
3312
   * @param {MTX?} [mtx]
3313
   * @returns {Promise<MTX>}
3314
   */
3315

3316
  async makeFinalize(name, acct, mtx) {
3317
    assert(typeof name === 'string');
40✔
3318

3319
    if (!rules.verifyName(name))
40!
3320
      throw new Error(`Invalid name: ${name}.`);
×
3321

3322
    let acctno;
3323

3324
    if (acct != null) {
40✔
3325
      assert((acct >>> 0) === acct || typeof acct === 'string');
27✔
3326
      acctno = await this.getAccountIndex(acct);
27✔
3327
    }
3328

3329
    const rawName = Buffer.from(name, 'ascii');
40✔
3330
    const nameHash = rules.hashName(rawName);
40✔
3331
    const ns = await this.getNameState(nameHash);
40✔
3332
    const height = this.wdb.height + 1;
40✔
3333
    const network = this.network;
40✔
3334

3335
    if (!ns)
40!
3336
      throw new Error(`Auction not found: ${name}.`);
×
3337

3338
    const {hash, index} = ns.owner;
40✔
3339
    const credit = await this.getCredit(hash, index);
40✔
3340

3341
    if (!credit)
40!
3342
      throw new Error(`Wallet does not own name: ${name}.`);
×
3343

3344
    if (credit.spent)
40✔
3345
      throw new Error(`Credit is already pending for: ${name}.`);
1✔
3346

3347
    if (ns.isExpired(height, network))
39!
3348
      throw new Error(`Name has expired: ${name}.`);
×
3349

3350
    const coin = credit.coin;
39✔
3351

3352
    // Is local?
3353
    if (coin.height < ns.height)
39!
3354
      throw new Error(`Wallet does not own name: ${name}.`);
×
3355

3356
    if (acctno != null) {
39✔
3357
      if (!await this.txdb.hasCoinByAccount(acctno, hash, index))
27✔
3358
        throw new Error(`Account does not own name: ${name}.`);
1✔
3359
    }
3360

3361
    if (!ns.isClosed(height, network))
38!
3362
      throw new Error(`Auction is not yet closed: ${name}.`);
×
3363

3364
    if (!coin.covenant.isTransfer())
38!
3365
      throw new Error(`Name is not being transferred: ${name}.`);
×
3366

3367
    if (height < coin.height + network.names.transferLockup)
38!
3368
      throw new Error(`Transfer is still locked up: ${name}.`);
×
3369

3370
    const version = coin.covenant.getU8(2);
38✔
3371
    const addr = coin.covenant.get(3);
38✔
3372
    const address = Address.fromHash(addr, version);
38✔
3373

3374
    let flags = 0;
38✔
3375

3376
    if (ns.weak)
38!
3377
      flags |= 1;
×
3378

3379
    const output = new Output();
38✔
3380
    output.address = address;
38✔
3381
    output.value = coin.value;
38✔
3382
    output.covenant.setFinalize(
38✔
3383
      nameHash,
3384
      ns.height,
3385
      rawName,
3386
      flags,
3387
      ns.claimed,
3388
      ns.renewals,
3389
      await this.wdb.getRenewalBlock()
3390
    );
3391

3392
    if (!mtx)
38✔
3393
      mtx = new MTX();
13✔
3394
    mtx.addOutpoint(ns.owner);
38✔
3395
    mtx.outputs.push(output);
38✔
3396

3397
    return mtx;
38✔
3398
  }
3399

3400
  /**
3401
   * Make a finazling MTX for all transferring names
3402
   * @private
3403
   * @param {MTX?} mtx
3404
   * @param {Number?} witnessSize
3405
   * @returns {Promise<MTX>}
3406
   */
3407

3408
  async makeFinalizeAll(mtx, witnessSize) {
3409
    // Only allowed in makeBatch
3410
    assert(mtx, 'Batch MTX required for makeFinalizeAll.');
26✔
3411
    assert(witnessSize, 'Witness size required for batch size estimation.');
26✔
3412
    const height = this.wdb.height + 1;
26✔
3413
    const network = this.network;
26✔
3414
    const names = await this.getNames();
26✔
3415

3416
    let finalizes = 0;
26✔
3417
    for (const ns of names) {
26✔
3418
      // Easiest check is for transfer state, do that first
3419
      if (!ns.transfer)
18,037✔
3420
        continue;
16,422✔
3421

3422
      const blocksLeft = (ns.transfer + network.names.transferLockup) - height;
1,615✔
3423
      if (blocksLeft > 0)
1,615✔
3424
        continue;
815✔
3425

3426
      // Then check for expiration
3427
      if (ns.isExpired(height, network))
800!
3428
        continue;
×
3429

3430
      // Now do the db lookups to see if we own the name
3431
      const {hash, index} = ns.owner;
800✔
3432
      const coin = await this.getUnspentCoin(hash, index);
800✔
3433
      if (!coin)
800!
3434
        continue;
×
3435

3436
      const version = coin.covenant.getU8(2);
800✔
3437
      const addr = coin.covenant.get(3);
800✔
3438
      const address = Address.fromHash(addr, version);
800✔
3439

3440
      let flags = 0;
800✔
3441

3442
      if (ns.weak)
800!
3443
        flags |= 1;
×
3444

3445
      const output = new Output();
800✔
3446
      output.address = address;
800✔
3447
      output.value = coin.value;
800✔
3448
      output.covenant.setFinalize(
800✔
3449
        ns.nameHash,
3450
        ns.height,
3451
        Buffer.from(ns.name, 'ascii'),
3452
        flags,
3453
        ns.claimed,
3454
        ns.renewals,
3455
        await this.wdb.getRenewalBlock()
3456
      );
3457

3458
      mtx.addOutpoint(new Outpoint(coin.hash, coin.index));
800✔
3459
      mtx.outputs.push(output);
800✔
3460

3461
      // Keep batches below policy size limit
3462
      if (this.isOversizedBatch(mtx, witnessSize)) {
800!
3463
        mtx.inputs.pop();
×
3464
        mtx.outputs.pop();
×
3465
        break;
×
3466
      }
3467

3468
      // TODO: Should this factor of 6 be user-configurable?
3469
      // Enforce consensus limit per block at a maxmium
3470
      finalizes++;
800✔
3471
      if (finalizes >= consensus.MAX_BLOCK_RENEWALS / 6)
800✔
3472
        break;
8✔
3473
    }
3474

3475
    return mtx;
26✔
3476
  }
3477

3478
  /**
3479
   * Create and finalize a finalize
3480
   * MTX without a lock.
3481
   * @param {String} name
3482
   * @param {Object} options
3483
   * @returns {Promise<MTX>}
3484
   */
3485

3486
  async _createFinalize(name, options) {
3487
    const acct = options ? options.account : null;
15✔
3488
    const mtx = await this.makeFinalize(name, acct);
15✔
3489
    await this.fill(mtx, options);
13✔
3490
    return this.finalize(mtx, options);
13✔
3491
  }
3492

3493
  /**
3494
   * Create and finalize a finalize
3495
   * MTX with a lock.
3496
   * @param {String} name
3497
   * @param {Object} options
3498
   * @returns {Promise<MTX>}
3499
   */
3500

3501
  async createFinalize(name, options) {
3502
    const unlock = await this.fundLock.lock();
2✔
3503
    try {
2✔
3504
      return await this._createFinalize(name, options);
2✔
3505
    } finally {
3506
      unlock();
2✔
3507
    }
3508
  }
3509

3510
  /**
3511
   * Create and send a finalize
3512
   * MTX without a lock.
3513
   * @param {String} name
3514
   * @param {Object} options
3515
   * @returns {Promise<TX>}
3516
   */
3517

3518
  async _sendFinalize(name, options) {
3519
    const passphrase = options ? options.passphrase : null;
13✔
3520
    const mtx = await this._createFinalize(name, options);
13✔
3521
    return this.sendMTX(mtx, passphrase);
11✔
3522
  }
3523

3524
  /**
3525
   * Create and send a finalize
3526
   * MTX with a lock.
3527
   * @param {String} name
3528
   * @param {Object} options
3529
   * @returns {Promise<TX>}
3530
   */
3531

3532
  async sendFinalize(name, options) {
3533
    const unlock = await this.fundLock.lock();
13✔
3534
    try {
13✔
3535
      return await this._sendFinalize(name, options);
13✔
3536
    } finally {
3537
      unlock();
13✔
3538
    }
3539
  }
3540

3541
  /**
3542
   * Make a revoke MTX.
3543
   * @param {String} name
3544
   * @param {(Number|String)?} acct
3545
   * @param {MTX?} [mtx]
3546
   * @returns {Promise<MTX>}
3547
   */
3548

3549
  async makeRevoke(name, acct, mtx) {
3550
    assert(typeof name === 'string');
24✔
3551

3552
    if (!rules.verifyName(name))
24!
3553
      throw new Error(`Invalid name: ${name}.`);
×
3554

3555
    let acctno;
3556

3557
    if (acct != null) {
24✔
3558
      assert((acct >>> 0) === acct || typeof acct === 'string');
15✔
3559
      acctno = await this.getAccountIndex(acct);
15✔
3560
    }
3561

3562
    const rawName = Buffer.from(name, 'ascii');
24✔
3563
    const nameHash = rules.hashName(rawName);
24✔
3564
    const ns = await this.getNameState(nameHash);
24✔
3565
    const height = this.wdb.height + 1;
24✔
3566
    const network = this.network;
24✔
3567

3568
    if (!ns)
24!
3569
      throw new Error(`Auction not found: ${name}.`);
×
3570

3571
    const {hash, index} = ns.owner;
24✔
3572
    const credit = await this.getCredit(hash, index);
24✔
3573

3574
    if (!credit)
24!
3575
      throw new Error(`Wallet does not own name: ${name}.`);
×
3576

3577
    if (credit.spent)
24✔
3578
      throw new Error(`Credit is already pending for: ${name}.`);
1✔
3579

3580
    if (acctno != null) {
23✔
3581
      if (!await this.txdb.hasCoinByAccount(acctno, hash, index))
15✔
3582
        throw new Error(`Account does not own name: ${name}.`);
1✔
3583
    }
3584

3585
    const coin = credit.coin;
22✔
3586

3587
    // Is local?
3588
    if (coin.height < ns.height)
22!
3589
      throw new Error(`Wallet does not own name: ${name}.`);
×
3590

3591
    if (ns.isExpired(height, network))
22!
3592
      throw new Error(`Name has expired: ${name}.`);
×
3593

3594
    if (!ns.isClosed(height, network))
22!
3595
      throw new Error(`Auction is not yet closed: ${name}.`);
×
3596

3597
    if (!coin.covenant.isRegister()
22!
3598
        && !coin.covenant.isUpdate()
3599
        && !coin.covenant.isRenew()
3600
        && !coin.covenant.isTransfer()
3601
        && !coin.covenant.isFinalize()) {
3602
      throw new Error(`Name is not registered: ${name}.`);
×
3603
    }
3604

3605
    const output = new Output();
22✔
3606
    output.address = coin.address;
22✔
3607
    output.value = coin.value;
22✔
3608

3609
    output.covenant.setRevoke(nameHash, ns.height);
22✔
3610

3611
    if (!mtx)
22✔
3612
      mtx = new MTX();
9✔
3613
    mtx.addOutpoint(ns.owner);
22✔
3614
    mtx.outputs.push(output);
22✔
3615

3616
    return mtx;
22✔
3617
  }
3618

3619
  /**
3620
   * Create and finalize a revoke
3621
   * MTX without a lock.
3622
   * @param {String} name
3623
   * @param {Object} options
3624
   * @returns {Promise<MTX>}
3625
   */
3626

3627
  async _createRevoke(name, options) {
3628
    const acct = options ? options.account : null;
11✔
3629
    const mtx = await this.makeRevoke(name, acct);
11✔
3630
    await this.fill(mtx, options);
9✔
3631
    return this.finalize(mtx, options);
9✔
3632
  }
3633

3634
  /**
3635
   * Create and finalize a revoke
3636
   * MTX with a lock.
3637
   * @param {String} name
3638
   * @param {Object} options
3639
   * @returns {Promise<MTX>}
3640
   */
3641

3642
  async createRevoke(name, options) {
3643
    const unlock = await this.fundLock.lock();
1✔
3644
    try {
1✔
3645
      return await this._createRevoke(name, options);
1✔
3646
    } finally {
3647
      unlock();
1✔
3648
    }
3649
  }
3650

3651
  /**
3652
   * Create and send a revoke
3653
   * MTX without a lock.
3654
   * @param {String} name
3655
   * @param {Object} options
3656
   * @returns {Promise<TX>}
3657
   */
3658

3659
  async _sendRevoke(name, options) {
3660
    const passphrase = options ? options.passphrase : null;
10✔
3661
    const mtx = await this._createRevoke(name, options);
10✔
3662
    return this.sendMTX(mtx, passphrase);
8✔
3663
  }
3664

3665
  /**
3666
   * Create and send a revoke
3667
   * MTX with a lock.
3668
   * @param {String} name
3669
   * @param {Object} options
3670
   * @returns {Promise<TX>}
3671
   */
3672

3673
  async sendRevoke(name, options) {
3674
    const unlock = await this.fundLock.lock();
10✔
3675
    try {
10✔
3676
      return await this._sendRevoke(name, options);
10✔
3677
    } finally {
3678
      unlock();
10✔
3679
    }
3680
  }
3681

3682
  /**
3683
   * Get account by address.
3684
   * @param {Address} address
3685
   * @returns {Promise<Account?>}
3686
   */
3687

3688
  async getAccountByAddress(address) {
3689
    const hash = Address.getHash(address);
391✔
3690
    const path = await this.getPath(hash);
391✔
3691

3692
    if (!path)
391✔
3693
      return null;
5✔
3694

3695
    return this.getAccount(path.account);
386✔
3696
  }
3697

3698
  /**
3699
   * Estimate witness size given output address.
3700
   * Unlike Bitcoin, our signatures are always 65 bytes.
3701
   * However, we still assume that the witness varInt size
3702
   * is only one byte. In short, this estimate may be off
3703
   * by 2 (at most) but only if a witness has > 253 items.
3704
   * Also note we are only processing the witness data here,
3705
   * which will be scaled down by WITNESS_SCALE_FACTOR to compute
3706
   * vsize. Input data like prevout and sequence count as base data
3707
   * and must be added in outside this function.
3708
   * @param {Address} addr
3709
   * @returns {Promise<Number>}
3710
   */
3711

3712
  async estimateSize(addr) {
3713
    const account = await this.getAccountByAddress(addr);
391✔
3714

3715
    if (!account)
391✔
3716
      return -1;
5✔
3717

3718
    let size = 0;
386✔
3719

3720
    // Varint witness items length.
3721
    size += 1;
386✔
3722

3723
    switch (account.type) {
386✔
3724
      case Account.types.PUBKEYHASH:
3725
        // P2PKH
3726
        // varint-len [signature]
3727
        size += 1 + 65;
381✔
3728
        // varint-len [key]
3729
        size += 1 + 33;
381✔
3730
        break;
381✔
3731
      case Account.types.MULTISIG:
3732
        // P2SH Multisig
3733
        // OP_0
3734
        size += 1;
5✔
3735
        // varint-len [signature] ...
3736
        size += (1 + 65) * account.m;
5✔
3737
        // varint-len [redeem]
3738
        // at 8 pubkeys (n) script size requires 3-byte varInt
3739
        size += account.n > 7 ? 3 : 1;
5!
3740
        // m value
3741
        size += 1;
5✔
3742
        // OP_PUSHDATA0 [key] ...
3743
        size += (1 + 33) * account.n;
5✔
3744
        // n value
3745
        size += 1;
5✔
3746
        // OP_CHECKMULTISIG
3747
        size += 1;
5✔
3748
        break;
5✔
3749
    }
3750

3751
    return size;
386✔
3752
  }
3753

3754
 /**
3755
  * Make a transaction with normal outputs.
3756
  * @param {Object[]} outputs - See {@link MTX#addOutput}
3757
  * @param {MTX?} [mtx] - MTX to modify instead of new one.
3758
  * @returns {MTX} - MTX with populated outputs.
3759
  */
3760

3761
  makeTX(outputs, mtx) {
3762
    assert(Array.isArray(outputs), 'output must be an array.');
1,119✔
3763
    assert(outputs.length > 0, 'At least one output is required.');
1,119✔
3764

3765
    if (!mtx)
1,118✔
3766
      mtx = new MTX();
1,104✔
3767

3768
    // Add the outputs
3769
    for (const obj of outputs) {
1,118✔
3770
      const output = new Output(obj);
1,240✔
3771
      const addr = output.getAddress();
1,240✔
3772

3773
      if (output.isDust())
1,240✔
3774
        throw new Error('Output is dust.');
1✔
3775

3776
      if (output.value > 0) {
1,239✔
3777
        if (!addr)
1,237!
3778
          throw new Error('Cannot send to unknown address.');
×
3779

3780
        if (addr.isNull())
1,237!
3781
          throw new Error('Cannot send to null address.');
×
3782
      }
3783

3784
      mtx.outputs.push(output);
1,239✔
3785
    }
3786

3787
    return mtx;
1,117✔
3788
  }
3789

3790
  /**
3791
   * Build a transaction, fill and finalize without a lock.
3792
   * @param {Object} options - See {@link Wallet#fund options}.
3793
   * @param {Object[]} options.outputs - See {@link MTX#addOutput}.
3794
   * @returns {Promise<MTX>} - MTX with populated inputs and outputs.
3795
   */
3796

3797
  async _createTX(options) {
3798
    const mtx = this.makeTX(options.outputs);
1,104✔
3799
    await this.fill(mtx, options);
1,103✔
3800
    return this.finalize(mtx, options);
1,103✔
3801
  }
3802

3803
  /**
3804
   * Build a transaction, fill and finalize with a lock.
3805
   * @param {Object} options - See {@link Wallet#fund options}.
3806
   * @param {Object[]} options.outputs - See {@link MTX#addOutput}.
3807
   * @returns {Promise} - Returns {@link MTX}.
3808
   */
3809

3810
  async createTX(options) {
3811
    const unlock = await this.fundLock.lock();
10✔
3812
    try {
10✔
3813
      return await this._createTX(options);
10✔
3814
    } finally {
3815
      unlock();
10✔
3816
    }
3817
  }
3818

3819
  /**
3820
   * Make a batch transaction with multiple actions.
3821
   * @param {Array} actions
3822
   * @param {Object} options
3823
   * @returns {Promise<MTX>}
3824
   */
3825

3826
  async makeBatch(actions, options) {
3827
    assert(Array.isArray(actions));
382✔
3828
    assert(actions.length, 'Batches require at least one action.');
382✔
3829

3830
    const acct = options ? options.account || 0 : 0;
381✔
3831
    const mtx = new MTX();
381✔
3832

3833
    // Track estimated size of batch TX to keep it under policy limit
3834
    const address = await this.changeAddress(acct);
381✔
3835
    const witnessSize = await this.estimateSize(address);
381✔
3836

3837
    // Sort actions so covenants that require linked inputs
3838
    // are pushed first into the mtx input/output arrays.
3839
    // This is a required step otherwise an unlinked
3840
    // covenant like NONE, OPEN, or BID could shift the
3841
    // output array out of sync with their corresponding inputs.
3842
    actions.sort((a, b) => {
381✔
3843
      assert(Array.isArray(a));
7,628✔
3844
      assert(Array.isArray(b));
7,628✔
3845
      assert(a.length);
7,628✔
3846
      assert(b.length);
7,628✔
3847

3848
      switch (b[0]) {
7,628✔
3849
        case 'REVEAL':
3850
        case 'REDEEM':
3851
        case 'UPDATE':
3852
        case 'RENEW':
3853
        case 'TRANSFER':
3854
        case 'FINALIZE':
3855
        case 'CANCEL':
3856
        case 'REVOKE':
3857
          return 1;
3,820✔
3858
        default:
3859
          return -1;
3,808✔
3860
      }
3861
    });
3862

3863
    // Some actions accept output addresses to avoid address reuse.
3864
    // We track that by bumping receiveIndex.
3865
    const account = await this.getAccount(acct);
381✔
3866
    let receiveIndex = account.receiveDepth - 1;
381✔
3867

3868
    // "actions" are arrays that start with a covenant type (or meta-type)
3869
    // followed by the arguments expected by the corresponding "make" function.
3870
    for (const action of actions) {
381✔
3871
      const type = action.shift();
7,798✔
3872
      assert(typeof type === 'string');
7,798✔
3873

3874
      switch (type) {
7,798✔
3875
        case 'NONE': {
3876
          assert(action.length === 2);
14✔
3877
          this.makeTX([{
14✔
3878
            address: action[0],
3879
            value: action[1]
3880
          }], mtx);
3881

3882
          break;
13✔
3883
        }
3884
        case 'OPEN': {
3885
          assert(action.length === 1, 'Bad arguments for OPEN.');
1,222✔
3886
          const name = action[0];
1,222✔
3887
          await this.makeOpen(name, acct, mtx);
1,222✔
3888
          break;
1,221✔
3889
        }
3890
        case 'BID': {
3891
          assert(action.length === 3, 'Bad arguments for BID.');
2,732✔
3892
          const address = account.deriveReceive(receiveIndex++).getAddress();
2,731✔
3893
          const name = action[0];
2,731✔
3894
          const value = action[1];
2,731✔
3895
          const lockup = action[2];
2,731✔
3896
          await this.makeBid(name, value, lockup, acct, mtx, address);
2,731✔
3897
          break;
2,730✔
3898
        }
3899
        case 'REVEAL': {
3900
          if (action.length === 1) {
135✔
3901
            const name = action[0];
103✔
3902
            await this.makeReveal(name, acct, mtx);
103✔
3903
            break;
102✔
3904
          }
3905

3906
          assert(action.length === 0, 'Bad arguments for REVEAL.');
32✔
3907
          await this.makeRevealAll(mtx, witnessSize);
32✔
3908
          break;
32✔
3909
        }
3910
        case 'REDEEM': {
3911
          if (action.length === 1) {
44✔
3912
            const name = action[0];
13✔
3913
            await this.makeRedeem(name, acct, mtx);
13✔
3914
            break;
13✔
3915
          }
3916

3917
          assert(action.length === 0, 'Bad arguments for REDEEM.');
31✔
3918
          await this.makeRedeemAll(mtx, witnessSize);
31✔
3919
          break;
31✔
3920
        }
3921
        case 'UPDATE': {
3922
          assert(action.length === 2, 'Bad arguments for UPDATE.');
1,504✔
3923
          const name = action[0];
1,504✔
3924
          const resource = action[1];
1,504✔
3925
          await this.makeUpdate(name, resource, acct, mtx);
1,504✔
3926
          break;
1,504✔
3927
        }
3928
        case 'RENEW': {
3929
          if (action.length === 1) {
641✔
3930
            const name = action[0];
615✔
3931
            await this.makeRenewal(name, acct, mtx);
615✔
3932
            break;
615✔
3933
          }
3934

3935
          assert(action.length === 0, 'Bad arguments for RENEW.');
26✔
3936
          await this.makeRenewalAll(mtx, witnessSize);
26✔
3937
          break;
26✔
3938
        }
3939
        case 'TRANSFER': {
3940
          assert(action.length === 2, 'Bad arguments for TRANSFER.');
1,440✔
3941
          const name = action[0];
1,440✔
3942
          const address = action[1];
1,440✔
3943
          await this.makeTransfer(name, address, acct, mtx);
1,440✔
3944
          break;
1,440✔
3945
        }
3946
        case 'FINALIZE': {
3947
          if (action.length === 1) {
51✔
3948
            const name = action[0];
25✔
3949
            await this.makeFinalize(name, acct, mtx);
25✔
3950
            break;
25✔
3951
          }
3952

3953
          assert(action.length === 0, 'Bad arguments for FINALIZE.');
26✔
3954
          await this.makeFinalizeAll(mtx, witnessSize);
26✔
3955
          break;
26✔
3956
        }
3957
        case 'CANCEL': {
3958
          assert(action.length === 1, 'Bad arguments for CANCEL.');
1✔
3959
          const name = action[0];
1✔
3960
          await this.makeCancel(name, acct, mtx);
1✔
3961
          break;
1✔
3962
        }
3963
        case 'REVOKE': {
3964
          assert(action.length === 1, 'Bad arguments for REVOKE.');
13✔
3965
          const name = action[0];
13✔
3966
          await this.makeRevoke(name, acct, mtx);
13✔
3967
          break;
13✔
3968
        }
3969
        default:
3970
          throw new Error(`Unknown action type: ${type}`);
1✔
3971
      }
3972

3973
      if (rules.countOpens(mtx) > consensus.MAX_BLOCK_OPENS)
7,792✔
3974
        throw new Error('Too many OPENs.');
1✔
3975

3976
      if (rules.countUpdates(mtx) > consensus.MAX_BLOCK_UPDATES)
7,791✔
3977
        throw new Error('Too many UPDATEs.');
2✔
3978

3979
      if (rules.countRenewals(mtx) > consensus.MAX_BLOCK_RENEWALS)
7,789✔
3980
        throw new Error('Too many RENEWs.');
1✔
3981
    }
3982

3983
    if (!mtx.outputs.length)
371✔
3984
      throw new Error('Nothing to do.');
7✔
3985

3986
    // Clean up.
3987
    // 1. Some actions MUST be the ONLY action for a name.
3988
    //    i.e. no duplicate OPENs or REVOKE/FINALIZE for same name in one tx.
3989
    const set = new BufferSet();
364✔
3990
    for (const output of mtx.outputs) {
364✔
3991
      const {covenant} = output;
11,468✔
3992
      if (!covenant.isName())
11,468✔
3993
        continue;
13✔
3994

3995
      const nameHash = covenant.getHash(0);
11,455✔
3996

3997
      switch (covenant.type) {
11,455!
3998
        case types.CLAIM:
3999
        case types.OPEN:
4000
          output.address = account.deriveReceive(receiveIndex++).getAddress();
914✔
4001
          assert(!set.has(nameHash), 'Duplicate name with exclusive action.');
914✔
4002
          set.add(nameHash);
912✔
4003
          break;
912✔
4004
        case types.BID:
4005
        case types.REVEAL:
4006
        case types.REDEEM:
4007
          break;
7,146✔
4008
        case types.REGISTER:
4009
        case types.UPDATE:
4010
        case types.RENEW:
4011
        case types.TRANSFER:
4012
        case types.FINALIZE:
4013
        case types.REVOKE:
4014
          assert(!set.has(nameHash), 'Duplicate name with exclusive action.');
3,395✔
4015
          set.add(nameHash);
3,395✔
4016
          break;
3,395✔
4017
      }
4018

4019
      if (receiveIndex > account.receiveDepth - 1 + account.lookahead)
11,453✔
4020
        throw new Error('Batch output addresses would exceed lookahead.');
1✔
4021
    }
4022

4023
    return mtx;
361✔
4024
  }
4025

4026
  /**
4027
   * Make a batch transaction with multiple actions.
4028
   * @param {Array} actions
4029
   * @param {Object} options
4030
   * @returns {Promise<MTX>}
4031
   */
4032

4033
  async _createBatch(actions, options) {
4034
    const mtx = await this.makeBatch(actions, options);
382✔
4035
    await this.fill(mtx, options);
361✔
4036
    return this.finalize(mtx, options);
361✔
4037
  }
4038

4039
  /**
4040
   * Make a batch transaction with multiple actions.
4041
   * @param {Array} actions
4042
   * @param {Object} options
4043
   * @returns {Promise<MTX>}
4044
   */
4045

4046
  async createBatch(actions, options) {
4047
    const unlock = await this.fundLock.lock();
85✔
4048
    try {
85✔
4049
      return await this._createBatch(actions, options);
85✔
4050
    } finally {
4051
      unlock();
85✔
4052
    }
4053
  }
4054

4055
  /**
4056
   * Create and send a batch transaction with multiple actions.
4057
   * @param {Array} actions
4058
   * @param {Object} options
4059
   * @returns {Promise<TX>}
4060
   */
4061

4062
  async _sendBatch(actions, options) {
4063
    const passphrase = options ? options.passphrase : null;
297✔
4064
    const mtx = await this._createBatch(actions, options);
297✔
4065
    return this.sendMTX(mtx, passphrase);
280✔
4066
  }
4067

4068
  /**
4069
   * Create and send a batch transaction with multiple actions.
4070
   * @param {Array} actions
4071
   * @param {Object} options
4072
   * @returns {Promise<TX>}
4073
   */
4074

4075
  async sendBatch(actions, options) {
4076
    const unlock = await this.fundLock.lock();
297✔
4077
    try {
297✔
4078
      return await this._sendBatch(actions, options);
297✔
4079
    } finally {
4080
      unlock();
297✔
4081
    }
4082
  }
4083

4084
  /**
4085
   * Check batch MTX for excessive size
4086
   * @param {MTX} mtx
4087
   * @param {Number} witnessSize
4088
   * @returns {Boolean}
4089
   */
4090

4091
  isOversizedBatch(mtx, witnessSize) {
4092
    if (!witnessSize)
7,653✔
4093
      return false;
1,547✔
4094

4095
    const sizes = mtx.getSizes();
6,106✔
4096

4097
    sizes.base += 40;  // Assume funding input: hash, index, sequence
6,106✔
4098
    sizes.witness += (mtx.inputs.length + 1) // Current inputs plus funding
6,106✔
4099
                     * (witnessSize - 1);    // Replace 0x00 placeholder
4100
    sizes.witness += 1; // the funding input never had a placeholder
6,106✔
4101

4102
    // Assume we need a change output, pay to scripthash address for safety
4103
    sizes.base += 44; // value, p2sh address, NONE covenant
6,106✔
4104

4105
    return sizes.getWeight() > policy.MAX_TX_WEIGHT;
6,106✔
4106
  }
4107

4108
  /**
4109
   * Finalize and template an MTX.
4110
   * @param {MTX} mtx
4111
   * @param {Object} options
4112
   * @returns {Promise<MTX>}
4113
   */
4114

4115
  async finalize(mtx, options) {
4116
    if (!options)
2,132✔
4117
      options = {};
508✔
4118

4119
    // Sort members a la BIP69
4120
    if (options.sort !== false)
2,132✔
4121
      mtx.sortMembers();
2,124✔
4122

4123
    // Set the locktime to target value.
4124
    if (options.locktime != null)
2,132✔
4125
      mtx.setLocktime(options.locktime);
1✔
4126

4127
    // Consensus sanity checks.
4128
    assert(mtx.isSane(), 'TX failed sanity check.');
2,132✔
4129
    assert(mtx.verifyInputs(this.wdb.height + 1, this.network),
2,132✔
4130
      'TX failed context check.');
4131
    assert(this.wdb.height + 1 >= this.network.txStart,
2,132✔
4132
      'Transactions are not allowed on network yet.');
4133

4134
    // Set the HD paths.
4135
    if (options.paths === true)
2,131✔
4136
      mtx.view = await this.getWalletCoinView(mtx, mtx.view);
21✔
4137

4138
    const total = await this.template(mtx);
2,131✔
4139

4140
    if (total === 0)
2,131!
4141
      throw new Error('Templating failed.');
×
4142

4143
    return mtx;
2,131✔
4144
  }
4145

4146
  /**
4147
   * Build a transaction, fill it with outputs and inputs,
4148
   * sort the members according to BIP69, set locktime,
4149
   * sign and broadcast. Doing this all in one go prevents
4150
   * coins from being double spent.
4151
   * @param {Object} options - See {@link Wallet#fund options}.
4152
   * @param {Object[]} options.outputs - See {@link MTX#addOutput}.
4153
   * @param {String} options.passphrase
4154
   * @returns {Promise<TX>}
4155
   */
4156

4157
  async send(options) {
4158
    const unlock = await this.fundLock.lock();
1,094✔
4159
    try {
1,094✔
4160
      return await this._send(options);
1,094✔
4161
    } finally {
4162
      unlock();
1,094✔
4163
    }
4164
  }
4165

4166
  /**
4167
   * Build and send a transaction without a lock.
4168
   * @private
4169
   * @param {Object} options - See {@link Wallet#fund options}.
4170
   * @param {Object[]} options.outputs - See {@link MTX#addOutput}.
4171
   * @param {String} options.passphrase
4172
   * @returns {Promise<TX>}
4173
   */
4174

4175
  async _send(options) {
4176
    const passphrase = options ? options.passphrase : null;
1,094!
4177
    const mtx = await this._createTX(options);
1,094✔
4178
    return this.sendMTX(mtx, passphrase);
1,092✔
4179
  }
4180

4181
  /**
4182
   * Sign and send a (templated) mutable transaction.
4183
   * @param {MTX} mtx
4184
   * @param {String} passphrase
4185
   * @returns {Promise<TX>}
4186
   */
4187

4188
  async sendMTX(mtx, passphrase) {
4189
    await this.sign(mtx, passphrase);
1,978✔
4190

4191
    if (!mtx.isSigned())
1,973!
4192
      throw new Error('TX could not be fully signed.');
×
4193

4194
    const tx = mtx.toTX();
1,973✔
4195

4196
    // Policy sanity checks.
4197
    if (tx.getSigops(mtx.view) > policy.MAX_TX_SIGOPS)
1,973!
4198
      throw new Error('TX exceeds policy sigops.');
×
4199

4200
    if (tx.getWeight() > policy.MAX_TX_WEIGHT)
1,973✔
4201
      throw new Error('TX exceeds policy weight.');
3✔
4202

4203
    const minFee = policy.getMinFee(
1,970✔
4204
      mtx.getVirtualSize(),
4205
      this.network.minRelay
4206
    );
4207

4208
    const absurdFee = minFee * this.absurdFactor;
1,970✔
4209

4210
    const fee = mtx.getFee();
1,970✔
4211

4212
    if (fee < minFee)
1,970✔
4213
      throw new Error('Fee is below minimum relay limit.');
1✔
4214

4215
    if (fee > absurdFee)
1,969✔
4216
      throw new Error('Fee exceeds absurd limit.');
1✔
4217

4218
    const ancestors = await this.getPendingAncestors(tx);
1,968✔
4219
    if (ancestors.size + 1 > this.maxAncestors)
1,968✔
4220
      throw new Error('TX exceeds maximum unconfirmed ancestors.');
1✔
4221

4222
    for (const output of tx.outputs) {
1,967✔
4223
      if (output.isDust())
14,482✔
4224
        throw new Error('Output is dust.');
1✔
4225

4226
      if (output.value > 0) {
14,481✔
4227
        if (!output.address)
13,425!
4228
          throw new Error('Cannot send to unknown address.');
×
4229

4230
        if (output.address.isNull())
13,425✔
4231
          throw new Error('Cannot send to null address.');
1✔
4232
      }
4233
    }
4234

4235
    await this.wdb.addTX(tx);
1,965✔
4236

4237
    this.logger.debug('Sending wallet tx (%s): %x', this.id, tx.hash());
1,965✔
4238

4239
    await this.wdb.send(tx);
1,965✔
4240

4241
    return tx;
1,965✔
4242
  }
4243

4244
  /**
4245
   * Intentionally double-spend outputs by
4246
   * increasing fee for an existing transaction.
4247
   * @param {Hash} hash
4248
   * @param {Rate} rate
4249
   * @param {(String|Buffer)?} passphrase
4250
   * @returns {Promise<TX>}
4251
   */
4252

4253
  async increaseFee(hash, rate, passphrase) {
4254
    assert((rate >>> 0) === rate, 'Rate must be a number.');
×
4255

4256
    const wtx = await this.getTX(hash);
×
4257

4258
    if (!wtx)
×
4259
      throw new Error('Transaction not found.');
×
4260

4261
    if (wtx.height !== -1)
×
4262
      throw new Error('Transaction is confirmed.');
×
4263

4264
    const tx = wtx.tx;
×
4265

4266
    if (tx.isCoinbase())
×
4267
      throw new Error('Transaction is a coinbase.');
×
4268

4269
    const view = await this.getSpentView(tx);
×
4270

4271
    if (!tx.hasCoins(view))
×
4272
      throw new Error('Not all coins available.');
×
4273

4274
    const oldFee = tx.getFee(view);
×
4275

4276
    const fee = tx.getMinFee(null, rate);
×
4277

4278
    if (oldFee >= fee)
×
4279
      throw new Error('Fee is not increasing.');
×
4280

4281
    const mtx = MTX.fromTX(tx);
×
4282
    mtx.view = view;
×
4283

4284
    for (const input of mtx.inputs)
×
4285
      input.witness.clear();
×
4286

4287
    let change = null;
×
4288

4289
    for (let i = 0; i < mtx.outputs.length; i++) {
×
4290
      const output = mtx.outputs[i];
×
4291
      const addr = output.getAddress();
×
4292

4293
      if (!addr)
×
4294
        continue;
×
4295

4296
      const path = await this.getPath(addr);
×
4297

4298
      if (!path)
×
4299
        continue;
×
4300

4301
      if (path.branch === 1) {
×
4302
        change = output;
×
4303
        mtx.changeIndex = i;
×
4304
        break;
×
4305
      }
4306
    }
4307

4308
    if (!change)
×
4309
      throw new Error('No change output.');
×
4310

4311
    change.value += oldFee;
×
4312

4313
    if (mtx.getFee() !== 0)
×
4314
      throw new Error('Arithmetic error for change.');
×
4315

4316
    change.value -= fee;
×
4317

4318
    if (change.value < 0)
×
4319
      throw new Error('Fee is too high.');
×
4320

4321
    if (change.isDust()) {
×
4322
      mtx.outputs.splice(mtx.changeIndex, 1);
×
4323
      mtx.changeIndex = -1;
×
4324
    }
4325

4326
    await this.sign(mtx, passphrase);
×
4327

4328
    if (!mtx.isSigned())
×
4329
      throw new Error('TX could not be fully signed.');
×
4330

4331
    const ntx = mtx.toTX();
×
4332

4333
    this.logger.debug(
×
4334
      'Increasing fee for wallet tx (%s): %x',
4335
      this.id, ntx.hash());
4336

4337
    await this.wdb.addTX(ntx);
×
4338
    await this.wdb.send(ntx);
×
4339

4340
    return ntx;
×
4341
  }
4342

4343
  /**
4344
   * Resend pending wallet transactions.
4345
   * @returns {Promise}
4346
   */
4347

4348
  async resend() {
4349
    const wtxs = await this.getPending();
×
4350

4351
    if (wtxs.length > 0)
×
4352
      this.logger.info('Rebroadcasting %d transactions.', wtxs.length);
×
4353

4354
    const txs = [];
×
4355

4356
    for (const wtx of wtxs) {
×
4357
      if (!wtx.tx.isCoinbase())
×
4358
        txs.push(wtx.tx);
×
4359
    }
4360

4361
    const sorted = common.sortDeps(txs);
×
4362

4363
    for (const tx of sorted)
×
4364
      await this.wdb.send(tx);
×
4365

4366
    return txs;
×
4367
  }
4368

4369
  /**
4370
   * Derive necessary addresses for signing a transaction.
4371
   * @param {MTX} mtx
4372
   * @returns {Promise<WalletKey[]>}
4373
   */
4374

4375
  async deriveInputs(mtx) {
4376
    assert(mtx.mutable);
4,267✔
4377

4378
    const paths = await this.getInputPaths(mtx);
4,267✔
4379
    const rings = [];
4,267✔
4380

4381
    for (const path of paths) {
4,267✔
4382
      const account = await this.getAccount(path.account);
24,180✔
4383

4384
      if (!account)
24,180!
4385
        continue;
×
4386

4387
      const ring = account.derivePath(path, this.master);
24,180✔
4388

4389
      if (ring)
24,180!
4390
        rings.push(ring);
24,180✔
4391
    }
4392

4393
    return rings;
4,267✔
4394
  }
4395

4396
  /**
4397
   * Retrieve a single keyring by address.
4398
   * @param {Address|Hash} address
4399
   * @returns {Promise}
4400
   */
4401

4402
  async getKey(address) {
4403
    const hash = Address.getHash(address);
29✔
4404
    const path = await this.getPath(hash);
29✔
4405

4406
    if (!path)
29✔
4407
      return null;
1✔
4408

4409
    const account = await this.getAccount(path.account);
28✔
4410

4411
    if (!account)
28!
4412
      return null;
×
4413

4414
    // The account index in the db may be wrong.
4415
    // We must read it from the stored xpub to be
4416
    // sure of its correctness.
4417
    //
4418
    // For more details see:
4419
    // https://github.com/bcoin-org/bcoin/issues/698.
4420
    //
4421
    // TODO(boymanjor): remove index manipulation
4422
    // once the watch-only wallet bug is fixed.
4423
    account.accountIndex = account.accountKey.childIndex;
28✔
4424

4425
    // Unharden the account index, if necessary.
4426
    if (account.accountIndex & HD.common.HARDENED)
28✔
4427
      account.accountIndex ^= HD.common.HARDENED;
26✔
4428

4429
    return account.derivePath(path, this.master);
28✔
4430
  }
4431

4432
  /**
4433
   * Retrieve a single keyring by address
4434
   * (with the private key reference).
4435
   * @param {Address|Hash} address
4436
   * @param {(Buffer|String)?} passphrase
4437
   * @returns {Promise}
4438
   */
4439

4440
  async getPrivateKey(address, passphrase) {
4441
    const hash = Address.getHash(address);
16✔
4442
    const path = await this.getPath(hash);
16✔
4443

4444
    if (!path)
16!
4445
      return null;
×
4446

4447
    const account = await this.getAccount(path.account);
16✔
4448

4449
    if (!account)
16!
4450
      return null;
×
4451

4452
    await this.unlock(passphrase);
16✔
4453

4454
    const key = account.derivePath(path, this.master);
16✔
4455

4456
    if (!key.privateKey)
16!
4457
      return null;
×
4458

4459
    return key;
16✔
4460
  }
4461

4462
  /**
4463
   * Map input addresses to paths.
4464
   * @param {MTX} mtx
4465
   * @returns {Promise} - Returns {@link Path}[].
4466
   */
4467

4468
  async getInputPaths(mtx) {
4469
    assert(mtx.mutable);
4,267✔
4470

4471
    if (!mtx.hasCoins())
4,267!
4472
      throw new Error('Not all coins available.');
×
4473

4474
    const hashes = mtx.getInputHashes();
4,267✔
4475
    const paths = [];
4,267✔
4476

4477
    for (const hash of hashes) {
4,267✔
4478
      const path = await this.getPath(hash);
24,202✔
4479
      if (path)
24,202✔
4480
        paths.push(path);
24,180✔
4481
    }
4482

4483
    return paths;
4,267✔
4484
  }
4485

4486
  /**
4487
   * Map output addresses to paths.
4488
   * @param {TX} tx
4489
   * @returns {Promise<Path[]>}
4490
   */
4491

4492
  async getOutputPaths(tx) {
4493
    const paths = [];
×
4494
    const hashes = tx.getOutputHashes();
×
4495

4496
    for (const hash of hashes) {
×
4497
      const path = await this.getPath(hash);
×
4498
      if (path)
×
4499
        paths.push(path);
×
4500
    }
4501

4502
    return paths;
×
4503
  }
4504

4505
  /**
4506
   * Sync address depths based on a transaction's outputs.
4507
   * This is used for deriving new addresses when
4508
   * a confirmed transaction is seen.
4509
   * @param {TX} tx
4510
   * @returns {Promise<WalletKey[]>} - derived rings.
4511
   */
4512

4513
  async syncOutputDepth(tx) {
4514
    const map = new Map();
15,893✔
4515

4516
    for (const hash of tx.getOutputHashes()) {
15,893✔
4517
      const path = await this.readPath(hash);
46,521✔
4518

4519
      if (!path)
46,521✔
4520
        continue;
8,034✔
4521

4522
      if (path.index === -1)
38,487✔
4523
        continue;
1✔
4524

4525
      if (!map.has(path.account))
38,486✔
4526
        map.set(path.account, []);
15,903✔
4527

4528
      map.get(path.account).push(path);
38,486✔
4529
    }
4530

4531
    const derived = [];
15,893✔
4532
    const b = this.db.batch();
15,893✔
4533

4534
    for (const [acct, paths] of map) {
15,893✔
4535
      let receive = -1;
15,903✔
4536
      let change = -1;
15,903✔
4537

4538
      for (const path of paths) {
15,903✔
4539
        switch (path.branch) {
38,486✔
4540
          case 0:
4541
            if (path.index > receive)
33,408✔
4542
              receive = path.index;
17,440✔
4543
            break;
33,408✔
4544
          case 1:
4545
            if (path.index > change)
5,078!
4546
              change = path.index;
5,078✔
4547
            break;
5,078✔
4548
        }
4549
      }
4550

4551
      receive += 2;
15,903✔
4552
      change += 2;
15,903✔
4553

4554
      const account = await this.getAccount(acct);
15,903✔
4555
      assert(account);
15,903✔
4556

4557
      const ring = await account.syncDepth(b, receive, change);
15,903✔
4558

4559
      if (ring)
15,903✔
4560
        derived.push(ring);
2,751✔
4561
    }
4562

4563
    await b.write();
15,893✔
4564

4565
    return derived;
15,893✔
4566
  }
4567

4568
  /**
4569
   * Build input scripts templates for a transaction (does not
4570
   * sign, only creates signature slots). Only builds scripts
4571
   * for inputs that are redeemable by this wallet.
4572
   * @param {MTX} mtx
4573
   * @returns {Promise<Number>} - total number of scripts built.
4574
   */
4575

4576
  async template(mtx) {
4577
    const rings = await this.deriveInputs(mtx);
2,131✔
4578
    return mtx.template(rings);
2,131✔
4579
  }
4580

4581
  /**
4582
   * Build input scripts and sign inputs for a transaction. Only attempts
4583
   * to build/sign inputs that are redeemable by this wallet.
4584
   * @param {MTX} mtx
4585
   * @param {String|Buffer} passphrase
4586
   * @returns {Promise} - Returns Number (total number
4587
   * of inputs scripts built and signed).
4588
   */
4589

4590
  async sign(mtx, passphrase) {
4591
    if (this.watchOnly)
2,141!
4592
      throw new Error('Cannot sign from a watch-only wallet.');
×
4593

4594
    await this.unlock(passphrase);
2,141✔
4595

4596
    const rings = await this.deriveInputs(mtx);
2,135✔
4597

4598
    return mtx.signAsync(rings, Script.hashType.ALL, this.wdb.workers);
2,135✔
4599
  }
4600

4601
  /**
4602
   * Get pending ancestors up to the policy limit
4603
   * @param {TX} tx
4604
   * @returns {Promise<BufferSet>} - Returns {BufferSet} with Hash
4605
   */
4606

4607
   async getPendingAncestors(tx) {
4608
    return this._getPendingAncestors(tx, new BufferSet());
1,973✔
4609
   }
4610

4611
  /**
4612
   * Get pending ancestors up to the policy limit.
4613
   * @param {TX} tx
4614
   * @param {BufferSet} set
4615
   * @returns {Promise<BufferSet>}
4616
   */
4617

4618
  async _getPendingAncestors(tx, set) {
4619
    for (const {prevout} of tx.inputs) {
3,454✔
4620
      const hash = prevout.hash;
11,093✔
4621

4622
      if (set.has(hash))
11,093!
4623
        continue;
×
4624

4625
      if (!await this.hasPending(hash))
11,093✔
4626
        continue;
9,612✔
4627

4628
      set.add(hash);
1,481✔
4629

4630
      if (set.size > this.maxAncestors)
1,481!
4631
        break;
×
4632

4633
      const parent = await this.getTX(hash);
1,481✔
4634
      await this._getPendingAncestors(parent.tx, set);
1,481✔
4635

4636
      if (set.size > this.maxAncestors)
1,481!
4637
        break;
×
4638
    }
4639

4640
    return set;
3,454✔
4641
  }
4642

4643
  /**
4644
   * Test whether the database has a pending transaction.
4645
   * @param {Hash} hash
4646
   * @returns {Promise<Boolean>}
4647
   */
4648

4649
  hasPending(hash) {
4650
    return this.txdb.hasPending(hash);
11,093✔
4651
  }
4652

4653
  /**
4654
   * Get a coin viewpoint.
4655
   * @param {TX} tx
4656
   * @returns {Promise<CoinView>}
4657
   */
4658

4659
  getCoinView(tx) {
4660
    return this.txdb.getCoinView(tx);
6✔
4661
  }
4662

4663
  /**
4664
   * Get a wallet coin viewpoint with HD paths.
4665
   * @param {TX} tx
4666
   * @param {CoinView?} [view] - Coins to be used in wallet coin viewpoint.
4667
   * @returns {Promise<WalletCoinView>}
4668
   */
4669

4670
  async getWalletCoinView(tx, view) {
4671
    if (!(view instanceof CoinView))
23✔
4672
      view = new CoinView();
2✔
4673

4674
    if (!tx.hasCoins(view))
23✔
4675
      view = await this.txdb.getCoinView(tx);
2✔
4676

4677
    const wview = WalletCoinView.fromCoinView(view);
23✔
4678

4679
    for (const input of tx.inputs) {
23✔
4680
      const prevout = input.prevout;
44✔
4681
      const coin = wview.getCoin(prevout);
44✔
4682

4683
      if (!coin)
44!
4684
        continue;
×
4685

4686
      const path = await this.getPath(coin.address);
44✔
4687

4688
      if (!path)
44!
4689
        continue;
×
4690

4691
      const account = await this.getAccount(path.account);
44✔
4692

4693
      if (!account)
44!
4694
        continue;
×
4695

4696
      // The account index in the db may be wrong.
4697
      // We must read it from the stored xpub to be
4698
      // sure of its correctness.
4699
      //
4700
      // For more details see:
4701
      // https://github.com/bcoin-org/bcoin/issues/698.
4702
      //
4703
      // TODO(boymanjor): remove index manipulation
4704
      // once the watch-only wallet bug is fixed.
4705
      path.account = account.accountKey.childIndex;
44✔
4706

4707
      // Unharden the account index, if necessary.
4708
      if (path.account & HD.common.HARDENED)
44!
4709
        path.account ^= HD.common.HARDENED;
44✔
4710

4711
      // Add path to the viewpoint.
4712
      wview.addPath(prevout, path);
44✔
4713
    }
4714

4715
    return wview;
23✔
4716
  }
4717

4718
  /**
4719
   * Get a historical coin viewpoint.
4720
   * @param {TX} tx
4721
   * @returns {Promise<CoinView>}
4722
   */
4723

4724
  getSpentView(tx) {
4725
    return this.txdb.getSpentView(tx);
×
4726
  }
4727

4728
  /**
4729
   * Convert transaction to transaction details.
4730
   * @param {TXRecord} wtx
4731
   * @returns {Promise<Details>}
4732
   */
4733

4734
  toDetails(wtx) {
4735
    return this.txdb.toDetails(wtx);
1,234✔
4736
  }
4737

4738
  /**
4739
   * Get transaction details.
4740
   * @param {Hash} hash
4741
   * @returns {Promise<Details>}
4742
   */
4743

4744
  getDetails(hash) {
4745
    return this.txdb.getDetails(hash);
936✔
4746
  }
4747

4748
  /**
4749
   * Get a coin from the wallet.
4750
   * @param {Hash} hash
4751
   * @param {Number} index
4752
   * @returns {Promise<Coin>}
4753
   */
4754

4755
  getCoin(hash, index) {
4756
    return this.txdb.getCoin(hash, index);
52✔
4757
  }
4758

4759
  /**
4760
   * Get an unspent coin from the wallet.
4761
   * @param {Hash} hash
4762
   * @param {Number} index
4763
   * @returns {Promise<Coin>}
4764
   */
4765

4766
  async getUnspentCoin(hash, index) {
4767
    const credit = await this.txdb.getCredit(hash, index);
52,721✔
4768

4769
    if (!credit || credit.spent)
52,721✔
4770
      return null;
41,910✔
4771

4772
    return credit.coin;
10,811✔
4773
  }
4774

4775
  /**
4776
   * Get credit from the wallet.
4777
   * @param {Hash} hash
4778
   * @param {Number} index
4779
   * @returns {Promise<Credit>}
4780
   */
4781

4782
  getCredit(hash, index) {
4783
    return this.txdb.getCredit(hash, index);
4,652✔
4784
  }
4785

4786
  /**
4787
   * Get a transaction from the wallet.
4788
   * @param {Hash} hash
4789
   * @returns {Promise<TXRecord>}
4790
   */
4791

4792
  getTX(hash) {
4793
    return this.txdb.getTX(hash);
1,517✔
4794
  }
4795

4796
  /**
4797
   * List blocks for the wallet.
4798
   * @returns {Promise<BlockRecord[]>}
4799
   */
4800

4801
  getBlocks() {
4802
    return this.txdb.getBlocks();
×
4803
  }
4804

4805
  /**
4806
   * Get a block from the wallet.
4807
   * @param {Number} height
4808
   * @returns {Promise} - Returns {@link BlockRecord}.
4809
   */
4810

4811
  getBlock(height) {
4812
    return this.txdb.getBlock(height);
×
4813
  }
4814

4815
  /**
4816
   * Get all names.
4817
   * @returns {Promise<NameState[]>}
4818
   */
4819

4820
  async getNames() {
4821
    return this.txdb.getNames();
68✔
4822
  }
4823

4824
  /**
4825
   * Get a name if present.
4826
   * @param {Buffer} nameHash
4827
   * @returns {Promise<NameState>}
4828
   */
4829

4830
  async getNameState(nameHash) {
4831
    return this.txdb.getNameState(nameHash);
127,796✔
4832
  }
4833

4834
  /**
4835
   * Get a name if present.
4836
   * @param {String|Buffer} name
4837
   * @returns {Promise<NameState>}
4838
   */
4839

4840
  async getNameStateByName(name) {
4841
    return this.txdb.getNameState(rules.hashName(name));
862✔
4842
  }
4843

4844
  /**
4845
   * Get a blind value if present.
4846
   * @param {Buffer} blind - Blind hash.
4847
   * @returns {Promise<BlindValue>}
4848
   */
4849

4850
  async getBlind(blind) {
4851
    return this.txdb.getBlind(blind);
4,075✔
4852
  }
4853

4854
  /**
4855
   * Get bid
4856
   * @param {Buffer} nameHash
4857
   * @param {Outpoint} outpoint
4858
   * @returns {Promise<BlindBid?>}
4859
   */
4860

4861
  async getBid(nameHash, outpoint) {
4862
    return this.txdb.getBid(nameHash, outpoint);
10✔
4863
  }
4864

4865
  /**
4866
   * Get all bids for name.
4867
   * @param {Buffer} [nameHash]
4868
   * @returns {Promise<BlindBid[]>}
4869
   */
4870

4871
  async getBids(nameHash) {
4872
    return this.txdb.getBids(nameHash);
325✔
4873
  }
4874

4875
  /**
4876
   * Get all bids for name.
4877
   * @param {String|Buffer} [name]
4878
   * @returns {Promise<BlindBid[]>}
4879
   */
4880

4881
  async getBidsByName(name) {
4882
    return this.txdb.getBids(name ? rules.hashName(name) : null);
35✔
4883
  }
4884

4885
  /**
4886
   * Get bid by reveal.
4887
   * @param {Buffer} nameHash
4888
   * @param {Outpoint} outpoint - reveal outpoint
4889
   * @returns {Promise<BlindBid?>}
4890
   */
4891

4892
  async getBidByReveal(nameHash, outpoint) {
4893
    return this.txdb.getBidByReveal(nameHash, outpoint);
5✔
4894
  }
4895

4896
  /**
4897
   * Get reveal.
4898
   * @param {Buffer} nameHash
4899
   * @param {Outpoint} outpoint
4900
   * @returns {Promise<BidReveal?>}
4901
   */
4902

4903
  async getReveal(nameHash, outpoint) {
4904
    return this.txdb.getReveal(nameHash, outpoint);
5✔
4905
  }
4906

4907
  /**
4908
   * Get all reveals by name.
4909
   * @param {Buffer} nameHash
4910
   * @returns {Promise<BidReveal[]>}
4911
   */
4912

4913
  async getReveals(nameHash) {
4914
    return this.txdb.getReveals(nameHash);
4✔
4915
  }
4916

4917
  /**
4918
   * Get all reveals by name.
4919
   * @param {String|Buffer} name
4920
   * @returns {Promise<BidReveal[]>}
4921
   */
4922

4923
  async getRevealsByName(name) {
4924
    return this.txdb.getReveals(name ? rules.hashName(name) : null);
9✔
4925
  }
4926

4927
  /**
4928
   * Get reveal for bid.
4929
   * @param {Buffer} nameHash
4930
   * @param {Outpoint} outpoint - bid outpoint
4931
   * @returns {Promise<BidReveal?>}
4932
   */
4933

4934
  async getRevealByBid(nameHash, outpoint) {
4935
    return this.txdb.getRevealByBid(nameHash, outpoint);
10✔
4936
  }
4937

4938
  /**
4939
   * Add a transaction to the wallets TX history.
4940
   * @param {TX} tx
4941
   * @param {BlockMeta} [block]
4942
   * @param {BlockExtraInfo} [extra]
4943
   * @returns {Promise<AddResult?>}
4944
   */
4945

4946
  async add(tx, block, extra) {
4947
    const unlock = await this.writeLock.lock();
21,634✔
4948
    try {
21,634✔
4949
      return await this._add(tx, block, extra);
21,634✔
4950
    } finally {
4951
      unlock();
21,634✔
4952
    }
4953
  }
4954

4955
  /**
4956
   * Add a transaction to the wallet without a lock.
4957
   * Potentially resolves orphans.
4958
   * @private
4959
   * @param {TX} tx
4960
   * @param {BlockMeta} [block]
4961
   * @param {BlockExtraInfo} [extra]
4962
   * @returns {Promise<AddResult?>}
4963
   */
4964

4965
  async _add(tx, block, extra) {
4966
    const details = await this.txdb.add(tx, block, extra);
21,634✔
4967

4968
    if (!details)
21,633✔
4969
      return null;
5,740✔
4970

4971
    const derived = await this.syncOutputDepth(tx);
15,893✔
4972

4973
    if (derived.length > 0) {
15,893✔
4974
      this.wdb.emit('address', this, derived);
2,750✔
4975
      this.emit('address', derived);
2,750✔
4976
    }
4977

4978
    return {
15,893✔
4979
      details,
4980
      derived
4981
    };
4982
  }
4983

4984
  /**
4985
   * Revert a block.
4986
   * @param {Number} height
4987
   * @returns {Promise<Number>} - number of txs removed.
4988
   */
4989

4990
  async revert(height) {
4991
    const unlock = await this.writeLock.lock();
1,603✔
4992
    try {
1,603✔
4993
      return await this.txdb.revert(height);
1,603✔
4994
    } finally {
4995
      unlock();
1,603✔
4996
    }
4997
  }
4998

4999
  /**
5000
   * Remove a wallet transaction.
5001
   * @param {Hash} hash
5002
   * @returns {Promise<Details?>}
5003
   */
5004

5005
  async remove(hash) {
5006
    const unlock = await this.writeLock.lock();
10✔
5007
    try {
10✔
5008
      return await this.txdb.remove(hash);
10✔
5009
    } finally {
5010
      unlock();
10✔
5011
    }
5012
  }
5013

5014
  /**
5015
   * Recalculate balances
5016
   * @returns {Promise}
5017
   */
5018

5019
  async recalculateBalances() {
5020
    const unlock1 = await this.writeLock.lock();
1,713✔
5021
    const unlock2 = await this.fundLock.lock();
1,713✔
5022

5023
    try {
1,713✔
5024
      return await this.txdb.recalculateBalances();
1,713✔
5025
    } finally {
5026
      unlock2();
1,713✔
5027
      unlock1();
1,713✔
5028
    }
5029
  }
5030

5031
  /**
5032
   * Zap stale TXs from wallet.
5033
   * @param {(Number|String)?} acct
5034
   * @param {Number} age - Age threshold (unix time, default=72 hours).
5035
   * @returns {Promise<Hash[]>}
5036
   */
5037

5038
  async zap(acct, age) {
5039
    const unlock = await this.writeLock.lock();
103✔
5040
    try {
103✔
5041
      return await this._zap(acct, age);
103✔
5042
    } finally {
5043
      unlock();
103✔
5044
    }
5045
  }
5046

5047
  /**
5048
   * Zap stale TXs from wallet without a lock.
5049
   * @private
5050
   * @param {(Number|String)?} acct
5051
   * @param {Number} age
5052
   * @returns {Promise<Hash[]>}
5053
   */
5054

5055
  async _zap(acct, age) {
5056
    const account = await this.ensureIndex(acct);
103✔
5057
    return this.txdb.zap(account, age);
103✔
5058
  }
5059

5060
  /**
5061
   * Abandon transaction.
5062
   * @param {Hash} hash
5063
   * @returns {Promise<Details>} - removed tx details.
5064
   */
5065

5066
  async abandon(hash) {
5067
    const unlock = await this.writeLock.lock();
19✔
5068
    try {
19✔
5069
      return await this._abandon(hash);
19✔
5070
    } finally {
5071
      unlock();
19✔
5072
    }
5073
  }
5074

5075
  /**
5076
   * Abandon transaction without a lock.
5077
   * @private
5078
   * @param {Hash} hash
5079
   * @returns {Promise<Details>} - removed tx details.
5080
   */
5081

5082
  _abandon(hash) {
5083
    return this.txdb.abandon(hash);
19✔
5084
  }
5085

5086
  /**
5087
   * Lock a single coin.
5088
   * @param {Coin|Outpoint} coin
5089
   */
5090

5091
  lockCoin(coin) {
5092
    return this.txdb.lockCoin(coin);
4✔
5093
  }
5094

5095
  /**
5096
   * Unlock a single coin.
5097
   * @param {Coin|Outpoint} coin
5098
   * @returns {Boolean}
5099
   */
5100

5101
  unlockCoin(coin) {
5102
    return this.txdb.unlockCoin(coin);
×
5103
  }
5104

5105
  /**
5106
   * Unlock all locked coins.
5107
   */
5108

5109
  unlockCoins() {
5110
    return this.txdb.unlockCoins();
×
5111
  }
5112

5113
  /**
5114
   * Test locked status of a single coin.
5115
   * @param {Coin|Outpoint} coin
5116
   * @returns {Boolean}
5117
   */
5118

5119
  isLocked(coin) {
5120
    return this.txdb.isLocked(coin);
×
5121
  }
5122

5123
  /**
5124
   * Return an array of all locked outpoints.
5125
   * @returns {Outpoint[]}
5126
   */
5127

5128
  getLocked() {
5129
    return this.txdb.getLocked();
×
5130
  }
5131

5132
  /**
5133
   * Get all available coins.
5134
   * @param {(String|Number)?} [acct]
5135
   * @returns {Promise<Coin[]>}
5136
   */
5137

5138
  async getCoins(acct) {
5139
    const account = await this.ensureIndex(acct);
2,151✔
5140
    return this.txdb.getCoins(account);
2,151✔
5141
  }
5142

5143
  /**
5144
   * Get all available credits.
5145
   * @param {(String|Number)?} [acct]
5146
   * @returns {Promise<Credit[]>}
5147
   */
5148

5149
  async getCredits(acct) {
5150
    const account = await this.ensureIndex(acct);
4✔
5151
    return this.txdb.getCredits(account);
4✔
5152
  }
5153

5154
  /**
5155
   * Get "smart" coins.
5156
   * @param {(String|Number)?} acct
5157
   * @returns {Promise<Coin[]>}
5158
   */
5159

5160
  async getSmartCoins(acct) {
5161
    const credits = await this.getCredits(acct);
4✔
5162
    const coins = [];
4✔
5163

5164
    for (const credit of credits) {
4✔
5165
      const coin = credit.coin;
28✔
5166

5167
      if (credit.spent)
28✔
5168
        continue;
2✔
5169

5170
      if (this.txdb.isLocked(coin))
26!
5171
        continue;
×
5172

5173
      // Always used confirmed coins.
5174
      if (coin.height !== -1) {
26✔
5175
        coins.push(coin);
12✔
5176
        continue;
12✔
5177
      }
5178

5179
      // Use unconfirmed only if they were
5180
      // created as a result of one of our
5181
      // _own_ transactions. i.e. they're
5182
      // not low-fee and not in danger of
5183
      // being double-spent by a bad actor.
5184
      if (!credit.own)
14✔
5185
        continue;
12✔
5186

5187
      coins.push(coin);
2✔
5188
    }
5189

5190
    return coins;
4✔
5191
  }
5192

5193
  /**
5194
   * Get all pending/unconfirmed transactions.
5195
   * @param {(String|Number)?} [acct]
5196
   * @returns {Promise<TXRecord[]>}
5197
   */
5198

5199
  async getPending(acct) {
5200
    const account = await this.ensureIndex(acct);
14✔
5201
    return this.txdb.getPending(account);
14✔
5202
  }
5203

5204
  /**
5205
   * Get wallet balance.
5206
   * @param {(String|Number)?} [acct]
5207
   * @returns {Promise<Balance>}
5208
   */
5209

5210
  async getBalance(acct) {
5211
    const account = await this.ensureIndex(acct);
3,795✔
5212
    return this.txdb.getBalance(account);
3,795✔
5213
  }
5214

5215
  /**
5216
   * @param {(String|Number)?} acct
5217
   * @param {Object} options
5218
   * @param {Number} options.limit
5219
   * @param {Boolean} options.reverse
5220
   * @returns {Promise<TXRecord[]>}
5221
   */
5222

5223
  async listHistory(acct, options) {
5224
    const account = await this.ensureIndex(acct);
160✔
5225
    return this.txdb.listHistory(account, options);
160✔
5226
  }
5227

5228
  /**
5229
   * @param {(String|Number)?} acct
5230
   * @param {Object} options
5231
   * @param {Buffer} options.hash
5232
   * @param {Number} options.limit
5233
   * @param {Boolean} options.reverse
5234
   * @returns {Promise<TXRecord[]>}
5235
   */
5236

5237
  async listHistoryAfter(acct, options) {
5238
    const account = await this.ensureIndex(acct);
668✔
5239
    return this.txdb.listHistoryAfter(account, options);
668✔
5240
  }
5241

5242
  /**
5243
   * @param {(String|Number)?} acct
5244
   * @param {Object} options
5245
   * @param {Buffer} options.hash
5246
   * @param {Number} options.limit
5247
   * @param {Boolean} options.reverse
5248
   * @returns {Promise<TXRecord[]>}
5249
   */
5250

5251
  async listHistoryFrom(acct, options) {
5252
    const account = await this.ensureIndex(acct);
1✔
5253
    return this.txdb.listHistoryAfter(account, options);
1✔
5254
  }
5255

5256
  /**
5257
   * @param {(String|Number)?} acct
5258
   * @param {Object} options
5259
   * @param {Number} options.time - Time in seconds.
5260
   * @param {Number} options.limit
5261
   * @param {Boolean} options.reverse
5262
   * @returns {Promise<TXRecord[]>}
5263
   */
5264

5265
  async listHistoryByTime(acct, options) {
5266
    const account = await this.ensureIndex(acct);
321✔
5267
    return this.txdb.listHistoryByTime(account, options);
321✔
5268
  }
5269

5270
  /**
5271
   * @param {(String|Number)?} acct
5272
   * @param {Object} options
5273
   * @param {Number} options.limit
5274
   * @param {Boolean} options.reverse
5275
   * @returns {Promise<TXRecord[]>}
5276
   */
5277

5278
  async listUnconfirmed(acct, options) {
5279
    const account = await this.ensureIndex(acct);
149✔
5280
    return this.txdb.listUnconfirmed(account, options);
148✔
5281
  }
5282

5283
  /**
5284
   * @param {(String|Number)?} acct
5285
   * @param {Object} options
5286
   * @param {Buffer} options.hash
5287
   * @param {Number} options.limit
5288
   * @param {Boolean} options.reverse
5289
   * @returns {Promise<TXRecord[]>}
5290
   */
5291

5292
  async listUnconfirmedAfter(acct, options) {
5293
    const account = await this.ensureIndex(acct);
328✔
5294
    return this.txdb.listUnconfirmedAfter(account, options);
328✔
5295
  }
5296

5297
  /**
5298
   * @param {(String|Number)?} acct
5299
   * @param {Object} options
5300
   * @param {Buffer} options.hash
5301
   * @param {Number} options.limit
5302
   * @param {Boolean} options.reverse
5303
   * @returns {Promise<TXRecord[]>}
5304
   */
5305

5306
  async listUnconfirmedFrom(acct, options) {
5307
    const account = await this.ensureIndex(acct);
1✔
5308
    return this.txdb.listUnconfirmedFrom(account, options);
1✔
5309
  }
5310

5311
  /**
5312
   * @param {(String|Number)?} acct
5313
   * @param {Object} options
5314
   * @param {Number} options.time - Time in seconds.
5315
   * @param {Number} options.limit
5316
   * @param {Boolean} options.reverse
5317
   * @returns {Promise<TXRecord[]>}
5318
   */
5319

5320
  async listUnconfirmedByTime(acct, options) {
5321
    const account = await this.ensureIndex(acct);
355✔
5322
    return this.txdb.listUnconfirmedByTime(account, options);
355✔
5323
  }
5324

5325
  /**
5326
   * Get account key.
5327
   * @param {Number} [acct=0]
5328
   * @returns {Promise<HDPublicKey>}
5329
   */
5330

5331
  async accountKey(acct = 0) {
1✔
5332
    const account = await this.getAccount(acct);
9✔
5333

5334
    if (!account)
9!
5335
      throw new Error('Account not found.');
×
5336

5337
    return account.accountKey;
9✔
5338
  }
5339

5340
  /**
5341
   * Get current receive depth.
5342
   * @param {Number} [acct=0]
5343
   * @returns {Promise<Number>}
5344
   */
5345

5346
  async receiveDepth(acct = 0) {
3✔
5347
    const account = await this.getAccount(acct);
3✔
5348

5349
    if (!account)
3!
5350
      throw new Error('Account not found.');
×
5351

5352
    return account.receiveDepth;
3✔
5353
  }
5354

5355
  /**
5356
   * Get current change depth.
5357
   * @param {Number} [acct=0]
5358
   * @returns {Promise<Number>}
5359
   */
5360

5361
  async changeDepth(acct = 0) {
3✔
5362
    const account = await this.getAccount(acct);
3✔
5363

5364
    if (!account)
3!
5365
      throw new Error('Account not found.');
×
5366

5367
    return account.changeDepth;
3✔
5368
  }
5369

5370
  /**
5371
   * Get current receive address.
5372
   * @param {(String|Number)} [acct=0]
5373
   * @returns {Promise<Address>}
5374
   */
5375

5376
  async receiveAddress(acct = 0) {
546✔
5377
    const account = await this.getAccount(acct);
2,918✔
5378

5379
    if (!account)
2,918!
5380
      throw new Error('Account not found.');
×
5381

5382
    return account.receiveAddress();
2,918✔
5383
  }
5384

5385
  /**
5386
   * Get current change address.
5387
   * @param {Number} [acct=0]
5388
   * @returns {Promise<Address>}
5389
   */
5390

5391
  async changeAddress(acct = 0) {
18✔
5392
    const account = await this.getAccount(acct);
2,546✔
5393

5394
    if (!account)
2,546!
5395
      throw new Error('Account not found.');
×
5396

5397
    return account.changeAddress();
2,546✔
5398
  }
5399

5400
  /**
5401
   * Get current receive key.
5402
   * @param {Number} [acct=0]
5403
   * @returns {Promise<WalletKey>}
5404
   */
5405

5406
  async receiveKey(acct = 0) {
×
5407
    const account = await this.getAccount(acct);
2✔
5408

5409
    if (!account)
2!
5410
      throw new Error('Account not found.');
×
5411

5412
    return account.receiveKey();
2✔
5413
  }
5414

5415
  /**
5416
   * Get current change key.
5417
   * @param {Number} [acct=0]
5418
   * @returns {Promise<WalletKey>}
5419
   */
5420

5421
  async changeKey(acct = 0) {
×
5422
    const account = await this.getAccount(acct);
×
5423

5424
    if (!account)
×
5425
      throw new Error('Account not found.');
×
5426

UNCOV
5427
    return account.changeKey();
×
5428
  }
5429

5430
  /**
5431
   * Convert the wallet to a more inspection-friendly object.
5432
   * @returns {Object}
5433
   */
5434

5435
  format() {
5436
    return {
×
5437
      wid: this.wid,
5438
      id: this.id,
5439
      network: this.network.type,
5440
      accountDepth: this.accountDepth,
5441
      token: this.token.toString('hex'),
5442
      tokenDepth: this.tokenDepth,
5443
      master: this.master
5444
    };
5445
  }
5446

5447
  /**
5448
   * Convert the wallet to a more inspection-friendly object.
5449
   * @returns {Object}
5450
   */
5451

5452
  inspect() {
5453
    return this.format();
×
5454
  }
5455

5456
  /**
5457
   * Convert the wallet to an object suitable for
5458
   * serialization.
5459
   * @param {Boolean?} [unsafe] - Whether to include
5460
   * the master key in the JSON.
5461
   * @param {Balance?} [balance]
5462
   * @returns {Object}
5463
   */
5464

5465
  getJSON(unsafe, balance) {
5466
    return {
248✔
5467
      network: this.network.type,
5468
      wid: this.wid,
5469
      id: this.id,
5470
      watchOnly: this.watchOnly,
5471
      accountDepth: this.accountDepth,
5472
      token: this.token.toString('hex'),
5473
      tokenDepth: this.tokenDepth,
5474
      master: this.master.getJSON(this.network, unsafe),
5475
      balance: balance ? balance.getJSON(true) : null
248✔
5476
    };
5477
  }
5478

5479
  /**
5480
   * Convert the wallet to an object suitable for
5481
   * serialization.
5482
   * @returns {Object}
5483
   */
5484

5485
  toJSON() {
5486
    return this.getJSON(false);
210✔
5487
  }
5488

5489
  /**
5490
   * Calculate serialization size.
5491
   * @returns {Number}
5492
   */
5493

5494
  getSize() {
5495
    let size = 0;
565✔
5496
    size += 41;
565✔
5497
    size += this.master.getSize();
565✔
5498
    return size;
565✔
5499
  }
5500

5501
  /**
5502
   * Serialize the wallet.
5503
   * @returns {Buffer}
5504
   */
5505

5506
  encode() {
5507
    const size = this.getSize();
565✔
5508
    const bw = bio.write(size);
565✔
5509

5510
    let flags = 0;
565✔
5511

5512
    if (this.watchOnly)
565✔
5513
      flags |= 1;
4✔
5514

5515
    bw.writeU8(flags);
565✔
5516
    bw.writeU32(this.accountDepth);
565✔
5517
    bw.writeBytes(this.token);
565✔
5518
    bw.writeU32(this.tokenDepth);
565✔
5519
    this.master.write(bw);
565✔
5520

5521
    return bw.render();
565✔
5522
  }
5523

5524
  /**
5525
   * Inject properties from serialized data.
5526
   * @param {Buffer} data
5527
   * @returns {this}
5528
   */
5529

5530
  decode(data) {
5531
    const br = bio.read(data);
30✔
5532

5533
    const flags = br.readU8();
30✔
5534

5535
    this.watchOnly = (flags & 1) !== 0;
30✔
5536
    this.accountDepth = br.readU32();
30✔
5537
    this.token = br.readBytes(32);
30✔
5538
    this.tokenDepth = br.readU32();
30✔
5539
    this.master.read(br);
30✔
5540

5541
    return this;
30✔
5542
  }
5543

5544
  /**
5545
   * Instantiate a wallet from serialized data.
5546
   * @param {WalletDB} wdb
5547
   * @param {Buffer} data
5548
   * @returns {Wallet}
5549
   */
5550

5551
  static decode(wdb, data) {
5552
    return new this(wdb).decode(data);
30✔
5553
  }
5554

5555
  /**
5556
   * Test an object to see if it is a Wallet.
5557
   * @param {Object} obj
5558
   * @returns {Boolean}
5559
   */
5560

5561
  static isWallet(obj) {
5562
    return obj instanceof Wallet;
×
5563
  }
5564
}
5565

5566
/*
5567
 * Expose
5568
 */
5569

5570
module.exports = Wallet;
1✔
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

© 2025 Coveralls, Inc