From 0c7c5d07619b5ee925efcaefad1d31be19db1c1c Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Mon, 1 Jul 2024 15:59:59 +0100 Subject: [PATCH 1/2] fix: add provider URL to SIWE signature verification --- web/netlify/functions/authUser.ts | 10 ++++-- web/src/consts/chains.ts | 5 ++- web/src/context/Web3Provider.tsx | 59 ++++++++++++++++++++++--------- 3 files changed, 53 insertions(+), 21 deletions(-) diff --git a/web/netlify/functions/authUser.ts b/web/netlify/functions/authUser.ts index 1fe1811fe..353e7a3e8 100644 --- a/web/netlify/functions/authUser.ts +++ b/web/netlify/functions/authUser.ts @@ -1,10 +1,11 @@ import middy from "@middy/core"; import jsonBodyParser from "@middy/http-json-body-parser"; import { createClient } from "@supabase/supabase-js"; +import { ethers } from "ethers"; import * as jwt from "jose"; import { SiweMessage } from "siwe"; -import { ETH_SIGNATURE_REGEX, DEFAULT_CHAIN } from "consts/processEnvConsts"; +import { ETH_SIGNATURE_REGEX, DEFAULT_CHAIN, isProductionDeployment } from "consts/processEnvConsts"; import { netlifyUri, netlifyDeployUri, netlifyDeployPrimeUri } from "src/generatedNetlifyInfo.json"; import { Database } from "src/types/supabase-notification"; @@ -73,9 +74,12 @@ const authUser = async (event) => { } try { - await siweMessage.verify({ signature, nonce: nonceData.nonce, time: new Date().toISOString() }); + const alchemyChain = isProductionDeployment() ? "arb-mainnet" : "arb-sepolia"; + const alchemyRpcURL = `https://${alchemyChain}.g.alchemy.com/v2/${process.env.ALCHEMY_FUNCTIONS_API_KEY}`; + const provider = new ethers.providers.JsonRpcProvider(alchemyRpcURL); + await siweMessage.verify({ signature, nonce: nonceData.nonce, time: new Date().toISOString() }, { provider }); } catch (err) { - throw new Error("Invalid signer"); + throw new Error("Invalid signer: " + JSON.stringify(err)); } const { error } = await supabase.from("user-nonce").delete().match({ address: lowerCaseAddress }); diff --git a/web/src/consts/chains.ts b/web/src/consts/chains.ts index 47ff04787..0286ffaa8 100644 --- a/web/src/consts/chains.ts +++ b/web/src/consts/chains.ts @@ -1,16 +1,19 @@ import { extractChain } from "viem"; -import { Chain, arbitrum, mainnet, arbitrumSepolia, gnosisChiado } from "viem/chains"; +import { Chain, arbitrum, mainnet, arbitrumSepolia, gnosis, gnosisChiado } from "viem/chains"; import { isProductionDeployment } from "./index"; export const DEFAULT_CHAIN = isProductionDeployment() ? arbitrum.id : arbitrumSepolia.id; +// Read/Write export const SUPPORTED_CHAINS: Record = { [isProductionDeployment() ? arbitrum.id : arbitrumSepolia.id]: isProductionDeployment() ? arbitrum : arbitrumSepolia, }; +// Read Only export const QUERY_CHAINS: Record = { [gnosisChiado.id]: gnosisChiado, + [gnosis.id]: gnosis, [mainnet.id]: mainnet, }; diff --git a/web/src/context/Web3Provider.tsx b/web/src/context/Web3Provider.tsx index 6f7809e9b..189297b83 100644 --- a/web/src/context/Web3Provider.tsx +++ b/web/src/context/Web3Provider.tsx @@ -3,34 +3,59 @@ import React from "react"; import { createWeb3Modal } from "@web3modal/wagmi/react"; import { type Chain } from "viem"; import { createConfig, fallback, http, WagmiProvider, webSocket } from "wagmi"; -import { mainnet, arbitrumSepolia, arbitrum, gnosisChiado } from "wagmi/chains"; +import { mainnet, arbitrumSepolia, arbitrum, gnosisChiado, gnosis, sepolia } from "wagmi/chains"; import { walletConnect } from "wagmi/connectors"; -import { ALL_CHAINS } from "consts/chains"; +import { ALL_CHAINS, DEFAULT_CHAIN } from "consts/chains"; import { isProductionDeployment } from "consts/index"; import { lightTheme } from "styles/themes"; -const projectId = import.meta.env.WALLETCONNECT_PROJECT_ID ?? ""; -export const alchemyApiKey = import.meta.env.ALCHEMY_API_KEY ?? ""; +const alchemyApiKey = import.meta.env.ALCHEMY_API_KEY ?? ""; +const isProduction = isProductionDeployment(); -const chains = ALL_CHAINS as [Chain, ...Chain[]]; +// https://github.com/alchemyplatform/alchemy-sdk-js/blob/96b3f62/src/types/types.ts#L98-L119 +const alchemyToViemChain = { + [arbitrum.id]: "arb-mainnet", + [arbitrumSepolia.id]: "arb-sepolia", + [mainnet.id]: "eth-mainnet", + [sepolia.id]: "eth-sepolia", +}; type AlchemyProtocol = "https" | "wss"; -type AlchemyChain = "arb-sepolia" | "eth-mainnet" | "arb"; -const alchemyURL = (protocol: AlchemyProtocol, chain: AlchemyChain) => - `${protocol}://${chain}.g.alchemy.com/v2/${alchemyApiKey}`; -const alchemyTransport = (chain: AlchemyChain) => - fallback([webSocket(alchemyURL("wss", chain)), http(alchemyURL("https", chain))]); - -const transports = { - [isProductionDeployment() ? arbitrum.id : arbitrumSepolia.id]: isProductionDeployment() - ? alchemyTransport("arb") - : alchemyTransport("arb-sepolia"), - [mainnet.id]: alchemyTransport("eth-mainnet"), - [gnosisChiado.id]: fallback([webSocket("wss://rpc.chiadochain.net/wss"), http("https://rpc.chiadochain.net")]), + +// https://github.com/alchemyplatform/alchemy-sdk-js/blob/96b3f62/src/util/const.ts#L16-L18 +const alchemyURL = (protocol: AlchemyProtocol, chainId: number) => + `${protocol}://${alchemyToViemChain[chainId]}.g.alchemy.com/v2/${alchemyApiKey}`; + +export const getChainRpcUrl = (protocol: AlchemyProtocol, chainId: number) => { + return alchemyURL(protocol, chainId); +}; + +export const getDefaultChainRpcUrl = (protocol: AlchemyProtocol) => { + return getChainRpcUrl(protocol, DEFAULT_CHAIN); +}; + +export const getTransports = () => { + const alchemyTransport = (chain: Chain) => + fallback([http(alchemyURL("https", chain.id)), webSocket(alchemyURL("wss", chain.id))]); + const defaultTransport = (chain: Chain) => + fallback([http(chain.rpcUrls.default?.http?.[0]), webSocket(chain.rpcUrls.default?.webSocket?.[0])]); + + return { + [isProduction ? arbitrum.id : arbitrumSepolia.id]: isProduction + ? alchemyTransport(arbitrum) + : alchemyTransport(arbitrumSepolia), + [isProduction ? gnosis.id : gnosisChiado.id]: isProduction + ? defaultTransport(gnosis) + : defaultTransport(gnosisChiado), + [mainnet.id]: alchemyTransport(mainnet), // Always enabled for ENS resolution + }; }; +const chains = ALL_CHAINS as [Chain, ...Chain[]]; +const transports = getTransports(); +const projectId = import.meta.env.WALLETCONNECT_PROJECT_ID ?? ""; const wagmiConfig = createConfig({ chains, transports, From 11f5ace4ded5570dd618c8e3db143f7defbdb6ee Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Thu, 4 Jul 2024 18:46:57 +0200 Subject: [PATCH 2/2] fix: api key fallback --- web/netlify/functions/authUser.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/netlify/functions/authUser.ts b/web/netlify/functions/authUser.ts index 353e7a3e8..8c4b18cd3 100644 --- a/web/netlify/functions/authUser.ts +++ b/web/netlify/functions/authUser.ts @@ -74,8 +74,10 @@ const authUser = async (event) => { } try { + // If the main Alchemy API key is permissioned, it won't work in a Netlify Function so we use a dedicated API key + const alchemyApiKey = process.env.ALCHEMY_FUNCTIONS_API_KEY ?? process.env.ALCHEMY_API_KEY; const alchemyChain = isProductionDeployment() ? "arb-mainnet" : "arb-sepolia"; - const alchemyRpcURL = `https://${alchemyChain}.g.alchemy.com/v2/${process.env.ALCHEMY_FUNCTIONS_API_KEY}`; + const alchemyRpcURL = `https://${alchemyChain}.g.alchemy.com/v2/${alchemyApiKey}`; const provider = new ethers.providers.JsonRpcProvider(alchemyRpcURL); await siweMessage.verify({ signature, nonce: nonceData.nonce, time: new Date().toISOString() }, { provider }); } catch (err) {