function handleSendXecError(errorObj, oneToManyFlag) {
// Set loading to false here as well, as balance may not change depending on where error occured in try loop
passLoadingStatus(false);
let message;
if (!errorObj.error && !errorObj.message) {
message = `Transaction failed: no response from ${getRestUrl()}.`;
} else if (
/Could not communicate with full node or other external service/.test(
errorObj.error,
)
) {
message = 'Could not communicate with API. Please try again.';
} else if (
errorObj.error &&
errorObj.error.includes(
'too-long-mempool-chain, too many unconfirmed ancestors [limit: 50] (code 64)',
)
) {
message = `The ${currency.ticker} you are trying to send has too many unconfirmed ancestors to send (limit 50). Sending will be possible after a block confirmation. Try again in about 10 minutes.`;
? `are you sure you want to send the following One to Many transaction?
${formData.address}`
: `Are you sure you want to send ${formData.value}${' '}
${selectedCurrency} to ${formData.address}?`}
</p>
</Modal>
<WalletInfoCtn>
<WalletLabel name={wallet.name}></WalletLabel>
{!balances.totalBalance ? (
<ZeroBalanceHeader>
You currently have 0 {currency.ticker}
<br />
Deposit some funds to use this feature
</ZeroBalanceHeader>
) : (
<>
<BalanceHeader
balance={balances.totalBalance}
ticker={currency.ticker}
/>
<BalanceHeaderFiat
balance={balances.totalBalance}
settings={cashtabSettings}
fiatPrice={fiatPrice}
/>
</>
)}
</WalletInfoCtn>
<SidePaddingCtn>
<Row type="flex">
<Col span={24}>
<Form
style={{
width: 'auto',
marginTop: '40px',
}}
>
{!isOneToManyXECSend ? (
<SendInputCtn>
<FormLabel>Send to</FormLabel>
<DestinationAddressSingle
style={{ marginBottom: '0px' }}
loadWithCameraOpen={
location &&
location.state &&
location.state.replyAddress
? false
: scannerSupported
}
validateStatus={
sendBchAddressError ? 'error' : ''
}
help={
sendBchAddressError
? sendBchAddressError
: ''
}
onScan={result =>
handleAddressChange({
target: {
name: 'address',
value: result,
},
})
}
inputProps={{
placeholder: `${currency.ticker} Address`,
name: 'address',
onChange: e =>
handleAddressChange(e),
required: true,
value: formData.address,
}}
></DestinationAddressSingle>
<FormLabel>Amount</FormLabel>
<SendBchInput
activeFiatCode={
cashtabSettings &&
cashtabSettings.fiatCurrency
? cashtabSettings.fiatCurrency.toUpperCase()
: 'USD'
}
validateStatus={
sendBchAmountError ? 'error' : ''
}
help={
sendBchAmountError
? sendBchAmountError
: ''
}
onMax={onMax}
inputProps={{
name: 'value',
dollar:
selectedCurrency === 'USD'
? 1
: 0,
placeholder: 'Amount',
onChange: e =>
handleBchAmountChange(e),
required: true,
value: formData.value,
disabled: priceApiError,
}}
selectProps={{
value: selectedCurrency,
disabled: queryStringText !== null,
onChange: e =>
handleSelectedCurrencyChange(e),
}}
></SendBchInput>
{priceApiError && (
<AlertMsg>
Error fetching fiat price. Setting
send by{' '}
{currency.fiatCurrencies[
cashtabSettings.fiatCurrency
].slug.toUpperCase()}{' '}
disabled
</AlertMsg>
)}
</SendInputCtn>
) : (
<>
<FormLabel>Send to</FormLabel>
<DestinationAddressMulti
validateStatus={
sendBchAddressError ? 'error' : ''
}
help={
sendBchAddressError
? sendBchAddressError
: ''
}
inputProps={{
placeholder: `One XEC address & value per line, separated by comma \ne.g. \necash:qpatql05s9jfavnu0tv6lkjjk25n6tmj9gkpyrlwu8,500 \necash:qzvydd4n3lm3xv62cx078nu9rg0e3srmqq0knykfed,700`,
name: 'address',
onChange: e =>
handleMultiAddressChange(e),
required: true,
value: formData.address,
}}
></DestinationAddressMulti>
</>
)}
{!priceApiError && !isOneToManyXECSend && (
<AmountPreviewCtn>
<LocaleFormattedValue>
{formatBalance(
formData.value,
userLocale,
)}{' '}
{selectedCurrency}
</LocaleFormattedValue>
<ConvertAmount>
{fiatPriceString !== '' && '='}{' '}
{fiatPriceString}
</ConvertAmount>
</AmountPreviewCtn>
)}
{queryStringText && (
<Alert
message={`You are sending a transaction to an address including query parameters "${queryStringText}." Only the "amount" parameter, in units of ${currency.ticker} satoshis, is currently supported.`}
type="warning"
/>
)}
<div
style={{
paddingTop: '12px',
}}
>
{!balances.totalBalance ||
apiError ||
sendBchAmountError ||
sendBchAddressError ||
priceApiError ? (
<DisabledButton>Send</DisabledButton>
) : (
<>
{txInfoFromUrl ? (
<PrimaryButton
onClick={() =>
checkForConfirmationBeforeSendXec()
}
>
Send
</PrimaryButton>
) : (
<PrimaryButton
onClick={() => {
checkForConfirmationBeforeSendXec();
}}
>
Send
</PrimaryButton>
)}
</>
)}
</div>
<CustomCollapseCtn
panelHeader="Advanced"
optionalDefaultActiveKey={
location &&
location.state &&
location.state.replyAddress
? ['1']
: ['0']
}
optionalKey="1"
>
<AntdFormWrapper
style={{
marginBottom: '20px',
}}
>
<TextAreaLabel>
Multiple Recipients:
<Switch
defaultunchecked="true"
checked={isOneToManyXECSend}
onChange={() => {
setIsOneToManyXECSend(
!isOneToManyXECSend,
);
setIsEncryptedOptionalOpReturnMsg(
false,
);
}}
style={{
marginBottom: '7px',
}}
/>
</TextAreaLabel>
<TextAreaLabel>
Message:
<Switch
disabled={isOneToManyXECSend}
style={{
marginBottom: '7px',
}}
checkedChildren="Private"
unCheckedChildren="Public"
defaultunchecked="true"
checked={
isEncryptedOptionalOpReturnMsg
}
onChange={() => {
setIsEncryptedOptionalOpReturnMsg(
prev => !prev,
);
setIsOneToManyXECSend(false);
}}
/>
</TextAreaLabel>
{isEncryptedOptionalOpReturnMsg ? (
<Alert
style={{
marginBottom: '10px',
}}
description="Please note encrypted messages can only be sent to wallets with at least 1 outgoing transaction."
type="warning"
showIcon
/>
) : (
<Alert
style={{
marginBottom: '10px',
}}
description="Please note this message will be public."
// based on ecies-lite's encryption logic, the encryption buffer is concatenated as follows:
// [ epk + iv + ct + mac ] whereby:
// - The first 32 or 64 chars of the encryptionBuffer is the epk
// - Both iv and ct params are 16 chars each, hence their combined substring is 32 chars from the end of the epk string
// - within this combined iv/ct substring, the first 16 chars is the iv param, and ct param being the later half
// - The mac param is appended to the end of the encryption buffer
// validate input buffer
if (!encryptionBuffer) {
throw new Error(
'cashmethods.convertToEncryptStruct() error: input must be a buffer',
);
}
try {
// variable tracking the starting char position for string extraction purposes
let startOfBuf = 0;
// *** epk param extraction ***
// The first char of the encryptionBuffer indicates the type of the public key
// If the first char is 4, then the public key is 64 chars
// If the first char is 3 or 2, then the public key is 32 chars
// Otherwise this is not a valid encryption buffer compatible with the ecies-lite library
let publicKey;
switch (encryptionBuffer[0]) {
case 4:
publicKey = encryptionBuffer.slice(0, 65); // extract first 64 chars as public key
break;
case 3:
case 2:
publicKey = encryptionBuffer.slice(0, 33); // extract first 32 chars as public key
break;
default:
throw new Error(`Invalid type: ${encryptionBuffer[0]}`);
}
// *** iv and ct param extraction ***
startOfBuf += publicKey.length; // sets the starting char position to the end of the public key (epk) in order to extract subsequent iv and ct substrings
const encryptionTagLength = 32; // the length of the encryption tag (i.e. mac param) computed from each block of ciphertext, and is used to verify no one has tampered with the encrypted data
const ivCtSubstring = encryptionBuffer.slice(
startOfBuf,
encryptionBuffer.length - encryptionTagLength,
); // extract the substring containing both iv and ct params, which is after the public key but before the mac param i.e. the 'encryption tag'
const ivbufParam = ivCtSubstring.slice(0, 16); // extract the first 16 chars of substring as the iv param
const ctbufParam = ivCtSubstring.slice(16); // extract the last 16 chars as substring the ct param
// *** mac param extraction ***
const macParam = encryptionBuffer.slice(
encryptionBuffer.length - encryptionTagLength,
encryptionBuffer.length,
); // extract the mac param appended to the end of the buffer