Merge branch 'feat/remove-pk-add-metamask'
This commit is contained in:
commit
b5187f4a8e
20
package.json
20
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "embarkjs-omg",
|
||||
"name": "embarkjs-plasma",
|
||||
"version": "1.0.0",
|
||||
"description": "EmbarkJS library for the OmiseGO plugin for Embark",
|
||||
"description": "EmbarkJS library for the OmiseGO Plasma plugin for Embark",
|
||||
"main": "dist/index.js",
|
||||
"browser": {
|
||||
"./dist/index.js": "./dist/browser/index.js",
|
||||
|
@ -13,6 +13,7 @@
|
|||
"> 0.2%"
|
||||
],
|
||||
"files": [
|
||||
"src",
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
|
@ -49,13 +50,12 @@
|
|||
"dependencies": {
|
||||
"@babel/plugin-proposal-optional-chaining": "7.2.0",
|
||||
"@babel/runtime-corejs2": "7.3.1",
|
||||
"@omisego/omg-js": "1.2.2",
|
||||
"@omisego/omg-js-childchain": "1.2.1",
|
||||
"@omisego/omg-js-rootchain": "1.2.2",
|
||||
"@omisego/omg-js-util": "1.2.1",
|
||||
"async": "3.0.1",
|
||||
"axios": "0.19.0",
|
||||
"ethers": "4.0.28"
|
||||
"@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",
|
||||
"human-standard-token-abi": "2.0.0",
|
||||
"web3": "1.0.0-beta.55"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.2.3",
|
||||
|
@ -78,4 +78,4 @@
|
|||
"npm": ">=6.4.1",
|
||||
"yarn": ">=1.12.3"
|
||||
}
|
||||
}
|
||||
}
|
358
src/index.js
358
src/index.js
|
@ -1,129 +1,157 @@
|
|||
/* global Web3 */
|
||||
import {
|
||||
confirmTransaction,
|
||||
normalizeUrl,
|
||||
selectUtxos,
|
||||
signTypedData
|
||||
} from "./utils";
|
||||
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";
|
||||
import Web3 from "web3";
|
||||
import {transaction} from "@omisego/omg-js-util";
|
||||
const ERC20_ABI = require("human-standard-token-abi");
|
||||
|
||||
const ACCOUNT_CONFIG_ERROR = "Blockchain accounts configuration is missing. To use the Embark-OMG plugin, you must configure blockchain accounts to use either a private key file, a private key, or a mnemonic.";
|
||||
const ACCOUNT_BALANCE_ERROR = "The configured account does not have enough funds. Please make sure this account has Rinkeby ETH.";
|
||||
const web3Options = {transactionConfirmationBlocks: 1};
|
||||
|
||||
export default class BaseEmbarkOmg {
|
||||
constructor({ pluginConfig, logger }) {
|
||||
export default class EmbarkJSPlasma {
|
||||
constructor({pluginConfig, logger}) {
|
||||
this.logger = logger;
|
||||
this.initing = false;
|
||||
this.inited = false;
|
||||
this.address = "";
|
||||
this.addressPrivateKey = "";
|
||||
this.currentAddress = "";
|
||||
this.maxDeposit = 0;
|
||||
|
||||
this.state = {
|
||||
account: {
|
||||
address: "",
|
||||
rootBalance: 0,
|
||||
childBalance: 0
|
||||
},
|
||||
transactions: [],
|
||||
utxos: []
|
||||
};
|
||||
this.rootChain = null;
|
||||
this.childChain = null;
|
||||
|
||||
// plugin opts
|
||||
this.plasmaContractAddress = pluginConfig.PLASMA_CONTRACT_ADDRESS;
|
||||
this.web3ProviderUrl = pluginConfig.WEB3_PROVIDER_URL;
|
||||
this.watcherUrl = pluginConfig.WATCHER_URL;
|
||||
this.childChainUrl = pluginConfig.CHILDCHAIN_URL;
|
||||
this.config = {
|
||||
plasmaContractAddress: pluginConfig.PLASMA_CONTRACT_ADDRESS || "0x740ecec4c0ee99c285945de8b44e9f5bfb71eea7",
|
||||
watcherUrl: normalizeUrl(pluginConfig.WATCHER_URL || "https://watcher.samrong.omg.network/"),
|
||||
childChainUrl: normalizeUrl(pluginConfig.CHILDCHAIN_URL || "https://samrong.omg.network/"),
|
||||
childChainExplorerUrl: normalizeUrl(pluginConfig.CHILDCHAIN_EXPLORER_URL || "https://quest.samrong.omg.network")
|
||||
};
|
||||
}
|
||||
async init(accounts, web3Path) {
|
||||
|
||||
async init(web3, useGivenWeb3 = false) {
|
||||
try {
|
||||
if (this.initing) {
|
||||
const message = "Already intializing the Plasma chain, please wait...";
|
||||
this.logger.error(message);
|
||||
throw new Error(message);
|
||||
}
|
||||
this.initing = true;
|
||||
|
||||
if (!(accounts && accounts.length)) {
|
||||
this.logger.error(ACCOUNT_CONFIG_ERROR);
|
||||
throw new Error(ACCOUNT_CONFIG_ERROR);
|
||||
if (useGivenWeb3) {
|
||||
this.web3 = web3;
|
||||
}
|
||||
const { address, privateKey } = accounts[0];
|
||||
this.address = address;
|
||||
this.addressPrivateKey = privateKey;
|
||||
|
||||
|
||||
// init Web3
|
||||
const web3Lib = web3Path ? require(web3Path) : Web3;
|
||||
this.web3 = new web3Lib();
|
||||
const web3Provider = new web3Lib.providers.HttpProvider(this.web3ProviderUrl);
|
||||
this.web3.setProvider(web3Provider);
|
||||
|
||||
// check account balance on the main chain
|
||||
try {
|
||||
this.maxDeposit = await this.web3.eth.getBalance(this.address);
|
||||
if (!this.maxDeposit || new BigNumber(this.maxDeposit).lte(0)) {
|
||||
this.logger.error(ACCOUNT_BALANCE_ERROR);
|
||||
throw new Error(ACCOUNT_BALANCE_ERROR);
|
||||
}
|
||||
this.maxDeposit = new BigNumber(this.maxDeposit);
|
||||
}
|
||||
catch (e) {
|
||||
this.logger.error(`Error getting balance for account ${this.address}: ${e}`);
|
||||
else {
|
||||
this.web3 = new Web3(web3.currentProvider, null, web3Options);
|
||||
}
|
||||
|
||||
// set up the Plasma chain
|
||||
this.rootChain = new RootChain(this.web3, this.plasmaContractAddress);
|
||||
this.childChain = new ChildChain(this.watcherUrl, this.childChainUrl);
|
||||
this.rootChain = new RootChain(this.web3, this.config.plasmaContractAddress);
|
||||
this.childChain = new ChildChain(this.config.watcherUrl); //, this.config.childChainUrl);
|
||||
|
||||
let accounts = await this.web3.eth.getAccounts();
|
||||
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.currentAddress = address;
|
||||
|
||||
// check account balance on the main chain
|
||||
// try {
|
||||
// this.maxDeposit = await this.web3.eth.getBalance(this.currentAddress);
|
||||
// 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.currentAddress}: ${e}`);
|
||||
// }
|
||||
|
||||
// set lifecycle state vars
|
||||
this.initing = false;
|
||||
this.inited = true;
|
||||
}
|
||||
catch (e) {
|
||||
|
||||
await this.updateState();
|
||||
} catch (e) {
|
||||
const message = `Error initializing Plasma chain: ${e}`;
|
||||
this.logger.error(message);
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
async deposit(amount) {
|
||||
async deposit(amount, currency = transaction.ETH_CURRENCY, approveDeposit = false) {
|
||||
|
||||
if (!this.inited) {
|
||||
const message = "Please wait for the Plasma chain to initialize...";
|
||||
this.logger.error(message);
|
||||
throw new Error(message);
|
||||
}
|
||||
amount = new BigNumber(amount);
|
||||
if (!amount || amount.lte(0)) {
|
||||
const message = "You must deposit more than 0 wei.";
|
||||
this.logger.error(message);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (amount.gt(this.maxDeposit)) {
|
||||
// recheck balance in case it was updated in a recent tx
|
||||
this.maxDeposit = await this.web3.eth.getBalance(this.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.`;
|
||||
this.logger.error(message);
|
||||
// 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.currentAddress);
|
||||
// if (amount.gt(this.maxDeposit)) {
|
||||
// const message = `You do not have enough funds for this deposit. Please deposit more funds in to ${this.currentAddress} and then try again.`;
|
||||
// throw new Error(message);
|
||||
// }
|
||||
// }
|
||||
// Create the deposit transaction
|
||||
const depositTx = transaction.encodeDeposit(this.currentAddress, 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.currentAddress});
|
||||
this.logger.trace(receipt);
|
||||
const message = `Successfully deposited ${amount} ${currency === transaction.ETH_CURRENCY ? "wei" : currency} 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}`;
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
// const DEPOSIT_AMT = "100000";
|
||||
this.logger.info(`Depositing ${amount} wei...`);
|
||||
const depositTx = transaction.encodeDeposit(this.address, amount, transaction.ETH_CURRENCY);
|
||||
try {
|
||||
const receipt = await this.rootChain.depositEth(depositTx, amount, { from: this.address, privateKey: this.addressPrivateKey });
|
||||
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}`;
|
||||
this.logger.info(message);
|
||||
return message;
|
||||
}
|
||||
catch (e) {
|
||||
const message = `Error depositing ${amount} wei: ${e}`;
|
||||
this.logger.error(message);
|
||||
throw new Error(message);
|
||||
|
||||
// ERC20 token deposit
|
||||
if (approveDeposit) {
|
||||
// First approve the plasma contract on the erc20 contract
|
||||
const erc20 = new this.web3.eth.Contract(ERC20_ABI, currency);
|
||||
// const approvePromise = Promise.promisify(erc20.approve.sendTransaction)
|
||||
|
||||
// TODO
|
||||
const gasPrice = 1000000;
|
||||
const receipt = await erc20.methods
|
||||
.approve(this.rootChain.plasmaContractAddress, amount)
|
||||
.send({from: this.currentAddress, gasPrice, gas: 2000000});
|
||||
// Wait for the approve tx to be mined
|
||||
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.currentAddress});
|
||||
}
|
||||
|
||||
async send(toAddress, val) {
|
||||
//const val = "555";
|
||||
// const toAddress = "0x38d5beb778b6e62d82e3ba4633e08987e6d0f990";
|
||||
const utxos = await this.childChain.getUtxos(this.address);
|
||||
const utxosToSpend = this.selectUtxos(utxos, val, transaction.ETH_CURRENCY);
|
||||
if (!utxosToSpend) {
|
||||
return this.logger.error(`No utxo big enough to cover the amount ${val}`);
|
||||
async transfer(toAddress, amount, currency = transaction.ETH_CURRENCY) {
|
||||
if (!this.inited) {
|
||||
const message = "Please wait for the Plasma chain to initialize...";
|
||||
throw new Error(message);
|
||||
}
|
||||
val = new BigNumber(val);
|
||||
if (!val || val.lte(0)) {
|
||||
return this.logger.error("Transaction value must be more than 0 wei.");
|
||||
const verifyingContract = this.config.plasmaContractAddress;
|
||||
|
||||
const utxosToSpend = await this.selectUtxos(amount, currency);
|
||||
if (!utxosToSpend) {
|
||||
throw new Error(`No utxo big enough to cover the amount ${amount}`);
|
||||
}
|
||||
|
||||
const txBody = {
|
||||
|
@ -131,59 +159,80 @@ export default class BaseEmbarkOmg {
|
|||
outputs: [
|
||||
{
|
||||
owner: toAddress,
|
||||
currency: transaction.ETH_CURRENCY,
|
||||
amount: val
|
||||
currency,
|
||||
amount: amount.toString()
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const utxoAmnt = new BigNumber(utxosToSpend[0].amount);
|
||||
if (utxoAmnt.gt(val)) {
|
||||
// specify the change amount back to yourself
|
||||
const changeAmnt = utxoAmnt.sub(val);
|
||||
const bnAmount = new BigNumber(utxosToSpend[0].amount);
|
||||
if (bnAmount.gt(new BigNumber(amount))) {
|
||||
// Need to add a 'change' output
|
||||
const CHANGE_AMOUNT = bnAmount.sub(new BigNumber(amount));
|
||||
txBody.outputs.push({
|
||||
owner: this.address,
|
||||
currency: transaction.ETH_CURRENCY,
|
||||
amount: changeAmnt
|
||||
owner: this.currentAddress,
|
||||
currency,
|
||||
amount: CHANGE_AMOUNT
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const unsignedTx = await this.childChain.createTransaction(txBody);
|
||||
|
||||
const signatures = await this.childChain.signTransaction(unsignedTx, [this.addressPrivateKey]);
|
||||
|
||||
const signedTx = await this.childChain.buildSignedTransaction(unsignedTx, signatures);
|
||||
|
||||
const result = await this.childChain.submitTransaction(signedTx);
|
||||
|
||||
const message = `Successfully submitted tx on the child chain: ${JSON.stringify(result)}\nView the transaction: http://quest.ari.omg.network/transaction/${result.txhash}`;
|
||||
|
||||
this.logger.info(message);
|
||||
return message;
|
||||
}
|
||||
catch (e) {
|
||||
this.logger.error(e);
|
||||
throw e;
|
||||
if (currency !== transaction.ETH_CURRENCY && utxosToSpend.length > 1) {
|
||||
// The fee input can be returned
|
||||
txBody.outputs.push({
|
||||
owner: this.currentAddress,
|
||||
currency: utxosToSpend[utxosToSpend.length - 1].currency,
|
||||
amount: utxosToSpend[utxosToSpend.length - 1].amount
|
||||
});
|
||||
}
|
||||
|
||||
// Get the transaction data
|
||||
const typedData = transaction.getTypedData(txBody, verifyingContract);
|
||||
|
||||
// We should really sign each input separately but in this we know that they're all
|
||||
// from the same address, so we can sign once and use that signature for each input.
|
||||
//
|
||||
// const sigs = await Promise.all(utxosToSpend.map(input => signTypedData(web3, web3.utils.toChecksumAddress(from), typedData)))
|
||||
//
|
||||
const signature = await signTypedData(
|
||||
this.web3,
|
||||
this.web3.utils.toChecksumAddress(this.currentAddress),
|
||||
JSON.stringify(typedData)
|
||||
);
|
||||
|
||||
const sigs = new Array(utxosToSpend.length).fill(signature);
|
||||
|
||||
// Build the signed transaction
|
||||
const signedTx = this.childChain.buildSignedTransaction(typedData, sigs);
|
||||
// Submit the signed transaction to the childchain
|
||||
const result = await this.childChain.submitTransaction(signedTx);
|
||||
|
||||
const message = `Successfully submitted tx on the child chain: ${JSON.stringify(
|
||||
result
|
||||
)}\nView the transaction: ${this.config.childChainExplorerUrl}transaction/${
|
||||
result.txhash
|
||||
}`;
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
async exit(fromAddress) {
|
||||
async exitAllUtxos(fromAddress) {
|
||||
if (!this.inited) {
|
||||
const message = "Please wait for the Plasma chain to initialize...";
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const utxos = await this.childChain.getUtxos(fromAddress);
|
||||
if (utxos.length <= 0) {
|
||||
const message = `No UTXOs found on the Plasma chain for ${fromAddress}.`;
|
||||
this.logger.error(message);
|
||||
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) => {
|
||||
utxos.forEach(async utxo => {
|
||||
const exitData = await this.childChain.getExitData(utxo);
|
||||
|
||||
try {
|
||||
let receipt = await this.rootChain.startStandardExit(
|
||||
exitData.utxo_pos.toString(),
|
||||
Number(exitData.utxo_pos.toString()),
|
||||
exitData.txbytes,
|
||||
exitData.proof,
|
||||
{
|
||||
|
@ -192,13 +241,15 @@ export default class BaseEmbarkOmg {
|
|||
|
||||
}
|
||||
);
|
||||
const message = `Exited UTXO from address ${fromAddress} with value ${utxo.amount}. View the transaction: https://rinkeby.etherscan.io/tx/${receipt.transactionHash}`;
|
||||
this.logger.info(message);
|
||||
return message;
|
||||
}
|
||||
catch (e) {
|
||||
const message = `Error exiting the Plasma chain for UTXO ${JSON.stringify(utxo)}: ${e}`;
|
||||
this.logger.error(message);
|
||||
return `Exited UTXO from address ${fromAddress} with value ${
|
||||
utxo.amount
|
||||
}. View the transaction: https://rinkeby.etherscan.io/tx/${
|
||||
receipt.transactionHash
|
||||
}`;
|
||||
} catch (e) {
|
||||
const message = `Error exiting the Plasma chain for UTXO ${JSON.stringify(
|
||||
utxo
|
||||
)}: ${e}`;
|
||||
errors.push(message);
|
||||
}
|
||||
});
|
||||
|
@ -207,12 +258,71 @@ 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 exitUtxo(from, utxoToExit) {
|
||||
if (!this.inited) {
|
||||
const message = "Please wait for the Plasma chain to initialize...";
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const exitData = await this.childChain.getExitData(utxoToExit);
|
||||
|
||||
return this.rootChain.startStandardExit(
|
||||
Number(exitData.utxo_pos.toString()),
|
||||
exitData.txbytes,
|
||||
exitData.proof,
|
||||
{from}
|
||||
);
|
||||
}
|
||||
|
||||
async selectUtxos(amount, currency) {
|
||||
const transferZeroFee = currency !== transaction.ETH_CURRENCY;
|
||||
const utxos = await this.childChain.getUtxos(this.currentAddress);
|
||||
return selectUtxos(utxos, amount, currency, transferZeroFee);
|
||||
}
|
||||
|
||||
async balances() {
|
||||
if (!this.inited) {
|
||||
const message = "Please wait for the Plasma chain to initialize...";
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const rootBalance = await this.web3.eth.getBalance(this.currentAddress);
|
||||
|
||||
const childchainBalances = await this.childChain.getBalance(this.currentAddress);
|
||||
const childBalances = await Promise.all(childchainBalances.map(
|
||||
async (balance) => {
|
||||
if (balance.currency === transaction.ETH_CURRENCY) {
|
||||
balance.symbol = 'wei';
|
||||
} else {
|
||||
const tokenContract = new this.web3.eth.Contract(ERC20_ABI, balance.currency);
|
||||
try {
|
||||
balance.symbol = await tokenContract.methods.symbol().call();
|
||||
} catch (err) {
|
||||
balance.symbol = 'Unknown ERC20';
|
||||
}
|
||||
}
|
||||
return balance;
|
||||
}
|
||||
));
|
||||
return {
|
||||
rootBalance,
|
||||
childBalances
|
||||
};
|
||||
}
|
||||
|
||||
async updateState() {
|
||||
if (!this.inited) {
|
||||
const message = "Please wait for the Plasma chain to initialize...";
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const {rootBalance, childBalances} = await this.balances();
|
||||
this.state.account.address = this.currentAddress;
|
||||
this.state.account.rootBalance = rootBalance;
|
||||
this.state.account.childBalances = childBalances;
|
||||
|
||||
this.state.transactions = await this.childChain.getTransactions({address: this.currentAddress});
|
||||
|
||||
this.state.utxos = await this.childChain.getUtxos(this.currentAddress);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
import BigNumber from "bn.js";
|
||||
import { transaction } from "@omisego/omg-js-util";
|
||||
|
||||
const DEFAULT_INTERVAL = 1000;
|
||||
const DEFAULT_BLOCKS_TO_WAIT = 13;
|
||||
|
||||
export function confirmTransaction(web3, txnHash, options) {
|
||||
const interval = options && options.interval ? options.interval : DEFAULT_INTERVAL;
|
||||
const blocksToWait = options && options.blocksToWait ? options.blocksToWait : DEFAULT_BLOCKS_TO_WAIT;
|
||||
const transactionReceiptAsync = async function (txnHash, resolve, reject) {
|
||||
try {
|
||||
const receipt = await web3.eth.getTransactionReceipt(txnHash);
|
||||
if (!receipt) {
|
||||
return setTimeout(function () {
|
||||
transactionReceiptAsync(txnHash, resolve, reject);
|
||||
}, interval);
|
||||
}
|
||||
if (blocksToWait > 0) {
|
||||
const resolvedReceipt = await receipt;
|
||||
if (!resolvedReceipt || !resolvedReceipt.blockNumber) {
|
||||
return setTimeout(function () {
|
||||
transactionReceiptAsync(txnHash, resolve, reject);
|
||||
}, interval);
|
||||
}
|
||||
try {
|
||||
const block = await web3.eth.getBlock(resolvedReceipt.blockNumber);
|
||||
const current = await web3.eth.getBlock('latest');
|
||||
if (current.number - block.number >= blocksToWait) {
|
||||
const txn = await web3.eth.getTransaction(txnHash);
|
||||
// eslint-disable-next-line max-depth
|
||||
if (txn.blockNumber !== null) {
|
||||
return resolve(resolvedReceipt);
|
||||
}
|
||||
return reject(new Error('Transaction with hash: ' + txnHash + ' ended up in an uncle block.'));
|
||||
}
|
||||
return setTimeout(function () {
|
||||
transactionReceiptAsync(txnHash, resolve, reject);
|
||||
}, interval);
|
||||
} catch (e) {
|
||||
setTimeout(function () {
|
||||
transactionReceiptAsync(txnHash, resolve, reject);
|
||||
}, interval);
|
||||
}
|
||||
} else resolve(receipt);
|
||||
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
if (Array.isArray(txnHash)) {
|
||||
const promises = [];
|
||||
txnHash.forEach(function (oneTxHash) {
|
||||
promises.push(confirmTransaction(web3, oneTxHash, options));
|
||||
});
|
||||
return Promise.all(promises);
|
||||
}
|
||||
return new Promise(function (resolve, reject) {
|
||||
transactionReceiptAsync(txnHash, resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
export function selectUtxos(utxos, amount, currency, includeFee) {
|
||||
// Filter by desired currency and sort in descending order
|
||||
const sorted = utxos
|
||||
.filter(utxo => utxo.currency === currency)
|
||||
.sort((a, b) => new BigNumber(b.amount).sub(new BigNumber(a.amount)));
|
||||
|
||||
if (sorted) {
|
||||
const selected = [];
|
||||
let currentBalance = new BigNumber(0);
|
||||
for (let i = 0; i < Math.min(sorted.length, 4); i++) {
|
||||
selected.push(sorted[i]);
|
||||
currentBalance.iadd(new BigNumber(sorted[i].amount));
|
||||
if (currentBalance.gte(new BigNumber(amount))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentBalance.gte(new BigNumber(amount))) {
|
||||
if (includeFee) {
|
||||
// Find the first ETH utxo (that's not selected)
|
||||
const ethUtxos = utxos.filter(
|
||||
utxo => utxo.currency === transaction.ETH_CURRENCY
|
||||
);
|
||||
const feeUtxo = ethUtxos.find(utxo => utxo !== selected);
|
||||
if (!feeUtxo) {
|
||||
throw new Error(`Can't find a fee utxo for transaction`);
|
||||
} else {
|
||||
selected.push(feeUtxo);
|
||||
}
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function signTypedData(web3, signer, data) {
|
||||
return web3.currentProvider.send('eth_signTypedData_v3', [signer, data]);
|
||||
}
|
||||
|
||||
export function normalizeUrl(url) {
|
||||
if (!url.endsWith("/")) {
|
||||
url += "/";
|
||||
}
|
||||
return url;
|
||||
}
|
Loading…
Reference in New Issue