Changeset View
Changeset View
Standalone View
Standalone View
cashtab/src/components/Send/SendToken.js
Show All 11 Lines | |||||
import { SidePaddingCtn, TxLink } from 'components/Common/Atoms'; | import { SidePaddingCtn, TxLink } from 'components/Common/Atoms'; | ||||
import BalanceHeaderToken from 'components/Common/BalanceHeaderToken'; | import BalanceHeaderToken from 'components/Common/BalanceHeaderToken'; | ||||
import { useNavigate } from 'react-router-dom'; | import { useNavigate } from 'react-router-dom'; | ||||
import { BN } from 'slp-mdm'; | import { BN } from 'slp-mdm'; | ||||
import { Event } from 'components/Common/GoogleAnalytics'; | import { Event } from 'components/Common/GoogleAnalytics'; | ||||
import { getWalletState } from 'utils/cashMethods'; | import { getWalletState } from 'utils/cashMethods'; | ||||
import ApiError from 'components/Common/ApiError'; | import ApiError from 'components/Common/ApiError'; | ||||
import { isValidEtokenBurnAmount, parseAddressInput } from 'validation'; | import { isValidEtokenBurnAmount, parseAddressInput } from 'validation'; | ||||
import { getTokenStats } from 'chronik'; | |||||
import { formatDate } from 'utils/formatting'; | import { formatDate } from 'utils/formatting'; | ||||
import styled from 'styled-components'; | import styled from 'styled-components'; | ||||
import TokenIcon from 'components/Etokens/TokenIcon'; | import TokenIcon from 'components/Etokens/TokenIcon'; | ||||
import { explorer } from 'config/explorer'; | import { explorer } from 'config/explorer'; | ||||
import { queryAliasServer } from 'alias'; | import { queryAliasServer } from 'alias'; | ||||
import aliasSettings from 'config/alias'; | import aliasSettings from 'config/alias'; | ||||
import cashaddr from 'ecashaddrjs'; | import cashaddr from 'ecashaddrjs'; | ||||
import appConfig from 'config/app'; | import appConfig from 'config/app'; | ||||
Show All 10 Lines | |||||
import { | import { | ||||
InputWithScanner, | InputWithScanner, | ||||
SendTokenInput, | SendTokenInput, | ||||
ModalInput, | ModalInput, | ||||
InputFlex, | InputFlex, | ||||
} from 'components/Common/Inputs'; | } from 'components/Common/Inputs'; | ||||
import CopyToClipboard from 'components/Common/CopyToClipboard'; | import CopyToClipboard from 'components/Common/CopyToClipboard'; | ||||
import { ThemedCopySolid } from 'components/Common/CustomIcons'; | import { ThemedCopySolid } from 'components/Common/CustomIcons'; | ||||
import { decimalizedTokenQtyToLocaleFormat } from 'utils/formatting'; | |||||
const TokenStatsTable = styled.div` | const TokenStatsTable = styled.div` | ||||
display: flex; | display: flex; | ||||
flex-direction: column; | flex-direction: column; | ||||
align-items: center; | align-items: center; | ||||
justify-content: center; | justify-content: center; | ||||
width: 100%; | width: 100%; | ||||
color: ${props => props.theme.contrast}; | color: ${props => props.theme.contrast}; | ||||
margin-bottom: 12px; | |||||
`; | `; | ||||
const TokenStatsRow = styled.div` | const TokenStatsRow = styled.div` | ||||
width: 100%; | width: 100%; | ||||
display: flex; | display: flex; | ||||
text-align: center; | text-align: center; | ||||
justify-content: center; | justify-content: center; | ||||
gap: 3px; | gap: 3px; | ||||
`; | `; | ||||
const TokenStatsCol = styled.div``; | const TokenStatsCol = styled.div``; | ||||
const TokenSentLink = styled.a` | const TokenSentLink = styled.a` | ||||
color: ${props => props.theme.walletBackground}; | color: ${props => props.theme.walletBackground}; | ||||
text-decoration: none; | text-decoration: none; | ||||
`; | `; | ||||
const AliasAddressPreviewLabel = styled.div` | const AliasAddressPreviewLabel = styled.div` | ||||
text-align: center; | text-align: center; | ||||
color: ${props => props.theme.forms.text}; | color: ${props => props.theme.forms.text}; | ||||
padding-left: 1px; | padding-left: 1px; | ||||
white-space: nowrap; | white-space: nowrap; | ||||
`; | `; | ||||
const SendToken = () => { | const SendToken = () => { | ||||
let navigate = useNavigate(); | let navigate = useNavigate(); | ||||
const { apiError, cashtabState, loading, chronik, chaintipBlockheight } = | const { apiError, cashtabState, chronik, chaintipBlockheight, loading } = | ||||
React.useContext(WalletContext); | React.useContext(WalletContext); | ||||
const { settings, wallets } = cashtabState; | const { settings, wallets, cashtabCache } = cashtabState; | ||||
const wallet = wallets.length > 0 ? wallets[0] : false; | const wallet = wallets.length > 0 ? wallets[0] : false; | ||||
const walletState = getWalletState(wallet); | const walletState = getWalletState(wallet); | ||||
const { tokens, balanceSats } = walletState; | const { tokens, balanceSats } = walletState; | ||||
const params = useParams(); | const params = useParams(); | ||||
const tokenId = params.tokenId; | const tokenId = params.tokenId; | ||||
const token = tokens.find(token => token.tokenId === tokenId); | const tokenBalance = tokens.get(tokenId); | ||||
const cachedInfo = | |||||
typeof cashtabCache.tokens.get(tokenId) !== 'undefined' | |||||
? cashtabCache.tokens.get(tokenId) | |||||
: { | |||||
genesisInfo: { | |||||
tokenName: 'UNCACHED', | |||||
tokenTicker: 'UNCACHED', | |||||
decimals: 0, | |||||
}, | |||||
genesisSupply: 0, | |||||
genesisMintBatons: 0, | |||||
}; | |||||
const { genesisInfo, genesisSupply, genesisMintBatons } = cachedInfo; | |||||
const { tokenName, tokenTicker, url, decimals } = genesisInfo; | |||||
const [tokenStats, setTokenStats] = useState(null); | |||||
const [sendTokenAddressError, setSendTokenAddressError] = useState(false); | const [sendTokenAddressError, setSendTokenAddressError] = useState(false); | ||||
const [sendTokenAmountError, setSendTokenAmountError] = useState(false); | const [sendTokenAmountError, setSendTokenAmountError] = useState(false); | ||||
const [showConfirmBurnEtoken, setShowConfirmBurnEtoken] = useState(false); | const [showConfirmBurnEtoken, setShowConfirmBurnEtoken] = useState(false); | ||||
const [burnTokenAmountError, setBurnTokenAmountError] = useState(false); | const [burnTokenAmountError, setBurnTokenAmountError] = useState(false); | ||||
const [burnConfirmationError, setBurnConfirmationError] = useState(false); | const [burnConfirmationError, setBurnConfirmationError] = useState(false); | ||||
const [confirmationOfEtokenToBeBurnt, setConfirmationOfEtokenToBeBurnt] = | const [confirmationOfEtokenToBeBurnt, setConfirmationOfEtokenToBeBurnt] = | ||||
useState(''); | useState(''); | ||||
const [aliasInputAddress, setAliasInputAddress] = useState(false); | const [aliasInputAddress, setAliasInputAddress] = useState(false); | ||||
// Load with QR code open if device is mobile | // Load with QR code open if device is mobile | ||||
const openWithScanner = | const openWithScanner = | ||||
settings && settings.autoCameraOn === true && isMobile(navigator); | settings && settings.autoCameraOn === true && isMobile(navigator); | ||||
const [isModalVisible, setIsModalVisible] = useState(false); | const [isModalVisible, setIsModalVisible] = useState(false); | ||||
const [formData, setFormData] = useState({ | const [formData, setFormData] = useState({ | ||||
amount: '', | amount: '', | ||||
address: '', | address: '', | ||||
burnAmount: '', | burnAmount: '', | ||||
}); | }); | ||||
const userLocale = getUserLocale(navigator); | const userLocale = getUserLocale(navigator); | ||||
useEffect(() => { | useEffect(() => { | ||||
if (typeof token === 'undefined' && loading === false) { | if ( | ||||
loading === false && | |||||
(typeof tokenBalance === 'undefined' || | |||||
typeof cashtabCache.tokens.get(tokenId) === 'undefined') | |||||
) { | |||||
// token can be undefined when the app is loading | // token can be undefined when the app is loading | ||||
// in practice, this only happens in integration tests or when the user navigates directly | // in practice, this only happens in integration tests or when the user navigates directly | ||||
// to send/tokenId screen, as cashtab locks UI while it loads | // to send/tokenId screen, as cashtab locks UI while it loads | ||||
// token becomes undefined when a user sends or burns all of their balance for this token | // token becomes undefined when a user sends or burns all of their balance for this token | ||||
// In this case -- loading === true and token === undefined -- navigate to the home page | // In this case -- loading === true and token === undefined -- navigate to the home page | ||||
navigate('/'); | navigate('/'); | ||||
} | } | ||||
}, [loading, token]); | }, [loading, tokenBalance, cashtabCache]); | ||||
// Fetch token stats if you do not have them and API did not return an error | |||||
if (tokenStats === null) { | |||||
getTokenStats(chronik, tokenId).then( | |||||
result => { | |||||
setTokenStats(result); | |||||
}, | |||||
err => { | |||||
console.log(`Error getting token stats: ${err}`); | |||||
}, | |||||
); | |||||
} | |||||
// Clears address and amount fields following a send token notification | // Clears address and amount fields following a send token notification | ||||
const clearInputForms = () => { | const clearInputForms = () => { | ||||
setFormData({ | setFormData({ | ||||
amount: '', | amount: '', | ||||
address: '', | address: '', | ||||
burnAmount: '', | burnAmount: '', | ||||
}); | }); | ||||
setAliasInputAddress(false); // clear alias address preview | setAliasInputAddress(false); // clear alias address preview | ||||
Show All 29 Lines | async function sendToken() { | ||||
} | } | ||||
try { | try { | ||||
// Get input utxos for slpv1 send tx | // Get input utxos for slpv1 send tx | ||||
const tokenInputInfo = getSendTokenInputs( | const tokenInputInfo = getSendTokenInputs( | ||||
wallet.state.slpUtxos, | wallet.state.slpUtxos, | ||||
tokenId, | tokenId, | ||||
amount, | amount, | ||||
token.info.decimals, | decimals, | ||||
); | ); | ||||
// Get targetOutputs for an slpv1 send tx | // Get targetOutputs for an slpv1 send tx | ||||
const tokenSendTargetOutputs = getSlpSendTargetOutputs( | const tokenSendTargetOutputs = getSlpSendTargetOutputs( | ||||
tokenInputInfo, | tokenInputInfo, | ||||
cleanAddress, | cleanAddress, | ||||
); | ); | ||||
Show All 37 Lines | const handleSlpAmountChange = e => { | ||||
let error = false; | let error = false; | ||||
const { value, name } = e.target; | const { value, name } = e.target; | ||||
// test if exceeds balance using BigNumber | // test if exceeds balance using BigNumber | ||||
let isGreaterThanBalance = false; | let isGreaterThanBalance = false; | ||||
if (!isNaN(value)) { | if (!isNaN(value)) { | ||||
const bigValue = new BN(value); | const bigValue = new BN(value); | ||||
// Returns 1 if greater, -1 if less, 0 if the same, null if n/a | // Returns 1 if greater, -1 if less, 0 if the same, null if n/a | ||||
isGreaterThanBalance = bigValue.comparedTo(new BN(token.balance)); | isGreaterThanBalance = bigValue.comparedTo(tokenBalance); | ||||
} | } | ||||
// Validate value for > 0 | // Validate value for > 0 | ||||
if (isNaN(value)) { | if (isNaN(value)) { | ||||
error = 'Amount must be a number'; | error = 'Amount must be a number'; | ||||
} else if (value <= 0) { | } else if (value <= 0) { | ||||
error = 'Amount must be greater than 0'; | error = 'Amount must be greater than 0'; | ||||
} else if (token && token.balance && isGreaterThanBalance === 1) { | } else if (tokenBalance && isGreaterThanBalance === 1) { | ||||
error = `Amount cannot exceed your ${token.info.tokenTicker} balance of ${token.balance}`; | error = `Amount cannot exceed your ${tokenTicker} balance of ${tokenBalance}`; | ||||
} else if (!isNaN(value) && value.toString().includes('.')) { | } else if (!isNaN(value) && value.toString().includes('.')) { | ||||
if (value.toString().split('.')[1].length > token.info.decimals) { | if (value.toString().split('.')[1].length > decimals) { | ||||
error = `This token only supports ${token.info.decimals} decimal places`; | error = `This token only supports ${decimals} decimal places`; | ||||
} | } | ||||
} | } | ||||
setSendTokenAmountError(error); | setSendTokenAmountError(error); | ||||
setFormData(p => ({ | setFormData(p => ({ | ||||
...p, | ...p, | ||||
[name]: value, | [name]: value, | ||||
})); | })); | ||||
}; | }; | ||||
▲ Show 20 Lines • Show All 61 Lines • ▼ Show 20 Lines | const handleTokenAddressChange = async e => { | ||||
[name]: value, | [name]: value, | ||||
})); | })); | ||||
}; | }; | ||||
const onMax = async () => { | const onMax = async () => { | ||||
// Clear this error before updating field | // Clear this error before updating field | ||||
setSendTokenAmountError(false); | setSendTokenAmountError(false); | ||||
try { | try { | ||||
let amount = token.balance; | let amount = tokenBalance; | ||||
setFormData({ | setFormData({ | ||||
...formData, | ...formData, | ||||
amount, | amount, | ||||
}); | }); | ||||
} catch (err) { | } catch (err) { | ||||
console.log(`Error in onMax:`); | console.log(`Error in onMax:`); | ||||
console.log(err); | console.log(err); | ||||
Show All 17 Lines | const handleOk = () => { | ||||
sendToken(); | sendToken(); | ||||
}; | }; | ||||
const handleCancel = () => { | const handleCancel = () => { | ||||
setIsModalVisible(false); | setIsModalVisible(false); | ||||
}; | }; | ||||
const handleEtokenBurnAmountChange = e => { | const handleEtokenBurnAmountChange = e => { | ||||
console.log(`handleEtokenBurnAmountChange`); | |||||
const { name, value } = e.target; | const { name, value } = e.target; | ||||
console.log(`name`, name); | |||||
console.log(`value`, value); | |||||
let error = false; | let error = false; | ||||
if (!isValidEtokenBurnAmount(new BN(value), token.balance)) { | if (!isValidEtokenBurnAmount(new BN(value), tokenBalance)) { | ||||
error = 'Burn amount must be between 1 and ' + token.balance; | error = 'Burn amount must be between 1 and ' + tokenBalance; | ||||
} | } | ||||
setBurnTokenAmountError(error); | setBurnTokenAmountError(error); | ||||
setFormData(p => ({ | setFormData(p => ({ | ||||
...p, | ...p, | ||||
[name]: value, | [name]: value, | ||||
})); | })); | ||||
}; | }; | ||||
const onMaxBurn = () => { | const onMaxBurn = () => { | ||||
// trigger validation on the inserted max value | // trigger validation on the inserted max value | ||||
handleEtokenBurnAmountChange({ | handleEtokenBurnAmountChange({ | ||||
target: { | target: { | ||||
name: 'burnAmount', | name: 'burnAmount', | ||||
value: token.balance, | value: tokenBalance, | ||||
}, | }, | ||||
}); | }); | ||||
}; | }; | ||||
async function burn() { | async function burn() { | ||||
if (burnConfirmationError || formData.burnAmount === '') { | if (burnConfirmationError || formData.burnAmount === '') { | ||||
return; | return; | ||||
} | } | ||||
Event('SendToken.js', 'Burn eToken', tokenId); | Event('SendToken.js', 'Burn eToken', tokenId); | ||||
try { | try { | ||||
// Get input utxos for slpv1 burn tx | // Get input utxos for slpv1 burn tx | ||||
// This is done the same way as for an slpv1 send tx | // This is done the same way as for an slpv1 send tx | ||||
const tokenInputInfo = getSendTokenInputs( | const tokenInputInfo = getSendTokenInputs( | ||||
wallet.state.slpUtxos, | wallet.state.slpUtxos, | ||||
tokenId, | tokenId, | ||||
formData.burnAmount, | formData.burnAmount, | ||||
token.info.decimals, | decimals, | ||||
); | ); | ||||
// Get targetOutputs for an slpv1 burn tx | // Get targetOutputs for an slpv1 burn tx | ||||
// this is NOT like an slpv1 send tx | // this is NOT like an slpv1 send tx | ||||
const tokenBurnTargetOutputs = | const tokenBurnTargetOutputs = | ||||
getSlpBurnTargetOutputs(tokenInputInfo); | getSlpBurnTargetOutputs(tokenInputInfo); | ||||
// Build and broadcast the tx | // Build and broadcast the tx | ||||
Show All 33 Lines | async function burn() { | ||||
setConfirmationOfEtokenToBeBurnt(''); | setConfirmationOfEtokenToBeBurnt(''); | ||||
toast.error(`${e}`); | toast.error(`${e}`); | ||||
} | } | ||||
} | } | ||||
const handleBurnConfirmationInput = e => { | const handleBurnConfirmationInput = e => { | ||||
const { value } = e.target; | const { value } = e.target; | ||||
if (value && value === `burn ${token.info.tokenTicker}`) { | if (value && value === `burn ${tokenTicker}`) { | ||||
setBurnConfirmationError(false); | setBurnConfirmationError(false); | ||||
} else { | } else { | ||||
setBurnConfirmationError( | setBurnConfirmationError( | ||||
`Input must exactly match "burn ${token.info.tokenTicker}"`, | `Input must exactly match "burn ${tokenTicker}"`, | ||||
); | ); | ||||
} | } | ||||
setConfirmationOfEtokenToBeBurnt(value); | setConfirmationOfEtokenToBeBurnt(value); | ||||
}; | }; | ||||
const handleBurnAmountInput = () => { | const handleBurnAmountInput = () => { | ||||
if (!burnTokenAmountError) { | if (!burnTokenAmountError) { | ||||
setShowConfirmBurnEtoken(true); | setShowConfirmBurnEtoken(true); | ||||
} | } | ||||
}; | }; | ||||
return ( | return ( | ||||
<> | <> | ||||
{tokenBalance && | |||||
typeof cashtabCache.tokens.get(tokenId) !== 'undefined' && ( | |||||
<SidePaddingCtn> | |||||
{isModalVisible && ( | {isModalVisible && ( | ||||
<Modal | <Modal | ||||
title="Confirm Send" | title="Confirm Send" | ||||
description={`Send ${formData.amount}${' '} | description={`Send ${formData.amount}${' '} | ||||
${token.info.tokenTicker} to ${formData.address}?`} | ${tokenTicker} to ${formData.address}?`} | ||||
handleOk={handleOk} | handleOk={handleOk} | ||||
handleCancel={handleCancel} | handleCancel={handleCancel} | ||||
showCancelButton | showCancelButton | ||||
> | > | ||||
<p> | <p> | ||||
{token && token.info && formData | Are you sure you want to send{' '} | ||||
? `Are you sure you want to send ${ | {formData.amount} {tokenTicker} to{' '} | ||||
formData.amount | {formData.address}? | ||||
}${' '} | |||||
${token.info.tokenTicker} to ${formData.address}?` | |||||
: ''} | |||||
</p> | </p> | ||||
</Modal> | </Modal> | ||||
)} | )} | ||||
{token && ( | |||||
<SidePaddingCtn> | |||||
{/* eToken burn modal */} | |||||
{showConfirmBurnEtoken && ( | {showConfirmBurnEtoken && ( | ||||
<Modal | <Modal | ||||
title={`Confirm ${token.info.tokenTicker} burn`} | title={`Confirm ${tokenTicker} burn`} | ||||
description={`Burn ${formData.burnAmount} ${token.info.tokenTicker}?`} | description={`Burn ${formData.burnAmount} ${tokenTicker}?`} | ||||
handleOk={burn} | handleOk={burn} | ||||
handleCancel={() => setShowConfirmBurnEtoken(false)} | handleCancel={() => | ||||
setShowConfirmBurnEtoken(false) | |||||
} | |||||
showCancelButton | showCancelButton | ||||
height={250} | height={250} | ||||
> | > | ||||
<ModalInput | <ModalInput | ||||
placeholder={`Type "burn ${token.info.tokenTicker}" to confirm`} | placeholder={`Type "burn ${tokenTicker}" to confirm`} | ||||
name="etokenToBeBurnt" | name="etokenToBeBurnt" | ||||
value={confirmationOfEtokenToBeBurnt} | value={confirmationOfEtokenToBeBurnt} | ||||
error={burnConfirmationError} | error={burnConfirmationError} | ||||
handleInput={handleBurnConfirmationInput} | handleInput={handleBurnConfirmationInput} | ||||
/> | /> | ||||
</Modal> | </Modal> | ||||
)} | )} | ||||
<BalanceHeaderToken | <BalanceHeaderToken | ||||
balance={new BN(token.balance)} | balance={new BN(tokenBalance)} | ||||
ticker={token.info.tokenTicker} | ticker={tokenTicker} | ||||
tokenDecimals={token.info.decimals} | tokenDecimals={decimals} | ||||
/> | /> | ||||
<TokenStatsTable | <TokenStatsTable | ||||
title={`Token info for "${token.info.tokenName}"`} | title={`Token info for "${tokenName}"`} | ||||
> | > | ||||
<TokenStatsRow> | <TokenStatsRow> | ||||
<TokenStatsCol colSpan={2}> | <TokenStatsCol colSpan={2}> | ||||
<CopyToClipboard data={token.tokenId} showToast> | <CopyToClipboard data={tokenId} showToast> | ||||
<TokenIcon size={128} tokenId={tokenId} /> | <TokenIcon | ||||
size={128} | |||||
tokenId={tokenId} | |||||
/> | |||||
</CopyToClipboard> | </CopyToClipboard> | ||||
</TokenStatsCol> | </TokenStatsCol> | ||||
</TokenStatsRow> | </TokenStatsRow> | ||||
<TokenStatsRow> | <TokenStatsRow> | ||||
<TokenStatsCol> | <TokenStatsCol> | ||||
Token Id: {token.tokenId.slice(0, 3)}... | Token Id: {tokenId.slice(0, 3)}... | ||||
{token.tokenId.slice(-3)} | {tokenId.slice(-3)} | ||||
</TokenStatsCol> | </TokenStatsCol> | ||||
<TokenStatsCol> | <TokenStatsCol> | ||||
<CopyToClipboard data={token.tokenId} showToast> | <CopyToClipboard data={tokenId} showToast> | ||||
<ThemedCopySolid /> | <ThemedCopySolid /> | ||||
</CopyToClipboard> | </CopyToClipboard> | ||||
</TokenStatsCol> | </TokenStatsCol> | ||||
</TokenStatsRow> | </TokenStatsRow> | ||||
<TokenStatsRow> | <TokenStatsRow> | ||||
<TokenStatsCol> | <TokenStatsCol> | ||||
{token.info.decimals} decimal places | {decimals} decimal places | ||||
</TokenStatsCol> | </TokenStatsCol> | ||||
</TokenStatsRow> | </TokenStatsRow> | ||||
<TokenStatsRow>{url}</TokenStatsRow> | |||||
{tokenStats && ( | |||||
<> | |||||
<TokenStatsRow> | |||||
{tokenStats.genesisInfo.url} | |||||
</TokenStatsRow> | |||||
<TokenStatsRow> | <TokenStatsRow> | ||||
Minted{' '} | Minted{' '} | ||||
{tokenStats.block && | {typeof cachedInfo.block !== 'undefined' | ||||
tokenStats.block.timestamp !== null | |||||
? formatDate( | ? formatDate( | ||||
tokenStats.block.timestamp, | cachedInfo.block.timestamp, | ||||
navigator.language, | navigator.language, | ||||
) | ) | ||||
: 'Just now (Genesis tx confirming)'} | : formatDate( | ||||
cachedInfo.timeFirstSeen, | |||||
navigator.language, | |||||
)} | |||||
</TokenStatsRow> | </TokenStatsRow> | ||||
</> | <TokenStatsRow> | ||||
Genesis Supply:{' '} | |||||
{decimalizedTokenQtyToLocaleFormat( | |||||
genesisSupply, | |||||
userLocale, | |||||
)} | )} | ||||
</TokenStatsRow> | |||||
<TokenStatsRow> | |||||
{genesisMintBatons === 0 | |||||
? 'Fixed Supply' | |||||
: 'Variable Supply'} | |||||
</TokenStatsRow> | |||||
</TokenStatsTable> | </TokenStatsTable> | ||||
<InputWithScanner | <InputWithScanner | ||||
placeholder={ | placeholder={ | ||||
aliasSettings.aliasEnabled | aliasSettings.aliasEnabled | ||||
? `Address or Alias` | ? `Address or Alias` | ||||
: `Address` | : `Address` | ||||
} | } | ||||
name="address" | name="address" | ||||
value={formData.address} | value={formData.address} | ||||
handleInput={handleTokenAddressChange} | handleInput={handleTokenAddressChange} | ||||
error={sendTokenAddressError} | error={sendTokenAddressError} | ||||
loadWithScannerOpen={openWithScanner} | loadWithScannerOpen={openWithScanner} | ||||
/> | /> | ||||
<AliasAddressPreviewLabel> | <AliasAddressPreviewLabel> | ||||
<TxLink | <TxLink | ||||
key={aliasInputAddress} | key={aliasInputAddress} | ||||
href={`${explorer.blockExplorerUrl}/address/${aliasInputAddress}`} | href={`${explorer.blockExplorerUrl}/address/${aliasInputAddress}`} | ||||
target="_blank" | target="_blank" | ||||
rel="noreferrer" | rel="noreferrer" | ||||
> | > | ||||
{aliasInputAddress && | {aliasInputAddress && | ||||
`${aliasInputAddress.slice( | `${aliasInputAddress.slice( | ||||
0, | 0, | ||||
10, | 10, | ||||
)}...${aliasInputAddress.slice(-5)}`} | )}...${aliasInputAddress.slice(-5)}`} | ||||
</TxLink> | </TxLink> | ||||
</AliasAddressPreviewLabel> | </AliasAddressPreviewLabel> | ||||
<br /> | <br /> | ||||
<SendTokenInput | <SendTokenInput | ||||
name="amount" | name="amount" | ||||
value={formData.amount} | value={formData.amount} | ||||
error={sendTokenAmountError} | error={sendTokenAmountError} | ||||
placeholder="Amount" | placeholder="Amount" | ||||
decimals={token.info.decimals} | decimals={decimals} | ||||
handleInput={handleSlpAmountChange} | handleInput={handleSlpAmountChange} | ||||
handleOnMax={onMax} | handleOnMax={onMax} | ||||
/> | /> | ||||
<SecondaryButton | <SecondaryButton | ||||
style={{ marginTop: '24px' }} | style={{ marginTop: '24px' }} | ||||
disabled={ | disabled={ | ||||
apiError || | apiError || | ||||
sendTokenAmountError || | sendTokenAmountError || | ||||
sendTokenAddressError | sendTokenAddressError | ||||
} | } | ||||
onClick={() => checkForConfirmationBeforeSendEtoken()} | onClick={() => | ||||
checkForConfirmationBeforeSendEtoken() | |||||
} | |||||
> | > | ||||
Send {token.info.tokenName} | Send {tokenName} | ||||
</SecondaryButton> | </SecondaryButton> | ||||
{apiError && <ApiError />} | {apiError && <ApiError />} | ||||
<TokenStatsTable | <TokenStatsTable | ||||
title={`Token info for "${token.info.tokenName}"`} | title={`Token info for "${tokenName}"`} | ||||
> | > | ||||
<TokenStatsRow> | <TokenStatsRow> | ||||
<Link | <Link | ||||
style={{ width: '100%' }} | style={{ width: '100%' }} | ||||
to="/airdrop" | to="/airdrop" | ||||
state={{ | state={{ | ||||
airdropEtokenId: token.tokenId, | airdropEtokenId: tokenId, | ||||
}} | }} | ||||
> | > | ||||
<PrimaryButton style={{ marginTop: '12px' }}> | <PrimaryButton | ||||
style={{ marginTop: '12px' }} | |||||
> | |||||
Airdrop | Airdrop | ||||
</PrimaryButton> | </PrimaryButton> | ||||
</Link> | </Link> | ||||
</TokenStatsRow> | </TokenStatsRow> | ||||
<TokenStatsRow> | <TokenStatsRow> | ||||
<InputFlex> | <InputFlex> | ||||
<SendTokenInput | <SendTokenInput | ||||
name="burnAmount" | name="burnAmount" | ||||
value={formData.burnAmount} | value={formData.burnAmount} | ||||
error={burnTokenAmountError} | error={burnTokenAmountError} | ||||
placeholder="Burn Amount" | placeholder="Burn Amount" | ||||
decimals={token.info.decimals} | decimals={decimals} | ||||
handleInput={handleEtokenBurnAmountChange} | handleInput={ | ||||
handleEtokenBurnAmountChange | |||||
} | |||||
handleOnMax={onMaxBurn} | handleOnMax={onMaxBurn} | ||||
/> | /> | ||||
<Button | <Button | ||||
type="primary" | type="primary" | ||||
onClick={handleBurnAmountInput} | onClick={handleBurnAmountInput} | ||||
danger | danger | ||||
> | > | ||||
Burn | Burn | ||||
{token.info.tokenTicker} | {tokenTicker} | ||||
</Button> | </Button> | ||||
</InputFlex> | </InputFlex> | ||||
</TokenStatsRow> | </TokenStatsRow> | ||||
</TokenStatsTable> | </TokenStatsTable> | ||||
</SidePaddingCtn> | </SidePaddingCtn> | ||||
)} | )} | ||||
</> | </> | ||||
); | ); | ||||
}; | }; | ||||
export default SendToken; | export default SendToken; |