'use strict'; var Interface = require('./interface.js'); var utils = (function() { return { defineProperty: require('ethers-utils/properties.js').defineProperty, getAddress: require('ethers-utils/address.js').getAddress, bigNumberify: require('ethers-utils/bignumber.js').bigNumberify, hexlify: require('ethers-utils/convert.js').hexlify, }; })(); var allowedTransactionKeys = { data: true, from: true, gasLimit: true, gasPrice:true, to: true, value: true } function Contract(address, contractInterface, signerOrProvider) { if (!(this instanceof Contract)) { throw new Error('missing new'); } address = utils.getAddress(address); if (!(contractInterface instanceof Interface)) { contractInterface = new Interface(contractInterface); } var signer = signerOrProvider; var provider = null; if (signerOrProvider.provider) { provider = signerOrProvider.provider; } else if (signerOrProvider) { provider = signerOrProvider; signer = null; } else { throw new Error('missing provider'); } utils.defineProperty(this, 'address', address); utils.defineProperty(this, 'interface', contractInterface); utils.defineProperty(this, 'signer', signer); utils.defineProperty(this, 'provider', provider); /* var filters = {}; function setupFilter(call, callback) { var info = filters[call.name]; // Stop and remove the filter if (!callback) { if (info) { info.filter.stopWatching(); } delete filters[call.name]; return; } if (typeof(callback) !== 'function') { throw new Error('invalid callback'); } // Already have a filter, just update the callback if (info) { info.callback = callback; return; } info = {callback: callback}; filters[call.name] = info; */ // Start a new filter /* info.filter = web3.eth.filter({ address: contractAddress, topics: call.topics }, function(error, result) { // @TODO: Emit errors to .onerror? Maybe? if (error) { console.log(error); return; } try { info.callback.apply(self, call.parse(result.data)); } catch(error) { console.log(error); } }); */ // } 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) { transaction = params.pop(); if (typeof(transaction) !== 'object') { throw new Error('invalid transaction overrides'); } for (var key in transaction) { if (!allowedTransactionKeys[key]) { throw new Error('unknown transaction override ' + key); } } } var call = method.apply(contractInterface, params); 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 ['data', 'gasLimit', 'gasPrice', 'to', 'value'].forEach(function(key) { if (transaction[key] != null) { throw new Error('call cannot override ' + key) ; } }); transaction.data = call.data; var fromPromise = null; if (transaction.from == null && signer) { fromPromise = new Promise(function(resolve, reject) { var address = signer.address; if (address instanceof Promise) { return address; } resolve(address); }); } else { fromPromise = Promise.resolve(null); } transaction.to = address; return fromPromise.then(function(address) { if (address) { transaction.from = utils.getAddress(address); } return provider.call(transaction); }).then(function(value) { return call.parse(value); }); case 'transaction': ['data', 'from', 'to'].forEach(function(key) { if (transaction[key] != null) { throw new Error('transaction cannot override ' + key) ; } }); transaction.data = call.data; transaction.to = address; if (transaction.gasLimit == null) { transaction.gasLimit = 3000000; } if (estimateOnly) { return provider.estimateGas(transaction) } var gasPricePromise = null; var noncePromise = null; if (transaction.nonce) { noncePromise = Promise.resolve(transaction.nonce) } else { noncePromise = provider.getTransactionCount(signer.address, 'pending'); } 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); }); } }; } var estimate = {}; utils.defineProperty(this, 'estimate', estimate); var execute = {}; utils.defineProperty(this, 'execute', execute); 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 (execute[method] == null) { utils.defineProperty(execute, methodName, run); utils.defineProperty(estimate, methodName, runMethod(method, true)); } }, this); Object.keys(contractInterface.events).forEach(function(eventName) { var eventInfo = contractInterface.events[eventName](); var eventCallback = null; function handleEvent(log) { try { var result = eventInfo.parse(log.data); eventCallback.apply(log, Array.prototype.slice.call(result)); } catch (error) { console.log(error); } } var property = { enumerable: true, get: function() { return eventCallback; }, 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; } }; var propertyName = 'on' + eventName.toLowerCase(); if (this[propertyName] == null) { Object.defineProperty(this, propertyName, property); } Object.defineProperty(events, eventName, property); }, this); } /* utils.defineProperty(Contract, 'getDeployTransaction', function(bytecode, contractInterface) { if (typeof(contractInterface) === 'string') { contractInterface = new Interface(contractInterface); } bytecode = utils.hexlify(bytecode); var args = Array.prototype.slice.call(arguments, 2); if (contractInterface.constructorFunction) { var init = contractInterface.constructorFunction.apply(contractInterface, args); console.log(init); bytecode += init.data.substring(2); } else { if (args.length) { throw new Error('constructor takes no parameters'); } } console.log('deploy', bytecode, args, init); return { data: bytecode } }); */ module.exports = Contract;