diff --git a/VERSION b/VERSION index 489200c52..f1cb5ae0c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0.9 \ No newline at end of file +3.0.10 \ No newline at end of file diff --git a/package.json b/package.json index c87c60e92..b177168c1 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "apollo-web-wallet", - "version": "3.0.9", + "version": "3.0.10", "private": true, "dependencies": { "@fortawesome/fontawesome-free": "5.13.0", - "apl-web-crypto": "^1.0.2", + "apl-web-crypto": "^1.0.29", "axios": "0.19.2", "bignumber.js": "9.1.0", "bootstrap": "4.4.1", diff --git a/packaging/pkg-apollo-web-ui.json b/packaging/pkg-apollo-web-ui.json index 3b47577ce..696c24bcb 100644 --- a/packaging/pkg-apollo-web-ui.json +++ b/packaging/pkg-apollo-web-ui.json @@ -1,6 +1,6 @@ { "name": "apollo-web-ui", "description": "Apollo blockchain web UI", - "version": "3.0.9", + "version": "3.0.10", "dependencies": [ ] } diff --git a/src/containers/components/form-components/MessageInputs/index.jsx b/src/containers/components/form-components/MessageInputs/index.jsx index c4cbda6bc..6f190c037 100644 --- a/src/containers/components/form-components/MessageInputs/index.jsx +++ b/src/containers/components/form-components/MessageInputs/index.jsx @@ -1,7 +1,7 @@ import React from 'react'; import { useFormikContext } from 'formik'; import CheckboxWithFormik from 'containers/components/check-button-input/CheckboxWithFormik'; -import CustomTextArea from '../TextArea'; +import CustomTextArea from '../TextArea/TextAreaWithFormik'; export const MessageInputs = ({ idGroup }) => { const { values } = useFormikContext(); diff --git a/src/containers/modals/currencies/transfer-currency/handle-form-submit/index.js b/src/containers/modals/currencies/transfer-currency/handle-form-submit/index.js index a79df376a..65aa656a9 100644 --- a/src/containers/modals/currencies/transfer-currency/handle-form-submit/index.js +++ b/src/containers/modals/currencies/transfer-currency/handle-form-submit/index.js @@ -1,17 +1,34 @@ import { NotificationManager } from 'react-notifications'; import { setBodyModalParamsAction } from 'modules/modals'; -import submitForm from 'helpers/forms/forms'; import { setModalProcessingTrueAction, setModalProcessingFalseAction } from 'actions/modals'; +import { sendCurrencyTransferOffline, checkIsVaultWallet } from 'helpers/transactions'; +import { getAccountRsSelector, getPassPhraseSelector } from 'selectors'; +import submitForm from 'helpers/forms/forms'; + +export const handleFormSubmit = ({decimals, ...values}) => async (dispatch, getState) => { + const state = getState(); + const accountRS = getAccountRsSelector(state); + const passPhrase = getPassPhraseSelector(state); -export const handleFormSubmit = ({decimals, ...values}) => async dispatch => { const data = { ...values, units: values.units * (10 ** (decimals || 0)), }; - dispatch(setModalProcessingTrueAction()) + dispatch(setModalProcessingTrueAction()); + + const isVaultWallet = checkIsVaultWallet(data.secretPhrase, accountRS); + let res = null; + + try { + res = isVaultWallet ? + await dispatch(submitForm.submitForm(data, 'transferCurrency')) : await sendCurrencyTransferOffline(data, accountRS, passPhrase); + } catch (e) { + dispatch(setModalProcessingFalseAction()); + NotificationManager.error(e.message, 'Error', 5000); + return; + } - const res = await dispatch(submitForm.submitForm(data, 'transferCurrency')); if (res && res.errorCode) { dispatch(setModalProcessingFalseAction()) NotificationManager.error(res.errorDescription, 'Error', 5000); diff --git a/src/containers/modals/send-apollo/index.jsx b/src/containers/modals/send-apollo/index.jsx index 45e9f08a9..0eafc4901 100644 --- a/src/containers/modals/send-apollo/index.jsx +++ b/src/containers/modals/send-apollo/index.jsx @@ -8,21 +8,25 @@ import { useSelector, useDispatch, shallowEqual } from 'react-redux'; import { NotificationManager } from 'react-notifications'; import { setBodyModalParamsAction } from 'modules/modals'; import { - getAccountSelector, + getAccountRsSelector, getDecimalsSelector, getModalDataSelector, + getPassPhraseSelector, getTickerSelector } from 'selectors'; import ModalBody from 'containers/components/modals/modal-body'; import {PrivateTransactionConfirm} from './PrivateTransactionConfirm/PrivateTransactionConfirm'; import SendApolloForm from './form'; +import { sendMoneyOfflineTransaction, checkIsVaultWallet } from 'helpers/transactions'; +import { setModalProcessingFalseAction, setModalProcessingTrueAction } from 'actions/modals'; export default function SendApollo({ closeModal, processForm }) { const [ isShowNotification, setIsShowNotification ] = useState(false); const dispatch = useDispatch(); const modalData = useSelector(getModalDataSelector, shallowEqual); - const account = useSelector(getAccountSelector); + const accountRS = useSelector(getAccountRsSelector); + const passPhrase = useSelector(getPassPhraseSelector); const ticker = useSelector(getTickerSelector); const decimals = useSelector(getDecimalsSelector); @@ -33,20 +37,40 @@ export default function SendApollo({ closeModal, processForm }) { return; } - if (values.doNotSign) { - data.publicKey = await crypto.getPublicKeyAPL(account, true); - delete data.secretPhrase; - } - - if (values.phasingFinishHeight) { - data.phased = true; - } + dispatch(setModalProcessingTrueAction()) if (values.alias) { data.recipient = values.alias; } - processForm({ decimals, ...data }, 'sendMoney', 'Transaction has been submitted!', res => { + const isVaultWallet = checkIsVaultWallet(data.secretPhrase, accountRS); + + if (isVaultWallet) { + processForm({ decimals, ...data }, 'sendMoney', 'Transaction has been submitted!', res => { + if (res.broadcasted === false) { + dispatch(setBodyModalParamsAction('RAW_TRANSACTION_DETAILS', { + request: data, + result: res, + })); + } else { + closeModal(); + } + + NotificationManager.success('Transaction has been submitted!', null, 5000); + }); + return; + } + + try { + const res = await sendMoneyOfflineTransaction(data, accountRS, passPhrase); + + dispatch(setModalProcessingFalseAction()); + + if (res && res.errorCode) { + NotificationManager.error(res.errorDescription, 'Error', 5000); + return; + } + if (res.broadcasted === false) { dispatch(setBodyModalParamsAction('RAW_TRANSACTION_DETAILS', { request: data, @@ -55,10 +79,13 @@ export default function SendApollo({ closeModal, processForm }) { } else { closeModal(); } - + NotificationManager.success('Transaction has been submitted!', null, 5000); - }); - }, [account, closeModal, decimals, dispatch, processForm]); + } catch (e) { + dispatch(setModalProcessingFalseAction()); + NotificationManager.error('Transaction error', 'Error', 5000); + } + }, [closeModal, decimals, dispatch, passPhrase, accountRS, processForm]); const handleShowNotification = (value) => () => { setIsShowNotification(value); diff --git a/src/helpers/transactions/index.js b/src/helpers/transactions/index.js new file mode 100644 index 000000000..765840e68 --- /dev/null +++ b/src/helpers/transactions/index.js @@ -0,0 +1,121 @@ +import { Crypto, Transaction } from 'apl-web-crypto'; +import { ONE_APL } from 'constants/constants'; +import LocalCrypto from '../crypto/crypto'; +import converters from '../converters'; + +export const checkIsVaultWallet = (secretPhrase, accountRS) => { + const publicKey = Crypto.getPublicKey(secretPhrase); + const userFromPublicKey = Crypto.getAccountIdFromPublicKey(publicKey, true); + return accountRS !== userFromPublicKey; +} + +// check user secretPhase and compare account RS from publicKey and from request +// return publicKey +const checkAccountForOfflineSignAndPublicKey = (secretPhrase, accountRS, appPassPhraseFromStore) => { + + if (!secretPhrase) throw new Error('Secret phrase is empty'); + + if (!accountRS) throw new Error('Set sender account'); + + if(appPassPhraseFromStore && secretPhrase !== appPassPhraseFromStore) { + throw new Error('Incorrect secret phrase') + } + + const publicKey = Crypto.getPublicKey(secretPhrase); + + const userFromPublicKey = Crypto.getAccountIdFromPublicKey(publicKey, true); + + if (accountRS !== userFromPublicKey) { + throw new Error('Incorrect secret phrase'); + } + return publicKey; +} + +export const sendMoneyOfflineTransaction = async ( + { + secretPhrase, + amountATM, + feeATM, + deadline = 1440, + recipient, + add_message, + encrypt_message, + permanent_message, + message, + }, + accountRS, + appPassPhraseFromStore +) => { + const publicKey = checkAccountForOfflineSignAndPublicKey(secretPhrase, accountRS, appPassPhraseFromStore); + + const data = { + publicKey, + requestType: 'sendMoney', + amountATM: amountATM * ONE_APL, + feeATM: feeATM * ONE_APL, + deadline, + recipient, + }; + + + if (add_message && encrypt_message) { + const privateKey = Crypto.getPrivateKey(secretPhrase); + + const encrypted = LocalCrypto.encryptDataAPL(converters.stringToByteArray(message), { + privateKey, + publicKey, + }) + + data.encryptedMessageData = converters.byteArrayToHexString(encrypted.data); + data.encryptedMessageNonce = converters.byteArrayToHexString(encrypted.nonce); + + // if (recipient === accountRS) { + // data.messageToEncryptToSelfIsText = "true" + // } else { + data.messageToEncryptIsText = "true" + // } + } + + if (add_message) { + data.message = message; + } + + if (add_message && permanent_message) { + data.permanent_message = permanent_message; + } + + const unsignedTransactionData = await Transaction.sendNotSign(data); + const sendData = {secretPhrase: secretPhrase }; + const signedResponse = await Transaction.processOfflineSign(sendData, unsignedTransactionData); + + const dataTransaction = { + requestType: 'broadcastTransaction', + transactionBytes: signedResponse.transactionBytes, + } + return await Transaction.send(dataTransaction); +} + +export const sendCurrencyTransferOffline = async ( + {secretPhrase, feeATM,...initialData}, + accountRS, + appPassPhraseFromStore +) => { + const publicKey = checkAccountForOfflineSignAndPublicKey(secretPhrase, accountRS, appPassPhraseFromStore); + + const unsignedTransactionData = await Transaction.sendNotSign({ + ...initialData, + publicKey, + requestType: 'transferCurrency', + deadline: 1440, + feeATM: feeATM * ONE_APL, + }); + + const sendData = {secretPhrase: secretPhrase }; + const signedResponse = await Transaction.processOfflineSign(sendData, unsignedTransactionData); + + const dataTransaction = { + requestType: 'broadcastTransaction', + transactionBytes: signedResponse.transactionBytes, + } + return await Transaction.send(dataTransaction); +} \ No newline at end of file