diff --git a/.env.development b/.env.development index b2e45373..19353a42 100644 --- a/.env.development +++ b/.env.development @@ -7,4 +7,7 @@ REACT_APP_SUPPORTED_CHAIN_ID=5 REACT_APP_STARKNET_CONTRACT_ADDRESS=0xde29d060D45901Fb19ED6C6e959EB22d8626708e REACT_APP_ETHERSCAN_URL=https://goerli.etherscan.io REACT_APP_VOYAGER_URL=https://goerli.voyager.online +REACT_APP_STARKSCAN_URL=https://testnet.starkscan.co REACT_APP_SUPPORTED_TOKENS=ETH,WBTC,USDC,USDT,DAI,SLF +REACT_APP_API_ENDPOINT_URL=http://localhost:3010 +REACT_APP_ENABLE_SCREENING=false diff --git a/.env.goerli b/.env.goerli index 1ab6975f..5f4c0dc0 100644 --- a/.env.goerli +++ b/.env.goerli @@ -7,4 +7,6 @@ REACT_APP_SUPPORTED_CHAIN_ID=5 REACT_APP_STARKNET_CONTRACT_ADDRESS=0xde29d060D45901Fb19ED6C6e959EB22d8626708e REACT_APP_ETHERSCAN_URL=https://goerli.etherscan.io REACT_APP_VOYAGER_URL=https://goerli.voyager.online +REACT_APP_STARKSCAN_URL=https://testnet.starkscan.co REACT_APP_SUPPORTED_TOKENS=ETH,WBTC,USDC,USDT,DAI +REACT_APP_API_ENDPOINT_URL=//goerli.starkgate.starknet.io/w diff --git a/.env.mainnet b/.env.mainnet index f17cda67..8ec87f12 100644 --- a/.env.mainnet +++ b/.env.mainnet @@ -7,4 +7,6 @@ REACT_APP_SUPPORTED_CHAIN_ID=1 REACT_APP_STARKNET_CONTRACT_ADDRESS=0xc662c410C0ECf747543f5bA90660f6ABeBD9C8c4 REACT_APP_ETHERSCAN_URL=https://etherscan.io REACT_APP_VOYAGER_URL=https://voyager.online +REACT_APP_STARKSCAN_URL=https://starkscan.co REACT_APP_SUPPORTED_TOKENS=ETH,DAI,WBTC,USDC,USDT +REACT_APP_API_ENDPOINT_URL=//starkgate.starknet.io/w \ No newline at end of file diff --git a/README.md b/README.md index 4fc62fc9..0810914c 100644 --- a/README.md +++ b/README.md @@ -69,18 +69,6 @@ The appropriate files are loading according to the `REACT_APP_ENV` value.\ > Note: each file can be overridden on a local environment using matching `.env.[ENV].local` file. -## Wallet Support - -`WIP` - -## Tokens Support - -`WIP` - -## Project Structure - -`WIP` - ## Contributing Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull @@ -93,4 +81,4 @@ the [tags on this repository](https://github.com/starkware-libs/starkgate-fronte ## License -`WIP` +Apache License, Version 2.0 diff --git a/package.json b/package.json index dd1c4f54..0ebf345c 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "starkgate-frontend", - "version": "1.9.0", + "version": "1.10.0", "homepage": "./", "scripts": { "clean": "rm -rf ./dist", "prebuild": "yarn run clean", "build": "react-scripts build", - "build:mainnet": "env-cmd -f .env.mainnet yarn run build", - "build:goerli": "env-cmd -f .env.goerli yarn run build", + "build:mainnet": "GENERATE_SOURCEMAP=false env-cmd -f .env.mainnet yarn run build", + "build:goerli": "GENERATE_SOURCEMAP=false env-cmd -f .env.goerli yarn run build", "start": "ESLINT_NO_DEV_ERRORS=true react-scripts start", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject", @@ -58,9 +58,10 @@ "@mui/material": "^5.5.3", "@mui/styles": "^5.5.0", "@splitbee/web": "^0.3.0", - "@starkware-industries/commons-js-enums": "1.1.0-dev.3", - "@starkware-industries/commons-js-libs": "1.2.0-dev.4", - "@starkware-industries/commons-js-utils": "1.0.0-dev.5", + "@starkware-industries/commons-js-enums": "1.1.0", + "@starkware-industries/commons-js-hooks": "1.0.0", + "@starkware-industries/commons-js-libs": "1.2.1", + "@starkware-industries/commons-js-utils": "1.1.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-hot-toast": "^2.2.0", diff --git a/public/index.html b/public/index.html index c71137a1..336dab9e 100644 --- a/public/index.html +++ b/public/index.html @@ -1,15 +1,15 @@ - - - StarkGate Alpha - - - - - - - - -
- + + + StarkGate Alpha + + + + + + + + +
+ diff --git a/scripts/set-env.sh b/scripts/set-env.sh new file mode 100755 index 00000000..9983430d --- /dev/null +++ b/scripts/set-env.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +content="window.__env__ = {" + +# Each line represents key=value pairs +while read -r line || [[ -n "$line" ]]; +do + # Split env variables by character `=` + if printf '%s\n' "$line" | grep -q -e '='; then + varname=$(printf '%s\n' "$line" | sed -e 's/=.*//') + varvalue=$(printf '%s\n' "$line" | sed -e 's/^[^=]*=//') + + content="${content} $varname: \"$varvalue\"," + echo $varname $varvalue + fi +done < <(printenv | grep REACT_APP) + +content="${content} };" +echo $content > ./usr/share/nginx/html/env-config.js diff --git a/scripts/start.sh b/scripts/start.sh new file mode 100755 index 00000000..462706bd --- /dev/null +++ b/scripts/start.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +echo "setting env vars" +sh ./scripts/set-env.sh + +echo "starting nginx" +nginx -g "daemon off;" diff --git a/src/App.js b/src/App.js index 12062e75..d1a0bd5f 100644 --- a/src/App.js +++ b/src/App.js @@ -3,17 +3,16 @@ import React from 'react'; import {Navigate, Route, Routes} from 'react-router-dom'; import styles from './App.module.scss'; -import {TrackEvent} from './analytics'; import {ReactComponent as DiscordIcon} from './assets/svg/icons/discord.svg'; import {Footer, Header} from './components/Containers'; import {StyledBackground} from './components/UI'; import {SideButton} from './components/UI/SideButton/SideButton'; -import {useConstants, useTracking} from './hooks'; +import {useConstants, useDiscordTabTracking} from './hooks'; import {useApp} from './providers/AppProvider'; import {Bridge, Faq, ProtectedRoute, Terms} from './routes'; export const App = () => { - const [trackDiscordClick] = useTracking(TrackEvent.DISCORD_TAB_CLICK); + const [trackDiscordClick] = useDiscordTabTracking(); const {DISCORD_LINK_URL} = useConstants(); const {isAcceptTerms, isScrollActive} = useApp(); diff --git a/src/abis/L1/StarknetMessaging.json b/src/abis/L1/StarknetMessaging.json index ec07f99a..5bfe79cc 100644 --- a/src/abis/L1/StarknetMessaging.json +++ b/src/abis/L1/StarknetMessaging.json @@ -5,13 +5,13 @@ { "indexed": true, "internalType": "uint256", - "name": "from_address", + "name": "fromAddress", "type": "uint256" }, { "indexed": true, "internalType": "address", - "name": "to_address", + "name": "toAddress", "type": "address" }, { @@ -30,13 +30,13 @@ { "indexed": true, "internalType": "address", - "name": "from_address", + "name": "fromAddress", "type": "address" }, { "indexed": true, "internalType": "uint256", - "name": "to_address", + "name": "toAddress", "type": "uint256" }, { @@ -50,24 +50,36 @@ "internalType": "uint256[]", "name": "payload", "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" } ], "name": "ConsumedMessageToL2", "type": "event" }, + { + "anonymous": false, + "inputs": [], + "name": "Finalized", + "type": "event" + }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "uint256", - "name": "from_address", + "name": "fromAddress", "type": "uint256" }, { "indexed": true, "internalType": "address", - "name": "to_address", + "name": "toAddress", "type": "address" }, { @@ -86,13 +98,13 @@ { "indexed": true, "internalType": "address", - "name": "from_address", + "name": "fromAddress", "type": "address" }, { "indexed": true, "internalType": "uint256", - "name": "to_address", + "name": "toAddress", "type": "uint256" }, { @@ -112,16 +124,259 @@ "internalType": "uint256", "name": "nonce", "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fee", + "type": "uint256" } ], "name": "LogMessageToL2", "type": "event" }, { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "acceptedGovernor", + "type": "address" + } + ], + "name": "LogNewGovernorAccepted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "nominatedGovernor", + "type": "address" + } + ], + "name": "LogNominatedGovernor", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "LogNominationCancelled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "LogOperatorAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "LogOperatorRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "removedGovernor", + "type": "address" + } + ], + "name": "LogRemovedGovernor", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "stateTransitionFact", + "type": "bytes32" + } + ], + "name": "LogStateTransitionFact", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "globalRoot", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "blockNumber", + "type": "int256" + } + ], + "name": "LogStateUpdate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "fromAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "toAddress", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "selector", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "MessageToL2Canceled", + "type": "event" + }, + { + "anonymous": false, "inputs": [ { + "indexed": true, + "internalType": "address", + "name": "fromAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "toAddress", + "type": "uint256" + }, + { + "indexed": true, "internalType": "uint256", - "name": "from_address", + "name": "selector", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "MessageToL2CancellationStarted", + "type": "event" + }, + { + "inputs": [], + "name": "MAX_L1_MSG_FEE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "toAddress", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "selector", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "cancelL1ToL2Message", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "configHash", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "fromAddress", "type": "uint256" }, { @@ -141,6 +396,116 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "finalize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "identify", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isFinalized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isFrozen", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "isOperator", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "msgHash", + "type": "bytes32" + } + ], + "name": "l1ToL2MessageCancellations", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l1ToL2MessageNonce", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -179,11 +544,50 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "messageCancellationDelay", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "programHash", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOperator", + "type": "address" + } + ], + "name": "registerOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { "internalType": "uint256", - "name": "to_address", + "name": "toAddress", "type": "uint256" }, { @@ -203,8 +607,207 @@ "internalType": "bytes32", "name": "", "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newConfigHash", + "type": "uint256" + } + ], + "name": "setConfigHash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "delayInSeconds", + "type": "uint256" + } + ], + "name": "setMessageCancellationDelay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newProgramHash", + "type": "uint256" + } + ], + "name": "setProgramHash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "starknetAcceptGovernance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "starknetCancelNomination", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "starknetIsGovernor", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newGovernor", + "type": "address" + } + ], + "name": "starknetNominateNewGovernor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "governorForRemoval", + "type": "address" + } + ], + "name": "starknetRemoveGovernor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "toAddress", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "selector", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "startL1ToL2MessageCancellation", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stateBlockNumber", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stateRoot", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "removedOperator", + "type": "address" + } + ], + "name": "unregisterOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "programOutput", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "onchainDataHash", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "onchainDataSize", + "type": "uint256" } ], + "name": "updateState", + "outputs": [], "stateMutability": "nonpayable", "type": "function" } diff --git a/src/analytics/track-event.js b/src/analytics/track-event.js index 61619085..2de00b1c 100644 --- a/src/analytics/track-event.js +++ b/src/analytics/track-event.js @@ -62,7 +62,8 @@ export const TrackEvent = { */ LOGIN: { WALLET_CLICK: 'LOGIN_MODAL/wallet_click', - LOGIN_ERROR: 'LOGIN_MODAL/login_error' + LOGIN_ERROR: 'LOGIN_MODAL/login_error', + BLOCKED_ADDRESS: 'LOGIN_MODAL/blocked_address' }, /** diff --git a/src/api/index.js b/src/api/index.js new file mode 100644 index 00000000..58bf48fb --- /dev/null +++ b/src/api/index.js @@ -0,0 +1 @@ +export * from './screening'; diff --git a/src/api/screening.js b/src/api/screening.js new file mode 100644 index 00000000..7efb83b9 --- /dev/null +++ b/src/api/screening.js @@ -0,0 +1,21 @@ +import { + promiseHandler, + createHttpClient, + parseHttpClientError +} from '@starkware-industries/commons-js-utils'; + +import {API_ENDPOINT_URL} from '../config/envs'; + +const SCREENING_ENDPOINT = '/api/screening/risk'; +const httpClient = createHttpClient({baseURL: API_ENDPOINT_URL + SCREENING_ENDPOINT}); + +export const screenAddress = async address => { + const [result, error] = await promiseHandler(httpClient.get(`/${address}`)); + if (error) { + throw parseHttpClientError(error); + } + const { + data: {blocked} + } = result; + return blocked; +}; diff --git a/src/assets/svg/icons/collapse.svg b/src/assets/svg/icons/collapse.svg index 89d1a0eb..cf92a5d2 100644 --- a/src/assets/svg/icons/collapse.svg +++ b/src/assets/svg/icons/collapse.svg @@ -1,3 +1,3 @@ - + diff --git a/src/assets/svg/starkscan.svg b/src/assets/svg/starkscan.svg new file mode 100644 index 00000000..6d358a00 --- /dev/null +++ b/src/assets/svg/starkscan.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/svg/voyager.svg b/src/assets/svg/voyager.svg new file mode 100644 index 00000000..a41bb221 --- /dev/null +++ b/src/assets/svg/voyager.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/components/Features/Account/Account.js b/src/components/Features/Account/Account.js index 5064e24e..e53dd89c 100644 --- a/src/components/Features/Account/Account.js +++ b/src/components/Features/Account/Account.js @@ -1,3 +1,4 @@ +import {addAddressPadding} from '@starkware-industries/commons-js-libs/starknet'; import {evaluate, findIndexById} from '@starkware-industries/commons-js-utils'; import PropTypes from 'prop-types'; import React from 'react'; @@ -5,14 +6,13 @@ import React from 'react'; import { useAccountTracking, useCompleteTransferToL1, - useConstants, useEnvs, useAccountTranslation } from '../../../hooks'; import {useMenu} from '../../../providers/MenuProvider'; import {useTransfer} from '../../../providers/TransferProvider'; import {useAccountTransfersLog} from '../../../providers/TransfersLogProvider'; -import {useWallets} from '../../../providers/WalletsProvider'; +import {useAccountHash, useWallets} from '../../../providers/WalletsProvider'; import { AccountAddress, BackButton, @@ -26,7 +26,7 @@ import {TransferLog} from '../TransferLog/TransferLog'; import styles from './Account.module.scss'; export const Account = ({transferId}) => { - const {titleTxt} = useAccountTranslation(); + const {titleTxt, btnTxt} = useAccountTranslation(); const [ trackTxLinkClick, trackAccountLinkClick, @@ -34,16 +34,16 @@ export const Account = ({transferId}) => { trackCompleteTransferClick, trackAddressCopied ] = useAccountTracking(); - const {ETHERSCAN_ACCOUNT_URL, VOYAGER_ACCOUNT_URL} = useEnvs(); - const {ETHERSCAN, VOYAGER} = useConstants(); + const {ETHERSCAN_ACCOUNT_URL, STARKSCAN_ACCOUNT_URL} = useEnvs(); const {showSourceMenu} = useMenu(); const {account, resetWallet} = useWallets(); const {isL1, isL2, fromNetwork} = useTransfer(); const transfers = useAccountTransfersLog(account); const completeTransferToL1 = useCompleteTransferToL1(); + const accountHash = useAccountHash(); const renderTransfers = () => { - return transfers.length + return accountHash && transfers.length ? transfers.map((transfer, index) => ( { resetWallet(); }; + const renderExplorers = () => { + const explorersL1 = [{text: btnTxt, url: ETHERSCAN_ACCOUNT_URL(account)}]; + const explorersL2 = [{text: btnTxt, url: STARKSCAN_ACCOUNT_URL(account)}]; + const explorers = isL1 ? explorersL1 : explorersL2; + + return ( +
+ {explorers.map(({text, url}) => ( + + ))} +
+ ); + }; + return (
showSourceMenu()} /> - - {isL1 && ( - - )} - {isL2 && ( - - )} + + {renderExplorers()} * { + margin-right: 5px; +} diff --git a/src/components/Features/ToastManager/ToastManager.js b/src/components/Features/ToastManager/ToastManager.js index 926a7fdb..fbb97e0f 100644 --- a/src/components/Features/ToastManager/ToastManager.js +++ b/src/components/Features/ToastManager/ToastManager.js @@ -4,6 +4,7 @@ import { isRejected, NetworkType } from '@starkware-industries/commons-js-enums'; +import {usePrevious} from '@starkware-industries/commons-js-hooks'; import {getCookie, getFullTime, setCookie} from '@starkware-industries/commons-js-utils'; import PropTypes from 'prop-types'; import React, {useEffect} from 'react'; @@ -16,10 +17,11 @@ import { HIDE_ELEMENT_COOKIE_DURATION_DAYS } from '../../../config/constants'; import {ActionType, Breakpoint, isMobile, ToastType} from '../../../enums'; -import {useCompleteTransferToL1, usePrevious} from '../../../hooks'; +import {useCompleteTransferToL1} from '../../../hooks'; import {useMenu} from '../../../providers/MenuProvider'; import {useIsL1, useIsL2} from '../../../providers/TransferProvider'; import {useTransfersLog} from '../../../providers/TransfersLogProvider'; +import {useAccountHash} from '../../../providers/WalletsProvider'; import {CompleteTransferToL1Toast, ToastBody, TransferToast, Bullet} from '../../UI'; import {AlphaDisclaimerToast} from '../../UI/Toast/AlphaDisclaimerToast/AlphaDisclaimerToast'; import styles from './ToastManager.module.scss'; @@ -37,6 +39,13 @@ export const ToastManager = () => { const [, swapToL1] = useIsL1(); const [, swapToL2] = useIsL2(); const {breakpoint} = useBreakpoint(Breakpoint); + const accountHash = useAccountHash(); + + useEffect(() => { + if (!accountHash) { + clearToasts(); + } + }, [accountHash]); useEffect(() => { const alphaDisclaimerCookie = getCookie(ALPHA_DISCLAIMER_COOKIE_NAME); @@ -46,8 +55,10 @@ export const ToastManager = () => { }, [breakpoint]); useDeepCompareEffect(() => { - renderToasts(); - }, [transfers]); + if (accountHash) { + renderToasts(); + } + }, [transfers, accountHash]); useEffect(() => { return () => clearToasts(); diff --git a/src/components/Features/Transfer/Transfer.js b/src/components/Features/Transfer/Transfer.js index 2e74f7b4..27891217 100644 --- a/src/components/Features/Transfer/Transfer.js +++ b/src/components/Features/Transfer/Transfer.js @@ -27,9 +27,10 @@ import { NetworkSwap, TokenInput, TransferButton, - LoginWalletButton, MenuBackground, - ReadMore + LinkType, + Link, + LoginWalletButton } from '../../UI'; import styles from './Transfer.module.scss'; @@ -179,7 +180,6 @@ export const Transfer = ({onNetworkSwap}) => { {isL1 ? renderL2Network() : renderL1Network()} {isLoggedIn ? ( @@ -197,13 +197,18 @@ Transfer.propTypes = { }; const BridgeIsFullError = () => { - const {bridgeIsFullErrorMsg} = useTransferTranslation(); + const {bridgeIsFullErrorMsg, readMoreTxt} = useTransferTranslation(); const {STARKGATE_ALPHA_LIMITATIONS_URL} = useConstants(); return ( - {bridgeIsFullErrorMsg} - + {bridgeIsFullErrorMsg}{' '} + ); }; diff --git a/src/components/Features/Transfer/Transfer.module.scss b/src/components/Features/Transfer/Transfer.module.scss index 33098824..86c791ed 100644 --- a/src/components/Features/Transfer/Transfer.module.scss +++ b/src/components/Features/Transfer/Transfer.module.scss @@ -7,3 +7,9 @@ color: $--color-jasper; margin-top: 8px; } + +.bridgeIsFullReadMore { + font-size: 12px; + color: $--color-very-light-azure; + line-height: 18px; +} diff --git a/src/components/Features/TransferLog/TransferLog.js b/src/components/Features/TransferLog/TransferLog.js index 430f0ca8..e5d745d6 100644 --- a/src/components/Features/TransferLog/TransferLog.js +++ b/src/components/Features/TransferLog/TransferLog.js @@ -17,7 +17,7 @@ import {LinkButton} from '../../UI/LinkButton/LinkButton'; import styles from './TransferLog.module.scss'; export const TransferLog = ({transfer, onCompleteTransferClick, onTxClick}) => { - const {VOYAGER_TX_URL, ETHERSCAN_TX_URL} = useEnvs(); + const {STARKSCAN_TX_URL, STARKSCAN_ETH_TX_URL} = useEnvs(); const {symbol, timestamp, name, amount, status, l1hash, l2hash} = transfer; const [sign, setSign] = useState(''); const {action, isL1} = useTransfer(); @@ -43,7 +43,7 @@ export const TransferLog = ({transfer, onCompleteTransferClick, onTxClick}) => { ); @@ -55,10 +55,10 @@ export const TransferLog = ({transfer, onCompleteTransferClick, onTxClick}) => { isDisabled={ !l2hash || !status || - TransactionStatusStep[status] < TransactionStatusStep[TransactionStatus.PENDING] + TransactionStatusStep[status] < TransactionStatusStep[TransactionStatus.RECEIVED] } text={`${NetworkType.L2} Tx`} - url={VOYAGER_TX_URL(l2hash)} + url={STARKSCAN_TX_URL(l2hash)} onClick={onTxClick} /> ); @@ -98,10 +98,13 @@ const CompleteTransferButton = ({onClick}) => { colorBackground="transparent" colorBorder={colorOrangeSoda} colorText={colorOrangeSoda} - height={10} style={{ fontSize: '12px', - padding: '14px' + padding: '0 8px', + lineHeight: '18px', + fontWeight: '400', + borderRadius: '5px', + margin: '0' }} text={completeTransferBtnTxt} onClick={onClick} diff --git a/src/components/UI/AccountAddress/AccountAddress.js b/src/components/UI/AccountAddress/AccountAddress.js index 4582f0ce..8c8ebf99 100644 --- a/src/components/UI/AccountAddress/AccountAddress.js +++ b/src/components/UI/AccountAddress/AccountAddress.js @@ -1,9 +1,10 @@ +import {useAnimation} from '@starkware-industries/commons-js-hooks'; import {shortenAddress, toClasses} from '@starkware-industries/commons-js-utils'; import PropTypes from 'prop-types'; import React, {useRef} from 'react'; import {ReactComponent as CopyIcon} from '../../../assets/svg/icons/copy.svg'; -import {useAccountTranslation, useAnimation} from '../../../hooks'; +import {useAccountTranslation} from '../../../hooks'; import styles from './AccountAddress.module.scss'; const COPIED_ANIMATION_TIMEOUT_INTERVAL = 1000; diff --git a/src/components/UI/BackButton/BackButton.js b/src/components/UI/BackButton/BackButton.js index 3583bc6e..1f00540b 100644 --- a/src/components/UI/BackButton/BackButton.js +++ b/src/components/UI/BackButton/BackButton.js @@ -2,11 +2,11 @@ import PropTypes from 'prop-types'; import React from 'react'; import {ReactComponent as BackIcon} from '../../../assets/svg/icons/back.svg'; -import {useTranslation} from '../../../hooks'; +import {useMenusTranslation} from '../../../hooks'; import styles from './BackButton.module.scss'; export const BackButton = ({onClick}) => { - const {backBtnTxt} = useTranslation('menus'); + const {backBtnTxt} = useMenusTranslation(); return (
diff --git a/src/components/UI/Button/Button.module.scss b/src/components/UI/Button/Button.module.scss index e31eab5d..00984a83 100644 --- a/src/components/UI/Button/Button.module.scss +++ b/src/components/UI/Button/Button.module.scss @@ -8,7 +8,7 @@ text-align: center; text-transform: capitalize; font-weight: 500; - font-size: 15px; + font-size: 16px; line-height: 24px; border: 1px solid; box-sizing: border-box; diff --git a/src/components/UI/ChainSelect/ChainSelect.js b/src/components/UI/ChainSelect/ChainSelect.js index 01039a13..d5a1efd6 100644 --- a/src/components/UI/ChainSelect/ChainSelect.js +++ b/src/components/UI/ChainSelect/ChainSelect.js @@ -1,10 +1,8 @@ -import {Select, MenuItem, FormControl} from '@mui/material'; +import {Select, MenuItem} from '@mui/material'; import {ChainInfo, ChainType} from '@starkware-industries/commons-js-enums'; import {openInNewTab} from '@starkware-industries/commons-js-utils'; import React from 'react'; -import {ReactComponent as CollapseIcon} from '../../../assets/svg/icons/collapse.svg'; -import {ReactComponent as SelectedIcon} from '../../../assets/svg/icons/selected.svg'; import {APP_URL_GOERLI, APP_URL_MAINNET} from '../../../config/constants'; import {useEnvs} from '../../../hooks'; import {ChainSelectTheme} from './ChainSelect.theme'; @@ -25,11 +23,6 @@ export const ChainSelect = () => { return ( {ChainInfo.L2[chainName].CHAIN} - {chainName === SUPPORTED_L2_CHAIN_ID && ( -
- -
- )}
); }); @@ -37,16 +30,14 @@ export const ChainSelect = () => { return ( - - - + ); }; diff --git a/src/components/UI/ChainSelect/ChainSelect.theme.js b/src/components/UI/ChainSelect/ChainSelect.theme.js index fbb0e31d..b98043a7 100644 --- a/src/components/UI/ChainSelect/ChainSelect.theme.js +++ b/src/components/UI/ChainSelect/ChainSelect.theme.js @@ -2,21 +2,23 @@ import {createTheme, ThemeProvider} from '@mui/material'; import PropTypes from 'prop-types'; import React from 'react'; +import CollapseIcon from '../../../assets/svg/icons/collapse.svg'; +import SelectedIcon from '../../../assets/svg/icons/selected.svg'; import {useColors, useFonts} from '../../../hooks'; export const ChainSelectTheme = ({children}) => { - const {colorWhite, colorDarkSlateBlue, colorToolbox} = useColors(); + const {colorWhite, colorSpaceCadet, colorSpaceCadet2} = useColors(); const {primaryFont} = useFonts(); const theme = createTheme({ components: { MuiOutlinedInput: { styleOverrides: { root: { + width: '127px', margin: '0 16px', fontSize: '14px', fontWeight: '500', lineHeight: '20px', - padding: '8px 16px', fontFamily: primaryFont, color: colorWhite, borderRadius: '8px', @@ -28,27 +30,27 @@ export const ChainSelectTheme = ({children}) => { } }, input: { - padding: '0', - paddingRight: '16px !important' - } - } - }, - MuiSelect: { - styleOverrides: { - icon: { - position: 'initial', - width: '16px' + padding: '8px 16px !important', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + '&:after': { + content: `url(${CollapseIcon})`, + paddingLeft: '12px', + transform: 'scale(1.3)', + lineHeight: '0' + } } } }, MuiPaper: { styleOverrides: { root: { - transform: 'translate(5px, 12px) !important', - border: `1px solid ${colorWhite}`, + boxShadow: 'none', borderRadius: '8px', - backgroundColor: colorDarkSlateBlue, - color: colorWhite + backgroundColor: colorSpaceCadet, + color: colorWhite, + marginTop: '4px' } } }, @@ -62,14 +64,24 @@ export const ChainSelectTheme = ({children}) => { MuiMenuItem: { styleOverrides: { root: { + fontFamily: primaryFont, fontSize: '14px', fontWeight: '500', lineHeight: '20px', - padding: '8px 12px 8px 16px', - fontFamily: primaryFont, - backgroundColor: colorDarkSlateBlue, - '&.Mui-selected': { - backgroundColor: `${colorToolbox} !important` + padding: '4px 8px', + borderRadius: '8px', + margin: '8px', + backgroundColor: `${colorSpaceCadet} !important`, + '&:hover': { + backgroundColor: `${colorSpaceCadet2} !important` + }, + '&.Mui-selected:after': { + content: `url(${SelectedIcon})`, + position: 'absolute', + right: '12px' + }, + '& .MuiTouchRipple-root': { + display: 'none' } } } diff --git a/src/components/UI/DynamicIcon/DynamicIcon.js b/src/components/UI/DynamicIcon/DynamicIcon.js index 01db0963..2ddb07a5 100644 --- a/src/components/UI/DynamicIcon/DynamicIcon.js +++ b/src/components/UI/DynamicIcon/DynamicIcon.js @@ -1,8 +1,7 @@ +import {useFetchData} from '@starkware-industries/commons-js-hooks'; import PropTypes from 'prop-types'; import React, {useEffect, useRef} from 'react'; -import {useFetchData} from '../../../hooks'; - export const DynamicIcon = ({path, size}) => { const ImportedIconRef = useRef(null); const {data, isLoading} = useFetchData(() => import(`../../../assets/svg/${path}`), [path]); diff --git a/src/components/UI/Link/Link.js b/src/components/UI/Link/Link.js new file mode 100644 index 00000000..cdcb1a5e --- /dev/null +++ b/src/components/UI/Link/Link.js @@ -0,0 +1,38 @@ +import {toClasses} from '@starkware-industries/commons-js-utils'; +import PropTypes from 'prop-types'; + +import styles from './Link.module.scss'; + +export const LinkType = { + EMAIL: 'email', + URL: 'url' +}; + +export const Link = ({ + className, + link, + text, + type = LinkType.URL, + openInNewTab = false, + ...props +}) => { + return ( + + {text || link} + + ); +}; + +Link.propTypes = { + className: PropTypes.string, + link: PropTypes.string, + text: PropTypes.string, + type: PropTypes.string, + openInNewTab: PropTypes.bool +}; diff --git a/src/components/UI/Link/Link.module.scss b/src/components/UI/Link/Link.module.scss new file mode 100644 index 00000000..0ab7fc2e --- /dev/null +++ b/src/components/UI/Link/Link.module.scss @@ -0,0 +1,5 @@ +@import '../../../index'; + +.link { + text-decoration: none; +} diff --git a/src/components/UI/LinkButton/LinkButton.module.scss b/src/components/UI/LinkButton/LinkButton.module.scss index ea562254..e67c56d0 100644 --- a/src/components/UI/LinkButton/LinkButton.module.scss +++ b/src/components/UI/LinkButton/LinkButton.module.scss @@ -3,7 +3,8 @@ .linkButton { color: $--color-dodger-blue; padding: 0 8px; - line-height: 20px; + line-height: 18px; + font-weight: 400; border-radius: 5px; border: 1px solid $--color-dodger-blue; font-size: 12px; diff --git a/src/components/UI/LogoutButton/LogoutButton.js b/src/components/UI/LogoutButton/LogoutButton.js index 8118e196..887c1a77 100644 --- a/src/components/UI/LogoutButton/LogoutButton.js +++ b/src/components/UI/LogoutButton/LogoutButton.js @@ -18,8 +18,7 @@ export const LogoutButton = ({onClick, isDisabled}) => { style={{ margin: '0', width: '100%', - borderRadius: '7px', - fontSize: '16px' + borderRadius: '7px' }} text={logoutBtnTxt} onClick={onClick} diff --git a/src/components/UI/MainMenuButton/MainMenuButton.module.scss b/src/components/UI/MainMenuButton/MainMenuButton.module.scss index 6fe84c13..68e9e09b 100644 --- a/src/components/UI/MainMenuButton/MainMenuButton.module.scss +++ b/src/components/UI/MainMenuButton/MainMenuButton.module.scss @@ -1,6 +1,5 @@ .mainMenuButton { width: 100%; - font-size: 16px; margin: 0; border-width: 0; font-weight: 600; diff --git a/src/components/UI/Modal/BlockedAddressModal/BlockedAddressModal.js b/src/components/UI/Modal/BlockedAddressModal/BlockedAddressModal.js new file mode 100644 index 00000000..1c9a238a --- /dev/null +++ b/src/components/UI/Modal/BlockedAddressModal/BlockedAddressModal.js @@ -0,0 +1,26 @@ +import React from 'react'; + +import {Link, LinkType} from '../../'; +import {useBlockedAddressModalTranslation, useConstants} from '../../../../hooks'; +import styles from './BlockedAddressModal.module.scss'; + +const BlockedAddressModal = () => { + const {STARKGATE_COMPLIANCE_MAIL_ADDRESS} = useConstants(); + const {descriptionTxt, complianceTxt} = useBlockedAddressModalTranslation(); + + return ( +
+
{descriptionTxt}
+
+ {complianceTxt}{' '} + +
+
+ ); +}; + +export default BlockedAddressModal; diff --git a/src/components/UI/Modal/BlockedAddressModal/BlockedAddressModal.module.scss b/src/components/UI/Modal/BlockedAddressModal/BlockedAddressModal.module.scss new file mode 100644 index 00000000..ed3d44ed --- /dev/null +++ b/src/components/UI/Modal/BlockedAddressModal/BlockedAddressModal.module.scss @@ -0,0 +1,15 @@ +@import '../../../../index'; + +.blockedAddressModal { + color: $--color-black; + margin: 0 auto 24px; + line-height: 24px; + + .complianceEmailLink { + color: $--color-orange-soda; + } + + .compliance { + margin-top: 20px; + } +} diff --git a/src/components/UI/Modal/LoginModal/LoginModal.js b/src/components/UI/Modal/LoginModal/LoginModal.js index abd66648..71094f6b 100644 --- a/src/components/UI/Modal/LoginModal/LoginModal.js +++ b/src/components/UI/Modal/LoginModal/LoginModal.js @@ -5,6 +5,7 @@ import { WalletErrorType, WalletStatus } from '@starkware-industries/commons-js-enums'; +import {useDidMountEffect} from '@starkware-industries/commons-js-hooks'; import {evaluate} from '@starkware-industries/commons-js-utils'; import PropTypes from 'prop-types'; import React, {useEffect, useState} from 'react'; @@ -13,8 +14,7 @@ import { useEnvs, useLoginTracking, useLoginTranslation, - useWalletHandlerProvider, - useDidMountEffect + useWalletHandlerProvider } from '../../../../hooks'; import {useLogin} from '../../../../providers/AppProvider'; import {useHideModal} from '../../../../providers/ModalProvider'; diff --git a/src/components/UI/Modal/ModalFooter/ModalFooter.module.scss b/src/components/UI/Modal/ModalFooter/ModalFooter.module.scss index 1ef9b257..59d9385d 100644 --- a/src/components/UI/Modal/ModalFooter/ModalFooter.module.scss +++ b/src/components/UI/Modal/ModalFooter/ModalFooter.module.scss @@ -3,6 +3,10 @@ .modalFooter { display: flex; justify-content: space-around; - flex-direction: row; + flex-wrap: wrap; align-items: center; + + button { + flex: 40%; + } } diff --git a/src/components/UI/Modal/ModalHeaderWithIcon/ModalHeaderWithIcon.js b/src/components/UI/Modal/ModalHeaderWithIcon/ModalHeaderWithIcon.js index 355b445f..154ba99d 100644 --- a/src/components/UI/Modal/ModalHeaderWithIcon/ModalHeaderWithIcon.js +++ b/src/components/UI/Modal/ModalHeaderWithIcon/ModalHeaderWithIcon.js @@ -3,17 +3,19 @@ import React from 'react'; import styles from './ModalHeaderWithIcon.module.scss'; -const ModalHeaderWithIcon = ({title, icon: Icon}) => { +const ModalHeaderWithIcon = ({title, subtitle, icon: Icon}) => { return (
{title}
+ {subtitle &&
{subtitle}
}
); }; ModalHeaderWithIcon.propTypes = { title: PropTypes.string, + subtitle: PropTypes.string, icon: PropTypes.object }; diff --git a/src/components/UI/Modal/ModalHeaderWithIcon/ModalHeaderWithIcon.module.scss b/src/components/UI/Modal/ModalHeaderWithIcon/ModalHeaderWithIcon.module.scss index d49a784d..7976cf1a 100644 --- a/src/components/UI/Modal/ModalHeaderWithIcon/ModalHeaderWithIcon.module.scss +++ b/src/components/UI/Modal/ModalHeaderWithIcon/ModalHeaderWithIcon.module.scss @@ -2,7 +2,7 @@ .container { width: 100%; - margin-bottom: 20px; + margin-bottom: 12px; .modalHeaderWithIcon { font-weight: 500; @@ -10,7 +10,13 @@ line-height: 32px; margin: 20px 0 -10px 0; color: $--color-dark-jungle-green; - float: left; width: 100%; } + + .subtitle { + margin-top: 16px; + color: $--color-auro-metal-saurus; + font-size: 14px; + line-height: 20px; + } } diff --git a/src/components/UI/Modal/ModalWrapper.js b/src/components/UI/Modal/ModalWrapper.js index bc6df46b..9eb5c907 100644 --- a/src/components/UI/Modal/ModalWrapper.js +++ b/src/components/UI/Modal/ModalWrapper.js @@ -15,9 +15,14 @@ import {ModalText} from './ModalText/ModalText'; export const ModalWrapper = () => { const modal = useModal(); - const handleOnClose = useHideModal(); const {withHeader, header, body, footer} = modal; + const hideModal = useHideModal(); + + const handleOnClose = () => { + footer.onClick && footer.onClick(); + hideModal(); + }; const getComponents = components => { return components diff --git a/src/components/UI/Modal/TransactionSubmittedModal/TransactionSubmittedModalButton.js b/src/components/UI/Modal/TransactionSubmittedModal/TransactionSubmittedModalButton.js index 7fb56350..429254ed 100644 --- a/src/components/UI/Modal/TransactionSubmittedModal/TransactionSubmittedModalButton.js +++ b/src/components/UI/Modal/TransactionSubmittedModal/TransactionSubmittedModalButton.js @@ -2,8 +2,9 @@ import {openInNewTab, evaluate} from '@starkware-industries/commons-js-utils'; import PropTypes from 'prop-types'; import React from 'react'; -import {ReactComponent as StarkNetLogo} from '../../../../assets/svg/chains/starknet.svg'; import {ReactComponent as EtherscanLogo} from '../../../../assets/svg/etherscan.svg'; +import {ReactComponent as StarkScanLogo} from '../../../../assets/svg/starkscan.svg'; +import {ReactComponent as VoyagerLogo} from '../../../../assets/svg/voyager.svg'; import {ActionType} from '../../../../enums'; import { useColors, @@ -15,37 +16,52 @@ import {Button} from '../../Button/Button'; import {Circle} from '../../Circle/Circle'; const TransactionSubmittedModalButton = ({transfer, buttonProps}) => { - const {ETHERSCAN, VOYAGER} = useConstants(); - const {ETHERSCAN_TX_URL, VOYAGER_TX_URL} = useEnvs(); + const {ETHERSCAN, VOYAGER, STARKSCAN} = useConstants(); + const {ETHERSCAN_TX_URL, VOYAGER_TX_URL, STARKSCAN_TX_URL, STARKSCAN_ETH_TX_URL} = useEnvs(); const {colorIndigo, colorWhite, colorGainsboro} = useColors(); const {btnTxt} = useTransactionSubmittedModalTranslation(); const {type, l2hash, l1hash} = transfer; const isTransferCompleted = l1hash && l2hash; - let explorer; + let explorers; if (type === ActionType.TRANSFER_TO_L2 || isTransferCompleted) { - explorer = { - name: ETHERSCAN, - url: ETHERSCAN_TX_URL(l1hash), - logo: - }; + explorers = [ + { + name: ETHERSCAN, + url: ETHERSCAN_TX_URL(l1hash), + logo: + }, + { + name: STARKSCAN, + url: STARKSCAN_ETH_TX_URL(l1hash), + logo: + } + ]; } if (type === ActionType.TRANSFER_TO_L1 && !isTransferCompleted) { - explorer = { - name: VOYAGER, - url: VOYAGER_TX_URL(l2hash), - logo: - }; + explorers = [ + { + name: VOYAGER, + url: VOYAGER_TX_URL(l2hash), + logo: + }, + { + name: STARKSCAN, + url: STARKSCAN_TX_URL(l2hash), + logo: + } + ]; } - const onClick = () => { - openInNewTab(explorer.url); + const onClick = url => { + openInNewTab(url); }; - return ( + return explorers.map(explorer => (