diff --git a/package.json b/package.json index 6d49a46..306a85c 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,8 @@ "@omisego/omg-js": "2.0.0-v0.2", "@omisego/omg-js-childchain": "2.0.0-v0.2", "@omisego/omg-js-rootchain": "2.0.0-v0.2", - "@omisego/omg-js-util": "2.0.0-v0.2" + "@omisego/omg-js-util": "2.0.0-v0.2", + "human-standard-token-abi": "2.0.0" }, "devDependencies": { "@babel/cli": "7.2.3", diff --git a/src/index.js b/src/index.js index d4a3d33..8d32ebf 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,3 @@ -/* global web3, ethereum */ import { confirmTransaction, normalizeUrl, @@ -9,6 +8,7 @@ import BigNumber from "bn.js"; import ChildChain from "@omisego/omg-js-childchain"; import RootChain from "@omisego/omg-js-rootchain"; import { transaction } from "@omisego/omg-js-util"; +const erc20abi = require("human-standard-token-abi"); const web3Options = { transactionConfirmationBlocks: 1 }; @@ -17,70 +17,43 @@ export default class BaseEmbarkOmg { this.logger = logger; this.initing = false; this.inited = false; - this.address = ""; - this.addressPrivateKey = ""; + this.account = { + address: "", + rootBalance: 0, + childBalance: 0 + }; + this.account.addressPrivateKey = ""; this.maxDeposit = 0; // plugin opts - this.plasmaContractAddress = - pluginConfig.PLASMA_CONTRACT_ADDRESS || - "0x740ecec4c0ee99c285945de8b44e9f5bfb71eea7"; - this.watcherUrl = normalizeUrl( - pluginConfig.WATCHER_URL || "https://watchersamrong.omg.network/" - ); - this.childChainUrl = normalizeUrl( - pluginConfig.CHILDCHAIN_URL || "https://samrong.omg.network/" - ); - this.childChainExplorerUrl = normalizeUrl( - pluginConfig.CHILDCHAIN_EXPLORER_URL || - "https://quest.samrong.omg.network" - ); - } - - async initWeb3() { - if (window.ethereum) { - this.web3 = new Web3(window.ethereum, null, web3Options); - try { - // Request account access - await ethereum.enable(); - return true; - } catch (err) { - // User denied account access :( - console.error(err); - } - } else if (window.web3) { - this.web3 = new Web3(window.web3.currentProvider, null, web3Options); - return true; - } - // No web3... - return false; + this.plasmaContractAddress = pluginConfig.PLASMA_CONTRACT_ADDRESS || "0x740ecec4c0ee99c285945de8b44e9f5bfb71eea7"; + this.watcherUrl = normalizeUrl(pluginConfig.WATCHER_URL || "https://watcher.samrong.omg.network/"); + this.childChainUrl = normalizeUrl(pluginConfig.CHILDCHAIN_URL || "https://samrong.omg.network/"); + this.childChainExplorerUrl = normalizeUrl(pluginConfig.CHILDCHAIN_EXPLORER_URL || "https://quest.samrong.omg.network"); } async init(web3) { - //}, web3Path) { - try { if (this.initing) { const message = "Already intializing the Plasma chain, please wait..."; throw new Error(message); } this.initing = true; + this.web3 = web3; - // if (!(await this.initWeb3())) { - this.web3 = web3; - // } let accounts = await this.web3.eth.getAccounts(); - this.address = accounts.length > 1 ? accounts[1] : accounts[0]; // ignore the first account because it is our deployer account, we want the manually added account + const address = accounts.length > 1 ? accounts[1] : accounts[0]; // ignore the first account because it is our deployer account, we want the manually added account + this.account.address = address; // check account balance on the main chain // try { - // this.maxDeposit = await this.web3.eth.getBalance(this.address); + // this.maxDeposit = await this.web3.eth.getBalance(this.account.address); // if (!this.maxDeposit || new BigNumber(this.maxDeposit).lte(0)) { // throw new Error("The configured account does not have enough funds. Please make sure this account has Rinkeby ETH."); // } // this.maxDeposit = new BigNumber(this.maxDeposit); // } // catch (e) { - // this.logger.warn(`Error getting balance for account ${this.address}: ${e}`); + // this.logger.warn(`Error getting balance for account ${this.account.address}: ${e}`); // } // set up the Plasma chain @@ -113,26 +86,24 @@ export default class BaseEmbarkOmg { } // if (amount.gt(this.maxDeposit) && this.maxDeposit.gt(0)) { // // recheck balance in case it was updated in a recent tx - // this.maxDeposit = await this.web3.eth.getBalance(this.address); + // this.maxDeposit = await this.web3.eth.getBalance(this.account.address); // if (amount.gt(this.maxDeposit)) { - // const message = `You do not have enough funds for this deposit. Please deposit more funds in to ${this.address} and then try again.`; + // const message = `You do not have enough funds for this deposit. Please deposit more funds in to ${this.account.address} and then try again.`; // throw new Error(message); // } // } // Create the deposit transaction - const depositTx = transaction.encodeDeposit(this.address, amount, currency); + const depositTx = transaction.encodeDeposit(this.account.address, amount, currency); if (currency === transaction.ETH_CURRENCY) { this.logger.info(`Depositing ${amount} wei...`); // ETH deposit try { - const receipt = await this.rootChain.depositEth(depositTx, amount, { - from: this.address - }); + const receipt = await this.rootChain.depositEth(depositTx, amount, { from: this.account.address }); this.logger.trace(receipt); const message = `Successfully deposited ${amount} wei in to the Plasma chain.\nView the transaction: https://rinkeby.etherscan.io/tx/${ receipt.transactionHash - }`; + }`; return message; } catch (e) { const message = `Error depositing ${amount} wei: ${e}`; @@ -150,18 +121,14 @@ export default class BaseEmbarkOmg { const gasPrice = 1000000; const receipt = await erc20.methods .approve(this.rootChain.plasmaContractAddress, amount) - .send({ from: this.address, gasPrice, gas: 2000000 }); + .send({ from: this.account.address, gasPrice, gas: 2000000 }); // Wait for the approve tx to be mined - this.logger.info( - `${amount} erc20 approved: ${ - receipt.transactionHash - }. Waiting for confirmation...` - ); + this.logger.info(`${amount} erc20 approved: ${receipt.transactionHash}. Waiting for confirmation...`); await confirmTransaction(this.web3, receipt.transactionHash); this.logger.info(`... ${receipt.transactionHash} confirmed.`); } - return this.rootChain.depositToken(depositTx, { from: this.address }); + return this.rootChain.depositToken(depositTx, { from: this.account.address }); } async transfer(toAddress, amount) { @@ -169,9 +136,7 @@ export default class BaseEmbarkOmg { const currency = transaction.ETH_CURRENCY; const verifyingContract = this.plasmaContractAddress; - const transferZeroFee = currency !== transaction.ETH_CURRENCY; - const utxos = await this.childChain.getUtxos(this.address); - const utxosToSpend = selectUtxos(utxos, amount, currency, transferZeroFee); + const utxosToSpend = await selectUtxos(amount, currency); if (!utxosToSpend) { throw new Error(`No utxo big enough to cover the amount ${amount}`); } @@ -192,16 +157,16 @@ export default class BaseEmbarkOmg { // Need to add a 'change' output const CHANGE_AMOUNT = bnAmount.sub(new BigNumber(amount)); txBody.outputs.push({ - owner: this.address, + owner: this.account.address, currency, amount: CHANGE_AMOUNT }); } - if (transferZeroFee && utxosToSpend.length > 1) { + if (currency !== transaction.ETH_CURRENCY && utxosToSpend.length > 1) { // The fee input can be returned txBody.outputs.push({ - owner: this.address, + owner: this.account.address, currency: utxosToSpend[utxosToSpend.length - 1].currency, amount: utxosToSpend[utxosToSpend.length - 1].amount }); @@ -217,15 +182,10 @@ export default class BaseEmbarkOmg { // const signature = await signTypedData( this.web3, - this.web3.utils.toChecksumAddress(this.address), + this.web3.utils.toChecksumAddress(this.account.address), JSON.stringify(typedData) ); - // const signer = this.web3.utils.toChecksumAddress(this.address); - // const data = JSON.stringify(typedData); - // const signature = this.web3.currentProvider.send("eth_signTypedData_v3", [ - // signer, - // data - // ]); + const sigs = new Array(utxosToSpend.length).fill(signature); // Build the signed transaction @@ -237,7 +197,7 @@ export default class BaseEmbarkOmg { result )}\nView the transaction: ${this.childChainExplorerUrl}transaction/${ result.txhash - }`; + }`; return message; } @@ -248,8 +208,6 @@ export default class BaseEmbarkOmg { const message = `No UTXOs found on the Plasma chain for ${fromAddress}.`; throw new Error(message); } - // NB This only exits the first UTXO. - // Selecting _which_ UTXO to exit is left as an exercise for the reader... const errors = []; utxos.forEach(async utxo => { const exitData = await this.childChain.getExitData(utxo); @@ -265,9 +223,9 @@ export default class BaseEmbarkOmg { ); return `Exited UTXO from address ${fromAddress} with value ${ utxo.amount - }. View the transaction: https://rinkeby.etherscan.io/tx/${ + }. View the transaction: https://rinkeby.etherscan.io/tx/${ receipt.transactionHash - }`; + }`; } catch (e) { const message = `Error exiting the Plasma chain for UTXO ${JSON.stringify( utxo @@ -280,14 +238,36 @@ export default class BaseEmbarkOmg { } } - selectUtxos(utxos, amount, currency) { - const correctCurrency = utxos.filter(utxo => utxo.currency === currency); - // Just find the first utxo that can fulfill the amount - const selected = correctCurrency.find(utxo => - new BigNumber(utxo.amount).gte(new BigNumber(amount)) - ); - if (selected) { - return [selected]; - } + async selectUtxos(amount, currency) { + const transferZeroFee = currency !== transaction.ETH_CURRENCY; + const utxos = await this.childChain.getUtxos(this.account.address); + return selectUtxos(utxos, amount, currency, transferZeroFee); + } + + async getTransactions() { + return this.childChain.getTransactions({ + address: this.account.address + }); + } + + async getBalances() { + this.account.rootBalance = await this.web3.eth.getBalance(this.account.address); + + const childchainBalance = await this.childChain.getBalance(this.account.address); + this.account.childBalance = await Promise.all(childchainBalance.map( + async (balance) => { + if (balance.currency === transaction.ETH_CURRENCY) { + balance.symbol = 'wei'; + } else { + const tokenContract = new this.web3.eth.Contract(erc20abi, balance.currency); + try { + balance.symbol = await tokenContract.methods.symbol().call(); + } catch (err) { + balance.symbol = 'Unknown ERC20'; + } + } + return balance; + } + )) } } diff --git a/yarn.lock b/yarn.lock index 6a66c73..369773a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1926,6 +1926,11 @@ hosted-git-info@^2.1.4: resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w== +human-standard-token-abi@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/human-standard-token-abi/-/human-standard-token-abi-2.0.0.tgz#e0c2057596d0a1d4a110f91f974a37f4b904f008" + integrity sha512-m1f5DiIvqaNmpgphNqx2OziyTCj4Lvmmk28uMSxGWrOc9/lMpAKH8UcMPhvb13DMNZPzxn07WYFhxOGKuPLryg== + iconv-lite@^0.4.17, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"