src/content/inpage.js rebuilds every rejection as p.reject(new Error(error.message || "Request failed")), dropping code. A dApp checking err.code === 4001 — the standard way to distinguish "the user said no" from "something broke" — sees undefined.
The code is produced correctly and does cross the messaging boundary; the e2e harness added by #273 asserts it is present there. It is lost in the last hop, in the provider the page actually talks to.
RULES.md requires the code. EIP-1193 defines 4001 as the user-rejection code and dApps branch on it: without it, a wallet that the user deliberately declined is indistinguishable from a wallet that failed, and well-behaved sites will show an error or retry instead of accepting the refusal.
Found by the e2e work on #183, which prints the observed code every run and deliberately does not assert the current behaviour either way, so this issue's fix can flip that assertion on.
Implementation requirements
Reject with an error object carrying code — and data where present — taken from the boundary response, not a bare Error with only a message.
Cover every request path the provider exposes, not just eth_requestAccounts.
Check what a non-rejection error should carry: EIP-1193 defines other codes (4100 unauthorized, 4200 unsupported method, 4900/4901 disconnected) and some are already produced upstream. Do not special-case 4001 alone.
Add a unit test on the provider itself, and flip the existing e2e probe from printing the code to REQUIRING it.
Definition of done
A user rejection reaches the page as an error with code === 4001.
Other EIP-1193 codes produced upstream reach the page intact.
message is unchanged for every case.
The e2e assertion in tests/e2e/run.js requires the code rather than printing it, and fails without the fix.
TODO.md updated in the same commit.
make check passes.
`src/content/inpage.js` rebuilds every rejection as `p.reject(new Error(error.message || "Request failed"))`, dropping `code`. A dApp checking `err.code === 4001` — the standard way to distinguish "the user said no" from "something broke" — sees `undefined`.
The code is produced correctly and does cross the messaging boundary; the e2e harness added by https://git.eeqj.de/sneak/AutistMask/pulls/273 asserts it is present there. It is lost in the last hop, in the provider the page actually talks to.
`RULES.md` requires the code. EIP-1193 defines `4001` as the user-rejection code and dApps branch on it: without it, a wallet that the user deliberately declined is indistinguishable from a wallet that failed, and well-behaved sites will show an error or retry instead of accepting the refusal.
Found by the e2e work on https://git.eeqj.de/sneak/AutistMask/issues/183, which prints the observed code every run and deliberately does not assert the current behaviour either way, so this issue's fix can flip that assertion on.
## Implementation requirements
- Reject with an error object carrying `code` — and `data` where present — taken from the boundary response, not a bare `Error` with only a message.
- Cover every request path the provider exposes, not just `eth_requestAccounts`.
- Check what a non-rejection error should carry: EIP-1193 defines other codes (4100 unauthorized, 4200 unsupported method, 4900/4901 disconnected) and some are already produced upstream. Do not special-case 4001 alone.
- Add a unit test on the provider itself, and flip the existing e2e probe from printing the code to REQUIRING it.
## Definition of done
- [ ] A user rejection reaches the page as an error with `code === 4001`.
- [ ] Other EIP-1193 codes produced upstream reach the page intact.
- [ ] `message` is unchanged for every case.
- [ ] The e2e assertion in `tests/e2e/run.js` requires the code rather than printing it, and fails without the fix.
- [ ] `TODO.md` updated in the same commit.
- [ ] `make check` passes.
clawbot
added this to the 1.0.0 milestone 2026-08-12 13:05:58 +02:00
Plan, on branch fix/issue-274-eip1193-error-codes (base next):
Shape: an EIP-1193 ProviderRpcError extends Error defined inside the inpage.js IIFE, with name, code and optional data. A class rather than properties bolted onto an Error because the object crosses no boundary after construction — it is built in the page's own realm and handed straight to the caller's catch — so the prototype survives and name is a stable thing for a dApp to see.
Single choke point: every entry point (request, enable, send, sendAsync) funnels through the one AUTISTMASK_RESPONSE listener, so one toPageError() there covers all of them.
Pass-through, not a whitelist: whatever code arrived is carried verbatim rather than matched against a list of known values. The background emits 4001, 4100 and 4902 on the RPC path today; a code added later must reach the page without editing the provider. An error the background sent with no code stays a plain Error with no code property at all — a ProviderRpcError whose code is undefined would advertise a conformance it does not have. message is untouched in every case.
Nothing in src/background/index.js or src/content/index.js changes.
Tests: a new tests/inpageErrors.test.js loading the real inpage.js against a stub window, plus flipping the four e2e rejection probes in tests/e2e/run.js from printing the code to requiring it.
Plan, on branch `fix/issue-274-eip1193-error-codes` (base `next`):
Shape: an EIP-1193 `ProviderRpcError extends Error` defined inside the `inpage.js` IIFE, with `name`, `code` and optional `data`. A class rather than properties bolted onto an `Error` because the object crosses no boundary after construction — it is built in the page's own realm and handed straight to the caller's `catch` — so the prototype survives and `name` is a stable thing for a dApp to see.
Single choke point: every entry point (`request`, `enable`, `send`, `sendAsync`) funnels through the one `AUTISTMASK_RESPONSE` listener, so one `toPageError()` there covers all of them.
Pass-through, not a whitelist: whatever `code` arrived is carried verbatim rather than matched against a list of known values. The background emits 4001, 4100 and 4902 on the RPC path today; a code added later must reach the page without editing the provider. An error the background sent with no code stays a plain `Error` with no `code` property at all — a `ProviderRpcError` whose `code` is `undefined` would advertise a conformance it does not have. `message` is untouched in every case.
Nothing in `src/background/index.js` or `src/content/index.js` changes.
Tests: a new `tests/inpageErrors.test.js` loading the real `inpage.js` against a stub window, plus flipping the four e2e rejection probes in `tests/e2e/run.js` from printing the code to requiring it.
Built and pushed as #278. Shape rationale and the full before/after output are in the PR body; this is the definition of done against what was verified.
A user rejection reaches the page as code === 4001 — asserted on the page's own Error, not just on the wire, for all four flows (eth_requestAccounts, personal_sign, eth_signTypedData_v4, eth_sendTransaction) in make test-e2e, and in tests/inpageErrors.test.js.
Other codes reach the page intact — the background emits 4001, 4100 and 4902 on the RPC path; all three are covered. The provider passes any code through verbatim rather than matching a list, tested with a 4900 the codebase never produces, so a code added later needs no change here. 4200/4901 are produced nowhere today; nothing was invented for them.
message unchanged — the fallback stays "Request failed", and the e2e records "message":"User rejected the request." byte-identically before and after. An error the background sent with no code stays a plain Error with no code property at all, asserted both ways.
The e2e probe requires the code and fails without the fix — make test-e2e exits 1 with 33/37 and four not ok lines reading reached the page as an error with no code property at all, exit 0 with 37/37 after. Full captured output in the PR.
Unit test on the provider — tests/inpageErrors.test.js, 17 cases, loading the real inpage.js; 12 of them fail with the provider change stashed.
Every request path — request, enable, send(method, params), send({method, params}) and sendAsync, each driven separately.
TODO.md updated in the same commit. make check exit 0: 681 tests, 28 suites, test-verify-build 18/18, prettier clean. Both gates re-run after rebasing onto next at c755a5e.
Confined to src/content/inpage.js and the tests — nothing the background produces changes. One adjacent gap found and filed rather than fixed here: handleRpc() reports an unsupported method with no code where EIP-1193 defines 4200 (#279).
Built and pushed as https://git.eeqj.de/sneak/AutistMask/pulls/278. Shape rationale and the full before/after output are in the PR body; this is the definition of done against what was verified.
- **A user rejection reaches the page as `code === 4001`** — asserted on the page's own Error, not just on the wire, for all four flows (`eth_requestAccounts`, `personal_sign`, `eth_signTypedData_v4`, `eth_sendTransaction`) in `make test-e2e`, and in `tests/inpageErrors.test.js`.
- **Other codes reach the page intact** — the background emits `4001`, `4100` and `4902` on the RPC path; all three are covered. The provider passes any code through verbatim rather than matching a list, tested with a `4900` the codebase never produces, so a code added later needs no change here. `4200`/`4901` are produced nowhere today; nothing was invented for them.
- **`message` unchanged** — the fallback stays `"Request failed"`, and the e2e records `"message":"User rejected the request."` byte-identically before and after. An error the background sent with no code stays a plain `Error` with no `code` property at all, asserted both ways.
- **The e2e probe requires the code and fails without the fix** — `make test-e2e` exits 1 with 33/37 and four `not ok` lines reading `reached the page as an error with no code property at all`, exit 0 with 37/37 after. Full captured output in the PR.
- **Unit test on the provider** — `tests/inpageErrors.test.js`, 17 cases, loading the real `inpage.js`; 12 of them fail with the provider change stashed.
- **Every request path** — `request`, `enable`, `send(method, params)`, `send({method, params})` and `sendAsync`, each driven separately.
- **`TODO.md`** updated in the same commit. **`make check`** exit 0: 681 tests, 28 suites, `test-verify-build` 18/18, prettier clean. Both gates re-run after rebasing onto `next` at `c755a5e`.
Confined to `src/content/inpage.js` and the tests — nothing the background produces changes. One adjacent gap found and filed rather than fixed here: `handleRpc()` reports an unsupported method with no code where EIP-1193 defines `4200` (https://git.eeqj.de/sneak/AutistMask/issues/279).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
src/content/inpage.jsrebuilds every rejection asp.reject(new Error(error.message || "Request failed")), droppingcode. A dApp checkingerr.code === 4001— the standard way to distinguish "the user said no" from "something broke" — seesundefined.The code is produced correctly and does cross the messaging boundary; the e2e harness added by #273 asserts it is present there. It is lost in the last hop, in the provider the page actually talks to.
RULES.mdrequires the code. EIP-1193 defines4001as the user-rejection code and dApps branch on it: without it, a wallet that the user deliberately declined is indistinguishable from a wallet that failed, and well-behaved sites will show an error or retry instead of accepting the refusal.Found by the e2e work on #183, which prints the observed code every run and deliberately does not assert the current behaviour either way, so this issue's fix can flip that assertion on.
Implementation requirements
code— anddatawhere present — taken from the boundary response, not a bareErrorwith only a message.eth_requestAccounts.Definition of done
code === 4001.messageis unchanged for every case.tests/e2e/run.jsrequires the code rather than printing it, and fails without the fix.TODO.mdupdated in the same commit.make checkpasses.Plan, on branch
fix/issue-274-eip1193-error-codes(basenext):Shape: an EIP-1193
ProviderRpcError extends Errordefined inside theinpage.jsIIFE, withname,codeand optionaldata. A class rather than properties bolted onto anErrorbecause the object crosses no boundary after construction — it is built in the page's own realm and handed straight to the caller'scatch— so the prototype survives andnameis a stable thing for a dApp to see.Single choke point: every entry point (
request,enable,send,sendAsync) funnels through the oneAUTISTMASK_RESPONSElistener, so onetoPageError()there covers all of them.Pass-through, not a whitelist: whatever
codearrived is carried verbatim rather than matched against a list of known values. The background emits 4001, 4100 and 4902 on the RPC path today; a code added later must reach the page without editing the provider. An error the background sent with no code stays a plainErrorwith nocodeproperty at all — aProviderRpcErrorwhosecodeisundefinedwould advertise a conformance it does not have.messageis untouched in every case.Nothing in
src/background/index.jsorsrc/content/index.jschanges.Tests: a new
tests/inpageErrors.test.jsloading the realinpage.jsagainst a stub window, plus flipping the four e2e rejection probes intests/e2e/run.jsfrom printing the code to requiring it.Built and pushed as #278. Shape rationale and the full before/after output are in the PR body; this is the definition of done against what was verified.
code === 4001— asserted on the page's own Error, not just on the wire, for all four flows (eth_requestAccounts,personal_sign,eth_signTypedData_v4,eth_sendTransaction) inmake test-e2e, and intests/inpageErrors.test.js.4001,4100and4902on the RPC path; all three are covered. The provider passes any code through verbatim rather than matching a list, tested with a4900the codebase never produces, so a code added later needs no change here.4200/4901are produced nowhere today; nothing was invented for them.messageunchanged — the fallback stays"Request failed", and the e2e records"message":"User rejected the request."byte-identically before and after. An error the background sent with no code stays a plainErrorwith nocodeproperty at all, asserted both ways.make test-e2eexits 1 with 33/37 and fournot oklines readingreached the page as an error with no code property at all, exit 0 with 37/37 after. Full captured output in the PR.tests/inpageErrors.test.js, 17 cases, loading the realinpage.js; 12 of them fail with the provider change stashed.request,enable,send(method, params),send({method, params})andsendAsync, each driven separately.TODO.mdupdated in the same commit.make checkexit 0: 681 tests, 28 suites,test-verify-build18/18, prettier clean. Both gates re-run after rebasing ontonextatc755a5e.Confined to
src/content/inpage.jsand the tests — nothing the background produces changes. One adjacent gap found and filed rather than fixed here:handleRpc()reports an unsupported method with no code where EIP-1193 defines4200(#279).