Skip to content

Commit

Permalink
App config: support multiple RPC URLs
Browse files Browse the repository at this point in the history
Fixes #2479
  • Loading branch information
tom2drum committed Jan 2, 2025
1 parent bd4c646 commit 5c8afc3
Show file tree
Hide file tree
Showing 16 changed files with 56 additions and 22 deletions.
19 changes: 17 additions & 2 deletions configs/app/chain.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { RollupType } from 'types/client/rollup';
import type { NetworkVerificationType, NetworkVerificationTypeEnvs } from 'types/networks';

import { getEnvValue } from './utils';
import { urlValidator } from 'ui/shared/forms/validators/url';

import { getEnvValue, parseEnvJson } from './utils';

const DEFAULT_CURRENCY_DECIMALS = 18;

Expand All @@ -17,6 +19,19 @@ const verificationType: NetworkVerificationType = (() => {
return getEnvValue('NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE') as NetworkVerificationTypeEnvs || 'mining';
})();

const rpcUrls = (() => {
const envValue = getEnvValue('NEXT_PUBLIC_NETWORK_RPC_URL');
const isUrl = urlValidator(envValue);

if (envValue && isUrl === true) {
return [ envValue ];
}

const parsedValue = parseEnvJson<Array<string>>(envValue);

return Array.isArray(parsedValue) ? parsedValue : [];
})();

const chain = Object.freeze({
id: getEnvValue('NEXT_PUBLIC_NETWORK_ID'),
name: getEnvValue('NEXT_PUBLIC_NETWORK_NAME'),
Expand All @@ -32,7 +47,7 @@ const chain = Object.freeze({
},
hasMultipleGasCurrencies: getEnvValue('NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES') === 'true',
tokenStandard: getEnvValue('NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME') || 'ERC',
rpcUrl: getEnvValue('NEXT_PUBLIC_NETWORK_RPC_URL'),
rpcUrls,
isTestnet: getEnvValue('NEXT_PUBLIC_IS_TESTNET') === 'true',
verificationType,
});
Expand Down
2 changes: 1 addition & 1 deletion configs/app/features/blockchainInteraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const config: Feature<{ walletConnect: { projectId: string } }> = (() => {
chain.currency.name &&
chain.currency.symbol &&
chain.currency.decimals &&
chain.rpcUrl &&
chain.rpcUrls.length > 0 &&
walletConnectProjectId
) {
return Object.freeze({
Expand Down
2 changes: 1 addition & 1 deletion configs/app/features/marketplace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const config: Feature<(
rating: { airtableApiKey: string; airtableBaseId: string } | undefined;
graphLinksUrl: string | undefined;
}> = (() => {
if (enabled === 'true' && chain.rpcUrl && submitFormUrl) {
if (enabled === 'true' && chain.rpcUrls.length > 0 && submitFormUrl) {
const props = {
submitFormUrl,
categoriesUrl,
Expand Down
16 changes: 15 additions & 1 deletion deploy/tools/envs-validator/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,21 @@ const schema = yup
NEXT_PUBLIC_NETWORK_NAME: yup.string().required(),
NEXT_PUBLIC_NETWORK_SHORT_NAME: yup.string(),
NEXT_PUBLIC_NETWORK_ID: yup.number().positive().integer().required(),
NEXT_PUBLIC_NETWORK_RPC_URL: yup.string().test(urlTest),
NEXT_PUBLIC_NETWORK_RPC_URL: yup
.mixed()
.test(
'shape',
'Invalid schema were provided for NEXT_PUBLIC_NETWORK_RPC_URL, it should be either array of URLs or URL string',
(data) => {
const isUrlSchema = yup.string().test(urlTest);
const isArrayOfUrlsSchema = yup
.array()
.transform(replaceQuotes)
.json()
.of(yup.string().test(urlTest));

return isUrlSchema.isValidSync(data) || isArrayOfUrlsSchema.isValidSync(data);
}),
NEXT_PUBLIC_NETWORK_CURRENCY_NAME: yup.string(),
NEXT_PUBLIC_NETWORK_CURRENCY_WEI_NAME: yup.string(),
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL: yup.string(),
Expand Down
3 changes: 2 additions & 1 deletion deploy/tools/envs-validator/test/.env.alt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ NEXT_PUBLIC_HOMEPAGE_STATS=[]
NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT=['base16','bech32']
NEXT_PUBLIC_VIEWS_ADDRESS_BECH_32_PREFIX=foo
NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx
NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY=deprecated
NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY=deprecated
NEXT_PUBLIC_NETWORK_RPC_URL=['https://example.com','https://example2.com']
2 changes: 1 addition & 1 deletion docs/ENVS.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will
| NEXT_PUBLIC_NETWORK_NAME | `string` | Displayed name of the network | Required | - | `Gnosis Chain` | v1.0.x+ |
| NEXT_PUBLIC_NETWORK_SHORT_NAME | `string` | Used for SEO attributes (e.g, page description) | - | - | `OoG` | v1.0.x+ |
| NEXT_PUBLIC_NETWORK_ID | `number` | Chain id, see [https://chainlist.org](https://chainlist.org) for the reference | Required | - | `99` | v1.0.x+ |
| NEXT_PUBLIC_NETWORK_RPC_URL | `string` | Chain public RPC server url, see [https://chainlist.org](https://chainlist.org) for the reference | - | - | `https://core.poa.network` | v1.0.x+ |
| NEXT_PUBLIC_NETWORK_RPC_URL | `string \| Array<string>` | Chain public RPC server url, see [https://chainlist.org](https://chainlist.org) for the reference. Can contain a single string value, or an array of urls. | - | - | `https://core.poa.network` | v1.0.x+ |
| NEXT_PUBLIC_NETWORK_CURRENCY_NAME | `string` | Network currency name | - | - | `Ether` | v1.0.x+ |
| NEXT_PUBLIC_NETWORK_CURRENCY_WEI_NAME | `string` | Name of network currency subdenomination | - | `wei` | `duck` | v1.23.0+ |
| NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL | `string` | Network currency symbol | - | - | `ETH` | v1.0.x+ |
Expand Down
2 changes: 1 addition & 1 deletion lib/web3/currentChain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const currentChain = {
},
rpcUrls: {
'default': {
http: [ config.chain.rpcUrl ?? '' ],
http: config.chain.rpcUrls,
},
},
blockExplorers: {
Expand Down
2 changes: 1 addition & 1 deletion lib/web3/useAddOrSwitchChain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default function useAddOrSwitchChain() {
symbol: config.chain.currency.symbol,
decimals: config.chain.currency.decimals,
},
rpcUrls: [ config.chain.rpcUrl ],
rpcUrls: config.chain.rpcUrls,
blockExplorerUrls: [ config.app.baseUrl ],
} ] as never;
// in wagmi types for wallet_addEthereumChain method is not provided
Expand Down
10 changes: 7 additions & 3 deletions lib/web3/wagmiConfig.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { WagmiAdapter } from '@reown/appkit-adapter-wagmi';
import { http } from 'viem';
import { fallback, http } from 'viem';
import { createConfig } from 'wagmi';

import config from 'configs/app';
Expand All @@ -13,7 +13,11 @@ const wagmi = (() => {
const wagmiConfig = createConfig({
chains: [ currentChain ],
transports: {
[currentChain.id]: http(config.chain.rpcUrl || `${ config.api.endpoint }/api/eth-rpc`),
[currentChain.id]: fallback(
config.chain.rpcUrls
.map((url) => http(url))
.concat(http(`${ config.api.endpoint }/api/eth-rpc`)),
),
},
ssr: true,
batch: { multicall: { wait: 100 } },
Expand All @@ -26,7 +30,7 @@ const wagmi = (() => {
networks: chains,
multiInjectedProviderDiscovery: true,
transports: {
[currentChain.id]: http(),
[currentChain.id]: fallback(config.chain.rpcUrls.map((url) => http(url))),
},
projectId: feature.walletConnect.projectId,
ssr: true,
Expand Down
2 changes: 1 addition & 1 deletion nextjs/csp/policies/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function app(): CspDev.DirectiveDescriptor {
getFeaturePayload(config.features.rewards)?.api.endpoint,

// chain RPC server
config.chain.rpcUrl,
...config.chain.rpcUrls,
'https://infragrid.v.network', // RPC providers

// github (spec for api-docs page)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const ContractVerificationSolidityFoundry = () => {
const address = watch('address');

const codeSnippet = `forge verify-contract \\
--rpc-url ${ config.chain.rpcUrl || `${ config.api.endpoint }/api/eth-rpc` } \\
--rpc-url ${ config.chain.rpcUrls[0] || `${ config.api.endpoint }/api/eth-rpc` } \\
--verifier blockscout \\
--verifier-url '${ config.api.endpoint }/api/' \\
${ address || '<address>' } \\
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const ContractVerificationSolidityHardhat = ({ config: formConfig }: { config: S
solidity: "${ latestSolidityVersion || '0.8.24' }", // replace if necessary
networks: {
'${ chainNameSlug }': {
url: '${ config.chain.rpcUrl || `${ config.api.endpoint }/api/eth-rpc` }'
url: '${ config.chain.rpcUrls[0] || `${ config.api.endpoint }/api/eth-rpc` }'
},
},
etherscan: {
Expand Down
2 changes: 1 addition & 1 deletion ui/pages/Address.pw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ test('degradation view', async({ render, page, mockRpcResponse, mockApiResponse
});

const component = await render(<Address/>, { hooksConfig });
await page.waitForResponse(config.chain.rpcUrl as string);
await page.waitForResponse(config.chain.rpcUrls[0]);

await expect(component).toHaveScreenshot({
mask: [ page.locator(pwConfig.adsBannerSelector) ],
Expand Down
6 changes: 3 additions & 3 deletions ui/pages/Block.pw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ test('degradation view, details tab', async({ render, mockApiResponse, mockRpcRe
});

const component = await render(<Block/>, { hooksConfig });
await page.waitForResponse(config.chain.rpcUrl as string);
await page.waitForResponse(config.chain.rpcUrls[0]);

await expect(component).toHaveScreenshot();
});
Expand All @@ -49,7 +49,7 @@ test('degradation view, txs tab', async({ render, mockApiResponse, mockRpcRespon
});

const component = await render(<Block/>, { hooksConfig });
await page.waitForResponse(config.chain.rpcUrl as string);
await page.waitForResponse(config.chain.rpcUrls[0]);

await expect(component).toHaveScreenshot();
});
Expand All @@ -71,7 +71,7 @@ test('degradation view, withdrawals tab', async({ render, mockApiResponse, mockR
});

const component = await render(<Block/>, { hooksConfig });
await page.waitForResponse(config.chain.rpcUrl as string);
await page.waitForResponse(config.chain.rpcUrls[0]);

await expect(component).toHaveScreenshot();
});
4 changes: 2 additions & 2 deletions ui/pages/MarketplaceApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const MarketplaceAppContent = ({ address, data, isPending, appUrl }: Props) => {
blockscoutNetworkName: config.chain.name,
blockscoutNetworkId: Number(config.chain.id),
blockscoutNetworkCurrency: config.chain.currency,
blockscoutNetworkRpc: config.chain.rpcUrl,
blockscoutNetworkRpc: config.chain.rpcUrls[0],
};

iframeRef?.current?.contentWindow?.postMessage(message, data.url);
Expand Down Expand Up @@ -159,7 +159,7 @@ const MarketplaceApp = () => {
<DappscoutIframeProvider
address={ address }
appUrl={ appUrl }
rpcUrl={ config.chain.rpcUrl }
rpcUrl={ config.chain.rpcUrls[0] }
sendTransaction={ sendTransaction }
signMessage={ signMessage }
signTypedData={ signTypedData }
Expand Down
2 changes: 1 addition & 1 deletion ui/shared/NetworkAddToWallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const NetworkAddToWallet = () => {
}
}, [ addOrSwitchChain, provider, toast, wallet ]);

if (!provider || !wallet || !config.chain.rpcUrl || !feature.isEnabled) {
if (!provider || !wallet || !config.chain.rpcUrls.length || !feature.isEnabled) {
return null;
}

Expand Down

0 comments on commit 5c8afc3

Please sign in to comment.