The one error type every failure at the JS boundary decodes into.
Before this module existed the bridge collapsed every failure into a string:
EIP-1193 code 4001 became a rejected tag and everything else became
failed: <message>. err.code never crossed the boundary, so 4902
(the chain is not in the wallet) was indistinguishable from a dropped
connection, and the standard switchChain -> addChain -> retry flow could
not be written at all. -32002 ("already processing a request") was only
recoverable inside connect, and a revert was a substring match away from a
network blip.
Now the shim forwards the numeric code, the wallet message, and the raw
revert payload, and this module turns those three fields into a value you can
case on:
case walletError of
Web3.Error.ChainNotAdded ->
-- the retry flow the old string-typed channel made impossible
( model, web3Cmd (Wallet.encode (Wallet.addChain pulseChain)) )
Web3.Error.RequestPending ->
( { model | toast = Just "Check your wallet" }, Cmd.none )
Web3.Error.UserRejected ->
( model, Cmd.none )
other ->
( { model | banner = Just (Web3.Error.toString other) }, Cmd.none )The wire shape this decodes is the failure payload the shim emits:
{ tag: "failed", error: "execution reverted", code: -32000, revertData: "0x08c3.." }Turn the three fields the boundary carries into an Error.
classify { code = Just 4902, message = "Unrecognized chain ID", revertData = Nothing }
--> ChainNotAdded
classify { code = Nothing, message = "Failed to fetch", revertData = Nothing }
--> NetworkError "Failed to fetch"Revert data wins over a generic provider code, because a wallet reports a
revert as its own transport error (-32000, -32603) with the useful part
buried in data. A recognised code (4001, -32002, 4902) still wins
over revert data -- those three are decisions the user or the wallet made,
not something the chain said.
Decode a failure message straight off the port.
Tag-aware, because the shim reports the two outcomes the user caused with
their own tags rather than as a code: rejected / connectRejected are
UserRejected and connectPending is
RequestPending. Everything else is read out of the
error / code / revertData fields by classify.
D.decodeValue Web3.Error.decoder incomingLift a JSON decoding failure into the same channel as everything else, so an application has one error type rather than two.
case D.decodeValue (Call.responseDecoder totalSupply) value of
Ok supply ->
...
Err jsonErr ->
handle (Web3.Error.fromDecodeError jsonErr)A one-line rendering, for a banner or a log. Not for control flow -- that is what the constructors are for.
Every way a request across the JS boundary can fail.
UserRejected-- EIP-11934001. The user dismissed the wallet prompt. Expected, not exceptional: do not show it as an error.RequestPending-- EIP-1193-32002. A request is already open in the wallet; the user has to go look at it. Retrying just queues another one.ChainNotAdded-- EIP-11934902. The wallet does not know this chain, sowallet_switchEthereumChaincannot switch to it. SendaddChainthenswitchChainagain.RpcError codeNumber message-- any other JSON-RPC or provider error, with the code preserved so a caller can match on codes this library has no opinion about.Reverted { reason, data }-- the transaction (or simulation) reverted.reasonis the decodedError(string)payload when there was one;datais the raw revert bytes, which is the only thing a custom error can be resolved against.Panic panicCode-- a SolidityPanic(uint256):0x11overflow,0x12divide by zero,0x32index out of bounds, and so on.DecodeError message-- the boundary produced something Elm could not read (a malformed address, a response that failed its decoder).NetworkError message-- transport-level failure with no code at all.