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:
@@ -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>;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user