Describe the bug
When connecting from a mobile browser (Android Chrome) using the EOA/WalletLink flow, the dapp tab gets stuck on "awaiting confirmation" after the user approves the connection in the Coinbase Wallet app. The user must manually return to Chrome, then attempt the connection a second time for it to succeed. Root cause: Android aggressively suspends WebSocket connections when Chrome is backgrounded (while the user is in the Coinbase Wallet app). When the tab becomes visible again, setupVisibilityChangeHandler in WalletLinkConnection.ts calls reconnectWithFreshWebSocket() which must complete a full WS handshake before fetchUnseenEventsAPI() is called. The approval event already sitting on Coinbase's server is missed during this race. On the second attempt, the event is found because the session is already cached.
Steps
- Open any dapp in Android Chrome (tested on Chrome 124+, Android 13/14)
- Select BASE chain and click "Connect Wallet"
- Choose "Coinbase Wallet" from the wallet selector
- The keys.coinbase.com relay tab opens — tap "Open Coinbase Wallet"
- Coinbase Wallet app opens — tap "Allow" to approve the connection
- Tap "Done" in the wallet app
- Return manually to Chrome
- Dapp still shows "Awaiting Confirmation" — never resolves
- Close the modal, try connecting again — now it works immediately
Expected behavior
After approving in the Coinbase Wallet app and returning to Chrome, the dapp should immediately show the wallet as connected without requiring a second attempt.
Version
4.x (latest master — confirmed in WalletLinkConnection.ts and WalletLinkRelay.ts)
Additional info
Two fixes are needed. Both are minimal, isolated changes:
FIX 1 — WalletLinkConnection.ts
File: packages/wallet-sdk/src/sign/walletlink/relay/connection/WalletLinkConnection.ts
Method: setupVisibilityChangeHandler()
Add an immediate fetchUnseenEventsAPI() call when the tab becomes visible, BEFORE waiting for WS reconnection. This is a plain HTTPS REST call that does not require an active WebSocket, so it can resolve the pending approval event instantly.
BEFORE:
this.visibilityChangeHandler = () => {
if (!document.hidden && !this.destroyed) {
if (!this.connected) {
this.reconnectWithFreshWebSocket();
} else {
this.heartbeat();
}
}
};
AFTER:
this.visibilityChangeHandler = () => {
if (!document.hidden && !this.destroyed) {
// Immediately poll for events that arrived while backgrounded.
// fetchUnseenEventsAPI is a plain HTTPS call — no WS needed.
// This catches the approval event Android dropped from the WS
// while Chrome was backgrounded, without waiting for reconnection.
this.fetchUnseenEventsAPI().catch(() => {});
if (!this.connected) {
this.reconnectWithFreshWebSocket();
} else {
this.heartbeat();
}
}
};
FIX 2 — WalletLinkRelay.ts (bonus fix for transaction signing same issue)
File: packages/wallet-sdk/src/sign/walletlink/relay/WalletLinkRelay.ts
Method: openCoinbaseWalletDeeplink()
The blur/focus { once: true } listeners are consumed by intermediate UI events (the redirect dialog overlay, keys.coinbase.com tab focus) before the user returns from the wallet app. Replace with visibilitychange which fires exactly once when the tab is foregrounded.
BEFORE:
default:
window.addEventListener(
'blur',
() => {
window.addEventListener(
'focus',
() => {
this.connection.checkUnseenEvents();
},
{ once: true }
);
},
{ once: true }
);
this.ui.openCoinbaseWalletDeeplink();
break;
AFTER:
default:
const handleVisibilityChange = () => {
if (document.visibilityState === 'visible') {
document.removeEventListener('visibilitychange', handleVisibilityChange);
this.connection.checkUnseenEvents();
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
this.ui.openCoinbaseWalletDeeplink();
break;
Desktop
No response
Smartphone
- Device: Android (reproduced on multiple devices)
- OS: Android 13 / 14
- Browser: Google Chrome (mobile)
- Version: Chrome 124+
Describe the bug
When connecting from a mobile browser (Android Chrome) using the EOA/WalletLink flow, the dapp tab gets stuck on "awaiting confirmation" after the user approves the connection in the Coinbase Wallet app. The user must manually return to Chrome, then attempt the connection a second time for it to succeed. Root cause: Android aggressively suspends WebSocket connections when Chrome is backgrounded (while the user is in the Coinbase Wallet app). When the tab becomes visible again,
setupVisibilityChangeHandlerinWalletLinkConnection.tscallsreconnectWithFreshWebSocket()which must complete a full WS handshake beforefetchUnseenEventsAPI()is called. The approval event already sitting on Coinbase's server is missed during this race. On the second attempt, the event is found because the session is already cached.Steps
Expected behavior
After approving in the Coinbase Wallet app and returning to Chrome, the dapp should immediately show the wallet as connected without requiring a second attempt.
Version
4.x (latest master — confirmed in WalletLinkConnection.ts and WalletLinkRelay.ts)
Additional info
Two fixes are needed. Both are minimal, isolated changes:
FIX 1 — WalletLinkConnection.ts
File: packages/wallet-sdk/src/sign/walletlink/relay/connection/WalletLinkConnection.ts
Method: setupVisibilityChangeHandler()
Add an immediate fetchUnseenEventsAPI() call when the tab becomes visible, BEFORE waiting for WS reconnection. This is a plain HTTPS REST call that does not require an active WebSocket, so it can resolve the pending approval event instantly.
BEFORE:
this.visibilityChangeHandler = () => {
if (!document.hidden && !this.destroyed) {
if (!this.connected) {
this.reconnectWithFreshWebSocket();
} else {
this.heartbeat();
}
}
};
AFTER:
this.visibilityChangeHandler = () => {
if (!document.hidden && !this.destroyed) {
// Immediately poll for events that arrived while backgrounded.
// fetchUnseenEventsAPI is a plain HTTPS call — no WS needed.
// This catches the approval event Android dropped from the WS
// while Chrome was backgrounded, without waiting for reconnection.
this.fetchUnseenEventsAPI().catch(() => {});
};
FIX 2 — WalletLinkRelay.ts (bonus fix for transaction signing same issue)
File: packages/wallet-sdk/src/sign/walletlink/relay/WalletLinkRelay.ts
Method: openCoinbaseWalletDeeplink()
The blur/focus { once: true } listeners are consumed by intermediate UI events (the redirect dialog overlay, keys.coinbase.com tab focus) before the user returns from the wallet app. Replace with visibilitychange which fires exactly once when the tab is foregrounded.
BEFORE:
default:
window.addEventListener(
'blur',
() => {
window.addEventListener(
'focus',
() => {
this.connection.checkUnseenEvents();
},
{ once: true }
);
},
{ once: true }
);
this.ui.openCoinbaseWalletDeeplink();
break;
AFTER:
default:
const handleVisibilityChange = () => {
if (document.visibilityState === 'visible') {
document.removeEventListener('visibilitychange', handleVisibilityChange);
this.connection.checkUnseenEvents();
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
this.ui.openCoinbaseWalletDeeplink();
break;
Desktop
No response
Smartphone