From ea242556a00380a9b8cd27072e91b632046c360b Mon Sep 17 00:00:00 2001 From: Alex Ruzenhack Date: Thu, 23 May 2024 16:03:14 +0100 Subject: [PATCH] wip: working on NewNanoContractTransactionContent feat: implement address selection modal feat: prepare for wallet connect integration feat: set show modal mechanics chore: rename modal chore: rename content to request feat: break down components --- package-lock.json | 150 +++--- package.json | 2 +- src/App.js | 4 + src/actions.js | 27 + src/components/HathorFlatList.js | 2 +- src/components/Icon/CircleInfo.icon.js | 2 +- ...ontractTransactionBalanceList.component.js | 4 +- ...NanoContractTransactionHeader.component.js | 2 +- .../WalletConnect/NanoContractTransaction.js | 1 - .../NewNanoContractTransactionModal.js | 163 ++++++ .../NewNanoContractTransactionRequest.js | 472 ++++++++++++++++++ src/components/WalletConnect/theme.js | 71 +++ src/reducers/reducer.js | 28 ++ .../NewNanoContractTransaction.screen.js | 45 ++ 14 files changed, 895 insertions(+), 78 deletions(-) delete mode 100644 src/components/WalletConnect/NanoContractTransaction.js create mode 100644 src/components/WalletConnect/NewNanoContractTransactionModal.js create mode 100644 src/components/WalletConnect/NewNanoContractTransactionRequest.js create mode 100644 src/components/WalletConnect/theme.js create mode 100644 src/screens/WalletConnect/NewNanoContractTransaction.screen.js diff --git a/package-lock.json b/package-lock.json index c78b02124..e933ecc8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@fortawesome/free-solid-svg-icons": "6.4.0", "@fortawesome/react-native-fontawesome": "0.2.7", "@hathor/unleash-client": "0.1.0", - "@hathor/wallet-lib": "1.0.1", + "@hathor/wallet-lib": "1.5.0", "@notifee/react-native": "5.7.0", "@react-native-async-storage/async-storage": "1.19.0", "@react-native-firebase/app": "16.7.0", @@ -2550,20 +2550,24 @@ } }, "node_modules/@hathor/wallet-lib": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@hathor/wallet-lib/-/wallet-lib-1.0.1.tgz", - "integrity": "sha512-Pfjq0oks3qxD2BJXNYv7X6jv0oIW0vdSBvpkLMVsKQ/VikXV2RHP+av2vQdnkC2lMdkvBeG/gtoREF21tQzing==", - "dependencies": { - "axios": "^0.21.4", - "bitcore-lib": "^8.25.10", - "bitcore-mnemonic": "^8.25.10", - "buffer": "^6.0.3", - "crypto-js": "^3.1.9-1", - "isomorphic-ws": "^4.0.1", - "level": "^8.0.0", - "lodash": "^4.17.21", - "long": "^4.0.0", - "ws": "^7.5.9" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@hathor/wallet-lib/-/wallet-lib-1.5.0.tgz", + "integrity": "sha512-H0wOuzaS+iUhADGG8fxvNpLddkM0BFjCAjt1nLml5Oe6DkTj5VtA2a7+aT1DR4qxUcPRmCo9D+azbMDiwbXtIA==", + "dependencies": { + "axios": "1.6.8", + "bitcore-lib": "8.25.10", + "bitcore-mnemonic": "8.25.10", + "buffer": "6.0.3", + "crypto-js": "4.2.0", + "isomorphic-ws": "4.0.1", + "level": "8.0.0", + "lodash": "4.17.21", + "long": "4.0.0", + "ws": "7.5.9" + }, + "engines": { + "node": ">=20.0.0", + "npm": ">=10.0.0" } }, "node_modules/@hathor/wallet-lib/node_modules/buffer": { @@ -2590,9 +2594,9 @@ } }, "node_modules/@hathor/wallet-lib/node_modules/crypto-js": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", - "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.11", @@ -7340,6 +7344,11 @@ "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -7361,11 +7370,13 @@ } }, "node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", "dependencies": { - "follow-redirects": "^1.14.0" + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/axobject-query": { @@ -7843,9 +7854,9 @@ ] }, "node_modules/bech32": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", - "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.3.tgz", + "integrity": "sha512-yuVFUvrNcoJi0sv5phmqc6P+Fl1HjRDRNOOkHY2X/3LBy2bIGNSFx4fZ95HMaXHupuS7cZR15AsvtmCIF4UEyg==" }, "node_modules/big-integer": { "version": "1.6.51", @@ -7855,33 +7866,12 @@ "node": ">=0.6" } }, - "node_modules/bigi": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/bigi/-/bigi-1.4.2.tgz", - "integrity": "sha512-ddkU+dFIuEIW8lE7ZwdIAf2UPoM90eaprg5m3YXAVVTmKlqV/9BX4A2M8BOK2yOq6/VgZFVhK6QAxJebhlbhzw==" - }, - "node_modules/bip-schnorr": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/bip-schnorr/-/bip-schnorr-0.6.4.tgz", - "integrity": "sha512-dNKw7Lea8B0wMIN4OjEmOk/Z5qUGqoPDY0P2QttLqGk1hmDPytLWW8PR5Pb6Vxy6CprcdEgfJpOjUu+ONQveyg==", - "dependencies": { - "bigi": "^1.4.2", - "ecurve": "^1.0.6", - "js-sha256": "^0.9.0", - "randombytes": "^2.1.0", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/bitcore-lib": { - "version": "8.25.47", - "resolved": "https://registry.npmjs.org/bitcore-lib/-/bitcore-lib-8.25.47.tgz", - "integrity": "sha512-qDZr42HuP4P02I8kMGZUx/vvwuDsz8X3rQxXLfM0BtKzlQBcbSM7ycDkDN99Xc5jzpd4fxNQyyFXOmc6owUsrQ==", + "version": "8.25.10", + "resolved": "https://registry.npmjs.org/bitcore-lib/-/bitcore-lib-8.25.10.tgz", + "integrity": "sha512-MyHpSg7aFRHe359RA/gdkaQAal3NswYZTLEuu0tGX1RGWXAYN9i/24fsjPqVKj+z0ua+gzAT7aQs0KiKXWCgKA==", "dependencies": { - "bech32": "=2.0.0", - "bip-schnorr": "=0.6.4", + "bech32": "=1.1.3", "bn.js": "=4.11.8", "bs58": "^4.0.1", "buffer-compare": "=1.1.1", @@ -7891,11 +7881,11 @@ } }, "node_modules/bitcore-mnemonic": { - "version": "8.25.47", - "resolved": "https://registry.npmjs.org/bitcore-mnemonic/-/bitcore-mnemonic-8.25.47.tgz", - "integrity": "sha512-wTa0imZZpFTqwlpyokvU8CNl+YdaIvQIrWKp/0AEL9gPX2vuzBnE+U8Ok6D5lHCnbG6dvmoesmtyf6R3aYI86A==", + "version": "8.25.10", + "resolved": "https://registry.npmjs.org/bitcore-mnemonic/-/bitcore-mnemonic-8.25.10.tgz", + "integrity": "sha512-FeXxO37BLV5JRvxPmVFB91zRHalavV8H4TdQGt1/hz0AkoPymIV68OkuB+TptpjeYgatcgKPoPvPhglJkTzFQQ==", "dependencies": { - "bitcore-lib": "^8.25.47", + "bitcore-lib": "^8.25.10", "unorm": "^1.4.1" }, "peerDependencies": { @@ -8502,6 +8492,17 @@ "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/command-exists": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", @@ -9016,6 +9017,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -9180,15 +9189,6 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, - "node_modules/ecurve": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/ecurve/-/ecurve-1.0.6.tgz", - "integrity": "sha512-/BzEjNfiSuB7jIWKcS/z8FK9jNjmEWvUV2YZ4RLSmcDtP7Lq0m6FvDuSnJpBlDpGRpfRQeTLGLBI8H+kEv0r+w==", - "dependencies": { - "bigi": "^1.1.0", - "safe-buffer": "^5.0.1" - } - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -10522,9 +10522,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -10573,6 +10573,19 @@ "integrity": "sha512-J+ler7Ta54FwwNcx6wQRDhTIbNeyDcARMkOcguEqnEdtm0jKvN3Li3PDAb2Du3ubJYEWfYL83XMROXdsXAXycw==", "dev": true }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/formidable": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", @@ -14665,11 +14678,6 @@ "@sideway/pinpoint": "^2.0.0" } }, - "node_modules/js-sha256": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", - "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/package.json b/package.json index 47f518e47..6c3f14ba8 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "@fortawesome/free-solid-svg-icons": "6.4.0", "@fortawesome/react-native-fontawesome": "0.2.7", "@hathor/unleash-client": "0.1.0", - "@hathor/wallet-lib": "1.0.1", + "@hathor/wallet-lib": "1.5.0", "@notifee/react-native": "5.7.0", "@react-native-async-storage/async-storage": "1.19.0", "@react-native-firebase/app": "16.7.0", diff --git a/src/App.js b/src/App.js index bca114aa9..05e976dbb 100644 --- a/src/App.js +++ b/src/App.js @@ -89,6 +89,8 @@ import { NetworkStatusBar } from './components/NetworkSettings/NetworkStatusBar' import { NanoContractTransactions } from './screens/NanoContract/NanoContractTransactions.screen'; import { NanoContractTransaction } from './screens/NanoContract/NanoContractTransaction.screen'; import { NanoContractRegister } from './screens/NanoContract/NanoContractRegister.screen'; +import { NewNanoContractTransaction } from './screens/WalletConnect/NewNanoContractTransaction.screen'; +import { NewNanoContractTransactionModal } from './components/WalletConnect/NewNanoContractTransactionModal'; /** * This Stack Navigator is exhibited when there is no wallet initialized on the local storage. @@ -387,6 +389,7 @@ const AppStack = () => { + ( > + diff --git a/src/actions.js b/src/actions.js index c4eb8cee1..ce191e1ef 100644 --- a/src/actions.js +++ b/src/actions.js @@ -113,6 +113,9 @@ export const types = { SET_WALLET_CONNECT: 'SET_WALLET_CONNECT', SET_WALLET_CONNECT_MODAL: 'SET_WALLET_CONNECT_MODAL', SET_WALLET_CONNECT_SESSIONS: 'SET_WALLET_CONNECT_SESSIONS', + WALLET_CONNECT_ACCEPT: 'WALLET_CONNECT_ACCEPT', + WALLET_CONNECT_REJECT: 'WALLET_CONNECT_REJECT', + SET_NEW_NANO_CONTRACT_TRANSACTION_MODAL: 'SET_NEW_NANO_CONTRACT_TRANSACTION_MODAL', SET_UNLEASH_CLIENT: 'SET_UNLEASH_CLIENT', WC_URI_INPUTTED: 'WC_URI_INPUTTED', WC_CANCEL_SESSION: 'WC_CANCEL_SESSION', @@ -217,6 +220,30 @@ export const walletConnectCancelSession = (sessionKey) => ({ payload: sessionKey, }); +/** + * @param {Object} data Data that the user has accepted. + */ +export const walletConnectAccept = (data) => ({ + type: types.WALLET_CONNECT_ACCEPT, + payload: data, +}); + +export const walletWalletReject = () => ({ + type: types.WALLET_CONNECT_REJECT, +}); + +/** + * @param {Object} ncRequest + * @param {boolean} ncRequest.show + * @param {Object} ncRequest.data + * @param {Object} ncRequest.data.nc + * @param {Object} ncRequest.data.dapp + */ +export const setNewNanoContractTransactionModal = (ncRequest) => ({ + type: types.SET_NEW_NANO_CONTRACT_TRANSACTION_MODAL, + payload: ncRequest +}); + /** * isShowingPinScreen {bool} * */ diff --git a/src/components/HathorFlatList.js b/src/components/HathorFlatList.js index 043e2dcec..f33445491 100644 --- a/src/components/HathorFlatList.js +++ b/src/components/HathorFlatList.js @@ -15,7 +15,7 @@ import { COLORS } from '../styles/themes'; * param {FlatListProps} props */ export const HathorFlatList = (props) => ( - + ( > diff --git a/src/components/NanoContract/NanoContractTransactionBalanceList.component.js b/src/components/NanoContract/NanoContractTransactionBalanceList.component.js index f1f6cb6a6..15b3dabd5 100644 --- a/src/components/NanoContract/NanoContractTransactionBalanceList.component.js +++ b/src/components/NanoContract/NanoContractTransactionBalanceList.component.js @@ -6,14 +6,14 @@ */ import React, { useEffect, useState } from 'react'; -import { StyleSheet, View, FlatList } from 'react-native'; +import { StyleSheet, View } from 'react-native'; import { useSelector } from 'react-redux'; import { transactionUtils } from '@hathor/wallet-lib'; import { t } from 'ttag'; import { COLORS } from '../../styles/themes'; import { NanoContractTransactionBalanceListItem } from './NanoContractTransactionBalanceListItem.component'; -import { HathorFlatList } from '../HathorFlatList.component'; +import { HathorFlatList } from '../HathorFlatList'; /** * Calculate the balance of a transaction diff --git a/src/components/NanoContract/NanoContractTransactionHeader.component.js b/src/components/NanoContract/NanoContractTransactionHeader.component.js index b24573c1b..f1254f76a 100644 --- a/src/components/NanoContract/NanoContractTransactionHeader.component.js +++ b/src/components/NanoContract/NanoContractTransactionHeader.component.js @@ -17,7 +17,7 @@ import { ArrowDownIcon } from '../Icon/ArrowDown.icon'; import { ArrowUpIcon } from '../Icon/ArrowUp.icon'; import { TextValue } from '../TextValue.component'; import { TextLabel } from '../TextLabel.component'; -import { TransactionStatusLabel } from '../TransactionStatusLabel.component'; +import { TransactionStatusLabel } from '../TransactionStatusLabel'; export const NanoContractTransactionHeader = ({ tx }) => { const [isShrank, toggleShrank] = useState(true); diff --git a/src/components/WalletConnect/NanoContractTransaction.js b/src/components/WalletConnect/NanoContractTransaction.js deleted file mode 100644 index b4c2dd3cf..000000000 --- a/src/components/WalletConnect/NanoContractTransaction.js +++ /dev/null @@ -1 +0,0 @@ -// TODO: implement modal... diff --git a/src/components/WalletConnect/NewNanoContractTransactionModal.js b/src/components/WalletConnect/NewNanoContractTransactionModal.js new file mode 100644 index 000000000..0b858edf5 --- /dev/null +++ b/src/components/WalletConnect/NewNanoContractTransactionModal.js @@ -0,0 +1,163 @@ +/** + * Copyright (c) Hathor Labs and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import { + StyleSheet, + View, + Text, +} from 'react-native'; +import { t } from 'ttag'; +import { useNavigation } from '@react-navigation/native'; +import { COLORS } from '../../styles/themes'; +import { CircleInfoIcon } from '../Icon/CircleInfo.icon'; + +import { ModalBase } from '../ModalBase.component'; +import SimpleButton from '../SimpleButton'; +import { useDispatch, useSelector } from 'react-redux'; +import { setNewNanoContractTransactionModal, walletWalletReject } from '../../actions'; +import { WALLET_STATUS } from '../../sagas/wallet'; + +// export const EditAddressModal = ({ item, show, onAddressChange, onDismiss }) => { +export const NewNanoContractTransactionModal = () => { + const navigation = useNavigation(); + const dispatch = useDispatch(); + const readMoreLink = 'https://docs.hathor.network/explanations/features/nano-contracts/'; + + const { + showModal, + ncTxRequest, + } = useSelector((state) => { + const { + walletStartState, + walletConnect: { + newNanoContractTransactionModal: { + show, + data, + } + }, + } = state; + const isWalletReady = walletStartState === WALLET_STATUS.READY; + + return { + showModal: show && isWalletReady, + ncTxRequest: data, + }; + }); + + const onDismiss = () => { + dispatch(walletWalletReject()); + dispatch(setNewNanoContractTransactionModal({ show: false, data: null })); + }; + + const navigatesToNewNanoContractScreen = () => { + dispatch(setNewNanoContractTransactionModal({ show: false, data: null })); + navigation.navigate('NewNanoContractTransaction', { ncTxRequest }); + }; + + return ( + + {t`New Nano Contract Transaction`} + + + + + + + + {t`Caution: There are risks associated with signing dapp transaction requests.`} + + + {}} + /> + + + + + {t`You have received a new Nano Contract Transaction. Please`} + + {' '}{t`carefully review the details`}{' '} + + {t`before deciding to accept or decline.`} + + + + + + ); +}; + +const styles = StyleSheet.create({ + pd0: { + paddingBottom: 0, + }, + pd8: { + paddingBottom: 8, + }, + body: { + paddingBottom: 24, + }, + fieldContainer: { + width: '100%', + paddingBottom: 4, + }, + text: { + fontSize: 14, + lineHeight: 20, + }, + bold: { + fontWeight: 'bold', + }, + warnContainer: { + flexShrink: 1, + flexDirection: 'row', + alignItems: 'center', + marginBottom: 24, + borderRadius: 8, + paddingTop: 8, + paddingBottom: 12, + paddingHorizontal: 16, + backgroundColor: 'hsla(46, 100%, 95%, 1)', + }, + warnContent: { + paddingLeft: 8, + }, + selectionContainer: { + borderRadius: 8, + paddingVertical: 8, + paddingHorizontal: 16, + backgroundColor: COLORS.freeze100, + }, + learnMoreWrapper: { + display: 'inline-block', + /* We are using negative margin here to correct the text position + * and create an optic effect of alignment. */ + marginBottom: -4, + paddingLeft: 2, + marginRight: 'auto', + }, + learnMoreContainer: { + justifyContent: 'flex-start', + borderBottomWidth: 1, + }, + learnMoreText: { + fontSize: 14, + lineHeight: 20, + fontWeight: 'bold', + color: 'black', + }, +}); diff --git a/src/components/WalletConnect/NewNanoContractTransactionRequest.js b/src/components/WalletConnect/NewNanoContractTransactionRequest.js new file mode 100644 index 000000000..4c7ec4867 --- /dev/null +++ b/src/components/WalletConnect/NewNanoContractTransactionRequest.js @@ -0,0 +1,472 @@ +/** + * Copyright (c) Hathor Labs and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, { useMemo, useState } from 'react'; +import { StyleSheet, View, Text, ScrollView, TouchableWithoutFeedback, TouchableOpacity } from 'react-native'; +import { } from 'react-native-gesture-handler'; +import { useDispatch, useSelector } from 'react-redux'; +import { t } from 'ttag'; +import { fetchTokensMetadata, walletConnectAccept, walletWalletReject } from '../../actions'; + +import { COLORS } from '../../styles/themes'; +import { renderValue } from '../../utils'; +import { HathorFlatList } from '../HathorFlatList'; +import { NanoContractIcon } from '../Icon/NanoContract.icon'; +import { PenIcon } from '../Icon/Pen.icon'; +import { ReceivedIcon } from '../Icon/Received.icon'; +import { SentIcon } from '../Icon/Sent.icon'; +import { ModalBase } from '../ModalBase.component'; +import NewHathorButton from '../NewHathorButton'; +import { SelectAddressModal } from '../NanoContract/SelectAddressModal.component'; +import { commonStyles } from './theme'; + +const actionTitleMap = ({ tokenValue }) => ({ + deposit: t`${tokenValue} Deposit`, + withdrawal: t`${tokenValue} Withdrawal`, +}); + +/** + * @param {Object} props + * @param {Object} props.ncTxRequest + * @param {Object} props.ncTxRequest.nc + * @param {Object} props.ncTxRequest.dapp + * @param {string} props.ncTxRequest.dapp.icon + * @param {string} props.ncTxRequest.dapp.proposer + * @param {string} props.ncTxRequest.dapp.url + * @param {string} props.ncTxRequest.dapp.description + */ +export const NewNanoContractTransactionRequest = ({ ncTxRequest }) => { + // const { nc, dapp } = ncTxRequest; + // TODO: update dapp info in the view + const { nc, dapp } = { + dapp: { + icon: '', + proposer: 'dApp name', + url: '', + description: '', + }, + nc: { + ncId: '000001342d3c5b858a4d4835baea93fcc683fa615ff5892bd044459621a0340a', + blueprintId: '0025dadebe337a79006f181c05e4799ce98639aedfbd26335806790bdea4b1d4', + method: 'swap', + caller: 'HTeZeYTCv7cZ8u7pBGHkWsPwhZAuoq5j3V', + ncActions: [ + { + type: 'withdrawal', + token: '00', + amount: 323, + address: 'HTeZeYTCv7cZ8u7pBGHkWsPwhZAuoq5j3V', + }, + { + type: 'deposit', + token: '00', + amount: 892, + }, + ], + ncArgs: [ + 'AAA', + 'WZhKusv57pvzotZrf4s7yt7P7PXEqyFTHk', + '1231200', + '1x0' + ], + }, + }; + const dispatch = useDispatch(); + + const [showDeclineModal, setShowDeclineModal] = useState(false); + + // Get blueprint metadata + // const registeredNc = useSelector((state) => state.nanoContract.registered[nc.ncId]); + // if (!registeredNc) { + // throw new Error(`Nano Contract ${nc.ncId} not registered.`); + // } + // const blueprintName = registeredNc.blueprintName; + // const [ncAddress, setNcAddress] = useState(registeredNc.address); + const blueprintName = 'Swap'; + const [ncAddress, setNcAddress] = useState(nc.caller); + const ncToAccept = useMemo(() => ({ ...nc, caller: ncAddress }), [ncAddress]) + + // Controle SelectAddressModal + const [showSelectAddressModal, setShowSelectAddressModal] = useState(false); + const toggleSelectAddressModal = () => setShowSelectAddressModal(!showSelectAddressModal); + const handleAddressSelection = (newAddress) => { + setNcAddress(newAddress); + toggleSelectAddressModal(); + }; + + // Get tokens metadata + const wallet = useSelector((state) => state.wallet); + const tokensUid = nc.ncActions.map((each) => each.token); + const registeredTokensMetadata = useSelector((state) => state.tokenMetadata); + const tokensMetadata = {}; + const tokensMetadataToDownload = []; + + tokensUid.forEach((uid) => { + if (uid in registeredTokensMetadata) { + tokensMetadata[uid] = registeredTokensMetadata[uid]; + } + else { + tokensMetadataToDownload.push(uid); + } + }); + + // TODO: Implement a spinner while token's metadata are under download + if (tokensMetadataToDownload.length > 0) { + const networkName = wallet.getNetworkObject().name; + fetchTokensMetadata(tokensMetadataToDownload, networkName) + .then((metadatas) => { + metadatas.forEach((metadata) => { + tokensMetadata[metadata.uid] = metadata; + }); + }) + .catch(() => { + // TODO: dispatch error for both user feedback and Sentry + }); + } + + // Accepts the Nano Contract data preseted. + const onAcceptTransaction = () => { + // Update the caller with the address selected by the user. + const acceptedNc = { ...nc, caller: ncAddress }; + // Signal the user has accepted the current request and pass the accepted data. + dispatch(walletConnectAccept(acceptedNc)); + }; + + const onDeclineTransaction = () => setShowDeclineModal(true); + const dismissDeclineModal = () => { + setShowDeclineModal(false); + dispatch(walletWalletReject()); + // TODO: it should navigates back to dashboard + }; + + // TODO: identify the argument name given its position + + return ( + <> + + + + + + + + + {/* User actions */} + + + + + + + + + {/* Decline Modal */} + + + + ); +}; + +const DappContainer = ({ dapp }) => { + const styles = StyleSheet.create({ + container: { + gap: 16, + paddingVertical: 16, + paddingHorizontal: 16, + }, + header: { + flexDirection: 'row', + gap: 16, + }, + avatar: { + flexShrink: 1, + alignSelf: 'flex-start', + maxWidth: 48, + maxHeight: 48, + }, + avatarIcon: { + width: 48, + height: 48, + backgroundColor: 'hsla(0, 0%, 85%, 1)', + borderRadius: 24, + }, + proposer: [ + commonStyles.text, + commonStyles.bold, + commonStyles.mb4, + ], + network: [ + commonStyles.text, + { color: 'hsla(263, 100%, 64%, 1)', } + ], + emphasis: [ + commonStyles.text, + commonStyles.bold + ] + }); + + return ( + + {/* Contextual Header */} + + {/* dApp img */} + + + + + + {dapp.proposer} + {'• '}{'mainnet'} + + + {/* Disclaimer content */} + + {t`Review your transaction from this dApp`} + + + {t`Stay vigilant and protect your data from potential phishing attempts.`} + + + ); +}; + +const NanoContractExecInfo = ({ nc, blueprintName, onSelectAddress }) => { + const styles = StyleSheet.create({ + property: [commonStyles.text, commonStyles.bold, commonStyles.mb4], + value: [commonStyles.text, commonStyles.value], + contentEditable: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + contentEditableValue: { + flexShrink: 1, + paddingRight: 8, + }, + contentEditableIcon: { + width: 24, + paddingRight: 2, + }, + }); + + return ( + + {/* Nano Contract execution information */} + + + + + + {t`Nano Contract ID`} + {nc.ncId} + + + {t`BluePrint ID`} + {nc.blueprintId} + + + {t`Blueprint Name`} + {blueprintName} + + + + + + Caller + {nc.caller} + + + + + + + + + ); +}; + +const NanoContractActions = ({ ncActions }) => { + const styles = StyleSheet.create({ + action: [commonStyles.text, commonStyles.bold], + valueLabel: [commonStyles.text, commonStyles.value, commonStyles.bold, commonStyles.mb4], + value: [commonStyles.text, commonStyles.value], + }); + + return ( + + + {'Action List'} + + ( + + + + {actionTitleMap(item.type)[item.type]} + {item.address + && ( + + {t`To Address:`} + {item.address} + + )} + + + + )} + keyExtractor={(item) => item.tokenUid} + /> + + ); +}; + +const NanoContractMethodParams = ({ ncArgs }) => { + const styles = StyleSheet.create({ + argPosition: { + flexShrink: 10, + width: '30%', + paddingRight: 8, + }, + argPositionText: [commonStyles.text, commonStyles.bold], + argValue: { + maxWidth: '70%', + backgroundColor: 'hsla(0, 0%, 96%, 1)', + paddingVertical: 2, + paddingHorizontal: 8, + borderRadius: 4, + }, + argValueText: { + fontSize: 12, + lineHeight: 16, + color: COLORS.black, + }, + }); + + return ( + + + {'Argument List'} + + {ncArgs.length + && ( + + + {ncArgs.map((argValue, index) => ( + + + {t`Position ${index}`} + + + {argValue} + + + ))} + + + )} + + ); +}; + +const DeclineModal = ({show, onDismiss}) => { + return ( + + {t`Decline transaction`} + + + {t`Are you sure you want to decline this transaction?`} + + + {}} + /> + + + ); +}; + +const Icon = ({ type }) => { + const iconMap = { + deposit: SentIcon({ type: 'default' }), + withdrawal: ReceivedIcon({ type: 'default' }), + }; + + return (iconMap[type]); +}; + +const BalanceValue = ({ balance, isNft }) => { + const balanceValue = renderValue(balance, isNft); + + const styles = StyleSheet.create({ + wrapper: { + marginLeft: 'auto', + }, + balance: { + fontSize: 16, + lineHeight: 20, + color: COLORS.black, + }, + }); + + return ( + + + {balanceValue} + + + ) +}; + +const styles = StyleSheet.create({ + wrapper: { + flex: 1, + rowGap: 24, + width: '100%', + paddingVertical: 16, + paddingHorizontal: 16, + }, + balanceReceived: { + color: 'hsla(180, 85%, 34%, 1)', + fontWeight: 'bold', + }, + actionContainer: { + flexDirection: 'column', + gap: 8, + paddingBottom: 48, + }, + declineModalBody: { + paddingBottom: 24, + }, +}); diff --git a/src/components/WalletConnect/theme.js b/src/components/WalletConnect/theme.js new file mode 100644 index 000000000..59165aeca --- /dev/null +++ b/src/components/WalletConnect/theme.js @@ -0,0 +1,71 @@ +import { StyleSheet } from "react-native"; +import { COLORS } from "../../styles/themes"; + +export const commonStyles = StyleSheet.create({ + // Card + card: { + paddingVertical: 16, + paddingHorizontal: 16, + backgroundColor: COLORS.backgroundColor, + borderTopLeftRadius: 16, + borderTopRightRadius: 16, + borderBottomLeftRadius: 16, + borderBottomRightRadius: 16, + shadowOffset: { height: 2, width: 0 }, + shadowRadius: 4, + shadowColor: COLORS.textColor, + shadowOpacity: 0.08, + }, + cardSplit: { + flexDirection: 'row', + gap: 16, + }, + cardSplitIcon: { + flexShrink: 1, + alignSelf: 'flex-start', + }, + cardSplitContent: { + maxWidth: '80%', + flexDirection: 'column', + gap: 8, + }, + cardSeparator: { + width: '100%', + height: 1, + backgroundColor: COLORS.borderColor + }, + cardStack: { + flexDirection: 'column', + }, + cardStackItem: { + flexDirection: 'row', + paddingVertical: 16, + }, + listItem: { + paddingVertical: 24, + paddingHorizontal: 16, + }, + + // General + text: { + fontSize: 14, + lineHeight: 20, + color: COLORS.black, + }, + bold: { + fontWeight: 'bold', + }, + value: { + color: 'hsla(0, 0%, 38%, 1)', + }, + mb4: { + marginBottom: 4, + }, + sectionTitle: { + fontSize: 16, + fontWeight: 'bold', + lineHeight: 20, + color: COLORS.black, + marginBottom: 24, + }, +}); diff --git a/src/reducers/reducer.js b/src/reducers/reducer.js index c4017564e..d6c7c0722 100644 --- a/src/reducers/reducer.js +++ b/src/reducers/reducer.js @@ -223,6 +223,24 @@ const initialState = { modal: { show: false, }, + /** + * newNanoContractTransactionModal {{ + * show: boolean; + * data: { + * nc: Object; + * dapp: { + * icon: string; + * proposer: string; + * url: string; + * description: string; + * }; + * }; + * }} + */ + newNanoContractTransactionModal: { + show: true, + data: null, + }, connectionFailed: false, sessions: {}, }, @@ -489,6 +507,8 @@ export const reducer = (state = initialState, action) => { return onNanoContractRegisterSuccess(state, action); case types.NANOCONTRACT_REGISTER_READY: return onNanoContractRegisterReady(state); + case types.SET_NEW_NANO_CONTRACT_TRANSACTION_MODAL: + return onSetNewNanoContractTransactionModal(state, action); default: return state; } @@ -1483,3 +1503,11 @@ export const onNanoContractHistorySuccess = (state, { payload }) => ({ }, }, }); + +export const onSetNewNanoContractTransactionModal = (state, { payload }) => ({ + ...state, + walletConnect: { + ...state.walletConnect, + newNanoContractTransactionModal: { ...payload }, + }, +}); diff --git a/src/screens/WalletConnect/NewNanoContractTransaction.screen.js b/src/screens/WalletConnect/NewNanoContractTransaction.screen.js new file mode 100644 index 000000000..3b0bfaa77 --- /dev/null +++ b/src/screens/WalletConnect/NewNanoContractTransaction.screen.js @@ -0,0 +1,45 @@ +/** + * Copyright (c) Hathor Labs and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import { + StyleSheet, + View, +} from 'react-native'; +import { t } from 'ttag'; + +import HathorHeader from '../../components/HathorHeader'; +import OfflineBar from '../../components/OfflineBar'; +import { NewNanoContractTransactionRequest } from '../../components/WalletConnect/NewNanoContractTransactionRequest'; +import { COLORS } from '../../styles/themes'; + +export function NewNanoContractTransaction({ route }) { + const { ncTxRequest } = route.params; + return ( + + + + + + ); +} + +const Wrapper = ({ children }) => ( + + {children} + +); + +const styles = StyleSheet.create({ + wrapper: { + flex: 1, + alignItems: 'center', + backgroundColor: COLORS.lowContrastDetail, // Defines an outer area on the main list content + }, +});