fix: EIP-1193 error codes never reach the page — a dApp cannot detect a user rejection #274
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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).