commit 94b8f712d025440cb5cf1c6e71db5ecb026b07c9
parent 564b7b53f6dfc7830c389793758fae9118c78b9f
Author: Jared Tobin <jared@jtobin.io>
Date: Sun, 25 Jan 2026 15:52:59 +0400
Add known features table with lookup functions
Implements Step 2 of IMPL1.md: encode all known features from BOLT #9
specification in a structured table. Provides Feature record type with
name, base bit, contexts, dependencies, and assumed fields. Includes
featureByBit and featureByName lookup functions.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat:
3 files changed, 197 insertions(+), 0 deletions(-)
diff --git a/flake.lock b/flake.lock
@@ -0,0 +1,88 @@
+{
+ "nodes": {
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1731533236,
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1766840161,
+ "narHash": "sha256-Ss/LHpJJsng8vz1Pe33RSGIWUOcqM1fjrehjUkdrWio=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "3edc4a30ed3903fdf6f90c837f961fa6b49582d1",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "ppad-nixpkgs": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs"
+ },
+ "locked": {
+ "lastModified": 1766932084,
+ "narHash": "sha256-GvVsbTfW+B7IQ9K/QP2xcXJAm1lhBin1jYZWNjOzT+o=",
+ "ref": "master",
+ "rev": "353e61763b959b960a55321a85423501e3e9ed7a",
+ "revCount": 2,
+ "type": "git",
+ "url": "git://git.ppad.tech/nixpkgs.git"
+ },
+ "original": {
+ "ref": "master",
+ "type": "git",
+ "url": "git://git.ppad.tech/nixpkgs.git"
+ }
+ },
+ "root": {
+ "inputs": {
+ "flake-utils": [
+ "ppad-nixpkgs",
+ "flake-utils"
+ ],
+ "nixpkgs": [
+ "ppad-nixpkgs",
+ "nixpkgs"
+ ],
+ "ppad-nixpkgs": "ppad-nixpkgs"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/lib/Lightning/Protocol/BOLT9/Features.hs b/lib/Lightning/Protocol/BOLT9/Features.hs
@@ -0,0 +1,108 @@
+{-# OPTIONS_HADDOCK prune #-}
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE DeriveGeneric #-}
+
+-- |
+-- Module: Lightning.Protocol.BOLT9.Features
+-- Copyright: (c) 2025 Jared Tobin
+-- License: MIT
+-- Maintainer: Jared Tobin <jared@ppad.tech>
+--
+-- Known feature table for the Lightning Network, per
+-- [BOLT #9](https://github.com/lightning/bolts/blob/master/09-features.md).
+
+module Lightning.Protocol.BOLT9.Features (
+ -- * Context
+ Context(..)
+
+ -- * Feature
+ , Feature(..)
+
+ -- * Lookup
+ , featureByBit
+ , featureByName
+
+ -- * Known features table
+ , knownFeatures
+ ) where
+
+import Control.DeepSeq (NFData)
+import Data.List (find)
+import Data.Word (Word16)
+import GHC.Generics (Generic)
+
+-- | Contexts in which a feature may be presented.
+data Context
+ = Init -- ^ 'I': presented in the @init@ message.
+ | NodeAnnouncement -- ^ 'N': presented in @node_announcement@ messages.
+ | ChannelAnnouncement
+ -- ^ 'C': presented in @channel_announcement@ messages.
+ | Invoice -- ^ '9': presented in BOLT 11 invoices.
+ | BlindedPath -- ^ 'B': presented in @allowed_features@ of a
+ -- blinded path.
+ | ChannelType -- ^ 'T': used in @channel_type@ field when opening
+ -- channels.
+ deriving (Eq, Ord, Show, Generic)
+
+instance NFData Context
+
+-- | A known feature from the BOLT #9 specification.
+data Feature = Feature {
+ featureName :: !String
+ -- ^ The canonical name of the feature.
+ , featureBaseBit :: {-# UNPACK #-} !Word16
+ -- ^ The even (compulsory) bit number; the odd (optional) bit is
+ -- @baseBit + 1@.
+ , featureContexts :: ![Context]
+ -- ^ Contexts in which this feature may be presented.
+ , featureDependencies :: ![String]
+ -- ^ Names of features this feature depends on.
+ , featureAssumed :: !Bool
+ -- ^ Whether this feature is assumed to be universally supported.
+ }
+ deriving (Eq, Show, Generic)
+
+instance NFData Feature
+
+-- | The complete table of known features from BOLT #9.
+knownFeatures :: [Feature]
+knownFeatures = [
+ Feature "option_data_loss_protect" 0 [] [] True
+ , Feature "option_upfront_shutdown_script" 4 [Init, NodeAnnouncement] [] False
+ , Feature "gossip_queries" 6 [] [] False
+ , Feature "var_onion_optin" 8 [] [] True
+ , Feature "gossip_queries_ex" 10 [Init, NodeAnnouncement] [] False
+ , Feature "option_static_remotekey" 12 [] [] True
+ , Feature "payment_secret" 14 [] [] True
+ , Feature "basic_mpp" 16 [Init, NodeAnnouncement, Invoice]
+ ["payment_secret"] False
+ , Feature "option_support_large_channel" 18 [Init, NodeAnnouncement] [] False
+ , Feature "option_anchors" 22 [Init, NodeAnnouncement, ChannelType] [] False
+ , Feature "option_route_blinding" 24 [Init, NodeAnnouncement, Invoice] [] False
+ , Feature "option_shutdown_anysegwit" 26 [Init, NodeAnnouncement] [] False
+ , Feature "option_dual_fund" 28 [Init, NodeAnnouncement] [] False
+ , Feature "option_quiesce" 34 [Init, NodeAnnouncement] [] False
+ , Feature "option_attribution_data" 36 [Init, NodeAnnouncement, Invoice]
+ [] False
+ , Feature "option_onion_messages" 38 [Init, NodeAnnouncement] [] False
+ , Feature "option_provide_storage" 42 [Init, NodeAnnouncement] [] False
+ , Feature "option_channel_type" 44 [] [] True
+ , Feature "option_scid_alias" 46 [Init, NodeAnnouncement, ChannelType] [] False
+ , Feature "option_payment_metadata" 48 [Invoice] [] False
+ , Feature "option_zeroconf" 50 [Init, NodeAnnouncement, ChannelType]
+ ["option_scid_alias"] False
+ , Feature "option_simple_close" 60 [Init, NodeAnnouncement]
+ ["option_shutdown_anysegwit"] False
+ ]
+
+-- | Look up a feature by bit number.
+--
+-- Accepts either the even (compulsory) or odd (optional) bit of the pair.
+featureByBit :: Word16 -> Maybe Feature
+featureByBit !bit =
+ let baseBit = bit - (bit `mod` 2) -- round down to even
+ in find (\f -> featureBaseBit f == baseBit) knownFeatures
+
+-- | Look up a feature by its canonical name.
+featureByName :: String -> Maybe Feature
+featureByName !name = find (\f -> featureName f == name) knownFeatures
diff --git a/ppad-bolt9.cabal b/ppad-bolt9.cabal
@@ -25,6 +25,7 @@ library
-Wall
exposed-modules:
Lightning.Protocol.BOLT9
+ Lightning.Protocol.BOLT9.Features
build-depends:
base >= 4.9 && < 5
, bytestring >= 0.9 && < 0.13