ethers.js/contracts/contract.js

327 lines
12 KiB
JavaScript
Raw Normal View History

2017-02-24 23:10:28 +03:00
'use strict';
var Interface = require('./interface.js');
2017-02-24 23:10:28 +03:00
var utils = (function() {
return {
defineProperty: require('ethers-utils/properties.js').defineProperty,
getAddress: require('ethers-utils/address.js').getAddress,
2017-02-24 23:10:28 +03:00
bigNumberify: require('ethers-utils/bignumber.js').bigNumberify,
hexlify: require('ethers-utils/convert.js').hexlify,
};
})();
var allowedTransactionKeys = {
2017-12-02 05:58:36 +03:00
data: true, from: true, gasLimit: true, gasPrice:true, nonce: true, to: true, value: true
2017-02-24 23:10:28 +03:00
}
function copyObject(object) {
var result = {};
for (var key in object) {
result[key] = object[key];
}
return result;
}
2017-05-22 03:27:47 +03:00
function Contract(addressOrName, contractInterface, signerOrProvider) {
if (!(this instanceof Contract)) { throw new Error('missing new'); }
2017-02-24 23:10:28 +03:00
2017-05-22 03:27:47 +03:00
// @TODO: Maybe still check the addressOrName looks like a valid address or name?
//address = utils.getAddress(address);
2017-02-24 23:10:28 +03:00
if (!(contractInterface instanceof Interface)) {
contractInterface = new Interface(contractInterface);
}
2017-02-24 23:10:28 +03:00
if (!signerOrProvider) { throw new Error('missing signer or provider'); }
var signer = signerOrProvider;
var provider = null;
if (signerOrProvider.provider) {
provider = signerOrProvider.provider;
} else {
provider = signerOrProvider;
signer = null;
}
2017-05-22 03:27:47 +03:00
utils.defineProperty(this, 'address', addressOrName);
utils.defineProperty(this, 'interface', contractInterface);
utils.defineProperty(this, 'signer', signer);
utils.defineProperty(this, 'provider', provider);
var addressPromise = provider.resolveName(addressOrName);
2017-02-24 23:10:28 +03:00
function runMethod(method, estimateOnly) {
return function() {
var transaction = {}
var params = Array.prototype.slice.call(arguments);
// If 1 extra parameter was passed in, it contains overrides
if (params.length == method.inputs.length + 1) {
2017-02-24 23:10:28 +03:00
transaction = params.pop();
if (typeof(transaction) !== 'object') {
throw new Error('invalid transaction overrides');
}
transaction = copyObject(transaction);
2017-03-08 09:52:53 +03:00
// Check for unexpected keys (e.g. using "gas" instead of "gasLimit")
2017-02-24 23:10:28 +03:00
for (var key in transaction) {
if (!allowedTransactionKeys[key]) {
throw new Error('unknown transaction override ' + key);
}
}
}
2017-03-08 09:52:53 +03:00
// Check overrides make sense
['data', 'to'].forEach(function(key) {
if (transaction[key] != null) {
throw new Error('cannot override ' + key) ;
}
});
2017-02-24 23:10:28 +03:00
var call = method.apply(contractInterface, params);
2017-03-08 09:52:53 +03:00
// Send to the contract address
2017-05-22 03:27:47 +03:00
transaction.to = addressOrName;
2017-03-08 09:52:53 +03:00
// Set the transaction data
transaction.data = call.data;
2017-02-24 23:10:28 +03:00
switch (call.type) {
case 'call':
// Call (constant functions) always cost 0 ether
if (estimateOnly) {
return Promise.resolve(new utils.bigNumberify(0));
}
// Check overrides make sense
2017-03-08 09:52:53 +03:00
['gasLimit', 'gasPrice', 'value'].forEach(function(key) {
2017-02-24 23:10:28 +03:00
if (transaction[key] != null) {
throw new Error('call cannot override ' + key) ;
}
});
var fromPromise = null;
2017-03-08 09:52:53 +03:00
if (transaction.from == null && signer && signer.getAddress) {
fromPromise = signer.getAddress();
2017-05-22 03:27:47 +03:00
if (!(fromPromise instanceof Promise)) {
fromPromise = Promise.resolve(fromPromise);
}
} else {
fromPromise = Promise.resolve(null);
2017-02-24 23:10:28 +03:00
}
return fromPromise.then(function(address) {
if (address) {
transaction.from = utils.getAddress(address);
}
return provider.call(transaction);
}).then(function(value) {
return call.parse(value);
2017-02-24 23:10:28 +03:00
});
case 'transaction':
2017-03-01 11:59:46 +03:00
if (!signer) { return Promise.reject(new Error('missing signer')); }
2017-03-08 09:52:53 +03:00
// Make sure they aren't overriding something they shouldn't
if (transaction.from != null) {
throw new Error('transaction cannot override from') ;
}
2017-02-24 23:10:28 +03:00
2017-03-08 09:52:53 +03:00
// Only computing the transaction estimate
2017-02-24 23:10:28 +03:00
if (estimateOnly) {
if (signer && signer.estimateGas) {
return signer.estimateGas(transaction);
}
return provider.estimateGas(transaction)
2017-02-24 23:10:28 +03:00
}
2017-03-08 09:52:53 +03:00
// If the signer supports sendTrasaction, use it
if (signer.sendTransaction) {
return signer.sendTransaction(transaction);
}
if (!signer.sign) {
return Promise.reject(new Error('custom signer does not support signing'));
}
2017-03-01 11:59:46 +03:00
if (transaction.gasLimit == null) {
2017-03-08 09:52:53 +03:00
transaction.gasLimit = signer.defaultGasLimit || 2000000;
2017-03-01 11:59:46 +03:00
}
var noncePromise = null;
if (transaction.nonce) {
noncePromise = Promise.resolve(transaction.nonce)
} else if (signer.getTransactionCount) {
noncePromise = signer.getTransactionCount();
if (!(noncePromise instanceof Promise)) {
noncePromise = Promise.resolve(noncePromise);
}
} else {
var addressPromise = signer.getAddress();
if (!(addressPromise instanceof Promise)) {
addressPromise = Promise.resolve(addressPromise);
}
noncePromise = addressPromise.then(function(address) {
return provider.getTransactionCount(address, 'pending');
});
}
2017-03-08 09:52:53 +03:00
var gasPricePromise = null;
if (transaction.gasPrice) {
gasPricePromise = Promise.resolve(transaction.gasPrice);
} else {
gasPricePromise = provider.getGasPrice();
}
return Promise.all([
noncePromise,
gasPricePromise
]).then(function(results) {
transaction.nonce = results[0];
transaction.gasPrice = results[1];
return signer.sign(transaction);
}).then(function(signedTransaction) {
return provider.sendTransaction(signedTransaction);
2017-02-24 23:10:28 +03:00
});
}
};
}
var estimate = {};
utils.defineProperty(this, 'estimate', estimate);
var functions = {};
utils.defineProperty(this, 'functions', functions);
var events = {};
utils.defineProperty(this, 'events', events);
Object.keys(contractInterface.functions).forEach(function(methodName) {
var method = contractInterface.functions[methodName];
var run = runMethod(method, false);
if (this[methodName] == null) {
utils.defineProperty(this, methodName, run);
} else {
console.log('WARNING: Multiple definitions for ' + method);
}
if (functions[method] == null) {
utils.defineProperty(functions, methodName, run);
utils.defineProperty(estimate, methodName, runMethod(method, true));
}
2017-02-24 23:10:28 +03:00
}, this);
Object.keys(contractInterface.events).forEach(function(eventName) {
var eventInfo = contractInterface.events[eventName]();
var eventCallback = null;
function handleEvent(log) {
addressPromise.then(function(address) {
// Not meant for us (the topics just has the same name)
if (address != log.address) { return; }
try {
var result = eventInfo.parse(log.topics, log.data);
// Some useful things to have with the log
log.args = result;
log.event = eventName;
log.parse = eventInfo.parse;
log.removeListener = function() {
provider.removeListener(eventInfo.topics, handleEvent);
}
var poller = function(func, key) {
return new Promise(function(resolve, reject) {
function poll() {
provider[func](log[key]).then(function(value) {
if (value == null) {
setTimeout(poll, 1000);
return;
}
resolve(value);
}, function(error) {
reject(error);
});
}
poll();
});
}
log.getBlock = function() { return poller('getBlock', 'blockHash'); }
log.getTransaction = function() { return poller('getTransaction', 'transactionHash'); }
log.getTransactionReceipt = function() { return poller('getTransactionReceipt', 'transactionHash'); }
log.eventSignature = eventInfo.signature;
eventCallback.apply(log, Array.prototype.slice.call(result));
} catch (error) {
console.log(error);
}
});
}
var property = {
2017-02-24 23:10:28 +03:00
enumerable: true,
get: function() {
return eventCallback;
2017-02-24 23:10:28 +03:00
},
set: function(value) {
if (!value) { value = null; }
if (!value && eventCallback) {
provider.removeListener(eventInfo.topics, handleEvent);
} else if (value && !eventCallback) {
provider.on(eventInfo.topics, handleEvent);
}
eventCallback = value;
2017-02-24 23:10:28 +03:00
}
};
var propertyName = 'on' + eventName.toLowerCase();
if (this[propertyName] == null) {
Object.defineProperty(this, propertyName, property);
}
Object.defineProperty(events, eventName, property);
2017-02-24 23:10:28 +03:00
}, this);
}
2017-03-01 11:59:46 +03:00
utils.defineProperty(Contract.prototype, 'connect', function(signerOrProvider) {
return new Contract(this.address, this.interface, signerOrProvider);
});
utils.defineProperty(Contract, 'getDeployTransaction', function(bytecode, contractInterface) {
if (!(contractInterface instanceof Interface)) {
contractInterface = new Interface(contractInterface);
}
2017-02-24 23:10:28 +03:00
2017-03-01 11:59:46 +03:00
var args = Array.prototype.slice.call(arguments);
args.splice(1, 1);
return {
2017-03-01 11:59:46 +03:00
data: contractInterface.deployFunction.apply(contractInterface, args).bytecode
}
});
2017-03-01 11:59:46 +03:00
2017-02-24 23:10:28 +03:00
module.exports = Contract;