From 7be050b5270755fd138990429de7bffe668b7c6e Mon Sep 17 00:00:00 2001 From: marcinbodnar Date: Tue, 31 May 2022 19:18:33 +0200 Subject: [PATCH 01/41] feat: Use createEntityAdapter for transactions reducer --- .../components/wallet/ActivitiesWrapper.js | 6 +- .../components/wallet/ActivityDetailModal.js | 5 +- .../src/redux/slices/transactions/index.js | 108 +++++++----------- 3 files changed, 49 insertions(+), 70 deletions(-) diff --git a/packages/frontend/src/components/wallet/ActivitiesWrapper.js b/packages/frontend/src/components/wallet/ActivitiesWrapper.js index 60e0a6b93d..1724314242 100644 --- a/packages/frontend/src/components/wallet/ActivitiesWrapper.js +++ b/packages/frontend/src/components/wallet/ActivitiesWrapper.js @@ -7,9 +7,9 @@ import { EXPLORER_URL } from '../../config'; import { selectAccountId } from '../../redux/slices/account'; import { actions as transactionsActions, - selectOneTransactionByIdentity, + selectTransactionsOneByIdentity, selectTransactionsByAccountId, - selectTransactionsLoading, + selectTransactionsLoading } from '../../redux/slices/transactions'; import classNames from '../../utils/classNames'; import FormButton from '../common/FormButton'; @@ -100,7 +100,7 @@ const ActivitiesWrapper = () => { const [transactionHash, setTransactionHash] = useState(); const accountId = useSelector(selectAccountId); const transactions = useSelector((state) => selectTransactionsByAccountId(state, { accountId })); - const transaction = useSelector((state) => selectOneTransactionByIdentity(state, { accountId, hash: transactionHash })); + const transaction = useSelector((state) => selectTransactionsOneByIdentity(state, { accountId, id: transactionHash })); const activityLoader = useSelector((state) => selectTransactionsLoading(state, { accountId })); useEffect(() => { diff --git a/packages/frontend/src/components/wallet/ActivityDetailModal.js b/packages/frontend/src/components/wallet/ActivityDetailModal.js index 3cb1c2cb56..e9b78bffb8 100644 --- a/packages/frontend/src/components/wallet/ActivityDetailModal.js +++ b/packages/frontend/src/components/wallet/ActivityDetailModal.js @@ -133,11 +133,12 @@ const ActivityDetailModal = ({ checkStatus, hash, signer_id, - block_timestamp + block_timestamp, + hash_with_index } = transaction; const dispatch = useDispatch(); - const getTransactionStatusConditions = () => checkStatus && !document.hidden && dispatch(transactionsActions.fetchTransactionStatus({ hash, signer_id, accountId })); + const getTransactionStatusConditions = () => checkStatus && !document.hidden && dispatch(transactionsActions.fetchTransactionStatus({ hash, signer_id, accountId, hash_with_index })); useEffect(() => { getTransactionStatusConditions(); diff --git a/packages/frontend/src/redux/slices/transactions/index.js b/packages/frontend/src/redux/slices/transactions/index.js index e960528aa3..abb8d51b1f 100644 --- a/packages/frontend/src/redux/slices/transactions/index.js +++ b/packages/frontend/src/redux/slices/transactions/index.js @@ -1,21 +1,31 @@ -import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; -import set from 'lodash.set'; +import { + createEntityAdapter, + createSlice, + createAsyncThunk +} from '@reduxjs/toolkit'; import { createSelector } from 'reselect'; -import { getTransactions, transactionExtraInfo } from '../../../utils/explorer-api'; +import { + getTransactions, + transactionExtraInfo +} from '../../../utils/explorer-api'; import handleAsyncThunkStatus from '../../reducerStatus/handleAsyncThunkStatus'; import initialStatusState from '../../reducerStatus/initialState/initialStatusState'; -import { createParameterSelector } from '../../selectors/topLevel'; const SLICE_NAME = 'transactions'; +const transactionsAdapter = createEntityAdapter({ + selectId: ({ hash_with_index }) => hash_with_index, + sortComparer: (a, b) => b.block_timestamp - a.block_timestamp, +}); + const initialState = { byAccountId: {} }; const initialAccountIdState = { ...initialStatusState, - items: [] + ...transactionsAdapter.getInitialState() }; const fetchTransactions = createAsyncThunk( @@ -25,15 +35,15 @@ const fetchTransactions = createAsyncThunk( const { actions: { setTransactions, updateTransactions } } = transactionsSlice; - !selectTransactionsByAccountId(getState(), { accountId }).length - ? dispatch(setTransactions({ transactions, accountId })) - : dispatch(updateTransactions({ transactions, accountId })); + selectTransactionsByAccountIdTotal(getState(), { accountId }) + ? dispatch(updateTransactions({ transactions, accountId })) + : dispatch(setTransactions({ transactions, accountId })); } ); const fetchTransactionStatus = createAsyncThunk( `${SLICE_NAME}/fetchTransactionStatus`, - async ({ hash, signer_id, accountId }, { dispatch, getState }) => { + async ({ hash, signer_id, accountId, hash_with_index }, { dispatch, getState }) => { let status; try { const transactionDetails = await transactionExtraInfo({ hash, signer_id }); @@ -43,7 +53,7 @@ const fetchTransactionStatus = createAsyncThunk( } const checkStatus = ['SuccessValue', 'Failure'].includes(status); const { actions: { updateTransactionStatus } } = transactionsSlice; - dispatch(updateTransactionStatus({ status, checkStatus, accountId, hash })); + dispatch(updateTransactionStatus({ status, checkStatus, accountId, hash, hash_with_index })); } ); @@ -51,39 +61,15 @@ const transactionsSlice = createSlice({ name: SLICE_NAME, initialState, reducers: { - setTransactions(state, { payload }) { - const { transactions, accountId } = payload; - set(state, ['byAccountId', accountId, 'items'], transactions); + setTransactions(state, { payload: { accountId, transactions }}) { + transactionsAdapter.setAll(state.byAccountId[accountId], transactions); }, - updateTransactions(state, { payload }) { - const { transactions, accountId } = payload; - - const transactionsState = state.byAccountId[accountId].items; - const hash = transactionsState.map((t) => t.hash_with_index); - - // when updating the transaction, we do not want to replace the entire array, because for some entries the tx status may already be fetched - transactions - .reverse() - .forEach((t) => { - if (!hash.includes(t.hash_with_index)) { - transactionsState.unshift(t); - if (transactionsState.length > 10) { - transactionsState.pop(); - } - } - } - ); + updateTransactions(state, { payload: { accountId, transactions }}) { + transactionsAdapter.upsertMany(state.byAccountId[accountId], transactions); + }, + updateTransactionStatus(state, { payload: { status, checkStatus, accountId, hash_with_index }}) { + transactionsAdapter.updateOne(state.byAccountId[accountId], { id: hash_with_index, changes: { status, checkStatus } }); }, - updateTransactionStatus(state, { payload }) { - const { status, checkStatus, accountId, hash } = payload; - - const transactionsState = state.byAccountId[accountId].items; - - const transactionEntry = transactionsState.find((t) => t.hash === hash); - if (transactionEntry) { - Object.assign(transactionEntry, { status, checkStatus}); - } - } }, extraReducers: ((builder) => { handleAsyncThunkStatus({ @@ -102,32 +88,24 @@ export const actions = { ...transactionsSlice.actions }; -const getAccountIdParam = createParameterSelector((params) => params.accountId); -const getHashParam = createParameterSelector((params) => params.hash); +// entity adapter selectors +export const { + selectAll: selectTransactionsByAccountId, + selectTotal: selectTransactionsByAccountIdTotal +} = transactionsAdapter.getSelectors((state, { accountId }) => ({ + ...initialAccountIdState, + ...state.transactions.byAccountId[accountId] +})); -// Top level selectors -const selectTransactionsSlice = (state) => state[SLICE_NAME] || {}; +export const selectTransactionsOneByIdentity = (state, { accountId, id }) => transactionsAdapter.getSelectors().selectById({ + ...initialAccountIdState, + ...state.transactions.byAccountId[accountId] +}, id); -export const selectTransactionsObjectByAccountId = createSelector( - [selectTransactionsSlice, getAccountIdParam], - (transactions, accountId) => ({ - ...initialAccountIdState, - ...transactions.byAccountId[accountId] - }) -); - -export const selectTransactionsByAccountId = createSelector( - [selectTransactionsObjectByAccountId], - (transactions) => transactions.items -); +// status selectors +export const selectTransactionsObject = (state, { accountId }) => state[SLICE_NAME].byAccountId[accountId] || {}; -export const selectOneTransactionByIdentity = createSelector( - [selectTransactionsByAccountId, getHashParam], - (transactions, hash) => transactions.find((transaction) => transaction.hash_with_index === hash) -); +export const selectTransactionsStatus = createSelector([selectTransactionsObject], (transactions) => transactions.status || initialStatusState); -export const selectTransactionsLoading = createSelector( - [selectTransactionsObjectByAccountId], - (transactions) => transactions.status.loading || false -); +export const selectTransactionsLoading = createSelector(selectTransactionsStatus, (status) => status.loading || initialStatusState.loading); From afcbfb9130f07197b5aae311cf25ec154b9f8750 Mon Sep 17 00:00:00 2001 From: no Date: Mon, 6 Jun 2022 18:19:21 +0100 Subject: [PATCH 02/41] config: update mixpanel tokens --- packages/frontend/netlify.toml | 2 +- .../src/config/environmentDefaults/mainnet.js | 110 +++++++++--------- .../environmentDefaults/mainnet_STAGING.js | 4 +- .../src/config/environmentDefaults/testnet.js | 6 +- .../environmentDefaults/testnet_STAGING.js | 6 +- 5 files changed, 64 insertions(+), 64 deletions(-) diff --git a/packages/frontend/netlify.toml b/packages/frontend/netlify.toml index 4269757752..2c5b67d709 100644 --- a/packages/frontend/netlify.toml +++ b/packages/frontend/netlify.toml @@ -26,7 +26,7 @@ NETLIFY_USE_YARN = "true" [context.production.environment] - BROWSER_MIXPANEL_TOKEN = "d5bbbbcc3a77ef8427f2b806b5689bf8" + BROWSER_MIXPANEL_TOKEN = "7c5730e5b3556a06b73829b3c3b40a86" SENTRY_PROJECT = "mainnet-wallet" [[headers]] diff --git a/packages/frontend/src/config/environmentDefaults/mainnet.js b/packages/frontend/src/config/environmentDefaults/mainnet.js index 477a36519f..845ec0f9df 100644 --- a/packages/frontend/src/config/environmentDefaults/mainnet.js +++ b/packages/frontend/src/config/environmentDefaults/mainnet.js @@ -2,59 +2,59 @@ import * as nearApiJs from 'near-api-js'; import { parseNearAmount } from 'near-api-js/lib/utils/format'; export default { - ACCOUNT_HELPER_URL: 'https://helper.mainnet.near.org', - ACCOUNT_ID_SUFFIX: 'near', - ACCESS_KEY_FUNDING_AMOUNT: nearApiJs.utils.format.parseNearAmount('0.25'), - ALLOW_2FA_ENABLE_HASHES: [ - 'E8jZ1giWcVrps8PcV75ATauu6gFRkcwjNtKp7NKmipZG', - '11111111111111111111111111111111' - ], - BROWSER_MIXPANEL_TOKEN: 'd5bbbbcc3a77ef8427f2b806b5689bf8', - DISABLE_CREATE_ACCOUNT: true, - DISABLE_PHONE_RECOVERY: true, - EXPLORE_APPS_URL: 'https://awesomenear.com/', - EXPLORE_DEFI_URL: 'https://awesomenear.com/categories/defi/', - EXPLORER_URL: 'https://explorer.mainnet.near.org', - HIDE_SIGN_IN_WITH_LEDGER_ENTER_ACCOUNT_ID_MODAL: false, - INDEXER_SERVICE_URL: 'https://api.kitwallet.app', - LINKDROP_GAS: '100000000000000', - LOCKUP_ACCOUNT_ID_SUFFIX: 'lockup.near', - MIN_BALANCE_FOR_GAS: nearApiJs.utils.format.parseNearAmount('0.05'), - MIN_BALANCE_TO_CREATE: nearApiJs.utils.format.parseNearAmount('0.1'), - MOONPAY_API_KEY: 'pk_live_jYDdkGL7bJsrwalHZs1lVIhdOHOtK8BR', - MOONPAY_API_URL: 'https://api.moonpay.com', - MOONPAY_BUY_URL: 'https://buy.moonpay.io?apiKey=', - UTORG_ORDER_URL:'https://app.utorg.pro/direct/wallet.near.org/', - MULTISIG_CONTRACT_HASHES: [ - // https://github.com/near/core-contracts/blob/fa3e2c6819ef790fdb1ec9eed6b4104cd13eb4b7/multisig/src/lib.rs - '7GQStUCd8bmCK43bzD8PRh7sD2uyyeMJU5h8Rj3kXXJk', - // https://github.com/near/core-contracts/blob/fb595e6ec09014d392e9874c2c5d6bbc910362c7/multisig/src/lib.rs - 'AEE3vt6S3pS2s7K6HXnZc46VyMyJcjygSMsaafFh67DF', - // https://github.com/near/core-contracts/blob/636e7e43f1205f4d81431fad0be39c5cb65455f1/multisig/src/lib.rs - '8DKTSceSbxVgh4ANXwqmRqGyPWCuZAR1fCqGPXUjD5nZ', - // https://github.com/near/core-contracts/blob/f93c146d87a779a2063a30d2c1567701306fcae4/multisig/res/multisig.wasm - '55E7imniT2uuYrECn17qJAk9fLcwQW4ftNSwmCJL5Di', - ], - MULTISIG_MIN_AMOUNT: '4', - MULTISIG_MIN_PROMPT_AMOUNT: '200', - NETWORK_ID: 'default', - NODE_URL: 'https://rpc.mainnet.near.org', - REACT_APP_USE_TESTINGLOCKUP: false, - RECAPTCHA_CHALLENGE_API_KEY: '6LeRzswaAAAAAGeS7mSasZ1wDcGnMcH3D7W1gy1b', - RECAPTCHA_ENTERPRISE_SITE_KEY: '6LcpJ3EcAAAAAFgA-nixKFNGWMo9IG9FQhH4XjSY', - SENTRY_DSN: 'https://15d0d1b94e8548dd9663b8c93bf4550a@o398573.ingest.sentry.io/5396205', - SHOW_PRERELEASE_WARNING: false, - SMS_BLACKLIST: ['CN', 'VN', 'TH'], - STAKING_GAS_BASE: '25000000000000', // 25 Tgas - WHITELISTED_CONTRACTS: [ - 'berryclub.ek.near', - 'wrap.near', - '6b175474e89094c44da98b954eedeac495271d0f.factory.bridge.near', - ], - NEAR_TOKEN_ID: 'wrap.near', - FARMING_CLAIM_GAS: parseNearAmount('0.00000000015'), - FARMING_CLAIM_YOCTO: '1', - REF_FINANCE_API_ENDPOINT: 'https://indexer.ref-finance.net', - REF_FINANCE_CONTRACT: 'v2.ref-finance.near', - USN_CONTRACT: 'usn' + ACCOUNT_HELPER_URL: 'https://helper.mainnet.near.org', + ACCOUNT_ID_SUFFIX: 'near', + ACCESS_KEY_FUNDING_AMOUNT: nearApiJs.utils.format.parseNearAmount('0.25'), + ALLOW_2FA_ENABLE_HASHES: [ + 'E8jZ1giWcVrps8PcV75ATauu6gFRkcwjNtKp7NKmipZG', + '11111111111111111111111111111111' + ], + BROWSER_MIXPANEL_TOKEN: '7c5730e5b3556a06b73829b3c3b40a86', + DISABLE_CREATE_ACCOUNT: true, + DISABLE_PHONE_RECOVERY: true, + EXPLORE_APPS_URL: 'https://awesomenear.com/', + EXPLORE_DEFI_URL: 'https://awesomenear.com/categories/defi/', + EXPLORER_URL: 'https://explorer.mainnet.near.org', + HIDE_SIGN_IN_WITH_LEDGER_ENTER_ACCOUNT_ID_MODAL: false, + INDEXER_SERVICE_URL: 'https://api.kitwallet.app', + LINKDROP_GAS: '100000000000000', + LOCKUP_ACCOUNT_ID_SUFFIX: 'lockup.near', + MIN_BALANCE_FOR_GAS: nearApiJs.utils.format.parseNearAmount('0.05'), + MIN_BALANCE_TO_CREATE: nearApiJs.utils.format.parseNearAmount('0.1'), + MOONPAY_API_KEY: 'pk_live_jYDdkGL7bJsrwalHZs1lVIhdOHOtK8BR', + MOONPAY_API_URL: 'https://api.moonpay.com', + MOONPAY_BUY_URL: 'https://buy.moonpay.io?apiKey=', + UTORG_ORDER_URL: 'https://app.utorg.pro/direct/wallet.near.org/', + MULTISIG_CONTRACT_HASHES: [ + // https://github.com/near/core-contracts/blob/fa3e2c6819ef790fdb1ec9eed6b4104cd13eb4b7/multisig/src/lib.rs + '7GQStUCd8bmCK43bzD8PRh7sD2uyyeMJU5h8Rj3kXXJk', + // https://github.com/near/core-contracts/blob/fb595e6ec09014d392e9874c2c5d6bbc910362c7/multisig/src/lib.rs + 'AEE3vt6S3pS2s7K6HXnZc46VyMyJcjygSMsaafFh67DF', + // https://github.com/near/core-contracts/blob/636e7e43f1205f4d81431fad0be39c5cb65455f1/multisig/src/lib.rs + '8DKTSceSbxVgh4ANXwqmRqGyPWCuZAR1fCqGPXUjD5nZ', + // https://github.com/near/core-contracts/blob/f93c146d87a779a2063a30d2c1567701306fcae4/multisig/res/multisig.wasm + '55E7imniT2uuYrECn17qJAk9fLcwQW4ftNSwmCJL5Di', + ], + MULTISIG_MIN_AMOUNT: '4', + MULTISIG_MIN_PROMPT_AMOUNT: '200', + NETWORK_ID: 'default', + NODE_URL: 'https://rpc.mainnet.near.org', + REACT_APP_USE_TESTINGLOCKUP: false, + RECAPTCHA_CHALLENGE_API_KEY: '6LeRzswaAAAAAGeS7mSasZ1wDcGnMcH3D7W1gy1b', + RECAPTCHA_ENTERPRISE_SITE_KEY: '6LcpJ3EcAAAAAFgA-nixKFNGWMo9IG9FQhH4XjSY', + SENTRY_DSN: 'https://15d0d1b94e8548dd9663b8c93bf4550a@o398573.ingest.sentry.io/5396205', + SHOW_PRERELEASE_WARNING: false, + SMS_BLACKLIST: ['CN', 'VN', 'TH'], + STAKING_GAS_BASE: '25000000000000', // 25 Tgas + WHITELISTED_CONTRACTS: [ + 'berryclub.ek.near', + 'wrap.near', + '6b175474e89094c44da98b954eedeac495271d0f.factory.bridge.near', + ], + NEAR_TOKEN_ID: 'wrap.near', + FARMING_CLAIM_GAS: parseNearAmount('0.00000000015'), + FARMING_CLAIM_YOCTO: '1', + REF_FINANCE_API_ENDPOINT: 'https://indexer.ref-finance.net', + REF_FINANCE_CONTRACT: 'v2.ref-finance.near', + USN_CONTRACT: 'usn' }; diff --git a/packages/frontend/src/config/environmentDefaults/mainnet_STAGING.js b/packages/frontend/src/config/environmentDefaults/mainnet_STAGING.js index 3d38f7d5ba..b10237de80 100644 --- a/packages/frontend/src/config/environmentDefaults/mainnet_STAGING.js +++ b/packages/frontend/src/config/environmentDefaults/mainnet_STAGING.js @@ -9,7 +9,7 @@ export default { 'E8jZ1giWcVrps8PcV75ATauu6gFRkcwjNtKp7NKmipZG', '11111111111111111111111111111111' ], - BROWSER_MIXPANEL_TOKEN: 'd5bbbbcc3a77ef8427f2b806b5689bf8', + BROWSER_MIXPANEL_TOKEN: '7c5730e5b3556a06b73829b3c3b40a86', DISABLE_CREATE_ACCOUNT: true, DISABLE_PHONE_RECOVERY: true, EXPLORE_APPS_URL: 'https://awesomenear.com/', @@ -24,7 +24,7 @@ export default { MOONPAY_API_KEY: 'pk_live_jYDdkGL7bJsrwalHZs1lVIhdOHOtK8BR', MOONPAY_API_URL: 'https://api.moonpay.com', MOONPAY_BUY_URL: 'https://buy.moonpay.io?apiKey=', - UTORG_ORDER_URL:'https://app.utorg.pro/direct/wallet.staging.near.org/', + UTORG_ORDER_URL: 'https://app.utorg.pro/direct/wallet.staging.near.org/', MULTISIG_CONTRACT_HASHES: [ // https://github.com/near/core-contracts/blob/fa3e2c6819ef790fdb1ec9eed6b4104cd13eb4b7/multisig/src/lib.rs '7GQStUCd8bmCK43bzD8PRh7sD2uyyeMJU5h8Rj3kXXJk', diff --git a/packages/frontend/src/config/environmentDefaults/testnet.js b/packages/frontend/src/config/environmentDefaults/testnet.js index 94bdb3594e..b3dbb43acb 100644 --- a/packages/frontend/src/config/environmentDefaults/testnet.js +++ b/packages/frontend/src/config/environmentDefaults/testnet.js @@ -9,7 +9,7 @@ export default { 'E8jZ1giWcVrps8PcV75ATauu6gFRkcwjNtKp7NKmipZG', '11111111111111111111111111111111' ], - BROWSER_MIXPANEL_TOKEN: '9edede4b70de19f399736d5840872910', + BROWSER_MIXPANEL_TOKEN: '778bd24eec7329cf885f0cecfc3d4f5d', DISABLE_CREATE_ACCOUNT: false, DISABLE_PHONE_RECOVERY: false, EXPLORE_APPS_URL: 'https://awesomenear.com/', @@ -24,7 +24,7 @@ export default { MOONPAY_API_KEY: 'pk_test_wQDTsWBsvUm7cPiz9XowdtNeL5xasP9', MOONPAY_API_URL: 'https://api.moonpay.com', MOONPAY_BUY_URL: 'https://buy.moonpay.io?apiKey=', - UTORG_ORDER_URL:'https://app.utorg.pro/direct/wallet.near.org/', + UTORG_ORDER_URL: 'https://app.utorg.pro/direct/wallet.near.org/', MULTISIG_CONTRACT_HASHES: [ // https://github.com/near/core-contracts/blob/fa3e2c6819ef790fdb1ec9eed6b4104cd13eb4b7/multisig/src/lib.rs '7GQStUCd8bmCK43bzD8PRh7sD2uyyeMJU5h8Rj3kXXJk', @@ -43,7 +43,7 @@ export default { SENTRY_DSN: 'https://75d1dabd0ab646329fad8a3e7d6c761d@o398573.ingest.sentry.io/5254526', SHOW_PRERELEASE_WARNING: false, SMS_BLACKLIST: ['CN', 'VN', 'TH'], - STAKING_GAS_BASE:'25000000000000', // 25 Tgas + STAKING_GAS_BASE: '25000000000000', // 25 Tgas WHITELISTED_CONTRACTS: ['meta.pool.testnet'], NEAR_TOKEN_ID: 'wrap.testnet', FARMING_CLAIM_GAS: parseNearAmount('0.00000000015'), diff --git a/packages/frontend/src/config/environmentDefaults/testnet_STAGING.js b/packages/frontend/src/config/environmentDefaults/testnet_STAGING.js index 8a6a31cd5f..f674d7e6fb 100644 --- a/packages/frontend/src/config/environmentDefaults/testnet_STAGING.js +++ b/packages/frontend/src/config/environmentDefaults/testnet_STAGING.js @@ -9,7 +9,7 @@ export default { 'E8jZ1giWcVrps8PcV75ATauu6gFRkcwjNtKp7NKmipZG', '11111111111111111111111111111111' ], - BROWSER_MIXPANEL_TOKEN: '9edede4b70de19f399736d5840872910', + BROWSER_MIXPANEL_TOKEN: '778bd24eec7329cf885f0cecfc3d4f5d', DISABLE_CREATE_ACCOUNT: false, DISABLE_PHONE_RECOVERY: false, EXPLORE_APPS_URL: 'https://awesomenear.com/', @@ -23,7 +23,7 @@ export default { MOONPAY_API_KEY: 'pk_test_wQDTsWBsvUm7cPiz9XowdtNeL5xasP9', MOONPAY_API_URL: 'https://api.moonpay.com', MOONPAY_BUY_URL: 'https://buy.moonpay.io?apiKey=', - UTORG_ORDER_URL:'https://app.utorg.pro/direct/wallet.near.org/', + UTORG_ORDER_URL: 'https://app.utorg.pro/direct/wallet.near.org/', MULTISIG_CONTRACT_HASHES: [ // https://github.com/near/core-contracts/blob/fa3e2c6819ef790fdb1ec9eed6b4104cd13eb4b7/multisig/src/lib.rs '7GQStUCd8bmCK43bzD8PRh7sD2uyyeMJU5h8Rj3kXXJk', @@ -42,7 +42,7 @@ export default { SENTRY_DSN: 'https://75d1dabd0ab646329fad8a3e7d6c761d@o398573.ingest.sentry.io/5254526', SHOW_PRERELEASE_WARNING: false, SMS_BLACKLIST: ['CN', 'VN', 'TH'], - STAKING_GAS_BASE:'25000000000000', // 25 Tgas + STAKING_GAS_BASE: '25000000000000', // 25 Tgas WHITELISTED_CONTRACTS: ['meta.pool.testnet'], NEAR_TOKEN_ID: 'wrap.testnet', FARMING_CLAIM_GAS: parseNearAmount('0.00000000015'), From 795241da136e653a7a51f313543c969de5b10f89 Mon Sep 17 00:00:00 2001 From: esaminu Date: Thu, 2 Jun 2022 01:13:39 +0400 Subject: [PATCH 03/41] feat: inital state machine and UI for batch import --- packages/frontend/src/components/Routing.js | 17 +- .../components/accounts/AccountListImport.js | 183 +++++++++++ .../accounts/BatchImportAccounts.js | 299 ++++++++++++++++++ .../SignInLedgerViews/ImportAccounts.js | 174 +--------- .../src/components/common/modal/Modal.js | 11 +- packages/frontend/src/images/icon-shield.svg | 4 + packages/frontend/src/images/import-arrow.svg | 5 + .../frontend/src/translations/en.global.json | 8 + 8 files changed, 527 insertions(+), 174 deletions(-) create mode 100644 packages/frontend/src/components/accounts/AccountListImport.js create mode 100644 packages/frontend/src/components/accounts/BatchImportAccounts.js create mode 100644 packages/frontend/src/images/icon-shield.svg create mode 100644 packages/frontend/src/images/import-arrow.svg diff --git a/packages/frontend/src/components/Routing.js b/packages/frontend/src/components/Routing.js index da99326cb2..5f6c723246 100644 --- a/packages/frontend/src/components/Routing.js +++ b/packages/frontend/src/components/Routing.js @@ -2,7 +2,7 @@ import { ConnectedRouter, getRouter } from 'connected-react-router'; import isString from 'lodash.isstring'; import { parseSeedPhrase } from 'near-seed-phrase'; import PropTypes from 'prop-types'; -import { stringify } from 'query-string'; +import { parse, stringify } from 'query-string'; import React, { Component } from 'react'; import ReactDOMServer from 'react-dom/server'; import { withLocalize } from 'react-localize-redux'; @@ -54,6 +54,7 @@ import { } from '../utils/wallet'; import AccessKeysWrapper from './access-keys/v2/AccessKeysWrapper'; import { AutoImportWrapper } from './accounts/auto_import/AutoImportWrapper'; +import BatchImportAccounts from './accounts/BatchImportAccounts'; import { ExistingAccountWrapper } from './accounts/create/existing_account/ExistingAccountWrapper'; import { InitialDepositWrapper } from './accounts/create/initial_deposit/InitialDepositWrapper'; import { CreateAccountLanding } from './accounts/create/landing/CreateAccountLanding'; @@ -559,6 +560,20 @@ class Routing extends Component { ); }} /> + { + let { keys, accounts } = parse(location.hash, {arrayFormat: 'comma'}); + if (!keys || !accounts) return 'error-screen'; + + // if single key or account param make an array of it + keys = Array.isArray(keys) ? keys : [keys]; + accounts = Array.isArray(accounts) ? accounts : [accounts]; + + const accountIdToKeyMap = accounts.reduce((acc, curr) => { + const [ accountId, keyIndex ] = curr.split('*'); + return { ...acc, [accountId]: keys[keyIndex] }; + }, {}); + return this.props.history.replace('/')}/>; + })} /> div:first-of-type { + margin-top: ${(props) => `-${props.animate * 60}px`}; + transition: 1s; +} + +.accountId { + overflow: hidden; + font-size: 14px; + text-overflow: ellipsis; +} + +.row { + border-top: 2px solid #f5f5f5; + display: flex; + height: 60px; + align-items: center; + + &.success .status { + text-align: right; + } + &.error .status { + background: #ffb1b2; + color: #450002; + } + &.rejected .status { + background: #f4f4f4; + color: #de2e32; + } + &.confirm .status { + background: #f4c898; + color: #ae6816; + text-align: left; + padding: 0 0 0 10px; + flex: 0 0 140px; + + :after { + content: '.'; + animation: dots 1s steps(5, end) infinite; + + @keyframes dots { + 0%, 20% { + color: rgba(0,0,0,0); + text-shadow: + .3em 0 0 rgba(0,0,0,0), + .6em 0 0 rgba(0,0,0,0); + } + 40% { + color: #ae6816; + text-shadow: + .3em 0 0 rgba(0,0,0,0), + .6em 0 0 rgba(0,0,0,0); + } + 60% { + text-shadow: + .3em 0 0 #ae6816, + .6em 0 0 rgba(0,0,0,0); + } + 80%, 100% { + text-shadow: + .3em 0 0 #ae6816, + .6em 0 0 #ae6816; + } + } + } + } + &.pending .status { + background: #f4c898; + color: #ae6816; + text-align: left; + padding: 0 0 0 10px; + flex: 0 0 82px; + + :after { + content: '.'; + animation: dots 1s steps(5, end) infinite; + + @keyframes dots { + 0%, 20% { + color: rgba(0,0,0,0); + text-shadow: + .3em 0 0 rgba(0,0,0,0), + .6em 0 0 rgba(0,0,0,0); + } + 40% { + color: #ae6816; + text-shadow: + .3em 0 0 rgba(0,0,0,0), + .6em 0 0 rgba(0,0,0,0); + } + 60% { + text-shadow: + .3em 0 0 #ae6816, + .6em 0 0 rgba(0,0,0,0); + } + 80%, 100% { + text-shadow: + .3em 0 0 #ae6816, + .6em 0 0 #ae6816; + } + } + } + } + &.waiting { + .status { + background: #f8f8f8; + color: #aaaaaa; + } + > div:first-of-type { + opacity: 0.4; + } + h3 { + color: #aaaaaa !important; + } + } + + .status { + flex: 0 0 72px; + margin-left: auto; + height: 24px; + border-radius: 12px; + text-align: center; + font-size: 12px; + line-height: 24px; + } +} +`; + +const AccountListImport = ({ accounts = [], animationScope = 0 }) => ( + + {accounts.map((account) => ( +
+ + + +
+ {account.accountId} +
+ {account.status ? +
+ {account.status !== 'success' + ? + : + } +
+ : null} +
+ ))} +
+); + +export default AccountListImport; diff --git a/packages/frontend/src/components/accounts/BatchImportAccounts.js b/packages/frontend/src/components/accounts/BatchImportAccounts.js new file mode 100644 index 0000000000..4d9d6aad6b --- /dev/null +++ b/packages/frontend/src/components/accounts/BatchImportAccounts.js @@ -0,0 +1,299 @@ +import React, { useEffect, useReducer, useState } from 'react'; +import { Translate } from 'react-localize-redux'; +import { useSelector } from 'react-redux'; +import styled from 'styled-components'; + +import ShieldIcon from '../../images/icon-shield.svg'; +import ImportArrow from '../../images/import-arrow.svg'; +import { selectAvailableAccounts, selectAvailableAccountsIsLoading } from '../../redux/slices/availableAccounts'; +import FormButton from '../common/FormButton'; +import FormButtonGroup from '../common/FormButtonGroup'; +import Modal from '../common/modal/Modal'; +import Container from '../common/styled/Container.css'; +import AccountListImport from './AccountListImport'; + + +const CustomContainer = styled.div` + width: 100%; + margin-top: 40px; + + .buttons-bottom-buttons { + margin-top: 40px; + } + + .title { + text-align: left; + font-size: 12px; + } +`; + +const ModalContainer = styled(Container)` + display: flex; + flex-direction: column; + align-items: center; + padding: 24px; + + .top-icon { + height: 60px; + width: 60px; + margin-bottom: 40px; + } + + .desc { + padding: 0 45px; + text-align: center; + margin-top: 24px; + p { + margin: 0; + } + } + + .button-group, button { + align-self: stretch; + margin-top: 48px !important; + } +`; + +const KEY_TYPES = { + LEDGER: 'ledger', + MULTISIG: 'multisig', + FAK: 'fullAccessKey' +}; + +const ACTIONS = { + BEGIN_IMPORT: 'BEGIN_IMPORT', + SET_CURRENT_DONE: 'SET_CURRENT_DONE', + SET_CURRENT_FAILED: 'SET_CURRENT_FAILED', + CONFIRM_URL: 'CONFIRM_URL', + REMOVE_ACCOUNTS: 'REMOVE_ACCOUNTS' +}; + +const IMPORT_STATUS = { + PENDING: 'pending', + SUCCESS: 'success', + UP_NEXT: 'waiting', + FAILED: 'error' +}; + +const reducer = (state, action) => { + switch (action.type) { + case ACTIONS.REMOVE_ACCOUNTS: + return { + ...state, + accounts: state.accounts.filter( + (account) => + !action.accounts.some( + (accountId) => account.accountId === accountId + ) + ), + }; + case ACTIONS.BEGIN_IMPORT: + return state.accounts.every(({ status }) => status === null) + ? { + accounts: state.accounts.map((acc, idx) => ({ + ...acc, + status: + idx === 0 + ? IMPORT_STATUS.PENDING + : IMPORT_STATUS.UP_NEXT, + })), + urlConfirmed: false, + } + : state; + case ACTIONS.SET_CURRENT_DONE: { + let currentIndex = state.accounts.findIndex( + (account) => account.status === IMPORT_STATUS.PENDING + ); + return { + accounts: state.accounts.map((acc, idx) => ({ + ...acc, + status: + idx === currentIndex + ? IMPORT_STATUS.SUCCESS + : idx === currentIndex + 1 + ? IMPORT_STATUS.PENDING + : state.accounts[idx].status, + })), + urlConfirmed: true, + }; + } + case ACTIONS.SET_CURRENT_FAILED: { + let currentIndex = state.accounts.findIndex( + (account) => account.status === IMPORT_STATUS.PENDING + ); + return { + accounts: state.accounts.map((acc, idx) => ({ + ...acc, + status: + idx === currentIndex + ? IMPORT_STATUS.FAILED + : idx === currentIndex + 1 + ? IMPORT_STATUS.PENDING + : state.accounts[idx].status, + })), + urlConfirmed: true, + }; + } + case ACTIONS.CONFIRM_URL: + return { + ...state, + urlConfirmed: true, + }; + default: + return state; + } +}; + +/**TODO + * Add success screen + * get key type on mount modal + * handle import based on key type + * condition UI for import tx based on key type (hide transaction details and estimated fees if not FAK) + * Add remaining UI e.g. text above list and X icon for error state + */ + +const BatchImportAccounts = ({ accountIdToKeyMap, onCancel }) => { + const availableAccountsIsLoading = useSelector(selectAvailableAccountsIsLoading); + const availableAccounts = useSelector(selectAvailableAccounts); + + const [state, dispatch] = useReducer(reducer, { + accounts: Object.keys(accountIdToKeyMap).map((accountId) => ({ + accountId, + status: null, + key: accountIdToKeyMap[accountId], + })), + }); + const currentAccount = state.accounts.find((account) => account.status === IMPORT_STATUS.PENDING); + const accountsApproved = state.accounts.reduce((acc, curr) => curr.status === IMPORT_STATUS.SUCCESS ? acc + 1 : acc,0); + const completed = state.accounts.every((account) => account.status === IMPORT_STATUS.SUCCESS || account.status === IMPORT_STATUS.FAILED); + const showSuccessScreen = completed && state.accounts.some((account) => account.status === IMPORT_STATUS.SUCCESS); + + useEffect(() => { + if (!currentAccount) { + dispatch({type: ACTIONS.REMOVE_ACCOUNTS, accounts: availableAccounts}); + } + },[availableAccounts]); + + return showSuccessScreen ? null : ( + <> + + ImportArrow + +
+ {accountsApproved}/{state.accounts.length} +
+ +
+ + + + + + dispatch({ type: ACTIONS.BEGIN_IMPORT }) + } + disabled={availableAccountsIsLoading || completed} + > + + + + + + {currentAccount ? ( + state.urlConfirmed ? ( + + dispatch({ type: ACTIONS.SET_CURRENT_DONE }) + } + onFail={() => + dispatch({ type: ACTIONS.SET_CURRENT_FAILED }) + } + /> + ) : ( + {}} + disableClose + > + + SHIELD +

+ +

+
+

+ +

+
+ + dispatch({ type: ACTIONS.CONFIRM_URL }) + } + > + + +
+
+ ) + ) : null} + + ); +}; + +const AccountImportModal = ({ account, onSuccess, onFail }) => { + const [keyType, setKeyType] = useState(null); + + useEffect(() => { + // logic to fetch key type + setKeyType(KEY_TYPES.MULTISIG); + },[keyType]); + + return ( + {}} + disableClose + > + + SHIELD +

+ +

+
+

+ +

+
+ + + + + + + + +
+
+ ); +}; + +export default BatchImportAccounts; diff --git a/packages/frontend/src/components/accounts/ledger/SignInLedgerViews/ImportAccounts.js b/packages/frontend/src/components/accounts/ledger/SignInLedgerViews/ImportAccounts.js index 35230ff547..7328ec8efc 100644 --- a/packages/frontend/src/components/accounts/ledger/SignInLedgerViews/ImportAccounts.js +++ b/packages/frontend/src/components/accounts/ledger/SignInLedgerViews/ImportAccounts.js @@ -2,32 +2,11 @@ import React from 'react'; import { Translate } from 'react-localize-redux'; import styled from 'styled-components'; -import IconCheck from '../../../../images/IconCheck'; -import UserIconGrey from '../../../../images/UserIconGrey'; import { LEDGER_MODAL_STATUS } from '../../../../redux/slices/ledger'; import FormButton from '../../../common/FormButton'; import LedgerImageCircle from '../../../svg/LedgerImageCircle'; import LedgerSuccessIcon from '../../../svg/LedgerSuccessIcon'; - -const UserIcon = styled.div` - background-size: 21px; - flex: 0 0 40px; - height: 40px; - border-radius: 50%; - background-color: #f8f8f8; - text-align: center; - margin: 0 12px 0 0; - - svg { - width: 26px; - height: 26px; - margin: 7px; - } - - @media (min-width: 940px) { - display: inline-block; - } -`; +import AccountListImport from '../../AccountListImport'; const CustomContainer = styled.div` width: 100%; @@ -43,138 +22,6 @@ const CustomContainer = styled.div` } `; -const AnimateList = styled.div` - margin-top: 10px; - height: 180px; - overflow: hidden; - - & > div:first-of-type { - margin-top: ${(props) => `-${props.animate * 60}px`}; - transition: 1s; - } - - .accountId { - overflow: hidden; - font-size: 14px; - text-overflow: ellipsis; - } - - .row { - border-top: 2px solid #f5f5f5; - display: flex; - height: 60px; - align-items: center; - - &.success .status { - text-align: right; - } - &.error .status { - background: #ffb1b2; - color: #450002; - } - &.rejected .status { - background: #f4f4f4; - color: #de2e32; - } - &.confirm .status { - background: #f4c898; - color: #ae6816; - text-align: left; - padding: 0 0 0 10px; - flex: 0 0 140px; - - :after { - content: '.'; - animation: dots 1s steps(5, end) infinite; - - @keyframes dots { - 0%, 20% { - color: rgba(0,0,0,0); - text-shadow: - .3em 0 0 rgba(0,0,0,0), - .6em 0 0 rgba(0,0,0,0); - } - 40% { - color: #ae6816; - text-shadow: - .3em 0 0 rgba(0,0,0,0), - .6em 0 0 rgba(0,0,0,0); - } - 60% { - text-shadow: - .3em 0 0 #ae6816, - .6em 0 0 rgba(0,0,0,0); - } - 80%, 100% { - text-shadow: - .3em 0 0 #ae6816, - .6em 0 0 #ae6816; - } - } - } - } - &.pending .status { - background: #f4c898; - color: #ae6816; - text-align: left; - padding: 0 0 0 10px; - flex: 0 0 82px; - - :after { - content: '.'; - animation: dots 1s steps(5, end) infinite; - - @keyframes dots { - 0%, 20% { - color: rgba(0,0,0,0); - text-shadow: - .3em 0 0 rgba(0,0,0,0), - .6em 0 0 rgba(0,0,0,0); - } - 40% { - color: #ae6816; - text-shadow: - .3em 0 0 rgba(0,0,0,0), - .6em 0 0 rgba(0,0,0,0); - } - 60% { - text-shadow: - .3em 0 0 #ae6816, - .6em 0 0 rgba(0,0,0,0); - } - 80%, 100% { - text-shadow: - .3em 0 0 #ae6816, - .6em 0 0 #ae6816; - } - } - } - } - &.waiting { - .status { - background: #f8f8f8; - color: #aaaaaa; - } - > div:first-of-type { - opacity: 0.4; - } - h3 { - color: #aaaaaa !important; - } - } - - .status { - flex: 0 0 72px; - margin-left: auto; - height: 24px; - border-radius: 12px; - text-align: center; - font-size: 12px; - line-height: 24px; - } - } -`; - const ImportAccounts = ({ accountsApproved, totalAccounts, @@ -205,24 +52,7 @@ const ImportAccounts = ({
{accountsApproved}/{totalAccounts}
- - {ledgerAccounts.map((account) => ( -
- - - -
- {account.accountId} -
-
- {account.status !== 'success' - ? - : - } -
-
- ))} -
+ {success &&
{ setFadeType('out'); }, [isOpen]); + useEffect(() => { + if (isOpen) { + setFadeType('out'); + setTimeout(() => { + setFadeType('in'); + }, 500); + } else { + setFadeType('out'); + } + }, [isOpen]); const checkFullScreen = () => { const modalHeight = document.getElementById('modal-container').getBoundingClientRect().height; diff --git a/packages/frontend/src/images/icon-shield.svg b/packages/frontend/src/images/icon-shield.svg new file mode 100644 index 0000000000..1d1420a514 --- /dev/null +++ b/packages/frontend/src/images/icon-shield.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/frontend/src/images/import-arrow.svg b/packages/frontend/src/images/import-arrow.svg new file mode 100644 index 0000000000..d8e58e29f3 --- /dev/null +++ b/packages/frontend/src/images/import-arrow.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/frontend/src/translations/en.global.json b/packages/frontend/src/translations/en.global.json index 1a021afb86..b493caaf27 100644 --- a/packages/frontend/src/translations/en.global.json +++ b/packages/frontend/src/translations/en.global.json @@ -195,6 +195,12 @@ "available": "Available balance", "reserved": "Reserved for fees" }, + "batchImportAccounts": { + "confirmModal": { + "desc": "To ensure account security during your transfer, confirm that the URL in your browser is correct:", + "title": "Confirm Your URL" + } + }, "button": { "acceptAndContinue": "Accept & Continue", "addACustomAddress": "Add a Custom Address", @@ -205,6 +211,7 @@ "authorize": "Authorize", "authorizing": "Authorizing", "backToSwap": "Back to Swap", + "beginImport": "Begin Import", "browseApps": "Browse Apps", "buy": "Buy", "cancel": "Cancel", @@ -252,6 +259,7 @@ "learnMore": "Learn More", "learnMoreAboutNear": "Learn more about NEAR", "loading": "Loading", + "looksGood": "Looks Good!", "moreInformation": "More information", "needToEditGoBack": "Need to edit? Go Back", "next": "Next", From 34564aae4311258b2001535111fed91ac84b6d2c Mon Sep 17 00:00:00 2001 From: esaminu Date: Tue, 7 Jun 2022 01:54:43 +0400 Subject: [PATCH 04/41] feat: add import functionality and basic ui --- .../components/accounts/AccountListImport.js | 18 +- .../accounts/BatchImportAccounts.js | 258 +++++++++++++----- .../BatchImportAccountsSuccessScreen.js | 41 +++ .../src/components/common/balance/helpers.js | 7 + .../src/components/sign/v2/SignTransaction.js | 5 +- .../frontend/src/redux/actions/account.js | 5 +- .../frontend/src/translations/en.global.json | 8 +- packages/frontend/src/utils/wallet.js | 85 ++++++ yarn.lock | 2 +- 9 files changed, 352 insertions(+), 77 deletions(-) create mode 100644 packages/frontend/src/components/accounts/BatchImportAccountsSuccessScreen.js diff --git a/packages/frontend/src/components/accounts/AccountListImport.js b/packages/frontend/src/components/accounts/AccountListImport.js index 0d65f6ac63..2a855b882d 100644 --- a/packages/frontend/src/components/accounts/AccountListImport.js +++ b/packages/frontend/src/components/accounts/AccountListImport.js @@ -2,6 +2,7 @@ import React from 'react'; import { Translate } from 'react-localize-redux'; import styled from 'styled-components'; +import RightArrow from '../../images/icon-arrow-right.svg'; import IconCheck from '../../images/IconCheck'; import UserIconGrey from '../../images/UserIconGrey'; @@ -145,6 +146,11 @@ overflow: hidden; } } + .right-arrow { + width: 24px; + height: 24px; + } + .status { flex: 0 0 72px; margin-left: auto; @@ -157,17 +163,23 @@ overflow: hidden; } `; -const AccountListImport = ({ accounts = [], animationScope = 0 }) => ( +const AccountListImport = ({ accounts = [], animationScope = 0, onClickAccount }) => ( {accounts.map((account) => ( -
+
onClickAccount ? onClickAccount(account) : null} + style={{ cursor: onClickAccount ? 'pointer' : 'default' }} + >
{account.accountId}
- {account.status ? + {onClickAccount ?
RightArrow
: null} + {account.status && !onClickAccount ?
{account.status !== 'success' ? diff --git a/packages/frontend/src/components/accounts/BatchImportAccounts.js b/packages/frontend/src/components/accounts/BatchImportAccounts.js index 4d9d6aad6b..c98e67b84d 100644 --- a/packages/frontend/src/components/accounts/BatchImportAccounts.js +++ b/packages/frontend/src/components/accounts/BatchImportAccounts.js @@ -1,17 +1,26 @@ -import React, { useEffect, useReducer, useState } from 'react'; +import BN from 'bn.js'; +import { KeyPair, transactions } from 'near-api-js'; +import React, { useCallback, useEffect, useMemo, useReducer, useState } from 'react'; import { Translate } from 'react-localize-redux'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import styled from 'styled-components'; import ShieldIcon from '../../images/icon-shield.svg'; import ImportArrow from '../../images/import-arrow.svg'; +import refreshAccountOwner from '../../redux/sharedThunks/refreshAccountOwner'; +import { selectAccountUrlReferrer } from '../../redux/slices/account'; import { selectAvailableAccounts, selectAvailableAccountsIsLoading } from '../../redux/slices/availableAccounts'; +import { wallet } from '../../utils/wallet'; +import { getEstimatedFees } from '../common/balance/helpers'; import FormButton from '../common/FormButton'; import FormButtonGroup from '../common/FormButtonGroup'; import Modal from '../common/modal/Modal'; import Container from '../common/styled/Container.css'; +import ConnectWithApplication from '../login/v2/ConnectWithApplication'; +import SignTransaction from '../sign/v2/SignTransaction'; +import SignTransactionDetails from '../sign/v2/SignTransactionDetails'; import AccountListImport from './AccountListImport'; - +import BatchImportAccountsSuccessScreen from './BatchImportAccountsSuccessScreen'; const CustomContainer = styled.div` width: 100%; @@ -33,6 +42,10 @@ const ModalContainer = styled(Container)` align-items: center; padding: 24px; + h3 { + text-align: center; + } + .top-icon { height: 60px; width: 60px; @@ -48,17 +61,32 @@ const ModalContainer = styled(Container)` } } - .button-group, button { + button { align-self: stretch; - margin-top: 48px !important; } -`; -const KEY_TYPES = { - LEDGER: 'ledger', - MULTISIG: 'multisig', - FAK: 'fullAccessKey' -}; + .link { + margin-top: 16px !important; + font-weight: normal !important; + } + + .button-group { + align-self: stretch; + } + + .connect-with-application { + margin: 20px auto 30px auto; + } + + .transfer-amount { + width: 100%; + } + + .error-label { + margin-top: 16px; + color: #FC5B5B; + } +`; const ACTIONS = { BEGIN_IMPORT: 'BEGIN_IMPORT', @@ -78,15 +106,17 @@ const IMPORT_STATUS = { const reducer = (state, action) => { switch (action.type) { case ACTIONS.REMOVE_ACCOUNTS: - return { - ...state, - accounts: state.accounts.filter( - (account) => - !action.accounts.some( - (accountId) => account.accountId === accountId - ) - ), - }; + return state.accounts.every(({ status }) => status === null) + ? { + ...state, + accounts: state.accounts.filter( + (account) => + !action.accounts.some( + (accountId) => account.accountId === accountId + ) + ), + } + : state; case ACTIONS.BEGIN_IMPORT: return state.accounts.every(({ status }) => status === null) ? { @@ -144,14 +174,6 @@ const reducer = (state, action) => { } }; -/**TODO - * Add success screen - * get key type on mount modal - * handle import based on key type - * condition UI for import tx based on key type (hide transaction details and estimated fees if not FAK) - * Add remaining UI e.g. text above list and X icon for error state - */ - const BatchImportAccounts = ({ accountIdToKeyMap, onCancel }) => { const availableAccountsIsLoading = useSelector(selectAvailableAccountsIsLoading); const availableAccounts = useSelector(selectAvailableAccounts); @@ -163,10 +185,10 @@ const BatchImportAccounts = ({ accountIdToKeyMap, onCancel }) => { key: accountIdToKeyMap[accountId], })), }); - const currentAccount = state.accounts.find((account) => account.status === IMPORT_STATUS.PENDING); - const accountsApproved = state.accounts.reduce((acc, curr) => curr.status === IMPORT_STATUS.SUCCESS ? acc + 1 : acc,0); - const completed = state.accounts.every((account) => account.status === IMPORT_STATUS.SUCCESS || account.status === IMPORT_STATUS.FAILED); - const showSuccessScreen = completed && state.accounts.some((account) => account.status === IMPORT_STATUS.SUCCESS); + const currentAccount = useMemo(() => state.accounts.find((account) => account.status === IMPORT_STATUS.PENDING), [state.accounts]); + const accountsApproved = useMemo(() => state.accounts.reduce((acc, curr) => curr.status === IMPORT_STATUS.SUCCESS ? acc + 1 : acc,0), [state.accounts]); + const completed = useMemo(() => state.accounts.every((account) => account.status === IMPORT_STATUS.SUCCESS || account.status === IMPORT_STATUS.FAILED), [state.accounts]); + const showSuccessScreen = useMemo(() => completed && state.accounts.some((account) => account.status === IMPORT_STATUS.SUCCESS), [completed, state.accounts]); useEffect(() => { if (!currentAccount) { @@ -174,13 +196,20 @@ const BatchImportAccounts = ({ accountIdToKeyMap, onCancel }) => { } },[availableAccounts]); - return showSuccessScreen ? null : ( + return showSuccessScreen ? ( + status === IMPORT_STATUS.SUCCESS + )} + /> + ) : ( <> ImportArrow
- {accountsApproved}/{state.accounts.length} + {accountsApproved}/{state.accounts.length}{' '} +
@@ -211,7 +240,7 @@ const BatchImportAccounts = ({ accountIdToKeyMap, onCancel }) => { dispatch({ type: ACTIONS.SET_CURRENT_DONE }) } onFail={() => - dispatch({ type: ACTIONS.SET_CURRENT_FAILED }) + dispatch({ type: ACTIONS.SET_CURRENT_FAILED }) } /> ) : ( @@ -219,7 +248,7 @@ const BatchImportAccounts = ({ accountIdToKeyMap, onCancel }) => { isOpen={currentAccount} modalSize="sm" modalClass="slim" - onClose={()=>{}} + onClose={() => {}} disableClose > @@ -229,17 +258,18 @@ const BatchImportAccounts = ({ accountIdToKeyMap, onCancel }) => { className="top-icon" />

- +

- +

dispatch({ type: ACTIONS.CONFIRM_URL }) } + style={{ marginTop: 48 }} > @@ -253,45 +283,137 @@ const BatchImportAccounts = ({ accountIdToKeyMap, onCancel }) => { const AccountImportModal = ({ account, onSuccess, onFail }) => { const [keyType, setKeyType] = useState(null); + const [accountBalance, setAccountBalance] = useState(null); + const [showTxDetails, setShowTxDetails] = useState(false); + const [addingKey, setAddingKey] = useState(false); + const [error, setError] = useState(false); + const accountUrlReferrer = useSelector(selectAccountUrlReferrer); + const addFAKTransaction = useMemo(() => { + let tx; + try { + tx = { + receiverId: account.accountId, + actions: [transactions.addKey(KeyPair.fromString(account.key), transactions.fullAccessKey())] + }; + } catch (error) { + tx = null; + } + return tx; + }, [account]); + const estimatedAddFAKTransactionFees = useMemo(() => addFAKTransaction ? getEstimatedFees([addFAKTransaction]) : new BN('0') ,[addFAKTransaction]); + const dispatch = useDispatch(); useEffect(() => { - // logic to fetch key type - setKeyType(KEY_TYPES.MULTISIG); - },[keyType]); + setKeyType(null); + setAccountBalance(null); + setShowTxDetails(false); + setAddingKey(false); + setError(false); + + let keyPair; + + try { + keyPair = KeyPair.fromString(account.key).getPublicKey().toString(); + } catch (error) { + setError(true); + setKeyType(wallet.KEY_TYPES.OTHER); + } + + if (keyPair) { + wallet + .getPublicKeyType( + account.accountId, + keyPair + ) + .then(setKeyType).catch(() => { + setError(true); + setKeyType(wallet.KEY_TYPES.OTHER); + }); + } + + wallet + .getBalance(account.accountId) + .then(({ available }) => setAccountBalance(available)); + },[account]); + + const addKeyToWalletKeyStore = useCallback(() => { + setAddingKey(true); + setError(false); + let keyPair; + + try { + keyPair = KeyPair.fromString(account.key); + } catch (error) { + setError(true); + setAddingKey(false); + } + + if (keyPair) { + wallet + .addExistingAccountKeyToWalletKeyStore( + account.accountId, + keyPair + ) + .then(() => { + console.log('added'); + dispatch(refreshAccountOwner({})); + }) + .then(onSuccess).catch(() => { + setError(true); + setAddingKey(false); + }); + } + + }, [setAddingKey, setError, onSuccess, account]); return ( {}} + onClose={() => {}} disableClose > - - SHIELD -

- -

-
-

- -

-
- - - - - - - - -
+ {showTxDetails ? ( + setShowTxDetails(false)} + transactions={[addFAKTransaction]} + signGasFee={estimatedAddFAKTransactionFees.toString()} + /> + ) : ( + +

+ +

+ + + {keyType === wallet.KEY_TYPES.FAK ? ( + setShowTxDetails(true)}> + + + ) : null} + {error ?
: null} + + + + + + + + +
+ )}
); }; diff --git a/packages/frontend/src/components/accounts/BatchImportAccountsSuccessScreen.js b/packages/frontend/src/components/accounts/BatchImportAccountsSuccessScreen.js new file mode 100644 index 0000000000..a743e9777d --- /dev/null +++ b/packages/frontend/src/components/accounts/BatchImportAccountsSuccessScreen.js @@ -0,0 +1,41 @@ +import React from 'react'; +import { Translate } from 'react-localize-redux'; +import { useDispatch } from 'react-redux'; +import styled from 'styled-components'; + +import { redirectTo, switchAccount } from '../../redux/actions/account'; +import Container from '../common/styled/Container.css'; +import AvatarSuccessIcon from '../svg/AvatarSuccessIcon'; +import AccountListImport from './AccountListImport'; + +const CustomContainer = styled.div` + width: 100%; + margin-top: 40px; + + .title { + text-align: left; + font-size: 12px; + } +`; + +const BatchImportAccountsSuccessScreen = ({ accounts = [] }) => { + const dispatch = useDispatch(); + + return ( + + + +
+ {accounts.length} +
+ { + await dispatch(switchAccount({accountId})); + dispatch(redirectTo('/')); + }} + /> +
+
+ ); +}; + +export default BatchImportAccountsSuccessScreen; diff --git a/packages/frontend/src/components/common/balance/helpers.js b/packages/frontend/src/components/common/balance/helpers.js index 21a5cdcddf..0ef52dbbb9 100644 --- a/packages/frontend/src/components/common/balance/helpers.js +++ b/packages/frontend/src/components/common/balance/helpers.js @@ -1,6 +1,8 @@ import BN from 'bn.js'; +import cloneDeep from 'lodash.clonedeep'; import { utils } from 'near-api-js'; +import { calculateGasLimit, increaseGasForFirstTransaction } from '../../../redux/slices/sign'; import { formatTokenAmount } from '../../../utils/amounts'; const NEAR_FRACTIONAL_DIGITS = 5; @@ -67,4 +69,9 @@ export const getTotalBalanceFromFungibleTokensListUSD = (fungibleTokensList) => totalBalanceUSD += token.fiatValueMetadata.usd * formatTokenAmount(token.balance, token.onChainFTMetadata?.decimals, 5); } return Number(totalBalanceUSD.toFixed(2)); +} + +export const getEstimatedFees = (transactionsList) => { + const tx = increaseGasForFirstTransaction({ transactions: cloneDeep(transactionsList)}); + return new BN(calculateGasLimit(tx.flatMap((t) => t.actions))); }; diff --git a/packages/frontend/src/components/sign/v2/SignTransaction.js b/packages/frontend/src/components/sign/v2/SignTransaction.js index 974cc22404..52b61f728a 100644 --- a/packages/frontend/src/components/sign/v2/SignTransaction.js +++ b/packages/frontend/src/components/sign/v2/SignTransaction.js @@ -81,7 +81,8 @@ export default ({ transferAmount, sender, estimatedFees, - availableBalance + availableBalance, + fromLabelId }) => { const isTransferTransaction = new BN(transferAmount).gt(new BN(0)); return ( @@ -94,7 +95,7 @@ export default ({ /> }
- +
{sender}
(dispatch, getState) => { if ([WALLET_CREATE_NEW_ACCOUNT_URL, WALLET_LINKDROP_URL].includes(currentPage) && search !== '') { saveState(parsedUrl); dispatch(refreshUrl(parsedUrl)); - } else if ([WALLET_LOGIN_URL, WALLET_SIGN_URL].includes(currentPage) && search !== '') { + } else if ([WALLET_LOGIN_URL, WALLET_SIGN_URL, WALLET_BATCH_IMPORT_URL].includes(currentPage) && search !== '') { saveState(parsedUrl); dispatch(refreshUrl(parsedUrl)); dispatch(checkContractId()); diff --git a/packages/frontend/src/translations/en.global.json b/packages/frontend/src/translations/en.global.json index b493caaf27..36d2896673 100644 --- a/packages/frontend/src/translations/en.global.json +++ b/packages/frontend/src/translations/en.global.json @@ -196,7 +196,13 @@ "reserved": "Reserved for fees" }, "batchImportAccounts": { - "confirmModal": { + "confirmImportModal": { + "accountToImport": "Account to Import", + "desc": "To ensure account security during your transfer, confirm that the URL in your browser is correct:", + "title": "Approve Account Import from", + "transactionDetails": "+ Transaction Details" + }, + "confirmUrlModal": { "desc": "To ensure account security during your transfer, confirm that the URL in your browser is correct:", "title": "Confirm Your URL" } diff --git a/packages/frontend/src/utils/wallet.js b/packages/frontend/src/utils/wallet.js index f3d352c292..a7ca9aad6f 100644 --- a/packages/frontend/src/utils/wallet.js +++ b/packages/frontend/src/utils/wallet.js @@ -1,3 +1,4 @@ +import isEqual from 'lodash.isequal'; import * as nearApiJs from 'near-api-js'; import { MULTISIG_CHANGE_METHODS } from 'near-api-js/lib/account_multisig'; import { PublicKey } from 'near-api-js/lib/utils'; @@ -34,6 +35,7 @@ export const WALLET_CREATE_NEW_ACCOUNT_FLOW_URLS = [ ]; export const WALLET_LOGIN_URL = 'login'; export const WALLET_SIGN_URL = 'sign'; +export const WALLET_BATCH_IMPORT_URL = 'batch-import'; export const WALLET_INITIAL_DEPOSIT_URL = 'initial-deposit'; export const WALLET_LINKDROP_URL = 'linkdrop'; export const WALLET_RECOVER_ACCOUNT_URL = 'recover-account'; @@ -148,6 +150,13 @@ class Wallet { this.accountId = localStorage.getItem(KEY_ACTIVE_ACCOUNT_ID) || ''; } + KEY_TYPES = { + LEDGER: 'ledger', + MULTISIG: 'multisig', + FAK: 'fullAccessKey', + OTHER: 'other' + }; + async removeWalletAccount(accountId) { let walletAccounts = this.getAccountsLocalStorage(); delete walletAccounts[accountId]; @@ -272,6 +281,82 @@ class Wallet { }))); } + async getPublicKeyType(accountId, publicKeyString) { + const allKeys = await this.getAccessKeys(accountId); + const keyInfoView = allKeys.find(({public_key}) => public_key === publicKeyString); + + if (keyInfoView) { + if (this.isFullAccessKey(keyInfoView)) return this.KEY_TYPES.FAK; + if (this.isLedgerKey(accountId, keyInfoView)) return this.KEY_TYPES.LEDGER; + if (this.isMultisigKey(accountId, keyInfoView)) return this.KEY_TYPES.MULTISIG; + return this.KEY_TYPES.OTHER; + } + + throw new Error('No matching key pair for public key'); + } + + isFullAccessKey(keyInfoView) { + return keyInfoView?.access_key?.permission === 'FullAccess'; + } + + isLedgerKey(accountId, keyInfoView) { + const receiver_id = keyInfoView?.access_key?.permission?.FunctionCall?.receiver_id; + const method_names = keyInfoView?.access_key?.permission?.FunctionCall?.method_names; + return receiver_id === accountId && isEqual(method_names, ['__wallet__metadata']); + } + + isMultisigKey(accountId, keyInfoView) { + const receiver_id = keyInfoView?.access_key?.permission?.FunctionCall?.receiver_id; + const method_names = keyInfoView?.access_key?.permission?.FunctionCall?.method_names; + return receiver_id === accountId && isEqual(method_names, ['add_request', 'add_request_and_confirm', 'delete_request', 'confirm']); + } + + async addExistingAccountKeyToWalletKeyStore(accountId, keyPair) { + const keyType = await this.getPublicKeyType( + accountId, + keyPair.getPublicKey().toString() + ); + + switch (keyType) { + case this.KEY_TYPES.FAK: { + const keyStore = new nearApiJs.keyStores.InMemoryKeyStore(); + await keyStore.setKey(NETWORK_ID, accountId, keyPair); + const newKeyPair = nearApiJs.KeyPair.fromRandom('ed25519'); + return new nearApiJs.Account( + nearApiJs.Connection.fromConfig({ + networkId: NETWORK_ID, + provider: { + type: 'JsonRpcProvider', + args: { url: NODE_URL + '/' }, + }, + signer: new nearApiJs.InMemorySigner(keyStore), + }), + accountId + ) + .addKey(newKeyPair.getPublicKey()) + .then(() => this.saveAccount(accountId, newKeyPair)) + .then(() => { + if (!this.accountId) { + return this.makeAccountActive(accountId); + } + return this.save(); + }); + } + case this.KEY_TYPES.MULTISIG: + case this.KEY_TYPES.LEDGER: + return this.saveAccount(accountId, keyPair) + .then(() => keyType === this.KEY_TYPES.LEDGER && setKeyMeta(keyPair.getPublicKey(), {type: 'ledger'})) + .then(() => { + if (!this.accountId) { + return this.makeAccountActive(accountId); + } + return this.save(); + }); + default: + throw new Error('Unable to add unrecognized key to wallet key store'); + } + } + async removeAccessKey(publicKey) { return await (await this.getAccount(this.accountId)).deleteKey(publicKey); } diff --git a/yarn.lock b/yarn.lock index e13e96bc1d..992da38778 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8482,7 +8482,7 @@ lodash.mergewith@^4.6.2: lodash.omit@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60" - integrity sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA= + integrity sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg== lodash.once@^4.1.1: version "4.1.1" From 91bc310850d6eac478e278964b64e0ba352e35d5 Mon Sep 17 00:00:00 2001 From: esaminu Date: Tue, 7 Jun 2022 02:34:23 +0400 Subject: [PATCH 05/41] feat: update yarn lock --- yarn.lock | 268 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 253 insertions(+), 15 deletions(-) diff --git a/yarn.lock b/yarn.lock index 992da38778..6f4bc130fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,14 @@ # yarn lockfile v1 +"@ampproject/remapping@^2.1.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" + integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== + dependencies: + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" + "@babel/code-frame@^7.0.0": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb" @@ -16,12 +24,24 @@ dependencies: "@babel/highlight" "^7.16.0" +"@babel/code-frame@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" + integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== + dependencies: + "@babel/highlight" "^7.16.7" + "@babel/compat-data@^7.13.11", "@babel/compat-data@^7.16.0", "@babel/compat-data@^7.16.4": version "7.16.4" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.4.tgz#081d6bbc336ec5c2435c6346b2ae1fb98b5ac68e" integrity sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q== -"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.14.3", "@babel/core@^7.14.8", "@babel/core@^7.4.4", "@babel/core@^7.7.2", "@babel/core@^7.7.5": +"@babel/compat-data@^7.17.10": + version "7.17.10" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.10.tgz#711dc726a492dfc8be8220028b1b92482362baab" + integrity sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw== + +"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.14.8", "@babel/core@^7.4.4", "@babel/core@^7.7.2", "@babel/core@^7.7.5": version "7.16.5" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.16.5.tgz#924aa9e1ae56e1e55f7184c8bf073a50d8677f5c" integrity sha512-wUcenlLzuWMZ9Zt8S0KmFwGlH6QKRh3vsm/dhDA3CHkiTA45YuG1XkHRcNRl73EFPXDp/d5kVOU0/y7x2w6OaQ== @@ -42,6 +62,27 @@ semver "^6.3.0" source-map "^0.5.0" +"@babel/core@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.2.tgz#87b2fcd7cce9becaa7f5acebdc4f09f3dd19d876" + integrity sha512-A8pri1YJiC5UnkdrWcmfZTJTV85b4UXTAfImGmCfYmax4TR9Cw8sDS0MOk++Gp2mE/BefVJ5nwy5yzqNJbP/DQ== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.18.2" + "@babel/helper-compilation-targets" "^7.18.2" + "@babel/helper-module-transforms" "^7.18.0" + "@babel/helpers" "^7.18.2" + "@babel/parser" "^7.18.0" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.18.2" + "@babel/types" "^7.18.2" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.1" + semver "^6.3.0" + "@babel/generator@^7.16.5", "@babel/generator@^7.4.4", "@babel/generator@^7.7.2": version "7.16.5" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.16.5.tgz#26e1192eb8f78e0a3acaf3eede3c6fc96d22bedf" @@ -51,6 +92,15 @@ jsesc "^2.5.1" source-map "^0.5.0" +"@babel/generator@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.2.tgz#33873d6f89b21efe2da63fe554460f3df1c5880d" + integrity sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw== + dependencies: + "@babel/types" "^7.18.2" + "@jridgewell/gen-mapping" "^0.3.0" + jsesc "^2.5.1" + "@babel/helper-annotate-as-pure@^7.16.0": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz#9a1f0ebcda53d9a2d00108c4ceace6a5d5f1f08d" @@ -76,6 +126,16 @@ browserslist "^4.17.5" semver "^6.3.0" +"@babel/helper-compilation-targets@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz#67a85a10cbd5fc7f1457fec2e7f45441dc6c754b" + integrity sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ== + dependencies: + "@babel/compat-data" "^7.17.10" + "@babel/helper-validator-option" "^7.16.7" + browserslist "^4.20.2" + semver "^6.3.0" + "@babel/helper-create-class-features-plugin@^7.16.0", "@babel/helper-create-class-features-plugin@^7.16.5": version "7.16.5" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.5.tgz#5d1bcd096792c1ebec6249eebc6358eec55d0cad" @@ -118,6 +178,11 @@ dependencies: "@babel/types" "^7.16.0" +"@babel/helper-environment-visitor@^7.16.7", "@babel/helper-environment-visitor@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz#8a6d2dedb53f6bf248e31b4baf38739ee4a637bd" + integrity sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ== + "@babel/helper-explode-assignable-expression@^7.16.0": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.0.tgz#753017337a15f46f9c09f674cff10cee9b9d7778" @@ -134,6 +199,14 @@ "@babel/template" "^7.16.0" "@babel/types" "^7.16.0" +"@babel/helper-function-name@^7.17.9": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz#136fcd54bc1da82fcb47565cf16fd8e444b1ff12" + integrity sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg== + dependencies: + "@babel/template" "^7.16.7" + "@babel/types" "^7.17.0" + "@babel/helper-get-function-arity@^7.16.0": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz#0088c7486b29a9cb5d948b1a1de46db66e089cfa" @@ -148,6 +221,13 @@ dependencies: "@babel/types" "^7.16.0" +"@babel/helper-hoist-variables@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" + integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== + dependencies: + "@babel/types" "^7.16.7" + "@babel/helper-member-expression-to-functions@^7.16.5": version "7.16.5" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.5.tgz#1bc9f7e87354e86f8879c67b316cb03d3dc2caab" @@ -162,6 +242,13 @@ dependencies: "@babel/types" "^7.16.0" +"@babel/helper-module-imports@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" + integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== + dependencies: + "@babel/types" "^7.16.7" + "@babel/helper-module-transforms@^7.16.5": version "7.16.5" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.16.5.tgz#530ebf6ea87b500f60840578515adda2af470a29" @@ -176,6 +263,20 @@ "@babel/traverse" "^7.16.5" "@babel/types" "^7.16.0" +"@babel/helper-module-transforms@^7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz#baf05dec7a5875fb9235bd34ca18bad4e21221cd" + integrity sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA== + dependencies: + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-simple-access" "^7.17.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/helper-validator-identifier" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.18.0" + "@babel/types" "^7.18.0" + "@babel/helper-optimise-call-expression@^7.16.0": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz#cecdb145d70c54096b1564f8e9f10cd7d193b338" @@ -215,6 +316,13 @@ dependencies: "@babel/types" "^7.16.0" +"@babel/helper-simple-access@^7.17.7": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz#4dc473c2169ac3a1c9f4a51cfcd091d1c36fcff9" + integrity sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ== + dependencies: + "@babel/types" "^7.18.2" + "@babel/helper-skip-transparent-expression-wrappers@^7.16.0": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz#0ee3388070147c3ae051e487eca3ebb0e2e8bb09" @@ -229,6 +337,13 @@ dependencies: "@babel/types" "^7.16.0" +"@babel/helper-split-export-declaration@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" + integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== + dependencies: + "@babel/types" "^7.16.7" + "@babel/helper-validator-identifier@^7.14.5": version "7.14.9" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48" @@ -239,11 +354,21 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== +"@babel/helper-validator-identifier@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" + integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== + "@babel/helper-validator-option@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== +"@babel/helper-validator-option@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" + integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== + "@babel/helper-wrap-function@^7.16.5": version "7.16.5" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.16.5.tgz#0158fca6f6d0889c3fee8a6ed6e5e07b9b54e41f" @@ -263,6 +388,15 @@ "@babel/traverse" "^7.16.5" "@babel/types" "^7.16.0" +"@babel/helpers@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.2.tgz#970d74f0deadc3f5a938bfa250738eb4ac889384" + integrity sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg== + dependencies: + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.18.2" + "@babel/types" "^7.18.2" + "@babel/highlight@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" @@ -281,6 +415,15 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/highlight@^7.16.7": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.17.12.tgz#257de56ee5afbd20451ac0a75686b6b404257351" + integrity sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + chalk "^2.0.0" + js-tokens "^4.0.0" + "@babel/parser@7.16.2": version "7.16.2" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.2.tgz#3723cd5c8d8773eef96ce57ea1d9b7faaccd12ac" @@ -291,6 +434,11 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.6.tgz#8f194828193e8fa79166f34a4b4e52f3e769a314" integrity sha512-Gr86ujcNuPDnNOY8mi383Hvi8IYrJVJYuf3XcuBM/Dgd+bINn/7tHqsj+tKkoreMbmGsFLsltI/JJd8fOFWGDQ== +"@babel/parser@^7.16.7", "@babel/parser@^7.18.0": + version "7.18.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.4.tgz#6774231779dd700e0af29f6ad8d479582d7ce5ef" + integrity sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.2": version "7.16.2" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.2.tgz#2977fca9b212db153c195674e57cfab807733183" @@ -1003,6 +1151,15 @@ "@babel/parser" "^7.16.0" "@babel/types" "^7.16.0" +"@babel/template@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" + integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/parser" "^7.16.7" + "@babel/types" "^7.16.7" + "@babel/traverse@^7.1.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.5", "@babel/traverse@^7.4.4", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2": version "7.16.5" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.5.tgz#d7d400a8229c714a59b87624fc67b0f1fbd4b2b3" @@ -1019,6 +1176,22 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/traverse@^7.18.0", "@babel/traverse@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.2.tgz#b77a52604b5cc836a9e1e08dca01cba67a12d2e8" + integrity sha512-9eNwoeovJ6KH9zcCNnENY7DMFwTU9JdGCFtqNLfUAqtUHRCOsTOqWoffosP8vKmNYeSBUv3yVJXjfd8ucwOjUA== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.18.2" + "@babel/helper-environment-visitor" "^7.18.2" + "@babel/helper-function-name" "^7.17.9" + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/parser" "^7.18.0" + "@babel/types" "^7.18.2" + debug "^4.1.0" + globals "^11.1.0" + "@babel/types@7.16.0", "@babel/types@^7.0.0", "@babel/types@^7.16.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.0.tgz#db3b313804f96aadd0b776c4823e127ad67289ba" @@ -1027,6 +1200,14 @@ "@babel/helper-validator-identifier" "^7.15.7" to-fast-properties "^2.0.0" +"@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.18.0", "@babel/types@^7.18.2": + version "7.18.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.4.tgz#27eae9b9fd18e9dccc3f9d6ad051336f307be354" + integrity sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -1309,6 +1490,46 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" +"@jridgewell/gen-mapping@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" + integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz#cf92a983c83466b8c0ce9124fadeaf09f7c66ea9" + integrity sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe" + integrity sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA== + +"@jridgewell/set-array@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.1.tgz#36a6acc93987adcf0ba50c66908bd0b70de8afea" + integrity sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.13" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c" + integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w== + +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz#dcfe3e95f224c8fe97a87a5235defec999aa92ea" + integrity sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@ledgerhq/devices@^5.51.1": version "5.51.1" resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-5.51.1.tgz#d741a4a5d8f17c2f9d282fd27147e6fe1999edb7" @@ -2057,15 +2278,6 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" -"@near-wallet/feature-flags@^0.0.4": - version "0.0.4" - resolved "https://registry.yarnpkg.com/@near-wallet/feature-flags/-/feature-flags-0.0.4.tgz#281a15ad66f9ba0c66bb63f81ea0b21a228071a9" - integrity sha512-TUu1io4U1CzvV5gGzLMGwQLFuA7Hj/RmRD1y0Ys1MdCcyErBced/cLIymQJtC5zZTYW6JlbJTQ3EmZb+HUaG4g== - dependencies: - fs-extra "^10.0.0" - ini "^2.0.0" - inquirer "^8.2.0" - "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -3543,6 +3755,17 @@ browserslist@^4.0.0, browserslist@^4.1.0, browserslist@^4.17.5, browserslist@^4. node-releases "^2.0.1" picocolors "^1.0.0" +browserslist@^4.20.2: + version "4.20.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.3.tgz#eb7572f49ec430e054f56d52ff0ebe9be915f8bf" + integrity sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg== + dependencies: + caniuse-lite "^1.0.30001332" + electron-to-chromium "^1.4.118" + escalade "^3.1.1" + node-releases "^2.0.3" + picocolors "^1.0.0" + bs58@^4.0.0, bs58@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" @@ -3743,6 +3966,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001286: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001287.tgz#5fab6a46ab9e47146d5dd35abfe47beaf8073c71" integrity sha512-4udbs9bc0hfNrcje++AxBuc6PfLNHwh3PO9kbwnfCQWyqtlzg3py0YgFu8jyRTTo85VAz4U+VLxSlID09vNtWA== +caniuse-lite@^1.0.30001332: + version "1.0.30001346" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001346.tgz#e895551b46b9cc9cc9de852facd42f04839a8fbe" + integrity sha512-q6ibZUO2t88QCIPayP/euuDREq+aMAxFE5S70PkrLh0iTDj/zEhgvJRKC2+CvXY6EWc6oQwUR48lL5vCW6jiXQ== + capability@^0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/capability/-/capability-0.2.5.tgz#51ad87353f1936ffd77f2f21c74633a4dea88801" @@ -5158,6 +5386,11 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= +electron-to-chromium@^1.4.118: + version "1.4.146" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.146.tgz#fd20970c3def2f9e6b32ac13a2e7a6b64e1b0c48" + integrity sha512-4eWebzDLd+hYLm4csbyMU2EbBnqhwl8Oe9eF/7CBDPWcRxFmqzx4izxvHH+lofQxzieg8UbB8ZuzNTxeukzfTg== + electron-to-chromium@^1.4.17: version "1.4.24" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.24.tgz#9cf8a92d5729c480ee47ff0aa5555f57467ae2fa" @@ -6999,11 +7232,6 @@ ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== -ini@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" - integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== - init-package-json@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-2.0.3.tgz#c8ae4f2a4ad353bcbc089e5ffe98a8f1a314e8fd" @@ -8180,6 +8408,11 @@ json5@^2.1.2: dependencies: minimist "^1.2.5" +json5@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== + jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -9313,6 +9546,11 @@ node-releases@^2.0.1: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== +node-releases@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.5.tgz#280ed5bc3eba0d96ce44897d8aee478bfb3d9666" + integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q== + nopt@^4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" From 5658c20b1719b49796c69fc34bbf0b6d25e7df9c Mon Sep 17 00:00:00 2001 From: esaminu Date: Tue, 7 Jun 2022 23:04:29 +0400 Subject: [PATCH 06/41] feat: Add ledgerHdPath param parsing and usage --- packages/frontend/src/components/Routing.js | 9 +++++---- .../src/components/accounts/BatchImportAccounts.js | 6 ++++-- packages/frontend/src/utils/wallet.js | 13 ++++++++++--- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/frontend/src/components/Routing.js b/packages/frontend/src/components/Routing.js index 5f6c723246..3bd3ca678c 100644 --- a/packages/frontend/src/components/Routing.js +++ b/packages/frontend/src/components/Routing.js @@ -561,16 +561,17 @@ class Routing extends Component { }} /> { - let { keys, accounts } = parse(location.hash, {arrayFormat: 'comma'}); - if (!keys || !accounts) return 'error-screen'; + let { keys, accounts, ledgerHdPaths } = parse(location.hash, {arrayFormat: 'comma'}); + if (!keys || !accounts) return ; // if single key or account param make an array of it keys = Array.isArray(keys) ? keys : [keys]; accounts = Array.isArray(accounts) ? accounts : [accounts]; + ledgerHdPaths = Array.isArray(ledgerHdPaths) ? ledgerHdPaths : [ledgerHdPaths]; const accountIdToKeyMap = accounts.reduce((acc, curr) => { - const [ accountId, keyIndex ] = curr.split('*'); - return { ...acc, [accountId]: keys[keyIndex] }; + const [ accountId, keyIndex, ledgerHdPathIndex ] = curr.split('*'); + return { ...acc, [accountId]: {key: keys[keyIndex], ledgerHdPath: ledgerHdPaths?.[ledgerHdPathIndex]} }; }, {}); return this.props.history.replace('/')}/>; })} /> diff --git a/packages/frontend/src/components/accounts/BatchImportAccounts.js b/packages/frontend/src/components/accounts/BatchImportAccounts.js index c98e67b84d..dedb24ecd4 100644 --- a/packages/frontend/src/components/accounts/BatchImportAccounts.js +++ b/packages/frontend/src/components/accounts/BatchImportAccounts.js @@ -182,7 +182,8 @@ const BatchImportAccounts = ({ accountIdToKeyMap, onCancel }) => { accounts: Object.keys(accountIdToKeyMap).map((accountId) => ({ accountId, status: null, - key: accountIdToKeyMap[accountId], + key: accountIdToKeyMap[accountId].key, + ledgerHdPath: accountIdToKeyMap[accountId].ledgerHdPath })), }); const currentAccount = useMemo(() => state.accounts.find((account) => account.status === IMPORT_STATUS.PENDING), [state.accounts]); @@ -352,7 +353,8 @@ const AccountImportModal = ({ account, onSuccess, onFail }) => { wallet .addExistingAccountKeyToWalletKeyStore( account.accountId, - keyPair + keyPair, + account.ledgerHdPath ) .then(() => { console.log('added'); diff --git a/packages/frontend/src/utils/wallet.js b/packages/frontend/src/utils/wallet.js index a7ca9aad6f..a11423099a 100644 --- a/packages/frontend/src/utils/wallet.js +++ b/packages/frontend/src/utils/wallet.js @@ -16,7 +16,7 @@ import sendJson from '../tmp_fetch_send_json'; import { decorateWithLockup } from './account-with-lockup'; import { getAccountIds } from './helper-api'; import { ledgerManager } from './ledgerManager'; -import { setAccountConfirmed, setWalletAccounts, removeActiveAccount, removeAccountConfirmed, getLedgerHDPath, removeLedgerHDPath } from './localStorage'; +import { setAccountConfirmed, setWalletAccounts, removeActiveAccount, removeAccountConfirmed, getLedgerHDPath, removeLedgerHDPath, setLedgerHdPath } from './localStorage'; import { TwoFactor } from './twoFactor'; import { WalletError } from './walletError'; @@ -311,7 +311,7 @@ class Wallet { return receiver_id === accountId && isEqual(method_names, ['add_request', 'add_request_and_confirm', 'delete_request', 'confirm']); } - async addExistingAccountKeyToWalletKeyStore(accountId, keyPair) { + async addExistingAccountKeyToWalletKeyStore(accountId, keyPair, ledgerHdPath) { const keyType = await this.getPublicKeyType( accountId, keyPair.getPublicKey().toString() @@ -345,7 +345,14 @@ class Wallet { case this.KEY_TYPES.MULTISIG: case this.KEY_TYPES.LEDGER: return this.saveAccount(accountId, keyPair) - .then(() => keyType === this.KEY_TYPES.LEDGER && setKeyMeta(keyPair.getPublicKey(), {type: 'ledger'})) + .then(() => { + if (keyType === this.KEY_TYPES.LEDGER) { + if (ledgerHdPath) { + setLedgerHdPath({accountId, path: ledgerHdPath}); + } + return this.getLedgerPublicKey(ledgerHdPath).then((publicKey) => setKeyMeta(publicKey.toString(), {type: "ledger"})); + } + }) .then(() => { if (!this.accountId) { return this.makeAccountActive(accountId); From 2930b714a0d6e11254291eee719aa572afe10458 Mon Sep 17 00:00:00 2001 From: esaminu Date: Tue, 7 Jun 2022 23:05:33 +0400 Subject: [PATCH 07/41] fix: lint errors --- packages/frontend/src/utils/wallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/utils/wallet.js b/packages/frontend/src/utils/wallet.js index a11423099a..b38d415957 100644 --- a/packages/frontend/src/utils/wallet.js +++ b/packages/frontend/src/utils/wallet.js @@ -350,7 +350,7 @@ class Wallet { if (ledgerHdPath) { setLedgerHdPath({accountId, path: ledgerHdPath}); } - return this.getLedgerPublicKey(ledgerHdPath).then((publicKey) => setKeyMeta(publicKey.toString(), {type: "ledger"})); + return this.getLedgerPublicKey(ledgerHdPath).then((publicKey) => setKeyMeta(publicKey.toString(), {type: 'ledger'})); } }) .then(() => { From 6db98415fe6c53cf47071579cd2bf001e1aabbb3 Mon Sep 17 00:00:00 2001 From: esaminu Date: Thu, 9 Jun 2022 23:58:30 +0400 Subject: [PATCH 08/41] feat: add copy and referrer --- .../components/accounts/AccountListImport.js | 42 ++++++++++++++----- .../accounts/BatchImportAccounts.js | 33 ++++++++++++--- .../BatchImportAccountsSuccessScreen.js | 26 ++++++++++-- .../src/components/common/balance/helpers.js | 2 +- .../frontend/src/images/IconArrowRight.js | 14 +++++++ packages/frontend/src/images/IconClose.js | 16 +++++++ .../frontend/src/translations/en.global.json | 8 ++++ packages/frontend/src/utils/getWalletURL.js | 18 +++++++- packages/frontend/src/utils/wallet.js | 12 +++--- yarn.lock | 16 ++++++- 10 files changed, 156 insertions(+), 31 deletions(-) create mode 100644 packages/frontend/src/images/IconArrowRight.js create mode 100644 packages/frontend/src/images/IconClose.js diff --git a/packages/frontend/src/components/accounts/AccountListImport.js b/packages/frontend/src/components/accounts/AccountListImport.js index 2a855b882d..e0cea84855 100644 --- a/packages/frontend/src/components/accounts/AccountListImport.js +++ b/packages/frontend/src/components/accounts/AccountListImport.js @@ -2,8 +2,9 @@ import React from 'react'; import { Translate } from 'react-localize-redux'; import styled from 'styled-components'; -import RightArrow from '../../images/icon-arrow-right.svg'; +import IconArrowRight from '../../images/IconArrowRight'; import IconCheck from '../../images/IconCheck'; +import IconClose from '../../images/IconClose'; import UserIconGrey from '../../images/UserIconGrey'; const UserIcon = styled.div` @@ -42,6 +43,13 @@ overflow: hidden; text-overflow: ellipsis; } +.status { + > svg { + width: 12px; + height: 12px; + } +} + .row { border-top: 2px solid #f5f5f5; display: flex; @@ -49,12 +57,18 @@ overflow: hidden; align-items: center; &.success .status { - text-align: right; - } - &.error .status { - background: #ffb1b2; - color: #450002; + > svg { + width: 24px; + height: 24px; + } + &.onclick { + > svg { + width: 12px; + height: 12px; + } + } } + &.rejected .status { background: #f4f4f4; color: #de2e32; @@ -178,13 +192,10 @@ const AccountListImport = ({ accounts = [], animationScope = 0, onClickAccount }
{account.accountId}
- {onClickAccount ?
RightArrow
: null} + {onClickAccount ?
: null} {account.status && !onClickAccount ?
- {account.status !== 'success' - ? - : - } +
: null}
@@ -192,4 +203,13 @@ const AccountListImport = ({ accounts = [], animationScope = 0, onClickAccount } ); +const StatusIcon = ({status}) => { + if (status === 'success') { + return ; + } else if (status === 'error') { + return ; + } + return ; +}; + export default AccountListImport; diff --git a/packages/frontend/src/components/accounts/BatchImportAccounts.js b/packages/frontend/src/components/accounts/BatchImportAccounts.js index dedb24ecd4..4d86070cb8 100644 --- a/packages/frontend/src/components/accounts/BatchImportAccounts.js +++ b/packages/frontend/src/components/accounts/BatchImportAccounts.js @@ -10,6 +10,7 @@ import ImportArrow from '../../images/import-arrow.svg'; import refreshAccountOwner from '../../redux/sharedThunks/refreshAccountOwner'; import { selectAccountUrlReferrer } from '../../redux/slices/account'; import { selectAvailableAccounts, selectAvailableAccountsIsLoading } from '../../redux/slices/availableAccounts'; +import getWalletURL from '../../utils/getWalletURL'; import { wallet } from '../../utils/wallet'; import { getEstimatedFees } from '../common/balance/helpers'; import FormButton from '../common/FormButton'; @@ -34,6 +35,11 @@ const CustomContainer = styled.div` text-align: left; font-size: 12px; } + + .screen-descripton { + margin-top: 40px; + margin-bottom: 56px; + } `; const ModalContainer = styled(Container)` @@ -86,6 +92,11 @@ const ModalContainer = styled(Container)` margin-top: 16px; color: #FC5B5B; } + + .wallet-url { + color: #000; + font-weight: bold; + } `; const ACTIONS = { @@ -177,6 +188,7 @@ const reducer = (state, action) => { const BatchImportAccounts = ({ accountIdToKeyMap, onCancel }) => { const availableAccountsIsLoading = useSelector(selectAvailableAccountsIsLoading); const availableAccounts = useSelector(selectAvailableAccounts); + const accountUrlReferrer = useSelector(selectAccountUrlReferrer); const [state, dispatch] = useReducer(reducer, { accounts: Object.keys(accountIdToKeyMap).map((accountId) => ({ @@ -206,8 +218,17 @@ const BatchImportAccounts = ({ accountIdToKeyMap, onCancel }) => { ) : ( <> - ImportArrow - + + ImportArrow +
+

+ + {accountUrlReferrer || } +

+
+
+ +
{accountsApproved}/{state.accounts.length}{' '} @@ -265,6 +286,9 @@ const BatchImportAccounts = ({ accountIdToKeyMap, onCancel }) => {

+
+
+ {getWalletURL()}
@@ -356,10 +380,7 @@ const AccountImportModal = ({ account, onSuccess, onFail }) => { keyPair, account.ledgerHdPath ) - .then(() => { - console.log('added'); - dispatch(refreshAccountOwner({})); - }) + .then(() => dispatch(refreshAccountOwner({}))) .then(onSuccess).catch(() => { setError(true); setAddingKey(false); diff --git a/packages/frontend/src/components/accounts/BatchImportAccountsSuccessScreen.js b/packages/frontend/src/components/accounts/BatchImportAccountsSuccessScreen.js index a743e9777d..e5f498dfd2 100644 --- a/packages/frontend/src/components/accounts/BatchImportAccountsSuccessScreen.js +++ b/packages/frontend/src/components/accounts/BatchImportAccountsSuccessScreen.js @@ -1,30 +1,48 @@ import React from 'react'; import { Translate } from 'react-localize-redux'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import styled from 'styled-components'; import { redirectTo, switchAccount } from '../../redux/actions/account'; +import { selectAccountUrlReferrer } from '../../redux/slices/account'; import Container from '../common/styled/Container.css'; import AvatarSuccessIcon from '../svg/AvatarSuccessIcon'; import AccountListImport from './AccountListImport'; const CustomContainer = styled.div` width: 100%; - margin-top: 40px; - .title { text-align: left; font-size: 12px; } + + .screen-descripton { + margin-top: 40px; + margin-bottom: 56px; + } + + svg { + margin-bottom: 0; + } `; const BatchImportAccountsSuccessScreen = ({ accounts = [] }) => { const dispatch = useDispatch(); + const accountUrlReferrer = useSelector(selectAccountUrlReferrer); return ( - + +
+

+ + {accountUrlReferrer || } +

+
+
+ +
{accounts.length}
diff --git a/packages/frontend/src/components/common/balance/helpers.js b/packages/frontend/src/components/common/balance/helpers.js index 0ef52dbbb9..0d96695953 100644 --- a/packages/frontend/src/components/common/balance/helpers.js +++ b/packages/frontend/src/components/common/balance/helpers.js @@ -69,7 +69,7 @@ export const getTotalBalanceFromFungibleTokensListUSD = (fungibleTokensList) => totalBalanceUSD += token.fiatValueMetadata.usd * formatTokenAmount(token.balance, token.onChainFTMetadata?.decimals, 5); } return Number(totalBalanceUSD.toFixed(2)); -} +}; export const getEstimatedFees = (transactionsList) => { const tx = increaseGasForFirstTransaction({ transactions: cloneDeep(transactionsList)}); diff --git a/packages/frontend/src/images/IconArrowRight.js b/packages/frontend/src/images/IconArrowRight.js new file mode 100644 index 0000000000..af9650a0f6 --- /dev/null +++ b/packages/frontend/src/images/IconArrowRight.js @@ -0,0 +1,14 @@ +import React from 'react'; + +export default ({ stroke = '#ccc' }) => ( + + + +); diff --git a/packages/frontend/src/images/IconClose.js b/packages/frontend/src/images/IconClose.js new file mode 100644 index 0000000000..30f04536a2 --- /dev/null +++ b/packages/frontend/src/images/IconClose.js @@ -0,0 +1,16 @@ +import React from 'react'; + +export default ({ stroke = '#ccc' }) => ( + + + + + + +); diff --git a/packages/frontend/src/translations/en.global.json b/packages/frontend/src/translations/en.global.json index 36d2896673..e54531fa03 100644 --- a/packages/frontend/src/translations/en.global.json +++ b/packages/frontend/src/translations/en.global.json @@ -205,6 +205,14 @@ "confirmUrlModal": { "desc": "To ensure account security during your transfer, confirm that the URL in your browser is correct:", "title": "Confirm Your URL" + }, + "importScreen": { + "desc": "Begin your import and confirm each account
when prompted.", + "title": "Ready to import ${noOfAccounts} account(s) from
" + }, + "successScreen": { + "desc": "Select an account to continue to your account dashboard.", + "title": "${noOfAccounts} account(s) were successfully
imported from " } }, "button": { diff --git a/packages/frontend/src/utils/getWalletURL.js b/packages/frontend/src/utils/getWalletURL.js index 2b00946fef..2fa44cee33 100644 --- a/packages/frontend/src/utils/getWalletURL.js +++ b/packages/frontend/src/utils/getWalletURL.js @@ -1,6 +1,7 @@ -import { IS_MAINNET, SHOW_PRERELEASE_WARNING } from '../config'; +import { IS_MAINNET, SHOW_PRERELEASE_WARNING, NEAR_WALLET_ENV } from '../config'; +import { isWhitelabel } from '../config/whitelabel'; -export default (https = true) => { +const getNearOrgWalletUrl = (https = true) => { let networkName = ''; if (SHOW_PRERELEASE_WARNING) { @@ -11,3 +12,16 @@ export default (https = true) => { return `${https ? 'https://' : ''}wallet.${networkName}near.org`; }; + +const getMyNearWalletUrl = (https = true) => { + const prefix = { + 'testnet': 'testnet.', + 'mainnet': 'app.', + 'development': 'testnet.', + 'mainnet_STAGING': 'staging.' + }[NEAR_WALLET_ENV]; + + return `${https ? 'https://' : ''}${prefix || ''}mynearwallet.com`; +}; + +export default isWhitelabel() ? getMyNearWalletUrl : getNearOrgWalletUrl; diff --git a/packages/frontend/src/utils/wallet.js b/packages/frontend/src/utils/wallet.js index b38d415957..b5c687b0cf 100644 --- a/packages/frontend/src/utils/wallet.js +++ b/packages/frontend/src/utils/wallet.js @@ -286,26 +286,26 @@ class Wallet { const keyInfoView = allKeys.find(({public_key}) => public_key === publicKeyString); if (keyInfoView) { - if (this.isFullAccessKey(keyInfoView)) return this.KEY_TYPES.FAK; - if (this.isLedgerKey(accountId, keyInfoView)) return this.KEY_TYPES.LEDGER; - if (this.isMultisigKey(accountId, keyInfoView)) return this.KEY_TYPES.MULTISIG; + if (this.isFullAccessKeyInfoView(keyInfoView)) return this.KEY_TYPES.FAK; + if (this.isLedgerKeyInfoView(accountId, keyInfoView)) return this.KEY_TYPES.LEDGER; + if (this.isMultisigKeyInfoView(accountId, keyInfoView)) return this.KEY_TYPES.MULTISIG; return this.KEY_TYPES.OTHER; } throw new Error('No matching key pair for public key'); } - isFullAccessKey(keyInfoView) { + isFullAccessKeyInfoView(keyInfoView) { return keyInfoView?.access_key?.permission === 'FullAccess'; } - isLedgerKey(accountId, keyInfoView) { + isLedgerKeyInfoView(accountId, keyInfoView) { const receiver_id = keyInfoView?.access_key?.permission?.FunctionCall?.receiver_id; const method_names = keyInfoView?.access_key?.permission?.FunctionCall?.method_names; return receiver_id === accountId && isEqual(method_names, ['__wallet__metadata']); } - isMultisigKey(accountId, keyInfoView) { + isMultisigKeyInfoView(accountId, keyInfoView) { const receiver_id = keyInfoView?.access_key?.permission?.FunctionCall?.receiver_id; const method_names = keyInfoView?.access_key?.permission?.FunctionCall?.method_names; return receiver_id === accountId && isEqual(method_names, ['add_request', 'add_request_and_confirm', 'delete_request', 'confirm']); diff --git a/yarn.lock b/yarn.lock index 6f4bc130fc..8895bc521a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -62,7 +62,7 @@ semver "^6.3.0" source-map "^0.5.0" -"@babel/core@^7.18.2": +"@babel/core@^7.14.3": version "7.18.2" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.2.tgz#87b2fcd7cce9becaa7f5acebdc4f09f3dd19d876" integrity sha512-A8pri1YJiC5UnkdrWcmfZTJTV85b4UXTAfImGmCfYmax4TR9Cw8sDS0MOk++Gp2mE/BefVJ5nwy5yzqNJbP/DQ== @@ -2278,6 +2278,15 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" +"@near-wallet/feature-flags@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@near-wallet/feature-flags/-/feature-flags-0.0.4.tgz#281a15ad66f9ba0c66bb63f81ea0b21a228071a9" + integrity sha512-TUu1io4U1CzvV5gGzLMGwQLFuA7Hj/RmRD1y0Ys1MdCcyErBced/cLIymQJtC5zZTYW6JlbJTQ3EmZb+HUaG4g== + dependencies: + fs-extra "^10.0.0" + ini "^2.0.0" + inquirer "^8.2.0" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -7232,6 +7241,11 @@ ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +ini@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + init-package-json@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-2.0.3.tgz#c8ae4f2a4ad353bcbc089e5ffe98a8f1a314e8fd" From f2ac9d4a398ea71590274cb73a0db1d2f0ed6ea3 Mon Sep 17 00:00:00 2001 From: esaminu Date: Fri, 10 Jun 2022 00:41:47 +0400 Subject: [PATCH 09/41] chore: update testEnvironment to jsdom --- packages/frontend/jest.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/frontend/jest.config.js b/packages/frontend/jest.config.js index 4f9ccdfef6..a94316bbb7 100644 --- a/packages/frontend/jest.config.js +++ b/packages/frontend/jest.config.js @@ -1,4 +1,5 @@ module.exports = { setupFilesAfterEnv: ['./jest.setup.js'], setupFiles: ['dotenv/config'], + testEnvironment: 'jsdom' }; From 10b235be4970d3087f6e59561cedf4dc6a904d5e Mon Sep 17 00:00:00 2001 From: esaminu Date: Fri, 10 Jun 2022 01:05:25 +0400 Subject: [PATCH 10/41] chore: add jest-svg-transformer --- packages/frontend/jest.config.js | 5 ++++- packages/frontend/package.json | 1 + yarn.lock | 19 +++++-------------- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/packages/frontend/jest.config.js b/packages/frontend/jest.config.js index a94316bbb7..e0a5699bb0 100644 --- a/packages/frontend/jest.config.js +++ b/packages/frontend/jest.config.js @@ -1,5 +1,8 @@ module.exports = { setupFilesAfterEnv: ['./jest.setup.js'], setupFiles: ['dotenv/config'], - testEnvironment: 'jsdom' + testEnvironment: 'jsdom', + moduleNameMapper: { + "^.+\\.svg$": "jest-svg-transformer" + } }; diff --git a/packages/frontend/package.json b/packages/frontend/package.json index ed89c2027b..486ca9871b 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -105,6 +105,7 @@ "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-react": "^7.23.2", "jest": "^27.0.1", + "jest-svg-transformer": "^1.0.0", "parcel-bundler": "^1.12.4", "parcel-plugin-bundle-visualiser": "^1.2.0", "react-is": "^17.0.2" diff --git a/yarn.lock b/yarn.lock index 8895bc521a..92852d03fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2278,15 +2278,6 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" -"@near-wallet/feature-flags@^0.0.4": - version "0.0.4" - resolved "https://registry.yarnpkg.com/@near-wallet/feature-flags/-/feature-flags-0.0.4.tgz#281a15ad66f9ba0c66bb63f81ea0b21a228071a9" - integrity sha512-TUu1io4U1CzvV5gGzLMGwQLFuA7Hj/RmRD1y0Ys1MdCcyErBced/cLIymQJtC5zZTYW6JlbJTQ3EmZb+HUaG4g== - dependencies: - fs-extra "^10.0.0" - ini "^2.0.0" - inquirer "^8.2.0" - "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -7241,11 +7232,6 @@ ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== -ini@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" - integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== - init-package-json@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-2.0.3.tgz#c8ae4f2a4ad353bcbc089e5ffe98a8f1a314e8fd" @@ -8220,6 +8206,11 @@ jest-snapshot@^27.4.5: pretty-format "^27.4.2" semver "^7.3.2" +jest-svg-transformer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/jest-svg-transformer/-/jest-svg-transformer-1.0.0.tgz#e38884ca4cd8b2295cdfa2a0b24667920c3a8a6d" + integrity sha512-+kD21VthJFHIbI3DZRz+jo4sBOSR1qWEMXhVC28owRMqC5nA+zEiJrHOlj+EqQIztYMouRc1dIjE8SJfFPJUXA== + jest-util@^27.4.2: version "27.4.2" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.4.2.tgz#ed95b05b1adfd761e2cda47e0144c6a58e05a621" From e3bf2bba53f722ed30e43925d2d898d2f77720b3 Mon Sep 17 00:00:00 2001 From: esaminu Date: Fri, 10 Jun 2022 01:12:05 +0400 Subject: [PATCH 11/41] fix: lint error --- packages/frontend/jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/jest.config.js b/packages/frontend/jest.config.js index e0a5699bb0..5cfe5e6914 100644 --- a/packages/frontend/jest.config.js +++ b/packages/frontend/jest.config.js @@ -3,6 +3,6 @@ module.exports = { setupFiles: ['dotenv/config'], testEnvironment: 'jsdom', moduleNameMapper: { - "^.+\\.svg$": "jest-svg-transformer" + '^.+\\.svg$': 'jest-svg-transformer' } }; From c800505fe41b7189818cbede1d623e1e7abd40a8 Mon Sep 17 00:00:00 2001 From: esaminu Date: Fri, 10 Jun 2022 01:26:42 +0400 Subject: [PATCH 12/41] chore: update yarn lock --- yarn.lock | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/yarn.lock b/yarn.lock index 92852d03fb..469ef66033 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2278,6 +2278,15 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" +"@near-wallet/feature-flags@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@near-wallet/feature-flags/-/feature-flags-0.0.4.tgz#281a15ad66f9ba0c66bb63f81ea0b21a228071a9" + integrity sha512-TUu1io4U1CzvV5gGzLMGwQLFuA7Hj/RmRD1y0Ys1MdCcyErBced/cLIymQJtC5zZTYW6JlbJTQ3EmZb+HUaG4g== + dependencies: + fs-extra "^10.0.0" + ini "^2.0.0" + inquirer "^8.2.0" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -7232,6 +7241,11 @@ ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +ini@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + init-package-json@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-2.0.3.tgz#c8ae4f2a4ad353bcbc089e5ffe98a8f1a314e8fd" From 636f425c591c0f42c1df71a282ee228ece83e596 Mon Sep 17 00:00:00 2001 From: esaminu Date: Fri, 10 Jun 2022 23:00:00 +0400 Subject: [PATCH 13/41] fix: require cycle --- packages/frontend/src/components/common/balance/helpers.js | 7 ------- packages/frontend/src/redux/slices/sign/index.js | 5 +++++ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/frontend/src/components/common/balance/helpers.js b/packages/frontend/src/components/common/balance/helpers.js index 0d96695953..21a5cdcddf 100644 --- a/packages/frontend/src/components/common/balance/helpers.js +++ b/packages/frontend/src/components/common/balance/helpers.js @@ -1,8 +1,6 @@ import BN from 'bn.js'; -import cloneDeep from 'lodash.clonedeep'; import { utils } from 'near-api-js'; -import { calculateGasLimit, increaseGasForFirstTransaction } from '../../../redux/slices/sign'; import { formatTokenAmount } from '../../../utils/amounts'; const NEAR_FRACTIONAL_DIGITS = 5; @@ -70,8 +68,3 @@ export const getTotalBalanceFromFungibleTokensListUSD = (fungibleTokensList) => } return Number(totalBalanceUSD.toFixed(2)); }; - -export const getEstimatedFees = (transactionsList) => { - const tx = increaseGasForFirstTransaction({ transactions: cloneDeep(transactionsList)}); - return new BN(calculateGasLimit(tx.flatMap((t) => t.actions))); -}; diff --git a/packages/frontend/src/redux/slices/sign/index.js b/packages/frontend/src/redux/slices/sign/index.js index b2a6121f8f..17ca6d0504 100644 --- a/packages/frontend/src/redux/slices/sign/index.js +++ b/packages/frontend/src/redux/slices/sign/index.js @@ -133,6 +133,11 @@ export const getFirstTransactionWithFunctionCallAction = ({ transactions }) => { }); }; +export const getEstimatedFees = (transactionsList) => { + const tx = increaseGasForFirstTransaction({ transactions: cloneDeep(transactionsList)}); + return new BN(calculateGasLimit(tx.flatMap((t) => t.actions))); +}; + export const increaseGasForFirstTransaction = ({ transactions }) => { const transaction = getFirstTransactionWithFunctionCallAction({ transactions }); From e6bf66e4445e443768e2a75c189da4bfb8730063 Mon Sep 17 00:00:00 2001 From: esaminu Date: Fri, 10 Jun 2022 23:14:37 +0400 Subject: [PATCH 14/41] fix: lint error --- .../frontend/src/components/accounts/BatchImportAccounts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/components/accounts/BatchImportAccounts.js b/packages/frontend/src/components/accounts/BatchImportAccounts.js index 4d86070cb8..82d815f619 100644 --- a/packages/frontend/src/components/accounts/BatchImportAccounts.js +++ b/packages/frontend/src/components/accounts/BatchImportAccounts.js @@ -10,9 +10,9 @@ import ImportArrow from '../../images/import-arrow.svg'; import refreshAccountOwner from '../../redux/sharedThunks/refreshAccountOwner'; import { selectAccountUrlReferrer } from '../../redux/slices/account'; import { selectAvailableAccounts, selectAvailableAccountsIsLoading } from '../../redux/slices/availableAccounts'; +import { getEstimatedFees } from '../../redux/slices/sign'; import getWalletURL from '../../utils/getWalletURL'; import { wallet } from '../../utils/wallet'; -import { getEstimatedFees } from '../common/balance/helpers'; import FormButton from '../common/FormButton'; import FormButtonGroup from '../common/FormButtonGroup'; import Modal from '../common/modal/Modal'; From 2637a565b7c9e9465917f131b12551087b3f655c Mon Sep 17 00:00:00 2001 From: esaminu Date: Fri, 10 Jun 2022 23:54:15 +0400 Subject: [PATCH 15/41] refactor: move reducer out --- .../accounts/BatchImportAccounts.js | 93 ++----------------- .../accounts/BatchImportAccountsReducer.js | 82 ++++++++++++++++ 2 files changed, 89 insertions(+), 86 deletions(-) create mode 100644 packages/frontend/src/components/accounts/BatchImportAccountsReducer.js diff --git a/packages/frontend/src/components/accounts/BatchImportAccounts.js b/packages/frontend/src/components/accounts/BatchImportAccounts.js index 82d815f619..e1afea1c34 100644 --- a/packages/frontend/src/components/accounts/BatchImportAccounts.js +++ b/packages/frontend/src/components/accounts/BatchImportAccounts.js @@ -21,6 +21,7 @@ import ConnectWithApplication from '../login/v2/ConnectWithApplication'; import SignTransaction from '../sign/v2/SignTransaction'; import SignTransactionDetails from '../sign/v2/SignTransactionDetails'; import AccountListImport from './AccountListImport'; +import reducer, { ACTIONS } from './BatchImportAccountsReducer'; import BatchImportAccountsSuccessScreen from './BatchImportAccountsSuccessScreen'; const CustomContainer = styled.div` @@ -99,92 +100,13 @@ const ModalContainer = styled(Container)` } `; -const ACTIONS = { - BEGIN_IMPORT: 'BEGIN_IMPORT', - SET_CURRENT_DONE: 'SET_CURRENT_DONE', - SET_CURRENT_FAILED: 'SET_CURRENT_FAILED', - CONFIRM_URL: 'CONFIRM_URL', - REMOVE_ACCOUNTS: 'REMOVE_ACCOUNTS' -}; - -const IMPORT_STATUS = { +export const IMPORT_STATUS = { PENDING: 'pending', SUCCESS: 'success', UP_NEXT: 'waiting', FAILED: 'error' }; -const reducer = (state, action) => { - switch (action.type) { - case ACTIONS.REMOVE_ACCOUNTS: - return state.accounts.every(({ status }) => status === null) - ? { - ...state, - accounts: state.accounts.filter( - (account) => - !action.accounts.some( - (accountId) => account.accountId === accountId - ) - ), - } - : state; - case ACTIONS.BEGIN_IMPORT: - return state.accounts.every(({ status }) => status === null) - ? { - accounts: state.accounts.map((acc, idx) => ({ - ...acc, - status: - idx === 0 - ? IMPORT_STATUS.PENDING - : IMPORT_STATUS.UP_NEXT, - })), - urlConfirmed: false, - } - : state; - case ACTIONS.SET_CURRENT_DONE: { - let currentIndex = state.accounts.findIndex( - (account) => account.status === IMPORT_STATUS.PENDING - ); - return { - accounts: state.accounts.map((acc, idx) => ({ - ...acc, - status: - idx === currentIndex - ? IMPORT_STATUS.SUCCESS - : idx === currentIndex + 1 - ? IMPORT_STATUS.PENDING - : state.accounts[idx].status, - })), - urlConfirmed: true, - }; - } - case ACTIONS.SET_CURRENT_FAILED: { - let currentIndex = state.accounts.findIndex( - (account) => account.status === IMPORT_STATUS.PENDING - ); - return { - accounts: state.accounts.map((acc, idx) => ({ - ...acc, - status: - idx === currentIndex - ? IMPORT_STATUS.FAILED - : idx === currentIndex + 1 - ? IMPORT_STATUS.PENDING - : state.accounts[idx].status, - })), - urlConfirmed: true, - }; - } - case ACTIONS.CONFIRM_URL: - return { - ...state, - urlConfirmed: true, - }; - default: - return state; - } -}; - const BatchImportAccounts = ({ accountIdToKeyMap, onCancel }) => { const availableAccountsIsLoading = useSelector(selectAvailableAccountsIsLoading); const availableAccounts = useSelector(selectAvailableAccounts); @@ -199,7 +121,7 @@ const BatchImportAccounts = ({ accountIdToKeyMap, onCancel }) => { })), }); const currentAccount = useMemo(() => state.accounts.find((account) => account.status === IMPORT_STATUS.PENDING), [state.accounts]); - const accountsApproved = useMemo(() => state.accounts.reduce((acc, curr) => curr.status === IMPORT_STATUS.SUCCESS ? acc + 1 : acc,0), [state.accounts]); + const accountsApproved = useMemo(() => state.accounts.filter((account) => account.status === IMPORT_STATUS.SUCCESS), [state.accounts]); const completed = useMemo(() => state.accounts.every((account) => account.status === IMPORT_STATUS.SUCCESS || account.status === IMPORT_STATUS.FAILED), [state.accounts]); const showSuccessScreen = useMemo(() => completed && state.accounts.some((account) => account.status === IMPORT_STATUS.SUCCESS), [completed, state.accounts]); @@ -211,9 +133,7 @@ const BatchImportAccounts = ({ accountIdToKeyMap, onCancel }) => { return showSuccessScreen ? ( status === IMPORT_STATUS.SUCCESS - )} + accounts={accountsApproved} /> ) : ( <> @@ -230,7 +150,7 @@ const BatchImportAccounts = ({ accountIdToKeyMap, onCancel }) => {
- {accountsApproved}/{state.accounts.length}{' '} + {accountsApproved.length}/{state.accounts.length}{' '}
@@ -381,7 +301,8 @@ const AccountImportModal = ({ account, onSuccess, onFail }) => { account.ledgerHdPath ) .then(() => dispatch(refreshAccountOwner({}))) - .then(onSuccess).catch(() => { + .then(onSuccess) + .catch(() => { setError(true); setAddingKey(false); }); diff --git a/packages/frontend/src/components/accounts/BatchImportAccountsReducer.js b/packages/frontend/src/components/accounts/BatchImportAccountsReducer.js new file mode 100644 index 0000000000..e412ffaf23 --- /dev/null +++ b/packages/frontend/src/components/accounts/BatchImportAccountsReducer.js @@ -0,0 +1,82 @@ +import { IMPORT_STATUS } from "./BatchImportAccounts"; + +export const ACTIONS = { + BEGIN_IMPORT: 'BEGIN_IMPORT', + SET_CURRENT_DONE: 'SET_CURRENT_DONE', + SET_CURRENT_FAILED: 'SET_CURRENT_FAILED', + CONFIRM_URL: 'CONFIRM_URL', + REMOVE_ACCOUNTS: 'REMOVE_ACCOUNTS' +}; + +const reducer = (state, action) => { + switch (action.type) { + case ACTIONS.REMOVE_ACCOUNTS: { + if (!state.accounts.every(({ status }) => status === null)) { + return state; + } + + return { + ...state, + accounts: state.accounts.filter( + (account) => + !action.accounts.some( + (accountId) => account.accountId === accountId + ) + ) + }; + } + case ACTIONS.BEGIN_IMPORT: { + if(!state.accounts.every(({ status }) => status === null)) { + return state; + } + + const [firstAccount, ...remainingAccounts] = state.accounts; + return { + accounts: [ + { ...firstAccount, status: IMPORT_STATUS.PENDING }, + ...remainingAccounts.map((account) => ({ ...account, status: IMPORT_STATUS.UP_NEXT })), + ], + urlConfirmed: false, + } + } + case ACTIONS.SET_CURRENT_DONE: { + const currentIndex = state.accounts.findIndex( + (account) => account.status === IMPORT_STATUS.PENDING + ); + return { + accounts: state.accounts.map((account, idx) => ({ + ...account, + status: { + [currentIndex]: IMPORT_STATUS.SUCCESS, + [currentIndex + 1]: IMPORT_STATUS.PENDING + }[idx] || account.status + })), + urlConfirmed: true, + }; + } + case ACTIONS.SET_CURRENT_FAILED: { + const currentIndex = state.accounts.findIndex( + (account) => account.status === IMPORT_STATUS.PENDING + ); + return { + accounts: state.accounts.map((account, idx) => ({ + ...account, + status: { + [currentIndex]: IMPORT_STATUS.FAILED, + [currentIndex + 1]: IMPORT_STATUS.PENDING + }[idx] || account.status + })), + urlConfirmed: true, + }; + } + case ACTIONS.CONFIRM_URL: + return { + ...state, + urlConfirmed: true, + }; + default: + return state; + } +}; + +export default reducer \ No newline at end of file From 123bf7c1ad398ce4772457bcf81d7b4160e7ab1f Mon Sep 17 00:00:00 2001 From: esaminu Date: Fri, 10 Jun 2022 23:55:29 +0400 Subject: [PATCH 16/41] fix: lint errors --- .../src/components/accounts/BatchImportAccountsReducer.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/frontend/src/components/accounts/BatchImportAccountsReducer.js b/packages/frontend/src/components/accounts/BatchImportAccountsReducer.js index e412ffaf23..97e0323840 100644 --- a/packages/frontend/src/components/accounts/BatchImportAccountsReducer.js +++ b/packages/frontend/src/components/accounts/BatchImportAccountsReducer.js @@ -1,4 +1,4 @@ -import { IMPORT_STATUS } from "./BatchImportAccounts"; +import { IMPORT_STATUS } from './BatchImportAccounts'; export const ACTIONS = { BEGIN_IMPORT: 'BEGIN_IMPORT', @@ -26,7 +26,7 @@ const reducer = (state, action) => { }; } case ACTIONS.BEGIN_IMPORT: { - if(!state.accounts.every(({ status }) => status === null)) { + if (!state.accounts.every(({ status }) => status === null)) { return state; } @@ -37,7 +37,7 @@ const reducer = (state, action) => { ...remainingAccounts.map((account) => ({ ...account, status: IMPORT_STATUS.UP_NEXT })), ], urlConfirmed: false, - } + }; } case ACTIONS.SET_CURRENT_DONE: { const currentIndex = state.accounts.findIndex( @@ -79,4 +79,4 @@ const reducer = (state, action) => { } }; -export default reducer \ No newline at end of file +export default reducer; From 28a04fb34ebaacb0114eef6bd7e6bda5eae24f48 Mon Sep 17 00:00:00 2001 From: esaminu Date: Sat, 11 Jun 2022 00:17:53 +0400 Subject: [PATCH 17/41] refactor: make KEY_TYPES static --- .../accounts/BatchImportAccounts.js | 12 +++++------ .../src/components/common/modal/Modal.js | 4 +--- packages/frontend/src/utils/getWalletURL.js | 9 +++++---- packages/frontend/src/utils/wallet.js | 20 +++++++++---------- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/packages/frontend/src/components/accounts/BatchImportAccounts.js b/packages/frontend/src/components/accounts/BatchImportAccounts.js index e1afea1c34..139d1a47cc 100644 --- a/packages/frontend/src/components/accounts/BatchImportAccounts.js +++ b/packages/frontend/src/components/accounts/BatchImportAccounts.js @@ -12,7 +12,7 @@ import { selectAccountUrlReferrer } from '../../redux/slices/account'; import { selectAvailableAccounts, selectAvailableAccountsIsLoading } from '../../redux/slices/availableAccounts'; import { getEstimatedFees } from '../../redux/slices/sign'; import getWalletURL from '../../utils/getWalletURL'; -import { wallet } from '../../utils/wallet'; +import WalletClass, { wallet } from '../../utils/wallet'; import FormButton from '../common/FormButton'; import FormButtonGroup from '../common/FormButtonGroup'; import Modal from '../common/modal/Modal'; @@ -261,7 +261,7 @@ const AccountImportModal = ({ account, onSuccess, onFail }) => { keyPair = KeyPair.fromString(account.key).getPublicKey().toString(); } catch (error) { setError(true); - setKeyType(wallet.KEY_TYPES.OTHER); + setKeyType(WalletClass.KEY_TYPES.OTHER); } if (keyPair) { @@ -272,7 +272,7 @@ const AccountImportModal = ({ account, onSuccess, onFail }) => { ) .then(setKeyType).catch(() => { setError(true); - setKeyType(wallet.KEY_TYPES.OTHER); + setKeyType(WalletClass.KEY_TYPES.OTHER); }); } @@ -333,10 +333,10 @@ const AccountImportModal = ({ account, onSuccess, onFail }) => { - {keyType === wallet.KEY_TYPES.FAK ? ( + {keyType === WalletClass.KEY_TYPES.FAK ? ( setShowTxDetails(true)}> @@ -349,7 +349,7 @@ const AccountImportModal = ({ account, onSuccess, onFail }) => { diff --git a/packages/frontend/src/components/common/modal/Modal.js b/packages/frontend/src/components/common/modal/Modal.js index 71ec40b0a8..fcfe66042c 100644 --- a/packages/frontend/src/components/common/modal/Modal.js +++ b/packages/frontend/src/components/common/modal/Modal.js @@ -46,13 +46,11 @@ function Modal({ },[]); useEffect(() => { + setFadeType('out'); if (isOpen) { - setFadeType('out'); setTimeout(() => { setFadeType('in'); }, 500); - } else { - setFadeType('out'); } }, [isOpen]); diff --git a/packages/frontend/src/utils/getWalletURL.js b/packages/frontend/src/utils/getWalletURL.js index 2fa44cee33..2caa712136 100644 --- a/packages/frontend/src/utils/getWalletURL.js +++ b/packages/frontend/src/utils/getWalletURL.js @@ -1,5 +1,6 @@ import { IS_MAINNET, SHOW_PRERELEASE_WARNING, NEAR_WALLET_ENV } from '../config'; import { isWhitelabel } from '../config/whitelabel'; +import Environments from '../../../../features/environments.json' const getNearOrgWalletUrl = (https = true) => { let networkName = ''; @@ -15,10 +16,10 @@ const getNearOrgWalletUrl = (https = true) => { const getMyNearWalletUrl = (https = true) => { const prefix = { - 'testnet': 'testnet.', - 'mainnet': 'app.', - 'development': 'testnet.', - 'mainnet_STAGING': 'staging.' + [Environments.TESTNET]: 'testnet.', + [Environments.MAINNET]: 'app.', + [Environments.DEVELOPMENT]: 'testnet.', + [Environments.MAINNET_STAGING]: 'staging.' }[NEAR_WALLET_ENV]; return `${https ? 'https://' : ''}${prefix || ''}mynearwallet.com`; diff --git a/packages/frontend/src/utils/wallet.js b/packages/frontend/src/utils/wallet.js index b5c687b0cf..7d24926d31 100644 --- a/packages/frontend/src/utils/wallet.js +++ b/packages/frontend/src/utils/wallet.js @@ -107,7 +107,7 @@ export async function getKeyMeta(publicKey) { } } -class Wallet { +export default class Wallet { constructor() { this.keyStore = new nearApiJs.keyStores.BrowserLocalStorageKeyStore(window.localStorage, 'nearlib:keystore:'); this.inMemorySigner = new nearApiJs.InMemorySigner(this.keyStore); @@ -150,7 +150,7 @@ class Wallet { this.accountId = localStorage.getItem(KEY_ACTIVE_ACCOUNT_ID) || ''; } - KEY_TYPES = { + static KEY_TYPES = { LEDGER: 'ledger', MULTISIG: 'multisig', FAK: 'fullAccessKey', @@ -286,10 +286,10 @@ class Wallet { const keyInfoView = allKeys.find(({public_key}) => public_key === publicKeyString); if (keyInfoView) { - if (this.isFullAccessKeyInfoView(keyInfoView)) return this.KEY_TYPES.FAK; - if (this.isLedgerKeyInfoView(accountId, keyInfoView)) return this.KEY_TYPES.LEDGER; - if (this.isMultisigKeyInfoView(accountId, keyInfoView)) return this.KEY_TYPES.MULTISIG; - return this.KEY_TYPES.OTHER; + if (this.isFullAccessKeyInfoView(keyInfoView)) return Wallet.KEY_TYPES.FAK; + if (this.isLedgerKeyInfoView(accountId, keyInfoView)) return Wallet.KEY_TYPES.LEDGER; + if (this.isMultisigKeyInfoView(accountId, keyInfoView)) return Wallet.KEY_TYPES.MULTISIG; + return Wallet.KEY_TYPES.OTHER; } throw new Error('No matching key pair for public key'); @@ -318,7 +318,7 @@ class Wallet { ); switch (keyType) { - case this.KEY_TYPES.FAK: { + case Wallet.KEY_TYPES.FAK: { const keyStore = new nearApiJs.keyStores.InMemoryKeyStore(); await keyStore.setKey(NETWORK_ID, accountId, keyPair); const newKeyPair = nearApiJs.KeyPair.fromRandom('ed25519'); @@ -342,11 +342,11 @@ class Wallet { return this.save(); }); } - case this.KEY_TYPES.MULTISIG: - case this.KEY_TYPES.LEDGER: + case Wallet.KEY_TYPES.MULTISIG: + case Wallet.KEY_TYPES.LEDGER: return this.saveAccount(accountId, keyPair) .then(() => { - if (keyType === this.KEY_TYPES.LEDGER) { + if (keyType === Wallet.KEY_TYPES.LEDGER) { if (ledgerHdPath) { setLedgerHdPath({accountId, path: ledgerHdPath}); } From 7a065c21ad4979434e9316f1674ba4257667b6cb Mon Sep 17 00:00:00 2001 From: esaminu Date: Sat, 11 Jun 2022 00:18:29 +0400 Subject: [PATCH 18/41] fix: lint errors --- packages/frontend/src/utils/getWalletURL.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/utils/getWalletURL.js b/packages/frontend/src/utils/getWalletURL.js index 2caa712136..cf0ecfcb9c 100644 --- a/packages/frontend/src/utils/getWalletURL.js +++ b/packages/frontend/src/utils/getWalletURL.js @@ -1,6 +1,6 @@ +import Environments from '../../../../features/environments.json'; import { IS_MAINNET, SHOW_PRERELEASE_WARNING, NEAR_WALLET_ENV } from '../config'; import { isWhitelabel } from '../config/whitelabel'; -import Environments from '../../../../features/environments.json' const getNearOrgWalletUrl = (https = true) => { let networkName = ''; From 0427ff7e80ba6e48a871fb8e6473ffddb9dd12af Mon Sep 17 00:00:00 2001 From: esaminu Date: Sat, 11 Jun 2022 00:37:12 +0400 Subject: [PATCH 19/41] refactor: promise chains to await --- packages/frontend/src/utils/wallet.js | 52 +++++++++++++-------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/frontend/src/utils/wallet.js b/packages/frontend/src/utils/wallet.js index 7d24926d31..a2a97112eb 100644 --- a/packages/frontend/src/utils/wallet.js +++ b/packages/frontend/src/utils/wallet.js @@ -322,7 +322,7 @@ export default class Wallet { const keyStore = new nearApiJs.keyStores.InMemoryKeyStore(); await keyStore.setKey(NETWORK_ID, accountId, keyPair); const newKeyPair = nearApiJs.KeyPair.fromRandom('ed25519'); - return new nearApiJs.Account( + const account = new nearApiJs.Account( nearApiJs.Connection.fromConfig({ networkId: NETWORK_ID, provider: { @@ -332,33 +332,33 @@ export default class Wallet { signer: new nearApiJs.InMemorySigner(keyStore), }), accountId - ) - .addKey(newKeyPair.getPublicKey()) - .then(() => this.saveAccount(accountId, newKeyPair)) - .then(() => { - if (!this.accountId) { - return this.makeAccountActive(accountId); - } - return this.save(); - }); + ); + + await account.addKey(newKeyPair.getPublicKey()); + await this.saveAccount(accountId, newKeyPair); + + if (!this.accountId) { + return this.makeAccountActive(accountId); + } + return this.save(); } case Wallet.KEY_TYPES.MULTISIG: - case Wallet.KEY_TYPES.LEDGER: - return this.saveAccount(accountId, keyPair) - .then(() => { - if (keyType === Wallet.KEY_TYPES.LEDGER) { - if (ledgerHdPath) { - setLedgerHdPath({accountId, path: ledgerHdPath}); - } - return this.getLedgerPublicKey(ledgerHdPath).then((publicKey) => setKeyMeta(publicKey.toString(), {type: 'ledger'})); - } - }) - .then(() => { - if (!this.accountId) { - return this.makeAccountActive(accountId); - } - return this.save(); - }); + case Wallet.KEY_TYPES.LEDGER: { + await this.saveAccount(accountId, keyPair); + + if (keyType === Wallet.KEY_TYPES.LEDGER) { + if (ledgerHdPath) { + setLedgerHdPath({accountId, path: ledgerHdPath}); + } + + await this.getLedgerPublicKey(ledgerHdPath).then((publicKey) => setKeyMeta(publicKey.toString(), {type: 'ledger'})); + } + + if (!this.accountId) { + return this.makeAccountActive(accountId); + } + return this.save(); + } default: throw new Error('Unable to add unrecognized key to wallet key store'); } From f1ac0e3421bb44ed21b54dcb7665936e0a2582ab Mon Sep 17 00:00:00 2001 From: esaminu Date: Sat, 11 Jun 2022 00:53:20 +0400 Subject: [PATCH 20/41] refactor: remove ternary --- .../frontend/src/components/accounts/BatchImportAccounts.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/components/accounts/BatchImportAccounts.js b/packages/frontend/src/components/accounts/BatchImportAccounts.js index 139d1a47cc..f09b4751ae 100644 --- a/packages/frontend/src/components/accounts/BatchImportAccounts.js +++ b/packages/frontend/src/components/accounts/BatchImportAccounts.js @@ -336,11 +336,11 @@ const AccountImportModal = ({ account, onSuccess, onFail }) => { estimatedFees={keyType === WalletClass.KEY_TYPES.FAK ? estimatedAddFAKTransactionFees : '0'} fromLabelId="batchImportAccounts.confirmImportModal.accountToImport" /> - {keyType === WalletClass.KEY_TYPES.FAK ? ( + {keyType === WalletClass.KEY_TYPES.FAK && ( setShowTxDetails(true)}> - ) : null} + )} {error ?
: null} From e90a9c15e18ae5c09c37b78dbd5ec66ea8e7080e Mon Sep 17 00:00:00 2001 From: esaminu Date: Thu, 16 Jun 2022 00:48:03 +0400 Subject: [PATCH 21/41] refactor: move into folder and use immer reducer --- packages/frontend/package.json | 3 +- packages/frontend/src/components/Routing.js | 2 +- .../components/accounts/AccountListImport.js | 1 - .../accounts/BatchImportAccounts.js | 365 ------------------ .../accounts/BatchImportAccountsReducer.js | 82 ---- .../AccountImportModal.js | 154 ++++++++ .../BatchImportAccountsSuccessScreen.js | 10 +- .../batch_import_accounts/immerReducer.js | 64 +++ .../accounts/batch_import_accounts/index.js | 167 ++++++++ .../accounts/batch_import_accounts/styles.js | 60 +++ yarn.lock | 19 +- 11 files changed, 458 insertions(+), 469 deletions(-) delete mode 100644 packages/frontend/src/components/accounts/BatchImportAccounts.js delete mode 100644 packages/frontend/src/components/accounts/BatchImportAccountsReducer.js create mode 100644 packages/frontend/src/components/accounts/batch_import_accounts/AccountImportModal.js rename packages/frontend/src/components/accounts/{ => batch_import_accounts}/BatchImportAccountsSuccessScreen.js (83%) create mode 100644 packages/frontend/src/components/accounts/batch_import_accounts/immerReducer.js create mode 100644 packages/frontend/src/components/accounts/batch_import_accounts/index.js create mode 100644 packages/frontend/src/components/accounts/batch_import_accounts/styles.js diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 486ca9871b..30337d790e 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -60,7 +60,8 @@ "regenerator-runtime": "^0.13.5", "reselect": "^4.0.0", "styled-components": "^5.3.0", - "timeago.js": "^4.0.2" + "timeago.js": "^4.0.2", + "use-immer": "^0.7.0" }, "scripts": { "predeploy": "yarn build", diff --git a/packages/frontend/src/components/Routing.js b/packages/frontend/src/components/Routing.js index 3bd3ca678c..f3584d49a5 100644 --- a/packages/frontend/src/components/Routing.js +++ b/packages/frontend/src/components/Routing.js @@ -54,7 +54,7 @@ import { } from '../utils/wallet'; import AccessKeysWrapper from './access-keys/v2/AccessKeysWrapper'; import { AutoImportWrapper } from './accounts/auto_import/AutoImportWrapper'; -import BatchImportAccounts from './accounts/BatchImportAccounts'; +import BatchImportAccounts from './accounts/batch_import_accounts'; import { ExistingAccountWrapper } from './accounts/create/existing_account/ExistingAccountWrapper'; import { InitialDepositWrapper } from './accounts/create/initial_deposit/InitialDepositWrapper'; import { CreateAccountLanding } from './accounts/create/landing/CreateAccountLanding'; diff --git a/packages/frontend/src/components/accounts/AccountListImport.js b/packages/frontend/src/components/accounts/AccountListImport.js index e0cea84855..accf5086db 100644 --- a/packages/frontend/src/components/accounts/AccountListImport.js +++ b/packages/frontend/src/components/accounts/AccountListImport.js @@ -29,7 +29,6 @@ const UserIcon = styled.div` const AnimateList = styled.div` margin-top: 10px; -height: 180px; overflow: hidden; & > div:first-of-type { diff --git a/packages/frontend/src/components/accounts/BatchImportAccounts.js b/packages/frontend/src/components/accounts/BatchImportAccounts.js deleted file mode 100644 index f09b4751ae..0000000000 --- a/packages/frontend/src/components/accounts/BatchImportAccounts.js +++ /dev/null @@ -1,365 +0,0 @@ -import BN from 'bn.js'; -import { KeyPair, transactions } from 'near-api-js'; -import React, { useCallback, useEffect, useMemo, useReducer, useState } from 'react'; -import { Translate } from 'react-localize-redux'; -import { useDispatch, useSelector } from 'react-redux'; -import styled from 'styled-components'; - -import ShieldIcon from '../../images/icon-shield.svg'; -import ImportArrow from '../../images/import-arrow.svg'; -import refreshAccountOwner from '../../redux/sharedThunks/refreshAccountOwner'; -import { selectAccountUrlReferrer } from '../../redux/slices/account'; -import { selectAvailableAccounts, selectAvailableAccountsIsLoading } from '../../redux/slices/availableAccounts'; -import { getEstimatedFees } from '../../redux/slices/sign'; -import getWalletURL from '../../utils/getWalletURL'; -import WalletClass, { wallet } from '../../utils/wallet'; -import FormButton from '../common/FormButton'; -import FormButtonGroup from '../common/FormButtonGroup'; -import Modal from '../common/modal/Modal'; -import Container from '../common/styled/Container.css'; -import ConnectWithApplication from '../login/v2/ConnectWithApplication'; -import SignTransaction from '../sign/v2/SignTransaction'; -import SignTransactionDetails from '../sign/v2/SignTransactionDetails'; -import AccountListImport from './AccountListImport'; -import reducer, { ACTIONS } from './BatchImportAccountsReducer'; -import BatchImportAccountsSuccessScreen from './BatchImportAccountsSuccessScreen'; - -const CustomContainer = styled.div` - width: 100%; - margin-top: 40px; - - .buttons-bottom-buttons { - margin-top: 40px; - } - - .title { - text-align: left; - font-size: 12px; - } - - .screen-descripton { - margin-top: 40px; - margin-bottom: 56px; - } -`; - -const ModalContainer = styled(Container)` - display: flex; - flex-direction: column; - align-items: center; - padding: 24px; - - h3 { - text-align: center; - } - - .top-icon { - height: 60px; - width: 60px; - margin-bottom: 40px; - } - - .desc { - padding: 0 45px; - text-align: center; - margin-top: 24px; - p { - margin: 0; - } - } - - button { - align-self: stretch; - } - - .link { - margin-top: 16px !important; - font-weight: normal !important; - } - - .button-group { - align-self: stretch; - } - - .connect-with-application { - margin: 20px auto 30px auto; - } - - .transfer-amount { - width: 100%; - } - - .error-label { - margin-top: 16px; - color: #FC5B5B; - } - - .wallet-url { - color: #000; - font-weight: bold; - } -`; - -export const IMPORT_STATUS = { - PENDING: 'pending', - SUCCESS: 'success', - UP_NEXT: 'waiting', - FAILED: 'error' -}; - -const BatchImportAccounts = ({ accountIdToKeyMap, onCancel }) => { - const availableAccountsIsLoading = useSelector(selectAvailableAccountsIsLoading); - const availableAccounts = useSelector(selectAvailableAccounts); - const accountUrlReferrer = useSelector(selectAccountUrlReferrer); - - const [state, dispatch] = useReducer(reducer, { - accounts: Object.keys(accountIdToKeyMap).map((accountId) => ({ - accountId, - status: null, - key: accountIdToKeyMap[accountId].key, - ledgerHdPath: accountIdToKeyMap[accountId].ledgerHdPath - })), - }); - const currentAccount = useMemo(() => state.accounts.find((account) => account.status === IMPORT_STATUS.PENDING), [state.accounts]); - const accountsApproved = useMemo(() => state.accounts.filter((account) => account.status === IMPORT_STATUS.SUCCESS), [state.accounts]); - const completed = useMemo(() => state.accounts.every((account) => account.status === IMPORT_STATUS.SUCCESS || account.status === IMPORT_STATUS.FAILED), [state.accounts]); - const showSuccessScreen = useMemo(() => completed && state.accounts.some((account) => account.status === IMPORT_STATUS.SUCCESS), [completed, state.accounts]); - - useEffect(() => { - if (!currentAccount) { - dispatch({type: ACTIONS.REMOVE_ACCOUNTS, accounts: availableAccounts}); - } - },[availableAccounts]); - - return showSuccessScreen ? ( - - ) : ( - <> - - - ImportArrow -
-

- - {accountUrlReferrer || } -

-
-
- -
-
- {accountsApproved.length}/{state.accounts.length}{' '} - -
- -
- - - - - - dispatch({ type: ACTIONS.BEGIN_IMPORT }) - } - disabled={availableAccountsIsLoading || completed} - > - - - - - - {currentAccount ? ( - state.urlConfirmed ? ( - - dispatch({ type: ACTIONS.SET_CURRENT_DONE }) - } - onFail={() => - dispatch({ type: ACTIONS.SET_CURRENT_FAILED }) - } - /> - ) : ( - {}} - disableClose - > - - SHIELD -

- -

-
-

- -

-
-
- {getWalletURL()} -
- - dispatch({ type: ACTIONS.CONFIRM_URL }) - } - style={{ marginTop: 48 }} - > - - -
-
- ) - ) : null} - - ); -}; - -const AccountImportModal = ({ account, onSuccess, onFail }) => { - const [keyType, setKeyType] = useState(null); - const [accountBalance, setAccountBalance] = useState(null); - const [showTxDetails, setShowTxDetails] = useState(false); - const [addingKey, setAddingKey] = useState(false); - const [error, setError] = useState(false); - const accountUrlReferrer = useSelector(selectAccountUrlReferrer); - const addFAKTransaction = useMemo(() => { - let tx; - try { - tx = { - receiverId: account.accountId, - actions: [transactions.addKey(KeyPair.fromString(account.key), transactions.fullAccessKey())] - }; - } catch (error) { - tx = null; - } - return tx; - }, [account]); - const estimatedAddFAKTransactionFees = useMemo(() => addFAKTransaction ? getEstimatedFees([addFAKTransaction]) : new BN('0') ,[addFAKTransaction]); - const dispatch = useDispatch(); - - useEffect(() => { - setKeyType(null); - setAccountBalance(null); - setShowTxDetails(false); - setAddingKey(false); - setError(false); - - let keyPair; - - try { - keyPair = KeyPair.fromString(account.key).getPublicKey().toString(); - } catch (error) { - setError(true); - setKeyType(WalletClass.KEY_TYPES.OTHER); - } - - if (keyPair) { - wallet - .getPublicKeyType( - account.accountId, - keyPair - ) - .then(setKeyType).catch(() => { - setError(true); - setKeyType(WalletClass.KEY_TYPES.OTHER); - }); - } - - wallet - .getBalance(account.accountId) - .then(({ available }) => setAccountBalance(available)); - },[account]); - - const addKeyToWalletKeyStore = useCallback(() => { - setAddingKey(true); - setError(false); - let keyPair; - - try { - keyPair = KeyPair.fromString(account.key); - } catch (error) { - setError(true); - setAddingKey(false); - } - - if (keyPair) { - wallet - .addExistingAccountKeyToWalletKeyStore( - account.accountId, - keyPair, - account.ledgerHdPath - ) - .then(() => dispatch(refreshAccountOwner({}))) - .then(onSuccess) - .catch(() => { - setError(true); - setAddingKey(false); - }); - } - - }, [setAddingKey, setError, onSuccess, account]); - - return ( - {}} - disableClose - > - {showTxDetails ? ( - setShowTxDetails(false)} - transactions={[addFAKTransaction]} - signGasFee={estimatedAddFAKTransactionFees.toString()} - /> - ) : ( - -

- -

- - - {keyType === WalletClass.KEY_TYPES.FAK && ( - setShowTxDetails(true)}> - - - )} - {error ?
: null} - - - - - - - - -
- )} -
- ); -}; - -export default BatchImportAccounts; diff --git a/packages/frontend/src/components/accounts/BatchImportAccountsReducer.js b/packages/frontend/src/components/accounts/BatchImportAccountsReducer.js deleted file mode 100644 index 97e0323840..0000000000 --- a/packages/frontend/src/components/accounts/BatchImportAccountsReducer.js +++ /dev/null @@ -1,82 +0,0 @@ -import { IMPORT_STATUS } from './BatchImportAccounts'; - -export const ACTIONS = { - BEGIN_IMPORT: 'BEGIN_IMPORT', - SET_CURRENT_DONE: 'SET_CURRENT_DONE', - SET_CURRENT_FAILED: 'SET_CURRENT_FAILED', - CONFIRM_URL: 'CONFIRM_URL', - REMOVE_ACCOUNTS: 'REMOVE_ACCOUNTS' -}; - -const reducer = (state, action) => { - switch (action.type) { - case ACTIONS.REMOVE_ACCOUNTS: { - if (!state.accounts.every(({ status }) => status === null)) { - return state; - } - - return { - ...state, - accounts: state.accounts.filter( - (account) => - !action.accounts.some( - (accountId) => account.accountId === accountId - ) - ) - }; - } - case ACTIONS.BEGIN_IMPORT: { - if (!state.accounts.every(({ status }) => status === null)) { - return state; - } - - const [firstAccount, ...remainingAccounts] = state.accounts; - return { - accounts: [ - { ...firstAccount, status: IMPORT_STATUS.PENDING }, - ...remainingAccounts.map((account) => ({ ...account, status: IMPORT_STATUS.UP_NEXT })), - ], - urlConfirmed: false, - }; - } - case ACTIONS.SET_CURRENT_DONE: { - const currentIndex = state.accounts.findIndex( - (account) => account.status === IMPORT_STATUS.PENDING - ); - return { - accounts: state.accounts.map((account, idx) => ({ - ...account, - status: { - [currentIndex]: IMPORT_STATUS.SUCCESS, - [currentIndex + 1]: IMPORT_STATUS.PENDING - }[idx] || account.status - })), - urlConfirmed: true, - }; - } - case ACTIONS.SET_CURRENT_FAILED: { - const currentIndex = state.accounts.findIndex( - (account) => account.status === IMPORT_STATUS.PENDING - ); - return { - accounts: state.accounts.map((account, idx) => ({ - ...account, - status: { - [currentIndex]: IMPORT_STATUS.FAILED, - [currentIndex + 1]: IMPORT_STATUS.PENDING - }[idx] || account.status - })), - urlConfirmed: true, - }; - } - case ACTIONS.CONFIRM_URL: - return { - ...state, - urlConfirmed: true, - }; - default: - return state; - } -}; - -export default reducer; diff --git a/packages/frontend/src/components/accounts/batch_import_accounts/AccountImportModal.js b/packages/frontend/src/components/accounts/batch_import_accounts/AccountImportModal.js new file mode 100644 index 0000000000..9f092d1b4c --- /dev/null +++ b/packages/frontend/src/components/accounts/batch_import_accounts/AccountImportModal.js @@ -0,0 +1,154 @@ +import BN from 'bn.js'; +import { KeyPair, transactions } from 'near-api-js'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { Translate } from 'react-localize-redux'; +import { useDispatch, useSelector } from 'react-redux'; + +import refreshAccountOwner from '../../../redux/sharedThunks/refreshAccountOwner'; +import { selectAccountUrlReferrer } from '../../../redux/slices/account'; +import { getEstimatedFees } from '../../../redux/slices/sign'; +import WalletClass, { wallet } from '../../../utils/wallet'; +import FormButton from '../../common/FormButton'; +import FormButtonGroup from '../../common/FormButtonGroup'; +import Modal from '../../common/modal/Modal'; +import ConnectWithApplication from '../../login/v2/ConnectWithApplication'; +import SignTransaction from '../../sign/v2/SignTransaction'; +import SignTransactionDetails from '../../sign/v2/SignTransactionDetails'; +import { ModalContainer } from './styles'; + + +const AccountImportModal = ({ account, onSuccess, onFail }) => { + const [keyType, setKeyType] = useState(null); + const [accountBalance, setAccountBalance] = useState(null); + const [showTxDetails, setShowTxDetails] = useState(false); + const [addingKey, setAddingKey] = useState(false); + const [error, setError] = useState(false); + const accountUrlReferrer = useSelector(selectAccountUrlReferrer); + const addFAKTransaction = useMemo(() => { + try { + return { + receiverId: account.accountId, + actions: [transactions.addKey(KeyPair.fromString(account.key), transactions.fullAccessKey())] + }; + } catch (error) { + return null; + } + }, [account]); + const estimatedAddFAKTransactionFees = useMemo(() => addFAKTransaction ? getEstimatedFees([addFAKTransaction]) : new BN('0') ,[addFAKTransaction]); + const dispatch = useDispatch(); + + useEffect(() => { + setKeyType(null); + setAccountBalance(null); + setShowTxDetails(false); + setAddingKey(false); + setError(false); + + let keyPair; + + try { + keyPair = KeyPair.fromString(account.key).getPublicKey().toString(); + } catch (error) { + setError(true); + setKeyType(WalletClass.KEY_TYPES.OTHER); + } + + if (keyPair) { + wallet + .getPublicKeyType( + account.accountId, + keyPair + ) + .then(setKeyType).catch(() => { + setError(true); + setKeyType(WalletClass.KEY_TYPES.OTHER); + }); + } + + wallet + .getBalance(account.accountId) + .then(({ available }) => setAccountBalance(available)); + },[account]); + + const addKeyToWalletKeyStore = useCallback(() => { + setAddingKey(true); + setError(false); + let keyPair; + + try { + keyPair = KeyPair.fromString(account.key); + } catch (error) { + setError(true); + setAddingKey(false); + } + + if (keyPair) { + wallet + .addExistingAccountKeyToWalletKeyStore( + account.accountId, + keyPair, + account.ledgerHdPath + ) + .then(() => dispatch(refreshAccountOwner({}))) + .then(onSuccess) + .catch(() => { + setError(true); + setAddingKey(false); + }); + } + + }, [setAddingKey, setError, onSuccess, account]); + + return ( + {}} + disableClose + > + {showTxDetails ? ( + setShowTxDetails(false)} + transactions={[addFAKTransaction]} + signGasFee={estimatedAddFAKTransactionFees.toString()} + /> + ) : ( + +

+ +

+ + + {keyType === WalletClass.KEY_TYPES.FAK && ( + setShowTxDetails(true)}> + + + )} + {error ?
: null} + + + + + + + + +
+ )} +
+ ); + }; + + export default AccountImportModal; diff --git a/packages/frontend/src/components/accounts/BatchImportAccountsSuccessScreen.js b/packages/frontend/src/components/accounts/batch_import_accounts/BatchImportAccountsSuccessScreen.js similarity index 83% rename from packages/frontend/src/components/accounts/BatchImportAccountsSuccessScreen.js rename to packages/frontend/src/components/accounts/batch_import_accounts/BatchImportAccountsSuccessScreen.js index e5f498dfd2..8684be43c4 100644 --- a/packages/frontend/src/components/accounts/BatchImportAccountsSuccessScreen.js +++ b/packages/frontend/src/components/accounts/batch_import_accounts/BatchImportAccountsSuccessScreen.js @@ -3,11 +3,11 @@ import { Translate } from 'react-localize-redux'; import { useDispatch, useSelector } from 'react-redux'; import styled from 'styled-components'; -import { redirectTo, switchAccount } from '../../redux/actions/account'; -import { selectAccountUrlReferrer } from '../../redux/slices/account'; -import Container from '../common/styled/Container.css'; -import AvatarSuccessIcon from '../svg/AvatarSuccessIcon'; -import AccountListImport from './AccountListImport'; +import { redirectTo, switchAccount } from '../../../redux/actions/account'; +import { selectAccountUrlReferrer } from '../../../redux/slices/account'; +import Container from '../../common/styled/Container.css'; +import AvatarSuccessIcon from '../../svg/AvatarSuccessIcon'; +import AccountListImport from '../AccountListImport'; const CustomContainer = styled.div` width: 100%; diff --git a/packages/frontend/src/components/accounts/batch_import_accounts/immerReducer.js b/packages/frontend/src/components/accounts/batch_import_accounts/immerReducer.js new file mode 100644 index 0000000000..114bfae30a --- /dev/null +++ b/packages/frontend/src/components/accounts/batch_import_accounts/immerReducer.js @@ -0,0 +1,64 @@ +import { differenceBy } from 'lodash'; + +import { IMPORT_STATUS } from '.'; + +export const ACTIONS = { + BEGIN_IMPORT: 'BEGIN_IMPORT', + SET_CURRENT_DONE: 'SET_CURRENT_DONE', + SET_CURRENT_FAILED: 'SET_CURRENT_FAILED', + CONFIRM_URL: 'CONFIRM_URL', + REMOVE_ACCOUNTS: 'REMOVE_ACCOUNTS', +}; + +const immerReducer = (state, action) => { + switch (action.type) { + case ACTIONS.REMOVE_ACCOUNTS: { + if (state.accounts.every(({ status }) => status === null)) { + state.accounts = differenceBy( + state.accounts, + action.accounts, + (accountOrId) => accountOrId?.accountId || accountOrId + ); + } + + return; + } + case ACTIONS.BEGIN_IMPORT: { + if (state.accounts.every(({ status }) => status === null)) { + const [firstAccount, ...remainingAccounts] = state.accounts; + + firstAccount.status = IMPORT_STATUS.PENDING; + remainingAccounts.forEach( + (account) => (account.status = IMPORT_STATUS.UP_NEXT) + ); + state.urlConfirmed = false; + } + return; + } + case ACTIONS.SET_CURRENT_DONE: { + const currentIndex = state.accounts.findIndex( + (account) => account.status === IMPORT_STATUS.PENDING + ); + state.accounts[currentIndex].status = IMPORT_STATUS.SUCCESS; + if (state.accounts[currentIndex + 1]) { + state.accounts[currentIndex + 1].status = IMPORT_STATUS.PENDING; + } + return; + } + case ACTIONS.SET_CURRENT_FAILED: { + const currentIndex = state.accounts.findIndex( + (account) => account.status === IMPORT_STATUS.PENDING + ); + state.accounts[currentIndex].status = IMPORT_STATUS.FAILED; + if (state.accounts[currentIndex + 1]) { + state.accounts[currentIndex + 1].status = IMPORT_STATUS.PENDING; + } + return; + } + case ACTIONS.CONFIRM_URL: + state.urlConfirmed = true; + return; + } +}; + +export default immerReducer; diff --git a/packages/frontend/src/components/accounts/batch_import_accounts/index.js b/packages/frontend/src/components/accounts/batch_import_accounts/index.js new file mode 100644 index 0000000000..cf81a85de5 --- /dev/null +++ b/packages/frontend/src/components/accounts/batch_import_accounts/index.js @@ -0,0 +1,167 @@ +import React, { useEffect, useMemo } from 'react'; +import { Translate } from 'react-localize-redux'; +import { useSelector } from 'react-redux'; +import styled from 'styled-components'; +import { useImmerReducer } from 'use-immer'; + +import ShieldIcon from '../../../images/icon-shield.svg'; +import ImportArrow from '../../../images/import-arrow.svg'; +import { selectAccountUrlReferrer } from '../../../redux/slices/account'; +import { selectAvailableAccounts, selectAvailableAccountsIsLoading } from '../../../redux/slices/availableAccounts'; +import getWalletURL from '../../../utils/getWalletURL'; +import FormButton from '../../common/FormButton'; +import FormButtonGroup from '../../common/FormButtonGroup'; +import Modal from '../../common/modal/Modal'; +import Container from '../../common/styled/Container.css'; +import AccountListImport from '../AccountListImport'; +import AccountImportModal from './AccountImportModal'; +import BatchImportAccountsSuccessScreen from './BatchImportAccountsSuccessScreen'; +import reducer, { ACTIONS } from './immerReducer'; +import { ModalContainer } from './styles'; + +const CustomContainer = styled.div` + width: 100%; + margin-top: 40px; + + .buttons-bottom-buttons { + margin-top: 40px; + } + + .title { + text-align: left; + font-size: 12px; + } + + .screen-descripton { + margin-top: 40px; + margin-bottom: 56px; + } +`; + +export const IMPORT_STATUS = { + PENDING: 'pending', + SUCCESS: 'success', + UP_NEXT: 'waiting', + FAILED: 'error' +}; + +const BatchImportAccounts = ({ accountIdToKeyMap, onCancel }) => { + const availableAccountsIsLoading = useSelector(selectAvailableAccountsIsLoading); + const availableAccounts = useSelector(selectAvailableAccounts); + const accountUrlReferrer = useSelector(selectAccountUrlReferrer); + + const [state, dispatch] = useImmerReducer(reducer, { + accounts: Object.keys(accountIdToKeyMap).map((accountId) => ({ + accountId, + status: null, + key: accountIdToKeyMap[accountId].key, + ledgerHdPath: accountIdToKeyMap[accountId].ledgerHdPath + })), + }); + const currentAccount = useMemo(() => state.accounts.find((account) => account.status === IMPORT_STATUS.PENDING), [state.accounts]); + const accountsApproved = useMemo(() => state.accounts.filter((account) => account.status === IMPORT_STATUS.SUCCESS), [state.accounts]); + const completed = useMemo(() => state.accounts.every((account) => account.status === IMPORT_STATUS.SUCCESS || account.status === IMPORT_STATUS.FAILED), [state.accounts]); + const showSuccessScreen = useMemo(() => completed && state.accounts.some((account) => account.status === IMPORT_STATUS.SUCCESS), [completed, state.accounts]); + + useEffect(() => { + if (!currentAccount) { + dispatch({type: ACTIONS.REMOVE_ACCOUNTS, accounts: availableAccounts}); + } + },[availableAccounts]); + + return showSuccessScreen ? ( + + ) : ( + <> + + + ImportArrow +
+

+ + {accountUrlReferrer || } +

+
+
+ +
+
+ {accountsApproved.length}/{state.accounts.length}{' '} + +
+ +
+ + + + + + dispatch({ type: ACTIONS.BEGIN_IMPORT }) + } + disabled={availableAccountsIsLoading || completed} + > + + + + + + {currentAccount ? ( + state.urlConfirmed ? ( + + dispatch({ type: ACTIONS.SET_CURRENT_DONE }) + } + onFail={() => + dispatch({ type: ACTIONS.SET_CURRENT_FAILED }) + } + /> + ) : ( + {}} + disableClose + > + + SHIELD +

+ +

+
+

+ +

+
+
+ {getWalletURL()} +
+ + dispatch({ type: ACTIONS.CONFIRM_URL }) + } + style={{ marginTop: 48 }} + > + + +
+
+ ) + ) : null} + + ); +}; + +export default BatchImportAccounts; diff --git a/packages/frontend/src/components/accounts/batch_import_accounts/styles.js b/packages/frontend/src/components/accounts/batch_import_accounts/styles.js new file mode 100644 index 0000000000..89699e57ff --- /dev/null +++ b/packages/frontend/src/components/accounts/batch_import_accounts/styles.js @@ -0,0 +1,60 @@ +import styled from 'styled-components'; + +import Container from '../../common/styled/Container.css'; + +export const ModalContainer = styled(Container)` + display: flex; + flex-direction: column; + align-items: center; + padding: 24px; + + h3 { + text-align: center; + } + + .top-icon { + height: 60px; + width: 60px; + margin-bottom: 40px; + } + + .desc { + padding: 0 45px; + text-align: center; + margin-top: 24px; + p { + margin: 0; + } + } + + button { + align-self: stretch; + } + + .link { + margin-top: 16px !important; + font-weight: normal !important; + } + + .button-group { + align-self: stretch; + } + + .connect-with-application { + margin: 20px auto 30px auto; + } + + .transfer-amount { + width: 100%; + } + + .error-label { + margin-top: 16px; + color: #fc5b5b; + } + + .wallet-url { + color: #000; + font-weight: bold; + } +`; diff --git a/yarn.lock b/yarn.lock index 469ef66033..d4391529f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2278,15 +2278,6 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" -"@near-wallet/feature-flags@^0.0.4": - version "0.0.4" - resolved "https://registry.yarnpkg.com/@near-wallet/feature-flags/-/feature-flags-0.0.4.tgz#281a15ad66f9ba0c66bb63f81ea0b21a228071a9" - integrity sha512-TUu1io4U1CzvV5gGzLMGwQLFuA7Hj/RmRD1y0Ys1MdCcyErBced/cLIymQJtC5zZTYW6JlbJTQ3EmZb+HUaG4g== - dependencies: - fs-extra "^10.0.0" - ini "^2.0.0" - inquirer "^8.2.0" - "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -7241,11 +7232,6 @@ ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== -ini@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" - integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== - init-package-json@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-2.0.3.tgz#c8ae4f2a4ad353bcbc089e5ffe98a8f1a314e8fd" @@ -13457,6 +13443,11 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" +use-immer@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/use-immer/-/use-immer-0.7.0.tgz#e3bfbb806b5e3ff6e37441be74c306d91c1e0962" + integrity sha512-Re4hjrP3a/2ABZjAc0b7AK9s626bnO+H33RO2VUhiDZ2StBz5B663K6WNNlr4QtHWaGUmvLpwt3whFvvWuolQw== + use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" From 84c7d14d1fa09ca1955740df12bfb2dd30afbe64 Mon Sep 17 00:00:00 2001 From: esaminu Date: Thu, 16 Jun 2022 01:15:20 +0400 Subject: [PATCH 22/41] refactor: misc readability improvements --- .../AccountImportModal.js | 2 +- .../accounts/batch_import_accounts/index.js | 26 ++++++++++--------- packages/frontend/src/utils/wallet.js | 19 +++++++------- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/packages/frontend/src/components/accounts/batch_import_accounts/AccountImportModal.js b/packages/frontend/src/components/accounts/batch_import_accounts/AccountImportModal.js index 9f092d1b4c..c7b7269377 100644 --- a/packages/frontend/src/components/accounts/batch_import_accounts/AccountImportModal.js +++ b/packages/frontend/src/components/accounts/batch_import_accounts/AccountImportModal.js @@ -97,7 +97,7 @@ const AccountImportModal = ({ account, onSuccess, onFail }) => { }); } - }, [setAddingKey, setError, onSuccess, account]); + }, [onSuccess, account]); return ( { const accountUrlReferrer = useSelector(selectAccountUrlReferrer); const [state, dispatch] = useImmerReducer(reducer, { - accounts: Object.keys(accountIdToKeyMap).map((accountId) => ({ - accountId, - status: null, - key: accountIdToKeyMap[accountId].key, - ledgerHdPath: accountIdToKeyMap[accountId].ledgerHdPath - })), + accounts: Object.entries(accountIdToKeyMap).map( + ([accountId, { key, ledgerHdPath }]) => ({ + accountId, + status: null, + key, + ledgerHdPath, + }) + ), }); const currentAccount = useMemo(() => state.accounts.find((account) => account.status === IMPORT_STATUS.PENDING), [state.accounts]); const accountsApproved = useMemo(() => state.accounts.filter((account) => account.status === IMPORT_STATUS.SUCCESS), [state.accounts]); @@ -67,13 +69,13 @@ const BatchImportAccounts = ({ accountIdToKeyMap, onCancel }) => { if (!currentAccount) { dispatch({type: ACTIONS.REMOVE_ACCOUNTS, accounts: availableAccounts}); } - },[availableAccounts]); + },[availableAccounts, currentAccount]); - return showSuccessScreen ? ( - - ) : ( + if (showSuccessScreen) { + return ; + } + + return ( <> diff --git a/packages/frontend/src/utils/wallet.js b/packages/frontend/src/utils/wallet.js index a2a97112eb..ccfb91ea17 100644 --- a/packages/frontend/src/utils/wallet.js +++ b/packages/frontend/src/utils/wallet.js @@ -342,18 +342,19 @@ export default class Wallet { } return this.save(); } - case Wallet.KEY_TYPES.MULTISIG: + case Wallet.KEY_TYPES.MULTISIG: { + await this.saveAccount(accountId, keyPair); + if (!this.accountId) { + return this.makeAccountActive(accountId); + } + return this.save(); + } case Wallet.KEY_TYPES.LEDGER: { await this.saveAccount(accountId, keyPair); - - if (keyType === Wallet.KEY_TYPES.LEDGER) { - if (ledgerHdPath) { - setLedgerHdPath({accountId, path: ledgerHdPath}); - } - - await this.getLedgerPublicKey(ledgerHdPath).then((publicKey) => setKeyMeta(publicKey.toString(), {type: 'ledger'})); + if (ledgerHdPath) { + setLedgerHdPath({accountId, path: ledgerHdPath}); } - + await this.getLedgerPublicKey(ledgerHdPath).then((publicKey) => setKeyMeta(publicKey.toString(), {type: 'ledger'})); if (!this.accountId) { return this.makeAccountActive(accountId); } From c9520311adc831947517e51e6045c8a5bb324c2b Mon Sep 17 00:00:00 2001 From: esaminu Date: Thu, 16 Jun 2022 01:36:54 +0400 Subject: [PATCH 23/41] fix: redact ed25519 keys from mixpanel --- packages/frontend/src/mixpanel/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/frontend/src/mixpanel/index.js b/packages/frontend/src/mixpanel/index.js index 6fc8f62c1b..06c3142cee 100644 --- a/packages/frontend/src/mixpanel/index.js +++ b/packages/frontend/src/mixpanel/index.js @@ -4,7 +4,8 @@ import { BROWSER_MIXPANEL_TOKEN } from '../config'; function buildTrackingProps() { const sanitizedUrl = decodeURI(window.location.href) - .replace(/(?:\w{3,12} ){11}(?:\w{3,12})/g, 'REDACTED'); + .replace(/(?:\w{3,12} ){11}(?:\w{3,12})/g, 'REDACTED') + .replace(/ed25519:(\w|\d)+/gi, 'REDACTED'); return { $current_url: encodeURI(sanitizedUrl), From c5fcd84d39af627bbd606b7394c6a29e0da8d415 Mon Sep 17 00:00:00 2001 From: esaminu Date: Thu, 16 Jun 2022 01:43:31 +0400 Subject: [PATCH 24/41] chore: update yarn lock --- yarn.lock | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/yarn.lock b/yarn.lock index d4391529f7..e5b1ca129a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2278,6 +2278,15 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" +"@near-wallet/feature-flags@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@near-wallet/feature-flags/-/feature-flags-0.0.4.tgz#281a15ad66f9ba0c66bb63f81ea0b21a228071a9" + integrity sha512-TUu1io4U1CzvV5gGzLMGwQLFuA7Hj/RmRD1y0Ys1MdCcyErBced/cLIymQJtC5zZTYW6JlbJTQ3EmZb+HUaG4g== + dependencies: + fs-extra "^10.0.0" + ini "^2.0.0" + inquirer "^8.2.0" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -7232,6 +7241,11 @@ ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +ini@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + init-package-json@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-2.0.3.tgz#c8ae4f2a4ad353bcbc089e5ffe98a8f1a314e8fd" From 8d239257cc72216c038002784dc83f7ac8b540b5 Mon Sep 17 00:00:00 2001 From: esaminu Date: Thu, 16 Jun 2022 02:10:00 +0400 Subject: [PATCH 25/41] fix: unset height and fix stroke props --- .../frontend/src/components/accounts/AccountListImport.js | 1 + packages/frontend/src/images/IconArrowRight.js | 6 +++--- packages/frontend/src/images/IconClose.js | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/frontend/src/components/accounts/AccountListImport.js b/packages/frontend/src/components/accounts/AccountListImport.js index accf5086db..7b61e4d089 100644 --- a/packages/frontend/src/components/accounts/AccountListImport.js +++ b/packages/frontend/src/components/accounts/AccountListImport.js @@ -30,6 +30,7 @@ const UserIcon = styled.div` const AnimateList = styled.div` margin-top: 10px; overflow: hidden; +height: unset; & > div:first-of-type { margin-top: ${(props) => `-${props.animate * 60}px`}; diff --git a/packages/frontend/src/images/IconArrowRight.js b/packages/frontend/src/images/IconArrowRight.js index af9650a0f6..8a1202adb6 100644 --- a/packages/frontend/src/images/IconArrowRight.js +++ b/packages/frontend/src/images/IconArrowRight.js @@ -6,9 +6,9 @@ export default ({ stroke = '#ccc' }) => ( d="m1 19 9-9-9-9" fill="none" stroke={stroke} - stroke-linecap="round" - stroke-linejoin="round" - stroke-width="2" + strokeLinecap="round" + strokeLinejoin="round" + strokeWidth="2" /> ); diff --git a/packages/frontend/src/images/IconClose.js b/packages/frontend/src/images/IconClose.js index 30f04536a2..5b13102274 100644 --- a/packages/frontend/src/images/IconClose.js +++ b/packages/frontend/src/images/IconClose.js @@ -5,9 +5,9 @@ export default ({ stroke = '#ccc' }) => ( From a7b39867f14a301be75318f3926ddd420fc2e8bf Mon Sep 17 00:00:00 2001 From: esaminu Date: Fri, 17 Jun 2022 01:23:40 +0400 Subject: [PATCH 26/41] fix: include WALLET_BATCH_IMPORT_URL in --- packages/frontend/src/redux/actions/account.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/redux/actions/account.js b/packages/frontend/src/redux/actions/account.js index 02057fe06f..edfac6afef 100644 --- a/packages/frontend/src/redux/actions/account.js +++ b/packages/frontend/src/redux/actions/account.js @@ -122,7 +122,7 @@ export const handleRefreshUrl = (prevRouter) => (dispatch, getState) => { const { pathname, search } = prevRouter?.location || getLocation(getState()); const currentPage = pathname.split('/')[pathname[1] === '/' ? 2 : 1]; - if ([...WALLET_CREATE_NEW_ACCOUNT_FLOW_URLS, WALLET_LOGIN_URL, WALLET_SIGN_URL, WALLET_LINKDROP_URL].includes(currentPage)) { + if ([...WALLET_CREATE_NEW_ACCOUNT_FLOW_URLS, WALLET_LOGIN_URL, WALLET_SIGN_URL, WALLET_LINKDROP_URL, WALLET_BATCH_IMPORT_URL].includes(currentPage)) { const parsedUrl = { ...parse(search), referrer: document.referrer ? new URL(document.referrer).hostname : undefined, From 8a2eec65d276fda7b9f9c1af77f5ebfcc2be7543 Mon Sep 17 00:00:00 2001 From: esaminu Date: Fri, 17 Jun 2022 21:31:51 +0400 Subject: [PATCH 27/41] refactor: rename reducer and add jsdoc, initState --- .../accounts/batch_import_accounts/index.js | 2 +- ...r.js => sequentialAccountImportReducer.js} | 28 +++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) rename packages/frontend/src/components/accounts/batch_import_accounts/{immerReducer.js => sequentialAccountImportReducer.js} (80%) diff --git a/packages/frontend/src/components/accounts/batch_import_accounts/index.js b/packages/frontend/src/components/accounts/batch_import_accounts/index.js index 50c75d520b..b192017347 100644 --- a/packages/frontend/src/components/accounts/batch_import_accounts/index.js +++ b/packages/frontend/src/components/accounts/batch_import_accounts/index.js @@ -16,7 +16,7 @@ import Container from '../../common/styled/Container.css'; import AccountListImport from '../AccountListImport'; import AccountImportModal from './AccountImportModal'; import BatchImportAccountsSuccessScreen from './BatchImportAccountsSuccessScreen'; -import reducer, { ACTIONS } from './immerReducer'; +import reducer, { ACTIONS } from './sequentialAccountImportReducer'; import { ModalContainer } from './styles'; const CustomContainer = styled.div` diff --git a/packages/frontend/src/components/accounts/batch_import_accounts/immerReducer.js b/packages/frontend/src/components/accounts/batch_import_accounts/sequentialAccountImportReducer.js similarity index 80% rename from packages/frontend/src/components/accounts/batch_import_accounts/immerReducer.js rename to packages/frontend/src/components/accounts/batch_import_accounts/sequentialAccountImportReducer.js index 114bfae30a..0c35a22c63 100644 --- a/packages/frontend/src/components/accounts/batch_import_accounts/immerReducer.js +++ b/packages/frontend/src/components/accounts/batch_import_accounts/sequentialAccountImportReducer.js @@ -2,6 +2,17 @@ import { differenceBy } from 'lodash'; import { IMPORT_STATUS } from '.'; +/** + * @typedef {{ + * accountId: string, + * status: "pending" | "success" | "waiting" | "error" | null , + * key: string, + * ledgerHdPath: string + * }} account + * + * @typedef {{accounts: account[], urlConfirmed: boolean}} state + */ + export const ACTIONS = { BEGIN_IMPORT: 'BEGIN_IMPORT', SET_CURRENT_DONE: 'SET_CURRENT_DONE', @@ -10,7 +21,20 @@ export const ACTIONS = { REMOVE_ACCOUNTS: 'REMOVE_ACCOUNTS', }; -const immerReducer = (state, action) => { +/** + * @type state + */ +const initialState = { + accounts: [], + urlConfirmed: false +}; + + +/** + * @param {state} state + * @returns {state} + * */ +const sequentialAccountImportReducer = (state = initialState, action) => { switch (action.type) { case ACTIONS.REMOVE_ACCOUNTS: { if (state.accounts.every(({ status }) => status === null)) { @@ -61,4 +85,4 @@ const immerReducer = (state, action) => { } }; -export default immerReducer; +export default sequentialAccountImportReducer; From 6eb1ffc6951811d51b518926b99a9165420bf8af Mon Sep 17 00:00:00 2001 From: esaminu Date: Fri, 17 Jun 2022 21:32:56 +0400 Subject: [PATCH 28/41] fix: refreshUrl if hash params exist --- packages/frontend/src/redux/actions/account.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/redux/actions/account.js b/packages/frontend/src/redux/actions/account.js index edfac6afef..0e0e2d9104 100644 --- a/packages/frontend/src/redux/actions/account.js +++ b/packages/frontend/src/redux/actions/account.js @@ -119,7 +119,7 @@ export const handleClearUrl = () => (dispatch, getState) => { export const parseTransactionsToSign = createAction('PARSE_TRANSACTIONS_TO_SIGN'); export const handleRefreshUrl = (prevRouter) => (dispatch, getState) => { - const { pathname, search } = prevRouter?.location || getLocation(getState()); + const { pathname, search, hash } = prevRouter?.location || getLocation(getState()); const currentPage = pathname.split('/')[pathname[1] === '/' ? 2 : 1]; if ([...WALLET_CREATE_NEW_ACCOUNT_FLOW_URLS, WALLET_LOGIN_URL, WALLET_SIGN_URL, WALLET_LINKDROP_URL, WALLET_BATCH_IMPORT_URL].includes(currentPage)) { @@ -131,7 +131,7 @@ export const handleRefreshUrl = (prevRouter) => (dispatch, getState) => { if ([WALLET_CREATE_NEW_ACCOUNT_URL, WALLET_LINKDROP_URL].includes(currentPage) && search !== '') { saveState(parsedUrl); dispatch(refreshUrl(parsedUrl)); - } else if ([WALLET_LOGIN_URL, WALLET_SIGN_URL, WALLET_BATCH_IMPORT_URL].includes(currentPage) && search !== '') { + } else if ([WALLET_LOGIN_URL, WALLET_SIGN_URL, WALLET_BATCH_IMPORT_URL].includes(currentPage) && (search !== '' || hash !== '')) { saveState(parsedUrl); dispatch(refreshUrl(parsedUrl)); dispatch(checkContractId()); From 82200f1607ba0da22aaed127fb5b54eccaf6c7b6 Mon Sep 17 00:00:00 2001 From: esaminu Date: Fri, 17 Jun 2022 21:43:38 +0400 Subject: [PATCH 29/41] chore: remove returns in jsdoc --- .../batch_import_accounts/sequentialAccountImportReducer.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/frontend/src/components/accounts/batch_import_accounts/sequentialAccountImportReducer.js b/packages/frontend/src/components/accounts/batch_import_accounts/sequentialAccountImportReducer.js index 0c35a22c63..1968d4e224 100644 --- a/packages/frontend/src/components/accounts/batch_import_accounts/sequentialAccountImportReducer.js +++ b/packages/frontend/src/components/accounts/batch_import_accounts/sequentialAccountImportReducer.js @@ -32,7 +32,6 @@ const initialState = { /** * @param {state} state - * @returns {state} * */ const sequentialAccountImportReducer = (state = initialState, action) => { switch (action.type) { From 7c9e797daf6a43983cabc603d08649ffb40dac58 Mon Sep 17 00:00:00 2001 From: gutsyphilip Date: Tue, 24 May 2022 00:12:16 +0400 Subject: [PATCH 30/41] feat: original pass at migration banner --- packages/frontend/src/components/Routing.js | 2 + .../src/components/common/MigrationBanner.js | 139 ++++++++++++++++++ .../src/components/common/NetworkBanner.js | 6 +- .../frontend/src/translations/en.global.json | 3 + 4 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 packages/frontend/src/components/common/MigrationBanner.js diff --git a/packages/frontend/src/components/Routing.js b/packages/frontend/src/components/Routing.js index f3584d49a5..07cfee779e 100644 --- a/packages/frontend/src/components/Routing.js +++ b/packages/frontend/src/components/Routing.js @@ -76,6 +76,7 @@ import { BuyNear } from './buy/BuyNear'; import Footer from './common/Footer'; import GlobalAlert from './common/GlobalAlert'; import GuestLandingRoute from './common/GuestLandingRoute'; +import MigrationBanner from './common/MigrationBanner'; import NetworkBanner from './common/NetworkBanner'; import PrivateRoute from './common/routing/PrivateRoute'; import PublicRoute from './common/routing/PublicRoute'; @@ -339,6 +340,7 @@ class Routing extends Component { > + diff --git a/packages/frontend/src/components/common/MigrationBanner.js b/packages/frontend/src/components/common/MigrationBanner.js new file mode 100644 index 0000000000..e223792b4d --- /dev/null +++ b/packages/frontend/src/components/common/MigrationBanner.js @@ -0,0 +1,139 @@ +import React, { useEffect } from 'react'; +import { Translate } from 'react-localize-redux'; +import styled from 'styled-components'; + +// import { MIGRATION_START_DATE, MIGRATION_END_DATE } from '../../config'; +import FormButton from './FormButton'; +import Container from './styled/Container.css'; + +const StyledContainer = styled.div` + color: #995200; + background-color: #FFECD6; + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + + .tooltip { + margin: 0 0 0 8px; + svg { + width: 18px; + height: 18px; + + path { + stroke: white; + } + } + } + + .network-link { + margin-left: 6px; + } + + a { + color: white; + :hover { + color: white; + text-decoration: underline; + } + } + + &.staging-banner { + background-color: #F6C98E; + color: #452500; + + .tooltip { + svg { + path { + stroke: #452500; + } + } + } + + .alert-triangle-icon { + margin-right: 8px; + min-width: 16px; + } + } +`; + +const ContentWrapper = styled(Container)` + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 0; +`; + +const CustomButton = styled(FormButton)` + color: #452500 !important; + background-color: #FFB259 !important; + border: none !important; + white-space: nowrap; + padding: 11px 16px; + margin: 0 !important; + height: 40px; +`; + +const MigrationBanner = ({ account }) => { + const setBannerHeight = () => { + const migrationBanner = document.getElementById('migration-banner'); + const bannerHeight = migrationBanner ? migrationBanner.offsetHeight : 0; + const networkBanner = document.getElementById('top-banner'); + if (networkBanner){ + networkBanner.style.top = bannerHeight ? `${bannerHeight}px` : 0; + } else { + const app = document.getElementById('app-container'); + const navContainer = document.getElementById('nav-container'); + navContainer.style.top = bannerHeight ? `${bannerHeight}px` : 0; + app.style.paddingTop = bannerHeight ? `${bannerHeight + 85}px` : '75px'; + } + }; + + const getInviteCalendarEventURl =()=>{ + const calenderURL = new URL('https://calendar.google.com/calendar/u/0/r/eventedit'); + calenderURL.searchParams.append('dates', '20160917T001500Z'); + calenderURL.searchParams.append('text', 'Migration Event'); + calenderURL.searchParams.append('details', 'Some stuff goes here.'); + return calenderURL; + }; + + const handleAddToCalendar = ()=>{ + window.open(getInviteCalendarEventURl(), '_blank'); + }; + + + + useEffect(() => { + setBannerHeight(); + window.addEventListener('resize', setBannerHeight); + return () => { + window.removeEventListener('resize', setBannerHeight); + }; + }, [account]); + + + + + const preMigrationMarkup = ( + + + + Set a Reminder + + + ); + + + + return ( + + {preMigrationMarkup} + + ); +}; + +export default MigrationBanner; diff --git a/packages/frontend/src/components/common/NetworkBanner.js b/packages/frontend/src/components/common/NetworkBanner.js index 81fed9051f..b3fa2510b3 100644 --- a/packages/frontend/src/components/common/NetworkBanner.js +++ b/packages/frontend/src/components/common/NetworkBanner.js @@ -76,11 +76,13 @@ const NetworkBanner = ({ account }) => { }, [account]); const setBannerHeight = () => { - const bannerHeight = document.getElementById('top-banner') && document.getElementById('top-banner').offsetHeight; + const banner = document.getElementById('top-banner'); + const bannerHeight = banner ? banner.getBoundingClientRect().top + banner.offsetHeight : 0; + console.log(bannerHeight); const app = document.getElementById('app-container'); const navContainer = document.getElementById('nav-container'); navContainer.style.top = bannerHeight ? `${bannerHeight}px` : 0; - app.style.paddingTop = bannerHeight ? `${bannerHeight + 85}px` : '75px'; + app.style.paddingTop = bannerHeight ? `${bannerHeight + 85}px` : '75px'; }; if (!IS_MAINNET) { diff --git a/packages/frontend/src/translations/en.global.json b/packages/frontend/src/translations/en.global.json index e54531fa03..2eb58bdfdb 100644 --- a/packages/frontend/src/translations/en.global.json +++ b/packages/frontend/src/translations/en.global.json @@ -832,6 +832,9 @@ "title": "Whoops, looks like this page is missing." }, "poweredByCoinGecko": "Prices powered by CoinGecko", + "preMigration": { + "message": "We’re moving! NEAR Wallet will be migrating to a new domain (mynearwallet.com) starting on 6/12/22.
Return once the new domain is live to migrate your accounts, or export your account keys from your account settings." + }, "profile": { "account": { "available": "Available balance", From 24a93039d695987304df83c7cb1d9567b1358003 Mon Sep 17 00:00:00 2001 From: gutsyphilip Date: Wed, 25 May 2022 05:01:13 +0400 Subject: [PATCH 31/41] feat: make banner responsive --- .../src/components/common/MigrationBanner.js | 110 ++++++++++-------- .../frontend/src/images/IconAlertTriangle.js | 21 ++++ packages/frontend/src/images/IconBell.js | 21 ++++ packages/frontend/src/images/IconShare.js | 21 ++++ .../frontend/src/translations/en.global.json | 5 +- 5 files changed, 131 insertions(+), 47 deletions(-) create mode 100644 packages/frontend/src/images/IconAlertTriangle.js create mode 100644 packages/frontend/src/images/IconBell.js create mode 100644 packages/frontend/src/images/IconShare.js diff --git a/packages/frontend/src/components/common/MigrationBanner.js b/packages/frontend/src/components/common/MigrationBanner.js index e223792b4d..8a1146f76d 100644 --- a/packages/frontend/src/components/common/MigrationBanner.js +++ b/packages/frontend/src/components/common/MigrationBanner.js @@ -2,7 +2,10 @@ import React, { useEffect } from 'react'; import { Translate } from 'react-localize-redux'; import styled from 'styled-components'; -// import { MIGRATION_START_DATE, MIGRATION_END_DATE } from '../../config'; +import { MIGRATION_START_DATE, MIGRATION_END_DATE } from '../../config'; +import IconAlertTriangle from '../../images/IconAlertTriangle'; +import IconBell from '../../images/IconBell'; +import IconShare from '../../images/IconShare'; import FormButton from './FormButton'; import Container from './styled/Container.css'; @@ -18,56 +21,49 @@ const StyledContainer = styled.div` align-items: center; justify-content: center; - .tooltip { - margin: 0 0 0 8px; - svg { - width: 18px; - height: 18px; + a { + color: #995200; + text-decoration: underline; + cursor: pointer; - path { - stroke: white; - } + :hover { + color: #995200; } } +`; - .network-link { - margin-left: 6px; +const ContentWrapper = styled(Container)` + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 0; + + &>*:first-child{ + margin-right: 10px; } - a { - color: white; - :hover { - color: white; - text-decoration: underline; - } + @media (max-width: 992px) { + padding: 16px; } - &.staging-banner { - background-color: #F6C98E; - color: #452500; + @media (max-width: 768px) { + flex-direction: column; + align-items: flex-start; + padding: 16px; + } - .tooltip { - svg { - path { - stroke: #452500; - } - } - } + & .content { + display: flex; + align-items: flex-start; + flex-wrap: none; - .alert-triangle-icon { - margin-right: 8px; - min-width: 16px; + svg{ + margin-right: 18px; + min-width: 24px; } } `; -const ContentWrapper = styled(Container)` - display: flex; - align-items: center; - justify-content: space-between; - margin-top: 0; -`; - const CustomButton = styled(FormButton)` color: #452500 !important; background-color: #FFB259 !important; @@ -76,9 +72,15 @@ const CustomButton = styled(FormButton)` padding: 11px 16px; margin: 0 !important; height: 40px; + + @media (max-width: 768px) { + margin-top: 16px !important; + } `; const MigrationBanner = ({ account }) => { + const migrationStartDate = new Date(MIGRATION_START_DATE *1000); + const migrationEndDate = new Date(MIGRATION_END_DATE *1000); const setBannerHeight = () => { const migrationBanner = document.getElementById('migration-banner'); const bannerHeight = migrationBanner ? migrationBanner.offsetHeight : 0; @@ -93,11 +95,14 @@ const MigrationBanner = ({ account }) => { } }; + const getInviteCalendarEventURl =()=>{ const calenderURL = new URL('https://calendar.google.com/calendar/u/0/r/eventedit'); - calenderURL.searchParams.append('dates', '20160917T001500Z'); - calenderURL.searchParams.append('text', 'Migration Event'); - calenderURL.searchParams.append('details', 'Some stuff goes here.'); + const calendarDate= migrationStartDate.toISOString().replace(/-|:|\.\d\d\d/g,''); + + calenderURL.searchParams.append('dates', `${calendarDate}/${calendarDate}`); + calenderURL.searchParams.append('text', 'Wallet Migration Event'); + calenderURL.searchParams.append('details', 'This is the official start date of the near wallet migration. Please make sure to start migrating your account as soon as possible.'); return calenderURL; }; @@ -115,23 +120,36 @@ const MigrationBanner = ({ account }) => { }; }, [account]); - - - const preMigrationMarkup = ( - +
+ + +
- Set a Reminder + Set a Reminder
); + const postMigrationMarkup = ( + +
+ + +
+ {}}> + Transfer My Accounts + +
+ ); + return ( - {preMigrationMarkup} + {/* {preMigrationMarkup} */} + {postMigrationMarkup} ); }; diff --git a/packages/frontend/src/images/IconAlertTriangle.js b/packages/frontend/src/images/IconAlertTriangle.js new file mode 100644 index 0000000000..429b367b10 --- /dev/null +++ b/packages/frontend/src/images/IconAlertTriangle.js @@ -0,0 +1,21 @@ +import * as React from 'react'; + +const IconAlertTriangle = (props) => ( + + + +); + +export default IconAlertTriangle; diff --git a/packages/frontend/src/images/IconBell.js b/packages/frontend/src/images/IconBell.js new file mode 100644 index 0000000000..65dff67eb7 --- /dev/null +++ b/packages/frontend/src/images/IconBell.js @@ -0,0 +1,21 @@ +import * as React from 'react'; + +const IconBell = (props) => ( + + + +); + +export default IconBell; diff --git a/packages/frontend/src/images/IconShare.js b/packages/frontend/src/images/IconShare.js new file mode 100644 index 0000000000..8ae741a066 --- /dev/null +++ b/packages/frontend/src/images/IconShare.js @@ -0,0 +1,21 @@ +import * as React from 'react'; + +const IconShare = (props) => ( + + + +); + +export default IconShare; diff --git a/packages/frontend/src/translations/en.global.json b/packages/frontend/src/translations/en.global.json index 2eb58bdfdb..5d18f595af 100644 --- a/packages/frontend/src/translations/en.global.json +++ b/packages/frontend/src/translations/en.global.json @@ -832,8 +832,11 @@ "title": "Whoops, looks like this page is missing." }, "poweredByCoinGecko": "Prices powered by CoinGecko", + "postMigration": { + "message": "NEAR Wallet has migrated to a new domain: https://mynearwallet.com.
You have until ${endDate} to transfer your accounts, or to export your account keys from your account settings." + }, "preMigration": { - "message": "We’re moving! NEAR Wallet will be migrating to a new domain (mynearwallet.com) starting on 6/12/22.
Return once the new domain is live to migrate your accounts, or export your account keys from your account settings." + "message": "We’re moving! NEAR Wallet will be migrating to a new domain (mynearwallet.com) starting on ${startDate}.
Return once the new domain is live to migrate your accounts, or export your account keys from your account settings." }, "profile": { "account": { From 782228baa435a896f797d00c8446aba3351563d3 Mon Sep 17 00:00:00 2001 From: gutsyphilip Date: Wed, 25 May 2022 05:34:15 +0400 Subject: [PATCH 32/41] feat: SHOW_MIGRATION_BANNER feature flag --- features/flags.json | 44 +++++++++++++++++++ packages/frontend/src/components/Routing.js | 4 +- .../src/components/common/MigrationBanner.js | 14 +++--- .../src/components/common/NetworkBanner.js | 1 - 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/features/flags.json b/features/flags.json index 9ae401dd50..69bc1fb252 100644 --- a/features/flags.json +++ b/features/flags.json @@ -86,5 +86,49 @@ "lastEditedBy": "esaminu", "lastEditedAt": "2022-05-10T17:07:20.351Z" } + }, + "SHOW_MIGRATION_BANNER": { + "createdBy": "gutsyphilip", + "createdAt": "2022-05-25T01:11:55.121Z", + "development": { + "enabled": true, + "lastEditedBy": "gutsyphilip", + "lastEditedAt": "2022-05-25T01:11:55.121Z" + }, + "testnet": { + "enabled": true, + "lastEditedBy": "gutsyphilip", + "lastEditedAt": "2022-05-25T01:11:55.121Z" + }, + "mainnet": { + "enabled": false, + "lastEditedBy": "gutsyphilip", + "lastEditedAt": "2022-05-25T01:11:55.121Z" + }, + "mainnet_STAGING": { + "enabled": false, + "lastEditedBy": "gutsyphilip", + "lastEditedAt": "2022-05-25T01:11:55.121Z" + }, + "testnet_STAGING": { + "enabled": false, + "lastEditedBy": "gutsyphilip", + "lastEditedAt": "2022-05-25T01:11:55.121Z" + }, + "testnet_NEARORG": { + "enabled": true, + "lastEditedBy": "gutsyphilip", + "lastEditedAt": "2022-05-25T01:11:55.121Z" + }, + "mainnet_NEARORG": { + "enabled": true, + "lastEditedBy": "gutsyphilip", + "lastEditedAt": "2022-05-25T01:11:55.121Z" + }, + "mainnet_STAGING_NEARORG": { + "enabled": true, + "lastEditedBy": "gutsyphilip", + "lastEditedAt": "2022-05-25T01:11:55.121Z" + } } } diff --git a/packages/frontend/src/components/Routing.js b/packages/frontend/src/components/Routing.js index 07cfee779e..6b81711995 100644 --- a/packages/frontend/src/components/Routing.js +++ b/packages/frontend/src/components/Routing.js @@ -10,6 +10,7 @@ import { connect } from 'react-redux'; import { Redirect, Switch } from 'react-router-dom'; import styled, { ThemeProvider } from 'styled-components'; +import { SHOW_MIGRATION_BANNER } from '../../../../features'; import favicon from '../../src/images/mynearwallet-cropped.svg'; import TwoFactorVerifyModal from '../components/accounts/two_factor/TwoFactorVerifyModal'; import { @@ -340,7 +341,8 @@ class Routing extends Component { > - + {SHOW_MIGRATION_BANNER && } + diff --git a/packages/frontend/src/components/common/MigrationBanner.js b/packages/frontend/src/components/common/MigrationBanner.js index 8a1146f76d..04ad4fc40e 100644 --- a/packages/frontend/src/components/common/MigrationBanner.js +++ b/packages/frontend/src/components/common/MigrationBanner.js @@ -1,3 +1,4 @@ +import { isAfter } from 'date-fns'; import React, { useEffect } from 'react'; import { Translate } from 'react-localize-redux'; import styled from 'styled-components'; @@ -79,8 +80,12 @@ const CustomButton = styled(FormButton)` `; const MigrationBanner = ({ account }) => { - const migrationStartDate = new Date(MIGRATION_START_DATE *1000); - const migrationEndDate = new Date(MIGRATION_END_DATE *1000); + const migrationStartDate = new Date( MIGRATION_START_DATE * 1000); + const migrationEndDate = new Date(MIGRATION_END_DATE * 1000); + + const showPostMigrationBanner = isAfter(new Date(), new Date(migrationStartDate)); + + const setBannerHeight = () => { const migrationBanner = document.getElementById('migration-banner'); const bannerHeight = migrationBanner ? migrationBanner.offsetHeight : 0; @@ -144,12 +149,9 @@ const MigrationBanner = ({ account }) => { ); - - return ( - {/* {preMigrationMarkup} */} - {postMigrationMarkup} + {showPostMigrationBanner ? postMigrationMarkup: preMigrationMarkup} ); }; diff --git a/packages/frontend/src/components/common/NetworkBanner.js b/packages/frontend/src/components/common/NetworkBanner.js index b3fa2510b3..c25b58d7f0 100644 --- a/packages/frontend/src/components/common/NetworkBanner.js +++ b/packages/frontend/src/components/common/NetworkBanner.js @@ -78,7 +78,6 @@ const NetworkBanner = ({ account }) => { const setBannerHeight = () => { const banner = document.getElementById('top-banner'); const bannerHeight = banner ? banner.getBoundingClientRect().top + banner.offsetHeight : 0; - console.log(bannerHeight); const app = document.getElementById('app-container'); const navContainer = document.getElementById('nav-container'); navContainer.style.top = bannerHeight ? `${bannerHeight}px` : 0; From 54ca649bdb3fc181d17e40998134df36f8169fa5 Mon Sep 17 00:00:00 2001 From: gutsyphilip Date: Wed, 25 May 2022 05:35:55 +0400 Subject: [PATCH 33/41] feat: update dynamic calendar start and end date --- packages/frontend/src/components/common/MigrationBanner.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/components/common/MigrationBanner.js b/packages/frontend/src/components/common/MigrationBanner.js index 04ad4fc40e..85060710f8 100644 --- a/packages/frontend/src/components/common/MigrationBanner.js +++ b/packages/frontend/src/components/common/MigrationBanner.js @@ -103,9 +103,10 @@ const MigrationBanner = ({ account }) => { const getInviteCalendarEventURl =()=>{ const calenderURL = new URL('https://calendar.google.com/calendar/u/0/r/eventedit'); - const calendarDate= migrationStartDate.toISOString().replace(/-|:|\.\d\d\d/g,''); + const calendarStartDate= migrationStartDate.toISOString().replace(/-|:|\.\d\d\d/g,''); + const calendarEndDate= migrationEndDate.toISOString().replace(/-|:|\.\d\d\d/g,''); - calenderURL.searchParams.append('dates', `${calendarDate}/${calendarDate}`); + calenderURL.searchParams.append('dates', `${calendarStartDate}/${calendarEndDate}`); calenderURL.searchParams.append('text', 'Wallet Migration Event'); calenderURL.searchParams.append('details', 'This is the official start date of the near wallet migration. Please make sure to start migrating your account as soon as possible.'); return calenderURL; From 925e73b9e9b3abb09d33d5d68aba2713bfd6db33 Mon Sep 17 00:00:00 2001 From: gutsyphilip Date: Mon, 30 May 2022 16:28:43 +0400 Subject: [PATCH 34/41] fix: PR review cleanups --- .../src/components/common/MigrationBanner.js | 18 +++++++----------- .../src/config/configFromEnvironment.js | 3 +++ packages/frontend/src/config/envParsers.js | 6 ++++++ .../frontend/src/translations/en.global.json | 2 ++ 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/packages/frontend/src/components/common/MigrationBanner.js b/packages/frontend/src/components/common/MigrationBanner.js index 85060710f8..8738c63d03 100644 --- a/packages/frontend/src/components/common/MigrationBanner.js +++ b/packages/frontend/src/components/common/MigrationBanner.js @@ -1,4 +1,3 @@ -import { isAfter } from 'date-fns'; import React, { useEffect } from 'react'; import { Translate } from 'react-localize-redux'; import styled from 'styled-components'; @@ -80,11 +79,8 @@ const CustomButton = styled(FormButton)` `; const MigrationBanner = ({ account }) => { - const migrationStartDate = new Date( MIGRATION_START_DATE * 1000); - const migrationEndDate = new Date(MIGRATION_END_DATE * 1000); - - const showPostMigrationBanner = isAfter(new Date(), new Date(migrationStartDate)); + const showPostMigrationBanner = new Date() > MIGRATION_START_DATE; const setBannerHeight = () => { const migrationBanner = document.getElementById('migration-banner'); @@ -103,8 +99,8 @@ const MigrationBanner = ({ account }) => { const getInviteCalendarEventURl =()=>{ const calenderURL = new URL('https://calendar.google.com/calendar/u/0/r/eventedit'); - const calendarStartDate= migrationStartDate.toISOString().replace(/-|:|\.\d\d\d/g,''); - const calendarEndDate= migrationEndDate.toISOString().replace(/-|:|\.\d\d\d/g,''); + const calendarStartDate= MIGRATION_START_DATE.toISOString().replace(/-|:|\.\d\d\d/g,''); + const calendarEndDate= MIGRATION_END_DATE.toISOString().replace(/-|:|\.\d\d\d/g,''); calenderURL.searchParams.append('dates', `${calendarStartDate}/${calendarEndDate}`); calenderURL.searchParams.append('text', 'Wallet Migration Event'); @@ -130,10 +126,10 @@ const MigrationBanner = ({ account }) => {
- +
- Set a Reminder +
); @@ -142,10 +138,10 @@ const MigrationBanner = ({ account }) => {
- +
{}}> - Transfer My Accounts +
); diff --git a/packages/frontend/src/config/configFromEnvironment.js b/packages/frontend/src/config/configFromEnvironment.js index b87e4155a7..fb932e1c98 100644 --- a/packages/frontend/src/config/configFromEnvironment.js +++ b/packages/frontend/src/config/configFromEnvironment.js @@ -7,6 +7,7 @@ import { envValIsSet, parseBooleanFromShell, parseCommaSeperatedStringAsArrayFromShell, + parseDateFromShell } from './envParsers'; const NEAR_WALLET_ENV = process.env.NEAR_WALLET_ENV; @@ -44,6 +45,8 @@ module.exports = { ].some((env) => env === NEAR_WALLET_ENV), LINKDROP_GAS: process.env.LINKDROP_GAS, LOCKUP_ACCOUNT_ID_SUFFIX: process.env.LOCKUP_ACCOUNT_ID_SUFFIX, + MIGRATION_START_DATE: parseDateFromShell(process.env.MIGRATION_START_DATE), + MIGRATION_END_DATE: parseDateFromShell(process.env.MIGRATION_END_DATE), MIN_BALANCE_FOR_GAS: process.env.REACT_APP_MIN_BALANCE_FOR_GAS, MIN_BALANCE_TO_CREATE: process.env.MIN_BALANCE_TO_CREATE, MOONPAY_API_KEY: process.env.MOONPAY_API_KEY, diff --git a/packages/frontend/src/config/envParsers.js b/packages/frontend/src/config/envParsers.js index 94fb91778a..3c43747c95 100644 --- a/packages/frontend/src/config/envParsers.js +++ b/packages/frontend/src/config/envParsers.js @@ -14,9 +14,15 @@ const parseObjectFromShell = (envVal) => { const parseCommaSeperatedStringAsArrayFromShell = (envVal) => envValIsSet(envVal) ? envVal.split(',') : undefined; +/* Returns a Date object from a unix timestamp string */ +const parseDateFromShell = (envVal) => { + return envValIsSet(envVal) ? new Date(envVal * 1000) : undefined; +}; + module.exports = { envValIsSet, parseBooleanFromShell, parseObjectFromShell, parseCommaSeperatedStringAsArrayFromShell, + parseDateFromShell }; diff --git a/packages/frontend/src/translations/en.global.json b/packages/frontend/src/translations/en.global.json index 5d18f595af..5aabf2aaed 100644 --- a/packages/frontend/src/translations/en.global.json +++ b/packages/frontend/src/translations/en.global.json @@ -833,9 +833,11 @@ }, "poweredByCoinGecko": "Prices powered by CoinGecko", "postMigration": { + "cta": "Transfer my Accounts", "message": "NEAR Wallet has migrated to a new domain: https://mynearwallet.com.
You have until ${endDate} to transfer your accounts, or to export your account keys from your account settings." }, "preMigration": { + "cta": "Set a Reminder", "message": "We’re moving! NEAR Wallet will be migrating to a new domain (mynearwallet.com) starting on ${startDate}.
Return once the new domain is live to migrate your accounts, or export your account keys from your account settings." }, "profile": { From 1ca08a47953812d101934be615f2b31ec92bc585 Mon Sep 17 00:00:00 2001 From: gutsyphilip Date: Mon, 30 May 2022 17:11:43 +0400 Subject: [PATCH 35/41] fix: localization for event calender information --- .../src/components/common/MigrationBanner.js | 26 ++++++++++++------- .../frontend/src/translations/en.global.json | 4 +++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/frontend/src/components/common/MigrationBanner.js b/packages/frontend/src/components/common/MigrationBanner.js index 8738c63d03..6995fe3f72 100644 --- a/packages/frontend/src/components/common/MigrationBanner.js +++ b/packages/frontend/src/components/common/MigrationBanner.js @@ -97,22 +97,21 @@ const MigrationBanner = ({ account }) => { }; - const getInviteCalendarEventURl =()=>{ + const getInviteCalendarEventURl =({ text, details })=>{ const calenderURL = new URL('https://calendar.google.com/calendar/u/0/r/eventedit'); const calendarStartDate= MIGRATION_START_DATE.toISOString().replace(/-|:|\.\d\d\d/g,''); const calendarEndDate= MIGRATION_END_DATE.toISOString().replace(/-|:|\.\d\d\d/g,''); calenderURL.searchParams.append('dates', `${calendarStartDate}/${calendarEndDate}`); - calenderURL.searchParams.append('text', 'Wallet Migration Event'); - calenderURL.searchParams.append('details', 'This is the official start date of the near wallet migration. Please make sure to start migrating your account as soon as possible.'); + calenderURL.searchParams.append('text', text); + calenderURL.searchParams.append('details', details); return calenderURL; }; - const handleAddToCalendar = ()=>{ - window.open(getInviteCalendarEventURl(), '_blank'); + const handleAddToCalendar = (eventInfo)=>{ + window.open(getInviteCalendarEventURl(eventInfo), '_blank'); }; - useEffect(() => { setBannerHeight(); @@ -121,6 +120,7 @@ const MigrationBanner = ({ account }) => { window.removeEventListener('resize', setBannerHeight); }; }, [account]); + const preMigrationMarkup = ( @@ -128,9 +128,17 @@ const MigrationBanner = ({ account }) => {
- - - + + {({ translate }) => + {handleAddToCalendar({ + text: translate('preMigration.calendarEvent.text'), + details: translate('preMigration.calendarEvent.details'), + });}}> + + + } + + ); diff --git a/packages/frontend/src/translations/en.global.json b/packages/frontend/src/translations/en.global.json index 5aabf2aaed..bfbb72767a 100644 --- a/packages/frontend/src/translations/en.global.json +++ b/packages/frontend/src/translations/en.global.json @@ -837,6 +837,10 @@ "message": "NEAR Wallet has migrated to a new domain: https://mynearwallet.com.
You have until ${endDate} to transfer your accounts, or to export your account keys from your account settings." }, "preMigration": { + "calendarEvent": { + "details": "This is the official start date of the near wallet migration. Please make sure to start migrating your account as soon as possible.", + "text": "NEAR Wallet Migration" + }, "cta": "Set a Reminder", "message": "We’re moving! NEAR Wallet will be migrating to a new domain (mynearwallet.com) starting on ${startDate}.
Return once the new domain is live to migrate your accounts, or export your account keys from your account settings." }, From 76863f1159629520e10018daf6e9001705ac9b5a Mon Sep 17 00:00:00 2001 From: gutsyphilip Date: Wed, 8 Jun 2022 16:21:22 +0400 Subject: [PATCH 36/41] fix: yarn.lock --- packages/frontend/src/translations/en.global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/translations/en.global.json b/packages/frontend/src/translations/en.global.json index bfbb72767a..1d92341802 100644 --- a/packages/frontend/src/translations/en.global.json +++ b/packages/frontend/src/translations/en.global.json @@ -831,11 +831,11 @@ "returnToWallet": "Return to Wallet", "title": "Whoops, looks like this page is missing." }, - "poweredByCoinGecko": "Prices powered by CoinGecko", "postMigration": { "cta": "Transfer my Accounts", "message": "NEAR Wallet has migrated to a new domain: https://mynearwallet.com.
You have until ${endDate} to transfer your accounts, or to export your account keys from your account settings." }, + "poweredByCoinGecko": "Prices powered by CoinGecko", "preMigration": { "calendarEvent": { "details": "This is the official start date of the near wallet migration. Please make sure to start migrating your account as soon as possible.", From 0bfd70831eccae27968b20067548f59a71f51088 Mon Sep 17 00:00:00 2001 From: gutsyphilip Date: Mon, 20 Jun 2022 15:50:01 +0400 Subject: [PATCH 37/41] chore: PR review cleanups --- packages/frontend/src/config/envParsers.js | 2 +- packages/frontend/src/translations/en.global.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/frontend/src/config/envParsers.js b/packages/frontend/src/config/envParsers.js index 3c43747c95..2edf0910b4 100644 --- a/packages/frontend/src/config/envParsers.js +++ b/packages/frontend/src/config/envParsers.js @@ -16,7 +16,7 @@ const parseCommaSeperatedStringAsArrayFromShell = (envVal) => /* Returns a Date object from a unix timestamp string */ const parseDateFromShell = (envVal) => { - return envValIsSet(envVal) ? new Date(envVal * 1000) : undefined; + return envValIsSet(envVal) ? new Date(envVal) : undefined; }; module.exports = { diff --git a/packages/frontend/src/translations/en.global.json b/packages/frontend/src/translations/en.global.json index 1d92341802..07e9dc7979 100644 --- a/packages/frontend/src/translations/en.global.json +++ b/packages/frontend/src/translations/en.global.json @@ -111,9 +111,9 @@ "zeroBalance": { "success": { "desc": "The account has not yet been funded. Purchase $NEAR to perform transactions with the account.", - "title": "Account Imported", + "ledger": "The following account was successfully imported using the Ledger key you provided:", "phrase": "The following account was successfully imported using the passphrase you provided:", - "ledger": "The following account was successfully imported using the Ledger key you provided:" + "title": "Account Imported" } } }, From 197aba3bb748e6b5aa8a9bde00b5d4b4a23a72ae Mon Sep 17 00:00:00 2001 From: gutsyphilip Date: Mon, 20 Jun 2022 15:59:52 +0400 Subject: [PATCH 38/41] fix: add date defaults --- packages/frontend/src/config/configFromEnvironment.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/config/configFromEnvironment.js b/packages/frontend/src/config/configFromEnvironment.js index fb932e1c98..f013687f4a 100644 --- a/packages/frontend/src/config/configFromEnvironment.js +++ b/packages/frontend/src/config/configFromEnvironment.js @@ -45,8 +45,8 @@ module.exports = { ].some((env) => env === NEAR_WALLET_ENV), LINKDROP_GAS: process.env.LINKDROP_GAS, LOCKUP_ACCOUNT_ID_SUFFIX: process.env.LOCKUP_ACCOUNT_ID_SUFFIX, - MIGRATION_START_DATE: parseDateFromShell(process.env.MIGRATION_START_DATE), - MIGRATION_END_DATE: parseDateFromShell(process.env.MIGRATION_END_DATE), + MIGRATION_START_DATE: parseDateFromShell(process.env.MIGRATION_START_DATE || '2022-06-28'), + MIGRATION_END_DATE: parseDateFromShell(process.env.MIGRATION_END_DATE || '2022-08-28'), MIN_BALANCE_FOR_GAS: process.env.REACT_APP_MIN_BALANCE_FOR_GAS, MIN_BALANCE_TO_CREATE: process.env.MIN_BALANCE_TO_CREATE, MOONPAY_API_KEY: process.env.MOONPAY_API_KEY, From 7f92c710d1963feda16ab0a68d6c0540405c2b07 Mon Sep 17 00:00:00 2001 From: Judith Lao Date: Mon, 20 Jun 2022 11:03:56 -0400 Subject: [PATCH 39/41] fix: do not break page when mediaUrl is undefined --- packages/frontend/src/components/nft/NFTMedia.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/components/nft/NFTMedia.js b/packages/frontend/src/components/nft/NFTMedia.js index 38cee59fc3..92e328e6e1 100644 --- a/packages/frontend/src/components/nft/NFTMedia.js +++ b/packages/frontend/src/components/nft/NFTMedia.js @@ -6,8 +6,8 @@ export function NFTMedia({ mediaUrl, autoPlay = false }) { const [isVideo, mimeType] = useMemo(() => { let mimeType; // check mediaUrl string for .webm or .mp4 endings (case-insensitive) - if (mediaUrl.match(/\.webm$/i)) mimeType = "webm"; - else if (mediaUrl.match(/\.mp4$/i)) mimeType = "mp4"; + if (mediaUrl && mediaUrl.match(/\.webm$/i)) mimeType = "webm"; + else if (mediaUrl && mediaUrl.match(/\.mp4$/i)) mimeType = "mp4"; // if there is a mediaUrl and a truthy mimeType (webm or mp4), we have a video const isVideo = !!mediaUrl && mimeType; return [isVideo, mimeType]; From 192eacc54e6465e1d4e16f593a9e79de91d76cf3 Mon Sep 17 00:00:00 2001 From: Judith Lao Date: Mon, 20 Jun 2022 11:05:22 -0400 Subject: [PATCH 40/41] fix: do not break page when mediaUrl is undefined --- packages/frontend/src/components/nft/NFTMedia.js | 10 +++++----- .../frontend/src/hooks/fungibleTokensIncludingNEAR.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/frontend/src/components/nft/NFTMedia.js b/packages/frontend/src/components/nft/NFTMedia.js index 92e328e6e1..f953aa8a61 100644 --- a/packages/frontend/src/components/nft/NFTMedia.js +++ b/packages/frontend/src/components/nft/NFTMedia.js @@ -1,13 +1,13 @@ -import React, { useMemo } from "react"; +import React, { useMemo } from 'react'; -import FailedToLoad from "../../images/failed_to_load.svg"; +import FailedToLoad from '../../images/failed_to_load.svg'; export function NFTMedia({ mediaUrl, autoPlay = false }) { const [isVideo, mimeType] = useMemo(() => { let mimeType; // check mediaUrl string for .webm or .mp4 endings (case-insensitive) - if (mediaUrl && mediaUrl.match(/\.webm$/i)) mimeType = "webm"; - else if (mediaUrl && mediaUrl.match(/\.mp4$/i)) mimeType = "mp4"; + if (mediaUrl && mediaUrl.match(/\.webm$/i)) mimeType = 'webm'; + else if (mediaUrl && mediaUrl.match(/\.mp4$/i)) mimeType = 'mp4'; // if there is a mediaUrl and a truthy mimeType (webm or mp4), we have a video const isVideo = !!mediaUrl && mimeType; return [isVideo, mimeType]; @@ -23,7 +23,7 @@ export function NFTMedia({ mediaUrl, autoPlay = false }) { onError={(e) => { e.target.onerror = null; e.target.parentElement.setAttribute( - "poster", + 'poster', FailedToLoad ); }} diff --git a/packages/frontend/src/hooks/fungibleTokensIncludingNEAR.js b/packages/frontend/src/hooks/fungibleTokensIncludingNEAR.js index 7ab9145cb9..b05fc6613b 100644 --- a/packages/frontend/src/hooks/fungibleTokensIncludingNEAR.js +++ b/packages/frontend/src/hooks/fungibleTokensIncludingNEAR.js @@ -9,7 +9,7 @@ import compare from '../utils/compare'; export const useFungibleTokensIncludingNEAR = function ({ showTokensWithZeroBalance = false, includeNearContractName = false } = {}) { - const NEARAsTokenWithMetadata = useSelector(state => selectNEARAsTokenWithMetadata(state, {includeNearContractName})); + const NEARAsTokenWithMetadata = useSelector((state) => selectNEARAsTokenWithMetadata(state, {includeNearContractName})); const accountId = useSelector(selectActiveAccountId); const fungibleTokens = useSelector((state) => selectTokensWithMetadataForAccountId(state, { accountId, showTokensWithZeroBalance }) From 236799d46f171456eb83ffa869dd3648de26ec4a Mon Sep 17 00:00:00 2001 From: esaminu Date: Mon, 20 Jun 2022 22:12:10 +0400 Subject: [PATCH 41/41] chore: flag migration banner off --- features/features.d.ts | 1 + features/flags.json | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/features/features.d.ts b/features/features.d.ts index 933d5e5c02..78f315b35c 100644 --- a/features/features.d.ts +++ b/features/features.d.ts @@ -4,4 +4,5 @@ export type Features = { CREATE_USN_CONTRACT: boolean; DONATE_TO_UKRAINE: boolean; + SHOW_MIGRATION_BANNER: boolean; }; diff --git a/features/flags.json b/features/flags.json index 69bc1fb252..4043e3d56e 100644 --- a/features/flags.json +++ b/features/flags.json @@ -91,14 +91,14 @@ "createdBy": "gutsyphilip", "createdAt": "2022-05-25T01:11:55.121Z", "development": { - "enabled": true, - "lastEditedBy": "gutsyphilip", - "lastEditedAt": "2022-05-25T01:11:55.121Z" + "enabled": false, + "lastEditedBy": "esaminu", + "lastEditedAt": "2022-06-20T18:11:26.582Z" }, "testnet": { - "enabled": true, - "lastEditedBy": "gutsyphilip", - "lastEditedAt": "2022-05-25T01:11:55.121Z" + "enabled": false, + "lastEditedBy": "esaminu", + "lastEditedAt": "2022-06-20T18:11:26.582Z" }, "mainnet": { "enabled": false, @@ -116,19 +116,19 @@ "lastEditedAt": "2022-05-25T01:11:55.121Z" }, "testnet_NEARORG": { - "enabled": true, - "lastEditedBy": "gutsyphilip", - "lastEditedAt": "2022-05-25T01:11:55.121Z" + "enabled": false, + "lastEditedBy": "esaminu", + "lastEditedAt": "2022-06-20T18:11:26.582Z" }, "mainnet_NEARORG": { - "enabled": true, - "lastEditedBy": "gutsyphilip", - "lastEditedAt": "2022-05-25T01:11:55.121Z" + "enabled": false, + "lastEditedBy": "esaminu", + "lastEditedAt": "2022-06-20T18:11:26.582Z" }, "mainnet_STAGING_NEARORG": { - "enabled": true, - "lastEditedBy": "gutsyphilip", - "lastEditedAt": "2022-05-25T01:11:55.121Z" + "enabled": false, + "lastEditedBy": "esaminu", + "lastEditedAt": "2022-06-20T18:11:26.582Z" } } }