mirror of
https://github.com/zhigang1992/xverse-web-extension.git
synced 2026-04-30 13:42:52 +08:00
[ENG-3496] fix: utxo 500 issue add a fallback option to our api httpsbtc (#738)
* chore: fix for core local dev * feat: use btc esplora api with a fallback url * feat: add fallbackBtcApiUrl to store and refactor network state * fix: types and comments * refactor: clean up local react state in changeNetwork * fix: clean up UI in change network node inputs * chore: bump core version * chore: bump core version * fix: type error from core version * fix: make fallback btc url not a required field, and update styles * fix: remove unused remote btcApiUrl config functionality * chore: not really a factory
This commit is contained in:
77
package-lock.json
generated
77
package-lock.json
generated
@@ -28,6 +28,7 @@
|
||||
"axios": "^1.1.3",
|
||||
"bignumber.js": "^9.1.0",
|
||||
"bip39": "^3.0.3",
|
||||
"buffer": "6.0.3",
|
||||
"c32check": "^2.0.0",
|
||||
"classnames": "^2.3.2",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
@@ -3624,29 +3625,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@zondax/ledger-stacks/node_modules/c32check/node_modules/buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/abab": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
|
||||
@@ -5418,29 +5396,6 @@
|
||||
"buffer": "^5.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-sha256/node_modules/buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
@@ -15829,7 +15784,7 @@
|
||||
"@stacks/profile": "^6.9.0",
|
||||
"@stacks/storage": "^6.9.0",
|
||||
"@stacks/transactions": "^6.9.0",
|
||||
"buffer": "^6.0.3",
|
||||
"buffer": "6.0.3",
|
||||
"c32check": "^2.0.0",
|
||||
"jsontokens": "^4.0.1",
|
||||
"triplesec": "^4.0.3",
|
||||
@@ -17017,7 +16972,7 @@
|
||||
"requires": {
|
||||
"@types/bn.js": "^5.1.0",
|
||||
"@types/node": "^18.0.4",
|
||||
"buffer": "^6.0.3"
|
||||
"buffer": "6.0.3"
|
||||
}
|
||||
},
|
||||
"@stacks/network": {
|
||||
@@ -17061,19 +17016,8 @@
|
||||
"integrity": "sha512-ADADE/PjAbJRlwpG3ShaOMbBUlJJZO7xaYSRD5Tub6PixQlgR4s36y9cvMf/YRGpkqX+QOxIdMw216iC320q9A==",
|
||||
"requires": {
|
||||
"base-x": "^3.0.8",
|
||||
"buffer": "^5.6.0",
|
||||
"buffer": "6.0.3",
|
||||
"cross-sha256": "^1.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"requires": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18453,18 +18397,7 @@
|
||||
"resolved": "https://registry.npmjs.org/cross-sha256/-/cross-sha256-1.2.0.tgz",
|
||||
"integrity": "sha512-KViLNMDZKV7jwFqjFx+rNhG26amnFYYQ0S+VaFlVvpk8tM+2XbFia/don/SjGHg9WQxnFVi6z64CGPuF3T+nNw==",
|
||||
"requires": {
|
||||
"buffer": "^5.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"requires": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
}
|
||||
"buffer": "6.0.3"
|
||||
}
|
||||
},
|
||||
"cross-spawn": {
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"axios": "^1.1.3",
|
||||
"bignumber.js": "^9.1.0",
|
||||
"bip39": "^3.0.3",
|
||||
"buffer": "6.0.3",
|
||||
"c32check": "^2.0.0",
|
||||
"classnames": "^2.3.2",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
@@ -77,8 +78,7 @@
|
||||
"style": "prettier --write \"src/**/*.{ts,tsx}\"",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"resolutions": {
|
||||
"styled-components": "^5",
|
||||
"overrides": {
|
||||
"buffer": "6.0.3"
|
||||
},
|
||||
"lint-staged": {
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
import useWalletSelector from '@hooks/useWalletSelector';
|
||||
import { getAppConfig } from '@secretkeylabs/xverse-core';
|
||||
import { ChangeNetworkAction } from '@stores/wallet/actions/actionCreators';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
const useAppConfig = () => {
|
||||
const { network, networkAddress, btcApiUrl } = useWalletSelector();
|
||||
const dispatch = useDispatch();
|
||||
const { network } = useWalletSelector();
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['app-config', network.type, btcApiUrl],
|
||||
queryKey: ['app-config', network.type],
|
||||
queryFn: async () => {
|
||||
const response = await getAppConfig(network.type);
|
||||
if (response.data.btcApiURL && network.type === 'Mainnet' && !btcApiUrl) {
|
||||
const updatedNetwork = { ...network, btcApiUrl: response.data.btcApiURL };
|
||||
dispatch(ChangeNetworkAction(updatedNetwork, networkAddress, ''));
|
||||
}
|
||||
return response;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,17 +3,18 @@ import { useMemo } from 'react';
|
||||
import useWalletSelector from './useWalletSelector';
|
||||
|
||||
const useBtcClient = () => {
|
||||
const { network, btcApiUrl } = useWalletSelector();
|
||||
const { type, btcApiUrl: remoteBtcApiURL } = network;
|
||||
const url = btcApiUrl || remoteBtcApiURL;
|
||||
const { network } = useWalletSelector();
|
||||
const { type, btcApiUrl, fallbackBtcApiUrl } = network;
|
||||
const url = btcApiUrl;
|
||||
|
||||
const esploraInstance = useMemo(
|
||||
() =>
|
||||
new BitcoinEsploraApiProvider({
|
||||
url,
|
||||
fallbackUrl: fallbackBtcApiUrl,
|
||||
network: type,
|
||||
}),
|
||||
[url, type],
|
||||
[url, fallbackBtcApiUrl, type],
|
||||
);
|
||||
|
||||
return esploraInstance;
|
||||
|
||||
@@ -3,14 +3,14 @@ import { useMemo } from 'react';
|
||||
import useWalletSelector from './useWalletSelector';
|
||||
|
||||
const useNetworkSelector = () => {
|
||||
const { network, networkAddress } = useWalletSelector();
|
||||
const { network } = useWalletSelector();
|
||||
|
||||
const selectedNetwork = useMemo(
|
||||
() =>
|
||||
network.type === 'Mainnet'
|
||||
? new StacksMainnet({ url: networkAddress })
|
||||
: new StacksTestnet({ url: networkAddress }),
|
||||
[network.type, networkAddress],
|
||||
? new StacksMainnet({ url: network.address })
|
||||
: new StacksTestnet({ url: network.address }),
|
||||
[network.type, network.address],
|
||||
);
|
||||
return selectedNetwork;
|
||||
};
|
||||
|
||||
@@ -11,7 +11,9 @@ import {
|
||||
newWallet,
|
||||
restoreWalletWithAccounts,
|
||||
SettingsNetwork,
|
||||
StacksMainnet,
|
||||
StacksNetwork,
|
||||
StacksTestnet,
|
||||
walletFromSeedPhrase,
|
||||
} from '@secretkeylabs/xverse-core';
|
||||
import {
|
||||
@@ -281,14 +283,9 @@ const useWalletReducer = () => {
|
||||
dispatch(fetchAccountAction(account, accountsList));
|
||||
};
|
||||
|
||||
const changeNetwork = async (
|
||||
changedNetwork: SettingsNetwork,
|
||||
networkObject: StacksNetwork,
|
||||
networkAddress: string,
|
||||
btcApiUrl: string,
|
||||
) => {
|
||||
const changeNetwork = async (changedNetwork: SettingsNetwork) => {
|
||||
const seedPhrase = await seedVault.getSeed();
|
||||
dispatch(ChangeNetworkAction(changedNetwork, networkAddress, btcApiUrl));
|
||||
dispatch(ChangeNetworkAction(changedNetwork));
|
||||
const wallet = await walletFromSeedPhrase({
|
||||
mnemonic: seedPhrase,
|
||||
index: 0n,
|
||||
@@ -305,6 +302,10 @@ const useWalletReducer = () => {
|
||||
stxPublicKey: wallet.stxPublicKey,
|
||||
};
|
||||
dispatch(setWalletAction(wallet));
|
||||
const networkObject =
|
||||
changedNetwork.type === 'Mainnet'
|
||||
? new StacksMainnet({ url: changedNetwork.address })
|
||||
: new StacksTestnet({ url: changedNetwork.address });
|
||||
try {
|
||||
await loadActiveAccounts(wallet.seedPhrase, changedNetwork, networkObject, [account]);
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
import Cross from '@assets/img/settings/x.svg';
|
||||
import ActionButton from '@components/button';
|
||||
import BottomBar from '@components/tabBar';
|
||||
import TopRow from '@components/topRow';
|
||||
import useWalletReducer from '@hooks/useWalletReducer';
|
||||
import useWalletSelector from '@hooks/useWalletSelector';
|
||||
import { SettingsNetwork, StacksMainnet, StacksTestnet } from '@secretkeylabs/xverse-core';
|
||||
import { initialNetworksList } from '@utils/constants';
|
||||
import {
|
||||
SettingsNetwork,
|
||||
defaultMainnet,
|
||||
defaultTestnet,
|
||||
initialNetworksList,
|
||||
} from '@secretkeylabs/xverse-core';
|
||||
import { isValidBtcApi, isValidStacksApi } from '@utils/helper';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import NetworkRow from './networkRow';
|
||||
import NodeInput from './nodeInput';
|
||||
|
||||
const Container = styled.div`
|
||||
${(props) => props.theme.typography.body_medium_m}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
@@ -26,180 +31,155 @@ const Container = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const NodeInputHeader = styled.div((props) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingLeft: props.theme.spacing(1),
|
||||
paddingRight: props.theme.spacing(1),
|
||||
}));
|
||||
const ButtonContainer = styled.div`
|
||||
margin: ${(props) => props.theme.space.m};
|
||||
`;
|
||||
|
||||
const NodeText = styled.h1((props) => ({
|
||||
...props.theme.body_medium_m,
|
||||
marginTop: props.theme.spacing(6),
|
||||
}));
|
||||
const NodeInputsContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${(props) => props.theme.space.s};
|
||||
margin-top: ${(props) => props.theme.space.s};
|
||||
`;
|
||||
|
||||
const NodeResetButton = styled.button((props) => ({
|
||||
background: 'none',
|
||||
color: props.theme.colors.action.classicLight,
|
||||
}));
|
||||
type NodeInputKey = keyof Pick<SettingsNetwork, 'address' | 'btcApiUrl' | 'fallbackBtcApiUrl'>;
|
||||
const nodeInputs: { key: NodeInputKey; labelKey: string }[] = [
|
||||
{ key: 'address', labelKey: 'STACKS_URL' },
|
||||
{ key: 'btcApiUrl', labelKey: 'BTC_URL' },
|
||||
{ key: 'fallbackBtcApiUrl', labelKey: 'FALLBACK_BTC_URL' },
|
||||
];
|
||||
|
||||
const InputContainer = styled.div((props) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
border: `1px solid ${props.theme.colors.elevation3}`,
|
||||
backgroundColor: props.theme.colors.elevation_n1,
|
||||
borderRadius: props.theme.radius(1),
|
||||
paddingLeft: props.theme.spacing(4),
|
||||
paddingRight: props.theme.spacing(4),
|
||||
marginTop: props.theme.spacing(4),
|
||||
marginBottom: props.theme.spacing(3),
|
||||
}));
|
||||
|
||||
const ButtonContainer = styled.div((props) => ({
|
||||
marginLeft: props.theme.spacing(8),
|
||||
marginRight: props.theme.spacing(8),
|
||||
marginBottom: props.theme.spacing(16),
|
||||
}));
|
||||
|
||||
const ErrorMessage = styled.h2((props) => ({
|
||||
...props.theme.body_medium_m,
|
||||
textAlign: 'left',
|
||||
color: props.theme.colors.feedback.error,
|
||||
}));
|
||||
|
||||
const Input = styled.input((props) => ({
|
||||
...props.theme.body_medium_m,
|
||||
height: 44,
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
backgroundColor: props.theme.colors.elevation_n1,
|
||||
color: props.theme.colors.white_0,
|
||||
border: 'none',
|
||||
}));
|
||||
|
||||
const Button = styled.button({
|
||||
background: 'none',
|
||||
});
|
||||
type NodeInputErrors = Record<NodeInputKey, string>;
|
||||
const initialNodeErrors: NodeInputErrors = {
|
||||
address: '',
|
||||
btcApiUrl: '',
|
||||
fallbackBtcApiUrl: '',
|
||||
};
|
||||
|
||||
function ChangeNetworkScreen() {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'SETTING_SCREEN' });
|
||||
const { network, btcApiUrl, networkAddress } = useWalletSelector();
|
||||
const [changedNetwork, setChangedNetwork] = useState<SettingsNetwork>(network);
|
||||
const [stacksUrlError, setStacksUrlError] = useState<string>('');
|
||||
const [btcURLError, setBtcURLError] = useState('');
|
||||
const [btcUrl, setBtcUrl] = useState(btcApiUrl || network.btcApiUrl);
|
||||
const [stacksUrl, setStacksUrl] = useState<string>(networkAddress || network.address);
|
||||
const [isChangingNetwork, setIsChangingNetwork] = useState<boolean>(false);
|
||||
const navigate = useNavigate();
|
||||
const { changeNetwork } = useWalletReducer();
|
||||
const { network, savedNetworks } = useWalletSelector();
|
||||
const [isChangingNetwork, setIsChangingNetwork] = useState(false);
|
||||
const [formErrors, setFormErrors] = useState<NodeInputErrors>(initialNodeErrors);
|
||||
const [formInputs, setFormInputs] = useState<SettingsNetwork>(network);
|
||||
|
||||
const handleBackButtonClick = () => {
|
||||
navigate('/settings');
|
||||
};
|
||||
|
||||
const onNetworkSelected = (networkSelected: SettingsNetwork) => {
|
||||
setStacksUrl(networkSelected.address);
|
||||
setChangedNetwork(networkSelected);
|
||||
setBtcUrl(networkSelected.btcApiUrl);
|
||||
setStacksUrlError('');
|
||||
setBtcURLError('');
|
||||
setFormInputs(networkSelected);
|
||||
setFormErrors(initialNodeErrors);
|
||||
};
|
||||
|
||||
const onChangeStacksUrl = (event: React.FormEvent<HTMLInputElement>) => {
|
||||
setStacksUrlError('');
|
||||
setStacksUrl(event.currentTarget.value);
|
||||
// TODO should validate required fields on change
|
||||
const onChangeCreator = (key: NodeInputKey) => (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFormErrors((prevErrors) => ({
|
||||
...prevErrors,
|
||||
[key]: '',
|
||||
}));
|
||||
setFormInputs((prevInputs) => ({
|
||||
...prevInputs,
|
||||
[key]: event.target.value,
|
||||
}));
|
||||
};
|
||||
|
||||
const onChangeBtcApiUrl = (event: React.FormEvent<HTMLInputElement>) => {
|
||||
setBtcURLError('');
|
||||
setBtcUrl(event.currentTarget.value);
|
||||
const onClearCreator = (key: NodeInputKey) => () => {
|
||||
setFormErrors((prevErrors) => ({
|
||||
...prevErrors,
|
||||
[key]: '',
|
||||
}));
|
||||
setFormInputs((prevInputs) => ({
|
||||
...prevInputs,
|
||||
[key]: '',
|
||||
}));
|
||||
};
|
||||
|
||||
const onClearStacksUrl = () => {
|
||||
setStacksUrl('');
|
||||
};
|
||||
|
||||
const onClearBtcUrl = () => {
|
||||
setBtcUrl('');
|
||||
};
|
||||
|
||||
const onResetBtcUrl = async () => {
|
||||
setBtcUrl(changedNetwork.btcApiUrl);
|
||||
setBtcURLError('');
|
||||
};
|
||||
|
||||
const onResetStacks = async () => {
|
||||
setStacksUrl(changedNetwork.address);
|
||||
setStacksUrlError('');
|
||||
const onResetCreator = (key: NodeInputKey) => () => {
|
||||
setFormErrors((prevErrors) => ({
|
||||
...prevErrors,
|
||||
[key]: '',
|
||||
}));
|
||||
setFormInputs((prevInputs) => ({
|
||||
...prevInputs,
|
||||
[key]: initialNetworksList.find((n) => n.type === formInputs.type)?.[key],
|
||||
}));
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
setIsChangingNetwork(true);
|
||||
|
||||
const [isValidStacksUrl, isValidBtcApiUrl] = await Promise.all([
|
||||
isValidStacksApi(stacksUrl, changedNetwork.type),
|
||||
isValidBtcApi(btcUrl, changedNetwork.type),
|
||||
if (!formInputs.address) {
|
||||
setFormErrors((prevErrors) => ({
|
||||
...prevErrors,
|
||||
address: t('REQUIRED'),
|
||||
}));
|
||||
setIsChangingNetwork(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formInputs.btcApiUrl) {
|
||||
setFormErrors((prevErrors) => ({
|
||||
...prevErrors,
|
||||
btcApiUrl: t('REQUIRED'),
|
||||
}));
|
||||
setIsChangingNetwork(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const [isValidStacksUrl, isValidBtcApiUrl, isValidFallbackBtcApiUrl] = await Promise.all([
|
||||
isValidStacksApi(formInputs.address, formInputs.type),
|
||||
isValidBtcApi(formInputs.btcApiUrl, formInputs.type),
|
||||
!formInputs.fallbackBtcApiUrl || isValidBtcApi(formInputs.fallbackBtcApiUrl, formInputs.type),
|
||||
]);
|
||||
|
||||
if (isValidStacksUrl && isValidBtcApiUrl) {
|
||||
const networkObject =
|
||||
changedNetwork.type === 'Mainnet'
|
||||
? new StacksMainnet({ url: stacksUrl })
|
||||
: new StacksTestnet({ url: stacksUrl });
|
||||
await changeNetwork(changedNetwork, networkObject, stacksUrl, btcUrl);
|
||||
if (isValidStacksUrl && isValidBtcApiUrl && isValidFallbackBtcApiUrl) {
|
||||
await changeNetwork(formInputs);
|
||||
navigate('/settings');
|
||||
} else {
|
||||
if (!isValidStacksUrl) {
|
||||
setStacksUrlError(t('INVALID_URL'));
|
||||
}
|
||||
if (!isValidBtcApiUrl) {
|
||||
setBtcURLError(t('INVALID_URL'));
|
||||
}
|
||||
setFormErrors({
|
||||
address: !isValidStacksUrl ? t('INVALID_URL') : '',
|
||||
btcApiUrl: !isValidBtcApiUrl ? t('INVALID_URL') : '',
|
||||
fallbackBtcApiUrl: !isValidFallbackBtcApiUrl ? t('INVALID_URL') : '',
|
||||
});
|
||||
setIsChangingNetwork(false);
|
||||
}
|
||||
};
|
||||
|
||||
const savedMainnet = savedNetworks.find((n) => n.type === 'Mainnet');
|
||||
const savedTestnet = savedNetworks.find((n) => n.type === 'Testnet');
|
||||
|
||||
return (
|
||||
<>
|
||||
<TopRow title={t('NETWORK')} onClick={handleBackButtonClick} />
|
||||
<Container>
|
||||
<NetworkRow
|
||||
network={initialNetworksList[0]}
|
||||
isSelected={changedNetwork.type === 'Mainnet'}
|
||||
network={savedMainnet || defaultMainnet}
|
||||
isSelected={formInputs.type === 'Mainnet'}
|
||||
onNetworkSelected={onNetworkSelected}
|
||||
showDivider
|
||||
/>
|
||||
<NetworkRow
|
||||
network={initialNetworksList[1]}
|
||||
isSelected={changedNetwork.type === 'Testnet'}
|
||||
network={savedTestnet || defaultTestnet}
|
||||
isSelected={formInputs.type === 'Testnet'}
|
||||
onNetworkSelected={onNetworkSelected}
|
||||
showDivider={false}
|
||||
/>
|
||||
<NodeInputHeader>
|
||||
<NodeText>{t('NODE')}</NodeText>
|
||||
<NodeResetButton onClick={onResetStacks}>Reset URL</NodeResetButton>
|
||||
</NodeInputHeader>
|
||||
<InputContainer>
|
||||
<Input onChange={onChangeStacksUrl} value={stacksUrl} />
|
||||
<Button onClick={onClearStacksUrl}>
|
||||
<img width={22} height={22} src={Cross} alt="cross" />
|
||||
</Button>
|
||||
</InputContainer>
|
||||
<ErrorMessage>{stacksUrlError}</ErrorMessage>
|
||||
<NodeInputHeader>
|
||||
<NodeText>BTC API URL</NodeText>
|
||||
<NodeResetButton onClick={onResetBtcUrl}>Reset URL</NodeResetButton>
|
||||
</NodeInputHeader>
|
||||
<InputContainer>
|
||||
<Input onChange={onChangeBtcApiUrl} value={btcUrl} />
|
||||
<Button onClick={onClearBtcUrl}>
|
||||
<img width={22} height={22} src={Cross} alt="cross" />
|
||||
</Button>
|
||||
</InputContainer>
|
||||
<ErrorMessage>{btcURLError}</ErrorMessage>
|
||||
<NodeInputsContainer>
|
||||
{nodeInputs.map(({ key, labelKey }) => (
|
||||
<NodeInput
|
||||
key={key}
|
||||
label={t(labelKey)}
|
||||
onChange={onChangeCreator(key)}
|
||||
value={formInputs[key]}
|
||||
onClear={onClearCreator(key)}
|
||||
onReset={onResetCreator(key)}
|
||||
error={formErrors[key]}
|
||||
/>
|
||||
))}
|
||||
</NodeInputsContainer>
|
||||
</Container>
|
||||
<ButtonContainer>
|
||||
<ActionButton
|
||||
@@ -209,7 +189,6 @@ function ChangeNetworkScreen() {
|
||||
disabled={isChangingNetwork}
|
||||
/>
|
||||
</ButtonContainer>
|
||||
|
||||
<BottomBar tab="settings" />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -20,7 +20,7 @@ const Button = styled.button<ButtonProps>((props) => ({
|
||||
}));
|
||||
|
||||
const Text = styled.h1<TitleProps>((props) => ({
|
||||
...props.theme.body_medium_m,
|
||||
...props.theme.typography.body_medium_m,
|
||||
color: props.color,
|
||||
flex: 1,
|
||||
textAlign: 'left',
|
||||
|
||||
90
src/app/screens/settings/changeNetwork/nodeInput.tsx
Normal file
90
src/app/screens/settings/changeNetwork/nodeInput.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import { XCircle } from '@phosphor-icons/react';
|
||||
import InputFeedback from '@ui-library/inputFeedback';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import styled, { useTheme } from 'styled-components';
|
||||
|
||||
const NodeInputHeader = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
padding-left: ${(props) => props.theme.spacing(1)};
|
||||
padding-right: ${(props) => props.theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const NodeText = styled.label`
|
||||
${(props) => props.theme.typography.body_medium_m}
|
||||
color: ${(props) => props.theme.colors.white_200};
|
||||
`;
|
||||
|
||||
const NodeResetButton = styled.button`
|
||||
${(props) => props.theme.typography.body_medium_m}
|
||||
background: none;
|
||||
color: ${(props) => props.theme.colors.white_200};
|
||||
`;
|
||||
|
||||
// TODO create and use a ui-library input with proper input box styling
|
||||
const InputContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
border: 1px solid ${(props) => props.theme.colors.elevation3};
|
||||
background-color: ${(props) => props.theme.colors.elevation_n1};
|
||||
border-radius: ${(props) => props.theme.radius(1)}px;
|
||||
padding-left: ${(props) => props.theme.space.m};
|
||||
padding-right: ${(props) => props.theme.space.m};
|
||||
margin-top: ${(props) => props.theme.space.s};
|
||||
margin-bottom: ${(props) => props.theme.space.s};
|
||||
`;
|
||||
|
||||
const Input = styled.input`
|
||||
${(props) => props.theme.typography.body_medium_m}
|
||||
height: 44px;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
background-color: ${(props) => props.theme.colors.elevation_n1};
|
||||
color: ${(props) => props.theme.colors.white_200};
|
||||
border: none;
|
||||
`;
|
||||
|
||||
const Button = styled.button`
|
||||
background: none;
|
||||
`;
|
||||
|
||||
function NodeInput({
|
||||
label,
|
||||
onChange,
|
||||
value,
|
||||
onClear,
|
||||
onReset,
|
||||
error,
|
||||
}: {
|
||||
label: string;
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
value: string;
|
||||
onClear: () => void;
|
||||
onReset: () => void;
|
||||
error: string;
|
||||
}) {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'SETTING_SCREEN' });
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<NodeInputHeader>
|
||||
<NodeText>{label}</NodeText>
|
||||
<NodeResetButton onClick={onReset}>{t('RESET_TO_DEFAULT')}</NodeResetButton>
|
||||
</NodeInputHeader>
|
||||
<InputContainer>
|
||||
<Input onChange={onChange} value={value} />
|
||||
<Button onClick={onClear}>
|
||||
<XCircle size={18} weight="fill" color={theme.colors.white_200} />
|
||||
</Button>
|
||||
</InputContainer>
|
||||
<InputFeedback variant="danger" message={error} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default NodeInput;
|
||||
@@ -176,16 +176,10 @@ export function ChangeFiatCurrencyAction(
|
||||
};
|
||||
}
|
||||
|
||||
export function ChangeNetworkAction(
|
||||
network: SettingsNetwork,
|
||||
networkAddress: string | undefined,
|
||||
btcApiUrl: string,
|
||||
): actions.ChangeNetwork {
|
||||
export function ChangeNetworkAction(network: SettingsNetwork): actions.ChangeNetwork {
|
||||
return {
|
||||
type: actions.ChangeNetworkKey,
|
||||
network,
|
||||
networkAddress,
|
||||
btcApiUrl,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -21,30 +21,21 @@ export const SetFeeMultiplierKey = 'SetFeeMultiplierKey';
|
||||
export const ChangeFiatCurrencyKey = 'ChangeFiatCurrency';
|
||||
export const ChangeNetworkKey = 'ChangeNetwork';
|
||||
export const GetActiveAccountsKey = 'GetActiveAccounts';
|
||||
|
||||
export const FetchStxWalletDataRequestKey = 'FetchStxWalletDataRequest';
|
||||
export const SetStxWalletDataKey = 'SetStxWalletDataKey';
|
||||
|
||||
export const SetBtcWalletDataKey = 'SetBtcWalletData';
|
||||
|
||||
export const SetCoinRatesKey = 'SetCoinRatesKey';
|
||||
|
||||
export const SetCoinDataKey = 'SetCoinDataKey';
|
||||
|
||||
export const ChangeHasActivatedOrdinalsKey = 'ChangeHasActivatedOrdinalsKey';
|
||||
export const RareSatsNoticeDismissedKey = 'RareSatsNoticeDismissedKey';
|
||||
export const ChangeHasActivatedRareSatsKey = 'ChangeHasActivatedRareSatsKey';
|
||||
export const ChangeHasActivatedRBFKey = 'ChangeHasActivatedRBFKey';
|
||||
|
||||
export const ChangeShowBtcReceiveAlertKey = 'ChangeShowBtcReceiveAlertKey';
|
||||
export const ChangeShowOrdinalReceiveAlertKey = 'ChangeShowOrdinalReceiveAlertKey';
|
||||
export const ChangeShowDataCollectionAlertKey = 'ChangeShowDataCollectionAlertKey';
|
||||
export const UpdateLedgerAccountsKey = 'UpdateLedgerAccountsKey';
|
||||
|
||||
export const SetBrcCoinsListKey = 'SetBrcCoinsList';
|
||||
|
||||
export const SetWalletLockPeriodKey = 'SetWalletLockPeriod';
|
||||
|
||||
export const SetWalletUnlockedKey = 'SetWalletUnlocked';
|
||||
|
||||
export enum WalletSessionPeriods {
|
||||
@@ -64,7 +55,8 @@ export interface WalletState {
|
||||
accountsList: Account[];
|
||||
ledgerAccountsList: Account[];
|
||||
selectedAccount: Account | null;
|
||||
network: SettingsNetwork;
|
||||
network: SettingsNetwork; // currently selected network urls and type
|
||||
savedNetworks: SettingsNetwork[]; // previously set network urls for type
|
||||
encryptedSeed: string;
|
||||
fiatCurrency: SupportedCurrency;
|
||||
btcFiatRate: string;
|
||||
@@ -78,7 +70,6 @@ export interface WalletState {
|
||||
coins: Coin[];
|
||||
brcCoinsList: FungibleToken[] | null;
|
||||
feeMultipliers: AppInfo | null;
|
||||
networkAddress: string | undefined;
|
||||
hasActivatedOrdinalsKey: boolean | undefined;
|
||||
hasActivatedRareSatsKey: boolean | undefined;
|
||||
hasActivatedRBFKey: boolean | undefined;
|
||||
@@ -88,7 +79,6 @@ export interface WalletState {
|
||||
showDataCollectionAlert: boolean | null;
|
||||
accountType: AccountType | undefined;
|
||||
accountName: string | undefined;
|
||||
btcApiUrl: string;
|
||||
walletLockPeriod: WalletSessionPeriods;
|
||||
isUnlocked: boolean;
|
||||
}
|
||||
@@ -178,8 +168,6 @@ export interface ChangeFiatCurrency {
|
||||
export interface ChangeNetwork {
|
||||
type: typeof ChangeNetworkKey;
|
||||
network: SettingsNetwork;
|
||||
networkAddress: string | undefined;
|
||||
btcApiUrl: string;
|
||||
}
|
||||
|
||||
export interface GetActiveAccounts {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { initialNetworksList } from '@utils/constants';
|
||||
import { defaultMainnet, initialNetworksList } from '@secretkeylabs/xverse-core';
|
||||
import {
|
||||
AddAccountKey,
|
||||
ChangeFiatCurrencyKey,
|
||||
ChangeHasActivatedOrdinalsKey,
|
||||
ChangeHasActivatedRareSatsKey,
|
||||
ChangeHasActivatedRBFKey,
|
||||
ChangeHasActivatedRareSatsKey,
|
||||
ChangeNetworkKey,
|
||||
ChangeShowBtcReceiveAlertKey,
|
||||
ChangeShowDataCollectionAlertKey,
|
||||
@@ -70,7 +70,8 @@ const initialWalletState: WalletState = {
|
||||
stxPublicKey: '',
|
||||
btcPublicKey: '',
|
||||
ordinalsPublicKey: '',
|
||||
network: initialNetworksList[0],
|
||||
network: { ...defaultMainnet },
|
||||
savedNetworks: initialNetworksList,
|
||||
accountsList: [],
|
||||
ledgerAccountsList: [],
|
||||
selectedAccount: null,
|
||||
@@ -87,8 +88,6 @@ const initialWalletState: WalletState = {
|
||||
coins: [],
|
||||
brcCoinsList: [],
|
||||
feeMultipliers: null,
|
||||
networkAddress: undefined,
|
||||
btcApiUrl: '',
|
||||
hasActivatedOrdinalsKey: undefined,
|
||||
hasActivatedRareSatsKey: undefined,
|
||||
hasActivatedRBFKey: true,
|
||||
@@ -204,8 +203,10 @@ const walletReducer = (
|
||||
return {
|
||||
...state,
|
||||
network: action.network,
|
||||
networkAddress: action.networkAddress,
|
||||
btcApiUrl: action.btcApiUrl,
|
||||
savedNetworks: [
|
||||
...state.savedNetworks.filter((n) => n.type !== action.network.type),
|
||||
action.network,
|
||||
],
|
||||
};
|
||||
case GetActiveAccountsKey:
|
||||
return {
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
/* eslint-disable prefer-destructuring */
|
||||
import type { NetworkType, SettingsNetwork } from '@secretkeylabs/xverse-core';
|
||||
import {
|
||||
BTC_BASE_URI_MAINNET,
|
||||
BTC_BASE_URI_TESTNET,
|
||||
HIRO_MAINNET_DEFAULT,
|
||||
HIRO_TESTNET_DEFAULT,
|
||||
} from '@secretkeylabs/xverse-core';
|
||||
import type { NetworkType } from '@secretkeylabs/xverse-core';
|
||||
|
||||
export const GAMMA_URL = 'https://gamma.io/';
|
||||
export const TERMS_LINK = 'https://xverse.app/terms';
|
||||
@@ -47,21 +41,6 @@ export const BITCOIN_DUST_AMOUNT_SATS = 1500;
|
||||
export const PAGINATION_LIMIT = 50;
|
||||
export const REFETCH_UNSPENT_UTXO_TIME = 2 * 60 * 60 * 1000;
|
||||
|
||||
export const initialNetworksList: SettingsNetwork[] = [
|
||||
{
|
||||
type: 'Mainnet',
|
||||
address: HIRO_MAINNET_DEFAULT,
|
||||
btcApiUrl: BTC_BASE_URI_MAINNET,
|
||||
fallbackBtcApiUrl: '',
|
||||
},
|
||||
{
|
||||
type: 'Testnet',
|
||||
address: HIRO_TESTNET_DEFAULT,
|
||||
btcApiUrl: BTC_BASE_URI_TESTNET,
|
||||
fallbackBtcApiUrl: '',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* contract id of send_many transaction type
|
||||
*/
|
||||
|
||||
@@ -711,6 +711,11 @@
|
||||
"DESCRIPTION": "Help improve the app experience, by allowing Xverse to collect anonymized usage data. This data cannot be used to identify your wallet individually.",
|
||||
"AUTHORIZE_DATA_COLLECTION": "Authorize data collection"
|
||||
},
|
||||
"BTC_URL": "BTC URL",
|
||||
"STACKS_URL": "Stacks URL",
|
||||
"FALLBACK_BTC_URL": "Fallback BTC URL",
|
||||
"RESET_TO_DEFAULT": "Reset to default",
|
||||
"REQUIRED": "Required",
|
||||
"NETWORK": "Network",
|
||||
"SECURITY": "Security",
|
||||
"UPDATE_PASSWORD": "Update Password",
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
"isolatedModules": true,
|
||||
"noEmit": false,
|
||||
"jsx": "react-jsx",
|
||||
"rootDir": "src",
|
||||
"outDir": "build/js",
|
||||
"baseUrl": "./src",
|
||||
"paths": {
|
||||
|
||||
Reference in New Issue
Block a user