2021-03-25 12:24:49 +01:00
|
|
|
// @flow
|
|
|
|
import React from 'react';
|
|
|
|
import Button from 'component/button';
|
|
|
|
import { FormField, Form } from 'component/common/form';
|
|
|
|
import { Lbryio } from 'lbryinc';
|
|
|
|
import Card from 'component/common/card';
|
|
|
|
import LbcSymbol from 'component/common/lbc-symbol';
|
|
|
|
import Spinner from 'component/spinner';
|
|
|
|
import Nag from 'component/common/nag';
|
|
|
|
import CopyableText from 'component/copyableText';
|
2021-04-08 11:02:37 +02:00
|
|
|
import Icon from 'component/common/icon';
|
2021-03-25 12:24:49 +01:00
|
|
|
import QRCode from 'component/common/qr-code';
|
|
|
|
import usePersistedState from 'effects/use-persisted-state';
|
|
|
|
import * as ICONS from 'constants/icons';
|
|
|
|
import * as MODALS from 'constants/modal_types';
|
2021-04-05 07:12:19 +02:00
|
|
|
import * as PAGES from 'constants/pages';
|
2021-03-25 12:24:49 +01:00
|
|
|
import { clipboard } from 'electron';
|
|
|
|
import I18nMessage from 'component/i18nMessage';
|
2021-04-05 07:12:19 +02:00
|
|
|
import { Redirect, useHistory } from 'react-router';
|
2021-03-25 12:24:49 +01:00
|
|
|
|
2021-04-08 11:02:37 +02:00
|
|
|
const ENABLE_ALTERNATIVE_COINS = true;
|
2021-03-25 12:24:49 +01:00
|
|
|
|
2021-04-08 11:02:37 +02:00
|
|
|
const BTC_SATOSHIS = 100000000;
|
|
|
|
const LBC_MAX = 21000000;
|
|
|
|
const LBC_MIN = 1;
|
2021-03-25 12:24:49 +01:00
|
|
|
|
|
|
|
const IS_DEV = process.env.NODE_ENV !== 'production';
|
|
|
|
const DEBOUNCE_BTC_CHANGE_MS = 400;
|
|
|
|
|
|
|
|
const INTERNAL_APIS_DOWN = 'internal_apis_down';
|
2021-04-08 11:02:37 +02:00
|
|
|
const BTC_API_STATUS_PENDING = 'NEW'; // Started swap, waiting for coin.
|
|
|
|
const BTC_API_STATUS_CONFIRMING = 'PENDING'; // Coin receiving, waiting confirmation.
|
|
|
|
const BTC_API_STATUS_PROCESSING = 'COMPLETED'; // Coin confirmed. Sending LBC.
|
|
|
|
const BTC_API_STATUS_UNRESOLVED = 'UNRESOLVED'; // Underpaid, overpaid, etc.
|
|
|
|
const BTC_API_STATUS_EXPIRED = 'EXPIRED'; // Charge expired (60 minutes).
|
2021-03-25 12:24:49 +01:00
|
|
|
const BTC_API_STATUS_ERROR = 'Error';
|
|
|
|
|
|
|
|
const ACTION_MAIN = 'action_main';
|
|
|
|
const ACTION_STATUS_PENDING = 'action_pending';
|
2021-04-05 08:07:37 +02:00
|
|
|
const ACTION_STATUS_CONFIRMING = 'action_confirming';
|
2021-03-25 12:24:49 +01:00
|
|
|
const ACTION_STATUS_PROCESSING = 'action_processing';
|
|
|
|
const ACTION_STATUS_SUCCESS = 'action_success';
|
|
|
|
const ACTION_PAST_SWAPS = 'action_past_swaps';
|
|
|
|
|
2021-04-08 11:02:37 +02:00
|
|
|
const NAG_API_STATUS_PENDING = 'Waiting to receive your crypto.';
|
|
|
|
const NAG_API_STATUS_CONFIRMING = 'Confirming transaction.';
|
|
|
|
const NAG_API_STATUS_PROCESSING = 'Crypto received. Sending your Credits.';
|
|
|
|
const NAG_API_STATUS_SUCCESS = 'Credits sent. You should see it in your wallet.';
|
2021-03-25 12:24:49 +01:00
|
|
|
const NAG_API_STATUS_ERROR = 'An error occurred on the previous swap.';
|
|
|
|
const NAG_SWAP_CALL_FAILED = 'Failed to initiate swap.';
|
|
|
|
// const NAG_STATUS_CALL_FAILED = 'Failed to query swap status.';
|
|
|
|
const NAG_SERVER_DOWN = 'The system is currently down. Come back later.';
|
|
|
|
const NAG_RATE_CALL_FAILED = 'Unable to obtain exchange rate. Try again later.';
|
2021-04-08 11:02:37 +02:00
|
|
|
const NAG_EXPIRED = 'Swap expired.';
|
2021-03-25 12:24:49 +01:00
|
|
|
|
|
|
|
type Props = {
|
|
|
|
receiveAddress: string,
|
2021-04-04 09:10:55 +02:00
|
|
|
coinSwaps: Array<CoinSwapInfo>,
|
2021-04-05 07:12:19 +02:00
|
|
|
isAuthenticated: boolean,
|
2021-03-25 12:24:49 +01:00
|
|
|
doToast: ({ message: string }) => void,
|
2021-04-04 09:10:55 +02:00
|
|
|
addCoinSwap: (CoinSwapInfo) => void,
|
2021-03-25 12:24:49 +01:00
|
|
|
getNewAddress: () => void,
|
|
|
|
checkAddressIsMine: (string) => void,
|
|
|
|
openModal: (string, {}) => void,
|
2021-04-08 11:02:37 +02:00
|
|
|
queryCoinSwapStatus: (string) => void,
|
2021-03-25 12:24:49 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
function WalletSwap(props: Props) {
|
2021-04-05 07:12:19 +02:00
|
|
|
const {
|
|
|
|
receiveAddress,
|
|
|
|
doToast,
|
|
|
|
coinSwaps,
|
|
|
|
isAuthenticated,
|
|
|
|
addCoinSwap,
|
|
|
|
getNewAddress,
|
|
|
|
checkAddressIsMine,
|
|
|
|
openModal,
|
2021-04-08 11:02:37 +02:00
|
|
|
queryCoinSwapStatus,
|
2021-04-05 07:12:19 +02:00
|
|
|
} = props;
|
2021-03-25 12:24:49 +01:00
|
|
|
|
2021-04-09 05:11:57 +02:00
|
|
|
const [btc, setBtc] = React.useState(0);
|
|
|
|
const [lbcError, setLbcError] = React.useState();
|
|
|
|
const [lbc, setLbc] = usePersistedState('swap-desired-lbc', LBC_MIN);
|
2021-03-25 12:24:49 +01:00
|
|
|
const [action, setAction] = React.useState(ACTION_MAIN);
|
|
|
|
const [nag, setNag] = React.useState(null);
|
|
|
|
const [showQr, setShowQr] = React.useState(false);
|
|
|
|
const [isFetchingRate, setIsFetchingRate] = React.useState(false);
|
|
|
|
const [isSwapping, setIsSwapping] = React.useState(false);
|
|
|
|
const [isRefreshingStatus, setIsRefreshingStatus] = React.useState(false);
|
2021-04-05 07:12:19 +02:00
|
|
|
const { location } = useHistory();
|
2021-04-08 11:02:37 +02:00
|
|
|
const [swap, setSwap] = React.useState({});
|
|
|
|
const [coin, setCoin] = React.useState('bitcoin');
|
|
|
|
const [lastStatusQuery, setLastStatusQuery] = React.useState();
|
|
|
|
const { goBack } = useHistory();
|
2021-03-25 12:24:49 +01:00
|
|
|
|
2021-04-09 05:11:57 +02:00
|
|
|
function formatCoinAmountString(amount) {
|
|
|
|
return amount === 0 ? '---' : amount.toLocaleString(undefined, { minimumFractionDigits: 8 });
|
2021-03-25 12:24:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function returnToMainAction() {
|
|
|
|
setIsSwapping(false);
|
|
|
|
setAction(ACTION_MAIN);
|
2021-04-08 11:02:37 +02:00
|
|
|
setSwap(null);
|
2021-03-25 12:24:49 +01:00
|
|
|
}
|
|
|
|
|
2021-04-08 11:02:37 +02:00
|
|
|
function removeCoinSwap(chargeCode) {
|
2021-03-25 12:24:49 +01:00
|
|
|
openModal(MODALS.CONFIRM_REMOVE_BTC_SWAP_ADDRESS, {
|
2021-04-08 11:02:37 +02:00
|
|
|
chargeCode: chargeCode,
|
2021-03-25 12:24:49 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure 'receiveAddress' is populated
|
|
|
|
React.useEffect(() => {
|
|
|
|
if (!receiveAddress) {
|
|
|
|
getNewAddress();
|
|
|
|
} else {
|
|
|
|
checkAddressIsMine(receiveAddress);
|
|
|
|
}
|
|
|
|
}, [receiveAddress, getNewAddress, checkAddressIsMine]);
|
|
|
|
|
2021-04-09 05:11:57 +02:00
|
|
|
// Get 'btc/rate' and calculate required BTC.
|
2021-03-25 12:24:49 +01:00
|
|
|
React.useEffect(() => {
|
2021-04-09 05:11:57 +02:00
|
|
|
if (isNaN(lbc) || lbc === 0) {
|
|
|
|
setBtc(0);
|
2021-03-25 12:24:49 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
setIsFetchingRate(true);
|
|
|
|
|
|
|
|
const timer = setTimeout(() => {
|
|
|
|
Lbryio.call('btc', 'rate', { satoshi: BTC_SATOSHIS })
|
2021-04-08 11:02:37 +02:00
|
|
|
.then((rate) => {
|
2021-03-25 12:24:49 +01:00
|
|
|
setIsFetchingRate(false);
|
2021-04-09 05:11:57 +02:00
|
|
|
setBtc((lbc * Math.round(BTC_SATOSHIS * rate)) / BTC_SATOSHIS);
|
2021-03-25 12:24:49 +01:00
|
|
|
})
|
2021-04-08 11:02:37 +02:00
|
|
|
.catch(() => {
|
2021-03-25 12:24:49 +01:00
|
|
|
setIsFetchingRate(false);
|
2021-04-09 05:11:57 +02:00
|
|
|
setBtc(0);
|
2021-03-25 12:24:49 +01:00
|
|
|
setNag({ msg: NAG_RATE_CALL_FAILED, type: 'error' });
|
|
|
|
});
|
|
|
|
}, DEBOUNCE_BTC_CHANGE_MS);
|
|
|
|
|
|
|
|
return () => clearTimeout(timer);
|
2021-04-09 05:11:57 +02:00
|
|
|
}, [lbc]);
|
2021-03-25 12:24:49 +01:00
|
|
|
|
2021-04-08 11:02:37 +02:00
|
|
|
// Resolve 'swap' with the latest info from 'coinSwaps'
|
2021-03-25 12:24:49 +01:00
|
|
|
React.useEffect(() => {
|
2021-04-08 11:02:37 +02:00
|
|
|
const swapInfo = swap && coinSwaps.find((x) => x.chargeCode === swap.chargeCode);
|
|
|
|
if (!swapInfo) {
|
|
|
|
return;
|
2021-03-25 12:24:49 +01:00
|
|
|
}
|
|
|
|
|
2021-04-08 11:02:37 +02:00
|
|
|
const jsonSwap = JSON.stringify(swap);
|
|
|
|
const jsonSwapInfo = JSON.stringify(swapInfo);
|
|
|
|
if (jsonSwap !== jsonSwapInfo) {
|
|
|
|
setSwap({ ...swapInfo });
|
2021-03-25 12:24:49 +01:00
|
|
|
}
|
|
|
|
|
2021-04-08 11:02:37 +02:00
|
|
|
if (!swapInfo.status) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (swapInfo.status.status) {
|
|
|
|
case BTC_API_STATUS_PENDING:
|
|
|
|
setAction(ACTION_STATUS_PENDING);
|
|
|
|
setNag({ msg: NAG_API_STATUS_PENDING, type: 'helpful' });
|
|
|
|
break;
|
|
|
|
case BTC_API_STATUS_CONFIRMING:
|
|
|
|
setAction(ACTION_STATUS_CONFIRMING);
|
|
|
|
setNag({ msg: NAG_API_STATUS_CONFIRMING, type: 'helpful' });
|
|
|
|
break;
|
|
|
|
case BTC_API_STATUS_PROCESSING:
|
|
|
|
if (swapInfo.status.lbcTxid) {
|
|
|
|
setAction(ACTION_STATUS_SUCCESS);
|
|
|
|
setNag({ msg: NAG_API_STATUS_SUCCESS, type: 'helpful' });
|
|
|
|
setIsSwapping(false);
|
|
|
|
} else {
|
|
|
|
setAction(ACTION_STATUS_PROCESSING);
|
|
|
|
setNag({ msg: NAG_API_STATUS_PROCESSING, type: 'helpful' });
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case BTC_API_STATUS_ERROR:
|
|
|
|
setNag({ msg: NAG_API_STATUS_ERROR, type: 'error' });
|
|
|
|
break;
|
|
|
|
case INTERNAL_APIS_DOWN:
|
|
|
|
setNag({ msg: NAG_SERVER_DOWN, type: 'error' });
|
|
|
|
break;
|
|
|
|
case BTC_API_STATUS_EXPIRED:
|
|
|
|
setNag({ msg: NAG_EXPIRED, type: 'error' });
|
|
|
|
if (action === ACTION_PAST_SWAPS) {
|
|
|
|
setAction(ACTION_STATUS_PENDING);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case BTC_API_STATUS_UNRESOLVED:
|
|
|
|
setNag({
|
|
|
|
msg: __(
|
|
|
|
'Received amount did not match order code %chargeCode%. Contact hello@lbry.com to resolve the payment.',
|
|
|
|
{ chargeCode: swapInfo.chargeCode }
|
|
|
|
),
|
|
|
|
type: 'error',
|
|
|
|
});
|
|
|
|
if (action === ACTION_PAST_SWAPS) {
|
|
|
|
setAction(ACTION_STATUS_PENDING);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
setNag({ msg: swapInfo.status.status, type: 'error' });
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}, [swap, coinSwaps]);
|
2021-03-25 12:24:49 +01:00
|
|
|
|
2021-04-09 05:11:57 +02:00
|
|
|
// Validate entered LBC
|
2021-03-25 12:24:49 +01:00
|
|
|
React.useEffect(() => {
|
|
|
|
let msg;
|
2021-04-09 05:11:57 +02:00
|
|
|
if (lbc < LBC_MIN) {
|
|
|
|
msg = __('The amount needs to be higher');
|
|
|
|
} else if (lbc > LBC_MAX) {
|
|
|
|
msg = __('The amount is too high');
|
2021-03-25 12:24:49 +01:00
|
|
|
}
|
2021-04-09 05:11:57 +02:00
|
|
|
setLbcError(msg);
|
|
|
|
}, [lbc]);
|
2021-03-25 12:24:49 +01:00
|
|
|
|
|
|
|
// 'Refresh' button feedback
|
|
|
|
React.useEffect(() => {
|
|
|
|
let timer;
|
|
|
|
if (isRefreshingStatus) {
|
|
|
|
timer = setTimeout(() => {
|
|
|
|
setIsRefreshingStatus(false);
|
|
|
|
}, 1000);
|
|
|
|
}
|
|
|
|
|
|
|
|
return () => clearTimeout(timer);
|
|
|
|
}, [isRefreshingStatus]);
|
|
|
|
|
2021-04-08 11:02:37 +02:00
|
|
|
function getCoinAddress(coin) {
|
|
|
|
if (swap && swap.sendAddresses) {
|
|
|
|
return swap.sendAddresses[coin];
|
|
|
|
}
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
function getCoinSendAmountStr(coin) {
|
|
|
|
if (swap && swap.sendAmounts && swap.sendAmounts[coin]) {
|
|
|
|
return `${swap.sendAmounts[coin].amount} ${swap.sendAmounts[coin].currency}`;
|
|
|
|
}
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
function currencyToCoin(currency) {
|
|
|
|
const MAP = {
|
|
|
|
DAI: 'dai',
|
|
|
|
USDC: 'usdc',
|
|
|
|
BTC: 'bitcoin',
|
|
|
|
ETH: 'ethereum',
|
|
|
|
LTC: 'litecoin',
|
|
|
|
BCH: 'bitcoincash',
|
|
|
|
};
|
|
|
|
return MAP[currency] || 'bitcoin';
|
|
|
|
}
|
|
|
|
|
|
|
|
function getSentAmountStr(swapInfo) {
|
|
|
|
if (swapInfo && swapInfo.status) {
|
|
|
|
const currency = swapInfo.status.receiptCurrency;
|
|
|
|
const coin = currencyToCoin(currency);
|
|
|
|
return getCoinSendAmountStr(coin);
|
|
|
|
}
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
function getCoinLabel(coin) {
|
|
|
|
const COIN_LABEL = {
|
|
|
|
dai: 'Dai',
|
|
|
|
usdc: 'USD Coin',
|
|
|
|
bitcoin: 'Bitcoin',
|
|
|
|
ethereum: 'Ethereum',
|
|
|
|
litecoin: 'Litecoin',
|
|
|
|
bitcoincash: 'Bitcoin Cash',
|
|
|
|
};
|
|
|
|
|
|
|
|
return COIN_LABEL[coin] || coin;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getLbcAmountStrForSwap(swap) {
|
|
|
|
if (swap && swap.lbcAmount) {
|
2021-04-09 05:11:57 +02:00
|
|
|
return formatCoinAmountString(swap.lbcAmount);
|
2021-04-08 11:02:37 +02:00
|
|
|
}
|
|
|
|
return '---';
|
|
|
|
}
|
|
|
|
|
2021-03-25 12:24:49 +01:00
|
|
|
function handleStartSwap() {
|
|
|
|
setIsSwapping(true);
|
2021-04-08 11:02:37 +02:00
|
|
|
setSwap(null);
|
2021-03-25 12:24:49 +01:00
|
|
|
setNag(null);
|
|
|
|
|
|
|
|
Lbryio.call('btc', 'swap', {
|
2021-04-09 05:11:57 +02:00
|
|
|
lbc_satoshi_requested: parseInt(lbc * BTC_SATOSHIS + 0.5),
|
|
|
|
btc_satoshi_provided: parseInt(btc * BTC_SATOSHIS + 0.5),
|
2021-03-25 12:24:49 +01:00
|
|
|
pay_to_wallet_address: receiveAddress,
|
|
|
|
})
|
2021-04-08 11:02:37 +02:00
|
|
|
.then((response) => {
|
2021-04-09 05:11:57 +02:00
|
|
|
const btcAmount = response.Charge.data.pricing['bitcoin'].amount;
|
|
|
|
const rate = response.Exchange.rate;
|
|
|
|
|
2021-04-08 11:02:37 +02:00
|
|
|
const swap = {
|
|
|
|
chargeCode: response.Exchange.charge_code,
|
|
|
|
coins: Object.keys(response.Charge.data.addresses),
|
|
|
|
sendAddresses: response.Charge.data.addresses,
|
|
|
|
sendAmounts: response.Charge.data.pricing,
|
2021-04-09 05:11:57 +02:00
|
|
|
lbcAmount: (btcAmount * BTC_SATOSHIS) / rate,
|
2021-04-08 11:02:37 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
setSwap({ ...swap });
|
|
|
|
addCoinSwap({ ...swap });
|
2021-03-25 12:24:49 +01:00
|
|
|
})
|
|
|
|
.catch((err) => {
|
|
|
|
setNag({ msg: err === INTERNAL_APIS_DOWN ? NAG_SWAP_CALL_FAILED : err.message, type: 'error' });
|
|
|
|
returnToMainAction();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleViewPastSwaps() {
|
|
|
|
setAction(ACTION_PAST_SWAPS);
|
|
|
|
setNag(null);
|
|
|
|
setIsRefreshingStatus(true);
|
|
|
|
|
2021-04-08 11:02:37 +02:00
|
|
|
const now = Date.now();
|
|
|
|
if (!lastStatusQuery || now - lastStatusQuery > 30000) {
|
|
|
|
// There is a '200/minute' limit in the commerce API. If the history is
|
|
|
|
// long, or if the user goes trigger-happy, the limit could be reached
|
|
|
|
// easily. Statuses don't change often, so just limit it to every 30s.
|
|
|
|
setLastStatusQuery(now);
|
|
|
|
coinSwaps.forEach((x) => {
|
|
|
|
queryCoinSwapStatus(x.chargeCode);
|
|
|
|
});
|
|
|
|
}
|
2021-03-25 12:24:49 +01:00
|
|
|
}
|
|
|
|
|
2021-04-05 08:07:37 +02:00
|
|
|
function getShortStatusStr(coinSwap: CoinSwapInfo) {
|
2021-04-08 11:02:37 +02:00
|
|
|
const swapInfo = coinSwaps.find((x) => x.chargeCode === coinSwap.chargeCode);
|
|
|
|
if (!swapInfo || !swapInfo.status) {
|
2021-03-25 12:24:49 +01:00
|
|
|
return '---';
|
|
|
|
}
|
|
|
|
|
|
|
|
let msg;
|
2021-04-08 11:02:37 +02:00
|
|
|
switch (swapInfo.status.status) {
|
2021-03-25 12:24:49 +01:00
|
|
|
case BTC_API_STATUS_PENDING:
|
2021-04-08 11:02:37 +02:00
|
|
|
msg = __('Waiting');
|
2021-03-25 12:24:49 +01:00
|
|
|
break;
|
2021-04-05 08:07:37 +02:00
|
|
|
case BTC_API_STATUS_CONFIRMING:
|
2021-04-08 11:02:37 +02:00
|
|
|
msg = __('Confirming');
|
2021-04-05 08:07:37 +02:00
|
|
|
break;
|
2021-03-25 12:24:49 +01:00
|
|
|
case BTC_API_STATUS_PROCESSING:
|
2021-04-08 11:02:37 +02:00
|
|
|
if (swapInfo.status.lbcTxid) {
|
|
|
|
msg = __('Credits sent');
|
|
|
|
} else {
|
|
|
|
msg = __('Sending Credits');
|
|
|
|
}
|
2021-03-25 12:24:49 +01:00
|
|
|
break;
|
|
|
|
case BTC_API_STATUS_ERROR:
|
|
|
|
msg = __('Failed');
|
|
|
|
break;
|
2021-04-08 11:02:37 +02:00
|
|
|
case BTC_API_STATUS_EXPIRED:
|
|
|
|
msg = __('Expired');
|
|
|
|
break;
|
|
|
|
case BTC_API_STATUS_UNRESOLVED:
|
|
|
|
msg = __('Unresolved');
|
|
|
|
break;
|
2021-03-25 12:24:49 +01:00
|
|
|
default:
|
2021-04-08 11:02:37 +02:00
|
|
|
msg = swapInfo.status.status;
|
2021-03-25 12:24:49 +01:00
|
|
|
// if (IS_DEV) throw new Error('Unhandled "status": ' + status.Status);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return msg;
|
|
|
|
}
|
|
|
|
|
2021-04-08 11:02:37 +02:00
|
|
|
function getViewTransactionElement(swap, isSend) {
|
|
|
|
if (!swap || !swap.status) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
const explorerUrl = (coin, txid) => {
|
2021-04-12 12:48:04 +02:00
|
|
|
// It's unclear whether we can link to sites like blockchain.com.
|
|
|
|
// Don't do it for now.
|
|
|
|
return '';
|
2021-04-08 11:02:37 +02:00
|
|
|
};
|
|
|
|
|
2021-03-25 12:24:49 +01:00
|
|
|
if (isSend) {
|
2021-04-08 11:02:37 +02:00
|
|
|
const sendTxId = swap.status.receiptTxid;
|
|
|
|
const url = explorerUrl(swap.status.receiptCurrency, sendTxId);
|
|
|
|
return sendTxId ? (
|
|
|
|
<>
|
|
|
|
{url && <Button button="link" href={url} label={__('View transaction')} />}
|
|
|
|
{!url && (
|
|
|
|
<Button
|
|
|
|
button="link"
|
2021-04-12 12:48:04 +02:00
|
|
|
label={__('Copy transaction ID')}
|
2021-04-08 11:02:37 +02:00
|
|
|
title={sendTxId}
|
|
|
|
onClick={() => {
|
|
|
|
clipboard.writeText(sendTxId);
|
|
|
|
doToast({
|
|
|
|
message: __('Transaction ID copied.'),
|
|
|
|
});
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</>
|
2021-03-25 12:24:49 +01:00
|
|
|
) : null;
|
|
|
|
} else {
|
2021-04-08 11:02:37 +02:00
|
|
|
const lbcTxId = swap.status.lbcTxid;
|
2021-03-25 12:24:49 +01:00
|
|
|
return lbcTxId ? (
|
|
|
|
<Button button="link" href={`https://explorer.lbry.com/tx/${lbcTxId}`} label={__('View transaction')} />
|
|
|
|
) : null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-08 11:02:37 +02:00
|
|
|
function getCloseButton() {
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<Button autoFocus button="primary" label={__('Close')} onClick={() => goBack()} />
|
|
|
|
<Icon
|
|
|
|
className="icon--help"
|
|
|
|
icon={ICONS.HELP}
|
|
|
|
tooltip
|
|
|
|
size={16}
|
|
|
|
customTooltipText={__(
|
|
|
|
'This page can be closed while the transactions are in progress.\nYou can view the status later from:\n • Wallet » Swap » View Past Swaps'
|
|
|
|
)}
|
|
|
|
/>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function getGap() {
|
|
|
|
return <div className="confirm__value" />; // better way?
|
|
|
|
}
|
|
|
|
|
2021-03-25 12:24:49 +01:00
|
|
|
function getActionElement() {
|
|
|
|
switch (action) {
|
|
|
|
case ACTION_MAIN:
|
|
|
|
return actionMain;
|
2021-04-05 08:07:37 +02:00
|
|
|
|
2021-03-25 12:24:49 +01:00
|
|
|
case ACTION_STATUS_PENDING:
|
|
|
|
return actionPending;
|
2021-04-05 08:07:37 +02:00
|
|
|
|
|
|
|
case ACTION_STATUS_CONFIRMING:
|
|
|
|
return actionConfirmingSend;
|
|
|
|
|
|
|
|
case ACTION_STATUS_PROCESSING: // fall-through
|
|
|
|
case ACTION_STATUS_SUCCESS:
|
2021-03-25 12:24:49 +01:00
|
|
|
return actionProcessingAndSuccess;
|
2021-04-05 08:07:37 +02:00
|
|
|
|
2021-03-25 12:24:49 +01:00
|
|
|
case ACTION_PAST_SWAPS:
|
|
|
|
return actionPastSwaps;
|
2021-04-05 08:07:37 +02:00
|
|
|
|
2021-03-25 12:24:49 +01:00
|
|
|
default:
|
|
|
|
if (IS_DEV) throw new Error('Unhandled action: ' + action);
|
|
|
|
return actionMain;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const actionMain = (
|
|
|
|
<>
|
|
|
|
<div className="section section--padded card--inline confirm__wrapper">
|
|
|
|
<div className="section">
|
|
|
|
<FormField
|
|
|
|
autoFocus
|
2021-04-09 05:11:57 +02:00
|
|
|
label={
|
|
|
|
<I18nMessage
|
|
|
|
tokens={{
|
|
|
|
lbc: <LbcSymbol size={22} />,
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
Enter desired %lbc%
|
|
|
|
</I18nMessage>
|
|
|
|
}
|
2021-03-25 12:24:49 +01:00
|
|
|
type="number"
|
2021-04-09 05:11:57 +02:00
|
|
|
name="lbc"
|
2021-03-25 12:24:49 +01:00
|
|
|
className="form-field--price-amount--auto"
|
|
|
|
affixClass="form-field--fix-no-height"
|
2021-04-09 05:11:57 +02:00
|
|
|
max={LBC_MAX}
|
|
|
|
min={LBC_MIN}
|
2021-03-25 12:24:49 +01:00
|
|
|
step={1 / BTC_SATOSHIS}
|
|
|
|
placeholder="12.34"
|
2021-04-09 05:11:57 +02:00
|
|
|
value={lbc}
|
|
|
|
error={lbcError}
|
2021-03-25 12:24:49 +01:00
|
|
|
disabled={isSwapping}
|
2021-04-09 05:11:57 +02:00
|
|
|
onChange={(event) => setLbc(parseFloat(event.target.value))}
|
2021-03-25 12:24:49 +01:00
|
|
|
/>
|
2021-04-08 11:02:37 +02:00
|
|
|
{getGap()}
|
2021-04-09 05:11:57 +02:00
|
|
|
<div className="confirm__label">{__('Estimated BTC price')}</div>
|
2021-03-25 12:24:49 +01:00
|
|
|
<div className="confirm__value">
|
2021-04-09 05:11:57 +02:00
|
|
|
{formatCoinAmountString(btc)} {btc === 0 ? '' : 'BTC'}
|
2021-03-25 12:24:49 +01:00
|
|
|
{isFetchingRate && <Spinner type="small" />}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="section__actions">
|
|
|
|
<Button
|
|
|
|
autoFocus
|
|
|
|
onClick={handleStartSwap}
|
|
|
|
button="primary"
|
2021-04-09 05:11:57 +02:00
|
|
|
disabled={isSwapping || isNaN(btc) || btc === 0 || lbc === 0 || lbcError}
|
2021-03-25 12:24:49 +01:00
|
|
|
label={isSwapping ? __('Processing...') : __('Start Swap')}
|
|
|
|
/>
|
2021-04-08 11:02:37 +02:00
|
|
|
{!isSwapping && coinSwaps.length !== 0 && (
|
|
|
|
<Button button="link" label={__('View Past Swaps')} onClick={handleViewPastSwaps} />
|
|
|
|
)}
|
|
|
|
{isSwapping && <Spinner type="small" />}
|
2021-03-25 12:24:49 +01:00
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
|
|
|
|
const actionPending = (
|
|
|
|
<>
|
|
|
|
<div className="section section--padded card--inline confirm__wrapper">
|
|
|
|
<div className="section">
|
2021-04-08 11:02:37 +02:00
|
|
|
{swap && swap.coins && ENABLE_ALTERNATIVE_COINS && (
|
|
|
|
<>
|
|
|
|
<FormField
|
|
|
|
type="select"
|
|
|
|
name="select_coin"
|
|
|
|
value={coin}
|
|
|
|
label={__('Alternative coins')}
|
|
|
|
onChange={(e) => setCoin(e.target.value)}
|
|
|
|
>
|
|
|
|
{swap.coins.map((x) => (
|
|
|
|
<option key={x} value={x}>
|
|
|
|
{getCoinLabel(x)}
|
|
|
|
</option>
|
|
|
|
))}
|
|
|
|
</FormField>
|
|
|
|
{getGap()}
|
|
|
|
</>
|
|
|
|
)}
|
2021-03-25 12:24:49 +01:00
|
|
|
<div className="confirm__label">{__('Send')}</div>
|
2021-04-08 11:02:37 +02:00
|
|
|
<CopyableText primaryButton copyable={getCoinSendAmountStr(coin)} snackMessage={__('Amount copied.')} />
|
|
|
|
{getGap()}
|
2021-03-25 12:24:49 +01:00
|
|
|
<div className="confirm__label">{__('To')}</div>
|
2021-04-08 11:02:37 +02:00
|
|
|
<CopyableText primaryButton copyable={getCoinAddress(coin)} snackMessage={__('Address copied.')} />
|
|
|
|
<div className="confirm__value--subitem">
|
2021-03-25 12:24:49 +01:00
|
|
|
<Button
|
|
|
|
button="link"
|
|
|
|
label={showQr ? __('Hide QR code') : __('Show QR code')}
|
|
|
|
onClick={() => setShowQr(!showQr)}
|
|
|
|
/>
|
2021-04-08 11:02:37 +02:00
|
|
|
{showQr && getCoinAddress(coin) && <QRCode value={getCoinAddress(coin)} />}
|
2021-03-25 12:24:49 +01:00
|
|
|
</div>
|
2021-04-08 11:02:37 +02:00
|
|
|
{getGap()}
|
2021-03-25 12:24:49 +01:00
|
|
|
<div className="confirm__label">{__('Receive')}</div>
|
2021-04-08 11:02:37 +02:00
|
|
|
<div className="confirm__value">{<LbcSymbol postfix={getLbcAmountStrForSwap(swap)} size={22} />}</div>
|
2021-03-25 12:24:49 +01:00
|
|
|
</div>
|
|
|
|
</div>
|
2021-04-08 11:02:37 +02:00
|
|
|
<div className="section__actions">{getCloseButton()}</div>
|
2021-03-25 12:24:49 +01:00
|
|
|
</>
|
|
|
|
);
|
|
|
|
|
2021-04-05 08:07:37 +02:00
|
|
|
const actionConfirmingSend = (
|
|
|
|
<>
|
|
|
|
<div className="section section--padded card--inline confirm__wrapper">
|
|
|
|
<div className="section">
|
|
|
|
<div className="confirm__label">{__('Confirming')}</div>
|
2021-04-08 11:02:37 +02:00
|
|
|
<div className="confirm__value confirm__value--no-gap">{getSentAmountStr(swap)}</div>
|
|
|
|
<div className="confirm__value--subitem">{getViewTransactionElement(swap, true)}</div>
|
2021-04-05 08:07:37 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
2021-04-08 11:02:37 +02:00
|
|
|
<div className="section__actions">{getCloseButton()}</div>
|
2021-04-05 08:07:37 +02:00
|
|
|
</>
|
|
|
|
);
|
|
|
|
|
2021-03-25 12:24:49 +01:00
|
|
|
const actionProcessingAndSuccess = (
|
|
|
|
<>
|
|
|
|
<div className="section section--padded card--inline confirm__wrapper">
|
|
|
|
<div className="section">
|
|
|
|
<div className="confirm__label">{__('Sent')}</div>
|
2021-04-08 11:02:37 +02:00
|
|
|
<div className="confirm__value confirm__value--no-gap">{getSentAmountStr(swap)}</div>
|
|
|
|
<div className="confirm__value--subitem">{getViewTransactionElement(swap, true)}</div>
|
|
|
|
{getGap()}
|
2021-03-25 12:24:49 +01:00
|
|
|
<div className="confirm__label">{action === ACTION_STATUS_SUCCESS ? __('Received') : __('Receiving')}</div>
|
2021-04-08 11:02:37 +02:00
|
|
|
<div className="confirm__value confirm__value--no-gap">
|
|
|
|
{<LbcSymbol postfix={getLbcAmountStrForSwap(swap)} size={22} />}
|
|
|
|
</div>
|
|
|
|
{action === ACTION_STATUS_SUCCESS && (
|
|
|
|
<div className="confirm__value--subitem">{getViewTransactionElement(swap, false)}</div>
|
|
|
|
)}
|
2021-03-25 12:24:49 +01:00
|
|
|
</div>
|
|
|
|
</div>
|
2021-04-08 11:02:37 +02:00
|
|
|
<div className="section__actions">{getCloseButton()}</div>
|
2021-03-25 12:24:49 +01:00
|
|
|
</>
|
|
|
|
);
|
|
|
|
|
|
|
|
const actionPastSwaps = (
|
|
|
|
<>
|
|
|
|
<div className="section section--padded card--inline confirm__wrapper">
|
|
|
|
<div className="section">
|
|
|
|
<div className="table__wrapper">
|
|
|
|
<table className="table table--btc-swap">
|
|
|
|
<thead>
|
|
|
|
<tr>
|
2021-04-08 11:02:37 +02:00
|
|
|
<th>{__('Code')}</th>
|
2021-04-05 11:22:19 +02:00
|
|
|
<th>{__('Status')}</th>
|
2021-03-25 12:24:49 +01:00
|
|
|
<th />
|
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
<tbody>
|
2021-04-04 09:10:55 +02:00
|
|
|
{coinSwaps.length === 0 && (
|
2021-03-25 12:24:49 +01:00
|
|
|
<tr>
|
|
|
|
<td>{'---'}</td>
|
|
|
|
</tr>
|
|
|
|
)}
|
2021-04-04 09:10:55 +02:00
|
|
|
{coinSwaps.length !== 0 &&
|
|
|
|
coinSwaps.map((x) => {
|
2021-03-25 12:24:49 +01:00
|
|
|
return (
|
2021-04-08 11:02:37 +02:00
|
|
|
<tr key={x.chargeCode}>
|
2021-03-25 12:24:49 +01:00
|
|
|
<td>
|
|
|
|
<Button
|
|
|
|
button="link"
|
|
|
|
className="button--hash-id"
|
2021-04-08 11:02:37 +02:00
|
|
|
title={x.chargeCode}
|
|
|
|
label={x.chargeCode}
|
2021-03-25 12:24:49 +01:00
|
|
|
onClick={() => {
|
2021-04-08 11:02:37 +02:00
|
|
|
setSwap({ ...x });
|
2021-03-25 12:24:49 +01:00
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</td>
|
2021-04-05 08:07:37 +02:00
|
|
|
<td>{isRefreshingStatus ? '...' : getShortStatusStr(x)}</td>
|
2021-03-25 12:24:49 +01:00
|
|
|
<td>
|
|
|
|
<Button
|
|
|
|
button="link"
|
|
|
|
icon={ICONS.REMOVE}
|
2021-04-08 11:02:37 +02:00
|
|
|
title={__('Remove swap')}
|
|
|
|
onClick={() => removeCoinSwap(x.chargeCode)}
|
2021-03-25 12:24:49 +01:00
|
|
|
/>
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
);
|
|
|
|
})}
|
|
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="section__actions">
|
2021-04-08 11:02:37 +02:00
|
|
|
<Button
|
|
|
|
autoFocus
|
|
|
|
button="primary"
|
|
|
|
label={__('Go Back')}
|
|
|
|
onClick={() => {
|
|
|
|
returnToMainAction();
|
|
|
|
setNag(null);
|
|
|
|
}}
|
|
|
|
/>
|
2021-04-04 09:10:55 +02:00
|
|
|
{coinSwaps.length !== 0 && !isRefreshingStatus && (
|
2021-03-25 12:24:49 +01:00
|
|
|
<Button button="link" label={__('Refresh')} onClick={handleViewPastSwaps} />
|
|
|
|
)}
|
|
|
|
{isRefreshingStatus && <Spinner type="small" />}
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
|
2021-04-05 07:12:19 +02:00
|
|
|
if (!isAuthenticated) {
|
|
|
|
return <Redirect to={`/$/${PAGES.AUTH_SIGNIN}?redirect=${location.pathname}`} />;
|
|
|
|
}
|
|
|
|
|
2021-03-25 12:24:49 +01:00
|
|
|
return (
|
|
|
|
<Form onSubmit={handleStartSwap}>
|
|
|
|
<Card
|
2021-04-08 11:02:37 +02:00
|
|
|
title={<I18nMessage tokens={{ lbc: <LbcSymbol size={22} /> }}>Swap Crypto for %lbc%</I18nMessage>}
|
|
|
|
subtitle={__('Send crypto to the address provided and you will be sent an equivalent amount of Credits.')}
|
2021-03-25 12:24:49 +01:00
|
|
|
actions={getActionElement()}
|
|
|
|
nag={nag ? <Nag relative type={nag.type} message={__(nag.msg)} /> : null}
|
|
|
|
/>
|
|
|
|
</Form>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export default WalletSwap;
|