@bzxnetwork/portal
Version:
Frontend demo portal for bZx
850 lines (785 loc) • 24.7 kB
JSX
import moment from "moment";
import styled from "styled-components";
import { BZxJS } from "bzx.js"; // eslint-disable-line
import { getTrackedTokens } from "../../common/trackedTokens";
import { getTokenBalance, getSymbol, getDecimals } from "../../common/tokens";
import { toBigNumber, fromBigNumber } from "../../common/utils";
const TxHashLink = styled.a.attrs({
target: `_blank`,
rel: `noopener noreferrer`
})`
font-family: monospace;
display: block;
text-overflow: ellipsis;
overflow: auto;
}
`;
export const getOrderHash = order => BZxJS.getLoanOrderHashHex(order);
const checkAllowance = async (bZx, accounts, tokenAddress) => {
const allowance = await bZx.getAllowance({
tokenAddress,
ownerAddress: accounts[0].toLowerCase()
});
return allowance.toNumber() !== 0;
};
const silentAllowance = async (bZx, accounts, tokenAddress) => {
const txOpts = {
from: accounts[0],
// gas: 1000000,
gasPrice: window.defaultGasPrice.toString()
};
const txObj = await bZx.setAllowanceUnlimited({
tokenAddress: tokenAddress,
ownerAddress: accounts[0].toLowerCase(),
getObject: true,
txOpts
});
try {
let gas = await txObj.estimateGas(txOpts);
console.log(gas);
txOpts.gas = window.gasValue(gas);
await txObj.send(txOpts);
return true;
} catch (error) {
console.error(error.message);
return false;
}
}
// TODO: Verify sufficient BZRX token balance to pay fees
const checkRelaysFees = async (
bZx,
accounts,
tokens,
order
) => {
if (order.feeRecipientAddress === `0x0000000000000000000000000000000000000000`) {
return true;
}
const makerRole = order.makerRole === `0` ? `lender` : `trader`;
const feeToken = tokens.filter(
t => t.symbol === `BZRX`
)[0];
if ((makerRole === `lender` && order.traderRelayFee !== `0`) || (makerRole === `trader` && order.lenderRelayFee !== `0`)) {
const a = await checkAllowance(bZx, accounts, feeToken.address);
if (!a) {
return (await silentAllowance(bZx, accounts, feeToken.address));
} else {
return true;
}
}
}
const checkCoinsApproved = async (
bZx,
accounts,
order,
collateralTokenAddress
) => {
const makerRole = order.makerRole === `0` ? `lender` : `trader`;
if (makerRole === `lender`) {
// check that user has approved collateralToken and interestToken
const { interestTokenAddress } = order;
let a = await checkAllowance(bZx, accounts, interestTokenAddress);
if (!a) {
a = await silentAllowance(bZx, accounts, interestTokenAddress);
}
let b = await checkAllowance(bZx, accounts, collateralTokenAddress);
if (!b) {
b = await silentAllowance(bZx, accounts, collateralTokenAddress);
}
return a && b;
} else {
// check that user has approved loanToken
const { loanTokenAddress } = order;
const a = await checkAllowance(bZx, accounts, loanTokenAddress);
if (!a) {
return (await silentAllowance(bZx, accounts, loanTokenAddress));
} else {
return true;
}
}
};
const getNetwork = networkId => {
if (!networkId) {
throw new Error(`networkId missed!`);
}
let networkName;
switch (networkId) {
case 1: {
networkName = `Mainnet`;
break;
}
case 3: {
networkName = `Ropsten Testnet`;
break;
}
case 4: {
networkName = `Rinkeby Testnet`;
break;
}
case 42: {
networkName = `Kovan Testnet`;
break;
}
default: {
networkName = `a network with id ${networkId}`;
break;
}
}
return networkName;
};
const getTotalInterest = order => {
let totalInterest = 0;
try {
if (order.interestAmount && order.expirationUnixTimestampSec) {
const exp = order.expirationUnixTimestampSec;
const now = moment().unix();
if (exp > now) {
totalInterest = ((exp - now) / 86400) * order.interestAmount;
}
}
} catch (e) {} // eslint-disable-line no-empty
return totalInterest;
};
export const validateFillOrder = async (
order,
fillOrderAmount,
loanTokenAvailable,
collateralTokenAddress,
collateralTokenAmount,
tokens,
oracles,
bZx,
accounts
) => {
try {
if (order.networkId !== bZx.networkId) {
alert(
`This order was created for ${getNetwork(
order.networkId
)}. It can only be filled on that network.`
);
return false;
}
if (order.makerAddress.toLowerCase() === accounts[0].toLowerCase()) {
alert(`This is an order you created, so you can't fill it.`);
return false;
}
if (order.takerAddress !== `0x0000000000000000000000000000000000000000` && order.takerAddress.toLowerCase() !== accounts[0].toLowerCase()) {
alert(`You are not the designated taker of this order, so you can't fill it.`);
return false;
}
if (order.expirationUnixTimestampSec > 0 && order.expirationUnixTimestampSec <= moment().unix()) {
alert(`This order has expired. It can no longer be filled.`);
return false;
}
/*const oracle = oracles.filter(o => o.address === order.oracleAddress)[0];
if (!oracle) {
alert(
`The oracle of this order is no longer active. Please try a different order.`
);
return false;
}*/
const makerRole = order.makerRole === `0` ? `lender` : `trader`;
const trackedTokens = getTrackedTokens(tokens);
if (makerRole === `lender`) {
loanTokenAvailable = toBigNumber(loanTokenAvailable); // eslint-disable-line no-param-reassign
fillOrderAmount = toBigNumber(fillOrderAmount); // eslint-disable-line no-param-reassign
if (!loanTokenAvailable) {
alert(
`This order is completely filled. There is no loan token remaining.`
);
return false;
}
if (!fillOrderAmount) {
alert(`Please enter the amount of loan token you want to borrow.`);
return false;
}
const loanTokenMultiplier =
10 ** getDecimals(tokens, order.loanTokenAddress);
if (
toBigNumber(fillOrderAmount, loanTokenMultiplier).gt(loanTokenAvailable)
) {
alert(
`You can't borrow more than ${fromBigNumber(
loanTokenAvailable,
loanTokenMultiplier
)} ${getSymbol(
tokens,
order.loanTokenAddress
)}. Please enter a lessor amount.`
);
return false;
}
// make sure the collateral token chosen is allowed
const collateralToken = tokens.filter(
t => t.address === collateralTokenAddress
)[0];
const notAllowed = {
1: [`BZRX`, `BZRXFAKE`],
3: [`BZRX`, `BZRXFAKE`],
4: [`BZRX`],
42: [`BZRX`],
50: [`BZRX`]
};
// early return if there is no restricted list for this network
if (
notAllowed[bZx.networkId] === undefined ||
notAllowed[bZx.networkId] === []
)
return true;
const collateralTokenNotAllowed = notAllowed[bZx.networkId].includes(
collateralToken && collateralToken.symbol
);
if (collateralTokenNotAllowed) {
alert(
`Token ${collateralToken.symbol} is not yet supported as collateral.`
);
return false;
}
// check that user has tracked collateralToken and interestToken
const { interestTokenAddress } = order;
/*if (
!trackedTokens.includes(interestTokenAddress) ||
!trackedTokens.includes(collateralTokenAddress)
) {
alert(
`Your interest token or collateral token is not tracked. Please go to the Balances page to make sure these tokens are added and approved.`
);
return false;
}*/
const collateralTokenBalance = await getTokenBalance(
bZx,
collateralTokenAddress,
accounts
);
// console.log("collateralTokenAmount: "+collateralTokenAmount);
// console.log("collateralTokenBalance: "+collateralTokenBalance);
if (Number.isNaN(collateralTokenAmount)) {
alert(
`We are unable to calculate your required collateral at this time. Please try again later to fill this order.`
);
return false;
}
if (
toBigNumber(
collateralTokenAmount,
10 ** getDecimals(tokens, collateralTokenAddress)
).gt(collateralTokenBalance)
) {
alert(
`Your collateral token balance is too low. You need at least ${collateralTokenAmount} ${getSymbol(
tokens,
collateralTokenAddress
)} to fill this order.`
);
return false;
}
const interestTokenBalance = await getTokenBalance(
bZx,
interestTokenAddress,
accounts
);
const interestTotalAmount = getTotalInterest(order);
// console.log("interestTotalAmount: "+interestTotalAmount);
// console.log("interestTokenBalance: "+interestTokenBalance);
if (toBigNumber(interestTotalAmount).gt(interestTokenBalance)) {
alert(
`Your interest token balance is too low. You need at least ${fromBigNumber(
interestTotalAmount,
10 ** getDecimals(tokens, interestTokenAddress)
)} ${getSymbol(tokens, interestTokenAddress)} to fill this order.`
);
return false;
}
} else {
// check that user has tracked loanToken
const { loanTokenAddress, loanTokenAmount } = order;
/*if (!trackedTokens.includes(loanTokenAddress)) {
alert(
`Your loan token is not tracked. Please go to the Balances page to make sure this token is added and approved.`
);
return false;
}*/
const loanTokenBalance = await getTokenBalance(
bZx,
loanTokenAddress,
accounts
);
// console.log("loanTokenAmount: "+loanTokenAmount);
// console.log("loanTokenBalance: "+loanTokenBalance);
if (toBigNumber(loanTokenAmount).gt(loanTokenBalance)) {
alert(
`Your loan token balance is too low. You need at least ${fromBigNumber(
loanTokenAmount,
10 ** getDecimals(tokens, loanTokenAddress)
)} ${getSymbol(tokens, loanTokenAddress)} to fill this order.`
);
return false;
}
}
const coinsApproved = await checkCoinsApproved(
bZx,
accounts,
order,
collateralTokenAddress
);
if (!coinsApproved) {
alert(
`Some of your tokens are not approved. Please go to the Balances page and approve these tokens.`
);
return false;
}
const relayFeesOk = await checkRelaysFees(bZx, accounts, tokens, order);
if (!relayFeesOk) {
alert(
`Please ensure you have enough BZRX to pay relay fees.`
);
return false;
}
console.log(`validateFillOrder`);
console.log(order, fillOrderAmount, collateralTokenAddress);
return true;
} catch (e) {
console.error(e);
alert(
`The JSON order has one or more invalid or missing parameters. It can't be filled.`
);
return false;
}
};
export const validateCancelOrder = async (
order,
cancelOrderAmount,
loanTokenAvailable,
tokens,
bZx,
accounts
) => {
try {
if (order.networkId !== bZx.networkId) {
alert(
`This order was created for ${getNetwork(
order.networkId
)}. It can only be filled on that network.`
);
return false;
}
if (order.makerAddress.toLowerCase() !== accounts[0].toLowerCase()) {
alert(`This is not an order you created, so you can't cancel it.`);
return false;
}
loanTokenAvailable = toBigNumber(loanTokenAvailable); // eslint-disable-line no-param-reassign
cancelOrderAmount = toBigNumber(cancelOrderAmount); // eslint-disable-line no-param-reassign
if (!loanTokenAvailable) {
alert(
`This order is completely filled. There is no loan token remaining to cancel.`
);
return false;
}
if (!cancelOrderAmount) {
alert(
`Please enter the amount of loan token you want to cancel for this order.`
);
return false;
}
const loanTokenMultiplier =
10 ** getDecimals(tokens, order.loanTokenAddress);
if (
toBigNumber(cancelOrderAmount, loanTokenMultiplier).gt(loanTokenAvailable)
) {
alert(
`You can't cancel more than ${fromBigNumber(
loanTokenAvailable,
loanTokenMultiplier
)} ${getSymbol(
tokens,
order.loanTokenAddress
)}. Please enter a lessor amount.`
);
return false;
}
console.log(`validateCancelOrder`);
//console.log(order, cancelOrderAmount);
return true;
} catch (e) {
console.error(e);
alert(
`The JSON order has one or more invalid or missing parameters. It can't be filled.`
);
return false;
}
};
export const submitFillOrder = (
order,
loanTokenAmountFilled,
collateralTokenAddress,
overCollateralize,
tokens,
web3,
bZx,
accounts,
resetOrder
) => {
const txOpts = {
from: accounts[0],
gas: 2000000,
gasPrice: window.defaultGasPrice.toString()
};
const makerIsLender = order.makerRole === `0`;
// console.log(`order`, order);
// console.log(`txOpts`, txOpts);
// console.log(`makerIsLender`, makerIsLender);
if (bZx.portalProviderName !== `MetaMask`) {
alert(`Please confirm this transaction on your device.`);
}
let txObj;
if (makerIsLender) {
if (overCollateralize) {
txObj = bZx.takeLoanOrderAsTrader({
order,
collateralTokenAddress,
loanTokenAmountFilled: toBigNumber(
loanTokenAmountFilled,
10 ** getDecimals(tokens, order.loanTokenAddress)
),
withdrawOnOpen: true,
getObject: true
});
} else {
txObj = bZx.takeLoanOrderAsTrader({
order,
collateralTokenAddress,
loanTokenAmountFilled: toBigNumber(
loanTokenAmountFilled,
10 ** getDecimals(tokens, order.loanTokenAddress)
),
withdrawOnOpen: false,
getObject: true
});
}
} else {
txObj = bZx.takeLoanOrderAsLender({
order,
getObject: true
});
}
console.log(txOpts);
try {
txObj
.estimateGas(txOpts)
.then(gas => {
console.log(gas);
txOpts.gas = window.gasValue(gas);
txObj
.send(txOpts)
.once(`transactionHash`, hash => {
alert(`Transaction submitted, transaction hash:`, {
component: () => (
<TxHashLink href={`${bZx.etherscanURL}tx/${hash}`}>
{hash}
</TxHashLink>
)
});
})
.then(() => {
console.log();
resetOrder();
alert(`Your loan has been opened.`);
this.setState({ isSubmitted: false });
})
.catch(error => {
console.error(error.message);
if (
error.message.includes(`denied transaction signature`) ||
error.message.includes(`Condition of use not satisfied`) ||
error.message.includes(`Invalid status`)
) {
alert();
}
this.setState({ isSubmitted: false });
});
})
.catch(error => {
console.error(error);
alert(
`The transaction is failing. This loan cannot be opened at this time. Please check the parameters of the order.`
);
this.setState({ isSubmitted: false });
});
} catch (error) {
console.error(error);
alert(
`The transaction is failing. This loan cannot be opened at this time. Please check the parameters of the order.`
);
this.setState({ isSubmitted: false });
}
return true;
};
export const submitFillOrderWithHash = (
loanOrderHash,
makerIsLender,
loanTokenAddress,
loanTokenAmountFilled,
collateralTokenAddress,
overCollateralize,
tokens,
web3,
bZx,
accounts,
changeOrderTab
) => {
const txOpts = {
from: accounts[0],
// gas: 1000000, // gas estimated in bZx.js
gasPrice: window.defaultGasPrice.toString()
};
// console.log(`loanOrderHash`, loanOrderHash);
// console.log(`txOpts`, txOpts);
// console.log(`makerIsLender`, makerIsLender);
if (bZx.portalProviderName !== `MetaMask`) {
alert(`Please confirm this transaction on your device.`);
}
let txObj;
if (makerIsLender) {
if (overCollateralize) {
txObj = bZx.takeLoanOrderOnChainAsTrader({
loanOrderHash,
collateralTokenAddress,
loanTokenAmountFilled: toBigNumber(
loanTokenAmountFilled,
10 ** getDecimals(tokens, loanTokenAddress)
),
withdrawOnOpen: true,
getObject: true
});
} else {
txObj = bZx.takeLoanOrderOnChainAsTrader({
loanOrderHash,
collateralTokenAddress,
loanTokenAmountFilled: toBigNumber(
loanTokenAmountFilled,
10 ** getDecimals(tokens, loanTokenAddress)
),
withdrawOnOpen: false,
getObject: true
});
}
} else {
txObj = bZx.takeLoanOrderOnChainAsLender({
loanOrderHash,
getObject: true
});
}
console.log(txOpts);
try {
txObj
.estimateGas(txOpts)
.then(gas => {
console.log(gas);
txOpts.gas = window.gasValue(gas);
txObj
.send(txOpts)
.once(`transactionHash`, hash => {
alert(`Transaction submitted, transaction hash:`, {
component: () => (
<TxHashLink href={`${bZx.etherscanURL}tx/${hash}`}>
{hash}
</TxHashLink>
)
});
})
.then(() => {
console.log();
changeOrderTab(`Orders_OrderBook`);
alert(`Your loan has been opened.`);
this.setState({ isSubmitted: false });
})
.catch(error => {
console.error(error.message);
if (
error.message.includes(`denied transaction signature`) ||
error.message.includes(`Condition of use not satisfied`) ||
error.message.includes(`Invalid status`)
) {
alert();
}
this.setState({ isSubmitted: false });
});
})
.catch(error => {
console.error(error);
alert(
`The transaction is failing. This loan cannot be opened at this time. Please check the parameters of the order.`
);
this.setState({ isSubmitted: false });
});
} catch (error) {
console.error(error);
alert(
`The transaction is failing. This loan cannot be opened at this time. Please check the parameters of the order.`
);
this.setState({ isSubmitted: false });
}
return true;
};
export const submitCancelOrder = (
order,
cancelLoanTokenAmount,
tokens,
web3,
bZx,
accounts,
resetOrder
) => {
const txOpts = {
from: accounts[0],
// gas: 1000000, // gas estimated in bZx.js
gasPrice: window.defaultGasPrice.toString()
};
// console.log(`order`, order);
// console.log(`txOpts`, txOpts);
// console.log(`makerIsLender`, makerIsLender);
if (bZx.portalProviderName !== `MetaMask`) {
alert(`Please confirm this transaction on your device.`);
}
const txObj = bZx.cancelLoanOrder({
order,
cancelLoanTokenAmount: toBigNumber(
cancelLoanTokenAmount,
10 ** getDecimals(tokens, order.loanTokenAddress)
),
getObject: true
});
console.log(txOpts);
try {
txObj
.estimateGas(txOpts)
.then(gas => {
console.log(gas);
txOpts.gas = window.gasValue(gas);
txObj
.send(txOpts)
.once(`transactionHash`, hash => {
alert(`Transaction submitted, transaction hash:`, {
component: () => (
<TxHashLink href={`${bZx.etherscanURL}tx/${hash}`}>
{hash}
</TxHashLink>
)
});
})
.then(() => {
console.log();
resetOrder();
alert(`You have canceled all or part of your loan order.`);
this.setState({ isSubmitted: false });
})
.catch(error => {
console.error(error.message);
if (
error.message.includes(`denied transaction signature`) ||
error.message.includes(`Condition of use not satisfied`) ||
error.message.includes(`Invalid status`)
) {
alert();
}
this.setState({ isSubmitted: false });
});
})
.catch(error => {
console.error(error);
alert(
`The transaction is failing. This loan cannot be canceled at this time. Please check the parameters of the order.`
);
this.setState({ isSubmitted: false });
});
} catch (error) {
console.error(error);
alert(
`The transaction is failing. This loan cannot be canceled at this time. Please check the parameters of the order.`
);
this.setState({ isSubmitted: false });
}
return true;
};
export const submitCancelOrderWithHash = (
loanOrderHash,
loanTokenAddress,
cancelLoanTokenAmount,
tokens,
web3,
bZx,
accounts,
changeOrderTab
) => {
const txOpts = {
from: accounts[0],
// gas: 1000000, // gas estimated in bZx.js
gasPrice: window.defaultGasPrice.toString()
};
// console.log(`loanOrderHash`, loanOrderHash);
// console.log(`txOpts`, txOpts);
// console.log(`makerIsLender`, makerIsLender);
if (bZx.portalProviderName !== `MetaMask`) {
alert(`Please confirm this transaction on your device.`);
}
const txObj = bZx.cancelLoanOrderWithHash({
loanOrderHash,
cancelLoanTokenAmount: toBigNumber(
cancelLoanTokenAmount,
10 ** getDecimals(tokens, loanTokenAddress)
),
getObject: true
});
console.log(txOpts);
try {
txObj
.estimateGas(txOpts)
.then(gas => {
console.log(gas);
txOpts.gas = window.gasValue(gas);
txObj
.send(txOpts)
.once(`transactionHash`, hash => {
alert(`Transaction submitted, transaction hash:`, {
component: () => (
<TxHashLink href={`${bZx.etherscanURL}tx/${hash}`}>
{hash}
</TxHashLink>
)
});
})
.then(() => {
console.log();
changeOrderTab(`Orders_OrderBook`);
alert(`You have canceled all or part of your loan order.`);
this.setState({ isSubmitted: false });
})
.catch(error => {
console.error(error.message);
if (
error.message.includes(`denied transaction signature`) ||
error.message.includes(`Condition of use not satisfied`) ||
error.message.includes(`Invalid status`)
) {
alert();
}
this.setState({ isSubmitted: false });
});
})
.catch(error => {
console.error(error);
alert(
`The transaction is failing. This loan cannot be canceled at this time. Please check the parameters of the order.`
);
this.setState({ isSubmitted: false });
});
} catch (error) {
console.error(error);
alert(
`The transaction is failing. This loan cannot be canceled at this time. Please check the parameters of the order.`
);
this.setState({ isSubmitted: false });
}
return true;
};