Skip to content

Commit

Permalink
Merge pull request #1034 from liquity/fix-sepolia
Browse files Browse the repository at this point in the history
fix: testnet Sepolia not working with Alchemy API key
  • Loading branch information
danielattilasimon authored Jan 4, 2024
2 parents e5e5367 + 40372dd commit ce7f382
Show file tree
Hide file tree
Showing 4 changed files with 362 additions and 2 deletions.
4 changes: 2 additions & 2 deletions packages/dev-frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from "react";
import { createClient, WagmiConfig } from "wagmi";
import { mainnet, goerli, sepolia, localhost } from "wagmi/chains";
import { ConnectKitProvider, getDefaultClient } from "connectkit";
import { ConnectKitProvider } from "connectkit";
import { Flex, Heading, ThemeProvider, Paragraph, Link } from "theme-ui";

// import { BatchedWebSocketAugmentedWeb3Provider } from "@liquity/providers";
import getDefaultClient from "./connectkit/defaultClient";
import { LiquityProvider } from "./hooks/LiquityContext";
import { WalletConnector } from "./components/WalletConnector";
import { TransactionProvider } from "./components/Transaction";
Expand Down
224 changes: 224 additions & 0 deletions packages/dev-frontend/src/connectkit/defaultClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
// BSD 2-Clause License
//
// Copyright (c) 2022, LFE, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.

// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import { Connector, configureChains, ChainProviderFn } from "wagmi";
import { Chain, mainnet, polygon, optimism, arbitrum } from "wagmi/chains";
import { Provider } from "@wagmi/core";

import { MetaMaskConnector } from "wagmi/connectors/metaMask";
import { WalletConnectConnector } from "wagmi/connectors/walletConnect";
import { WalletConnectLegacyConnector } from "wagmi/connectors/walletConnectLegacy";
import { CoinbaseWalletConnector } from "wagmi/connectors/coinbaseWallet";
import { SafeConnector } from "wagmi/connectors/safe";
import { InjectedConnector } from "wagmi/connectors/injected";

import { alchemyProvider } from "../wagmi/alchemyProvider";
import { infuraProvider } from "wagmi/providers/infura";
import { jsonRpcProvider } from "wagmi/providers/jsonRpc";
import { publicProvider } from "wagmi/providers/public";

let globalAppName: string;
let globalAppIcon: string;

export const getAppName = () => globalAppName;
export const getAppIcon = () => globalAppIcon;

const defaultChains = [mainnet, polygon, optimism, arbitrum];

type DefaultConnectorsProps = {
chains?: Chain[];
app: {
name: string;
icon?: string;
description?: string;
url?: string;
};
walletConnectProjectId?: string;
};

type DefaultClientProps = {
appName: string;
appIcon?: string;
appDescription?: string;
appUrl?: string;
autoConnect?: boolean;
alchemyId?: string;
infuraId?: string;
chains?: Chain[];
connectors?: any;
provider?: any;
webSocketProvider?: any;
enableWebSocketProvider?: boolean;
stallTimeout?: number;
/* WC 2.0 requires a project ID (get one here: https://cloud.walletconnect.com/sign-in) */
walletConnectProjectId: string;
};

type ConnectKitClientProps = {
autoConnect?: boolean;
connectors?: Connector[];
provider: Provider;
webSocketProvider?: any;
};

const getDefaultConnectors = ({ chains, app, walletConnectProjectId }: DefaultConnectorsProps) => {
const hasAllAppData = app.name && app.icon && app.description && app.url;
const shouldUseSafeConnector = !(typeof window === "undefined") && window?.parent !== window;

let connectors: Connector[] = [];

// If we're in an iframe, include the SafeConnector
if (shouldUseSafeConnector) {
connectors = [
...connectors,
new SafeConnector({
chains,
options: {
allowedDomains: [/gnosis-safe.io$/, /app.safe.global$/],
debug: false
}
})
];
}

// Add the rest of the connectors
connectors = [
...connectors,
new MetaMaskConnector({
chains,
options: {
shimDisconnect: true,
UNSTABLE_shimOnConnectSelectAccount: true
}
}),
new CoinbaseWalletConnector({
chains,
options: {
appName: app.name,
headlessMode: true
}
}),
walletConnectProjectId
? new WalletConnectConnector({
chains,
options: {
showQrModal: false,
projectId: walletConnectProjectId,
metadata: hasAllAppData
? {
name: app.name,
description: app.description!,
url: app.url!,
icons: [app.icon!]
}
: undefined
}
})
: new WalletConnectLegacyConnector({
chains,
options: {
qrcode: false
}
}),
new InjectedConnector({
chains,
options: {
shimDisconnect: true,
name: detectedName =>
`Injected (${typeof detectedName === "string" ? detectedName : detectedName.join(", ")})`
}
})
];

return connectors;
};

const defaultClient = ({
autoConnect = true,
appName = "ConnectKit",
appIcon,
appDescription,
appUrl,
chains = defaultChains,
alchemyId,
infuraId,
connectors,
provider,
stallTimeout,
webSocketProvider,
enableWebSocketProvider,
walletConnectProjectId
}: DefaultClientProps) => {
globalAppName = appName;
if (appIcon) globalAppIcon = appIcon;

const providers: ChainProviderFn[] = [];
if (alchemyId) {
providers.push(alchemyProvider({ apiKey: alchemyId, stallTimeout }));
}
if (infuraId) {
providers.push(infuraProvider({ apiKey: infuraId, stallTimeout }));
}
providers.push(
jsonRpcProvider({
rpc: c => {
return { http: c.rpcUrls.default.http[0] };
},
stallTimeout
})
);
providers.push(publicProvider());

const {
provider: configuredProvider,
chains: configuredChains,
webSocketProvider: configuredWebSocketProvider
} = configureChains(chains, providers);

const connectKitClient: ConnectKitClientProps = {
autoConnect,
connectors:
connectors ??
getDefaultConnectors({
chains: configuredChains,
app: {
name: appName,
icon: appIcon,
description: appDescription,
url: appUrl
},
walletConnectProjectId
}),
provider: provider ?? configuredProvider,
webSocketProvider: enableWebSocketProvider // Removed by default, breaks if used in Next.js – "unhandledRejection: Error: could not detect network"
? webSocketProvider ?? configuredWebSocketProvider
: undefined
};

return { ...connectKitClient };
};

export default defaultClient;
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Network, Networkish } from "@ethersproject/networks";
import { defineReadOnly } from "@ethersproject/properties";
import type { ConnectionInfo } from "@ethersproject/web";

import {
AlchemyProvider,
WebSocketProvider,
CommunityResourcable,
showThrottleMessage
} from "@ethersproject/providers";

const defaultApiKey = "_gg7wSSi0KMBsdKnGVfHDueq6xMB9EkC";

export class AlchemyWebSocketProviderWithSepoliaSupport
extends WebSocketProvider
implements CommunityResourcable {
readonly apiKey!: string;

constructor(network?: Networkish, apiKey?: any) {
const provider = new AlchemyProvider(network, apiKey);

const url = provider.connection.url
.replace(/^http/i, "ws")
.replace(".alchemyapi.", ".ws.alchemyapi.");

super(url, provider.network);
defineReadOnly(this, "apiKey", provider.apiKey);
}

isCommunityResource(): boolean {
return this.apiKey === defaultApiKey;
}
}

export class AlchemyProviderWithSepoliaSupport extends AlchemyProvider {
static getUrl(network: Network, apiKey: string): ConnectionInfo {
let host = null;
switch (network.name) {
case "sepolia":
host = "eth-sepolia.g.alchemy.com/v2/";
break;
default:
return AlchemyProvider.getUrl(network, apiKey);
}

return {
allowGzip: true,
url: "https:/" + "/" + host + apiKey,
throttleCallback: () => {
if (apiKey === defaultApiKey) {
showThrottleMessage();
}
return Promise.resolve(true);
}
};
}
}
79 changes: 79 additions & 0 deletions packages/dev-frontend/src/wagmi/alchemyProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// MIT License

// Copyright (c) 2023-present weth, LLC

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import type { Chain } from "@wagmi/chains";

import type { ChainProviderFn, FallbackProviderConfig } from "@wagmi/core";

import {
AlchemyProviderWithSepoliaSupport,
AlchemyWebSocketProviderWithSepoliaSupport
} from "../providers/AlchemyProviderWithSepoliaSupport";

export type AlchemyProviderConfig = FallbackProviderConfig & {
/** Your Alchemy API key from the [Alchemy Dashboard](https://dashboard.alchemyapi.io/). */
apiKey: string;
};

export function alchemyProvider<TChain extends Chain = Chain>({
apiKey,
priority,
stallTimeout,
weight
}: AlchemyProviderConfig): ChainProviderFn<
TChain,
AlchemyProviderWithSepoliaSupport,
AlchemyWebSocketProviderWithSepoliaSupport
> {
return function (chain) {
if (!chain.rpcUrls.alchemy?.http[0]) return null;
return {
chain: {
...chain,
rpcUrls: {
...chain.rpcUrls,
default: { http: [`${chain.rpcUrls.alchemy?.http[0]}/${apiKey}`] }
}
} as TChain,
provider: () => {
const provider = new AlchemyProviderWithSepoliaSupport(
{
chainId: chain.id,
name: chain.network,
ensAddress: chain.contracts?.ensRegistry?.address
},
apiKey
);
return Object.assign(provider, { priority, stallTimeout, weight });
},
webSocketProvider: () =>
new AlchemyWebSocketProviderWithSepoliaSupport(
{
chainId: chain.id,
name: chain.network,
ensAddress: chain.contracts?.ensRegistry?.address
},
apiKey
)
};
};
}

0 comments on commit ce7f382

Please sign in to comment.