Wallet: recover from a dead WalletConnect session instead of failing the buy

Probe the session after connect and retry once after a "disconnected" send
error by wiping the stale session and re-opening the picker (Trust kills the
session after its own security stop).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-09 06:41:09 -05:00
parent 15a29af104
commit 68d68b1ebf
3 changed files with 31 additions and 3 deletions
+28
View File
@@ -85,6 +85,24 @@ window.IAPWallet = (function () {
function eth() { if (!provider) throw new Error('Connect your wallet first.'); return provider; } function eth() { if (!provider) throw new Error('Connect your wallet first.'); return provider; }
// A WalletConnect session can die underneath AppKit's cached "connected"
// state: the wallet app rejects or kills it (Trust does this after its own
// security stop), the phone sleeps, the relay drops. AppKit still reports an
// address, so the next request fails with a "disconnected" style error.
// Detect that, wipe the stale session, and re-open the picker for a fresh one.
const DEAD_RE = /disconnect|not connected|no matching key|session (topic|expired|deleted|not found)|call connect|please call connect|missing or invalid|relay/i;
const isDead = e => DEAD_RE.test(String((e && e.message) || e || ''));
async function freshConnect(c) {
try { IAP.status('Your wallet session dropped. Reconnect in the picker…'); } catch (e) {}
await disconnect();
try { await modal.open(); } catch (e) {}
const addr = await waitForConnection(180000);
try { if (modal && modal.close) await modal.close(); } catch (e) {}
provider = await resolveProvider();
await ensureChain(c).catch(() => {});
return addr;
}
async function connect() { async function connect() {
const c = await IAP.getConfig(); const c = await IAP.getConfig();
await initAppKit(c); await initAppKit(c);
@@ -97,6 +115,9 @@ window.IAPWallet = (function () {
if (!addr) { try { await modal.open(); } catch (e) {} addr = await waitForConnection(180000); } if (!addr) { try { await modal.open(); } catch (e) {} addr = await waitForConnection(180000); }
try { if (modal && modal.close) await modal.close(); } catch (e) {} // dismiss the picker once we're connected try { if (modal && modal.close) await modal.close(); } catch (e) {} // dismiss the picker once we're connected
provider = await resolveProvider(); provider = await resolveProvider();
// probe the session: a dead WalletConnect session answers with a disconnect error
try { await provider.request({ method: 'eth_chainId' }); }
catch (e) { if (isDead(e)) return freshConnect(c); }
await ensureChain(c).catch(() => {}); // AppKit already connects on the right network; switch is best-effort await ensureChain(c).catch(() => {}); // AppKit already connects on the right network; switch is best-effort
return addr; return addr;
} }
@@ -159,8 +180,15 @@ window.IAPWallet = (function () {
tx.maxFeePerGas = g.maxFeePerGas; tx.maxFeePerGas = g.maxFeePerGas;
} }
} catch (e) {} } catch (e) {}
try {
return await eth().request({ method: 'eth_sendTransaction', params: [tx] });
} catch (e) {
if (!isDead(e)) throw e;
// session died between connect and send: reconnect once and resend
tx.from = await freshConnect(c);
return eth().request({ method: 'eth_sendTransaction', params: [tx] }); return eth().request({ method: 'eth_sendTransaction', params: [tx] });
} }
}
async function waitTx(hash) { async function waitTx(hash) {
for (let i = 0; i < 90; i++) { for (let i = 0; i < 90; i++) {
+1 -1
View File
@@ -439,7 +439,7 @@
</section> </section>
<script src="/assets/common.js?v=20260909a"></script> <script src="/assets/common.js?v=20260909a"></script>
<script src="/assets/wallet.js?v=20260908t"></script> <script src="/assets/wallet.js?v=20260909a"></script>
<script src="/assets/home.js?v=20260906m"></script> <script src="/assets/home.js?v=20260906m"></script>
<script src="/assets/chat.js?v=20260906m"></script> <script src="/assets/chat.js?v=20260906m"></script>
</body> </body>
+1 -1
View File
@@ -735,7 +735,7 @@
</div> </div>
</div> </div>
<script src="/assets/common.js?v=20260909a"></script> <script src="/assets/common.js?v=20260909a"></script>
<script src="/assets/wallet.js?v=20260908t"></script> <script src="/assets/wallet.js?v=20260909a"></script>
<script src="/assets/promo.js?v=20260909b"></script> <script src="/assets/promo.js?v=20260909b"></script>
<script src="/assets/my.js?v=20260909e"></script> <script src="/assets/my.js?v=20260909e"></script>
<script src="/assets/chat.js?v=20260907l"></script> <script src="/assets/chat.js?v=20260907l"></script>