From e0b82f741f54307b827f75a0dca863866b873533 Mon Sep 17 00:00:00 2001 From: samsiegart Date: Thu, 18 Jan 2024 20:34:16 -0800 Subject: [PATCH 1/4] feat: add node selectors to connection settings --- .../components/ConnectionSettingsDialog.tsx | 122 +++++++++++++++++- wallet/src/contexts/Provider.tsx | 4 +- wallet/src/util/SuggestChain.ts | 35 +++-- wallet/src/util/connections.ts | 7 + yarn.lock | 16 ++- 5 files changed, 164 insertions(+), 20 deletions(-) diff --git a/wallet/src/components/ConnectionSettingsDialog.tsx b/wallet/src/components/ConnectionSettingsDialog.tsx index 99f90b2d..88877b47 100644 --- a/wallet/src/components/ConnectionSettingsDialog.tsx +++ b/wallet/src/components/ConnectionSettingsDialog.tsx @@ -8,9 +8,13 @@ import DialogContent from '@mui/material/DialogContent'; import DialogTitle from '@mui/material/DialogTitle'; import TextField from '@mui/material/TextField'; import { makeStyles } from '@mui/styles'; -import { useMemo, useState } from 'react'; +import { useMemo, useState, useEffect } from 'react'; import { withApplicationContext } from '../contexts/Application'; -import { NetworkConfigSource, networkConfigUrl } from '../util/connections'; +import { + ConnectionConfig, + NetworkConfigSource, + networkConfigUrl, +} from '../util/connections'; import isEqual from 'lodash-es/isEqual'; import { maybeSave } from '../util/storage'; @@ -43,6 +47,43 @@ const ErrorLabel = ({ children }) => { ); }; +const useNodeSuggestions = (networkConfigHref: string) => { + const [apiSuggestions, setApiSuggestions] = useState([]); + const [rpcSuggestions, setRpcSuggestions] = useState([]); + + useEffect(() => { + let didConfigChange = false; + setRpcSuggestions([]); + setApiSuggestions([]); + + const updateSuggestions = async () => { + const url = new URL(networkConfigHref); + const res = await fetch(url); + if (!res.ok) { + throw Error(`Cannot fetch network: ${res.status}`); + } + const networkConfig = await res.json(); + + if (didConfigChange) return; + setApiSuggestions(networkConfig.apiAddrs); + setRpcSuggestions(networkConfig.rpcAddrs); + }; + + updateSuggestions().catch(() => + console.error( + 'error fetching node suggestions from config', + networkConfigHref, + ), + ); + + return () => { + didConfigChange = true; + }; + }, [networkConfigHref]); + + return { apiSuggestions, rpcSuggestions }; +}; + const ConnectionSettingsDialog = ({ onClose, open, @@ -60,10 +101,16 @@ const ConnectionSettingsDialog = ({ networkConfigUrl.toSource(connectionConfig.href), ); - const [config, setConfig] = useState( - connectionConfig || { href: networkConfigUrl.fromSource(configSource) }, + const [config, setConfig] = useState( + connectionConfig || { + href: networkConfigUrl.fromSource(configSource), + rpc: undefined, + api: undefined, + }, ); + const { apiSuggestions, rpcSuggestions } = useNodeSuggestions(config.href); + const errors = new Set(); try { @@ -93,10 +140,10 @@ const ConnectionSettingsDialog = ({ } setConnectionConfig(config); disconnect(true); - const { href } = config; + const { href, rpc, api } = config; const isKnown = allConnectionConfigs.some(c => c.href === href); if (!isKnown) { - setAllConnectionConfigs(conns => [{ href }, ...conns]); + setAllConnectionConfigs(conns => [{ href, rpc, api }, ...conns]); } } onClose(); @@ -119,11 +166,13 @@ const ConnectionSettingsDialog = ({ case 'testnet': case 'devnet': setConfig({ + ...config, href: `https://${value}.agoric.net/network-config`, }); break; case 'localhost': setConfig({ + ...config, href: `${window.location.origin}/wallet/network-config`, }); break; @@ -149,7 +198,9 @@ const ConnectionSettingsDialog = ({ options={smartConnectionHrefs} sx={{ width: 360, mt: 2 }} onChange={(_, newValue) => + newValue && setConfig({ + ...config, href: newValue, }) } @@ -164,12 +215,71 @@ const ConnectionSettingsDialog = ({ onChange={ev => ev.target.value !== config.href && setConfig({ + ...config, href: ev.target.value, }) } /> )} /> + + setConfig({ + ...config, + rpc: newValue ?? undefined, + }) + } + renderOption={(props, option) =>
  • {option}
  • } + freeSolo + selectOnFocus + handleHomeEndKeys + renderInput={params => ( + + ev.target.value !== config.rpc && + setConfig({ + ...config, + rpc: ev.target.value, + }) + } + /> + )} + /> + + setConfig({ + ...config, + api: newValue ?? undefined, + }) + } + renderOption={(props, option) =>
  • {option}
  • } + freeSolo + selectOnFocus + handleHomeEndKeys + renderInput={params => ( + + ev.target.value !== config.api && + setConfig({ + ...config, + api: ev.target.value, + }) + } + /> + )} + /> {errors.has(Errors.INVALID_URL) ? 'Enter a valid URL' : ''} diff --git a/wallet/src/contexts/Provider.tsx b/wallet/src/contexts/Provider.tsx index 7c82ab32..00df603a 100644 --- a/wallet/src/contexts/Provider.tsx +++ b/wallet/src/contexts/Provider.tsx @@ -33,6 +33,7 @@ export type KeplrUtils = { interactiveSigner: InteractiveSigner; backgroundSigner: BackgroundSigner; }; + rpc: string; }; const useDebugLogging = (state, watch) => { @@ -212,7 +213,7 @@ const Provider = ({ children }) => { assert(keplr, 'Missing window.keplr'); const { getBytes } = Random; - const chainInfo = await suggestChain(connectionConfig.href, { + const chainInfo = await suggestChain(connectionConfig, { fetch, keplr, random: Math.random, @@ -236,7 +237,6 @@ const Provider = ({ children }) => { address: accounts[0]?.address, signers: { interactiveSigner, backgroundSigner }, chainId: chainInfo.chainId, - // @ts-expect-error used? rpc: chainInfo.rpc, }); }; diff --git a/wallet/src/util/SuggestChain.ts b/wallet/src/util/SuggestChain.ts index 6b0d3ac8..05032f0f 100644 --- a/wallet/src/util/SuggestChain.ts +++ b/wallet/src/util/SuggestChain.ts @@ -1,6 +1,7 @@ import type { NetworkConfig } from '@agoric/casting/src/netconfig'; import type { ChainInfo, Keplr } from '@keplr-wallet/types'; import { bech32Config, stableCurrency, stakeCurrency } from './chainInfo'; +import { ConnectionConfig } from './connections'; export const AGORIC_COIN_TYPE = 564; export const COSMOS_COIN_TYPE = 118; @@ -10,18 +11,29 @@ export const makeChainInfo = ( caption: string, randomFloat: number, walletUrlForStaking?: string, + config?: ConnectionConfig, ): ChainInfo => { const { chainName, rpcAddrs, apiAddrs } = networkConfig; - const index = Math.floor(randomFloat * rpcAddrs.length); + const rpcIndex = Math.floor(randomFloat * rpcAddrs.length); - const rpcAddr = rpcAddrs[index]; - const rpc = rpcAddr.match(/:\/\//) ? rpcAddr : `http://${rpcAddr}`; + let rpc: string; + if (config?.rpc) { + rpc = config.rpc; + } else { + const rpcAddr = rpcAddrs[rpcIndex]; + rpc = rpcAddr.match(/:\/\//) ? rpcAddr : `http://${rpcAddr}`; + } - const rest = apiAddrs - ? // pick the same index - apiAddrs[index] - : // adapt from rpc - rpc.replace(/(:\d+)?$/, ':1317'); + let rest: string; + if (config?.api) { + rest = config.api; + } else { + rest = apiAddrs + ? // pick the same index as rpc node + apiAddrs[rpcIndex] + : // adapt from rpc + rpc.replace(/(:\d+)?$/, ':1317'); + } return { rpc, @@ -41,7 +53,7 @@ export const makeChainInfo = ( }; export async function suggestChain( - networkConfigHref: string, + config: ConnectionConfig, { fetch, keplr, @@ -53,8 +65,8 @@ export async function suggestChain( }, caption?: string, ) { - console.log('suggestChain: fetch', networkConfigHref); // log net IO - const url = new URL(networkConfigHref); + console.log('suggestChain: fetch', config.href); // log net IO + const url = new URL(config.href); const res = await fetch(url); if (!res.ok) { throw Error(`Cannot fetch network: ${res.status}`); @@ -76,6 +88,7 @@ export async function suggestChain( caption, random(), walletUrlForStaking, + config, ); console.log('chainInfo', chainInfo); await keplr.experimentalSuggestChain(chainInfo); diff --git a/wallet/src/util/connections.ts b/wallet/src/util/connections.ts index 6bc30a7e..34c91b89 100644 --- a/wallet/src/util/connections.ts +++ b/wallet/src/util/connections.ts @@ -26,3 +26,10 @@ export const networkConfigUrl = { }; export type NetworkConfigSource = ReturnType; + +export type ConnectionConfig = { + href: string; + api?: string; + rpc?: string; + accessToken?: string; +}; diff --git a/yarn.lock b/yarn.lock index 79689ea5..7c98a1e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -108,13 +108,27 @@ "@endo/promise-kit" "^0.2.56" node-fetch "^2.6.0" -"@agoric/cosmic-proto@0.2.2-dev-a5437cf.0", "@agoric/cosmic-proto@0.3.1-dev-ff36f02.0+ff36f02", "@agoric/cosmic-proto@^0.3.0": +"@agoric/cosmic-proto@0.2.2-dev-a5437cf.0": version "0.2.2-dev-a5437cf.0" resolved "https://registry.yarnpkg.com/@agoric/cosmic-proto/-/cosmic-proto-0.2.2-dev-a5437cf.0.tgz#cba81f0455ba1875d3f389b69d93e36d5569bfb3" integrity sha512-ocwgjLUJcuKeDP7/RHWQXDqpDDCEEBqsX1JQNjmnkljE5dNkUBiDNMXurzmD+SZJqGDZ1HxaQFfF/9zo99q4vA== dependencies: protobufjs "^7.0.0" +"@agoric/cosmic-proto@0.3.1-dev-ff36f02.0+ff36f02": + version "0.3.1-dev-ff36f02.0" + resolved "https://registry.yarnpkg.com/@agoric/cosmic-proto/-/cosmic-proto-0.3.1-dev-ff36f02.0.tgz#f6d6cf21b62e4fffbbc3c5634d3754d17d793f70" + integrity sha512-y266YxgI6ZN8UxqDezFjfcHTTOlo9FhFM22cN9EQVah0Z4FbNzXplYwrNRZZljGvtbb33w+oZNfv89fZ2aB5Gw== + dependencies: + protobufjs "^7.0.0" + +"@agoric/cosmic-proto@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@agoric/cosmic-proto/-/cosmic-proto-0.3.0.tgz#c9d31d3946c91fbb1630f89d8ba63a662bcdacc5" + integrity sha512-cIunby6gs53sGkHx3ALraREbfVQXvsIcObMjQQ0/tZt5HVqwoS7Y1Qj1Xl0ZZvqE8B1Zyk7QMDj829mbTII+9g== + dependencies: + protobufjs "^7.0.0" + "@agoric/ertp@0.16.3-dev-ff36f02.0+ff36f02", "@agoric/ertp@^0.16.3-dev-6bce049.0": version "0.16.3-dev-ff36f02.0" resolved "https://registry.yarnpkg.com/@agoric/ertp/-/ertp-0.16.3-dev-ff36f02.0.tgz#6c3cc73cd79383782d16a141cd5901b2de9e1637" From b58c29409cad16d1ca420448b156776d2077adb2 Mon Sep 17 00:00:00 2001 From: samsiegart Date: Fri, 19 Jan 2024 11:53:11 -0800 Subject: [PATCH 2/4] fix: dont crash on missing apiAddrs --- wallet/src/components/ConnectionSettingsDialog.tsx | 10 +++++----- .../components/tests/ConnectionSettingsDialog.test.tsx | 4 +--- wallet/src/util/SuggestChain.ts | 2 +- wallet/src/util/connections.ts | 5 ++--- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/wallet/src/components/ConnectionSettingsDialog.tsx b/wallet/src/components/ConnectionSettingsDialog.tsx index 88877b47..5b12ecd5 100644 --- a/wallet/src/components/ConnectionSettingsDialog.tsx +++ b/wallet/src/components/ConnectionSettingsDialog.tsx @@ -65,8 +65,8 @@ const useNodeSuggestions = (networkConfigHref: string) => { const networkConfig = await res.json(); if (didConfigChange) return; - setApiSuggestions(networkConfig.apiAddrs); - setRpcSuggestions(networkConfig.rpcAddrs); + setApiSuggestions(networkConfig.apiAddrs ?? []); + setRpcSuggestions(networkConfig.rpcAddrs ?? []); }; updateSuggestions().catch(() => @@ -167,10 +167,10 @@ const ConnectionSettingsDialog = ({ case 'devnet': setConfig({ ...config, - href: `https://${value}.agoric.net/network-config`, + href: networkConfigUrl.fromSource(value), }); break; - case 'localhost': + case 'local': setConfig({ ...config, href: `${window.location.origin}/wallet/network-config`, @@ -185,7 +185,7 @@ const ConnectionSettingsDialog = ({ Mainnet Testnet Devnet - Localhost + Local Custom URL diff --git a/wallet/src/components/tests/ConnectionSettingsDialog.test.tsx b/wallet/src/components/tests/ConnectionSettingsDialog.test.tsx index 5fb1d510..6161fb60 100644 --- a/wallet/src/components/tests/ConnectionSettingsDialog.test.tsx +++ b/wallet/src/components/tests/ConnectionSettingsDialog.test.tsx @@ -59,9 +59,7 @@ describe('Connection setting dialog', () => { ); const networkSelect = component.find(Select).first(); - act(() => - networkSelect.props().onChange({ target: { value: 'localhost' } }), - ); + act(() => networkSelect.props().onChange({ target: { value: 'local' } })); component.update(); const textField = component.find(TextField).first(); diff --git a/wallet/src/util/SuggestChain.ts b/wallet/src/util/SuggestChain.ts index 05032f0f..b22cd1a8 100644 --- a/wallet/src/util/SuggestChain.ts +++ b/wallet/src/util/SuggestChain.ts @@ -20,7 +20,7 @@ export const makeChainInfo = ( if (config?.rpc) { rpc = config.rpc; } else { - const rpcAddr = rpcAddrs[rpcIndex]; + const rpcAddr = rpcAddrs[rpcIndex] ?? ''; rpc = rpcAddr.match(/:\/\//) ? rpcAddr : `http://${rpcAddr}`; } diff --git a/wallet/src/util/connections.ts b/wallet/src/util/connections.ts index 34c91b89..543a4da3 100644 --- a/wallet/src/util/connections.ts +++ b/wallet/src/util/connections.ts @@ -1,9 +1,8 @@ export const KnownNetworkConfigUrls = { main: 'https://main.agoric.net/network-config', devnet: 'https://devnet.agoric.net/network-config', - testnet: 'https://testnet.agoric.net/network-config', - // for localhost skip https and assume it's subpathed to /wallet - localhost: 'http://localhost:3000/wallet/network-config', + testnet: 'https://emerynet.agoric.net/network-config', + local: `${window.location.origin}/wallet/network-config`, }; export const DEFAULT_CONNECTION_CONFIGS = Object.values( From 476256014c408d3d146356a8b88148b297677ff2 Mon Sep 17 00:00:00 2001 From: samsiegart Date: Fri, 19 Jan 2024 12:15:12 -0800 Subject: [PATCH 3/4] feat: rename testnet to emerynet --- wallet/src/components/ConnectionSettingsDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet/src/components/ConnectionSettingsDialog.tsx b/wallet/src/components/ConnectionSettingsDialog.tsx index 5b12ecd5..0e89bc56 100644 --- a/wallet/src/components/ConnectionSettingsDialog.tsx +++ b/wallet/src/components/ConnectionSettingsDialog.tsx @@ -183,7 +183,7 @@ const ConnectionSettingsDialog = ({ }} > Mainnet - Testnet + Emerynet Devnet Local From 5d62489f7d1d0d77655f91038376a2ea27683d29 Mon Sep 17 00:00:00 2001 From: samsiegart Date: Tue, 6 Feb 2024 10:34:10 -0800 Subject: [PATCH 4/4] feat: add stTIA and stOSMO icons --- wallet/public/tokens/stosmo.svg | 9 +++++++++ wallet/public/tokens/sttia.svg | 4 ++++ wallet/src/util/Icons.ts | 2 ++ 3 files changed, 15 insertions(+) create mode 100644 wallet/public/tokens/stosmo.svg create mode 100644 wallet/public/tokens/sttia.svg diff --git a/wallet/public/tokens/stosmo.svg b/wallet/public/tokens/stosmo.svg new file mode 100644 index 00000000..cd1ed2d0 --- /dev/null +++ b/wallet/public/tokens/stosmo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/wallet/public/tokens/sttia.svg b/wallet/public/tokens/sttia.svg new file mode 100644 index 00000000..10bd37de --- /dev/null +++ b/wallet/public/tokens/sttia.svg @@ -0,0 +1,4 @@ + + + + diff --git a/wallet/src/util/Icons.ts b/wallet/src/util/Icons.ts index 3f1b38b5..93b41ca1 100644 --- a/wallet/src/util/Icons.ts +++ b/wallet/src/util/Icons.ts @@ -12,6 +12,8 @@ export const icons = { stATOM: 'tokens/statom.svg', DAI_grv: 'tokens/DAI_grv.png', USDT: 'tokens/USDT.png', + stTIA: 'tokens/sttia.svg', + stOSMO: 'tokens/stosmo.svg', }; export const defaultIcon = 'tokens/default.png';