intrepidshape/elm-web3-ui/2.3.0

NOTE: the latest version is 4.0.0

Web3.Ui.SimulateFirst

Simulate-then-send: preview a write's outcome before asking for a signature. The structural guarantee, in the family of ApprovalFlow: a transaction cannot be sent without a completed simulation — confirm is the only door into Sending, and it only opens from Previewing.

elm-web3 already has the capability: Contract.Call.withFrom runs the write's calldata as an eth_call from the user's address, returning what the transaction would do (or its revert) for free.

-- user clicks the primary button:
( { model | sim = SimulateFirst.start model.sim }
, web3Cmd (Call.encode simulatedCall)      -- readCall withFrom
)


-- simulation answer (callResult or failed for our id):
{ model | sim = SimulateFirst.onSimResult (Ok decodedPreview) model.sim }


-- user confirms the previewed outcome:
( { model | sim = SimulateFirst.confirm model.sim }
, web3Cmd (Send.encode theRealWrite)
)


-- tx lifecycle:
{ model | sim = SimulateFirst.onTx txMsg model.sim }

Wallet rejection is not failure: rejecting the signature returns to Previewing — the preview is still true; the user just declined.

CSS classes: web3-simulate, web3-simulate--idle/--simulating/--preview/ --sending/--done/--refused, web3-simulate__preview, web3-simulate__button, web3-simulate__error.

type Step
= Idle
| Simulating
| Previewing String
| Sending String Status
| Done Receipt
| Refused String

Previewing carries the simulated outcome (already decoded/rendered to a string by the app); Refused carries the reason (simulation revert, tx failure).

Idle -> Simulating (fire the simulation). No-op elsewhere.

onSimResult : Result String String -> Step -> Step

Apply the simulation's answer. Only meaningful in Simulating.

Previewing -> Sending AwaitingSignature — the only door into Sending; unreachable without a completed simulation.

onTx : Msg -> Step -> Step

Drive the inner transaction. Only meaningful in Sending: confirmed → Done, failed → Refused, wallet-rejected → back to Previewing (the preview is still valid).

Done/Refused -> Idle. No-op elsewhere.

view : Config msg -> Step -> Html msg

The one valid control per step, plus the preview/receipt/error surface.

type alias Config msg =
{ simulateLabel : String
, confirmLabel : String
, onStart : msg
, onConfirm : msg
, onReset : msg
}