intrepidshape/elm-web3/1.4.3

NOTE: the latest version is 3.0.0

Web3.Subscription

Push-based event subscriptions over eth_subscribe.

The dominant pattern for reactive dapps. Where Web3.Contract.Event fetches historical logs via eth_getLogs, this module opens a long-lived WebSocket subscription so every NEW log is pushed to your update fn the moment it lands on-chain — no polling, no missed blocks.

The subscription identifier

Subscriptions are tagged with an opaque SubscriptionId so multiple subscriptions can coexist. Pass the same id to close to stop a subscription, and match incoming LogEvents against it to route updates.

Opaque tag identifying a subscription. Construct with subscriptionId.

Build a SubscriptionId from a label. Labels must be unique within a session; reusing a label closes the previous subscription with the same label.

factorySid =
    Subscription.subscriptionId "factory:TokenCreated"

Read the raw string back out of a SubscriptionId. Useful for routing incoming events in your update fn.

Building a logs filter

Subscriptions are built with a tiny pipeline: start with logs, narrow with atAddress, withTopic, or withTopics:

factoryEvents : SubscriptionId -> Cmd msg
factoryEvents sid =
    Subscription.logs
        |> Subscription.atAddress factoryAddress
        |> Subscription.withTopic 0 tokenCreatedTopicHash
        |> Subscription.open sid

Opaque, fluent builder for an eth_subscribe("logs", ...) filter.

Start a logs filter that matches every log in every block. Refine with atAddress and withTopic — usually you want both, otherwise you'll receive every event from every contract on the chain.

Narrow the filter to logs emitted by a specific contract.

withTopic : Int -> String -> LogFilter -> LogFilter

Match a specific topic at a given position. Topic 0 is always the event signature hash; topics 1–3 are the event's indexed parameters in declaration order.

-- match ERC-20 Transfer(address indexed from, address indexed to, uint256)
Subscription.logs
    |> Subscription.atAddress tokenAddress
    |> Subscription.withTopic 0 transferTopic
    |> Subscription.withTopic 2 myAddressTopic
withTopics : List (Maybe String) -> LogFilter -> LogFilter

Replace the entire topics list with a pre-built list of optional topic hashes. Nothing at a position means "match anything" — use the variadic withTopic for the common case.

Reading events back

type alias LogEvent =
{ address : Address
, topics : List String
, data : String
, blockNumber : Int
, logIndex : Int
, transactionHash : String
, removed : Bool
}

A single log event pushed by the subscription.

blockNumber and logIndex are zero when unavailable (pending logs in some node implementations). removed is True if a chain reorg made this log no longer canonical — your update fn should usually treat a removed log as an "undo" of an earlier emission.

type Status
= Opening
| Open
| Closed
| Failed String

The high-level lifecycle of a subscription. Surfaced via statusDecoder so you can show "connecting…" / "reconnecting…" indicators.

  • Opening — handshake in progress, no events yet.
  • Open — chain push is live.
  • Closed — socket closed; the runtime will retry automatically.
  • Failed err — the WS endpoint refused or the chain rejected the filter; the runtime will NOT retry without an explicit close + open cycle.

Wire format

The Cmd helpers below encode a port payload for elm-web3-ports.js to consume. Pair them with the decoders to interpret incoming subscription events.

Open the subscription. Encodes to the watchEvent Cmd that elm-web3-ports.js consumes.

Close an open subscription by id. The runtime stops pushing events and (if the WS connection has no other active subscriptions) closes the socket.

Decode a { tag: "subscribed", id, status } port message into a ( SubscriptionId, Status ) pair.

eventDecoder : Decoder ( SubscriptionId, LogEvent )

Decode a { tag: "eventLog", id, … } port message into a ( SubscriptionId, LogEvent ) pair.