diff --git a/messages.js b/messages.js
index b0f42a2..b95d8d9 100644
--- a/messages.js
+++ b/messages.js
@@ -80,7 +80,10 @@ function verifyChallenge(address, signature) {
if (!ch || ch.exp < Date.now()) return { error: 'Challenge expired - tap sign-in again.' };
let rec;
try { rec = recoverAddress(ch.message, signature); } catch (e) { return { error: 'Invalid signature: ' + e.message }; }
- if (rec !== a) return { error: 'Signature does not match this wallet.' };
+ if (rec !== a) return { error: 'Your wallet signed with a different account than the page is using. '
+ + 'The page asked for ' + a.slice(0, 6) + '…' + a.slice(-4) + ' but the signature came from '
+ + rec.slice(0, 6) + '…' + rec.slice(-4) + '. Switch your wallet to the account that owns this '
+ + 'position, reload the page, then tap sign-in again.', signer: rec, expected: a };
challenges.delete(a);
const id = chain.memberIdByAccount(a);
if (!id) return { error: 'No RM Circle position is registered to this wallet.' };
diff --git a/public/wallet-check.html b/public/wallet-check.html
new file mode 100644
index 0000000..ad7a672
--- /dev/null
+++ b/public/wallet-check.html
@@ -0,0 +1,29 @@
+
This page doesn't sign anything or ask for anything. It just reports what your browser can see, so we can work out why sign-in isn't working. Open it the same way you were opening the dashboard, then screenshot this whole page.
+
+
+
+
+
+
+
collecting…
+
+
+
+
diff --git a/public/wallet-check.js b/public/wallet-check.js
new file mode 100644
index 0000000..b07ff15
--- /dev/null
+++ b/public/wallet-check.js
@@ -0,0 +1,80 @@
+// Diagnostic page for wallet sign-in failures. Reports what the page can
+// actually see, from the member's own device, instead of us guessing.
+//
+// It captures the one thing we cannot infer from the outside: whether a CSP
+// violation is firing when the wallet tries to inject its provider. Our
+// script-src is 'self' with no 'unsafe-inline', and wallet browsers that inject
+// via a