Additional fixes to 0.9 branch - #579
Open
Montoya wants to merge 17 commits into
Open
Conversation
This surfaces errors to the React frontend better
…nses Rapidly switching networks in the companion dapp caused starkNet_getCurrentAccount to fail with "TypeError: Cannot read properties of undefined (reading '0')" and could serve wrong-chain data. Two fixes: - ChainRpcController: all RPC controllers are module-level singletons, but preExecute stored the resolved network on `this.network`. Two concurrent requests for different chains raced on that field, so a request could execute against the other request's network. Executions are now serialized per controller instance with a mutex. - ContractReader.callContract: starknet.js's fetchEndpoint resolves to undefined when a node responds without a `result` or `error` field, which crashed callers indexing `resp[0]` (AccountContractReader). Validate the response is a non-empty array and throw a descriptive ContractReadError (including entrypoint, address, and raw response) instead. Also guard `error.message` access for non-Error throws.
Inside the Snap sandbox, `fetch` is proxied across the sandbox boundary by `endowment:network-access`, and account discovery issues several concurrent contract calls through it. Intermittently a response arrives with neither `result` nor `error`, which starknet.js's fetchEndpoint passes through as `undefined`. The same requests replayed outside the sandbox (720 direct RPC calls, 80 via an identical starknet.js Provider) consistently return well-formed bodies, so the malformed shape is transient and specific to the sandboxed transport. ContractReader.callContract now retries up to 3 times with backoff when the response shape is invalid, logging each bad attempt's raw response. Genuine RPC errors (e.g. "Contract not found" for undeployed accounts) are not retried and still throw immediately. Exhausting retries throws a descriptive ContractReadError instead of the previous "TypeError: Cannot read properties of undefined (reading '0')".
Montoya
requested review from
Julink-eth and
jonesho
and removed request for
a team
August 13, 2026 16:03
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Error handling and reliability fixes for the companion dapp and Snap
While testing the starknet.js v9 changes locally, snap RPC failures surfaced in the dapp as
[object Object]with no usable stack, hiding several real bugs behind unreadable errors. This PR fixes the error reporting on both sides, plus three bugs the improved reporting uncovered.wallet-ui: readable errors instead of
[object Object]utils/error.tswithnormalizeSnapError()/SnapRpcError: MetaMask rejectsprovider.requestwith a plain{code, message, data}object, which CRA's dev overlay renders as[object Object]. Errors are now rewrapped as realErrors with a readable message (includingdata.walletRpcError.code), preservingcode/dataso existing checks (err.code === 4100,isUserDenyError) still work.useSnap.ts:invokeSnap,getInstalledSnaps, andrequestSnaprethrow normalized errors tagged with the RPC method name.App.tsx: theinitSnap/checkConnection/initWalletDataeffects no longer leak unhandled promise rejections; failures log and show a toast.useStarkNetSnap.ts:isSnapRequireUpdate()moved insideinitSnap's try block (was an unhandled-rejection site).initWalletDataclears the loader infinallyso failures don't strand the loading backdrop.-32603force-reinstall modal is skipped forlocal:snaps, since the snap returns-32603for every internal failure and this falsely told local developers to "install the latest version".starknet-snap: three bugs
Original errors were discarded (
index.tsx): theonRpcRequestcatch-all wrapped unknown errors inUnknownError('Unable to execute the rpc request')and logged only the wrapper, making root causes unrecoverable. The original stack is now logged, and outsideSNAP_ENV=prodthe original message is appended to the client-visible error.Cross-request network race (
chain-rpc-controller.ts): RPC controllers are module-level singletons, butpreExecutestored the resolved network onthis.network. Concurrent requests for different chains (e.g. switching networks quickly in the dapp) raced on that field and could execute with each other's network. Executions are now serialized per controller instance with a mutex. Verified no nestedexecutecalls, so no deadlock; lock ordering (instance mutex → global state mutex) is consistent.Invalid RPC response shapes crashed discovery (
utils/contract.ts): inside the sandbox (fetchproxied byendowment:network-access), contract call responses intermittently arrive with neitherresultnorerror; starknet.js passes this through asundefinedandAccountContractReader'sresp[0]threwTypeError: Cannot read properties of undefined (reading '0')— reproducible on fresh install and on every network switch. The same requests replayed outside the sandbox (720 direct RPC calls, 80 via an identical starknet.jsProvider) consistently returned well-formed bodies, so the corruption is transient and transport-specific.ContractReader.callContractnow validates the response shape, retries up to 3× with backoff (logging each bad attempt's raw body), and throws a descriptiveContractReadErrorif exhausted. Genuine RPC errors (Contract not foundfor undeployed accounts) still throw immediately without retry.Testing
tsc --noEmitand eslint clean on both packages.[object Object]→ now initializes cleanly, with transient transport failures retried and named when they persist.Note
Medium Risk
Touches core Snap RPC execution, network-scoped state, and wallet bootstrap paths; behavior changes are targeted but affect concurrent chain operations and failure UX.
Overview
Improves error visibility in the companion dapp and reliability in the Snap after starknet.js v9 testing surfaced failures as
[object Object].wallet-ui adds
normalizeSnapError/SnapRpcErrorso MetaMask’s plain JSON-RPC rejections become realErrors with readable messages while keepingcodeanddata.useSnapnormalizes failures from invoke/install flows;Appcatches async init/check/load rejections and toasts them.initSnapmoves version checks insidetry,initWalletDataalways clears the loader infinally, and the-32603force-reinstall modal is skipped forlocal:snaps.starknet-snap logs the original error before wrapping unknown RPC failures, and in non-prod includes the underlying message in the client error. Chain RPC controllers serialize
executewith a per-singleton mutex so concurrent requests cannot race onthis.network. Contract reads retry transient invalid/empty RPC shapes (up to 3× with backoff) instead of crashing discovery withundefined[0].Reviewed by Cursor Bugbot for commit a39a52f. Bugbot is set up for automated code reviews on this repo. Configure here.