Skip to content

Commit

Permalink
✨ migrate to wagmi+viem v2
Browse files Browse the repository at this point in the history
  • Loading branch information
cruzdanilo committed Nov 24, 2023
1 parent f3367f5 commit 9f5e7b7
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 55 deletions.
20 changes: 11 additions & 9 deletions app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
import "expo-webauthn";

import { AlchemyProvider } from "@alchemy/aa-alchemy";
import FontAwesome from "@expo/vector-icons/FontAwesome";
import InterBold from "@tamagui/font-inter/otf/Inter-Bold.otf";
import Inter from "@tamagui/font-inter/otf/Inter-Medium.otf";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { reconnect } from "@wagmi/core";
import { type FontSource, useFonts } from "expo-font";
import { Slot, SplashScreen } from "expo-router";
import { StatusBar } from "expo-status-bar";
import React, { useEffect, useState } from "react";
import React, { useEffect } from "react";
import * as Sentry from "sentry-expo";
import { TamaguiProvider } from "tamagui";
import { TextEncoder } from "text-encoding";
import { WagmiProvider, createConfig, http } from "wagmi";
import { WagmiProvider, createConfig, custom } from "wagmi";

import metadata from "../package.json";
import tamaguiConfig from "../tamagui.config";
import alchemyConnector from "../utils/alchemyConnector";
import { chain, httpURL } from "../utils/constants";
import { alchemyAPIKey, chain } from "../utils/constants";

export { ErrorBoundary } from "expo-router";

Expand All @@ -34,10 +36,11 @@ Sentry.init({
autoSessionTracking: true,
});

const provider = new AlchemyProvider({ apiKey: alchemyAPIKey, chain });
const wagmiConfig = createConfig({
chains: [chain],
connectors: [alchemyConnector()],
transports: { [chain.id]: http(httpURL) },
connectors: [alchemyConnector(provider)],
transports: { [chain.id]: custom(provider) },
});
const queryClient = new QueryClient();

Expand All @@ -47,18 +50,17 @@ export default function RootLayout() {
InterBold: InterBold as FontSource,
...FontAwesome.font,
});
const [mounted, setMounted] = useState(false);

useEffect(() => {
if (error) throw error;
}, [error]);

useEffect(() => {
setMounted(true);
reconnect(wagmiConfig);
if (loaded) SplashScreen.hideAsync().catch(() => {});
}, [loaded]);
}, [loaded, reconnect]);

if (!loaded || !mounted) return;
if (!loaded) return;

return (
<>
Expand Down
2 changes: 1 addition & 1 deletion app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { UAParser } from "ua-parser-js";
import { useAccount, useConnect, useDisconnect, useSendTransaction } from "wagmi";

import base64URLEncode from "../utils/base64URLEncode";
import { rpId, turnkeyAPIPublicKey, turnkeyAPIPrivateKey, turnkeyOrganizationId } from "../utils/constants";
import { rpId, turnkeyAPIPrivateKey, turnkeyAPIPublicKey, turnkeyOrganizationId } from "../utils/constants";
import generateRandomBuffer from "../utils/generateRandomBuffer";
import handleError from "../utils/handleError";

Expand Down
9 changes: 6 additions & 3 deletions app/pomelo+api.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { createConfig, getBlockNumber, http } from "@wagmi/core";
import { type ExpoRequest, ExpoResponse } from "expo-router/server";

import { chain, httpURL } from "../utils/constants";
import { alchemyAPIKey, chain } from "../utils/constants";

const wagmiConfig = createConfig({ chains: [chain], transports: { [chain.id]: http(httpURL) } });
const httpConfig = createConfig({
chains: [chain],
transports: { [chain.id]: http(`${chain.rpcUrls.alchemy.http[0]}/${alchemyAPIKey}`) },
});

// eslint-disable-next-line import/prefer-default-export
export async function GET(_: ExpoRequest) {
const blockNumber = await getBlockNumber(wagmiConfig);
const blockNumber = await getBlockNumber(httpConfig);
return ExpoResponse.json(Number(blockNumber));
}
Binary file modified bun.lockb
Binary file not shown.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"@react-native-async-storage/async-storage": "1.18.2",
"@sentry/react-native": "^5.14.1",
"@tamagui/config": "^1.79.1",
"@tanstack/react-query": "^5.8.4",
"@tanstack/react-query": "^5.8.6",
"@turnkey/api-key-stamper": "^0.2.0",
"@turnkey/http": "^2.3.1",
"@turnkey/viem": "^0.3.4",
Expand Down Expand Up @@ -56,8 +56,8 @@
"tamagui": "^1.79.1",
"text-encoding": "^0.7.0",
"ua-parser-js": "^1.0.37",
"viem": "^2.0.0-beta.6",
"wagmi": "^2.0.0-beta.5",
"viem": "2.0.0-beta.6",
"wagmi": "2.0.0-beta.5",
"zustand": "^4.4.6"
},
"devDependencies": {
Expand Down
77 changes: 42 additions & 35 deletions utils/alchemyConnector.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getDefaultLightAccountFactoryAddress, LightSmartContractAccount } from "@alchemy/aa-accounts";
import { AlchemyProvider } from "@alchemy/aa-alchemy";
import type { AlchemyProvider } from "@alchemy/aa-alchemy";
import { LocalAccountSigner } from "@alchemy/aa-core";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { ApiKeyStamper } from "@turnkey/api-key-stamper";
Expand All @@ -11,7 +11,6 @@ import { ChainNotConfiguredError, createConnector, normalizeChainId } from "wagm

import handleError from "./handleError";
import {
alchemyAPIKey,
alchemyGasPolicyId,
chain,
rpId,
Expand All @@ -20,24 +19,29 @@ import {
turnkeyOrganizationId,
} from "../utils/constants";

export default function alchemyConnector() {
export default function alchemyConnector(provider: AlchemyProvider) {
let account: LightSmartContractAccount | undefined;
let turnkeyClient: TurnkeyClient | undefined;
let alchemyProvider: AlchemyProvider | undefined;

return createConnector<AlchemyProvider, { getTurnkeyClient: () => TurnkeyClient }>((config) => ({
return createConnector<
AlchemyProvider,
{
getAccount: (signWith: string, subOrganizationId: string) => Promise<LightSmartContractAccount>;
getTurnkeyClient: () => TurnkeyClient;
}
>((config) => ({
id: "alchemy",
name: "Alchemy",
async connect({ chainId } = {}) {
async connect({ chainId, isReconnecting } = {}) {
if (chainId && chainId !== chain.id) throw new SwitchChainError(new ChainNotConfiguredError());
const provider = await this.getProvider();
provider.on("accountsChanged", this.onAccountsChanged);
provider.on("chainChanged", this.onChainChanged);
provider.on("disconnect", this.onDisconnect);

let signWith: string;
let subOrganizationId: string;
try {
if (!isReconnecting) throw new Error("new connection");
const json = await AsyncStorage.getItem("account.store");
if (!json) throw new Error("no store");
const store = JSON.parse(json) as { signWith?: string; subOrganizationId?: string };
Expand All @@ -59,34 +63,18 @@ export default function alchemyConnector() {
if (!walletAccount) throw new Error("no ethereum account");
signWith = walletAccount.address;
subOrganizationId = organizationId;
AsyncStorage.setItem("account.store", JSON.stringify({ signWith, subOrganizationId })).catch(handleError);
}

const owner = new LocalAccountSigner(
await createAccount({
client: this.getTurnkeyClient(),
organizationId: subOrganizationId,
ethereumAddress: signWith,
signWith,
}),
);
account = provider.connect(
(rpcClient) =>
new LightSmartContractAccount({
factoryAddress: getDefaultLightAccountFactoryAddress(chain),
rpcClient,
chain,
owner,
}),
).account;
provider.withAlchemyGasManager({ policyId: alchemyGasPolicyId });
account = await this.getAccount(signWith, subOrganizationId);
return { accounts: [getAddress(await account.getAddress())], chainId: chain.id };
},
disconnect() {
account = undefined;
alchemyProvider?.disconnect();
alchemyProvider?.removeListener("accountsChanged", this.onAccountsChanged);
alchemyProvider?.removeListener("chainChanged", this.onChainChanged);
alchemyProvider?.removeListener("disconnect", this.onDisconnect);
provider.disconnect();
provider.removeListener("accountsChanged", this.onAccountsChanged);
provider.removeListener("chainChanged", this.onChainChanged);
provider.removeListener("disconnect", this.onDisconnect);
return Promise.resolve();
},
async getAccounts() {
Expand All @@ -96,8 +84,7 @@ export default function alchemyConnector() {
return Promise.resolve(chain.id);
},
getProvider() {
alchemyProvider ??= new AlchemyProvider({ apiKey: alchemyAPIKey, chain });
return Promise.resolve(alchemyProvider);
return Promise.resolve(provider);
},
isAuthorized() {
return Promise.resolve(!!account);
Expand All @@ -116,12 +103,32 @@ export default function alchemyConnector() {
onDisconnect(error) {
config.emitter.emit("disconnect");
account = undefined;
alchemyProvider?.disconnect();
alchemyProvider?.removeListener("accountsChanged", this.onAccountsChanged);
alchemyProvider?.removeListener("chainChanged", this.onChainChanged);
alchemyProvider?.removeListener("disconnect", this.onDisconnect);
provider.removeListener("accountsChanged", this.onAccountsChanged);
provider.removeListener("chainChanged", this.onChainChanged);
provider.removeListener("disconnect", this.onDisconnect);
handleError(error);
},
async getAccount(signWith: string, subOrganizationId: string) {
const owner = new LocalAccountSigner(
await createAccount({
client: this.getTurnkeyClient(),
organizationId: subOrganizationId,
ethereumAddress: signWith,
signWith,
}),
);
account = provider.connect(
(rpcClient) =>
new LightSmartContractAccount({
factoryAddress: getDefaultLightAccountFactoryAddress(chain),
rpcClient,
chain,
owner,
}),
).account;
provider.withAlchemyGasManager({ policyId: alchemyGasPolicyId });
return account;
},
getTurnkeyClient() {
turnkeyClient ??= new TurnkeyClient({ baseUrl: "https://api.turnkey.com" }, new WebauthnStamper({ rpId }));
return turnkeyClient;
Expand Down
22 changes: 18 additions & 4 deletions utils/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { goerli } from "@wagmi/core/chains";
import { Platform } from "react-native";

export { goerli as chain } from "@wagmi/core/chains";

export const rpId = __DEV__ && Platform.OS === "web" ? "localhost" : "exactly.app";

if (!process.env.EXPO_PUBLIC_ALCHEMY_API_KEY) throw new Error("missing alchemy api key");
Expand All @@ -16,5 +15,20 @@ export const turnkeyAPIPublicKey = process.env.EXPO_PUBLIC_TURNKEY_API_PUBLIC_KE
export const turnkeyAPIPrivateKey = process.env.EXPO_PUBLIC_TURNKEY_API_PRIVATE_KEY;
export const turnkeyOrganizationId = process.env.EXPO_PUBLIC_TURNKEY_ORGANIZATION_ID;

export const httpURL = `https://eth-goerli.g.alchemy.com/v2/${alchemyAPIKey}`;
export const wsURL = `wss://eth-goerli.g.alchemy.com/v2/${alchemyAPIKey}`;
export const chain = {
...goerli,
fees: undefined,
network: "goerli",
rpcUrls: {
...goerli.rpcUrls,
public: {
http: ["https://rpc.ankr.com/eth_goerli"],
},
alchemy: {
http: ["https://eth-goerli.g.alchemy.com/v2"],
webSocket: ["wss://eth-goerli.g.alchemy.com/v2"],
},
},
};

export const auditorAddress = "0xB957a4Aa46F859b14C745b8356c28B8361319fAB";

0 comments on commit 9f5e7b7

Please sign in to comment.