Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Noe/scw 3.0 #1310

Open
wants to merge 27 commits into
base: release/3.0.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
514754d
upgrade thirdweb
nmalzieu Oct 17, 2024
082f2d3
WIP: coinbase smart wallet connection
nmalzieu Oct 17, 2024
36cebd2
Trying to login with SCW - basic
nmalzieu Oct 17, 2024
b159d5e
Trying to login with SCW - basic
nmalzieu Oct 18, 2024
3a42530
Keep testing SCW
nmalzieu Oct 21, 2024
86fe21c
SCW work with crypto polyfill
nmalzieu Oct 28, 2024
df7d582
Podfile update
nmalzieu Oct 30, 2024
ca1a1eb
Disconnect SCW
nmalzieu Oct 30, 2024
99e745d
SCW Login almost complete
nmalzieu Oct 30, 2024
475b91c
Ability to login with SCW, detect SCWOnly
nmalzieu Oct 30, 2024
2ad857c
Merge branch 'release/2.0.8' into noe/scw-login-new
nmalzieu Nov 12, 2024
0bd0ad4
Fix SCW autoconnect
nmalzieu Nov 12, 2024
71f4ab5
Cleaning up thirdweb createWallet() methods, in a single palce
nmalzieu Nov 12, 2024
1b360f7
Add the android MWP config
nmalzieu Nov 12, 2024
189b209
Use the universal link for Coinbase Smart Wallet as well
nmalzieu Nov 12, 2024
54a32a7
Merge branch 'release/3.0.0' into noe/scw-login-new
nmalzieu Nov 18, 2024
12ee316
Fix universal links for mobile wallet protocol
nmalzieu Nov 19, 2024
92829e4
building
nmalzieu Dec 4, 2024
d996dbf
WIP merge 3.0 SCW
nmalzieu Dec 4, 2024
848100f
Make SCW login work
nmalzieu Dec 4, 2024
dfb4b74
Merge branch 'release/3.0.0' into noe/scw-3.0
nmalzieu Dec 5, 2024
795003e
Merge branch 'release/3.0.0' into noe/scw-3.0
nmalzieu Dec 5, 2024
a430c74
Podfile
nmalzieu Dec 5, 2024
a31242d
remove playground scw
nmalzieu Dec 6, 2024
2df37ea
fix the disconnect method
nmalzieu Dec 6, 2024
80a0bdb
Thirdweb wallets cleanup
nmalzieu Dec 6, 2024
1f05d86
Upgrade cocoapods
nmalzieu Dec 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@
<data android:host="dev.converse.xyz" android:pathPrefix="/group" android:scheme="https"/>
<data android:host="dev.getconverse.app" android:pathPrefix="/coinbase" android:scheme="https"/>
<data android:host="dev.converse.xyz" android:pathPrefix="/coinbase" android:scheme="https"/>
<data android:host="dev.getconverse.app" android:pathPrefix="/mobile-wallet-protocol" android:scheme="https"/>
<data android:host="dev.converse.xyz" android:pathPrefix="/mobile-wallet-protocol" android:scheme="https"/>
<data android:host="dev.getconverse.app" android:pathPrefix="/desktopconnect" android:scheme="https"/>
<data android:host="dev.converse.xyz" android:pathPrefix="/desktopconnect" android:scheme="https"/>
</intent-filter>
Expand Down
2 changes: 2 additions & 0 deletions android/app/src/preview/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
<data android:host="preview.converse.xyz" android:pathPrefix="/group" android:scheme="https"/>
<data android:host="preview.getconverse.app" android:pathPrefix="/coinbase" android:scheme="https"/>
<data android:host="preview.converse.xyz" android:pathPrefix="/coinbase" android:scheme="https"/>
<data android:host="preview.getconverse.app" android:pathPrefix="/mobile-wallet-protocol" android:scheme="https"/>
<data android:host="preview.converse.xyz" android:pathPrefix="/mobile-wallet-protocol" android:scheme="https"/>
<data android:host="preview.getconverse.app" android:pathPrefix="/desktopconnect" android:scheme="https"/>
<data android:host="preview.converse.xyz" android:pathPrefix="/desktopconnect" android:scheme="https"/>
</intent-filter>
Expand Down
2 changes: 2 additions & 0 deletions android/app/src/prod/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
<data android:host="converse.xyz" android:pathPrefix="/group" android:scheme="https"/>
<data android:host="getconverse.app" android:pathPrefix="/coinbase" android:scheme="https"/>
<data android:host="converse.xyz" android:pathPrefix="/coinbase" android:scheme="https"/>
<data android:host="getconverse.app" android:pathPrefix="/mobile-wallet-protocol" android:scheme="https"/>
<data android:host="converse.xyz" android:pathPrefix="/mobile-wallet-protocol" android:scheme="https"/>
<data android:host="getconverse.app" android:pathPrefix="/desktopconnect" android:scheme="https"/>
<data android:host="converse.xyz" android:pathPrefix="/desktopconnect" android:scheme="https"/>
</intent-filter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type IConnectViaWalletStoreProps = {
address: string;
alreadyV3Db: boolean;
signer: Signer;
isSCW: boolean;
};

type IConnectViaWalletStoreProviderProps =
Expand Down Expand Up @@ -42,6 +43,7 @@ const createConnectViaWalletStore = (props: IConnectViaWalletStoreProps) =>
createStore<IConnectViaWalletStoreState>()((set) => ({
address: props.address,
signer: props.signer,
isSCW: props.isSCW,
alreadyV3Db: props.alreadyV3Db,
loading: false,
numberOfSignaturesDone: 0,
Expand Down
16 changes: 12 additions & 4 deletions components/Onboarding/ConnectViaWallet/ConnectViaWallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ import { useInitConnectViaWalletState } from "./useInitConnectViaWalletState";

type IConnectViaWalletProps = {
address: string;
isSCW: boolean;
onDoneConnecting: () => void;
onErrorConnecting: (arg: { error: Error }) => void;
};

export const ConnectViaWallet = memo(function ConnectViaWallet(
props: IConnectViaWalletProps
) {
const { address, onErrorConnecting, onDoneConnecting } = props;
const { address, isSCW, onErrorConnecting, onDoneConnecting } = props;

const finishedConnectingRef = useRef(false);

Expand Down Expand Up @@ -71,16 +72,22 @@ export const ConnectViaWallet = memo(function ConnectViaWallet(
onDoneConnecting={handleDoneConnecting}
onErrorConnecting={handleErrorConnecting}
>
<ConnectViaWalletStateWrapper address={address} />
<ConnectViaWalletStateWrapper address={address} isSCW={isSCW} />
</ConnectViaWalletContextProvider>
);
});

// 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 <LoadingState />;
Expand All @@ -94,6 +101,7 @@ const ConnectViaWalletStateWrapper = memo(
return (
<ConnectViaWalletStoreProvider
address={address}
isSCW={isSCW}
alreadyV3Db={alreadyV3Db}
signer={signer}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
{
Expand Down Expand Up @@ -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[] = [];

Expand All @@ -174,7 +65,9 @@ export const getInstalledWallets = async (
): Promise<InstalledWallet[]> => {
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[] = [];

Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
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,
useSetActiveWallet as useSetThirdwebActiveWallet,
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<TableViewItemType>
Expand Down Expand Up @@ -79,17 +77,32 @@ 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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Update all usages of onAccountDoesNotExist to include isSCW

The onAccountDoesNotExist callback now includes an additional parameter isSCW: boolean. Ensure that all implementations and calls to this function across the codebase are updated to pass this new parameter.

}) {
const { onAccountExists, onAccountDoesNotExist } = props;

const walletsInstalled = useInstalledWallets();

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]);
Comment on lines +97 to +104
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve the disconnection polling implementation

The current implementation has several concerns:

  1. The while loop with setTimeout is not the most efficient way to handle disconnection
  2. There's no timeout limit which could lead to infinite polling
  3. The double negation in the condition is unnecessary

Consider this improved implementation:

 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));
-  }
+  // Wait for disconnect with timeout
+  const maxAttempts = 50; // 5 seconds total
+  let attempts = 0;
+  while (thirdwebActiveWalletRef.current && attempts < maxAttempts) {
+    await new Promise((r) => setTimeout(r, 100));
+    attempts++;
+  }
+  if (thirdwebActiveWalletRef.current) {
+    logger.warn('Wallet disconnect timeout exceeded');
+  }
 }, [disconnectThirdweb]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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 disconnectActiveThirdweb = useCallback(async () => {
if (!thirdwebActiveWalletRef.current) return;
disconnectThirdweb(thirdwebActiveWalletRef.current);
// Wait for disconnect with timeout
const maxAttempts = 50; // 5 seconds total
let attempts = 0;
while (thirdwebActiveWalletRef.current && attempts < maxAttempts) {
await new Promise((r) => setTimeout(r, 100));
attempts++;
}
if (thirdwebActiveWalletRef.current) {
logger.warn('Wallet disconnect timeout exceeded');
}
}, [disconnectThirdweb]);
🧰 Tools
🪛 Biome (1.9.4)

[error] 101-102: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


const [isProcessingWalletId, setIsProcessingWalletId] = useState<
string | null
>(null);
Expand Down Expand Up @@ -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"
technoplato marked this conversation as resolved.
Show resolved Hide resolved
}`
);

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");
Expand All @@ -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,
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -70,6 +71,7 @@ export function useInitXmptClient() {

await createXmtpClientFromSigner(
signer,
isSCW,
async () => {
logger.debug("[Connect Wallet] Installation revoked, disconnecting");
try {
Expand Down
Loading
Loading