refactor: improve Coinbase connection handling (#3340)

### TL;DR

To prevent popups from Coinbase wallet being blocked in Safari, pre-connection actions have been added. Specifically, popups will be displayed immediately upon connection request and before transaction preprocessing.

### What changed?

1. Removed the `COINBASE` constant and its related connection flow.
2. Integrated `onConnectRequested` optional function for wallets.
3. Updated Coinbase wallet's `onConnectRequested` to show the popup immediately.
4. Added `onTransactionRequested` to show popup before transaction processing.

### How to test?

1. Initiate connection with Coinbase wallet and observe that the popup appears immediately.
2. Perform a transaction with Coinbase wallet and ensure the popup is not blocked.
3. Test across different browsers, focusing on Safari.

### Why make this change?

Safari has stricter rules regarding popups, and the previous delay in showing the popup led to it being blocked. This change ensures a smoother user experience for Coinbase wallet users on Safari by addressing the popup blocking issue.

---

<!-- start pr-codex -->

---

## PR-Codex overview
The focus of this PR is to enhance wallet connectivity and transaction handling in the application.

### Detailed summary
- Added `onConnectRequested` and `onTransactionRequested` functions to wallet interfaces
- Updated wallet connection and transaction handling logic
- Preloaded wallet providers for smoother user experience

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
This commit is contained in:
joaquim-verges
2024-06-16 23:02:22 +00:00
parent 17c0e6f282
commit df84f9c4ce
9 changed files with 66 additions and 30 deletions
@@ -1,9 +1,9 @@
import { getContract } from "thirdweb";
import { zkSyncSepolia } from "thirdweb/chains";
import { baseSepolia } from "thirdweb/chains";
import { THIRDWEB_CLIENT } from "../../lib/client";
export const chain = zkSyncSepolia;
export const editionDropAddress = "0xd563ACBeD80e63B257B2524562BdD7Ec666eEE77";
export const chain = baseSepolia;
export const editionDropAddress = "0x638263e3eAa3917a53630e61B1fBa685308024fa";
export const editionDropTokenId = 0n;
export const editionDropContract = getContract({
@@ -50,7 +50,7 @@ export function SponsoredTxPreview() {
/>
) : null}
{smartAccount ? (
<>
<div className="flex flex-col justify-center p-8">
<p className="font-semibold text-center mb-2">
You own {ownedNfts?.[0]?.quantityOwned.toString() || "0"}{" "}
Kittens
@@ -73,7 +73,7 @@ export function SponsoredTxPreview() {
>
Mint
</TransactionButton>
</>
</div>
) : (
<p
style={{
@@ -1,6 +1,5 @@
"use client";
import { Suspense, lazy, useCallback } from "react";
import { COINBASE } from "../../../../../wallets/constants.js";
import type { Wallet } from "../../../../../wallets/interfaces/wallet.js";
import { useSiweAuth } from "../../../../core/hooks/auth/useSiweAuth.js";
import { useConnectUI } from "../../../../core/hooks/others/useWalletConnectionCtx.js";
@@ -42,7 +41,6 @@ export const ConnectModalContent = (props: {
connectModal,
connectLocale,
client,
chain,
} = useConnectUI();
const setActiveWallet = useSetActiveWallet();
const setSelectionData = useSetSelectionData();
@@ -97,23 +95,13 @@ export const ConnectModalContent = (props: {
setScreen(reservedScreens.getStarted);
}}
selectWallet={(newWallet) => {
setScreen(newWallet);
// When selecting Coinbase, fire off the connection immediately
// this fixes an issue on safari where the CB popup window would get blocked
// because of the delay between this click and the actual connection
// that happens within 'CoinbaseSDKConnection' component 2 layers down
// when the cb extension is installed -> pops the extension
// when the extension is not installed -> pops a new window
// then in 'CoinbaseSDKConnection' the connection is requested again
// which just "links" to the extension / popup window a few ms later
// and handle the completion / error handling there
// this connection flow structure needs to be refactored to properly handle this
if (newWallet.id === COINBASE) {
newWallet.connect({
client,
chain,
});
if (newWallet.onConnectRequested) {
newWallet
.onConnectRequested()
.then(() => setScreen(newWallet))
.catch(console.error); // TODO propagate error down
} else {
setScreen(newWallet);
}
}}
onShowAll={() => {
@@ -2,7 +2,7 @@
import { useEffect, useState } from "react";
import type { ThirdwebClient } from "../../../../client/client.js";
import { webLocalStorage } from "../../../../utils/storage/webStorage.js";
import { getInstalledWalletProviders } from "../../../../wallets/injected/mipdStore.js";
import { getWalletInfo } from "../../../../wallets/__generated__/getWalletInfo.js";
import { getStoredActiveWalletId } from "../../../../wallets/manager/index.js";
import type { WalletId } from "../../../../wallets/wallet-types.js";
import { radius } from "../../../core/design-system/index.js";
@@ -47,9 +47,7 @@ export function WalletImage(props: {
activeEOAId = storedId;
}
}
let mipdImage = getInstalledWalletProviders().find(
(provider) => provider.info.rdns === activeEOAId,
)?.info.icon;
let mipdImage: string | undefined;
if (
activeEOAId === "inApp" &&
@@ -78,6 +76,8 @@ export function WalletImage(props: {
mipdImage = passkeyIcon;
break;
}
} else {
mipdImage = await getWalletInfo(activeEOAId, true);
}
setImage(mipdImage);
@@ -33,6 +33,10 @@ export async function sendTransaction(
): Promise<WaitForReceiptOptions> {
const { account, transaction, gasless } = options;
if (account.onTransactionRequested) {
await account.onTransactionRequested(transaction);
}
const serializableTransaction = await toSerializableTransaction({
transaction: transaction,
from: account.address,
@@ -104,5 +104,16 @@ export function coinbaseWalletSDK(args: {
switchChain: async (newChain) => {
await handleSwitchChain(newChain);
},
onConnectRequested: async () => {
// make sure to show the coinbase popup IMMEDIATELY on connection requested
// otherwise the popup might get blocked in safari
// TODO these 2 awaits are fast only thanks to preloading that happens in our components
// these probably need to actually imported / created synchronously to be used headless properly
const { getCoinbaseWebProvider, showCoinbasePopup } = await import(
"./coinbaseSDKWallet.js"
);
const provider = await getCoinbaseWebProvider(createOptions);
await showCoinbasePopup(provider);
},
};
}
@@ -334,11 +334,21 @@ function createAccount(provider: ProviderInterface, address: string) {
params: [account.address, stringifiedData],
});
},
onTransactionRequested: async () => {
// make sure to show the coinbase popup BEFORE doing any transaction preprocessing
// otherwise the popup might get blocked in safari
await showCoinbasePopup(provider);
},
};
return account;
}
export async function showCoinbasePopup(provider: ProviderInterface) {
// biome-ignore lint/suspicious/noExplicitAny: based on the latest CB SDK - scary but works
await (provider as any).communicator?.waitForPopupLoaded();
}
function onConnect(
address: string,
chain: Chain,
@@ -123,6 +123,13 @@ export type Wallet<TWalletId extends WalletId = WalletId> = {
* ```
*/
getConfig: () => CreateWalletArgs<TWalletId>[1];
// OPTIONAL
/**
* Can be used to execute any pre-connection actions like showing a modal, etc.
*/
onConnectRequested?: () => Promise<void>;
};
/**
@@ -223,4 +230,12 @@ export type Account = {
sendRawTransaction?: (
tx: SendRawTransactionOptions,
) => Promise<SendTransactionResult>;
/**
* Used to do any pre-transaction actions like showing a confirmation modal, etc.
* @returns
*/
onTransactionRequested?: (
// biome-ignore lint/suspicious/noExplicitAny: any transaction type is allowed here
transaction: PreparedTransaction<any>,
) => Promise<void>;
};
+10 -2
View File
@@ -162,7 +162,7 @@ async function createSmartAccount(
options: SmartAccountOptions,
): Promise<Account> {
const { accountContract } = options;
const account = {
const account: Account = {
address: accountContract.address,
async sendTransaction(transaction: SendTransactionOption) {
const executeTx = prepareExecute({
@@ -353,6 +353,9 @@ async function createSmartAccount(
"Unable to verify signature on smart account, please make sure the smart account is deployed and the signature is valid.",
);
},
async onTransactionRequested(transaction) {
return options.personalAccount.onTransactionRequested?.(transaction);
},
};
return account;
}
@@ -364,7 +367,7 @@ function createZkSyncAccount(args: {
sponsorGas: boolean;
}): Account {
const { creationOptions, connectionOptions, chain } = args;
const account = {
const account: Account = {
address: connectionOptions.personalAccount.address,
async sendTransaction(transaction: SendTransactionOption) {
// override passed tx, we have to refetch gas and fees always
@@ -430,6 +433,11 @@ function createZkSyncAccount(args: {
const typedData = parseTypedData(_typedData);
return connectionOptions.personalAccount.signTypedData(typedData);
},
async onTransactionRequested(transaction) {
return connectionOptions.personalAccount.onTransactionRequested?.(
transaction,
);
},
};
return account;
}