'use strict';

const Network = require('../lib/protocol/network');
const MTX = require('../lib/primitives/mtx');
const {isSignatureEncoding, isKeyEncoding} = require('../lib/script/common');
const {Resource} = require('../lib/dns/resource');
const Address = require('../lib/primitives/address');
const Output = require('../lib/primitives/output');
const HD = require('../lib/hd/hd');
const Mnemonic = require('../lib/hd/mnemonic');
const rules = require('../lib/covenants/rules');
const {types} = rules;
const secp256k1 = require('bcrypto/lib/secp256k1');
const network = Network.get('regtest');
const assert = require('bsert');
const {BufferSet} = require('buffer-map');
const common = require('./util/common');
const Outpoint = require('../lib/primitives/outpoint');
const consensus = require('../lib/protocol/consensus');
const NodeContext = require('./util/node-context');
const {forEvent, sleep} = require('./util/common');
const {generateInitialBlocks} = require('./util/pagination');

const {
  treeInterval,
  biddingPeriod,
  revealPeriod,
  transferLockup
} = network.names;

describe('Wallet HTTP', function() {
  this.timeout(20000);

  /** @type {NodeContext} */
  let nodeCtx;
  let wclient, nclient;

  // primary wallet client.
  let wallet, cbAddress;

  const beforeAll = async () => {
    nodeCtx = new NodeContext({
      apiKey: 'foo',
      network: 'regtest',
      walletAuth: true,
      wallet: true
    });

    await nodeCtx.open();

    wclient = nodeCtx.wclient;
    nclient = nodeCtx.nclient;

    wallet = nodeCtx.wclient.wallet('primary');
    cbAddress = (await wallet.createAddress('default')).address;
  };

  const afterAll = async () => {
    await nodeCtx.close();
  };

  describe('Create wallet', function() {
    before(beforeAll);
    after(afterAll);

    it('should create wallet', async () => {
      const info = await wclient.createWallet('test');
      assert.strictEqual(info.id, 'test');
      const wallet = wclient.wallet('test', info.token);
      await wallet.open();
    });

    it('should create wallet with spanish mnemonic', async () => {
      await wclient.createWallet(
        'cartera1',
        {language: 'spanish'}
      );
      const master = await wclient.getMaster('cartera1');
      const phrase = master.mnemonic.phrase;
      for (const word of phrase.split(' ')) {
        const language = Mnemonic.getLanguage(word);
        assert.strictEqual(language, 'spanish');
        // Comprobar la cordura:
        assert.notStrictEqual(language, 'english');
      }

      // Verificar
      await wclient.createWallet(
        'cartera2',
        {mnemonic: phrase}
      );
      assert.deepStrictEqual(
        await wclient.getAccount('cartera1', 'default'),
        await wclient.getAccount('cartera2', 'default')
      );
    });
  });

  describe('Lookahead', function() {
    before(beforeAll);
    after(afterAll);

    it('should create wallet with default account 1000 lookahead', async () => {
      const wname = 'lookahead';
      await wclient.createWallet(wname, {
        lookahead: 1000
      });

      const defAccount = await wclient.getAccount(wname, 'default');
      assert.strictEqual(defAccount.lookahead, 1000);

      const newAccount = await wclient.createAccount(wname, 'newaccount', {
        lookahead: 1001
      });
      assert.strictEqual(newAccount.lookahead, 1001);
      const getNewAccount = await wclient.getAccount(wname, 'newaccount', {
        lookahead: 1001
      });

      assert.strictEqual(getNewAccount.lookahead, 1001);
    });

    it('should modify account lookahead to 1000', async () => {
      const wname = 'lookahead2';
      await wclient.createWallet(wname);

      const defAccount = await wclient.getAccount(wname, 'default');
      assert.strictEqual(defAccount.lookahead, 200);

      const modified = await wclient.modifyAccount(wname, 'default', {
        lookahead: 1000
      });
      assert.strictEqual(modified.lookahead, 1000);
    });
  });

  describe('Wallet info', function() {
    let wallet;

    before(async () => {
      await beforeAll();

      await wclient.createWallet('test');
      wallet = wclient.wallet('test');
    });
    after(afterAll);

    it('should get wallet info', async () => {
      const info = await wallet.getInfo();
      assert.strictEqual(info.id, 'test');
      const acct = await wallet.getAccount('default');
      const str = acct.receiveAddress;
      assert(typeof str === 'string');
    });
  });

  describe('Key/Address', function() {
    before(beforeAll);
    after(afterAll);

    it('should get key by address from watch-only', async () => {
      const phrase = 'abandon abandon abandon abandon abandon abandon '
        + 'abandon abandon abandon abandon abandon about';
      const master = HD.HDPrivateKey.fromPhrase(phrase);
      const xprv = master.deriveAccount(44, 5355, 5);
      const xpub = xprv.toPublic();
      const pubkey = xpub.derive(0).derive(0);
      const addr = Address.fromPubkey(pubkey.publicKey);
      const wallet = wclient.wallet('watchonly');
      await wclient.createWallet('watchonly', {
        watchOnly: true,
        accountKey: xpub.xpubkey('regtest')
      });
      const key = await wallet.getKey(addr.toString('regtest'));
      assert.equal(xpub.childIndex ^ HD.common.HARDENED, key.account);
      assert.equal(0, key.branch);
      assert.equal(0, key.index);
    });
  });

  describe('Mine/Fund', function() {
    before(beforeAll);
    after(afterAll);

    it('should mine to the primary/default wallet', async () => {
      const height = 20;

      await nodeCtx.mineBlocks(height, cbAddress);

      const info = await nclient.getInfo();
      assert.equal(info.chain.height, height);

      const accountInfo = await wallet.getAccount('default');
      // each coinbase output was indexed
      assert.equal(accountInfo.balance.coin, height);

      const coins = await wallet.getCoins();
      // the wallet has no previous history besides
      // what it has mined
      assert.ok(coins.every(coin => coin.coinbase === true));
    });
  });

  describe('Events', function() {
    before(beforeAll);
    after(afterAll);

    it('balance address and tx events', async () => {
      await wclient.createWallet('test');
      const testWallet = wclient.wallet('test');
      await testWallet.open();
      const {address} = await testWallet.createAddress('default');

      const mtx = new MTX();
      mtx.addOutpoint(new Outpoint(consensus.ZERO_HASH, 0));
      mtx.addOutput(address, 50460);
      mtx.addOutput(address, 50460);
      mtx.addOutput(address, 50460);
      mtx.addOutput(address, 50460);

      const tx = mtx.toTX();

      let balance = null;
      testWallet.once('balance', (b) => {
        balance = b;
      });

      let receive = null;
      testWallet.once('address', (r) => {
        receive = r[0];
      });

      let details = null;
      testWallet.once('tx', (d) => {
        details = d;
      });

      await nodeCtx.wdb.addTX(tx);
      await new Promise(r => setTimeout(r, 300));

      assert(receive);
      assert.strictEqual(receive.name, 'default');
      assert.strictEqual(receive.branch, 0);
      assert(balance);
      assert.strictEqual(balance.confirmed, 0);
      assert.strictEqual(balance.unconfirmed, 201840);
      assert(details);
      assert.strictEqual(details.hash, tx.txid());
    });
  });

  describe('Create/Send transaction', function() {
    let wallet2;

    before(async () => {
      await beforeAll();
      await nodeCtx.mineBlocks(20, cbAddress);
      await wclient.createWallet('secondary');
      wallet2 = wclient.wallet('secondary');
    });

    after(afterAll);

    it('should create a transaction', async () => {
      const tx = await wallet.createTX({
        outputs: [{ address: cbAddress, value: 1e4 }]
      });

      assert.ok(tx);
      assert.equal(tx.outputs.length, 1 + 1); // send + change
      assert.equal(tx.locktime, 0);
    });

    it('should create self-send transaction with HD paths', async () => {
      const tx = await wallet.createTX({
        paths: true,
        outputs: [{ address: cbAddress, value: 1e4 }]
      });

      assert.ok(tx);
      assert.ok(tx.inputs);

      for (let i = 0; i < tx.inputs.length; i++) {
        const path = tx.inputs[i].path;

        assert.ok(typeof path.name === 'string');
        assert.ok(typeof path.account === 'number');
        assert.ok(typeof path.change === 'boolean');
        assert.ok(typeof path.derivation === 'string');
      }

      // cbAddress is a self-send
      // so all output paths including change should be known
      for (let i = 0; i < tx.outputs.length; i++) {
        const path = tx.outputs[i].path;

        assert.ok(typeof path.name === 'string');
        assert.ok(typeof path.account === 'number');
        assert.ok(typeof path.change === 'boolean');
        assert.ok(typeof path.derivation === 'string');
      }
    });

    it('should create a transaction with HD paths', async () => {
      const tx = await wallet.createTX({
        paths: true,
        outputs: [{
          address: 'rs1qlf5se77y0xlg5940slyf00djvveskcsvj9sdrd',
          value: 1e4
        }]
      });

      assert.ok(tx);
      assert.ok(tx.inputs);

      for (let i = 0; i < tx.inputs.length; i++) {
        const path = tx.inputs[i].path;

        assert.ok(typeof path.name === 'string');
        assert.ok(typeof path.account === 'number');
        assert.ok(typeof path.change === 'boolean');
        assert.ok(typeof path.derivation === 'string');
      }
      {
        const path = tx.outputs[1].path; // change
        assert.ok(typeof path.name === 'string');
        assert.ok(typeof path.account === 'number');
        assert.ok(typeof path.change === 'boolean');
        assert.ok(typeof path.derivation === 'string');
      }
      {
        const path = tx.outputs[0].path; // receiver
        assert(!path);
      }
    });

    it('should create a transaction with a locktime', async () => {
      const locktime = 8e6;

      const tx = await wallet.createTX({
        locktime: locktime,
        outputs: [{ address: cbAddress, value: 1e4 }]
      });

      assert.equal(tx.locktime, locktime);
    });

    it('should create a transaction that is not bip 69 sorted', async () => {
      // create a list of outputs that descend in value
      // bip 69 sorts in ascending order based on the value
      const outputs = [];
      for (let i = 0; i < 5; i++) {
        const addr = await wallet.createAddress('default');
        outputs.push({ address: addr.address, value: (5 - i) * 1e5 });
      }

      const tx = await wallet.createTX({
        outputs: outputs,
        sort: false
      });

      // assert outputs in the same order that they were sent from the client
      for (const [i, output] of outputs.entries()) {
        assert.equal(tx.outputs[i].value, output.value);
        assert.equal(tx.outputs[i].address.toString(network), output.address);
      }

      const mtx = MTX.fromJSON(tx);
      mtx.sortMembers();

      // the order changes after sorting
      assert.ok(tx.outputs[0].value !== mtx.outputs[0].value);
    });

    it('should create a transaction that is bip 69 sorted', async () => {
      const outputs = [];
      for (let i = 0; i < 5; i++) {
        const addr = await wallet.createAddress('default');
        outputs.push({ address: addr.address, value: (5 - i) * 1e5 });
      }

      const tx = await wallet.createTX({
        outputs: outputs
      });

      const mtx = MTX.fromJSON(tx);
      mtx.sortMembers();

      // assert the ordering of the outputs is the
      // same after sorting the response client side
      for (const [i, output] of tx.outputs.entries()) {
        assert.equal(output.value, mtx.outputs[i].value);
        assert.equal(output.address, mtx.outputs[i].address.toString(network));
      }
    });

    it('should mine to the secondary/default wallet', async () => {
      const height = 5;

      const {address} = await wallet2.createAddress('default');
      await nodeCtx.mineBlocks(height, address);

      const accountInfo = await wallet2.getAccount('default');
      assert.equal(accountInfo.balance.coin, height);
    });
  });

  describe('Get balance', function() {
    before(async () => {
      await beforeAll();
      await nodeCtx.mineBlocks(20, cbAddress);
    });

    after(afterAll);

    it('should get balance', async () => {
      const balance = await wallet.getBalance();
      assert.equal(balance.tx, 20);
      assert.equal(balance.coin, 20);
    });
  });

  describe('Get TX', function() {
    let hash;

    before(async () => {
      await beforeAll();

      await nodeCtx.mineBlocks(10, cbAddress);
      const {address} = await wallet.createAddress('default');
      const tx = await wallet.send({outputs: [{address, value: 1e4}]});

      hash = tx.hash;
    });

    after(afterAll);

    it('should fail to get TX that does not exist', async () => {
      const hash = consensus.ZERO_HASH;
      const tx = await wallet.getTX(hash.toString('hex'));
      assert.strictEqual(tx, null);
    });

    it('should get TX', async () => {
      const tx = await wallet.getTX(hash.toString('hex'));
      assert(tx);
      assert.strictEqual(tx.hash, hash);
    });
  });

  describe('Zap TXs', function() {
    const TEST_WALLET = 'test';
    const DEFAULT = 'default';
    const ALT = 'alt';

    let testWallet;

    const resetPending = async () => {
      await wallet.zap(null, 0);
      nodeCtx.mempool.reset();
    };

    before(async () => {
      await beforeAll();

      await wclient.createWallet(TEST_WALLET);
      testWallet = wclient.wallet(TEST_WALLET);

      await testWallet.createAccount(ALT);

      await nodeCtx.mineBlocks(10, cbAddress);
    });

    afterEach(resetPending);

    after(afterAll);

    it('should zap all txs (wallet)', async () => {
      for (const account of [DEFAULT, ALT]) {
        const {address} = await testWallet.createAddress(account);

        for (let i = 0; i < 3; i++)
          await wallet.send({outputs: [{address, value: 1e4}]});
      }

      const result = await testWallet.zap(null, 0);
      assert.strictEqual(result.zapped, 6);
    });

    it('should zap all txs (account)', async () => {
      for (const account of [DEFAULT, ALT]) {
        const {address} = await testWallet.createAddress(account);

        for (let i = 0; i < 3; i++)
          await wallet.send({outputs: [{address, value: 1e4}]});
      }

      const resultDefault = await testWallet.zap(DEFAULT, 0);
      assert.strictEqual(resultDefault.zapped, 3);

      const resultAlt = await testWallet.zap(ALT, 0);
      assert.strictEqual(resultAlt.zapped, 3);
    });
  });

  describe('Create account (Integration)', function() {
    before(beforeAll);
    after(afterAll);

    it('should create an account', async () => {
      const info = await wallet.createAccount('foo');
      assert(info);
      assert(info.initialized);
      assert.strictEqual(info.name, 'foo');
      assert.strictEqual(info.accountIndex, 1);
      assert.strictEqual(info.m, 1);
      assert.strictEqual(info.n, 1);
    });

    it('should create account', async () => {
      const info = await wallet.createAccount('foo1');
      assert(info);
      assert(info.initialized);
      assert.strictEqual(info.name, 'foo1');
      assert.strictEqual(info.accountIndex, 2);
      assert.strictEqual(info.m, 1);
      assert.strictEqual(info.n, 1);
    });

    it('should create account', async () => {
      const info = await wallet.createAccount('foo2', {
        type: 'multisig',
        m: 1,
        n: 2
      });
      assert(info);
      assert(!info.initialized);
      assert.strictEqual(info.name, 'foo2');
      assert.strictEqual(info.accountIndex, 3);
      assert.strictEqual(info.m, 1);
      assert.strictEqual(info.n, 2);
    });
  });

  describe('Wallet auction (Integration)', function() {
    const accountTwo = 'foobar';

    let name, wallet2;

    const ownedNames = [];
    const allNames = [];

    before(async () => {
      await beforeAll();

      await nodeCtx.mineBlocks(20, cbAddress);
      await wallet.createAccount(accountTwo);

      await wclient.createWallet('secondary');
      wallet2 = wclient.wallet('secondary');
      const saddr = (await wallet2.createAddress('default')).address;
      await nodeCtx.mineBlocks(5, saddr);
    });

    after(afterAll);

    beforeEach(async () => {
      name = await nclient.execute('grindname', [5]);
    });

    afterEach(async () => {
      await nodeCtx.mempool.reset();
    });

    it('should have no name state indexed initially', async () => {
      const names = await wallet.getNames();
      assert.strictEqual(names.length, 0);
    });

    it('should allow covenants with create tx', async () => {
      const {address} = await wallet.createChange('default');

      const output = openOutput(name, address, network);

      const tx = await wallet.createTX({
        outputs: [output.getJSON(network)]
      });
      assert.equal(tx.outputs[0].covenant.type, types.OPEN);
    });

    it('should allow covenants with send tx', async () => {
      const {address} = await wallet.createChange('default');

      const output = openOutput(name, address, network);

      const tx = await wallet.send({
        outputs: [output.getJSON(network)]
      });

      assert.equal(tx.outputs[0].covenant.type, types.OPEN);
    });

    it('should create an open and broadcast the tx', async () => {
      let emitted = 0;
      const handler = () => emitted++;
      nodeCtx.mempool.on('tx', handler);

      const mempoolTXEvent = common.forEvent(nodeCtx.mempool, 'tx');
      const json = await wallet.createOpen({
        name: name
      });
      await mempoolTXEvent;

      const mempool = await nodeCtx.nclient.getMempool();

      assert.ok(mempool.includes(json.hash));

      const opens = json.outputs.filter(output => output.covenant.type === types.OPEN);
      assert.equal(opens.length, 1);

      assert.equal(emitted, 1);

      // reset for next test
      nodeCtx.mempool.removeListener('tx', handler);
    });

    it('should create an open and not broadcast the transaction', async () => {
      let entered = false;
      const handler = () => entered = true;
      nodeCtx.mempool.on('tx', handler);

      const json = await wallet.createOpen({
        name: name,
        broadcast: false
      });

      await sleep(200);

      // tx is not in the mempool
      assert.equal(entered, false);
      const mempool = await nclient.getMempool();
      assert.ok(!mempool.includes(json.hash));

      const mtx = MTX.fromJSON(json);
      assert.ok(mtx.hasWitness());

      // the signature and pubkey are templated correctly
      const sig = mtx.inputs[0].witness.get(0);
      assert.ok(isSignatureEncoding(sig));
      const pubkey = mtx.inputs[0].witness.get(1);
      assert.ok(isKeyEncoding(pubkey));
      assert.ok(secp256k1.publicKeyVerify(pubkey));

      // transaction is valid
      assert.ok(mtx.verify());

      const opens = mtx.outputs.filter(output => output.covenant.type === types.OPEN);
      assert.equal(opens.length, 1);

      // reset for next test
      nodeCtx.mempool.removeListener('tx', handler);
    });

    it('should create an open and not sign the transaction', async () => {
      let entered = false;
      const handler = () => entered = true;
      nodeCtx.mempool.on('tx', handler);

      const json = await wallet.createOpen({
        name: name,
        broadcast: false,
        sign: false
      });

      await sleep(200);

      // tx is not in the mempool
      assert.equal(entered, false);
      const mempool = await nclient.getMempool();
      assert.ok(!mempool.includes(json.hash));

      // the signature is templated as an
      // empty buffer
      const mtx = MTX.fromJSON(json);
      const sig = mtx.inputs[0].witness.get(0);
      assert.bufferEqual(Buffer.from(''), sig);
      assert.ok(!isSignatureEncoding(sig));

      // the pubkey is properly templated
      const pubkey = mtx.inputs[0].witness.get(1);
      assert.ok(isKeyEncoding(pubkey));
      assert.ok(secp256k1.publicKeyVerify(pubkey));

      // transaction not valid
      assert.equal(mtx.verify(), false);

      // reset for next test
      nodeCtx.mempool.removeListener('tx', handler);
    });

    it('should throw error with incompatible broadcast and sign options', async () => {
      const fn = async () => await (wallet.createOpen({
        name: name,
        broadcast: true,
        sign: false
      }));

      await assert.rejects(fn, {message: 'Must sign when broadcasting.'});
    });

    it('should fail to create open for account with no monies', async () => {
      const info = await wallet.getAccount(accountTwo);
      assert.equal(info.balance.tx, 0);
      assert.equal(info.balance.coin, 0);

      const fn = async () => (await wallet.createOpen({
        name: name,
        account: accountTwo
      }));

      await assert.rejects(fn, {message: /Not enough funds./});
    });

    it('should mine to the account with no monies', async () => {
      const height = 5;

      const {receiveAddress} = await wallet.getAccount(accountTwo);

      await nodeCtx.mineBlocks(height, receiveAddress);

      const info = await wallet.getAccount(accountTwo);
      assert.equal(info.balance.tx, height);
      assert.equal(info.balance.coin, height);
    });

    it('should create open for specific account', async () => {
      const json = await wallet.createOpen({
        name: name,
        account: accountTwo
      });

      const info = await wallet.getAccount(accountTwo);

      // assert that each of the inputs belongs to the account
      for (const {address} of json.inputs) {
        const keyInfo = await wallet.getKey(address);
        assert.equal(keyInfo.name, info.name);
      }
    });

    it('should open an auction', async () => {
      await wallet.createOpen({
        name: name
      });

      // save chain height for later comparison
      const info = await nclient.getInfo();

      await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);

      // Confirmed OPEN adds name to wallet's namemap
      allNames.push(name);

      const json = await wallet.createBid({
        name: name,
        bid: 1000,
        lockup: 2000
      });

      const bids = json.outputs.filter(output => output.covenant.type === types.BID);
      assert.equal(bids.length, 1);

      const [bid] = bids;
      assert.equal(bid.covenant.items.length, 4);

      const [nameHash, start, rawName, blind] = bid.covenant.items;
      assert.equal(nameHash, rules.hashName(name).toString('hex'));

      // initially opened in the first block mined, so chain.height + 1
      const hex = Buffer.from(start, 'hex').reverse().toString('hex');
      assert.equal(parseInt(hex, 16), info.chain.height + 1);

      assert.equal(rawName, Buffer.from(name, 'ascii').toString('hex'));

      // blind is type string, so 32 * 2
      assert.equal(blind.length, 32 * 2);
    });

    it('should be able to get nonce', async () => {
      const bid = 100;

      const response = await wallet.getNonce(name, {
        address: cbAddress,
        bid: bid
      });

      const address = Address.fromString(cbAddress, network.type);
      const nameHash = rules.hashName(name);

      const primary = nodeCtx.wdb.primary;
      const nonces = await primary.generateNonces(nameHash, address, bid);
      const blinds = nonces.map(nonce => rules.blind(bid, nonce));

      assert.deepStrictEqual(response, {
        address: address.toString(network.type),
        blinds: blinds.map(blind => blind.toString('hex')),
        nonces: nonces.map(nonce => nonce.toString('hex')),
        bid: bid,
        name: name,
        nameHash: nameHash.toString('hex')
      });
    });

    it('should be able to get nonce for bid=0', async () => {
      const bid = 0;

      const response = await wallet.getNonce(name, {
        address: cbAddress,
        bid: bid
      });

      const address = Address.fromString(cbAddress, network.type);
      const nameHash = rules.hashName(name);

      const primary = nodeCtx.wdb.primary;
      const nonces = await primary.generateNonces(nameHash, address, bid);
      const blinds = nonces.map(nonce => rules.blind(bid, nonce));

      assert.deepStrictEqual(response, {
        address: address.toString(network.type),
        blinds: blinds.map(blind => blind.toString('hex')),
        nonces: nonces.map(nonce => nonce.toString('hex')),
        bid: bid,
        name: name,
        nameHash: nameHash.toString('hex')
      });
    });

    it('should get name info', async () => {
      const names = await wallet.getNames();

      assert.strictEqual(allNames.length, names.length);

      assert(names.length > 0);
      const [ns] = names;

      const nameInfo = await wallet.getName(ns.name);

      assert.deepEqual(ns, nameInfo);
    });

    it('should fail to open a bid without a bid value', async () => {
      const fn = async () => (await wallet.createBid({
        name: name
      }));

      await assert.rejects(fn, {message: 'Bid is required.'});
    });

    it('should fail to open a bid without a lockup value', async () => {
      const fn = async () => (await wallet.createBid({
        name: name,
        bid: 1000
      }));

      await assert.rejects(fn, {message: 'Lockup is required.'});
    });

    it('should send bid with 0 value and non-dust lockup', async () => {
      await wallet.createOpen({
        name: name
      });

      await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);

      // Confirmed OPEN adds name to wallet's namemap
      allNames.push(name);

      await wallet.createBid({
        name: name,
        bid: 0,
        lockup: 1000
      });
    });

    it('should fail to send bid with 0 value and 0 lockup', async () => {
      await wallet.createOpen({
        name: name
      });

      await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);

      // Confirmed OPEN adds name to wallet's namemap
      allNames.push(name);

      const fn = async () => await wallet.createBid({
        name: name,
        bid: 0,
        lockup: 0
      });

      await assert.rejects(fn, {message: 'Output is dust.'});
    });

    it('should get all bids (single player)', async () => {
      await wallet.createOpen({
        name: name
      });

      await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);

      // Confirmed OPEN adds name to wallet's namemap
      allNames.push(name);

      const tx1 = await wallet.createBid({
        name: name,
        bid: 1000,
        lockup: 2000
      });

      const tx2 = await wallet.createBid({
        name: name,
        bid: 2000,
        lockup: 3000
      });

      const tx3 = await wallet.createBid({
        name: name,
        bid: 4000,
        lockup: 5000
      });

      await nodeCtx.mineBlocks(1, cbAddress);

      // this method gets all bids for all names
      const bids = await wallet.getBids();

      // this depends on this it block creating
      // the first bids of this test suite
      assert.equal(bids.length, 3);
      assert.ok(bids.every(bid => bid.name === name));
      assert.ok(bids.every(bid => bid.height === nodeCtx.height));

      // tx1
      assert.ok(bids.find(bid =>
        (bid.value === 1000
          && bid.lockup === 2000
          && bid.prevout.hash === tx1.hash)
      ));

      // tx2
      assert.ok(bids.find(bid =>
        (bid.value === 2000
          && bid.lockup === 3000
          && bid.prevout.hash === tx2.hash)
      ));

      // tx3
      assert.ok(bids.find(bid =>
        (bid.value === 4000
          && bid.lockup === 5000
          && bid.prevout.hash === tx3.hash)
      ));
    });

    it('should get all bids (two players)', async () => {
      await wallet.createOpen({
        name: name
      });

      await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);

      // Confirmed OPEN adds name to wallet's namemap
      allNames.push(name);

      const tx1 = await wallet.createBid({
        name: name,
        bid: 1000,
        lockup: 2000
      });

      const tx2 = await wallet2.createBid({
        name: name,
        bid: 2000,
        lockup: 3000
      });

      await nodeCtx.mineBlocks(1, cbAddress);

      {
        await sleep(100);
        // fetch all bids for the name
        const bids = await wallet.getBidsByName(name);
        assert.equal(bids.length, 2);

        assert.ok(bids.every(bid => bid.height === nodeCtx.height));

        // there is no value property on bids
        // from other wallets
        assert.ok(bids.find(bid =>
          (bid.lockup === 2000
            && bid.prevout.hash === tx1.hash)
        ));

        assert.ok(bids.find(bid =>
          (bid.lockup === 3000
            && bid.prevout.hash === tx2.hash)
        ));
      }

      {
        // fetch only own bids for the name
        const bids = await wallet.getBidsByName(name, {own: true});
        assert.equal(bids.length, 1);
        const [bid] = bids;
        assert.equal(bid.prevout.hash, tx1.hash);
        assert.strictEqual(bid.height, nodeCtx.height);
      }
    });

    it('should create a reveal', async () => {
      await wallet.createOpen({
        name: name
      });

      await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);

      // Confirmed OPEN adds name to wallet's namemap
      allNames.push(name);

      await wallet.createBid({
        name: name,
        bid: 1000,
        lockup: 2000
      });

      await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);

      const {info} = await nclient.execute('getnameinfo', [name]);
      assert.equal(info.name, name);
      assert.equal(info.state, 'REVEAL');

      const json = await wallet.createReveal({
        name: name
      });

      const reveals = json.outputs.filter(output => output.covenant.type === types.REVEAL);
      assert.equal(reveals.length, 1);
    });

    it('should create all reveals', async () => {
      await wallet.createOpen({
        name: name
      });

      await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);

      // Confirmed OPEN adds name to wallet's namemap
      allNames.push(name);

      for (let i = 0; i < 3; i++) {
        await wallet.createBid({
          name: name,
          bid: 1000,
          lockup: 2000
        });
      }

      await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);

      const {info} = await nclient.execute('getnameinfo', [name]);
      assert.equal(info.name, name);
      assert.equal(info.state, 'REVEAL');

      const json = await wallet.createReveal();

      const reveals = json.outputs.filter(output => output.covenant.type === types.REVEAL);
      assert.equal(reveals.length, 3);

      ownedNames.push(name);

      await nodeCtx.mineBlocks(1, cbAddress);

      const allReveals = await wallet.getReveals();
      assert.strictEqual(allReveals.length, 3);
      assert.ok(allReveals.every(reveal => reveal.name === name));
      assert.ok(allReveals.every(reveal => reveal.height === nodeCtx.height));

      const revealsByName = await wallet.getRevealsByName(name);
      assert.strictEqual(revealsByName.length, 3);
      assert.ok(revealsByName.every(reveal => reveal.name === name));
      assert.ok(revealsByName.every(reveal => reveal.height === nodeCtx.height));
    });

    it('should get all reveals (single player)', async () => {
      await wallet.createOpen({
        name: name
      });

      const name2 = await nclient.execute('grindname', [5]);

      await wallet.createOpen({
        name: name2
      });

      await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);

      // Confirmed OPEN adds name to wallet's namemap
      allNames.push(name);
      allNames.push(name2);

      await wallet.createBid({
        name: name,
        bid: 1000,
        lockup: 2000
      });

      await wallet.createBid({
        name: name2,
        bid: 2000,
        lockup: 3000
      });

      await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);

      await wallet.createReveal({
        name: name
      });

      await wallet.createReveal({
        name: name2
      });

      await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress);

      // Confirmed REVEAL with highest bid makes wallet the owner
      ownedNames.push(name);
      ownedNames.push(name2);

      {
        const reveals = await wallet.getReveals();
        assert.equal(reveals.length, 5);
      }

      {
        // a single reveal per name
        const reveals = await wallet.getRevealsByName(name);
        const [reveal] = reveals;
        assert.strictEqual(reveal.height + revealPeriod, nodeCtx.height);
        assert.equal(reveals.length, 1);
      }
    });

    // this test creates namestate to use duing the
    // next test, hold on to the name being used.
    const state = {
      name: '',
      bids: [],
      reveals: []
    };

    it('should get own reveals (two players)', async () => {
      state.name = name;

      await wallet.createOpen({
        name: name
      });

      await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);

      // Confirmed OPEN adds name to wallet's namemap
      allNames.push(name);

      const b1 = await wallet.createBid({
        name: name,
        bid: 1000,
        lockup: 2000
      });

      const b2 = await wallet2.createBid({
        name: name,
        bid: 2000,
        lockup: 3000
      });

      state.bids.push(b1);
      state.bids.push(b2);

      await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);

      const r1 = await wallet.createReveal({
        name: name
      });

      const r2 = await wallet2.createReveal({
        name: name
      });

      state.reveals.push(r1);
      state.reveals.push(r2);

      await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress);

      // wallet did not win this auction so name is not pushed to ownedNames[]

      {
        const reveals = await wallet.getRevealsByName(name, {own: true});
        assert.strictEqual(reveals.length, 1);
        const [reveal] = reveals;
        assert.strictEqual(reveal.bidPrevout.hash, state.bids[0].hash);
        assert.strictEqual(reveal.bidPrevout.index, 0);
        assert.strictEqual(reveal.own, true);
        assert.strictEqual(reveal.prevout.hash, r1.hash);
      }

      {
        const reveals = await wallet.getRevealsByName(name);
        assert.strictEqual(reveals.length, 2);

        const r1 = reveals.find(reveal =>
          reveal.prevout.hash === state.reveals[0].hash);
        const r2 = reveals.find(reveal =>
          reveal.prevout.hash === state.reveals[1].hash);

        assert.ok(r1);
        assert.ok(r2);

        assert.strictEqual(r1.bidPrevout.hash, state.bids[0].hash);
        assert.strictEqual(r1.bidPrevout.index, 0);

        assert.strictEqual(r2.bidPrevout.hash, state.bids[1].hash);
        assert.strictEqual(r2.bidPrevout.index, 0);
      }

      const dump = await nodeCtx.wdb.dump();
      const dumpSlice = {};

      Object.keys(dump).filter((key) => {
        const wid1 = '7400000001';
        // txdblayout.t
        if (key.startsWith(wid1 + '74'))
          dumpSlice[key] = dump[key];

        // txdblayout.i
        if (key.startsWith(wid1 + '69'))
          dumpSlice[key] = dump[key];

        // txdblayout.B
        if (key.startsWith(wid1 + '42'))
          dumpSlice[key] = dump[key];
      });
    });

    it('should get auction info', async () => {
      const ns = await wallet.getName(state.name);

      const auction = await wallet.getAuctionByName(ns.name);

      // auction info returns a list of bids
      // and a list of reveals for the name
      assert.ok(Array.isArray(auction.bids));
      assert.ok(Array.isArray(auction.reveals));

      // 2 bids and 2 reveals in the previous test
      assert.equal(auction.bids.length, 2);
      assert.equal(auction.reveals.length, 2);

      // ordering can be nondeterministic
      function matchTxId(namestates, target) {
        assert.ok(namestates.find(ns => ns.prevout.hash === target));
      }

      matchTxId(auction.bids, state.bids[0].hash);
      matchTxId(auction.bids, state.bids[1].hash);
      matchTxId(auction.reveals, state.reveals[0].hash);
      matchTxId(auction.reveals, state.reveals[1].hash);
    });

    it('should create a bid and a reveal (reveal in advance)', async () => {
      const balanceBeforeTest = await wallet.getBalance();
      const lockConfirmedBeforeTest = balanceBeforeTest.lockedConfirmed;
      const lockUnconfirmedBeforeTest = balanceBeforeTest.lockedUnconfirmed;

      await wallet.createOpen({ name: name });

      await nodeCtx.mineBlocks(treeInterval + 2, cbAddress);

      // Confirmed OPEN adds name to wallet's namemap
      allNames.push(name);

      const balanceBeforeBid = await wallet.getBalance();
      assert.equal(balanceBeforeBid.lockedConfirmed - lockConfirmedBeforeTest, 0);
      assert.equal(
        balanceBeforeBid.lockedUnconfirmed - lockUnconfirmedBeforeTest,
        0
      );

      const bidValue = 1000000;
      const lockupValue = 5000000;

      const auctionTXs = await wallet.client.post(
        `/wallet/${wallet.id}/auction`,
        {
          name: name,
          bid: 1000000,
          lockup: 5000000,
          broadcastBid: true
        }
      );

      await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);

      let walletAuction = await wallet.getAuctionByName(name);
      const bidFromWallet = walletAuction.bids.find(
        b => b.prevout.hash === auctionTXs.bid.hash
      );
      assert(bidFromWallet);

      const { info } = await nclient.execute('getnameinfo', [name]);
      assert.equal(info.name, name);
      assert.equal(info.state, 'REVEAL');

      const b5 = await wallet.getBalance();
      assert.equal(b5.lockedConfirmed - lockConfirmedBeforeTest, lockupValue);
      assert.equal(b5.lockedUnconfirmed - lockUnconfirmedBeforeTest, lockupValue);

      await nclient.broadcast(auctionTXs.reveal.hex);
      await nodeCtx.mineBlocks(1, cbAddress);

      // Confirmed REVEAL with highest bid makes wallet the owner
      ownedNames.push(name);

      walletAuction = await wallet.getAuctionByName(name);
      const revealFromWallet = walletAuction.reveals.find(
        b => b.prevout.hash === auctionTXs.reveal.hash
      );
      assert(revealFromWallet);

      const b6 = await wallet.getBalance();
      assert.equal(b6.lockedConfirmed - lockConfirmedBeforeTest, bidValue);
      assert.equal(b6.lockedUnconfirmed - lockUnconfirmedBeforeTest, bidValue);

      await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress);

      const ns = await nclient.execute('getnameinfo', [name]);
      const coin = await wallet.getCoin(ns.info.owner.hash, ns.info.owner.index);
      assert.ok(coin);
    });

    it('should create a redeem', async () => {
      await wallet.createOpen({
        name: name
      });

      await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);

      // Confirmed OPEN adds name to wallet's namemap
      allNames.push(name);

      // wallet2 wins the auction, wallet can submit redeem
      await wallet.createBid({
        name: name,
        bid: 1000,
        lockup: 2000
      });

      await wallet2.createBid({
        name: name,
        bid: 2000,
        lockup: 3000
      });

      await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);

      await wallet.createReveal({
        name: name
      });

      await wallet2.createReveal({
        name: name
      });

      await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress);

      // wallet did not win this auction so name is not pushed to ownedNames[]

      // wallet2 is the winner, therefore cannot redeem
      const fn = async () => (await wallet2.createRedeem({
        name: name
      }));

      await assert.rejects(
        fn,
        {message: `No reveals to redeem for name: ${name}.`}
      );

      const json = await wallet.createRedeem({
        name: name
      });

      const redeem = json.outputs.filter(({covenant}) => covenant.type === types.REDEEM);
      assert.ok(redeem.length > 0);
    });

    it('should create an update', async () => {
      await wallet.createOpen({
        name: name
      });

      await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);

      // Confirmed OPEN adds name to wallet's namemap
      allNames.push(name);

      await wallet.createBid({
        name: name,
        bid: 1000,
        lockup: 2000
      });

      await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);

      await wallet.createReveal({
        name: name
      });

      await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress);

      // Confirmed REVEAL with highest bid makes wallet the owner
      ownedNames.push(name);

      {
        const json = await wallet.createUpdate({
          name: name,
          data: {
            records: [
              {
                type: 'TXT',
                txt: ['foobar']
              }
            ]
          }
        });

        // register directly after reveal
        const registers = json.outputs.filter(({covenant}) => covenant.type === types.REGISTER);
        assert.equal(registers.length, 1);
      }

      // mine a block
      await nodeCtx.mineBlocks(1, cbAddress);

      {
        const json = await wallet.createUpdate({
          name: name,
          data: {
            records: [
              {
                type: 'TXT',
                txt: ['barfoo']
              }
            ]
          }
        });

        // update after register or update
        const updates = json.outputs.filter(({covenant}) => covenant.type === types.UPDATE);
        assert.equal(updates.length, 1);
      }
    });

    it('should get name resource', async () => {
      const names = await wallet.getNames();
      // filter out names that have data
      // this test depends on the previous test
      const [ns] = names.filter(n => n.data.length > 0);
      assert(ns);

      const state = Resource.decode(Buffer.from(ns.data, 'hex'));

      const resource = await wallet.getResource(ns.name);
      assert(resource);
      const res = Resource.fromJSON(resource);

      assert.deepEqual(state, res);
    });

    it('should fail to get name resource for non existent name', async () => {
      const name = await nclient.execute('grindname', [10]);

      const resource = await wallet.getResource(name);
      assert.equal(resource, null);
    });

    it('should create a renewal', async () => {
      await wallet.createOpen({
        name: name
      });

      await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);

      // Confirmed OPEN adds name to wallet's namemap
      allNames.push(name);

      await wallet.createBid({
        name: name,
        bid: 1000,
        lockup: 2000
      });

      await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);

      await wallet.createReveal({
        name: name
      });

      await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress);

      // Confirmed REVEAL with highest bid makes wallet the owner
      ownedNames.push(name);

      await wallet.createUpdate({
        name: name,
        data: {
          records: [
            {
              type: 'TXT',
              txt: ['foobar']
            }
          ]
        }
      });

      // mine up to the earliest point in which a renewal
      // can be submitted, a treeInterval into the future
      await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);

      const json = await wallet.createRenewal({
        name
      });

      const updates = json.outputs.filter(({covenant}) => covenant.type === types.RENEW);
      assert.equal(updates.length, 1);
    });

    it('should create a transfer', async () => {
      await wallet.createOpen({
        name: name
      });

      await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);

      // Confirmed OPEN adds name to wallet's namemap
      allNames.push(name);

      await wallet.createBid({
        name: name,
        bid: 1000,
        lockup: 2000
      });

      await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);

      await wallet.createReveal({
        name: name
      });

      await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress);

      // Confirmed REVEAL with highest bid makes wallet the owner
      ownedNames.push(name);

      await wallet.createUpdate({
        name: name,
        data: {
          records: [
            {
              type: 'TXT',
              txt: ['foobar']
            }
          ]
        }
      });

      await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);

      const {receiveAddress} = await wallet.getAccount(accountTwo);

      const json = await wallet.createTransfer({
        name,
        address: receiveAddress
      });

      const xfer = json.outputs.filter(({covenant}) => covenant.type === types.TRANSFER);
      assert.equal(xfer.length, 1);
    });

    it('should create a finalize', async () => {
      await wallet.createOpen({
        name: name
      });

      await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);

      // Confirmed OPEN adds name to wallet's namemap
      allNames.push(name);

      await wallet.createBid({
        name: name,
        bid: 1000,
        lockup: 2000
      });

      await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);

      await wallet.createReveal({
        name: name
      });

      await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress);

      // Confirmed REVEAL with highest bid makes wallet the owner
      ownedNames.push(name);

      await wallet.createUpdate({
        name: name,
        data: {
          records: [
            {
              type: 'TXT',
              txt: ['foobar']
            }
          ]
        }
      });

      await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);

      const {receiveAddress} = await wallet2.getAccount('default');

      await wallet.createTransfer({
        name,
        address: receiveAddress
      });

      await nodeCtx.mineBlocks(transferLockup + 1, cbAddress);

      const json = await wallet.createFinalize({
        name
      });

      const final = json.outputs.filter(({covenant}) => covenant.type === types.FINALIZE);
      assert.equal(final.length, 1);

      await nodeCtx.mineBlocks(1, cbAddress);

      // Confirmed FINALIZE means this wallet is not the owner anymore!
      ownedNames.splice(ownedNames.indexOf(name), 1);

      const ns = await nclient.execute('getnameinfo', [name]);
      const coin = await nclient.getCoin(ns.info.owner.hash, ns.info.owner.index);

      assert.equal(coin.address, receiveAddress);
    });

    it('should create a cancel', async () => {
      await wallet.createOpen({
        name: name
      });

      await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);

      // Confirmed OPEN adds name to wallet's namemap
      allNames.push(name);

      await wallet.createBid({
        name: name,
        bid: 1000,
        lockup: 2000
      });

      await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);

      await wallet.createReveal({
        name: name
      });

      await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress);

      // Confirmed REVEAL with highest bid makes wallet the owner
      ownedNames.push(name);

      await wallet.createUpdate({
        name: name,
        data: {
          records: [
            {
              type: 'TXT',
              txt: ['foobar']
            }
          ]
        }
      });

      await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);

      const {receiveAddress} = await wallet.getAccount(accountTwo);

      await wallet.createTransfer({
        name,
        address: receiveAddress
      });

      await nodeCtx.mineBlocks(transferLockup + 1, cbAddress);

      const json = await wallet.createCancel({name});

      const cancel = json.outputs.filter(({covenant}) => covenant.type === types.UPDATE);
      assert.equal(cancel.length, 1);

      await nodeCtx.mineBlocks(1, cbAddress);

      const ns = await nclient.execute('getnameinfo', [name]);
      assert.equal(ns.info.name, name);

      const coin = await wallet.getCoin(ns.info.owner.hash, ns.info.owner.index);
      assert.ok(coin);

      const keyInfo = await wallet.getKey(coin.address);
      assert.ok(keyInfo);
    });

    it('should create a revoke', async () => {
      await wallet.createOpen({
        name: name
      });

      await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);

      // Confirmed OPEN adds name to wallet's namemap
      allNames.push(name);

      await wallet.createBid({
        name: name,
        bid: 1000,
        lockup: 2000
      });

      await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);

      await wallet.createReveal({
        name: name
      });

      await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress);

      // Confirmed REVEAL with highest bid makes wallet the owner
      ownedNames.push(name);

      await wallet.createUpdate({
        name: name,
        data: {
          records: [
            {
              type: 'TXT',
              txt: ['foobar']
            }
          ]
        }
      });

      await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);

      const json = await wallet.createRevoke({name});

      const final = json.outputs.filter(({covenant}) => covenant.type === types.REVOKE);
      assert.equal(final.length, 1);

      await nodeCtx.mineBlocks(1, cbAddress);

      // Confirmed REVOKE means no one owns this name anymore
      ownedNames.splice(ownedNames.indexOf(name), 1);

      const ns = await nclient.execute('getnameinfo', [name]);
      assert.equal(ns.info.name, name);
      assert.equal(ns.info.state, 'REVOKED');
    });

    it('should require passphrase for auction TXs', async () => {
      const passphrase = 'BitDNS!5353';
      await wclient.createWallet('lockedWallet', {passphrase});
      const lockedWallet = await wclient.wallet('lockedWallet');

      // Fast-forward through the default 60-second unlock timeout
      async function lock() {
        const wallet = await nodeCtx.wdb.get('lockedWallet');
        return wallet.lock();
      }
      await lock();

      // Wallet is created and encrypted
      const info = await lockedWallet.getInfo();
      assert(info);
      assert(info.master.encrypted);

      // Fund
      const addr = await lockedWallet.createAddress('default');
      await nodeCtx.mineBlocks(10, addr.address);
      await common.forValue(nodeCtx.wdb, 'height', nodeCtx.chain.height);
      const bal = await lockedWallet.getBalance();
      assert(bal.confirmed > 0);

      // Open
      await assert.rejects(
        lockedWallet.createOpen({name}),
        {message: 'No passphrase.'}
      );

      await lockedWallet.createOpen({name, passphrase});
      await lock();

      await nodeCtx.mineBlocks(treeInterval + 1, cbAddress);

      // Bid
      await assert.rejects(
        lockedWallet.createBid({name, lockup: 1, bid: 1}),
        {message: 'No passphrase.'}
      );

      // Send multiple bids, wallet remains unlocked for 60 seconds (all 3 bids)
      await lockedWallet.createBid(
        {name, lockup: 1000000, bid: 1000000, passphrase}
      );
      await lockedWallet.createBid({name, lockup: 2000000, bid: 2000000});
      await lockedWallet.createBid({name, lockup: 3000000, bid: 3000000});
      await lock();

      await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress);

      // Reveal
      await assert.rejects(
        lockedWallet.createReveal({name}),
        {message: 'No passphrase.'}
      );
      const revealAll = await lockedWallet.createReveal({name, passphrase});
      await lock();

      // All 3 bids are revealed
      const reveals = revealAll.outputs.filter(
        output => output.covenant.type === types.REVEAL
      );
      assert.equal(reveals.length, 3);

      await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress);

      // Redeem all by not passing specific name
      await assert.rejects(
        lockedWallet.createRedeem(),
        {message: 'No passphrase.'}
      );
      const redeemAll = await lockedWallet.createRedeem({passphrase});
      await lock();

      // Only 2 reveals are redeemed (because the third one is the winner)
      const redeems = redeemAll.outputs.filter(
        output => output.covenant.type === types.REDEEM
      );
      assert.equal(redeems.length, 2);

      // Register
      await assert.rejects(
        lockedWallet.createUpdate({name, data: {records: []}}),
        {message: 'No passphrase.'}
      );
      const register = await lockedWallet.createUpdate(
        {name, data: {records: []}, passphrase}
      );
      await lock();

      // Only 1 register, only 1 winner!
      const registers = register.outputs.filter(
        output => output.covenant.type === types.REGISTER
      );
      assert.equal(registers.length, 1);
    });

    it('should get all wallet names', async () => {
      const names = await wallet.getNames();

      assert.equal(allNames.length, names.length);

      for (const {name} of names) {
        assert(allNames.includes(name));
      }
    });

    it('should only get wallet-owned names', async () => {
      const names = await wallet.getNames({ own: true });

      assert.equal(names.length, ownedNames.length);

      for (const {name} of names) {
        assert(ownedNames.includes(name));
      }
    });

    it('should get owned names name info', async () => {
      const ownedNSes = await wallet.getNames({ own: true });
      const ownedNames = new Map(ownedNSes.map(ns => [ns.name, ns]));

      for (const name of allNames) {
        const isOwned = ownedNames.has(name);
        const ns = await wallet.getName(name, { own: true });

        assert.strictEqual(ns != null, isOwned);

        if (isOwned)
          assert.deepEqual(ns, ownedNames.get(name));
      }
    });
  });

  describe('HTTP tx races (Integration)', function() {
    const WNAME1 = 'racetest-1';
    const WNAME2 = 'racetest-2';
    const FUND_VALUE = 1e6;
    const HARD_FEE = 1e4;
    const NAMES = [];
    const PASSPHRASE1 = 'racetest-passphrase-1';
    const PASSPHRASE2 = 'racetest-passphrase-2';

    let rcwallet1, rcwallet2, wclient;
    let w1addr;

    const checkDoubleSpends = (txs) => {
      const spentCoins = new BufferSet();

      for (const tx of txs) {
        for (const input of tx.inputs) {
          const key = input.prevout.toKey();

          if (spentCoins.has(key))
            throw new Error(`Input ${input.prevout.format()} is already spent.`);

          spentCoins.add(key);
        }
      }
    };

    const wMineBlocks = async (n = 1) => {
      const forConnect = common.forEvent(nodeCtx.wdb, 'block connect', n);
      await nodeCtx.mineBlocks(n, w1addr);
      await forConnect;
    };

    const fundNcoins = async (recvWallet, n, value = FUND_VALUE) => {
      assert(typeof n === 'number');
      for (let i = 0; i < n; i++) {
        const addr = (await recvWallet.createAddress('default')).address;

        await wallet.send({
          hardFee: HARD_FEE,
          outputs: [{
            address: addr,
            value: value
          }]
        });
      }

      await wMineBlocks(1);
    };

    before(async () => {
      await beforeAll();

      wclient = nodeCtx.wclient;
      rcwallet1 = wclient.wallet(WNAME1);
      rcwallet2 = wclient.wallet(WNAME2);

      w1addr = (await wallet.createAddress('default')).address;
      const winfo1 = await wclient.createWallet(WNAME1, {
        passphrase: PASSPHRASE1
      });

      const winfo2 = await wclient.createWallet(WNAME2, {
        passphrase: PASSPHRASE2
      });

      assert(winfo1);
      assert(winfo2);

      // Fund primary wallet.
      await wMineBlocks(5);
    });

    after(afterAll);

    beforeEach(async () => {
      await rcwallet1.lock();
      await rcwallet2.lock();
    });

    it('should fund 3 new transactions', async () => {
      const promises = [];

      await fundNcoins(rcwallet1, 3);

      const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3);

      for (let i = 0; i < 3; i++) {
        promises.push(rcwallet1.send({
          passphrase: PASSPHRASE1,
          subtractFee: true,
          hardFee: HARD_FEE,
          outputs: [{
            address: w1addr,
            value: FUND_VALUE
          }]
        }));
      }

      const results = await Promise.all(promises);
      const txs = results.map(details => MTX.fromHex(details.tx));
      checkDoubleSpends(txs);

      await forMemTX;
      await wMineBlocks(1);

      const balance = await rcwallet1.getBalance();

      assert.strictEqual(balance.confirmed, 0);
      assert.strictEqual(balance.unconfirmed, 0);
      assert.strictEqual(balance.coin, 0);
    });

    it('should open 3 name auctions', async () => {
      await fundNcoins(rcwallet1, 3);

      for (let i = 0; i < 3; i++)
        NAMES.push(rules.grindName(10, nodeCtx.chain.tip.height, network));

      const promises = [];

      const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 4);

      for (let i = 0; i < 3; i++) {
        promises.push(rcwallet1.createOpen({
          name: NAMES[i],
          passphrase: PASSPHRASE1,
          hardFee: HARD_FEE
        }));
      }

      const results = await Promise.all(promises);
      const txs = results.map(result => MTX.fromHex(result.hex));
      checkDoubleSpends(txs);

      // spend all money for now.
      // Passphrase not necessary as the wallet is unlocked.
      await rcwallet1.send({
        subtractFee: true,
        outputs: [{
          value: (FUND_VALUE - HARD_FEE) * 3,
          address: w1addr
        }]
      });

      await forMemTX;
      await wMineBlocks(1);

      const balance = await rcwallet1.getBalance();
      // 3 opens (0 value)
      assert.strictEqual(balance.coin, 3);
      assert.strictEqual(balance.confirmed, 0);
    });

    it('should bid 3 times', async () => {
      const promises = [];

      // 2 blocks.
      await fundNcoins(rcwallet1, 3);
      await fundNcoins(rcwallet2, 6);

      // this is 2 blocks ahead, but does not matter for this test.
      await wMineBlocks(network.names.treeInterval + 1);

      const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3 + 3 * 2);

      for (let i = 0; i < 3; i++) {
        // make sure we use ALL coins, no NONE left.
        // winner.
        promises.push(rcwallet1.createBid({
          name: NAMES[i],
          bid: HARD_FEE,
          lockup: HARD_FEE,
          passphrase: PASSPHRASE1,
          hardFee: FUND_VALUE - HARD_FEE
        }));

        // We want redeemer to not have enough funds
        // to redeem the money back and has to use
        // extra funds for it.
        //
        // ALSO We want to have enough redeems to
        // do redeemAll and redeem.
        for (let j = 0; j < 2; j++) {
          promises.push(rcwallet2.createBid({
            name: NAMES[i],
            bid: HARD_FEE - 1,
            lockup: HARD_FEE - 1,
            passphrase: PASSPHRASE2,
            // lose all funds in fees.
            hardFee: FUND_VALUE - HARD_FEE
          }));
        }
      }

      const results = await Promise.all(promises);
      const txs = results.map(result => MTX.fromHex(result.hex));
      checkDoubleSpends(txs);

      await forMemTX;

      await wMineBlocks(1);
      const balance1 = await rcwallet1.getBalance();
      const balance2 = await rcwallet2.getBalance();

      // 3 opens and 3 bids (nothing extra)
      assert.strictEqual(balance1.coin, 6);
      assert.strictEqual(balance1.confirmed, HARD_FEE * 3);

      // 6 bids (nothing extra)
      assert.strictEqual(balance2.coin, 6);
      assert.strictEqual(balance2.confirmed, (HARD_FEE - 1) * 6);
    });

    it('should reveal 3 times and reveal all', async () => {
      // Now we don't have fees to reveal. Fund these fees.
      await fundNcoins(rcwallet1, 3, HARD_FEE);
      await fundNcoins(rcwallet2, 1, HARD_FEE);

      const promises = [];

      await wMineBlocks(network.names.biddingPeriod);

      const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 4);

      for (let i = 0; i < 3; i++) {
        promises.push(rcwallet1.createReveal({
          name: NAMES[i],
          passphrase: PASSPHRASE1,
          hardFee: HARD_FEE
        }));
      }

      // do reveal all
      promises.push(rcwallet2.createReveal({
        passphrase: PASSPHRASE2,
        hardFee: HARD_FEE
      }));

      const results = await Promise.all(promises);
      const txs = results.map(r => MTX.fromHex(r.hex));
      checkDoubleSpends(txs);
      await forMemTX;

      await wMineBlocks(1);

      const balance1 = await rcwallet1.getBalance();

      // 3 opens and 3 reveals
      assert.strictEqual(balance1.coin, 6);
      assert.strictEqual(balance1.confirmed, HARD_FEE * 3);

      const balance2 = await rcwallet2.getBalance();

      // 6 reveals
      assert.strictEqual(balance2.coin, 6);
      assert.strictEqual(balance2.confirmed, (HARD_FEE - 1) * 6);
      await wMineBlocks(network.names.revealPeriod);
    });

    it('should register 3 times', async () => {
      const promises = [];

      // We don't have funds to fund anything.
      // Add 3 coins to pay for the fees and cause
      // double spend.
      await fundNcoins(rcwallet1, 3, HARD_FEE);

      const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3);

      for (let i = 0; i < 3; i++) {
        promises.push(rcwallet1.createUpdate({
          name: NAMES[i],
          passphrase: PASSPHRASE1,
          hardFee: HARD_FEE,
          data: {
            records: [
              {
                type: 'TXT',
                txt: ['foobar']
              }
            ]
          }
        }));
      }

      const results = await Promise.all(promises);
      const txs = results.map(r => MTX.fromHex(r.hex));
      checkDoubleSpends(txs);
      await forMemTX;

      await wMineBlocks(1);
    });

    it('should redeem 3 times and redeem all', async () => {
      const promises = [];

      await fundNcoins(rcwallet2, 3, HARD_FEE);

      const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3);

      for (let i = 0; i < 2; i++) {
        promises.push(rcwallet2.createRedeem({
          name: NAMES[i],
          passphrase: PASSPHRASE2,
          hardFee: HARD_FEE
        }));
      }

      promises.push(rcwallet2.createRedeem({
        hardFee: HARD_FEE
      }));

      const results = await Promise.all(promises);
      const txs = results.map(r => MTX.fromHex(r.hex));
      checkDoubleSpends(txs);
      await forMemTX;
    });

    it('should renew 3 names', async () => {
      const promises = [];

      await wMineBlocks(network.names.treeInterval);
      await fundNcoins(rcwallet1, 3, HARD_FEE);

      const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3);

      for (let i = 0; i < 3; i++) {
        promises.push(rcwallet1.createRenewal({
          name: NAMES[i],
          passphrase: PASSPHRASE1,
          hardFee: HARD_FEE
        }));
      }

      const results = await Promise.all(promises);
      const txs = results.map(r => MTX.fromHex(r.hex));
      checkDoubleSpends(txs);
      await forMemTX;

      await wMineBlocks(1);
    });

    it('should transfer 3 names', async () => {
      const promises = [];

      await fundNcoins(rcwallet1, 3, HARD_FEE);

      const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3);

      const addrs = [
        (await rcwallet2.createAddress('default')).address,
        (await rcwallet2.createAddress('default')).address,
        (await rcwallet2.createAddress('default')).address
      ];

      for (let i = 0; i < 3; i++) {
        promises.push(rcwallet1.createTransfer({
          name: NAMES[i],
          address: addrs[i],
          passphrase: PASSPHRASE1,
          hardFee: HARD_FEE
        }));
      }

      const results = await Promise.all(promises);
      const txs = results.map(r => MTX.fromHex(r.hex));
      checkDoubleSpends(txs);
      await forMemTX;
      await wMineBlocks(1);
    });

    it('should cancel 3 names', async () => {
      const promises = [];

      await fundNcoins(rcwallet1, 3, HARD_FEE);

      const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3);

      for (let i = 0; i < 3; i++) {
        promises.push(rcwallet1.createCancel({
          name: NAMES[i],
          passphrase: PASSPHRASE1,
          hardFee: HARD_FEE
        }));
      }

      const results = await Promise.all(promises);
      const txs = results.map(r => MTX.fromHex(r.hex));
      checkDoubleSpends(txs);
      await forMemTX;
      await wMineBlocks(1);
    });

    it('should finalize 3 names', async () => {
      await fundNcoins(rcwallet1, 6, HARD_FEE);

      let forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3);

      const addrs = [
        (await rcwallet2.createAddress('default')).address,
        (await rcwallet2.createAddress('default')).address,
        (await rcwallet2.createAddress('default')).address
      ];

      for (let i = 0; i < 3; i++) {
        await rcwallet1.createTransfer({
          name: NAMES[i],
          address: addrs[i],
          passphrase: PASSPHRASE1,
          hardFee: HARD_FEE
        });
      }

      await forMemTX;
      await wMineBlocks(network.names.transferLockup);

      // Now we finalize all.
      const promises = [];

      forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3);

      for (let i = 0; i < 3; i++) {
        promises.push(rcwallet1.createFinalize({
          name: NAMES[i],
          passphrase: PASSPHRASE1,
          hardFee: HARD_FEE
        }));
      }

      const results = await Promise.all(promises);
      const txs = results.map(r => MTX.fromHex(r.hex));
      checkDoubleSpends(txs);
      await forMemTX;

      await wMineBlocks(1);
    });

    it('should revoke 3 names', async () => {
      // send them back
      await fundNcoins(rcwallet2, 6, HARD_FEE);

      let forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3);

      const addrs = [
        (await rcwallet1.createAddress('default')).address,
        (await rcwallet1.createAddress('default')).address,
        (await rcwallet1.createAddress('default')).address
      ];

      for (let i = 0; i < 3; i++) {
        await rcwallet2.createTransfer({
          name: NAMES[i],
          address: addrs[i],
          passphrase: PASSPHRASE2,
          hardFee: HARD_FEE
        });
      }

      await forMemTX;
      await wMineBlocks(network.names.transferLockup);

      forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3);
      const promises = [];

      for (let i = 0; i < 3; i++) {
        promises.push(rcwallet2.createRevoke({
          name: NAMES[i],
          passphrase: PASSPHRASE2,
          hardFee: HARD_FEE
        }));
      }

      const results = await Promise.all(promises);
      const txs = results.map(r => MTX.fromHex(r.hex));
      checkDoubleSpends(txs);
      await forMemTX;
    });
  });

  describe('Wallet TX pagination', function() {
    const GENESIS_TIME = 1580745078;

    // account to receive single tx per block.
    const SINGLE_ACCOUNT = 'single';
    const DEFAULT_ACCOUNT = 'default';

    let fundWallet, testWallet, unconfirmedTime;

    async function sendTXs(count, account = DEFAULT_ACCOUNT) {
      const mempoolTXs = forEvent(nodeCtx.mempool, 'tx', count);

      for (let i = 0; i < count; i++) {
        const {address} = await testWallet.createAddress(account);
        await fundWallet.send({ outputs: [{address, value: 1e6}] });
      }

      await mempoolTXs;
    }

    before(async () => {
      await beforeAll();

      await wclient.createWallet('test');
      fundWallet = wclient.wallet('primary');
      testWallet = wclient.wallet('test');

      await testWallet.createAccount(SINGLE_ACCOUNT);

      const fundAddress = (await fundWallet.createAddress('default')).address;

      await generateInitialBlocks({
        nodeCtx,
        sendTXs,
        singleAccount: SINGLE_ACCOUNT,
        coinbase: fundAddress,
        genesisTime: GENESIS_TIME
      });

      unconfirmedTime = Math.floor(Date.now() / 1000);

      // 20 txs unconfirmed
      const all = forEvent(nodeCtx.wdb, 'tx', 20);
      await sendTXs(20);
      await all;
    });

    after(async () => {
      await afterAll();
    });

    describe('confirmed and unconfirmed txs (dsc)', function() {
      it('first page', async () => {
        const history = await testWallet.getHistory({
          limit: 100,
          reverse: true
        });

        assert.strictEqual(history.length, 100);
        assert.strictEqual(history[0].confirmations, 0);
        assert.strictEqual(history[19].confirmations, 0);
        assert.strictEqual(history[20].confirmations, 1);
        assert.strictEqual(history[39].confirmations, 1);
        assert.strictEqual(history[40].confirmations, 2);
        assert.strictEqual(history[99].confirmations, 4);
      });

      it('second page', async () => {
        const one = await testWallet.getHistory({
          limit: 100,
          reverse: true
        });

        assert.strictEqual(one.length, 100);
        assert.strictEqual(one[0].confirmations, 0);
        assert.strictEqual(one[19].confirmations, 0);
        assert.strictEqual(one[20].confirmations, 1);
        assert.strictEqual(one[99].confirmations, 4);

        const after = one[99].hash;

        const two = await testWallet.getHistory({
          after,
          limit: 100,
          reverse: true
        });

        assert.strictEqual(two.length, 100);
        assert.strictEqual(two[0].confirmations, 5);
        assert.strictEqual(two[19].confirmations, 5);
        assert.strictEqual(two[20].confirmations, 6);
        assert.strictEqual(two[99].confirmations, 9);
        assert.notStrictEqual(two[0].hash, one[99].hash);
      });

      it('first page (w/ account)', async () => {
        const history = await testWallet.getHistory({
          account: SINGLE_ACCOUNT,
          limit: 100,
          reverse: true
        });

        // we are sending txs from coinbase.
        assert.strictEqual(history.length, 20);
        assert.strictEqual(history[0].confirmations, 1);
        assert.strictEqual(history[1].confirmations, 2);
        assert.strictEqual(history[19].confirmations, 20);
      });

      it('second page (w/ account)', async () => {
        const one = await testWallet.getHistory({
          account: SINGLE_ACCOUNT,
          limit: 10,
          reverse: true
        });

        assert.strictEqual(one.length, 10);

        const after = one[9].hash;

        const two = await testWallet.getHistory({
          account: SINGLE_ACCOUNT,
          after: after,
          limit: 10,
          reverse: true
        });

        assert.strictEqual(two.length, 10);
        assert.strictEqual(two[0].confirmations, 11);
        assert.strictEqual(two[9].confirmations, 20);
        assert.notStrictEqual(two[0].hash, one[9].hash);
      });

      it('with datetime (MTP in epoch seconds)', async () => {
        const history = await testWallet.getHistory({
          limit: 100,
          time: Math.ceil(Date.now() / 1000),
          reverse: true
        });

        assert.strictEqual(history.length, 100);
        assert(history[0].confirmations < history[99].confirmations);
      });
    });

    describe('confirmed txs (asc)', function() {
      it('first page', async () => {
        const history = await testWallet.getHistory({
          account: SINGLE_ACCOUNT,
          limit: 12,
          reverse: false
        });

        assert.strictEqual(history.length, 12);
        assert.strictEqual(history[0].confirmations, 20);
        assert.strictEqual(history[11].confirmations, 9);
      });

      it('second page', async () => {
        const one = await testWallet.getHistory({
          account: SINGLE_ACCOUNT,
          limit: 12,
          reverse: false
        });

        assert.strictEqual(one.length, 12);
        assert.strictEqual(one[0].confirmations, 20);
        assert.strictEqual(one[11].confirmations, 9);

        const after = one[11].hash;

        const two = await testWallet.getHistory({
          account: SINGLE_ACCOUNT,
          after: after,
          limit: 10,
          reverse: false
        });

        assert.strictEqual(two.length, 8);
        assert.strictEqual(two[0].confirmations, 8);
        assert.strictEqual(two[7].confirmations, 1);
        assert.notStrictEqual(two[0].hash, one[7].hash);
      });

      it('with datetime (MTP in epoch seconds)', async () => {
        const history = await testWallet.getHistory({
          limit: 100,
          time: GENESIS_TIME,
          reverse: false
        });

        assert.strictEqual(history.length, 100);
        assert(history[0].confirmations > history[99].confirmations);
      });
    });

    describe('unconfirmed txs (dsc)', function() {
      it('first page', async () => {
        const history = await testWallet.getPending({
          limit: 50,
          reverse: true
        });

        assert.strictEqual(history.length, 20);
        assert.strictEqual(history[0].confirmations, 0);
        const a = history[0].mtime;
        assert.strictEqual(Number.isInteger(a), true);
        assert.strictEqual(history[19].confirmations, 0);
        const b = history[19].mtime;
        assert.strictEqual(Number.isInteger(b), true);
        assert.strictEqual(a >= b, true);

        const historyAccount = await testWallet.getPending({
          account: DEFAULT_ACCOUNT,
          limit: 50,
          reverse: true
        });

        assert.deepStrictEqual(historyAccount, history);
      });

      it('second page', async () => {
        const one = await testWallet.getPending({
          limit: 5,
          reverse: true
        });

        const oneAccount = await testWallet.getPending({
          account: DEFAULT_ACCOUNT,
          limit: 5,
          reverse: true
        });

        assert.deepStrictEqual(oneAccount, one);

        const after = one[4].hash;

        const two = await testWallet.getPending({
          after: after,
          limit: 40,
          reverse: true
        });

        const twoAccount = await testWallet.getPending({
          after: after,
          account: DEFAULT_ACCOUNT,
          limit: 40,
          reverse: true
        });

        assert.deepStrictEqual(twoAccount, two);

        assert.strictEqual(two.length, 15);
        assert.strictEqual(two[0].confirmations, 0);
        const a = two[0].mtime;
        assert.strictEqual(Number.isInteger(a), true);
        assert.strictEqual(two[14].confirmations, 0);
        const b = two[14].mtime;
        assert.strictEqual(Number.isInteger(b), true);
        assert.strictEqual(a >= b, true);

        assert.notStrictEqual(two[0].hash, one[4].hash);
      });

      it('with datetime (MTP in epoch seconds)', async () => {
        const history = await testWallet.getPending({
          limit: 20,
          time: Math.ceil((Date.now() + 2000) / 1000),
          reverse: true
        });

        assert.strictEqual(history.length, 20);
        assert(history[0].mtime >= history[19].mtime);

        const historyAccount = await testWallet.getPending({
          account: DEFAULT_ACCOUNT,
          limit: 20,
          time: Math.ceil((Date.now() + 2000) / 1000),
          reverse: true
        });

        assert.deepStrictEqual(historyAccount, history);
      });
    });

    describe('unconfirmed txs (asc)', function() {
      it('first page', async () => {
        const history = await testWallet.getPending({
          limit: 50,
          reverse: false
        });

        const historyAccount = await testWallet.getPending({
          account: DEFAULT_ACCOUNT,
          limit: 50,
          reverse: false
        });

        assert.deepStrictEqual(historyAccount, history);

        assert.strictEqual(history.length, 20);
        assert.strictEqual(history[0].confirmations, 0);
        const a = history[0].mtime;
        assert.strictEqual(Number.isInteger(a), true);
        assert.strictEqual(history[19].confirmations, 0);
        const b = history[19].mtime;
        assert.strictEqual(Number.isInteger(b), true);
        assert.strictEqual(a <= b, true);
      });

      it('second page', async () => {
        const one = await testWallet.getPending({
          limit: 5,
          reverse: false
        });

        const oneAccount = await testWallet.getPending({
          account: DEFAULT_ACCOUNT,
          limit: 5,
          reverse: false
        });

        assert.deepStrictEqual(oneAccount, one);

        assert.strictEqual(one.length, 5);
        const after = one[4].hash;

        const two = await testWallet.getPending({
          after: after,
          limit: 15,
          reverse: false
        });

        const twoAccount = await testWallet.getPending({
          after: after,
          account: DEFAULT_ACCOUNT,
          limit: 15,
          reverse: false
        });

        assert.deepStrictEqual(twoAccount, two);

        assert.strictEqual(two.length, 15);
        assert.strictEqual(two[0].confirmations, 0);
        const a = two[0].mtime;
        assert.strictEqual(Number.isInteger(a), true);
        assert.strictEqual(two[14].confirmations, 0);
        const b = two[14].mtime;
        assert.strictEqual(Number.isInteger(b), true);
        assert.strictEqual(a <= b, true);

        assert.notStrictEqual(two[0].hash, one[4].hash);
      });

      it('with datetime (MTP in epoch seconds)', async () => {
        const history = await testWallet.getPending({
          limit: 20,
          time: unconfirmedTime,
          reverse: false
        });

        assert.strictEqual(history.length, 20);
        assert(history[0].mtime <= history[19].mtime);

        const historyAccount = await testWallet.getPending({
          account: DEFAULT_ACCOUNT,
          limit: 20,
          time: unconfirmedTime,
          reverse: false
        });

        assert.deepStrictEqual(historyAccount, history);
      });
    });
  });
});

// create an OPEN output
function openOutput(name, address, network) {
  const nameHash = rules.hashName(name);
  const rawName = Buffer.from(name, 'ascii');

  const output = new Output();
  output.address = Address.fromString(address, network);
  output.value = 0;
  output.covenant.setOpen(nameHash, rawName);

  return output;
}