<commit_before>'use strict';

var _ = require('lodash');
var chai = require('chai');
var sinon = require('sinon');
var should = chai.should();
var levelup = require('levelup');
var memdown = require('memdown');
var async = require('async');
var request = require('supertest');
var Client = require('../../lib/client');
var API = Client.API;
var Bitcore = require('bitcore');
var TestData = require('./clienttestdata');
var WalletUtils = require('../../lib/walletutils');
var ExpressApp = require('../../lib/expressapp');
var Storage = require('../../lib/storage');


var helpers = {};

helpers.getRequest = function(app) {
  return function(args, cb) {
    var req = request(app);
    var r = req[args.method](args.relUrl);

    if (args.headers) {
      _.each(args.headers, function(v, k) {
        r.set(k, v);
      })
    }
    if (!_.isEmpty(args.body)) {
      r.send(args.body);
    };
    r.end(function(err, res) {
      return cb(err, res, res.body);
    });
  };
};

helpers.createAndJoinWallet = function(clients, m, n, cb) {
  clients[0].createWallet('wallet name', 'creator', m, n, 'testnet',
    function(err, secret) {
      if (err) return cb(err);
      if (n == 1) return cb();

      should.exist(secret);
      async.each(_.range(n - 1), function(i, cb) {
        clients[i + 1].joinWallet(secret, 'copayer ' + (i + 1), function(err, result) {
          should.not.exist(err);
          return cb(err);
        });
      }, function(err) {
        if (err) return cb(err);
        return cb(null, {
          m: m,
          n: n,
          secret: secret,
        });
      });
    });
};


var fsmock = {};
var content = {};
fsmock.readFile = function(name, enc, cb) {
  if (!content || _.isEmpty(content[name]))
    return cb('empty');

  return cb(null, content[name]);
};
fsmock.writeFile = function(name, data, cb) {
  content[name] = data;
  return cb();
};
fsmock.reset = function() {
  content = {};
};

fsmock._get = function(name) {
  return content[name];
};


var utxos = [];
var blockExplorerMock = {};




blockExplorerMock.getUnspentUtxos = function(dummy, cb) {
  var ret = _.map(utxos || [], function(x) {
    x.toObject = function() {
      return this;
    };
    return x;
  });
  return cb(null, ret);
};

blockExplorerMock.setUtxo = function(address, amount, m) {
  utxos.push({
    txid: Bitcore.crypto.Hash.sha256(new Buffer(Math.random() * 100000)).toString('hex'),
    vout: Math.floor((Math.random() * 10) + 1),
    amount: amount,
    address: address.address,
    scriptPubKey: Bitcore.Script.buildMultisigOut(address.publicKeys, m).toScriptHashOut(),
  });
};

blockExplorerMock.reset = function() {
  utxos = [];
};

describe('client API ', function() {
  var clients, app;

  beforeEach(function() {
    clients = [];
    var db = levelup(memdown, {
      valueEncoding: 'json'
    });
    var storage = new Storage({
      db: db
    });
    app = ExpressApp.start({
      CopayServer: {
        storage: storage,
        blockExplorer: blockExplorerMock,
      }
    });
    // Generates 5 clients
    _.each(_.range(5), function(i) {
      var storage = new Client.FileStorage({
        filename: 'client' + i,
        fs: fsmock,
      });
      var client = new Client({
        storage: storage,
      });

      client.request = helpers.getRequest(app);
      clients.push(client);
    });
    fsmock.reset();
    blockExplorerMock.reset();
  });

  describe('Wallet Creation', function() {
    it('should check balance in a 1-1 ', function(done) {
      helpers.createAndJoinWallet(clients, 1, 1, function(err) {
        should.not.exist(err);
        clients[0].getBalance(function(err, x) {
          should.not.exist(err);
          done();
        })
      });
    });
    it('should be able to complete wallets in copayer that joined later', function(done) {
      helpers.createAndJoinWallet(clients, 2, 3, function(err) {
        should.not.exist(err);
        clients[0].getBalance(function(err, x) {
          should.not.exist(err);
          clients[1].getBalance(function(err, x) {
            should.not.exist(err);
            clients[2].getBalance(function(err, x) {
              should.not.exist(err);
              done();
            })
          })
        })
      });
    });

    it('should not allow to join a full wallet ', function(done) {
      helpers.createAndJoinWallet(clients, 2, 2, function(err, w) {
        should.not.exist(err);
        should.exist(w.secret);
        clients[4].joinWallet(w.secret, 'copayer', function(err, result) {
          err.should.contain('Request error');
          done();
        });
      });
    });
    it('should fail with a unknown secret', function(done) {
      var oldSecret = '<KEY>';
      clients[0].joinWallet(oldSecret, 'copayer', function(err, result) {
        err.should.contain('Request error');
        done();
      });
    });
    it('should reject wallets with bad signatures', function(done) {
      helpers.createAndJoinWallet(clients, 2, 3, function(err) {
        should.not.exist(err);

        // Get right response
        var data = clients[0]._load(function(err, data) {
          var url = '/v1/wallets/';
          clients[0]._doGetRequest(url, data, function(err, x) {

            // Tamper data
            x.wallet.copayers[0].xPubKey = x.wallet.copayers[1].xPubKey;

            // Tamper response
            clients[1]._doGetRequest = sinon.stub().yields(null, x);

            clients[1].getBalance(function(err, x) {
              err.should.contain('verified');
              done();
            });
          });
        });
      });
    });

    it('should reject wallets with missing signatures', function(done) {
      helpers.createAndJoinWallet(clients, 2, 3, function(err) {
        should.not.exist(err);

        // Get right response
        var data = clients[0]._load(function(err, data) {
          var url = '/v1/wallets/';
          clients[0]._doGetRequest(url, data, function(err, x) {

            // Tamper data
            delete x.wallet.copayers[1].xPubKey;

            // Tamper response
            clients[1]._doGetRequest = sinon.stub().yields(null, x);

            clients[1].getBalance(function(err, x) {
              err.should.contain('verified');
              done();
            });
          });
        });
      });
    });


    it('should reject wallets missing caller"s pubkey', function(done) {
      helpers.createAndJoinWallet(clients, 2, 3, function(err) {
        should.not.exist(err);

        // Get right response
        var data = clients[0]._load(function(err, data) {
          var url = '/v1/wallets/';
          clients[0]._doGetRequest(url, data, function(err, x) {

            // Tamper data. Replace caller's pubkey
            x.wallet.copayers[1].xPubKey = (new Bitcore.HDPrivateKey()).publicKey;
            // Add a correct signature
            x.wallet.copayers[1].xPubKeySignature = WalletUtils.signMessage(
              x.wallet.copayers[1].xPubKey, data.walletPrivKey),

            // Tamper response
            clients[1]._doGetRequest = sinon.stub().yields(null, x);

            clients[1].getBalance(function(err, x) {
              err.should.contain('verified');
              done();
            });
          });
        });
      });
    });
  });


  describe('Address Creation', function() {
    it('should be able to create address in all copayers in a 2-3 wallet', function(done) {
      this.timeout(5000);
      helpers.createAndJoinWallet(clients, 2, 3, function(err) {
        should.not.exist(err);
        clients[0].createAddress(function(err, x0) {
          should.not.exist(err);
          should.exist(x0.address);
          clients[1].createAddress(function(err, x1) {
            should.not.exist(err);
            should.exist(x1.address);
            clients[2].createAddress(function(err, x2) {
              should.not.exist(err);
              should.exist(x2.address);
              done();
            });
          });
        });
      });
    });
    it('should see balance on address created by others', function(done) {
      helpers.createAndJoinWallet(clients, 2, 2, function(err, w) {
        should.not.exist(err);
        clients[0].createAddress(function(err, x0) {
          should.not.exist(err);
          should.exist(x0.address);

          blockExplorerMock.setUtxo(x0, 10, w.m);
          clients[0].getBalance(function(err, bal0) {
            should.not.exist(err);
            bal0.totalAmount.should.equal(10 * 1e8);
            bal0.lockedAmount.should.equal(0);
            clients[1].getBalance(function(err, bal1) {
              bal1.totalAmount.should.equal(10 * 1e8);
              bal1.lockedAmount.should.equal(0);
              done();
            });
          });
        });
      });
    });
  });


  describe('Wallet Backups and Mobility', function() {

    it('round trip #import #export', function(done) {
      helpers.createAndJoinWallet(clients, 2, 2, function(err, w) {
        should.not.exist(err);
        clients[0].export(function(err, str) {
          should.not.exist(err);
          var original = JSON.parse(fsmock._get('client0'));
          clients[2].import(str, function(err, wallet) {
            should.not.exist(err);
            var clone = JSON.parse(fsmock._get('client2'));
            delete original.walletPrivKey; // no need to persist it.
            clone.should.deep.equal(original);
            done();
          });

        });
      });
    });
    it('should recreate a wallet, create addresses and receive money', function(done) {
      var backup = '["<KEY>",2,["<KEY>"],"<KEY>]';
      clients[0].import(backup, function(err, wallet) {
        should.not.exist(err);
        clients[0].reCreateWallet('pepe', function(err, wallet) {
          should.not.exist(err);

          clients[0].createAddress(function(err, x0) {
            should.not.exist(err);
            should.exist(x0.address);
            blockExplorerMock.setUtxo(x0, 10, 2);
            clients[0].getBalance(function(err, bal0) {
              should.not.exist(err);
              bal0.totalAmount.should.equal(10 * 1e8);
              bal0.lockedAmount.should.equal(0);
              done();
            });
          });
        });
      });
    });
  });


  describe.only('Send Transactions', function() {
    it('Send and broadcast in 1-1 wallet', function(done) {
      helpers.createAndJoinWallet(clients, 1, 1, function(err, w) {
        clients[0].createAddress(function(err, x0) {
          should.not.exist(err);
          should.exist(x0.address);
          blockExplorerMock.setUtxo(x0, 10, 1);
          var opts = {
            amount: 10000,
            toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
            message: 'hola 1-1',
          };
          clients[0].sendTxProposal(opts, function(err, x) {
            should.not.exist(err);
            x.requiredRejections.should.equal(1);
            x.requiredSignatures.should.equal(1);
            x.status.should.equal('pending');
            x.changeAddress.path.should.equal('m/2147483647/1/0');
            clients[0].signTxProposal(x.id, function(err, res) {
              should.not.exist(err, err.message);
              done();
            });
          });
        });
      });
    });
    it('Send and broadcast in 2-3 wallet', function(done) {
      helpers.createAndJoinWallet(clients, 2, 3, function(err, w) {
        clients[0].createAddress(function(err, x0) {
          should.not.exist(err);
          should.exist(x0.address);
          blockExplorerMock.setUtxo(x0, 10, 1);
          var opts = {
            amount: 10000,
            toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
            message: 'hola 1-1',
          };
          clients[0].sendTxProposal(opts, function(err, x) {
            should.not.exist(err);
            x.status.should.equal('pending');
            x.requiredRejections.should.equal(2);
            x.requiredSignatures.should.equal(2);
            clients[0].signTxProposal(x.id, function(err, res) {
              should.not.exist(err, err.message);
              done();
            });
          });
        });
      });
    });
 
  });


  /*
  describe('TODO', function(x) {
    it('should detect fake addresses ', function(done) {
      var response = {
        createdOn: 1424105995,
        address: '2N3fA6wDtnebzywPkGuNK9KkFaEzgbPRRTq',
        path: 'm/2147483647/0/8',
        publicKeys: ['03f6a5fe8db51bfbaf26ece22a3e3bc242891a47d3048fc70bc0e8c03a071ad76f']
      };
      var request = sinon.mock().yields(null, {
        statusCode: 200
      }, response);
      client.request = request;
      client.createAddress(function(err, x) {<commit_msg>sendTx sign reject tests<commit_after>'use strict';

var _ = require('lodash');
var chai = require('chai');
var sinon = require('sinon');
var should = chai.should();
var levelup = require('levelup');
var memdown = require('memdown');
var async = require('async');
var request = require('supertest');
var Client = require('../../lib/client');
var API = Client.API;
var Bitcore = require('bitcore');
var TestData = require('./clienttestdata');
var WalletUtils = require('../../lib/walletutils');
var ExpressApp = require('../../lib/expressapp');
var Storage = require('../../lib/storage');


var helpers = {};

helpers.getRequest = function(app) {
  return function(args, cb) {
    var req = request(app);
    var r = req[args.method](args.relUrl);

    if (args.headers) {
      _.each(args.headers, function(v, k) {
        r.set(k, v);
      })
    }
    if (!_.isEmpty(args.body)) {
      r.send(args.body);
    };
    r.end(function(err, res) {
      return cb(err, res, res.body);
    });
  };
};

helpers.createAndJoinWallet = function(clients, m, n, cb) {
  clients[0].createWallet('wallet name', 'creator', m, n, 'testnet',
    function(err, secret) {
      if (err) return cb(err);
      if (n == 1) return cb();

      should.exist(secret);
      async.each(_.range(n - 1), function(i, cb) {
        clients[i + 1].joinWallet(secret, 'copayer ' + (i + 1), function(err, result) {
          should.not.exist(err);
          return cb(err);
        });
      }, function(err) {
        if (err) return cb(err);
        return cb(null, {
          m: m,
          n: n,
          secret: secret,
        });
      });
    });
};


var fsmock = {};
var content = {};
fsmock.readFile = function(name, enc, cb) {
  if (!content || _.isEmpty(content[name]))
    return cb('empty');

  return cb(null, content[name]);
};
fsmock.writeFile = function(name, data, cb) {
  content[name] = data;
  return cb();
};
fsmock.reset = function() {
  content = {};
};

fsmock._get = function(name) {
  return content[name];
};


var blockExplorerMock = {};
blockExplorerMock.utxos = [];




blockExplorerMock.getUnspentUtxos = function(dummy, cb) {
  var ret = _.map(blockExplorerMock.utxos || [], function(x) {
    x.toObject = function() {
      return this;
    };
    return x;
  });
  return cb(null, ret);
};

blockExplorerMock.setUtxo = function(address, amount, m) {
  blockExplorerMock.utxos.push({
    txid: Bitcore.crypto.Hash.sha256(new Buffer(Math.random() * 100000)).toString('hex'),
    vout: Math.floor((Math.random() * 10) + 1),
    amount: amount,
    address: address.address,
    scriptPubKey: Bitcore.Script.buildMultisigOut(address.publicKeys, m).toScriptHashOut().toString(),
  });
};


blockExplorerMock.broadcast = function(raw, cb) {
  blockExplorerMock.lastBroadcasted = raw;
  return cb(null, (new Bitcore.Transaction(raw)).id);
};

blockExplorerMock.reset = function() {
  blockExplorerMock.utxos = [];
};

describe('client API ', function() {
  var clients, app;

  beforeEach(function() {
    clients = [];
    var db = levelup(memdown, {
      valueEncoding: 'json'
    });
    var storage = new Storage({
      db: db
    });
    app = ExpressApp.start({
      CopayServer: {
        storage: storage,
        blockExplorer: blockExplorerMock,
      }
    });
    // Generates 5 clients
    _.each(_.range(5), function(i) {
      var storage = new Client.FileStorage({
        filename: 'client' + i,
        fs: fsmock,
      });
      var client = new Client({
        storage: storage,
      });

      client.request = helpers.getRequest(app);
      clients.push(client);
    });
    fsmock.reset();
    blockExplorerMock.reset();
  });

  describe('Wallet Creation', function() {
    it('should check balance in a 1-1 ', function(done) {
      helpers.createAndJoinWallet(clients, 1, 1, function(err) {
        should.not.exist(err);
        clients[0].getBalance(function(err, x) {
          should.not.exist(err);
          done();
        })
      });
    });
    it('should be able to complete wallets in copayer that joined later', function(done) {
      helpers.createAndJoinWallet(clients, 2, 3, function(err) {
        should.not.exist(err);
        clients[0].getBalance(function(err, x) {
          should.not.exist(err);
          clients[1].getBalance(function(err, x) {
            should.not.exist(err);
            clients[2].getBalance(function(err, x) {
              should.not.exist(err);
              done();
            })
          })
        })
      });
    });

    it('should not allow to join a full wallet ', function(done) {
      helpers.createAndJoinWallet(clients, 2, 2, function(err, w) {
        should.not.exist(err);
        should.exist(w.secret);
        clients[4].joinWallet(w.secret, 'copayer', function(err, result) {
          err.should.contain('Request error');
          done();
        });
      });
    });
    it('should fail with a unknown secret', function(done) {
      var oldSecret = '<KEY>';
      clients[0].joinWallet(oldSecret, 'copayer', function(err, result) {
        err.should.contain('Request error');
        done();
      });
    });
    it('should reject wallets with bad signatures', function(done) {
      helpers.createAndJoinWallet(clients, 2, 3, function(err) {
        should.not.exist(err);

        // Get right response
        var data = clients[0]._load(function(err, data) {
          var url = '/v1/wallets/';
          clients[0]._doGetRequest(url, data, function(err, x) {

            // Tamper data
            x.wallet.copayers[0].xPubKey = x.wallet.copayers[1].xPubKey;

            // Tamper response
            clients[1]._doGetRequest = sinon.stub().yields(null, x);

            clients[1].getBalance(function(err, x) {
              err.should.contain('verified');
              done();
            });
          });
        });
      });
    });

    it('should reject wallets with missing signatures', function(done) {
      helpers.createAndJoinWallet(clients, 2, 3, function(err) {
        should.not.exist(err);

        // Get right response
        var data = clients[0]._load(function(err, data) {
          var url = '/v1/wallets/';
          clients[0]._doGetRequest(url, data, function(err, x) {

            // Tamper data
            delete x.wallet.copayers[1].xPubKey;

            // Tamper response
            clients[1]._doGetRequest = sinon.stub().yields(null, x);

            clients[1].getBalance(function(err, x) {
              err.should.contain('verified');
              done();
            });
          });
        });
      });
    });


    it('should reject wallets missing caller"s pubkey', function(done) {
      helpers.createAndJoinWallet(clients, 2, 3, function(err) {
        should.not.exist(err);

        // Get right response
        var data = clients[0]._load(function(err, data) {
          var url = '/v1/wallets/';
          clients[0]._doGetRequest(url, data, function(err, x) {

            // Tamper data. Replace caller's pubkey
            x.wallet.copayers[1].xPubKey = (new Bitcore.HDPrivateKey()).publicKey;
            // Add a correct signature
            x.wallet.copayers[1].xPubKeySignature = WalletUtils.signMessage(
              x.wallet.copayers[1].xPubKey, data.walletPrivKey),

            // Tamper response
            clients[1]._doGetRequest = sinon.stub().yields(null, x);

            clients[1].getBalance(function(err, x) {
              err.should.contain('verified');
              done();
            });
          });
        });
      });
    });
  });


  describe('Address Creation', function() {
    it('should be able to create address in all copayers in a 2-3 wallet', function(done) {
      this.timeout(5000);
      helpers.createAndJoinWallet(clients, 2, 3, function(err) {
        should.not.exist(err);
        clients[0].createAddress(function(err, x0) {
          should.not.exist(err);
          should.exist(x0.address);
          clients[1].createAddress(function(err, x1) {
            should.not.exist(err);
            should.exist(x1.address);
            clients[2].createAddress(function(err, x2) {
              should.not.exist(err);
              should.exist(x2.address);
              done();
            });
          });
        });
      });
    });
    it('should see balance on address created by others', function(done) {
      helpers.createAndJoinWallet(clients, 2, 2, function(err, w) {
        should.not.exist(err);
        clients[0].createAddress(function(err, x0) {
          should.not.exist(err);
          should.exist(x0.address);

          blockExplorerMock.setUtxo(x0, 10, w.m);
          clients[0].getBalance(function(err, bal0) {
            should.not.exist(err);
            bal0.totalAmount.should.equal(10 * 1e8);
            bal0.lockedAmount.should.equal(0);
            clients[1].getBalance(function(err, bal1) {
              bal1.totalAmount.should.equal(10 * 1e8);
              bal1.lockedAmount.should.equal(0);
              done();
            });
          });
        });
      });
    });
  });


  describe('Wallet Backups and Mobility', function() {

    it('round trip #import #export', function(done) {
      helpers.createAndJoinWallet(clients, 2, 2, function(err, w) {
        should.not.exist(err);
        clients[0].export(function(err, str) {
          should.not.exist(err);
          var original = JSON.parse(fsmock._get('client0'));
          clients[2].import(str, function(err, wallet) {
            should.not.exist(err);
            var clone = JSON.parse(fsmock._get('client2'));
            delete original.walletPrivKey; // no need to persist it.
            clone.should.deep.equal(original);
            done();
          });

        });
      });
    });
    it('should recreate a wallet, create addresses and receive money', function(done) {
      var backup = '["<KEY>",2,["<KEY>"],"sd0kzXmlXBgTGHrKaBW4aA=="]';
      clients[0].import(backup, function(err, wallet) {
        should.not.exist(err);
        clients[0].reCreateWallet('pepe', function(err, wallet) {
          should.not.exist(err);

          clients[0].createAddress(function(err, x0) {
            should.not.exist(err);
            should.exist(x0.address);
            blockExplorerMock.setUtxo(x0, 10, 2);
            clients[0].getBalance(function(err, bal0) {
              should.not.exist(err);
              bal0.totalAmount.should.equal(10 * 1e8);
              bal0.lockedAmount.should.equal(0);
              done();
            });
          });
        });
      });
    });
  });


  describe('Send Transactions', function() {
    it('Send and broadcast in 1-1 wallet', function(done) {
      helpers.createAndJoinWallet(clients, 1, 1, function(err, w) {
        clients[0].createAddress(function(err, x0) {
          should.not.exist(err);
          should.exist(x0.address);
          blockExplorerMock.setUtxo(x0, 1, 1);
          var opts = {
            amount: '0.1btc',
            toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
            message: 'hola 1-1',
          };
          clients[0].sendTxProposal(opts, function(err, x) {
            should.not.exist(err);
            x.requiredRejections.should.equal(1);
            x.requiredSignatures.should.equal(1);
            x.status.should.equal('pending');
            x.changeAddress.path.should.equal('m/2147483647/1/0');
            clients[0].signTxProposal(x, function(err, tx) {
              should.not.exist(err);
              tx.status.should.equal('broadcasted');
              tx.txid.should.equal((new Bitcore.Transaction(blockExplorerMock.lastBroadcasted)).id);
              done();
            });
          });
        });
      });
    });
    it('Send and broadcast in 2-3 wallet', function(done) {
      helpers.createAndJoinWallet(clients, 2, 3, function(err, w) {
        clients[0].createAddress(function(err, x0) {
          should.not.exist(err);
          should.exist(x0.address);
          blockExplorerMock.setUtxo(x0, 10, 1);
          var opts = {
            amount: 10000,
            toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
            message: 'hola 1-1',
          };
          clients[0].sendTxProposal(opts, function(err, x) {
            should.not.exist(err);
            x.status.should.equal('pending');
            x.requiredRejections.should.equal(2);
            x.requiredSignatures.should.equal(2);
            clients[0].signTxProposal(x, function(err, tx) {
              should.not.exist(err, err);
              tx.status.should.equal('pending');
              clients[1].signTxProposal(x, function(err, tx) {
                should.not.exist(err);
                tx.status.should.equal('broadcasted');
                tx.txid.should.equal((new Bitcore.Transaction(blockExplorerMock.lastBroadcasted)).id);
                done();
              });
            });
          });
        });
      });
    });

    it('Send, reject, 2 signs and broadcast in 2-3 wallet', function(done) {
      helpers.createAndJoinWallet(clients, 2, 3, function(err, w) {
        clients[0].createAddress(function(err, x0) {
          should.not.exist(err);
          should.exist(x0.address);
          blockExplorerMock.setUtxo(x0, 10, 1);
          var opts = {
            amount: 10000,
            toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
            message: 'hola 1-1',
          };
          clients[0].sendTxProposal(opts, function(err, x) {
            should.not.exist(err);
            x.status.should.equal('pending');
            x.requiredRejections.should.equal(2);
            x.requiredSignatures.should.equal(2);
            clients[0].rejectTxProposal(x, 'no me gusto', function(err, tx) {
              should.not.exist(err, err);
              tx.status.should.equal('pending');
              clients[1].signTxProposal(x, function(err, tx) {
                should.not.exist(err);
                clients[2].signTxProposal(x, function(err, tx) {
                  should.not.exist(err);
                  tx.status.should.equal('broadcasted');
                  tx.txid.should.equal((new Bitcore.Transaction(blockExplorerMock.lastBroadcasted)).id);
                  done();
                });
              });
            });
          });
        });
      });
    });

    it('Send, reject in 3-4 wallet', function(done) {
      helpers.createAndJoinWallet(clients, 3, 4, function(err, w) {
        clients[0].createAddress(function(err, x0) {
          should.not.exist(err);
          should.exist(x0.address);
          blockExplorerMock.setUtxo(x0, 10, 1);
          var opts = {
            amount: 10000,
            toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
            message: 'hola 1-1',
          };
          clients[0].sendTxProposal(opts, function(err, x) {
            should.not.exist(err);
            x.status.should.equal('pending');
            x.requiredRejections.should.equal(2);
            x.requiredSignatures.should.equal(3);

            clients[0].rejectTxProposal(x, 'no me gusto', function(err, tx) {
              should.not.exist(err, err);
              tx.status.should.equal('pending');
              clients[1].signTxProposal(x, function(err, tx) {
                should.not.exist(err);
                tx.status.should.equal('pending');
                clients[2].rejectTxProposal(x, 'tampoco me gusto', function(err, tx) {
                  should.not.exist(err);
                  tx.status.should.equal('rejected');
                  done();
                });
              });
            });
          });
        });
      });
    });


  });


  /*
  describe('TODO', function(x) {
    it('should detect fake addresses ', function(done) {
      var response = {
        createdOn: 1424105995,
        address: '2N3fA6wDtnebzywPkGuNK9KkFaEzgbPRRTq',
        path: 'm/2147483647/0/8',
        publicKeys: ['03f6a5fe8db51bfbaf26ece22a3e3bc242891a47d3048fc70bc0e8c03a071ad76f']
      };
      var request = sinon.mock().yields(null, {
        statusCode: 200
      }, response);
      client.request = request;
      client.createAddress(function(err, x) {
{'fraction_non_alphanumeric': 0.15256117002841893, 'fraction_numerical': 0.020170513620295278, 'mean_word_length': 2.0956978864928657, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 10, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3602269', 'n_tokens_mistral': 9354, 'n_tokens_neox': 9092, 'n_words': 1579}