1 |
|
2 | 'use strict'
|
3 |
|
4 | var $ = require('jquery')
|
5 | var yo = require('yo-yo')
|
6 | var ethJSUtil = require('ethereumjs-util')
|
7 | var BN = ethJSUtil.BN
|
8 | var helper = require('./lib/helper')
|
9 | var copyToClipboard = require('./app/ui/copy-to-clipboard')
|
10 | var css = require('./universal-dapp-styles')
|
11 | var MultiParamManager = require('./multiParamManager')
|
12 | var remixLib = require('remix-lib')
|
13 | var typeConversion = remixLib.execution.typeConversion
|
14 | var txExecution = remixLib.execution.txExecution
|
15 | var txFormat = remixLib.execution.txFormat
|
16 |
|
17 | var executionContext = require('./execution-context')
|
18 |
|
19 | var confirmDialog = require('./app/execution/confirmDialog')
|
20 | var modalCustom = require('./app/ui/modal-dialog-custom')
|
21 | var modalDialog = require('./app/ui/modaldialog')
|
22 | var TreeView = require('./app/ui/TreeView')
|
23 |
|
24 | function UniversalDAppUI (udapp, registry) {
|
25 | this.udapp = udapp
|
26 | this.registry = registry
|
27 |
|
28 | this.compilerData = {contractsDetails: {}}
|
29 | this._deps = {
|
30 | compilersartefacts: registry.get('compilersartefacts').api
|
31 | }
|
32 | }
|
33 |
|
34 | function decodeResponseToTreeView (response, fnabi) {
|
35 | var treeView = new TreeView({
|
36 | extractData: (item, parent, key) => {
|
37 | var ret = {}
|
38 | if (BN.isBN(item)) {
|
39 | ret.self = item.toString(10)
|
40 | ret.children = []
|
41 | } else {
|
42 | ret = treeView.extractDataDefault(item, parent, key)
|
43 | }
|
44 | return ret
|
45 | }
|
46 | })
|
47 | return treeView.render(txFormat.decodeResponse(response, fnabi))
|
48 | }
|
49 |
|
50 | UniversalDAppUI.prototype.renderInstance = function (contract, address, contractName) {
|
51 | var noInstances = document.querySelector('[class^="noInstancesText"]')
|
52 | if (noInstances) {
|
53 | noInstances.parentNode.removeChild(noInstances)
|
54 | }
|
55 | var abi = this.udapp.getABI(contract)
|
56 | return this.renderInstanceFromABI(abi, address, contractName)
|
57 | }
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 | UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address, contractName) {
|
64 | var self = this
|
65 | address = (address.slice(0, 2) === '0x' ? '' : '0x') + address.toString('hex')
|
66 | var instance = yo`<div class="instance ${css.instance} ${css.hidesub}" id="instance${address}"></div>`
|
67 | var context = self.udapp.context()
|
68 |
|
69 | var shortAddress = helper.shortenAddress(address)
|
70 | var title = yo`
|
71 | <div class="${css.title} alert alert-secondary p-2">
|
72 | <button class="btn ${css.titleExpander}" onclick="${(e) => { toggleClass(e) }}">
|
73 | <i class="fas fa-angle-right" aria-hidden="true"></i>
|
74 | </button>
|
75 | <div class="input-group ${css.nameNbuts}">
|
76 | <div class="${css.titleText} input-group-prepend">
|
77 | <span class="input-group-text ${css.spanTitleText}">
|
78 | ${contractName} at ${shortAddress} (${context})
|
79 | </span>
|
80 | </div>
|
81 | <div class="btn-group">
|
82 | <button class="btn p-1 btn-secondary">${copyToClipboard(() => address)}</button>
|
83 | </div>
|
84 | </div>
|
85 | </div>
|
86 | `
|
87 |
|
88 | var close = yo`
|
89 | <button
|
90 | class="${css.udappClose} p-1 btn btn-secondary"
|
91 | onclick=${remove}
|
92 | title="Remove from the list"
|
93 | >
|
94 | <i class="${css.closeIcon} fas fa-times" aria-hidden="true"></i>
|
95 | </button>`
|
96 | title.querySelector('.btn-group').appendChild(close)
|
97 |
|
98 | var contractActionsWrapper = yo`
|
99 | <div class="${css.cActionsWrapper}">
|
100 | </div>
|
101 | `
|
102 |
|
103 | function remove () {
|
104 | instance.remove()
|
105 |
|
106 | }
|
107 |
|
108 | function toggleClass (e) {
|
109 | $(instance).toggleClass(`${css.hidesub}`)
|
110 |
|
111 | e.currentTarget.querySelector('i').classList.toggle(`fa-angle-right`)
|
112 | e.currentTarget.querySelector('i').classList.toggle(`fa-angle-down`)
|
113 | }
|
114 |
|
115 | instance.appendChild(title)
|
116 | instance.appendChild(contractActionsWrapper)
|
117 |
|
118 |
|
119 | var fallback = self.udapp.getFallbackInterface(contractABI)
|
120 | if (fallback) {
|
121 | contractActionsWrapper.appendChild(this.getCallButton({
|
122 | funABI: fallback,
|
123 | address: address,
|
124 | contractAbi: contractABI,
|
125 | contractName: contractName
|
126 | }))
|
127 | }
|
128 |
|
129 | $.each(contractABI, (i, funABI) => {
|
130 | if (funABI.type !== 'function') {
|
131 | return
|
132 | }
|
133 |
|
134 | contractActionsWrapper.appendChild(this.getCallButton({
|
135 | funABI: funABI,
|
136 | address: address,
|
137 | contractAbi: contractABI,
|
138 | contractName: contractName
|
139 | }))
|
140 | })
|
141 |
|
142 | return instance
|
143 | }
|
144 |
|
145 |
|
146 |
|
147 | UniversalDAppUI.prototype.getCallButton = function (args) {
|
148 | var self = this
|
149 |
|
150 |
|
151 | var lookupOnly = args.funABI.constant
|
152 |
|
153 | var outputOverride = yo`<div class=${css.value}></div>`
|
154 |
|
155 | function clickButton (valArr, inputsValues) {
|
156 | var logMsg
|
157 | if (!args.funABI.constant) {
|
158 | logMsg = `transact to ${args.contractName}.${(args.funABI.name) ? args.funABI.name : '(fallback)'}`
|
159 | } else {
|
160 | logMsg = `call to ${args.contractName}.${(args.funABI.name) ? args.funABI.name : '(fallback)'}`
|
161 | }
|
162 |
|
163 | var value = inputsValues
|
164 |
|
165 | var confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
|
166 | if (network.name !== 'Main') {
|
167 | return continueTxExecution(null)
|
168 | }
|
169 | var amount = executionContext.web3().fromWei(typeConversion.toInt(tx.value), 'ether')
|
170 | var content = confirmDialog(tx, amount, gasEstimation, self.udapp,
|
171 | (gasPrice, cb) => {
|
172 | let txFeeText, priceStatus
|
173 |
|
174 |
|
175 | try {
|
176 | var fee = executionContext.web3().toBigNumber(tx.gas).mul(executionContext.web3().toBigNumber(executionContext.web3().toWei(gasPrice.toString(10), 'gwei')))
|
177 | txFeeText = ' ' + executionContext.web3().fromWei(fee.toString(10), 'ether') + ' Ether'
|
178 | priceStatus = true
|
179 | } catch (e) {
|
180 | txFeeText = ' Please fix this issue before sending any transaction. ' + e.message
|
181 | priceStatus = false
|
182 | }
|
183 | cb(txFeeText, priceStatus)
|
184 | },
|
185 | (cb) => {
|
186 | executionContext.web3().eth.getGasPrice((error, gasPrice) => {
|
187 | var warnMessage = ' Please fix this issue before sending any transaction. '
|
188 | if (error) {
|
189 | return cb('Unable to retrieve the current network gas price.' + warnMessage + error)
|
190 | }
|
191 | try {
|
192 | var gasPriceValue = executionContext.web3().fromWei(gasPrice.toString(10), 'gwei')
|
193 | cb(null, gasPriceValue)
|
194 | } catch (e) {
|
195 | cb(warnMessage + e.message, null, false)
|
196 | }
|
197 | })
|
198 | }
|
199 | )
|
200 | modalDialog('Confirm transaction', content,
|
201 | { label: 'Confirm',
|
202 | fn: () => {
|
203 | self.udapp._deps.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', content.querySelector('input#confirmsetting').checked)
|
204 |
|
205 | if (!content.gasPriceStatus) {
|
206 | cancelCb('Given gas price is not correct')
|
207 | } else {
|
208 | var gasPrice = executionContext.web3().toWei(content.querySelector('#gasprice').value, 'gwei')
|
209 | continueTxExecution(gasPrice)
|
210 | }
|
211 | }}, {
|
212 | label: 'Cancel',
|
213 | fn: () => {
|
214 | return cancelCb('Transaction canceled by user.')
|
215 | }
|
216 | })
|
217 | }
|
218 |
|
219 | var continueCb = (error, continueTxExecution, cancelCb) => {
|
220 | if (error) {
|
221 | var msg = typeof error !== 'string' ? error.message : error
|
222 | modalDialog('Gas estimation failed', yo`<div>Gas estimation errored with the following message (see below).
|
223 | The transaction execution will likely fail. Do you want to force sending? <br>
|
224 | ${msg}
|
225 | </div>`,
|
226 | {
|
227 | label: 'Send Transaction',
|
228 | fn: () => {
|
229 | continueTxExecution()
|
230 | }}, {
|
231 | label: 'Cancel Transaction',
|
232 | fn: () => {
|
233 | cancelCb()
|
234 | }
|
235 | })
|
236 | } else {
|
237 | continueTxExecution()
|
238 | }
|
239 | }
|
240 |
|
241 | var outputCb = (decoded) => {
|
242 | outputOverride.innerHTML = ''
|
243 | outputOverride.appendChild(decoded)
|
244 | }
|
245 |
|
246 | var promptCb = (okCb, cancelCb) => {
|
247 | modalCustom.promptPassphrase('Passphrase requested', 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb)
|
248 | }
|
249 |
|
250 |
|
251 | txFormat.buildData(args.contractName, args.contractAbi, {}, false, args.funABI, args.funABI.type !== 'fallback' ? value : '', (error, data) => {
|
252 | if (!error) {
|
253 | if (!args.funABI.constant) {
|
254 | self.registry.get('logCallback').api(`${logMsg} pending ... `)
|
255 | } else {
|
256 | self.registry.get('logCallback').api(`${logMsg}`)
|
257 | }
|
258 | if (args.funABI.type === 'fallback') data.dataHex = value
|
259 | self.udapp.callFunction(args.address, data, args.funABI, confirmationCb, continueCb, promptCb, (error, txResult) => {
|
260 | if (!error) {
|
261 | var isVM = executionContext.isVM()
|
262 | if (isVM) {
|
263 | var vmError = txExecution.checkVMError(txResult)
|
264 | if (vmError.error) {
|
265 | self.registry.get('logCallback').api(`${logMsg} errored: ${vmError.message} `)
|
266 | return
|
267 | }
|
268 | }
|
269 | if (lookupOnly) {
|
270 | var decoded = decodeResponseToTreeView(executionContext.isVM() ? txResult.result.vm.return : ethJSUtil.toBuffer(txResult.result), args.funABI)
|
271 | outputCb(decoded)
|
272 | }
|
273 | } else {
|
274 | self.registry.get('logCallback').api(`${logMsg} errored: ${error} `)
|
275 | }
|
276 | })
|
277 | } else {
|
278 | self.registry.get('logCallback').api(`${logMsg} errored: ${error} `)
|
279 | }
|
280 | }, (msg) => {
|
281 | self.registry.get('logCallback').api(msg)
|
282 | }, (data, runTxCallback) => {
|
283 |
|
284 | self.udapp.runTx(data, confirmationCb, runTxCallback)
|
285 | })
|
286 | }
|
287 |
|
288 | var multiParamManager = new MultiParamManager(lookupOnly, args.funABI, (valArray, inputsValues, domEl) => {
|
289 | clickButton(valArray, inputsValues, domEl)
|
290 | }, self.udapp.getInputs(args.funABI))
|
291 |
|
292 | var contractActionsContainer = yo`<div class="${css.contractActionsContainer}" >${multiParamManager.render()}</div>`
|
293 | contractActionsContainer.appendChild(outputOverride)
|
294 |
|
295 | return contractActionsContainer
|
296 | }
|
297 |
|
298 | module.exports = UniversalDAppUI
|