From d10c0b795112afa7a345b3f4b1aa471e0c16dd18 Mon Sep 17 00:00:00 2001 From: Pascal Precht Date: Thu, 6 Jun 2019 18:52:01 +0200 Subject: [PATCH] feat(@cockpit/explorer): enable users to send ether through payable methods (#1649) --- .../embark-contracts-manager/src/index.js | 15 ++++++-- packages/embark-ui/src/actions/index.js | 2 +- .../src/components/ContractOverview.js | 38 ++++++++++++++++--- .../src/components/ContractOverview.scss | 1 + 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/packages/embark-contracts-manager/src/index.js b/packages/embark-contracts-manager/src/index.js index c45ad64a9..7622aa2c2 100644 --- a/packages/embark-contracts-manager/src/index.js +++ b/packages/embark-contracts-manager/src/index.js @@ -107,6 +107,9 @@ class ContractsManager { contract: (callback) => { self.events.request('contracts:contract', req.body.contractName, (contract) => callback(null, contract)); }, + web3: (callback) => { + self.events.request("blockchain:get", web3 => callback(null, web3)); + }, account: (callback) => { self.events.request("blockchain:defaultAccount:get", (account) => callback(null, account)); } @@ -114,14 +117,20 @@ class ContractsManager { if (error) { return res.send({error: error.message}); } - const {account, contract} = result; + const {account, contract, web3} = result; const abi = contract.abiDefinition.find(definition => definition.name === req.body.method); const funcCall = (abi.constant === true || abi.stateMutability === 'view' || abi.stateMutability === 'pure') ? 'call' : 'send'; self.events.request("blockchain:contract:create", {abi: contract.abiDefinition, address: contract.deployedAddress}, async (contractObj) => { try { - const gas = await contractObj.methods[req.body.method].apply(this, req.body.inputs).estimateGas(); - contractObj.methods[req.body.method].apply(this, req.body.inputs)[funcCall]({from: account, gasPrice: req.body.gasPrice, gas: Math.floor(gas)}, (error, result) => { + const value = req.body.value !== undefined ? web3.utils.toWei(req.body.value, 'ether') : 0 + const gas = await contractObj.methods[req.body.method].apply(this, req.body.inputs).estimateGas({ value }); + contractObj.methods[req.body.method].apply(this, req.body.inputs)[funcCall]({ + from: account, + gasPrice: req.body.gasPrice, + gas: Math.floor(gas), + value + }, (error, result) => { const paramString = abi.inputs.map((input, idx) => { const quote = input.type.indexOf("int") === -1 ? '"' : ''; return quote + req.body.inputs[idx] + quote; diff --git a/packages/embark-ui/src/actions/index.js b/packages/embark-ui/src/actions/index.js index 55d80fa2e..90ef730f5 100644 --- a/packages/embark-ui/src/actions/index.js +++ b/packages/embark-ui/src/actions/index.js @@ -214,7 +214,7 @@ export const contractFile = { export const CONTRACT_FUNCTION = createRequestTypes('CONTRACT_FUNCTION'); export const contractFunction = { - post: (contractName, method, inputs, gasPrice) => action(CONTRACT_FUNCTION[REQUEST], {contractName, method, inputs, gasPrice}), + post: (contractName, method, inputs, gasPrice, value) => action(CONTRACT_FUNCTION[REQUEST], {contractName, method, inputs, gasPrice, value}), success: (result, payload) => action(CONTRACT_FUNCTION[SUCCESS], {contractFunctions: [{...result, ...payload}]}), failure: (error) => action(CONTRACT_FUNCTION[FAILURE], {error}) }; diff --git a/packages/embark-ui/src/components/ContractOverview.js b/packages/embark-ui/src/components/ContractOverview.js index ad6302a7a..f65e01dd3 100644 --- a/packages/embark-ui/src/components/ContractOverview.js +++ b/packages/embark-ui/src/components/ContractOverview.js @@ -28,13 +28,23 @@ import "./ContractOverview.scss"; class ContractFunction extends Component { constructor(props) { super(props); - this.state = {inputs: {}, optionsCollapse: false, functionCollapse: false, gasPriceCollapse: false}; + this.state = { + inputs: {}, + optionsCollapse: false, + functionCollapse: false, + gasPriceCollapse: false, + value: 0 + }; } static isPureCall(method) { return (method.stateMutability === 'view' || method.stateMutability === 'pure'); } + static isPayable(method) { + return method.payable; + } + static isEvent(method) { return !this.isPureCall(method) && (method.type === 'event'); } @@ -59,9 +69,13 @@ class ContractFunction extends Component { } handleChange(e, name) { - const newInputs = this.state.inputs; - newInputs[name] = e.target.value; - this.setState({inputs: newInputs}); + if (name === `${this.props.method.name}-value`) { + this.setState({ value: e.target.value }); + } else { + const newInputs = this.state.inputs; + newInputs[name] = e.target.value; + this.setState({inputs: newInputs}); + } } autoSetGasPrice(e) { @@ -78,7 +92,8 @@ class ContractFunction extends Component { this.props.contractName, this.props.method.name, this.inputsAsArray(), - this.state.inputs.gasPrice * 1000000000 + this.state.inputs.gasPrice * 1000000000, + this.state.value ); } @@ -171,12 +186,25 @@ class ContractFunction extends Component { : (ContractFunction.isPureCall(this.props.method) && this.makeBadge('success', 'white', 'call')) || this.makeBadge('warning', 'black', 'send')} + {ContractFunction.isPayable(this.props.method) && + this.makeBadge('light', 'black', 'payable') + } {!ContractFunction.isFallback(this.props.method) && + {ContractFunction.isPayable(this.props.method) && +
+ + this.handleChange(e, this.props.method.name + '-value')} + onKeyPress={(e) => this.handleKeyPress(e)} + /> +
+ }
{this.props.method.inputs.map((input, idx) => ( diff --git a/packages/embark-ui/src/components/ContractOverview.scss b/packages/embark-ui/src/components/ContractOverview.scss index 2a6d6925c..95344d9d7 100644 --- a/packages/embark-ui/src/components/ContractOverview.scss +++ b/packages/embark-ui/src/components/ContractOverview.scss @@ -1,5 +1,6 @@ .contract-function-badge { cursor: default; + margin-left: 1em; code { font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;