async wallet cwallet.
This commit is contained in:
parent
420d72d647
commit
14088e5b0c
@ -52,7 +52,8 @@ function TXDB(db, options) {
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
this.db = db;
|
||||
this.wdb = db;
|
||||
this.db = db.db;
|
||||
this.options = options;
|
||||
this.network = bcoin.network.get(options.network);
|
||||
this.busy = false;
|
||||
@ -568,21 +569,21 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
self.emit('tx', tx, map);
|
||||
self.wdb.sync(tx, map, function(err) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
if (updated) {
|
||||
if (tx.ts !== 0) {
|
||||
self.emit('confirmed', tx, map, function() {
|
||||
self.emit('updated', tx, map);
|
||||
return callback(null, true);
|
||||
});
|
||||
return;
|
||||
self.emit('tx', tx, map);
|
||||
|
||||
if (updated) {
|
||||
if (tx.ts !== 0)
|
||||
self.emit('confirmed', tx, map);
|
||||
|
||||
self.emit('updated', tx, map);
|
||||
}
|
||||
|
||||
self.emit('updated', tx, map);
|
||||
}
|
||||
|
||||
return callback(null, true);
|
||||
return callback(null, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -764,7 +765,11 @@ TXDB.prototype._confirm = function _confirm(tx, map, callback, force) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
self.emit('confirmed', tx, map, function() {
|
||||
self.wdb.sync(tx, map, function(err) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
self.emit('confirmed');
|
||||
self.emit('tx', tx, map);
|
||||
|
||||
return callback(null, true);
|
||||
|
||||
@ -63,10 +63,11 @@ function Wallet(options) {
|
||||
if (!options.master)
|
||||
options.master = bcoin.hd.fromMnemonic(null, this.network);
|
||||
|
||||
if (!(options.master instanceof bcoin.hd))
|
||||
if (!(options.master instanceof bcoin.hd) && !(options.master instanceof MasterKey))
|
||||
options.master = bcoin.hd.fromAny(options.master, this.network);
|
||||
|
||||
options.master = MasterKey.fromKey(options.master);
|
||||
if (!(options.master instanceof MasterKey))
|
||||
options.master = MasterKey.fromKey(options.master);
|
||||
|
||||
this.id = options.id || null;
|
||||
this.master = options.master || null;
|
||||
@ -510,9 +511,6 @@ Wallet.prototype.deriveAddress = function deriveAddress(change, index) {
|
||||
path = change;
|
||||
|
||||
if (path) {
|
||||
// Map address to path
|
||||
if (path.indexOf('/') === -1)
|
||||
return;
|
||||
data = this.parsePath(path);
|
||||
} else {
|
||||
data = {
|
||||
@ -1257,7 +1255,7 @@ Wallet.prototype.sign = function sign(tx, options, callback) {
|
||||
return callback(err);
|
||||
|
||||
try {
|
||||
key = self.master.decrypt(options.passphrase);
|
||||
var master = self.master.decrypt(options.passphrase);
|
||||
} catch (e) {
|
||||
return callback(null, 0);
|
||||
}
|
||||
@ -1265,12 +1263,17 @@ Wallet.prototype.sign = function sign(tx, options, callback) {
|
||||
for (i = 0; i < addresses.length; i++) {
|
||||
address = addresses[i];
|
||||
|
||||
if (self.derivation === 'bip44')
|
||||
key = master;
|
||||
if (self.derivation === 'bip44') {
|
||||
key = key.deriveAccount44(self.accountIndex);
|
||||
else if (self.derivation === 'bip45')
|
||||
assert.equal(key.xpubkey, self.accountKey.xpubkey);
|
||||
} else if (self.derivation === 'bip45')
|
||||
key = key.derivePurpose45();
|
||||
else
|
||||
assert(false);
|
||||
|
||||
key = key.derive(address.path);
|
||||
assert(utils.equal(key.getPublicKey(), address.key));
|
||||
|
||||
total += address.sign(tx, key, options.index, options.type);
|
||||
}
|
||||
@ -1706,3 +1709,537 @@ MasterKey.fromJSON = function fromJSON(json) {
|
||||
*/
|
||||
|
||||
module.exports = Wallet;
|
||||
|
||||
/*!
|
||||
* wallet.js - wallet object for bcoin
|
||||
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
|
||||
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
|
||||
* https://github.com/indutny/bcoin
|
||||
*/
|
||||
|
||||
var bcoin = require('./env');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var utils = require('./utils');
|
||||
var assert = utils.assert;
|
||||
var constants = bcoin.protocol.constants;
|
||||
var BufferWriter = require('./writer');
|
||||
|
||||
/**
|
||||
* HD BIP-44/45 wallet
|
||||
* @exports Wallet
|
||||
* @constructor
|
||||
* @param {Object} options
|
||||
* @param {WalletDB} options.db
|
||||
* present, no coins will be available.
|
||||
* @param {(HDPrivateKey|HDPublicKey)?} options.master - Master HD key. If not
|
||||
* present, it will be generated.
|
||||
* @param {Boolean?} options.witness - Whether to use witness programs.
|
||||
* @param {Number?} options.accountIndex - The BIP44 account index (default=0).
|
||||
* @param {Number?} options.receiveDepth - The index of the _next_ receiving
|
||||
* address.
|
||||
* @param {Number?} options.changeDepth - The index of the _next_ change
|
||||
* address.
|
||||
* @param {Boolean?} options.copayBIP45 - Use copay-style BIP45 if bip45
|
||||
* derivation is used.
|
||||
* @param {Number?} options.lookahead - Amount of lookahead addresses
|
||||
* (default=5).
|
||||
* @param {String?} options.type - Type of wallet (pubkeyhash, multisig)
|
||||
* (default=pubkeyhash).
|
||||
* @param {String?} options.derivation - Derivation type (bip44, bip45)
|
||||
* (default=bip44).
|
||||
* @param {Boolean?} options.compressed - Whether to use compressed
|
||||
* public keys (default=true).
|
||||
* @param {Number?} options.m - `m` value for multisig.
|
||||
* @param {Number?} options.n - `n` value for multisig.
|
||||
* @param {String?} options.id - Wallet ID (used for storage)
|
||||
* (default=account key "address").
|
||||
*/
|
||||
|
||||
function CWallet(id, db) {
|
||||
var i, key;
|
||||
|
||||
if (!(this instanceof CWallet))
|
||||
return new CWallet(id, db);
|
||||
|
||||
EventEmitter.call(this);
|
||||
|
||||
this.network = db.network;
|
||||
this.db = db || bcoin.walletdb({ name: 'wtmp', db: 'memory' });
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
utils.inherits(CWallet, EventEmitter);
|
||||
|
||||
/**
|
||||
* Open the wallet, wait for the database to load.
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
CWallet.prototype.open = function open(callback) {
|
||||
var self = this;
|
||||
|
||||
if (this.loaded)
|
||||
return utils.nextTick(callback);
|
||||
|
||||
if (this._loading)
|
||||
return this.once('open', callback);
|
||||
|
||||
this.db.register(this.id, this);
|
||||
callback();
|
||||
};
|
||||
|
||||
/**
|
||||
* Close the wallet, wait for the database to close.
|
||||
* @method
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
CWallet.prototype.close =
|
||||
CWallet.prototype.destroy = function destroy(callback) {
|
||||
callback = utils.ensure(callback);
|
||||
|
||||
if (!this.db)
|
||||
return utils.nextTick(callback);
|
||||
|
||||
this.db.unregister(this.id, this);
|
||||
this.db = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a public account/purpose key to the wallet for multisig.
|
||||
* @param {HDPublicKey|Base58String} key - Account (bip44) or
|
||||
* Purpose (bip45) key (can be in base58 form).
|
||||
* @throws Error on non-hdkey/non-accountkey.
|
||||
*/
|
||||
|
||||
CWallet.prototype.addKey = function addKey(key, callback) {
|
||||
this.db.addKey(this.id, key, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a public account/purpose key to the wallet for multisig.
|
||||
* @param {HDPublicKey|Base58String} key - Account (bip44) or Purpose
|
||||
* (bip45) key (can be in base58 form).
|
||||
* @throws Error on non-hdkey/non-accountkey.
|
||||
*/
|
||||
|
||||
CWallet.prototype.removeKey = function removeKey(key, callback) {
|
||||
this.db.removeKey(this.id, key, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the wallet ID which is either the passed in `id`
|
||||
* option, or the account/purpose key converted to an
|
||||
* address with a prefix of `0x03be04` (`WLT`).
|
||||
* @returns {Base58String}
|
||||
*/
|
||||
|
||||
CWallet.prototype.getID = function getID() {
|
||||
return this.id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new receiving address (increments receiveDepth).
|
||||
* @returns {KeyRing}
|
||||
*/
|
||||
|
||||
CWallet.prototype.createReceive = function createReceive(callback) {
|
||||
return this.db.createAddress(this.id, false, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new change address (increments receiveDepth).
|
||||
* @returns {KeyRing}
|
||||
*/
|
||||
|
||||
CWallet.prototype.createChange = function createChange(callback) {
|
||||
return this.db.createAddress(this.id, true, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new address (increments depth).
|
||||
* @param {Boolean} change
|
||||
* @returns {KeyRing}
|
||||
*/
|
||||
|
||||
CWallet.prototype.createAddress = function createAddress(change, callback) {
|
||||
return this.db.createAddress(this.id, change, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Test whether the wallet posesses an address.
|
||||
* @param {Base58Address} address
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
CWallet.prototype.hasAddress = function hasAddress(address, callback) {
|
||||
this.db.hasAddress(this.id, address, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fill a transaction with inputs, estimate
|
||||
* transaction size, calculate fee, and add a change output.
|
||||
* @see MTX#selectCoins
|
||||
* @see MTX#fill
|
||||
* @param {MTX} tx - _Must_ be a mutable transaction.
|
||||
* @param {Object?} options
|
||||
* @param {String?} options.selection - Coin selection priority. Can
|
||||
* be `age`, `random`, or `all`. (default=age).
|
||||
* @param {Boolean} options.round - Whether to round to the nearest
|
||||
* kilobyte for fee calculation.
|
||||
* See {@link TX#getMinFee} vs. {@link TX#getRoundFee}.
|
||||
* @param {Rate} options.rate - Rate used for fee calculation.
|
||||
* @param {Boolean} options.confirmed - Select only confirmed coins.
|
||||
* @param {Boolean} options.free - Do not apply a fee if the
|
||||
* transaction priority is high enough to be considered free.
|
||||
* @param {Amount?} options.fee - Use a hard fee rather than calculating one.
|
||||
* @param {Number|Boolean} options.subtractFee - Whether to subtract the
|
||||
* fee from existing outputs rather than adding more inputs.
|
||||
*/
|
||||
|
||||
CWallet.prototype.fill = function fill(tx, options, callback) {
|
||||
this.db.fill(this.id, tx, options, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fill transaction with coins (accesses db).
|
||||
* @param {TX} tx
|
||||
* @param {Function} callback - Returns [Error, {@link TX}].
|
||||
*/
|
||||
|
||||
CWallet.prototype.fillCoins = function fillCoins(tx, callback) {
|
||||
this.db.fillHistory(this.id, tx, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a coin from the wallet (accesses db).
|
||||
* @param {Hash} hash
|
||||
* @param {Number} index
|
||||
* @param {Function} callback - Returns [Error, {@link Coin}].
|
||||
*/
|
||||
|
||||
CWallet.prototype.getCoin = function getCoin(hash, index, callback) {
|
||||
this.db.getCoin(hash, index, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a transaction from the wallet (accesses db).
|
||||
* @param {Hash} hash
|
||||
* @param {Function} callback - Returns [Error, {@link TX}].
|
||||
*/
|
||||
|
||||
CWallet.prototype.getTX = function getTX(hash, callback) {
|
||||
this.db.getTX(hash, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Build a transaction, fill it with outputs and inputs,
|
||||
* sort the members according to BIP69, set locktime,
|
||||
* and sign it (accesses db).
|
||||
* @param {Object} options - See {@link CWallet#fill options}.
|
||||
* @param {Object[]} outputs - See {@link Script.createOutputScript}.
|
||||
* @param {Function} callback - Returns [Error, {@link MTX}].
|
||||
*/
|
||||
|
||||
CWallet.prototype.createTX = function createTX(options, outputs, callback) {
|
||||
this.db.createTX(this.id, options, outputs, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get path by address.
|
||||
* @param {Base58Address} address - Base58 address.
|
||||
*/
|
||||
|
||||
CWallet.prototype.getPath = function getPath(address, callback) {
|
||||
if (!address || typeof address !== 'string')
|
||||
return callback();
|
||||
this.db.getPath(this.id, address, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a redeem script or witness script by hash.
|
||||
* @param {Hash} hash - Can be a ripemd160 or a sha256.
|
||||
* @returns {Script}
|
||||
*/
|
||||
|
||||
CWallet.prototype.getRedeem = function getRedeem(hash, callback) {
|
||||
var self = this;
|
||||
var address;
|
||||
|
||||
if (typeof hash === 'string')
|
||||
hash = new Buffer(hash, 'hex');
|
||||
|
||||
this.getPath(hash.toString('hex'), function(err, path) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
if (!path)
|
||||
return callback();
|
||||
|
||||
address = self.deriveAddress(path);
|
||||
|
||||
if (address.program && hash.length === 20) {
|
||||
if (utils.equal(hash, address.programHash))
|
||||
return callback(null, address.program);
|
||||
}
|
||||
|
||||
return callback(null, address.script);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Zap stale TXs from wallet (accesses db).
|
||||
* @param {Number} now - Current time (unix time).
|
||||
* @param {Number} age - Age threshold (unix time, default=72 hours).
|
||||
* @param {Function} callback - Returns [Error].
|
||||
*/
|
||||
|
||||
CWallet.prototype.zap = function zap(now, age, callback) {
|
||||
return this.db.zapWallet(this.id, now, age, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Build input scripts templates for a transaction (does not
|
||||
* sign, only creates signature slots). Only builds scripts
|
||||
* for inputs that are redeemable by this wallet.
|
||||
* @param {MTX} tx
|
||||
* @param {Number?} index - Index of input. If not present,
|
||||
* it will attempt to sign all redeemable inputs.
|
||||
* @returns {Number} Total number of scripts built.
|
||||
*/
|
||||
|
||||
CWallet.prototype.scriptInputs = function scriptInputs(tx, callback) {
|
||||
this.db.scriptInputs(this.id, tx, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Build input scripts and sign inputs for a transaction. Only attempts
|
||||
* to build/sign inputs that are redeemable by this wallet.
|
||||
* @param {MTX} tx
|
||||
* @param {Number?} index - Index of input. If not present,
|
||||
* it will attempt to build and sign all redeemable inputs.
|
||||
* @param {SighashType?} type
|
||||
* @returns {Number} Total number of inputs scripts built and signed.
|
||||
*/
|
||||
|
||||
CWallet.prototype.sign = function sign(tx, options, callback) {
|
||||
this.db.sign(this.id, tx, options, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a transaction to the wallets TX history (accesses db).
|
||||
* @param {TX} tx
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
CWallet.prototype.addTX = function addTX(tx, callback) {
|
||||
return this.db.addTX(tx, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all transactions in transaction history (accesses db).
|
||||
* @param {Function} callback - Returns [Error, {@link TX}[]].
|
||||
*/
|
||||
|
||||
CWallet.prototype.getHistory = function getHistory(callback) {
|
||||
return this.db.getHistory(this.id, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all available coins (accesses db).
|
||||
* @param {Function} callback - Returns [Error, {@link Coin}[]].
|
||||
*/
|
||||
|
||||
CWallet.prototype.getCoins = function getCoins(callback) {
|
||||
return this.db.getCoins(this.id, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all pending/unconfirmed transactions (accesses db).
|
||||
* @param {Function} callback - Returns [Error, {@link TX}[]].
|
||||
*/
|
||||
|
||||
CWallet.prototype.getUnconfirmed = function getUnconfirmed(callback) {
|
||||
return this.db.getUnconfirmed(this.id, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get wallet balance (accesses db).
|
||||
* @param {Function} callback - Returns [Error, {@link Balance}].
|
||||
*/
|
||||
|
||||
CWallet.prototype.getBalance = function getBalance(callback) {
|
||||
return this.db.getBalance(this.id, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get last timestamp and height this wallet was active
|
||||
* at (accesses db). Useful for resetting the chain
|
||||
* to a certain height when in SPV mode.
|
||||
* @param {Function} callback - Returns [Error, Number(ts), Number(height)].
|
||||
*/
|
||||
|
||||
CWallet.prototype.getLastTime = function getLastTime(callback) {
|
||||
return this.db.getLastTime(this.id, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the last N transactions (accesses db).
|
||||
* @param {Number} limit
|
||||
* @param {Function} callback - Returns [Error, {@link TX}[]].
|
||||
*/
|
||||
|
||||
CWallet.prototype.getLast = function getLast(limit, callback) {
|
||||
return this.db.getLast(this.id, limit, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a range of transactions between two timestamps (accesses db).
|
||||
* @param {Object} options
|
||||
* @param {Number} options.start
|
||||
* @param {Number} options.end
|
||||
* @param {Function} callback - Returns [Error, {@link TX}[]].
|
||||
*/
|
||||
|
||||
CWallet.prototype.getTimeRange = function getTimeRange(options, callback) {
|
||||
return this.db.getTimeRange(this.id, options, callback);
|
||||
};
|
||||
|
||||
CWallet.prototype.getReceiveAddress = function getReceiveAddress(callback) {
|
||||
return this.db.getReceiveAddress(this.id, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert the wallet to a more inspection-friendly object.
|
||||
* @returns {Object}
|
||||
*/
|
||||
|
||||
CWallet.prototype.inspect = function inspect() {
|
||||
return {
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
network: this.network.type,
|
||||
m: this.m,
|
||||
n: this.n,
|
||||
keyAddress: this.initialized
|
||||
? this.keyAddress
|
||||
: null,
|
||||
scriptAddress: this.initialized
|
||||
? this.scriptAddress
|
||||
: null,
|
||||
programAddress: this.initialized
|
||||
? this.programAddress
|
||||
: null,
|
||||
witness: this.witness,
|
||||
derivation: this.derivation,
|
||||
copayBIP45: this.copayBIP45,
|
||||
accountIndex: this.accountIndex,
|
||||
receiveDepth: this.receiveDepth,
|
||||
changeDepth: this.changeDepth,
|
||||
master: this.master.toJSON(),
|
||||
accountKey: this.accountKey.xpubkey,
|
||||
keys: this.keys.map(function(key) {
|
||||
return key.xpubkey;
|
||||
})
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert the wallet to an object suitable for
|
||||
* serialization. Will automatically encrypt the
|
||||
* master key based on the `passphrase` option.
|
||||
* @returns {Object}
|
||||
*/
|
||||
|
||||
CWallet.prototype.toJSON = function toJSON() {
|
||||
return {
|
||||
v: 3,
|
||||
name: 'wallet',
|
||||
network: this.network.type,
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
m: this.m,
|
||||
n: this.n,
|
||||
initialized: this.initialized,
|
||||
saved: this.saved,
|
||||
witness: this.witness,
|
||||
derivation: this.derivation,
|
||||
copayBIP45: this.copayBIP45,
|
||||
accountIndex: this.accountIndex,
|
||||
receiveDepth: this.receiveDepth,
|
||||
changeDepth: this.changeDepth,
|
||||
master: this.master.toJSON(),
|
||||
accountKey: this.accountKey.xpubkey,
|
||||
keys: this.keys.map(function(key) {
|
||||
return key.xpubkey;
|
||||
})
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a deserialized JSON wallet object.
|
||||
* @returns {Object} A "naked" wallet (a
|
||||
* plain javascript object which is suitable
|
||||
* for passing to the Wallet constructor).
|
||||
* @param {Object} json
|
||||
* @param {String?} passphrase
|
||||
* @returns {Object}
|
||||
* @throws Error on bad decrypt
|
||||
*/
|
||||
|
||||
CWallet.parseJSON = function parseJSON(json) {
|
||||
assert.equal(json.v, 3);
|
||||
assert.equal(json.name, 'wallet');
|
||||
|
||||
return {
|
||||
network: json.network,
|
||||
id: json.id,
|
||||
type: json.type,
|
||||
m: json.m,
|
||||
n: json.n,
|
||||
initialized: json.initialized,
|
||||
saved: json.saved,
|
||||
witness: json.witness,
|
||||
derivation: json.derivation,
|
||||
copayBIP45: json.copayBIP45,
|
||||
accountIndex: json.accountIndex,
|
||||
receiveDepth: json.receiveDepth,
|
||||
changeDepth: json.changeDepth,
|
||||
master: MasterKey.fromJSON(json.master),
|
||||
accountKey: bcoin.hd.fromBase58(json.accountKey),
|
||||
keys: json.keys.map(function(key) {
|
||||
return bcoin.hd.fromBase58(key);
|
||||
})
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate a Wallet from a
|
||||
* jsonified wallet object.
|
||||
* @param {Object} json - The jsonified wallet object.
|
||||
* @returns {Wallet}
|
||||
*/
|
||||
|
||||
CWallet.fromJSON = function fromJSON(json) {
|
||||
return new CWallet(CWallet.parseJSON(json));
|
||||
};
|
||||
|
||||
/**
|
||||
* Test an object to see if it is a CWallet.
|
||||
* @param {Object} obj
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
CWallet.isCWallet = function isCWallet(obj) {
|
||||
return obj
|
||||
&& typeof obj.receiveDepth === 'number'
|
||||
&& obj.deriveAddress === 'function';
|
||||
};
|
||||
|
||||
/*
|
||||
* Expose
|
||||
*/
|
||||
|
||||
module.exports = Wallet;
|
||||
bcoin.cwallet = CWallet;
|
||||
module.exports.CWallet = CWallet;
|
||||
|
||||
@ -108,7 +108,7 @@ WalletDB.prototype._init = function _init() {
|
||||
writeBufferSize: 4 << 20
|
||||
});
|
||||
|
||||
this.tx = new bcoin.txdb(this.db, {
|
||||
this.tx = new bcoin.txdb(this, {
|
||||
network: this.network,
|
||||
indexExtra: true,
|
||||
indexAddress: true,
|
||||
@ -141,19 +141,11 @@ WalletDB.prototype._init = function _init() {
|
||||
});
|
||||
});
|
||||
|
||||
this.tx.on('confirmed', function(tx, map, callback) {
|
||||
this.tx.on('confirmed', function(tx, map) {
|
||||
self.emit('confirmed', tx, map);
|
||||
map.all.forEach(function(id) {
|
||||
self.fire(id, 'confirmed', tx);
|
||||
});
|
||||
utils.forEachSerial(map.output, function(id, next) {
|
||||
self.syncOutputDepth(id, tx, next);
|
||||
}, function(err) {
|
||||
if (err)
|
||||
self.emit('error', err);
|
||||
if (callback)
|
||||
callback();
|
||||
});
|
||||
});
|
||||
|
||||
this.tx.on('unconfirmed', function(tx, map) {
|
||||
@ -197,6 +189,13 @@ WalletDB.prototype._init = function _init() {
|
||||
});
|
||||
};
|
||||
|
||||
WalletDB.prototype.sync = function sync(tx, map, callback) {
|
||||
var self = this;
|
||||
utils.forEachSerial(map.output, function(id, next) {
|
||||
self.syncOutputDepth(id, tx, next);
|
||||
}, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Open the walletdb, wait for the database to load.
|
||||
* @param {Function} callback
|
||||
@ -221,50 +220,6 @@ WalletDB.prototype.destroy = function destroy(callback) {
|
||||
this.db.close(callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sync address depths based on a transaction's outputs.
|
||||
* This is used for deriving new addresses when
|
||||
* a confirmed transaction is seen.
|
||||
* @param {WalletID} id
|
||||
* @param {TX} tx
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
WalletDB.prototype.syncOutputDepth = function syncOutputDepth(id, tx, callback) {
|
||||
var self = this;
|
||||
|
||||
callback = utils.ensure(callback);
|
||||
|
||||
this.get(id, function(err, wallet) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
if (!wallet)
|
||||
return callback(new Error('No wallet.'));
|
||||
|
||||
wallet.syncOutputDepth(tx, function(err) {
|
||||
if (err) {
|
||||
wallet.destroy();
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
wallet.destroy();
|
||||
|
||||
if (!self.providers[id])
|
||||
return callback();
|
||||
|
||||
self.providers[id].forEach(function(provider) {
|
||||
provider.receiveDepth = wallet.receiveDepth;
|
||||
provider.changeDepth = wallet.changeDepth;
|
||||
provider.receiveAddress = wallet.receiveAddress;
|
||||
provider.changeAddress = wallet.changeAddress;
|
||||
});
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Derive an address.
|
||||
* @param {WalletID} id
|
||||
@ -272,89 +227,87 @@ WalletDB.prototype.syncOutputDepth = function syncOutputDepth(id, tx, callback)
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
WalletDB.prototype.createAddress = function createAddress(id, change, callback) {
|
||||
WalletDB.prototype.rpc = function rpc(id, callback, method) {
|
||||
var self = this;
|
||||
var address;
|
||||
|
||||
callback = utils.ensure(callback);
|
||||
|
||||
this.get(id, function(err, wallet) {
|
||||
this.get(id, function(err, _, wallet) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
if (!wallet)
|
||||
return callback(new Error('No wallet.'));
|
||||
|
||||
wallet.createAddress(change, function(err) {
|
||||
if (err) {
|
||||
wallet.destroy();
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
wallet.destroy();
|
||||
|
||||
if (!self.providers[id])
|
||||
return callback();
|
||||
|
||||
self.providers[id].forEach(function(provider) {
|
||||
provider.receiveDepth = wallet.receiveDepth;
|
||||
provider.changeDepth = wallet.changeDepth;
|
||||
provider.receiveAddress = wallet.receiveAddress;
|
||||
provider.changeAddress = wallet.changeAddress;
|
||||
});
|
||||
|
||||
callback();
|
||||
});
|
||||
method(wallet);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a public account/purpose key to the wallet for multisig.
|
||||
* @param {WalletID} id
|
||||
* @param {HDPublicKey|Base58String} key - Account (bip44) or
|
||||
* Purpose (bip45) key (can be in base58 form).
|
||||
* @param {Function} callback
|
||||
*/
|
||||
WalletDB.prototype.syncOutputDepth = function syncOutputDepth(id, tx, callback) {
|
||||
this.rpc(id, callback, function(wallet) {
|
||||
wallet.syncOutputDepth(tx, callback);
|
||||
});
|
||||
};
|
||||
|
||||
WalletDB.prototype.modifyKey = function modifyKey(id, key, remove, callback) {
|
||||
var self = this;
|
||||
WalletDB.prototype.createAddress = function createAddress(id, change, callback) {
|
||||
this.rpc(id, callback, function(wallet) {
|
||||
wallet.createAddress(change, callback);
|
||||
});
|
||||
};
|
||||
|
||||
callback = utils.ensure(callback);
|
||||
WalletDB.prototype.getReceiveAddress = function getReceiveAddress(id, callback) {
|
||||
this.rpc(id, callback, function(wallet) {
|
||||
callback(null, wallet.receiveAddress);
|
||||
});
|
||||
};
|
||||
|
||||
this.get(id, function(err, json) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
WalletDB.prototype.getChangeAddress = function getChangeAddress(id, callback) {
|
||||
this.rpc(id, callback, function(wallet) {
|
||||
callback(null, wallet.changeAddress);
|
||||
});
|
||||
};
|
||||
|
||||
if (!wallet)
|
||||
return callback(new Error('No wallet.'));
|
||||
WalletDB.prototype.fill = function fill(id, tx, options, callback) {
|
||||
this.rpc(id, callback, function(wallet) {
|
||||
wallet.fill(tx, options, callback);
|
||||
});
|
||||
};
|
||||
|
||||
function done(err) {
|
||||
if (err) {
|
||||
wallet.destroy();
|
||||
return callback(err);
|
||||
}
|
||||
WalletDB.prototype.scriptInputs = function scriptInputs(id, tx, callback) {
|
||||
this.rpc(id, callback, function(wallet) {
|
||||
wallet.scriptInputs(tx, callback);
|
||||
});
|
||||
};
|
||||
|
||||
wallet.destroy();
|
||||
WalletDB.prototype.sign = function sign(id, tx, options, callback) {
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
if (!self.providers[id])
|
||||
return callback();
|
||||
if (typeof options === 'string' || Buffer.isBuffer(options))
|
||||
options = { passphrase: options };
|
||||
|
||||
self.providers[id].forEach(function(provider) {
|
||||
provider.keys = wallet.keys.slice();
|
||||
provider.initialized = wallet.initialized;
|
||||
});
|
||||
this.rpc(id, callback, function(wallet) {
|
||||
wallet.sign(tx, options, callback);
|
||||
});
|
||||
};
|
||||
|
||||
callback();
|
||||
}
|
||||
WalletDB.prototype.createTX = function createTX(id, options, outputs, callback) {
|
||||
this.rpc(id, callback, function(wallet) {
|
||||
wallet.createTX(options, outputs, callback);
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
if (!remove)
|
||||
wallet.addKey(key, done);
|
||||
else
|
||||
wallet.removeKey(key, done);
|
||||
} catch (e) {
|
||||
return done(e);
|
||||
}
|
||||
WalletDB.prototype.addKey = function addKey(id, key, callback) {
|
||||
this.rpc(id, callback, function(wallet) {
|
||||
wallet.addKey(key, callback);
|
||||
});
|
||||
};
|
||||
|
||||
WalletDB.prototype.removeKey = function removeKey(id, key, callback) {
|
||||
this.rpc(id, callback, function(wallet) {
|
||||
wallet.removeKey(key, callback);
|
||||
});
|
||||
};
|
||||
|
||||
@ -454,7 +407,7 @@ WalletDB.prototype.get = function get(id, callback) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
return callback(null, wallet);
|
||||
return callback(null, new bcoin.cwallet(wallet.id, self), wallet);
|
||||
});
|
||||
});
|
||||
};
|
||||
@ -510,6 +463,7 @@ WalletDB.prototype.create = function create(options, callback) {
|
||||
return callback(
|
||||
new Error('`' + options.id + '` already exists.'),
|
||||
null,
|
||||
null,
|
||||
json);
|
||||
}
|
||||
|
||||
@ -524,7 +478,7 @@ WalletDB.prototype.create = function create(options, callback) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
return callback(null, wallet);
|
||||
return callback(null, new bcoin.cwallet(wallet.id, self), wallet);
|
||||
});
|
||||
}
|
||||
|
||||
@ -543,12 +497,12 @@ WalletDB.prototype.create = function create(options, callback) {
|
||||
|
||||
WalletDB.prototype.ensure = function ensure(options, callback) {
|
||||
var self = this;
|
||||
return this.create(options, function(err, wallet, json) {
|
||||
return this.create(options, function(err, cwallet, wallet, json) {
|
||||
if (err && !json)
|
||||
return callback(err);
|
||||
|
||||
if (wallet)
|
||||
return callback(null, wallet);
|
||||
if (cwallet)
|
||||
return callback(null, cwallet);
|
||||
|
||||
assert(json);
|
||||
|
||||
@ -564,7 +518,7 @@ WalletDB.prototype.ensure = function ensure(options, callback) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
return callback(null, wallet);
|
||||
return callback(null, new bcoin.cwallet(wallet.id, self), wallet);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -82,37 +82,40 @@ describe('Wallet', function() {
|
||||
if (witness)
|
||||
flags |= bcoin.protocol.constants.flags.VERIFY_WITNESS;
|
||||
|
||||
wdb.create({ witness: witness }, function(err, w) {
|
||||
wdb.create({ witness: witness }, function(err, w, w2) {
|
||||
assert.ifError(err);
|
||||
|
||||
if (witness)
|
||||
assert(bcoin.address.parseBase58(w.getAddress()).type === 'witnesspubkeyhash');
|
||||
else
|
||||
assert(bcoin.address.parseBase58(w.getAddress()).type === 'pubkeyhash');
|
||||
w.getReceiveAddress(function(err, a) {
|
||||
if (witness)
|
||||
assert(bcoin.address.parseBase58(a.getAddress()).type === 'witnesspubkeyhash');
|
||||
else
|
||||
assert(bcoin.address.parseBase58(a.getAddress()).type === 'pubkeyhash');
|
||||
|
||||
// Input transcation
|
||||
var src = bcoin.mtx({
|
||||
outputs: [{
|
||||
value: 5460 * 2,
|
||||
address: bullshitNesting
|
||||
? w.getProgramAddress()
|
||||
: w.getAddress()
|
||||
}, {
|
||||
value: 5460 * 2,
|
||||
address: bcoin.address.fromData(new Buffer([])).toBase58()
|
||||
}]
|
||||
});
|
||||
// Input transcation
|
||||
var src = bcoin.mtx({
|
||||
outputs: [{
|
||||
value: 5460 * 2,
|
||||
address: bullshitNesting
|
||||
? a.getProgramAddress()
|
||||
: a.getAddress()
|
||||
}, {
|
||||
value: 5460 * 2,
|
||||
address: bcoin.address.fromData(new Buffer([])).toBase58()
|
||||
}]
|
||||
});
|
||||
|
||||
src.addInput(dummyInput);
|
||||
src.addInput(dummyInput);
|
||||
|
||||
var tx = bcoin.mtx()
|
||||
.addInput(src, 0)
|
||||
.addOutput(w.getAddress(), 5460);
|
||||
var tx = bcoin.mtx()
|
||||
.addInput(src, 0)
|
||||
.addOutput(a.getAddress(), 5460);
|
||||
|
||||
w.sign(tx, function(err) {
|
||||
assert.ifError(err);
|
||||
assert(tx.verify(null, true, flags));
|
||||
cb();
|
||||
w.sign(tx, function(err) {
|
||||
assert.ifError(err);
|
||||
utils.print(tx);
|
||||
assert(tx.verify(null, true, flags));
|
||||
cb();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -171,21 +174,25 @@ describe('Wallet', function() {
|
||||
var dw, di;
|
||||
it('should have TX pool and be serializable', function(cb) {
|
||||
wdb.create({}, function(err, w) {
|
||||
assert.ifError(err);
|
||||
w.getReceiveAddress(function(err, aw) {
|
||||
assert.ifError(err);
|
||||
wdb.create({}, function(err, f) {
|
||||
assert.ifError(err);
|
||||
f.getReceiveAddress(function(err, af) {
|
||||
assert.ifError(err);
|
||||
dw = w;
|
||||
|
||||
// Coinbase
|
||||
var t1 = bcoin.mtx().addOutput(w, 50000).addOutput(w, 1000);
|
||||
var t1 = bcoin.mtx().addOutput(aw, 50000).addOutput(aw, 1000);
|
||||
t1.addInput(dummyInput);
|
||||
// balance: 51000
|
||||
// w.sign(t1);
|
||||
w.sign(t1, function(err) {
|
||||
assert.ifError(err);
|
||||
var t2 = bcoin.mtx().addInput(t1, 0) // 50000
|
||||
.addOutput(w, 24000)
|
||||
.addOutput(w, 24000);
|
||||
.addOutput(aw, 24000)
|
||||
.addOutput(aw, 24000);
|
||||
di = t2.inputs[0];
|
||||
// balance: 49000
|
||||
// w.sign(t2);
|
||||
@ -193,27 +200,27 @@ describe('Wallet', function() {
|
||||
assert.ifError(err);
|
||||
var t3 = bcoin.mtx().addInput(t1, 1) // 1000
|
||||
.addInput(t2, 0) // 24000
|
||||
.addOutput(w, 23000);
|
||||
.addOutput(aw, 23000);
|
||||
// balance: 47000
|
||||
// w.sign(t3);
|
||||
w.sign(t3, function(err) {
|
||||
assert.ifError(err);
|
||||
var t4 = bcoin.mtx().addInput(t2, 1) // 24000
|
||||
.addInput(t3, 0) // 23000
|
||||
.addOutput(w, 11000)
|
||||
.addOutput(w, 11000);
|
||||
.addOutput(aw, 11000)
|
||||
.addOutput(aw, 11000);
|
||||
// balance: 22000
|
||||
// w.sign(t4);
|
||||
w.sign(t4, function(err) {
|
||||
assert.ifError(err);
|
||||
var f1 = bcoin.mtx().addInput(t4, 1) // 11000
|
||||
.addOutput(f, 10000);
|
||||
.addOutput(af, 10000);
|
||||
// balance: 11000
|
||||
// w.sign(f1);
|
||||
w.sign(f1, function(err) {
|
||||
assert.ifError(err);
|
||||
var fake = bcoin.mtx().addInput(t1, 1) // 1000 (already redeemed)
|
||||
.addOutput(w, 500);
|
||||
.addOutput(aw, 500);
|
||||
// Script inputs but do not sign
|
||||
// w.scriptInputs(fake);
|
||||
w.scriptInputs(fake, function(err) {
|
||||
@ -262,7 +269,7 @@ describe('Wallet', function() {
|
||||
return tx.hash('hex') === f1.hash('hex');
|
||||
}));
|
||||
|
||||
var w2 = bcoin.wallet.fromJSON(w.toJSON());
|
||||
//var w2 = bcoin.wallet.fromJSON(w.toJSON());
|
||||
// assert.equal(w2.getBalance(), 11000);
|
||||
// assert(w2.getHistory().some(function(tx) {
|
||||
// return tx.hash('hex') === f1.hash('hex');
|
||||
@ -278,6 +285,8 @@ describe('Wallet', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -305,16 +314,20 @@ describe('Wallet', function() {
|
||||
|
||||
it('should fill tx with inputs', function(cb) {
|
||||
wdb.create({}, function(err, w1) {
|
||||
assert.ifError(err);
|
||||
w1.getReceiveAddress(function(err, aw1) {
|
||||
assert.ifError(err);
|
||||
wdb.create({}, function(err, w2) {
|
||||
assert.ifError(err);
|
||||
w2.getReceiveAddress(function(err, aw2) {
|
||||
assert.ifError(err);
|
||||
|
||||
// Coinbase
|
||||
var t1 = bcoin.mtx()
|
||||
.addOutput(w1, 5460)
|
||||
.addOutput(w1, 5460)
|
||||
.addOutput(w1, 5460)
|
||||
.addOutput(w1, 5460);
|
||||
.addOutput(aw1, 5460)
|
||||
.addOutput(aw1, 5460)
|
||||
.addOutput(aw1, 5460)
|
||||
.addOutput(aw1, 5460);
|
||||
|
||||
t1.addInput(dummyInput);
|
||||
|
||||
@ -323,7 +336,7 @@ describe('Wallet', function() {
|
||||
assert.ifError(err);
|
||||
|
||||
// Create new transaction
|
||||
var t2 = bcoin.mtx().addOutput(w2, 5460);
|
||||
var t2 = bcoin.mtx().addOutput(aw2, 5460);
|
||||
w1.fill(t2, { rate: 10000, round: true }, function(err) {
|
||||
assert.ifError(err);
|
||||
w1.sign(t2, function(err) {
|
||||
@ -338,7 +351,7 @@ describe('Wallet', function() {
|
||||
assert.equal(t2.getFee(), 10920);
|
||||
|
||||
// Create new transaction
|
||||
var t3 = bcoin.mtx().addOutput(w2, 15000);
|
||||
var t3 = bcoin.mtx().addOutput(aw2, 15000);
|
||||
w1.fill(t3, { rate: 10000, round: true }, function(err) {
|
||||
assert(err);
|
||||
assert.equal(err.requiredFunds, 25000);
|
||||
@ -347,22 +360,28 @@ describe('Wallet', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should fill tx with inputs with accurate fee', function(cb) {
|
||||
wdb.create({ master: KEY1 }, function(err, w1) {
|
||||
assert.ifError(err);
|
||||
w1.getReceiveAddress(function(err, aw1) {
|
||||
assert.ifError(err);
|
||||
wdb.create({ master: KEY2 }, function(err, w2) {
|
||||
assert.ifError(err);
|
||||
w2.getReceiveAddress(function(err, aw2) {
|
||||
assert.ifError(err);
|
||||
|
||||
// Coinbase
|
||||
var t1 = bcoin.mtx()
|
||||
.addOutput(w1, 5460)
|
||||
.addOutput(w1, 5460)
|
||||
.addOutput(w1, 5460)
|
||||
.addOutput(w1, 5460);
|
||||
.addOutput(aw1, 5460)
|
||||
.addOutput(aw1, 5460)
|
||||
.addOutput(aw1, 5460)
|
||||
.addOutput(aw1, 5460);
|
||||
|
||||
t1.addInput(dummyInput);
|
||||
|
||||
@ -371,7 +390,7 @@ describe('Wallet', function() {
|
||||
assert.ifError(err);
|
||||
|
||||
// Create new transaction
|
||||
var t2 = bcoin.mtx().addOutput(w2, 5460);
|
||||
var t2 = bcoin.mtx().addOutput(aw2, 5460);
|
||||
w1.fill(t2, { rate: 10000 }, function(err) {
|
||||
assert.ifError(err);
|
||||
w1.sign(t2, function(err) {
|
||||
@ -393,7 +412,7 @@ describe('Wallet', function() {
|
||||
// Create new transaction
|
||||
wdb.addTX(t2, function(err) {
|
||||
assert.ifError(err);
|
||||
var t3 = bcoin.mtx().addOutput(w2, 15000);
|
||||
var t3 = bcoin.mtx().addOutput(aw2, 15000);
|
||||
w1.fill(t3, { rate: 10000 }, function(err) {
|
||||
assert(err);
|
||||
cb();
|
||||
@ -404,32 +423,40 @@ describe('Wallet', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should sign multiple inputs using different keys', function(cb) {
|
||||
wdb.create({}, function(err, w1) {
|
||||
assert.ifError(err);
|
||||
w1.getReceiveAddress(function(err, aw1) {
|
||||
assert.ifError(err);
|
||||
wdb.create({}, function(err, w2) {
|
||||
assert.ifError(err);
|
||||
w2.getReceiveAddress(function(err, aw2) {
|
||||
assert.ifError(err);
|
||||
wdb.create({}, function(err, to) {
|
||||
assert.ifError(err);
|
||||
to.getReceiveAddress(function(err, ato) {
|
||||
assert.ifError(err);
|
||||
|
||||
// Coinbase
|
||||
var t1 = bcoin.mtx()
|
||||
.addOutput(w1, 5460)
|
||||
.addOutput(w1, 5460)
|
||||
.addOutput(w1, 5460)
|
||||
.addOutput(w1, 5460);
|
||||
.addOutput(aw1, 5460)
|
||||
.addOutput(aw1, 5460)
|
||||
.addOutput(aw1, 5460)
|
||||
.addOutput(aw1, 5460);
|
||||
|
||||
t1.addInput(dummyInput);
|
||||
|
||||
// Fake TX should temporarly change output
|
||||
// Coinbase
|
||||
var t2 = bcoin.mtx()
|
||||
.addOutput(w2, 5460)
|
||||
.addOutput(w2, 5460)
|
||||
.addOutput(w2, 5460)
|
||||
.addOutput(w2, 5460);
|
||||
.addOutput(aw2, 5460)
|
||||
.addOutput(aw2, 5460)
|
||||
.addOutput(aw2, 5460)
|
||||
.addOutput(aw2, 5460);
|
||||
|
||||
t2.addInput(dummyInput);
|
||||
// Fake TX should temporarly change output
|
||||
@ -441,7 +468,7 @@ describe('Wallet', function() {
|
||||
|
||||
// Create our tx with an output
|
||||
var tx = bcoin.mtx();
|
||||
tx.addOutput(to, 5460);
|
||||
tx.addOutput(ato, 5460);
|
||||
|
||||
var cost = tx.getOutputValue();
|
||||
var total = cost * constants.tx.MIN_FEE;
|
||||
@ -452,7 +479,7 @@ describe('Wallet', function() {
|
||||
assert.ifError(err);
|
||||
|
||||
// Add dummy output (for `left`) to calculate maximum TX size
|
||||
tx.addOutput(w1, 0);
|
||||
tx.addOutput(aw1, 0);
|
||||
|
||||
// Add our unspent inputs to sign
|
||||
tx.addInput(coins1[0]);
|
||||
@ -507,6 +534,9 @@ describe('Wallet', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function multisig(witness, bullshitNesting, cb) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user