diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 367a3cc6b..761cf0f86 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -91,6 +91,8 @@
+
+
diff --git a/android/app/src/preview/AndroidManifest.xml b/android/app/src/preview/AndroidManifest.xml
index 5ef15e678..1a29663fc 100644
--- a/android/app/src/preview/AndroidManifest.xml
+++ b/android/app/src/preview/AndroidManifest.xml
@@ -23,6 +23,8 @@
+
+
diff --git a/android/app/src/prod/AndroidManifest.xml b/android/app/src/prod/AndroidManifest.xml
index f0ee2c224..12a518196 100644
--- a/android/app/src/prod/AndroidManifest.xml
+++ b/android/app/src/prod/AndroidManifest.xml
@@ -23,6 +23,8 @@
+
+
diff --git a/components/Onboarding/ConnectViaWallet/ConnectViaWallet.store.tsx b/components/Onboarding/ConnectViaWallet/ConnectViaWallet.store.tsx
index 532ec7cc9..0e0853b98 100644
--- a/components/Onboarding/ConnectViaWallet/ConnectViaWallet.store.tsx
+++ b/components/Onboarding/ConnectViaWallet/ConnectViaWallet.store.tsx
@@ -6,6 +6,7 @@ type IConnectViaWalletStoreProps = {
address: string;
alreadyV3Db: boolean;
signer: Signer;
+ isSCW: boolean;
};
type IConnectViaWalletStoreProviderProps =
@@ -42,6 +43,7 @@ const createConnectViaWalletStore = (props: IConnectViaWalletStoreProps) =>
createStore()((set) => ({
address: props.address,
signer: props.signer,
+ isSCW: props.isSCW,
alreadyV3Db: props.alreadyV3Db,
loading: false,
numberOfSignaturesDone: 0,
diff --git a/components/Onboarding/ConnectViaWallet/ConnectViaWallet.tsx b/components/Onboarding/ConnectViaWallet/ConnectViaWallet.tsx
index ab659d789..fce072771 100644
--- a/components/Onboarding/ConnectViaWallet/ConnectViaWallet.tsx
+++ b/components/Onboarding/ConnectViaWallet/ConnectViaWallet.tsx
@@ -24,6 +24,7 @@ import { useInitConnectViaWalletState } from "./useInitConnectViaWalletState";
type IConnectViaWalletProps = {
address: string;
+ isSCW: boolean;
onDoneConnecting: () => void;
onErrorConnecting: (arg: { error: Error }) => void;
};
@@ -31,7 +32,7 @@ type IConnectViaWalletProps = {
export const ConnectViaWallet = memo(function ConnectViaWallet(
props: IConnectViaWalletProps
) {
- const { address, onErrorConnecting, onDoneConnecting } = props;
+ const { address, isSCW, onErrorConnecting, onDoneConnecting } = props;
const finishedConnectingRef = useRef(false);
@@ -71,16 +72,22 @@ export const ConnectViaWallet = memo(function ConnectViaWallet(
onDoneConnecting={handleDoneConnecting}
onErrorConnecting={handleErrorConnecting}
>
-
+
);
});
// Wrapper to init the wallet state and then provide the data to the UI
const ConnectViaWalletStateWrapper = memo(
- function ConnectViaWalletStateWrapper({ address }: { address: string }) {
+ function ConnectViaWalletStateWrapper({
+ address,
+ isSCW,
+ }: {
+ address: string;
+ isSCW: boolean;
+ }) {
const { isInitializing, alreadyV3Db, signer } =
- useInitConnectViaWalletState({ address });
+ useInitConnectViaWalletState({ address, isSCW });
if (isInitializing) {
return ;
@@ -94,6 +101,7 @@ const ConnectViaWalletStateWrapper = memo(
return (
diff --git a/components/Onboarding/ConnectViaWallet/ConnectViaWalletSupportedWallets.ts b/components/Onboarding/ConnectViaWallet/ConnectViaWalletSupportedWallets.ts
index 3d19cd2b8..acaa47710 100644
--- a/components/Onboarding/ConnectViaWallet/ConnectViaWalletSupportedWallets.ts
+++ b/components/Onboarding/ConnectViaWallet/ConnectViaWalletSupportedWallets.ts
@@ -16,6 +16,7 @@ import { WalletId } from "thirdweb/wallets";
import { useAppStateHandlers } from "../../../hooks/useAppStateHandlers";
import { isEthOS } from "../../../utils/ethos";
+import { InstalledWallet, SUPPORTED_WALLETS } from "@utils/evm/wallets";
export const POPULAR_WALLETS = [
{
@@ -56,116 +57,6 @@ export const POPULAR_WALLETS = [
},
];
-const SUPPORTED_WALLETS = [
- {
- name: "Coinbase Wallet",
- iconURL:
- "https://explorer-api.walletconnect.com/v3/logo/sm/a5ebc364-8f91-4200-fcc6-be81310a0000?projectId=2f05ae7f1116030fde2d36508f472bfb",
- customScheme: "cbwallet://",
- thirdwebId: "com.coinbase.wallet",
- },
- {
- name: "Ledger Live",
- walletConnectId:
- "19177a98252e07ddfc9af2083ba8e07ef627cb6103467ffebb3f8f4205fd7927",
- iconURL:
- "https://explorer-api.walletconnect.com/v3/logo/sm/a7f416de-aa03-4c5e-3280-ab49269aef00?projectId=2f05ae7f1116030fde2d36508f472bfb",
- customScheme: "ledgerlive://",
- thirdwebId: "com.ledger",
- },
- {
- name: "Rainbow",
- walletConnectId:
- "1ae92b26df02f0abca6304df07debccd18262fdf5fe82daa81593582dac9a369",
- iconURL:
- "https://explorer-api.walletconnect.com/v3/logo/sm/7a33d7f1-3d12-4b5c-f3ee-5cd83cb1b500?projectId=2f05ae7f1116030fde2d36508f472bfb",
- customScheme: "rainbow://",
- thirdwebId: "me.rainbow",
- // Rainbow Mobile does not support tesnets (even Sepolia)
- // https://rainbow.me/en/support/app/testnets
- supportedChains: [
- ethereum,
- base,
- optimism,
- polygon,
- arbitrum,
- avalanche,
- blast,
- zora,
- ],
- },
- {
- name: "MetaMask",
- walletConnectId:
- "c57ca95b47569778a828d19178114f4db188b89b763c899ba0be274e97267d96",
- iconURL:
- "https://explorer-api.walletconnect.com/v3/logo/sm/018b2d52-10e9-4158-1fde-a5d5bac5aa00?projectId=2f05ae7f1116030fde2d36508f472bfb",
- customScheme: "metamask://",
- thirdwebId: "io.metamask",
- },
- {
- name: "Trust Wallet",
- walletConnectId:
- "4622a2b2d6af1c9844944291e5e7351a6aa24cd7b23099efac1b2fd875da31a0",
- iconURL:
- "https://explorer-api.walletconnect.com/v3/logo/sm/0528ee7e-16d1-4089-21e3-bbfb41933100?projectId=2f05ae7f1116030fde2d36508f472bfb",
- customScheme: "trust://",
- thirdwebId: "com.trustwallet.app",
- },
- {
- name: "Uniswap Wallet",
- walletConnectId:
- "c03dfee351b6fcc421b4494ea33b9d4b92a984f87aa76d1663bb28705e95034a",
- iconURL:
- "https://explorer-api.walletconnect.com/v3/logo/sm/bff9cf1f-df19-42ce-f62a-87f04df13c00?projectId=2f05ae7f1116030fde2d36508f472bfb",
- customScheme: "uniswap://",
- thirdwebId: "org.uniswap",
- },
- {
- name: "Zerion",
- walletConnectId:
- "ecc4036f814562b41a5268adc86270fba1365471402006302e70169465b7ac18",
- iconURL:
- "https://explorer-api.walletconnect.com/v3/logo/sm/73f6f52f-7862-49e7-bb85-ba93ab72cc00?projectId=2f05ae7f1116030fde2d36508f472bfb",
- customScheme: "zerion://",
- thirdwebId: "io.zerion.wallet",
- },
- {
- name: "Exodus",
- walletConnectId:
- "e9ff15be73584489ca4a66f64d32c4537711797e30b6660dbcb71ea72a42b1f4",
- iconURL:
- "https://explorer-api.walletconnect.com/v3/logo/sm/4c16cad4-cac9-4643-6726-c696efaf5200?projectId=2f05ae7f1116030fde2d36508f472bfb",
- customScheme: "exodus://",
- universalLink: "https://exodus.com/m",
- thirdwebId: "com.exodus",
- },
- // {
- // name: "1inch Wallet",
- // walletConnectId:
- // "c286eebc742a537cd1d6818363e9dc53b21759a1e8e5d9b263d0c03ec7703576",
- // iconURL:
- // "https://explorer-api.walletconnect.com/v3/logo/sm/52b1da3c-9e72-40ae-5dac-6142addd9c00?projectId=2f05ae7f1116030fde2d36508f472bfb",
- // customScheme: "oneinch://",
- // universalLink: "https://wallet.1inch.io",
- // },
-];
-
-type ISupportedWalletName =
- | (typeof SUPPORTED_WALLETS)[number]["name"]
- | "EthOS Wallet";
-
-export type InstalledWallet = {
- name: ISupportedWalletName;
- iconURL: string;
- customScheme?: string;
- universalLink?: string;
- walletConnectId?: string;
- platforms?: string[];
- thirdwebId?: WalletId;
- supportedChains?: Chain[];
-};
-
let hasCheckedInstalled = false;
export let installedWallets: InstalledWallet[] = [];
@@ -174,7 +65,9 @@ export const getInstalledWallets = async (
): Promise => {
if (hasCheckedInstalled && !refresh) return installedWallets;
const checkInstalled = await Promise.all(
- SUPPORTED_WALLETS.map((w) => Linking.canOpenURL(`${w.customScheme}wc`))
+ Object.values(SUPPORTED_WALLETS).map(
+ (w) => !!w.customScheme && Linking.canOpenURL(`${w.customScheme}wc`)
+ )
);
const wallets: InstalledWallet[] = [];
@@ -187,8 +80,8 @@ export const getInstalledWallets = async (
}
wallets.push(
- ...(SUPPORTED_WALLETS as InstalledWallet[]).filter(
- (w, i) => checkInstalled[i]
+ ...(Object.values(SUPPORTED_WALLETS) as InstalledWallet[]).filter(
+ (w, i) => checkInstalled[i] || w.isSmartContractWallet
)
);
installedWallets = wallets;
diff --git a/components/Onboarding/ConnectViaWallet/ConnectViaWalletTableViewItems.tsx b/components/Onboarding/ConnectViaWallet/ConnectViaWalletTableViewItems.tsx
index 70795071f..d9706ae3c 100644
--- a/components/Onboarding/ConnectViaWallet/ConnectViaWalletTableViewItems.tsx
+++ b/components/Onboarding/ConnectViaWallet/ConnectViaWalletTableViewItems.tsx
@@ -1,5 +1,6 @@
+import AsyncStorage from "@react-native-async-storage/async-storage";
// TODO: move out of ConnectViaWallet
-import { memo, useState } from "react";
+import { memo, useCallback, useEffect, useRef, useState } from "react";
import { ActivityIndicator } from "react-native";
import {
useDisconnect as useThirdwebDisconnect,
@@ -7,22 +8,19 @@ import {
useConnect as useThirdwebConnect,
useActiveWallet as useThirdwebActiveWallet,
} from "thirdweb/react";
-import { createWallet } from "thirdweb/wallets";
-import {
- InstalledWallet,
- useInstalledWallets,
-} from "./ConnectViaWalletSupportedWallets";
+import { useInstalledWallets } from "./ConnectViaWalletSupportedWallets";
import config from "../../../config";
import { getAccountsList } from "../../../data/store/accountsStore";
import { useAppStateHandlers } from "../../../hooks/useAppStateHandlers";
import { translate } from "../../../i18n";
import { getEthOSSigner } from "../../../utils/ethos";
import logger from "../../../utils/logger";
-import { thirdwebClient } from "../../../utils/thirdweb";
+import { thirdwebClient, thirdwebWallets } from "../../../utils/thirdweb";
import TableView, { TableViewItemType } from "../../TableView/TableView";
import { TableViewEmoji, TableViewImage } from "../../TableView/TableViewImage";
import { RightViewChevron } from "../../TableView/TableViewRightChevron";
+import { InstalledWallet, ISupportedWalletName } from "@utils/evm/wallets";
export function getConnectViaWalletTableViewPrivateKeyItem(
args: Partial
@@ -79,7 +77,7 @@ export function getConnectViaWalletInstalledWalletTableViewItem(args: {
export const InstalledWalletsTableView = memo(
function InstalledWalletsTableView(props: {
onAccountExists: (arg: { address: string }) => void;
- onAccountDoesNotExist: (arg: { address: string }) => void;
+ onAccountDoesNotExist: (arg: { address: string; isSCW: boolean }) => void;
}) {
const { onAccountExists, onAccountDoesNotExist } = props;
@@ -87,9 +85,24 @@ export const InstalledWalletsTableView = memo(
const { connect: thirdwebConnect } = useThirdwebConnect();
const { disconnect: disconnectThirdweb } = useThirdwebDisconnect();
+
const thirdwebActiveWallet = useThirdwebActiveWallet();
+ const thirdwebActiveWalletRef = useRef(thirdwebActiveWallet);
+ useEffect(() => {
+ thirdwebActiveWalletRef.current = thirdwebActiveWallet;
+ }, [thirdwebActiveWallet]);
+
const setThirdwebActiveWallet = useSetThirdwebActiveWallet();
+ const disconnectActiveThirdweb = useCallback(async () => {
+ if (!thirdwebActiveWalletRef.current) return;
+ disconnectThirdweb(thirdwebActiveWalletRef.current);
+ // Wait for the disconnect to complete
+ while (!!thirdwebActiveWalletRef.current) {
+ await new Promise((r) => setTimeout(r, 100));
+ }
+ }, [disconnectThirdweb]);
+
const [isProcessingWalletId, setIsProcessingWalletId] = useState<
string | null
>(null);
@@ -117,38 +130,35 @@ export const InstalledWalletsTableView = memo(
walletName: wallet.name,
}),
action: async () => {
+ const isSCW = !!wallet?.isSmartContractWallet;
logger.debug(
- `[Onboarding] Clicked on wallet ${wallet.name} - opening external app`
+ `[Onboarding] Clicked on wallet ${wallet.name} - ${
+ isSCW ? "Opening web page" : "opening external app"
+ }`
);
setIsProcessingWalletId(wallet.name);
- if (thirdwebActiveWallet) {
- disconnectThirdweb(thirdwebActiveWallet);
- }
+ await disconnectActiveThirdweb();
try {
let walletAddress: string = "";
// Specific flow for Coinbase Wallet
- if (wallet.name === "Coinbase Wallet") {
- const wallet = await thirdwebConnect(async () => {
- const coinbaseWallet = createWallet("com.coinbase.wallet", {
- appMetadata: config.walletConnectConfig.appMetadata,
- mobileConfig: {
- callbackURL: `https://${config.websiteDomain}/coinbase`,
- },
- });
+ if (wallet.thirdwebId === "com.coinbase.wallet") {
+ const thirdwebWallet = await thirdwebConnect(async () => {
+ const coinbaseWallet =
+ thirdwebWallets[wallet.name as ISupportedWalletName];
await coinbaseWallet.connect({ client: thirdwebClient });
setThirdwebActiveWallet(coinbaseWallet);
return coinbaseWallet;
});
- if (!wallet) {
+ if (!thirdwebWallet) {
throw new Error("No coinbase wallet");
}
- const account = wallet.getAccount();
+ const account = thirdwebWallet.getAccount();
if (!account) {
throw new Error("No coinbase account found");
@@ -166,7 +176,8 @@ export const InstalledWalletsTableView = memo(
}
// Generic flow for all other wallets
else if (wallet.thirdwebId) {
- const walletConnectWallet = createWallet(wallet.thirdwebId);
+ const walletConnectWallet =
+ thirdwebWallets[wallet.name as ISupportedWalletName];
const account = await walletConnectWallet.connect({
client: thirdwebClient,
walletConnect: config.walletConnectConfig,
@@ -179,7 +190,10 @@ export const InstalledWalletsTableView = memo(
if (getAccountsList().includes(walletAddress)) {
onAccountExists({ address: walletAddress });
} else {
- onAccountDoesNotExist({ address: walletAddress });
+ onAccountDoesNotExist({
+ address: walletAddress,
+ isSCW,
+ });
}
} catch (e: any) {
logger.error("Error connecting to wallet:", e);
diff --git a/components/Onboarding/ConnectViaWallet/useConnectViaWalletInitXmtpClient.tsx b/components/Onboarding/ConnectViaWallet/useConnectViaWalletInitXmtpClient.tsx
index f4df8a0a2..914a91eec 100644
--- a/components/Onboarding/ConnectViaWallet/useConnectViaWalletInitXmtpClient.tsx
+++ b/components/Onboarding/ConnectViaWallet/useConnectViaWalletInitXmtpClient.tsx
@@ -53,6 +53,7 @@ export function useInitXmptClient() {
const signer = connectViewWalletStore.getState().signer!; // We can assume that signer is set at this point
const address = connectViewWalletStore.getState().address!; // We can assume that address is set at this point
const alreadyV3Db = connectViewWalletStore.getState().alreadyV3Db;
+ const isSCW = connectViewWalletStore.getState().isSCW;
const waitForClickSignature = async () => {
while (!connectViewWalletStore.getState().clickedSignature) {
@@ -70,6 +71,7 @@ export function useInitXmptClient() {
await createXmtpClientFromSigner(
signer,
+ isSCW,
async () => {
logger.debug("[Connect Wallet] Installation revoked, disconnecting");
try {
diff --git a/components/Onboarding/ConnectViaWallet/useInitConnectViaWalletState.ts b/components/Onboarding/ConnectViaWallet/useInitConnectViaWalletState.ts
index 16c700156..9353721a7 100644
--- a/components/Onboarding/ConnectViaWallet/useInitConnectViaWalletState.ts
+++ b/components/Onboarding/ConnectViaWallet/useInitConnectViaWalletState.ts
@@ -19,8 +19,11 @@ import { getInboxId } from "@utils/xmtpRN/signIn";
* But ideally a lot of this it outside React.
* This function is for initializing the state of the wallet. V3? XMTP? etc...
*/
-export function useInitConnectViaWalletState(args: { address: string }) {
- const { address } = args;
+export function useInitConnectViaWalletState(args: {
+ address: string;
+ isSCW: boolean;
+}) {
+ const { address, isSCW } = args;
const { onErrorConnecting } = useConnectViaWalletContext();
@@ -91,7 +94,9 @@ export function useInitConnectViaWalletState(args: { address: string }) {
setSigner(thirdwebSigner);
logger.debug(
- `[Connect Wallet] User connected wallet ${thirdwebWallet?.id} (${address}). V3 database ${hasV3 ? "already" : "not"} present`
+ `[Connect Wallet] User connected ${
+ isSCW ? "SCW" : "EOA"
+ } wallet ${thirdwebWallet?.id} (${address}). V3 database ${hasV3 ? "already" : "not"} present`
);
} catch (error) {
logger.error("[Connect Wallet] Error initializing wallet:", error);
@@ -109,6 +114,7 @@ export function useInitConnectViaWalletState(args: { address: string }) {
setIsInitializing,
thirdwebWallet,
thirdwebSigner,
+ isSCW,
onErrorConnecting,
]);
diff --git a/components/Onboarding/init-xmtp-client.ts b/components/Onboarding/init-xmtp-client.ts
index ec41fbb51..f1afbd8a9 100644
--- a/components/Onboarding/init-xmtp-client.ts
+++ b/components/Onboarding/init-xmtp-client.ts
@@ -30,7 +30,7 @@ export async function initXmtpClient(args: {
}
try {
- await createXmtpClientFromSigner(signer, async () => {
+ await createXmtpClientFromSigner(signer, false, async () => {
await awaitableAlert(
translate("current_installation_revoked"),
translate("current_installation_revoked_description")
diff --git a/components/TransactionPreview/TransactionActions.tsx b/components/TransactionPreview/TransactionActions.tsx
index dfcddab56..0e0f276c5 100644
--- a/components/TransactionPreview/TransactionActions.tsx
+++ b/components/TransactionPreview/TransactionActions.tsx
@@ -1,4 +1,3 @@
-import { InstalledWallet } from "@components/Onboarding/ConnectViaWallet/ConnectViaWalletSupportedWallets";
import { Text } from "@design-system/Text";
import { TouchableOpacity } from "@design-system/TouchableOpacity";
import { VStack } from "@design-system/VStack";
@@ -7,7 +6,7 @@ import { spacing } from "@theme/spacing";
import { TransactionToTrigger } from "./TransactionPreview";
import TransactionNext from "../../assets/transaction-next.svg";
-import { CHAIN_BY_ID } from "@utils/evm/wallets";
+import { CHAIN_BY_ID, InstalledWallet } from "@utils/evm/wallets";
type ITransactionActionsProps = {
shouldSwitchChain?: boolean;
diff --git a/components/TransactionPreview/TransactionContent.tsx b/components/TransactionPreview/TransactionContent.tsx
index 08d93ef62..9ae224f4a 100644
--- a/components/TransactionPreview/TransactionContent.tsx
+++ b/components/TransactionPreview/TransactionContent.tsx
@@ -1,4 +1,3 @@
-import { InstalledWallet } from "@components/Onboarding/ConnectViaWallet/ConnectViaWalletSupportedWallets";
import { translate } from "@i18n";
import { SimulateAssetChangesResponse } from "alchemy-sdk";
@@ -10,6 +9,7 @@ import { TransactionPreviewRow } from "./TransactionPreviewRow";
import { SimulationResult } from "./TransactionSimulationResult";
import { TransactionResult } from "./TransactionResult";
import { shortAddress } from "@utils/strings/shortAddress";
+import { InstalledWallet } from "@/utils/evm/wallets";
type TransactionState = {
status: "pending" | "triggering" | "triggered" | "success" | "failure";
diff --git a/features/ExternalWalletPicker/ExternalWalletPicker.tsx b/features/ExternalWalletPicker/ExternalWalletPicker.tsx
index 81de8dc6e..7640419ab 100644
--- a/features/ExternalWalletPicker/ExternalWalletPicker.tsx
+++ b/features/ExternalWalletPicker/ExternalWalletPicker.tsx
@@ -11,19 +11,17 @@ import { $globalStyles } from "@theme/styles";
import { ThemedStyle, useAppTheme } from "@theme/useAppTheme";
import { waitUntilAppActive } from "@utils/appState/waitUntilAppActive";
import { ensureError } from "@utils/error";
-import { thirdwebClient } from "@utils/thirdweb";
+import { thirdwebClient, thirdwebWallets } from "@utils/thirdweb";
import { Image } from "expo-image";
import { useCallback } from "react";
import { Alert, ViewStyle } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
-import { Account, createWallet, Wallet } from "thirdweb/wallets";
+import { Account, Wallet } from "thirdweb/wallets";
import { useExternalWalletPickerContext } from "./ExternalWalletPicker.context";
-import {
- InstalledWallet,
- useInstalledWallets,
-} from "../../components/Onboarding/ConnectViaWallet/ConnectViaWalletSupportedWallets";
+import { useInstalledWallets } from "../../components/Onboarding/ConnectViaWallet/ConnectViaWalletSupportedWallets";
import config from "../../config";
+import { InstalledWallet, ISupportedWalletName } from "@utils/evm/wallets";
type IExternalWalletPickerProps = {
title?: string;
@@ -50,20 +48,10 @@ export function ExternalWalletPicker(props: IExternalWalletPickerProps) {
throw new Error("No installed wallet could be found");
}
- let wallet: Wallet | undefined;
+ const wallet =
+ thirdwebWallets[installedWallet.name as ISupportedWalletName];
let account: Account | undefined;
- if (installedWallet.name === "Coinbase Wallet") {
- wallet = createWallet("com.coinbase.wallet", {
- appMetadata: config.walletConnectConfig.appMetadata,
- mobileConfig: {
- callbackURL: `https://${config.websiteDomain}/coinbase`,
- },
- });
- } else if (installedWallet.thirdwebId) {
- wallet = createWallet(installedWallet.thirdwebId);
- }
-
if (!wallet) {
throw new Error("Wallet could not be created");
}
diff --git a/ios/Podfile b/ios/Podfile
index efd6f949f..254fded31 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -11,13 +11,13 @@ require 'json'
podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {}
plugin 'cocoapods-pod-linkage'
-platform :ios, podfile_properties['ios.deploymentTarget'] || '14.0'
+platform :ios, podfile_properties['ios.deploymentTarget'] || '15.6'
install! 'cocoapods',
:deterministic_uuids => false
# Version must match version from XMTP Podspec (matching @xmtp/react-native-sdk from package.json)
-# https://github.com/xmtp/xmtp-react-native/blob/v2.6.2/ios/XMTPReactNative.podspec#L29
+# https://github.com/xmtp/xmtp-react-native/blob/v3.0.6/ios/XMTPReactNative.podspec#L29
$xmtpVersion = '3.0.6'
# Pinning MMKV to 1.3.3 that has included that fix https://github.com/Tencent/MMKV/pull/1222#issuecomment-1905164314
@@ -64,7 +64,7 @@ target 'Converse' do
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['SWIFT_VERSION'] = '5.0'
- config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0'
+ config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.6'
config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
end
end
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index ecf87f213..c740989df 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -2868,8 +2868,8 @@ SPEC CHECKSUMS:
web3.swift: 2263d1e12e121b2c42ffb63a5a7beb1acaf33959
XMTP: 48d0c71ef732ac4d79c2942902a132bf71661029
XMTPReactNative: 080300cc2cb53ffd117d2808c4d9922357ce1d34
- Yoga: b05994d1933f507b0a28ceaa4fdb968dc18da178
+ Yoga: a9ef4f5c2cd79ad812110525ef61048be6a582a4
-PODFILE CHECKSUM: 8de80dcb8fc027dda19e4b9af8900634e49f9b40
+PODFILE CHECKSUM: e3b306b0081266b928275fbd16d3483e5a0ae250
COCOAPODS: 1.16.2
diff --git a/navigation/OnboardingNavigator.tsx b/navigation/OnboardingNavigator.tsx
index baa29e63e..8e58fb93c 100644
--- a/navigation/OnboardingNavigator.tsx
+++ b/navigation/OnboardingNavigator.tsx
@@ -17,6 +17,7 @@ type OnboardingParamList = {
OnboardingPrivy: undefined;
OnboardingConnectWallet: {
address: string;
+ isSCW: boolean;
};
OnboardingNotifications: undefined;
OnboardingUserProfile: undefined;
diff --git a/polyfills.ts b/polyfills.ts
index b21d8c4b2..c9b30e6c4 100644
--- a/polyfills.ts
+++ b/polyfills.ts
@@ -9,6 +9,7 @@ import "@azure/core-asynciterator-polyfill";
import "react-native-polyfill-globals/auto";
import { Crypto as WebCrypto } from "@peculiar/webcrypto";
import "@stardazed/streams-polyfill";
+import { NativeModules } from "react-native";
// Necessary for @peculiar/webcrypto.
if (!global.Buffer) {
@@ -19,3 +20,26 @@ if (!global.crypto.subtle) {
const webCrypto = new WebCrypto();
(global.crypto as any).subtle = webCrypto.subtle;
}
+
+function randomUUID() {
+ if (NativeModules.RandomUuid) {
+ return NativeModules.RandomUuid.getRandomUuid();
+ }
+
+ // Expo SDK 48+
+ if (
+ global.expo &&
+ global.expo.modules &&
+ global.expo.modules.ExpoCrypto &&
+ global.expo.modules.ExpoCrypto.randomUUID
+ ) {
+ // ExpoCrypto.randomUUID() sometimes returns uppercase UUIDs, so we convert them to lowercase
+ return global.expo.modules.ExpoCrypto.randomUUID().toLowerCase();
+ }
+
+ throw new Error("No random UUID available");
+}
+
+if (typeof global.crypto.randomUUID !== "function") {
+ global.crypto.randomUUID = randomUUID;
+}
diff --git a/screens/Navigation/Navigation.tsx b/screens/Navigation/Navigation.tsx
index 480c4c1f9..daba159e6 100644
--- a/screens/Navigation/Navigation.tsx
+++ b/screens/Navigation/Navigation.tsx
@@ -53,6 +53,7 @@ export type NavigationParamList = {
OnboardingPrivy: undefined;
OnboardingConnectWallet: {
address: string;
+ isSCW: boolean;
};
OnboardingPrivateKey: undefined;
OnboardingNotifications: undefined;
@@ -64,6 +65,7 @@ export type NavigationParamList = {
NewAccountUserProfile: undefined;
NewAccountConnectWallet: {
address: string;
+ isSCW: boolean;
};
NewAccountPrivy: undefined;
NewAccountPrivateKey: undefined;
diff --git a/screens/NewAccount/NewAccountConnectWalletScreen.tsx b/screens/NewAccount/NewAccountConnectWalletScreen.tsx
index afa30cf53..d7294b2b6 100644
--- a/screens/NewAccount/NewAccountConnectWalletScreen.tsx
+++ b/screens/NewAccount/NewAccountConnectWalletScreen.tsx
@@ -11,7 +11,7 @@ export const NewAccountConnectWalletScreen = memo(
function NewAccountConnectWalletScreen({
route,
}: NativeStackScreenProps) {
- const { address } = route.params;
+ const { address, isSCW } = route.params;
const router = useRouter();
@@ -40,6 +40,7 @@ export const NewAccountConnectWalletScreen = memo(
{/* For now we don't need to have specific stuff for onboarding vs new account so we use this component to encapsulate the connect view wallet logic */}
diff --git a/screens/NewAccount/NewAccountScreen.tsx b/screens/NewAccount/NewAccountScreen.tsx
index 04e76355c..f440e23a3 100644
--- a/screens/NewAccount/NewAccountScreen.tsx
+++ b/screens/NewAccount/NewAccountScreen.tsx
@@ -64,8 +64,8 @@ export const NewAccountScreen = memo(function NewAccountScreen() {
// TODO: Add a better message
Alert.alert("Account already connected");
}}
- onAccountDoesNotExist={({ address }) => {
- router.navigate("NewAccountConnectWallet", { address });
+ onAccountDoesNotExist={({ address, isSCW }) => {
+ router.navigate("NewAccountConnectWallet", { address, isSCW });
}}
/>
)}
diff --git a/screens/Onboarding/OnboardingConnectWalletScreen.tsx b/screens/Onboarding/OnboardingConnectWalletScreen.tsx
index 1871de6b5..234f8a045 100644
--- a/screens/Onboarding/OnboardingConnectWalletScreen.tsx
+++ b/screens/Onboarding/OnboardingConnectWalletScreen.tsx
@@ -19,8 +19,7 @@ export const OnboardingConnectWalletScreen = memo(
"OnboardingConnectWallet"
>
) {
- const { address } = props.route.params;
-
+ const { address, isSCW } = props.route.params;
const router = useRouter();
const handleDoneConnecting = useCallback(() => {
@@ -45,6 +44,7 @@ export const OnboardingConnectWalletScreen = memo(
{/* For now we don't need to have specific stuff for onboarding vs new account so we use this component to encapsulate the connect view wallet logic */}
diff --git a/screens/Onboarding/OnboardingGetStartedScreen.tsx b/screens/Onboarding/OnboardingGetStartedScreen.tsx
index 45ef0c76e..0b024c687 100644
--- a/screens/Onboarding/OnboardingGetStartedScreen.tsx
+++ b/screens/Onboarding/OnboardingGetStartedScreen.tsx
@@ -85,8 +85,8 @@ export function OnboardingGetStartedScreen() {
status: "signedIn",
});
}}
- onAccountDoesNotExist={({ address }) => {
- router.navigate("OnboardingConnectWallet", { address });
+ onAccountDoesNotExist={({ address, isSCW }) => {
+ router.navigate("OnboardingConnectWallet", { address, isSCW });
}}
/>
diff --git a/utils/date.test.ts b/utils/date.test.ts
index 609190325..556065fb8 100644
--- a/utils/date.test.ts
+++ b/utils/date.test.ts
@@ -1,8 +1,7 @@
-import { format } from "date-fns";
+import { format, enUS, fr } from "date-fns";
import { getLocales } from "react-native-localize";
import { getMinimalDate, getRelativeDate, getRelativeDateTime } from "./date";
-import { enUS, fr } from "date-fns/locale";
jest.mock("react-native-localize", () => ({
getLocales: jest.fn(),
diff --git a/utils/evm/external.ts b/utils/evm/external.ts
index 0886476ef..bd36e22e2 100644
--- a/utils/evm/external.ts
+++ b/utils/evm/external.ts
@@ -2,7 +2,7 @@ import { useInstalledWallets } from "@components/Onboarding/ConnectViaWallet/Con
import { translate } from "@i18n";
import logger from "@utils/logger";
import { sentryTrackError } from "@utils/sentry";
-import { thirdwebClient } from "@utils/thirdweb";
+import { thirdwebClient, thirdwebWallets } from "@utils/thirdweb";
import { Signer } from "ethers";
import { useCallback, useEffect, useMemo } from "react";
import { Alert } from "react-native";
@@ -183,15 +183,6 @@ export const useAutoConnectExternalWallet = () => {
// thirdweb external wallet
useAutoConnect({
client: thirdwebClient,
- // The Coinbase wallet callbackURL needs to be set
- // here also for autoconnect to work
- wallets: [
- createWallet("com.coinbase.wallet", {
- appMetadata: config.walletConnectConfig.appMetadata,
- mobileConfig: {
- callbackURL: `https://${config.websiteDomain}/coinbase`,
- },
- }),
- ],
+ wallets: Object.values(thirdwebWallets),
});
};
diff --git a/utils/evm/wallets.ts b/utils/evm/wallets.ts
index b8f9263c6..319d83502 100644
--- a/utils/evm/wallets.ts
+++ b/utils/evm/wallets.ts
@@ -19,7 +19,9 @@ import {
scroll,
mantaPacific,
xai,
+ Chain,
} from "thirdweb/chains";
+import { WalletId } from "thirdweb/wallets";
export const DEFAULT_SUPPORTED_CHAINS = [
ethereum,
@@ -50,3 +52,113 @@ export const CHAIN_BY_ID = DEFAULT_SUPPORTED_CHAINS.reduce(
},
{} as Record
);
+const _SUPPORTED_WALLETS = {
+ // Keep this one first to enable auto-connect without conflict with Coinbase Wallet
+ "Coinbase Smart Wallet": {
+ iconURL:
+ "https://explorer-api.walletconnect.com/v3/logo/sm/a5ebc364-8f91-4200-fcc6-be81310a0000?projectId=2f05ae7f1116030fde2d36508f472bfb",
+ thirdwebId: "com.coinbase.wallet",
+ isSmartContractWallet: true,
+ },
+ "Coinbase Wallet": {
+ iconURL:
+ "https://explorer-api.walletconnect.com/v3/logo/sm/a5ebc364-8f91-4200-fcc6-be81310a0000?projectId=2f05ae7f1116030fde2d36508f472bfb",
+ customScheme: "cbwallet://",
+ thirdwebId: "com.coinbase.wallet",
+ },
+ "Ledger Live": {
+ walletConnectId:
+ "19177a98252e07ddfc9af2083ba8e07ef627cb6103467ffebb3f8f4205fd7927",
+ iconURL:
+ "https://explorer-api.walletconnect.com/v3/logo/sm/a7f416de-aa03-4c5e-3280-ab49269aef00?projectId=2f05ae7f1116030fde2d36508f472bfb",
+ customScheme: "ledgerlive://",
+ thirdwebId: "com.ledger",
+ },
+ Rainbow: {
+ walletConnectId:
+ "1ae92b26df02f0abca6304df07debccd18262fdf5fe82daa81593582dac9a369",
+ iconURL:
+ "https://explorer-api.walletconnect.com/v3/logo/sm/7a33d7f1-3d12-4b5c-f3ee-5cd83cb1b500?projectId=2f05ae7f1116030fde2d36508f472bfb",
+ customScheme: "rainbow://",
+ thirdwebId: "me.rainbow",
+ // Rainbow Mobile does not support tesnets (even Sepolia)
+ // https://rainbow.me/en/support/app/testnets
+ supportedChains: [
+ ethereum,
+ base,
+ optimism,
+ polygon,
+ arbitrum,
+ avalanche,
+ blast,
+ zora,
+ ],
+ },
+ MetaMask: {
+ walletConnectId:
+ "c57ca95b47569778a828d19178114f4db188b89b763c899ba0be274e97267d96",
+ iconURL:
+ "https://explorer-api.walletconnect.com/v3/logo/sm/018b2d52-10e9-4158-1fde-a5d5bac5aa00?projectId=2f05ae7f1116030fde2d36508f472bfb",
+ customScheme: "metamask://",
+ thirdwebId: "io.metamask",
+ },
+ "Trust Wallet": {
+ walletConnectId:
+ "4622a2b2d6af1c9844944291e5e7351a6aa24cd7b23099efac1b2fd875da31a0",
+ iconURL:
+ "https://explorer-api.walletconnect.com/v3/logo/sm/0528ee7e-16d1-4089-21e3-bbfb41933100?projectId=2f05ae7f1116030fde2d36508f472bfb",
+ customScheme: "trust://",
+ thirdwebId: "com.trustwallet.app",
+ },
+ "Uniswap Wallet": {
+ walletConnectId:
+ "c03dfee351b6fcc421b4494ea33b9d4b92a984f87aa76d1663bb28705e95034a",
+ iconURL:
+ "https://explorer-api.walletconnect.com/v3/logo/sm/bff9cf1f-df19-42ce-f62a-87f04df13c00?projectId=2f05ae7f1116030fde2d36508f472bfb",
+ customScheme: "uniswap://",
+ thirdwebId: "org.uniswap",
+ },
+ Zerion: {
+ walletConnectId:
+ "ecc4036f814562b41a5268adc86270fba1365471402006302e70169465b7ac18",
+ iconURL:
+ "https://explorer-api.walletconnect.com/v3/logo/sm/73f6f52f-7862-49e7-bb85-ba93ab72cc00?projectId=2f05ae7f1116030fde2d36508f472bfb",
+ customScheme: "zerion://",
+ thirdwebId: "io.zerion.wallet",
+ },
+ Exodus: {
+ walletConnectId:
+ "e9ff15be73584489ca4a66f64d32c4537711797e30b6660dbcb71ea72a42b1f4",
+ iconURL:
+ "https://explorer-api.walletconnect.com/v3/logo/sm/4c16cad4-cac9-4643-6726-c696efaf5200?projectId=2f05ae7f1116030fde2d36508f472bfb",
+ customScheme: "exodus://",
+ universalLink: "https://exodus.com/m",
+ thirdwebId: "com.exodus",
+ },
+};
+
+export type ISupportedWalletName =
+ | keyof typeof _SUPPORTED_WALLETS
+ | "EthOS Wallet";
+
+export const SUPPORTED_WALLETS = Object.fromEntries(
+ Object.entries(_SUPPORTED_WALLETS).map(([name, config]) => [
+ name,
+ {
+ name,
+ ...config,
+ },
+ ])
+) as Record;
+
+export type InstalledWallet = {
+ name: string;
+ iconURL: string;
+ customScheme?: string;
+ universalLink?: string;
+ walletConnectId?: string;
+ platforms?: string[];
+ thirdwebId?: WalletId;
+ isSmartContractWallet?: boolean;
+ supportedChains?: Chain[];
+};
diff --git a/utils/thirdweb.ts b/utils/thirdweb.ts
index 9cd0dfc9b..ea12e0b97 100644
--- a/utils/thirdweb.ts
+++ b/utils/thirdweb.ts
@@ -1,6 +1,8 @@
import { createThirdwebClient } from "thirdweb";
import config from "../config";
+import { createWallet, Wallet, WalletId } from "thirdweb/wallets";
+import { ISupportedWalletName, SUPPORTED_WALLETS } from "./evm/wallets";
const thirdwebGateway = `https://${config.thirdwebClientId}.ipfscdn.io`;
@@ -18,3 +20,37 @@ export const getIPFSAssetURI = (ipfsURI?: string) => {
export const thirdwebClient = createThirdwebClient({
clientId: config.thirdwebClientId,
});
+
+export const thirdwebWallets: Record =
+ Object.fromEntries(
+ Object.entries(SUPPORTED_WALLETS)
+ .filter(([_, walletConfig]) => !!walletConfig.thirdwebId)
+ .map(([walletName, walletConfig]) => {
+ let wallet: Wallet;
+ if (walletName === "Coinbase Smart Wallet") {
+ wallet = createWallet("com.coinbase.wallet", {
+ appMetadata: config.walletConnectConfig.appMetadata,
+ mobileConfig: {
+ callbackURL: `https://${config.websiteDomain}`,
+ },
+ walletConfig: {
+ options: "smartWalletOnly",
+ },
+ });
+ } else if (walletName === "Coinbase Wallet") {
+ wallet = createWallet("com.coinbase.wallet", {
+ appMetadata: config.walletConnectConfig.appMetadata,
+ mobileConfig: {
+ callbackURL: `https://${config.websiteDomain}/coinbase`,
+ },
+ walletConfig: {
+ options: "eoaOnly",
+ },
+ });
+ } else {
+ wallet = createWallet(walletConfig.thirdwebId!);
+ }
+
+ return [walletName, wallet];
+ })
+ ) as Record;
diff --git a/utils/xmtpRN/signIn.ts b/utils/xmtpRN/signIn.ts
index 04dec513a..4ddd879e1 100644
--- a/utils/xmtpRN/signIn.ts
+++ b/utils/xmtpRN/signIn.ts
@@ -20,6 +20,7 @@ export const getInboxId = (address: string) =>
export const createXmtpClientFromSigner = async (
signer: Signer,
+ isSCW: boolean,
onInstallationRevoked: () => Promise,
preAuthenticateToInboxCallback?: () => Promise
) => {
@@ -32,13 +33,14 @@ export const createXmtpClientFromSigner = async (
dbDirectory: tempDirectory,
dbEncryptionKey,
};
+
const inboxId = await getInboxId(await signer.getAddress());
await copyDatabasesToTemporaryDirectory(tempDirectory, inboxId);
logger.debug("Instantiating client from signer");
- const client = await Client.create(ethersSignerToXmtpSigner(signer), {
+ const client = await Client.create(ethersSignerToXmtpSigner(signer, isSCW), {
...options,
preAuthenticateToInboxCallback,
});