Skip to content

Commit 2b35582

Browse files
authored
Merge pull request #22 from tokenhost/feat/ui-metamask-dx
UI DX: make MetaMask connect + network switching more reliable
2 parents 84d7b3a + da815e0 commit 2b35582

1 file changed

Lines changed: 86 additions & 26 deletions

File tree

  • packages/templates/next-export-ui/src/lib

packages/templates/next-export-ui/src/lib/clients.ts

Lines changed: 86 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -41,45 +41,105 @@ export function makeWalletClient(chain: Chain): any {
4141
});
4242
}
4343

44+
function extractErrorCode(e: any): string | number | null {
45+
const codes = [
46+
e?.code,
47+
e?.cause?.code,
48+
e?.cause?.cause?.code,
49+
e?.data?.code,
50+
e?.cause?.data?.code,
51+
e?.cause?.cause?.data?.code
52+
];
53+
54+
for (const c of codes) {
55+
if (c === undefined || c === null) continue;
56+
return c;
57+
}
58+
return null;
59+
}
60+
61+
function extractErrorMessage(e: any): string {
62+
const parts = [
63+
e?.shortMessage,
64+
e?.message,
65+
e?.cause?.message,
66+
e?.cause?.cause?.message
67+
]
68+
.filter(Boolean)
69+
.map(String);
70+
71+
if (parts.length > 0) return parts.join(' | ');
72+
try {
73+
return JSON.stringify(e);
74+
} catch {
75+
return String(e ?? '');
76+
}
77+
}
78+
79+
function isUserRejected(e: any): boolean {
80+
const code = extractErrorCode(e);
81+
const msg = extractErrorMessage(e);
82+
return String(code) === '4001' || /user rejected|rejected the request/i.test(msg);
83+
}
84+
4485
export async function requestWalletAddress(chain: Chain): Promise<`0x${string}`> {
4586
const wallet = makeWalletClient(chain);
87+
88+
// Connect first. Some wallets behave better if the dapp is already connected before switching networks.
89+
let addr: `0x${string}` | undefined;
90+
try {
91+
const addrs = await wallet.requestAddresses();
92+
addr = addrs?.[0];
93+
} catch (e: any) {
94+
if (isUserRejected(e)) {
95+
throw new Error('Wallet connection was rejected. Please approve the wallet connection prompt and retry.');
96+
}
97+
throw new Error(`Wallet connection failed. ${extractErrorMessage(e)}`);
98+
}
99+
100+
if (!addr) throw new Error('No wallet address returned.');
101+
46102
const currentChainId = await wallet.getChainId();
47103
if (currentChainId !== chain.id) {
104+
const rpcUrl = resolveRpcUrl(chain);
105+
const manualHint = rpcUrl
106+
? `In MetaMask, add/switch to "${chain.name}" (chainId ${chain.id}) with RPC URL ${rpcUrl}.`
107+
: `Switch networks in your wallet to chainId ${chain.id}.`;
108+
48109
try {
49110
await wallet.switchChain({ id: chain.id });
50-
} catch (e: any) {
51-
const code = e?.cause?.code ?? e?.code ?? null;
52-
const msg = String(e?.shortMessage ?? e?.message ?? e ?? '');
53-
54-
// MetaMask uses 4902 for "Unrecognized chain" when switching to a chain that hasn't been added.
55-
const looksUnrecognized =
56-
String(code) === '4902' ||
57-
/4902/.test(msg) ||
58-
/unrecognized chain|unknown chain|not added/i.test(msg);
59-
60-
if (looksUnrecognized) {
61-
try {
62-
// Attempt to add the chain to the wallet, then retry the switch.
63-
await wallet.addChain({ chain });
64-
await wallet.switchChain({ id: chain.id });
65-
} catch (e2: any) {
66-
const msg2 = String(e2?.shortMessage ?? e2?.message ?? e2 ?? '');
111+
} catch (e1: any) {
112+
if (isUserRejected(e1)) {
113+
throw new Error(
114+
`Wrong network. Wallet is on chainId ${currentChainId} but this app's primary deployment is chainId ${chain.id}. ` +
115+
`You rejected the network switch request. Please approve it and retry.`
116+
);
117+
}
118+
119+
// Many wallets don't reliably surface the "unknown chain" code/message.
120+
// Try add+switch as a best-effort fallback.
121+
try {
122+
await wallet.addChain({ chain });
123+
} catch (eAdd: any) {
124+
if (isUserRejected(eAdd)) {
67125
throw new Error(
68126
`Wrong network. Wallet is on chainId ${currentChainId} but this app's primary deployment is chainId ${chain.id}. ` +
69-
`We tried to add/switch the chain automatically but it failed. ` +
70-
`Add/switch networks in your wallet and retry. ${msg2 ? `(${msg2})` : ''}`
127+
`You rejected the request to add the network. ${manualHint}`
71128
);
72129
}
130+
// Ignore other addChain errors and still retry switching; the chain may already exist.
73131
}
74132

75-
throw new Error(
76-
`Wrong network. Wallet is on chainId ${currentChainId} but this app's primary deployment is chainId ${chain.id}. ` +
77-
`Switch networks in your wallet and retry. ${msg ? `(${msg})` : ''}`
78-
);
133+
try {
134+
await wallet.switchChain({ id: chain.id });
135+
} catch (e2: any) {
136+
throw new Error(
137+
`Wrong network. Wallet is on chainId ${currentChainId} but this app's primary deployment is chainId ${chain.id}. ` +
138+
`Automatic network switch failed. ${manualHint} (${extractErrorMessage(e2)})`
139+
);
140+
}
79141
}
80142
}
81-
const addrs = await wallet.requestAddresses();
82-
const addr = addrs?.[0];
83-
if (!addr) throw new Error('No wallet address returned.');
143+
84144
return addr;
85145
}

0 commit comments

Comments
 (0)