PHP WebShell
Текущая директория: /usr/lib/node_modules/bitgo-express/node_modules/bitgo/src/eth
Просмотр файла: ethWallet.js
//
// Wallet Object
// BitGo accessor for a specific wallet
//
// Copyright 2014, BitGo, Inc. All Rights Reserved.
//
var bitcoin = require('../bitcoin');
var Util = require('../util');
var ethAbi = function() {};
var ethUtil = function() {};
var assert = require('assert');
var common = require('../common');
var Q = require('q');
var _ = require('lodash');
try {
ethAbi = require('ethereumjs-abi');
ethUtil = require('ethereumjs-util');
} catch (e) {
// ethereum currently not supported
}
//
// Constructor
// TODO: WORK IN PROGRESS
//
var EthWallet = function(bitgo, wallet) {
this.bitgo = bitgo;
this.wallet = wallet;
this.signingAddresses = [];
if (wallet.private) {
this.signingAddresses = wallet.private.addresses;
}
};
EthWallet.prototype.toJSON = function() {
return this.wallet;
};
//
// id
// Get the id of this wallet.
//
EthWallet.prototype.id = function() {
return this.wallet.id;
};
//
// label
// Get the label of this wallet.
//
EthWallet.prototype.label = function() {
return this.wallet.label;
};
//
// balance
// Get the balance of this wallet.
//
EthWallet.prototype.balance = function() {
return new ethUtil.BN(this.wallet.balance);
};
//
// balance
// Get the spendable balance of this wallet.
// This is the total of all funds available for s(p)ending
//
EthWallet.prototype.spendableBalance = function() {
return new ethUtil.BN(this.wallet.spendableBalance);
};
//
// type
// Get the type of this wallet, e.g. 'eth'
//
EthWallet.prototype.type = function() {
return this.wallet.type;
};
//
// url
// Get the URL of this wallet
//
EthWallet.prototype.url = function(extra) {
extra = extra || '';
return this.bitgo.url('/eth/wallet/' + this.id() + extra);
};
//
// get
// Refetches this wallet and returns it
//
EthWallet.prototype.get = function(params, callback) {
params = params || {};
common.validateParams(params, [], [], callback);
var self = this;
return this.bitgo.get(this.url())
.result()
.then(function(res) {
self.wallet = res;
return self;
})
.nodeify(callback);
};
//
// freeze
// Freeze the wallet for a duration of choice, stopping BitGo from signing any transactions
// Parameters include:
// limit: the duration to freeze the wallet for in seconds, defaults to 3600
//
EthWallet.prototype.freeze = function(params, callback) {
params = params || {};
common.validateParams(params, [], [], callback);
if (params.duration) {
if (typeof(params.duration) != 'number') {
throw new Error('invalid duration - should be number of seconds');
}
}
return this.bitgo.post(this.url('/freeze'))
.send(params)
.result()
.nodeify(callback);
};
//
// delete
// Deletes the wallet
//
EthWallet.prototype.delete = function(params, callback) {
params = params || {};
common.validateParams(params, [], [], callback);
return this.bitgo.del(this.url())
.result()
.nodeify(callback);
};
/**
* Rename a wallet
* @param params
* - label: the wallet's intended new name
* @param callback
* @returns {*}
*/
EthWallet.prototype.setWalletName = function(params, callback) {
params = params || {};
common.validateParams(params, ['label'], [], callback);
var url = this.url();
return this.bitgo.put(url)
.send({ label: params.label })
.result()
.nodeify(callback);
};
//
// labels
// List the labels for the addresses in a given wallet
//
EthWallet.prototype.labels = function(params, callback) {
params = params || {};
common.validateParams(params, [], [], callback);
var url = this.bitgo.url('/labels/' + this.id());
return this.bitgo.get(url)
.result('labels')
.nodeify(callback);
};
//
// setLabel
// Sets a label on the provided address
//
EthWallet.prototype.setLabel = function(params, callback) {
params = params || {};
common.validateParams(params, ['address', 'label'], [], callback);
var self = this;
if (!self.bitgo.eth().verifyAddress({ address: params.address })) {
throw new Error('Invalid Ethereum address: ' + params.address);
}
var url = this.bitgo.url('/labels/' + this.id() + '/' + params.address);
return this.bitgo.put(url)
.send({ 'label': params.label })
.result()
.nodeify(callback);
};
//
// deleteLabel
// Deletes the label associated with the provided address
//
EthWallet.prototype.deleteLabel = function(params, callback) {
params = params || {};
common.validateParams(params, ['address'], [], callback);
var self = this;
if (!self.bitgo.eth().verifyAddress({ address: params.address })) {
throw new Error('Invalid Ethereum address: ' + params.address);
}
var url = this.bitgo.url('/labels/' + this.id() + '/' + params.address);
return this.bitgo.del(url)
.result()
.nodeify(callback);
};
//
// transactions
// List the transactions for a given wallet
// Options include:
// TODO: Add iterators for start/count/etc
EthWallet.prototype.transactions = function(params, callback) {
params = params || {};
common.validateParams(params, [], [], callback);
var query = Util.preparePageableQuery(params);
if (params.minHeight) {
if (typeof(params.minHeight) != 'number') {
throw new Error('invalid minHeight argument, expecting number');
}
query.minHeight = params.minHeight;
}
var url = this.url('/tx');
return this.bitgo.get(url)
.query(query)
.result()
.nodeify(callback);
};
//
// transfers
// List the transfers for a given wallet
// Options include: skip, limit, minHeight
EthWallet.prototype.transfers = function(params, callback) {
params = params || {};
common.validateParams(params, [], [], callback);
var query = Util.preparePageableQuery(params);
var url = this.url('/transfer');
return this.bitgo.get(url)
.query(query)
.result()
.nodeify(callback);
};
//
// transaction
// Get a transaction by ID for a given wallet
EthWallet.prototype.getTransaction = function(params, callback) {
params = params || {};
common.validateParams(params, ['id'], [], callback);
var url = this.url('/tx/' + params.id);
return this.bitgo.get(url)
.result()
.nodeify(callback);
};
//
// transfer
// Get a transfer by ID for a given wallet
EthWallet.prototype.getTransfer = function(params, callback) {
params = params || {};
common.validateParams(params, ['id'], [], callback);
var url = this.url('/transfer/' + params.id);
return this.bitgo.get(url)
.result()
.nodeify(callback);
};
//
// Key chains
// Gets the user key chain for this wallet
// The user key chain is typically the first keychain of the wallet and has the encrypted xpriv stored on BitGo.
// Useful when trying to get the users' keychain from the server before decrypting to sign a transaction.
EthWallet.prototype.getEncryptedUserKeychain = function(params, callback) {
params = params || {};
common.validateParams(params, [], [], callback);
var self = this;
return self.bitgo.keychains()
.get({ 'ethAddress': self.signingAddresses[0].address })
.then(function(keychain) {
if (!keychain.encryptedXprv) {
return self.bitgo.reject('No encrypted keychains on this wallet.', callback);
}
return keychain;
})
.nodeify(callback);
};
//
// createAddress
// Creates a forwarder/proxy contract that redirects funds to the main wallet
//
EthWallet.prototype.createAddress = function(params, callback) {
return this.bitgo.post(this.url('/address'))
.result()
.nodeify(callback);
};
//
// getTransactionPreBuildParams
// Gets transaction pre-build parameters on this wallet
//
// Returns:
// {
// gasLimit: maximum amount of gas this transaction will likely cost,
// gasprice: current market rate per gas,
// nextContractSequenceId: next sequence id to sign with on the wallet
// }
//
EthWallet.prototype.getTransactionPreBuildParams = function(params, callback) {
return this.bitgo.post(this.url('/tx/prebuild'))
.send({}) // in future we'll send the target destination, value and other data
.result()
.nodeify(callback);
};
//
// getOperationSha3ForExecuteAndConfirm
// Helper to pack the transaction parameters into a sha3 for signing.
// Equivalent of sha3(...) in solidity, which gets verified in the contract.
//
// Parameters:
// recipients: [] array of { toAddress, value, data } objects
// expireTime: unix timestamp (seconds since 1970) when the first signature will expire
//
var getOperationSha3ForExecuteAndConfirm = function(recipients, expireTime, contractSequenceId) {
if (!recipients || !Array.isArray(recipients)) {
throw new Error('expecting array of recipients');
}
// Right now we only support 1 recipient
if (recipients.length != 1) {
throw new Error("must send to exactly 1 recipient");
}
if (typeof(expireTime) !== 'number') {
throw new Error("expireTime must be number of seconds since epoch");
}
if (typeof(contractSequenceId) !== 'number') {
throw new Error("contractSequenceId must be number");
}
// Check inputs
recipients.forEach(function(recipient) {
if (typeof(recipient.toAddress) !== 'string' || !ethUtil.isValidAddress(ethUtil.addHexPrefix(recipient.toAddress))) {
throw new Error("Invalid address: " + recipient.toAddress);
}
if (typeof(recipient.value) !== 'string') {
throw new Error("Invalid value for: " + recipient.toAddress + ' - should be of type string in wei');
}
if (recipient.data && typeof(recipient.data) !== 'string') {
throw new Error("Data for recipient " + recipient.toAddress + ' - should be of type hex string');
}
});
var recipient = recipients[0];
return ethUtil.bufferToHex(ethAbi.soliditySHA3(
["address", "uint", "string", "uint", "uint"],
[
new ethUtil.BN(ethUtil.stripHexPrefix(recipient.toAddress), 16),
recipient.value,
ethUtil.stripHexPrefix(recipient.data) || "",
expireTime,
contractSequenceId
]
));
};
//
// send
// Send a transaction to the Ethereum network via BitGo.
// This method will sign the recipient address, value, data, expireTime and sequenceId.
// BitGo will use the signature and these values to create and sign a transaction to invoke the
// executeAndConfirm method on the wallet contract, which has the following prototype:
// executeAndConfirm(address _to, uint _value, bytes _data, uint _expireTime, uint _sequenceId, bytes _signature)
//
// executeandconfirm will do a ecrecover on sha3(_to, _value, _data, _expireTime, _sequenceId) and the signature
// and expect the signer address to be an address on the wallet (we call this the "first" signature").
//
// Thus the first signature will come from the one made on this client and the second will be BitGo's signature on the
// transaction that sent to the blockchain.
//
// Parameters:
// recipients: [] array of { toAddress, value, data } objects
// expireTime: unix timestamp (seconds since 1970) when the first signature will expire
// walletPassphrase - the passphrase to be used to decrypt the user key on this wallet OR
// xprv - the private key in string form
//
// Returns:
// txHash - the hash of the transaction
// txHex - the hex of the entire transaction
//
EthWallet.prototype.sendTransaction = function(params, callback) {
params = _.extend({}, params);
common.validateParams(params, [], ['message', 'otp'], callback);
var EXPIRETIME_DEFAULT = 60 * 60 * 24 * 7; // This signature will be valid for 1 week
if (!params.recipients && params.toAddress && params.value) {
params.recipients = [{ toAddress: params.toAddress, value: params.value }];
}
if (!params.recipients || !Array.isArray(params.recipients)) {
throw new Error('expecting array of recipients');
}
if (params.recipients.length !== 1) {
throw new Error('only 1 recipient currently supported per transaction');
}
// Check inputs
params.recipients.forEach(function(recipient) {
if (typeof(recipient.toAddress) !== 'string' || !ethUtil.isValidAddress(ethUtil.addHexPrefix(recipient.toAddress))) {
throw new Error("Invalid address: " + recipient.toAddress);
}
if (typeof(recipient.value) !== 'string') {
throw new Error("Invalid value for: " + recipient.toAddress + ' - should be of type string in wei');
}
if (recipient.data && typeof(recipient.data) !== 'string') {
throw new Error("Data for recipient " + recipient.toAddress + ' - should be of type hex string');
}
});
if (params.expireTime !== undefined) {
if (typeof(params.expireTime) == 'number') {
if (params.expireTime < ((new Date().getTime()) / 1000)) {
throw new Error('expireTime is before current time');
}
} else {
throw new Error('expecting number of seconds since epoch for expireTime');
}
}
if (params.gasLimit !== undefined) {
if (typeof(params.gasLimit) != 'number' || params.gasLimit < 1) {
throw new Error('expecting positive integer for gasLimit');
}
}
var self = this;
return Q()
.then(function() {
// wrap in case one of these throws
return Q.all([self.getAndPrepareSigningKeychain(params), self.getTransactionPreBuildParams(params)]);
})
.spread(function(keychain, prebuildParams) {
var secondsSinceEpoch = Math.floor((new Date().getTime()) / 1000);
var expireTime = params.expireTime || secondsSinceEpoch + EXPIRETIME_DEFAULT;
var operationHash = getOperationSha3ForExecuteAndConfirm(params.recipients, expireTime, prebuildParams.nextContractSequenceId);
var signature = Util.ethSignMsgHash(operationHash, Util.xprvToEthPrivateKey(keychain.xprv));
var txParams = {
recipients: params.recipients,
expireTime: expireTime,
contractSequenceId: prebuildParams.nextContractSequenceId,
sequenceId: params.sequenceId,
operationHash: operationHash,
signature: signature,
gasLimit: params.gasLimit
};
return self.bitgo.post(self.url('/tx/send'))
.send(txParams)
.result();
})
.nodeify(callback);
};
//
// getAndPrepareSigningKeychain
// INTERNAL function to get the user keychain for signing.
// Caller must provider either a keychain, or walletPassphrase or xprv as a string
// If the caller provides the keychain with xprv, it is simply returned.
// If the caller provides the encrypted xprv (walletPassphrase), then fetch the keychain object and decrypt
// Otherwise if the xprv is provided, fetch the keychain object and augment it with the xprv.
//
// Parameters:
// keychain - keychain with xprv
// xprv - the private key in string form
// walletPassphrase - the passphrase to be used to decrypt the user key on this wallet
// Returns:
// Keychain object containing xprv, xpub and paths
//
EthWallet.prototype.getAndPrepareSigningKeychain = function(params, callback) {
params = params || {};
// If keychain with xprv is already provided, use it
if (typeof(params.keychain) === 'object' && params.keychain.xprv) {
return Q(params.keychain);
}
common.validateParams(params, [], ['walletPassphrase', 'xprv'], callback);
if ((params.walletPassphrase && params.xprv) || (!params.walletPassphrase && !params.xprv)) {
throw new Error('must provide exactly one of xprv or walletPassphrase');
}
var self = this;
// Caller provided a wallet passphrase
if (params.walletPassphrase) {
return self.getEncryptedUserKeychain()
.then(function(keychain) {
// Decrypt the user key with a passphrase
try {
keychain.xprv = self.bitgo.decrypt({ password: params.walletPassphrase, input: keychain.encryptedXprv });
} catch (e) {
throw new Error('Unable to decrypt user keychain');
}
return keychain;
});
}
// Caller provided an xprv - validate and construct keychain object
var xpub;
try {
xpub = bitcoin.HDNode.fromBase58(params.xprv).neutered().toBase58();
} catch (e) {
throw new Error('Unable to parse the xprv');
}
if (xpub == params.xprv) {
throw new Error('xprv provided was not a private key (found xpub instead)');
}
var walletAddresses = _.map(self.signingAddresses, 'address');
if (!_.includes(walletAddresses, Util.xpubToEthAddress(xpub))) {
throw new Error('xprv provided did not correspond to any address on this wallet!');
}
// get the keychain object from bitgo to find the path and (potential) wallet structure
return self.bitgo.keychains().get({ xpub: xpub })
.then(function(keychain) {
keychain.xprv = params.xprv;
return keychain;
});
};
EthWallet.prototype.listWebhooks = function(params, callback) {
params = params || {};
common.validateParams(params, [], [], callback);
return this.bitgo.get(this.url('/webhooks'))
.send()
.result()
.nodeify(callback);
};
EthWallet.prototype.addWebhook = function(params, callback) {
params = params || {};
common.validateParams(params, ['url', 'type'], [], callback);
return this.bitgo.post(this.url('/webhooks'))
.send(params)
.result()
.nodeify(callback);
};
EthWallet.prototype.removeWebhook = function(params, callback) {
params = params || {};
common.validateParams(params, ['url', 'type'], [], callback);
return this.bitgo.del(this.url('/webhooks'))
.send(params)
.result()
.nodeify(callback);
};
module.exports = EthWallet;
Выполнить команду
Для локальной разработки. Не используйте в интернете!