diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
 # Revision history for borsh
 
+## 0.3.0 -- 2023-03-17
+
+* Introduce `BorshMaxSize`
+* Rename `Struct` (for deriving-via) to `AsStruct`
+* Introduce `AsEnum` (for deriving-via) alongside `AsStruct`
+  (this is also the reason for the renaming: `Enum` is in the prelude.)
+* Remove the generic defaults for `ToBorsh`, `FromBorsh`, `BorshSize` and
+  `MaxBorshSize`; users should instead use `AsEnum` or `AsStruct` (#10).
+
 ## 0.2.0 -- 2022-11-15
 
 * Remove dependency on `memory`
diff --git a/borsh.cabal b/borsh.cabal
--- a/borsh.cabal
+++ b/borsh.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               borsh
-version:            0.2.0
+version:            0.3.0
 synopsis:           Implementation of BORSH serialisation
 description:
   This package provides type classes and combinators for
diff --git a/src/Codec/Borsh.hs b/src/Codec/Borsh.hs
--- a/src/Codec/Borsh.hs
+++ b/src/Codec/Borsh.hs
@@ -13,20 +13,27 @@
   , Size(..)
   , KnownSize(..)
   , BorshSizeSum(..)
+  , BorshMaxSize(..)
     -- * Deriving-via support
-  , Struct(..)
+  , AsEnum(..)
+  , AsStruct(..)
+  , KnownImpliesMax(..)
   ) where
 
 import Codec.Borsh.Class
-    ( BorshSizeSum(..),
-      Struct(..),
-      FromBorsh(..),
-      ToBorsh(..),
-      BorshSize(..),
-      Size(..),
-      KnownSize(..),
-      serialiseBorsh,
-      deserialiseBorsh )
+    ( BorshSizeSum(..)
+    , AsEnum(..)
+    , AsStruct(..)
+    , FromBorsh(..)
+    , ToBorsh(..)
+    , BorshSize(..)
+    , Size(..)
+    , KnownSize(..)
+    , BorshMaxSize(..)
+    , KnownImpliesMax(..)
+    , serialiseBorsh
+    , deserialiseBorsh
+    )
 import Codec.Borsh.Encoding (Encoder(..))
 import Codec.Borsh.Incremental (Decoder)
 import Codec.Borsh.Incremental.Monad (DeserialiseFailure(..))
diff --git a/src/Codec/Borsh/Class.hs b/src/Codec/Borsh/Class.hs
--- a/src/Codec/Borsh/Class.hs
+++ b/src/Codec/Borsh/Class.hs
@@ -5,12 +5,15 @@
     ToBorsh(..)
   , FromBorsh(..)
     -- ** Deriving-via support
-  , Struct(..)
+  , AsEnum(..)
+  , AsStruct(..)
+  , KnownImpliesMax(..)
     -- * Size information
   , KnownSize(..)
   , Size(..)
   , BorshSize(..)
   , BorshSizeSum(..)
+  , BorshMaxSize(..)
     -- * Derived functionality
   , serialiseBorsh
   , deserialiseBorsh
@@ -50,7 +53,7 @@
 
 -- | The statically known size of encodings of values of a particular type.
 data Size (a :: KnownSize) where
-  SizeKnown    :: Int -> Size 'HasKnownSize
+  SizeKnown    :: Word32 -> Size 'HasKnownSize
   SizeVariable :: Size 'HasVariableSize
 
 deriving instance Show (Size a)
@@ -58,20 +61,33 @@
 
 class BorshSize (a :: Type) where
   type StaticBorshSize a :: KnownSize
-  type StaticBorshSize a = SumKnownSize (Code a)
 
-  -- | Size of the Borsh encoding, if known ahead of time
+  -- | Size of the Borsh encoding, if known statically (independent of a value)
   --
-  -- See 'encodeBorsh' for discussion of the generic instance.
+  -- There is no generic default implementation of 'borshSize'; instead use
+  -- deriving-via using 'AsStruct' or 'AsEnum'.
   borshSize :: Proxy a -> Size (StaticBorshSize a)
 
-  default borshSize ::
-       ( StaticBorshSize a ~ SumKnownSize (Code a)
-       , BorshSizeSum (Code a)
-       )
-    => Proxy a -> Size (StaticBorshSize a)
-  borshSize _ = borshSizeSum (Proxy @(Code a))
+class BorshMaxSize (a :: Type) where
+  -- | Maximum size of the Borsh encoding
+  --
+  -- There is no generic default implementation of 'borshMaxSize'; instead use
+  -- deriving-via using 'AsStruct' or 'AsEnum'.
+  --
+  -- However, while it is possible to use deriving-via to derive 'BorshMaxSize'
+  -- for your own types, recursive types should /not/ be given an instance (and
+  -- the derived function will not terminate).
+  borshMaxSize :: Proxy a -> Word32
 
+-- | If the size of a type's Borsh encoding is statically known then we also
+-- know the maximum size of the encoding. Useful for deriving-via.
+newtype KnownImpliesMax a = KnownImpliesMax { getKnownImpliesMax :: a }
+
+instance ( BorshSize a
+         , StaticBorshSize a ~ 'HasKnownSize
+         ) => BorshMaxSize (KnownImpliesMax a) where
+  borshMaxSize _ = case borshSize (Proxy @a) of SizeKnown n -> n
+
 {-------------------------------------------------------------------------------
   Definition
 -------------------------------------------------------------------------------}
@@ -79,33 +95,22 @@
 class BorshSize a => ToBorsh a where
   -- | Encoder to Borsh
   --
-  -- NOTE: The default generic encoder uses the Borsh encoding for enums,
-  -- and will therefore use constructor tag; see 'Struct' for detailed
-  -- discussion. Since the spec mandates the presence of that constructor tag,
-  -- the generic encoder/decoder does not apply to types without constructors.
+  -- There is no generic default implementation of 'encodeBorsh'; instead use
+  -- deriving-via using 'AsStruct' or 'AsEnum'.
   encodeBorsh :: Encoder a
 
-  default encodeBorsh ::
-       (Generic a, BorshSizeSum (Code a), All2 ToBorsh (Code a))
-    => Encoder a
-  encodeBorsh = Encoder $ runEncoder encodeBorsh . from
-
 class BorshSize a => FromBorsh a where
   -- | Decode from Borsh
   --
-  -- See 'encodeBorsh' for discussion of the generic instance.
+  -- There is no generic default implementation of 'decodeBorsh'; instead use
+  -- deriving-via using 'AsStruct' or 'AsEnum'.
   decodeBorsh :: Decoder s a
 
-  default decodeBorsh ::
-       (Generic a, BorshSizeSum (Code a), All2 FromBorsh (Code a))
-    => Decoder s a
-  decodeBorsh = to <$> decodeBorsh
-
 {-------------------------------------------------------------------------------
-  Structs
+  Enums
 -------------------------------------------------------------------------------}
 
--- | Deriving-via support for structs
+-- | Deriving-via support for enums (general ADTs)
 --
 -- The Borsh spec <https://borsh.io/> mandates that enums have a tag indicating
 -- the constructor, even when there is only a single constructor in the enum.
@@ -114,34 +119,76 @@
 -- the only difference between them is that a struct is an enum with a single
 -- constructor.
 --
--- The default generic encoder en decoder you get in 'ToBorsh' and 'FromBorsh'
--- will therefore add the tag, independent of the number of constructors. If
--- you want the encoding of a struct, without the tag, you need to use deriving
--- via:
+-- The generic encoder en decoder you get in 'ToBorsh' and 'FromBorsh' when
+-- deriving via @AsEnum@ will therefore add the tag, independent of the number of
+-- constructors:
 --
+-- > data MyEnum = ..
+-- >   deriving (BorshSize, ToBorsh, FromBorsh) via AsEnum MyEnum
+--
+-- If you want the encoding of a struct, without the tag, you need to derive via
+-- 'AsStruct'.
+newtype AsEnum a = AsEnum { getEnum :: a }
+
+instance BorshSizeSum (Code a) => BorshSize (AsEnum a) where
+  type StaticBorshSize (AsEnum a) = SumKnownSize (Code a)
+  borshSize _ = borshSizeSum (Proxy @(Code a))
+
+instance ( Generic a
+         , All2 BorshMaxSize (Code a)
+         ) => BorshMaxSize (AsEnum a) where
+  borshMaxSize _ = borshMaxSize (Proxy @(SOP I (Code a)))
+
+instance ( Generic a
+         , BorshSizeSum (Code a)
+         , All2 ToBorsh (Code a)
+         ) => ToBorsh (AsEnum a) where
+  encodeBorsh = contramap (from . getEnum) encodeBorsh
+
+instance ( Generic a
+         , BorshSizeSum  (Code a)
+         , All2 FromBorsh (Code a)
+         ) => FromBorsh (AsEnum a) where
+  decodeBorsh = fmap (AsEnum . to) decodeBorsh
+
+{-------------------------------------------------------------------------------
+  Structs
+-------------------------------------------------------------------------------}
+
+-- | Deriving-via support for structs
+--
+-- Usage:
+--
 -- > data MyStruct = ..
--- >   deriving (BorshSize, ToBorsh, FromBorsh) via Struct MyStruct
+-- >   deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct MyStruct
 --
 -- NOTE: Doing so may have consequences for forwards compatibility: if a tag
 -- is present, additional constructors can be added without invalidating the
--- encoding of existing constructors.
-newtype Struct a = Struct { getStruct :: a }
+-- encoding of existing constructors. See also 'AsEnum'.
+newtype AsStruct a = AsStruct { getStruct :: a }
 
-instance (IsProductType a xs, All BorshSize xs) => BorshSize (Struct a) where
-  type StaticBorshSize (Struct a) = ProdKnownSize (ProductCode a)
+instance ( IsProductType a xs
+         , All BorshSize xs
+         ) => BorshSize (AsStruct a) where
+  type StaticBorshSize (AsStruct a) = ProdKnownSize (ProductCode a)
   borshSize _ = sizeOfProd (Proxy @(ProductCode a))
 
 instance ( IsProductType a xs
+         , All BorshMaxSize xs
+         ) => BorshMaxSize (AsStruct a) where
+  borshMaxSize _ = borshMaxSize (Proxy @(NP I (ProductCode a)))
+
+instance ( IsProductType a xs
          , All BorshSize xs
          , All ToBorsh xs
-         ) => ToBorsh (Struct a) where
+         ) => ToBorsh (AsStruct a) where
   encodeBorsh = contramap (productTypeFrom . getStruct) encodeBorsh
 
 instance ( IsProductType a xs
          , All BorshSize xs
          , All FromBorsh xs
-         ) => FromBorsh (Struct a) where
-  decodeBorsh = fmap (Struct . productTypeTo) decodeBorsh
+         ) => FromBorsh (AsStruct a) where
+  decodeBorsh = fmap (AsStruct . productTypeTo) decodeBorsh
 
 {-------------------------------------------------------------------------------
   Derived functionality
@@ -220,11 +267,8 @@
   type StaticBorshSize Text = 'HasVariableSize
   borshSize _ = SizeVariable
 
-instance BorshSize [a] where
-  -- Use generic defaults
-
-instance BorshSize (Maybe a) where
-  -- Use generic defaults
+deriving via AsEnum [a]       instance BorshSize [a]
+deriving via AsEnum (Maybe a) instance BorshSize (Maybe a)
 
 instance BorshSize (Set a) where
   type StaticBorshSize (Set a) = 'HasVariableSize
@@ -243,6 +287,54 @@
   borshSize _ = borshSizeSum (Proxy @xss)
 
 {-------------------------------------------------------------------------------
+  Maximum sizes
+-------------------------------------------------------------------------------}
+
+deriving via KnownImpliesMax Word8   instance BorshMaxSize Word8
+deriving via KnownImpliesMax Word16  instance BorshMaxSize Word16
+deriving via KnownImpliesMax Word32  instance BorshMaxSize Word32
+deriving via KnownImpliesMax Word64  instance BorshMaxSize Word64
+deriving via KnownImpliesMax Word128 instance BorshMaxSize Word128
+
+deriving via KnownImpliesMax Int8   instance BorshMaxSize Int8
+deriving via KnownImpliesMax Int16  instance BorshMaxSize Int16
+deriving via KnownImpliesMax Int32  instance BorshMaxSize Int32
+deriving via KnownImpliesMax Int64  instance BorshMaxSize Int64
+deriving via KnownImpliesMax Int128 instance BorshMaxSize Int128
+
+deriving via KnownImpliesMax Float  instance BorshMaxSize Float
+deriving via KnownImpliesMax Double instance BorshMaxSize Double
+
+instance ( KnownNat n
+         , BorshMaxSize a
+         ) => BorshMaxSize (FixedSizeArray n a) where
+  borshMaxSize _ = fromIntegral (natVal (Proxy @n)) * borshMaxSize (Proxy @a)
+
+deriving via AsEnum (Maybe a) instance BorshMaxSize a => BorshMaxSize (Maybe a)
+
+instance (All2 BorshMaxSize xss, All SListI xss) => BorshMaxSize (SOP I xss) where
+  borshMaxSize _ = aux . hcollapse $ sizes
+    where
+      aux :: [[Word32]] -> Word32
+      aux [] = 1
+      aux xs = 1 + maximum (map sum xs)
+
+      borshMaxSize' :: forall a. (BorshMaxSize a) => K Word32 a
+      borshMaxSize' = K $ borshMaxSize (Proxy @a)
+
+      sizes :: POP (K Word32) xss
+      sizes = hcpure (Proxy @BorshMaxSize) borshMaxSize'
+
+instance (All BorshMaxSize xs) => BorshMaxSize (NP I xs) where
+  borshMaxSize _ = sum . hcollapse $ sizes
+    where
+      borshMaxSize' :: forall a. (BorshMaxSize a) => K Word32 a
+      borshMaxSize' = K $ borshMaxSize (Proxy @a)
+
+      sizes :: NP (K Word32) xs
+      sizes = hcpure (Proxy @BorshMaxSize) borshMaxSize'
+
+{-------------------------------------------------------------------------------
   ToBorsh instances
 -------------------------------------------------------------------------------}
 
@@ -334,25 +426,32 @@
 
 -- size 0
 
-deriving via Struct () instance BorshSize ()
-deriving via Struct () instance ToBorsh   ()
-deriving via Struct () instance FromBorsh ()
+deriving via AsStruct () instance BorshSize    ()
+deriving via AsStruct () instance BorshMaxSize ()
+deriving via AsStruct () instance ToBorsh      ()
+deriving via AsStruct () instance FromBorsh    ()
 
 -- size 2
 
-deriving via Struct (a, b)
+deriving via AsStruct (a, b)
          instance
               ( BorshSize a
               , BorshSize b
               )
            => BorshSize (a, b)
-deriving via Struct (a, b)
+deriving via AsStruct (a, b)
          instance
+              ( BorshMaxSize a
+              , BorshMaxSize b
+              )
+           => BorshMaxSize (a, b)
+deriving via AsStruct (a, b)
+         instance
               ( ToBorsh a
               , ToBorsh b
               )
            => ToBorsh (a, b)
-deriving via Struct (a, b)
+deriving via AsStruct (a, b)
          instance
               ( FromBorsh a
               , FromBorsh b
@@ -361,21 +460,28 @@
 
 -- size 3
 
-deriving via Struct (a, b, c)
+deriving via AsStruct (a, b, c)
          instance
               ( BorshSize a
               , BorshSize b
               , BorshSize c
               )
            => BorshSize (a, b, c)
-deriving via Struct (a, b, c)
+deriving via AsStruct (a, b, c)
          instance
+              ( BorshMaxSize a
+              , BorshMaxSize b
+              , BorshMaxSize c
+              )
+           => BorshMaxSize (a, b, c)
+deriving via AsStruct (a, b, c)
+         instance
               ( ToBorsh a
               , ToBorsh b
               , ToBorsh c
               )
            => ToBorsh (a, b, c)
-deriving via Struct (a, b, c)
+deriving via AsStruct (a, b, c)
          instance
               ( FromBorsh a
               , FromBorsh b
@@ -385,7 +491,7 @@
 
 -- size 4
 
-deriving via Struct (a, b, c, d)
+deriving via AsStruct (a, b, c, d)
          instance
               ( BorshSize a
               , BorshSize b
@@ -393,15 +499,23 @@
               , BorshSize d
               )
            => BorshSize (a, b, c, d)
-deriving via Struct (a, b, c, d)
+deriving via AsStruct (a, b, c, d)
          instance
+              ( BorshMaxSize a
+              , BorshMaxSize b
+              , BorshMaxSize c
+              , BorshMaxSize d
+              )
+           => BorshMaxSize (a, b, c, d)
+deriving via AsStruct (a, b, c, d)
+         instance
               ( ToBorsh a
               , ToBorsh b
               , ToBorsh c
               , ToBorsh d
               )
            => ToBorsh (a, b, c, d)
-deriving via Struct (a, b, c, d)
+deriving via AsStruct (a, b, c, d)
          instance
               ( FromBorsh a
               , FromBorsh b
@@ -412,7 +526,7 @@
 
 -- size 5
 
-deriving via Struct (a, b, c, d, e)
+deriving via AsStruct (a, b, c, d, e)
          instance
               ( BorshSize a
               , BorshSize b
@@ -421,8 +535,17 @@
               , BorshSize e
               )
            => BorshSize (a, b, c, d, e)
-deriving via Struct (a, b, c, d, e)
+deriving via AsStruct (a, b, c, d, e)
          instance
+              ( BorshMaxSize a
+              , BorshMaxSize b
+              , BorshMaxSize c
+              , BorshMaxSize d
+              , BorshMaxSize e
+              )
+           => BorshMaxSize (a, b, c, d, e)
+deriving via AsStruct (a, b, c, d, e)
+         instance
               ( ToBorsh a
               , ToBorsh b
               , ToBorsh c
@@ -430,7 +553,7 @@
               , ToBorsh e
               )
            => ToBorsh (a, b, c, d, e)
-deriving via Struct (a, b, c, d, e)
+deriving via AsStruct (a, b, c, d, e)
          instance
               ( FromBorsh a
               , FromBorsh b
@@ -442,7 +565,7 @@
 
 -- size 6
 
-deriving via Struct (a, b, c, d, e, f)
+deriving via AsStruct (a, b, c, d, e, f)
          instance
               ( BorshSize a
               , BorshSize b
@@ -452,8 +575,18 @@
               , BorshSize f
               )
            => BorshSize (a, b, c, d, e, f)
-deriving via Struct (a, b, c, d, e, f)
+deriving via AsStruct (a, b, c, d, e, f)
          instance
+              ( BorshMaxSize a
+              , BorshMaxSize b
+              , BorshMaxSize c
+              , BorshMaxSize d
+              , BorshMaxSize e
+              , BorshMaxSize f
+              )
+           => BorshMaxSize (a, b, c, d, e, f)
+deriving via AsStruct (a, b, c, d, e, f)
+         instance
               ( ToBorsh a
               , ToBorsh b
               , ToBorsh c
@@ -462,7 +595,7 @@
               , ToBorsh f
               )
            => ToBorsh (a, b, c, d, e, f)
-deriving via Struct (a, b, c, d, e, f)
+deriving via AsStruct (a, b, c, d, e, f)
          instance
               ( FromBorsh a
               , FromBorsh b
@@ -475,7 +608,7 @@
 
 -- size 7
 
-deriving via Struct (a, b, c, d, e, f, g)
+deriving via AsStruct (a, b, c, d, e, f, g)
          instance
               ( BorshSize a
               , BorshSize b
@@ -486,8 +619,19 @@
               , BorshSize g
               )
            => BorshSize (a, b, c, d, e, f, g)
-deriving via Struct (a, b, c, d, e, f, g)
+deriving via AsStruct (a, b, c, d, e, f, g)
          instance
+              ( BorshMaxSize a
+              , BorshMaxSize b
+              , BorshMaxSize c
+              , BorshMaxSize d
+              , BorshMaxSize e
+              , BorshMaxSize f
+              , BorshMaxSize g
+              )
+           => BorshMaxSize (a, b, c, d, e, f, g)
+deriving via AsStruct (a, b, c, d, e, f, g)
+         instance
               ( ToBorsh a
               , ToBorsh b
               , ToBorsh c
@@ -497,7 +641,7 @@
               , ToBorsh g
               )
            => ToBorsh (a, b, c, d, e, f, g)
-deriving via Struct (a, b, c, d, e, f, g)
+deriving via AsStruct (a, b, c, d, e, f, g)
          instance
               ( FromBorsh a
               , FromBorsh b
@@ -511,7 +655,7 @@
 
 -- size 8
 
-deriving via Struct (a, b, c, d, e, f, g, h)
+deriving via AsStruct (a, b, c, d, e, f, g, h)
          instance
               ( BorshSize a
               , BorshSize b
@@ -523,8 +667,20 @@
               , BorshSize h
               )
            => BorshSize (a, b, c, d, e, f, g, h)
-deriving via Struct (a, b, c, d, e, f, g, h)
+deriving via AsStruct (a, b, c, d, e, f, g, h)
          instance
+              ( BorshMaxSize a
+              , BorshMaxSize b
+              , BorshMaxSize c
+              , BorshMaxSize d
+              , BorshMaxSize e
+              , BorshMaxSize f
+              , BorshMaxSize g
+              , BorshMaxSize h
+              )
+           => BorshMaxSize (a, b, c, d, e, f, g, h)
+deriving via AsStruct (a, b, c, d, e, f, g, h)
+         instance
               ( ToBorsh a
               , ToBorsh b
               , ToBorsh c
@@ -535,7 +691,7 @@
               , ToBorsh h
               )
            => ToBorsh (a, b, c, d, e, f, g, h)
-deriving via Struct (a, b, c, d, e, f, g, h)
+deriving via AsStruct (a, b, c, d, e, f, g, h)
          instance
               ( FromBorsh a
               , FromBorsh b
@@ -550,7 +706,7 @@
 
 -- size 9
 
-deriving via Struct (a, b, c, d, e, f, g, h, i)
+deriving via AsStruct (a, b, c, d, e, f, g, h, i)
          instance
               ( BorshSize a
               , BorshSize b
@@ -563,8 +719,21 @@
               , BorshSize i
               )
            => BorshSize (a, b, c, d, e, f, g, h, i)
-deriving via Struct (a, b, c, d, e, f, g, h, i)
+deriving via AsStruct (a, b, c, d, e, f, g, h, i)
          instance
+              ( BorshMaxSize a
+              , BorshMaxSize b
+              , BorshMaxSize c
+              , BorshMaxSize d
+              , BorshMaxSize e
+              , BorshMaxSize f
+              , BorshMaxSize g
+              , BorshMaxSize h
+              , BorshMaxSize i
+              )
+           => BorshMaxSize (a, b, c, d, e, f, g, h, i)
+deriving via AsStruct (a, b, c, d, e, f, g, h, i)
+         instance
               ( ToBorsh a
               , ToBorsh b
               , ToBorsh c
@@ -576,7 +745,7 @@
               , ToBorsh i
               )
            => ToBorsh (a, b, c, d, e, f, g, h, i)
-deriving via Struct (a, b, c, d, e, f, g, h, i)
+deriving via AsStruct (a, b, c, d, e, f, g, h, i)
          instance
               ( FromBorsh a
               , FromBorsh b
@@ -592,7 +761,7 @@
 
 -- size 10
 
-deriving via Struct (a, b, c, d, e, f, g, h, i, j)
+deriving via AsStruct (a, b, c, d, e, f, g, h, i, j)
          instance
               ( BorshSize a
               , BorshSize b
@@ -606,8 +775,22 @@
               , BorshSize j
               )
            => BorshSize (a, b, c, d, e, f, g, h, i, j)
-deriving via Struct (a, b, c, d, e, f, g, h, i, j)
+deriving via AsStruct (a, b, c, d, e, f, g, h, i, j)
          instance
+              ( BorshMaxSize a
+              , BorshMaxSize b
+              , BorshMaxSize c
+              , BorshMaxSize d
+              , BorshMaxSize e
+              , BorshMaxSize f
+              , BorshMaxSize g
+              , BorshMaxSize h
+              , BorshMaxSize i
+              , BorshMaxSize j
+              )
+           => BorshMaxSize (a, b, c, d, e, f, g, h, i, j)
+deriving via AsStruct (a, b, c, d, e, f, g, h, i, j)
+         instance
               ( ToBorsh a
               , ToBorsh b
               , ToBorsh c
@@ -620,7 +803,7 @@
               , ToBorsh j
               )
            => ToBorsh (a, b, c, d, e, f, g, h, i, j)
-deriving via Struct (a, b, c, d, e, f, g, h, i, j)
+deriving via AsStruct (a, b, c, d, e, f, g, h, i, j)
          instance
               ( FromBorsh a
               , FromBorsh b
@@ -669,6 +852,8 @@
   type StaticBorshSize Char = 'HasKnownSize
   borshSize _ = SizeKnown 4
 
+deriving via KnownImpliesMax Char instance BorshMaxSize Char
+
 instance ToBorsh Char where
   encodeBorsh = encodeChar
 
@@ -681,6 +866,8 @@
   type StaticBorshSize Bool = 'HasKnownSize
   borshSize _ = SizeKnown 1
 
+deriving via KnownImpliesMax Bool instance BorshMaxSize Bool
+
 instance ToBorsh Bool where
   encodeBorsh = encodeBool
 
@@ -689,10 +876,22 @@
 
 -- Either
 
-deriving instance BorshSize (Either a b)
-deriving instance (ToBorsh   a, ToBorsh   b) => ToBorsh   (Either a b)
-deriving instance (FromBorsh a, FromBorsh b) => FromBorsh (Either a b)
+deriving
+  via AsEnum (Either a b)
+  instance BorshSize (Either a b)
 
+deriving
+  via AsEnum (Either a b)
+  instance (BorshMaxSize a, BorshMaxSize b) => BorshMaxSize (Either a b)
+
+deriving
+  via AsEnum (Either a b)
+  instance (ToBorsh a, ToBorsh b) => ToBorsh (Either a b)
+
+deriving
+  via AsEnum (Either a b)
+  instance (FromBorsh a, FromBorsh b) => FromBorsh (Either a b)
+
 {-------------------------------------------------------------------------------
   Internal auxiliary: size of products and sums-of-products
 -------------------------------------------------------------------------------}
@@ -749,7 +948,7 @@
 instance All BorshSize xs => BorshSizeSum '[xs] where
   borshSizeSum _ =
     -- This assumes the presence of the constructor tag
-    -- (see detailed discussion in 'Struct')
+    -- (see detailed discussion in 'AsStruct')
     case sizeOfProd (Proxy @xs) of
       SizeKnown sz -> SizeKnown (sz + 1)
       SizeVariable -> SizeVariable
diff --git a/src/Codec/Borsh/Incremental/Decoder.hs b/src/Codec/Borsh/Incremental/Decoder.hs
--- a/src/Codec/Borsh/Incremental/Decoder.hs
+++ b/src/Codec/Borsh/Incremental/Decoder.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE CPP #-}
+
 {-# OPTIONS_GHC -Wno-incomplete-patterns #-}
 
 module Codec.Borsh.Incremental.Decoder (
@@ -16,9 +18,12 @@
 
 import Control.Applicative
 import Control.Monad
-import Control.Monad.Fail
 import Control.Monad.ST
 import Data.Word
+
+#if !MIN_VERSION_base(4,13,0)
+import Control.Monad.Fail
+#endif
 
 import qualified Data.ByteString      as S
 import qualified Data.ByteString.Lazy as L
diff --git a/test/Test/Codec/Borsh/ExampleType/BTree.hs b/test/Test/Codec/Borsh/ExampleType/BTree.hs
--- a/test/Test/Codec/Borsh/ExampleType/BTree.hs
+++ b/test/Test/Codec/Borsh/ExampleType/BTree.hs
@@ -15,18 +15,8 @@
 
 -- | Binary trees
 data BTree a = BTip | BLeaf a | BNode (BTree a) (BTree a)
-  deriving (
-      Show
-    , Eq
-    , Ord
-    , Functor
-    , Foldable
-    , Traversable
-    , GHC.Generic
-    , Generic
-    , BorshSize
-    , FromBorsh
-    )
+  deriving (Show, Eq, Ord, Functor, Foldable, Traversable, GHC.Generic, Generic)
+  deriving (BorshSize, FromBorsh) via AsEnum (BTree a)
 
 -- Manual ToBorsh instance as a sort of "golden" test for the derived FromBorsh
 -- instance
diff --git a/test/Test/Codec/Borsh/ExampleType/NTree.hs b/test/Test/Codec/Borsh/ExampleType/NTree.hs
--- a/test/Test/Codec/Borsh/ExampleType/NTree.hs
+++ b/test/Test/Codec/Borsh/ExampleType/NTree.hs
@@ -15,18 +15,9 @@
 
 -- | N-ary trees
 data NTree a = NLeaf | NNode a [NTree a]
-  deriving (
-      Show
-    , Eq
-    , Ord
-    , Functor
-    , Foldable
-    , Traversable
-    , GHC.Generic
-    , Generic
-    , BorshSize
-    , ToBorsh
-    )
+  deriving (Show, Eq, Ord, Functor, Foldable, Traversable, GHC.Generic, Generic)
+  deriving (BorshSize, ToBorsh) via AsEnum (NTree a)
+
  -- Manual FromBorsh instance as a sort of "golden" test for the derived ToBorsh
 -- instance
 instance FromBorsh a => FromBorsh (NTree a) where
diff --git a/test/Test/Codec/Borsh/ExampleType/SimpleStructs.hs b/test/Test/Codec/Borsh/ExampleType/SimpleStructs.hs
--- a/test/Test/Codec/Borsh/ExampleType/SimpleStructs.hs
+++ b/test/Test/Codec/Borsh/ExampleType/SimpleStructs.hs
@@ -19,7 +19,11 @@
 
 data PolyStruct a = Poly a a a
   deriving (Show, Eq, Ord, GHC.Generic, Generic)
-  deriving (BorshSize, ToBorsh, FromBorsh) via Struct (PolyStruct a)
+  deriving ( BorshSize
+           , BorshMaxSize
+           , ToBorsh
+           , FromBorsh
+           ) via AsStruct (PolyStruct a)
 
 instance Arbitrary a => Arbitrary (PolyStruct a) where
   arbitrary = Poly <$> arbitrary <*> arbitrary <*> arbitrary
@@ -31,7 +35,11 @@
 
 data SimpleStruct1 = Struct1 Word8 () Word64
   deriving (Show, Eq, Ord, GHC.Generic, Generic)
-  deriving (BorshSize, ToBorsh, FromBorsh) via Struct SimpleStruct1
+  deriving ( BorshSize
+           , BorshMaxSize
+           , ToBorsh
+           , FromBorsh
+           ) via AsStruct SimpleStruct1
 
 instance Arbitrary SimpleStruct1 where
   arbitrary = Struct1 <$> arbitrary <*> arbitrary <*> arbitrary
@@ -48,7 +56,7 @@
 
 data SimpleStruct2 = Struct2 () SimpleStruct1 Word16
   deriving (Show, Eq, Ord, GHC.Generic, Generic)
-  deriving (ToBorsh, FromBorsh) via Struct SimpleStruct2
+  deriving (BorshMaxSize, ToBorsh, FromBorsh) via AsStruct SimpleStruct2
 
 instance BorshSize SimpleStruct2 where
   type StaticBorshSize SimpleStruct2 = 'HasKnownSize
diff --git a/test/Test/Codec/Borsh/Size.hs b/test/Test/Codec/Borsh/Size.hs
--- a/test/Test/Codec/Borsh/Size.hs
+++ b/test/Test/Codec/Borsh/Size.hs
@@ -1,6 +1,7 @@
 module Test.Codec.Borsh.Size (tests) where
 
 import Data.Proxy
+import Data.SOP.Dict
 import Test.Tasty
 import Test.Tasty.QuickCheck
 
@@ -9,10 +10,12 @@
 import Codec.Borsh
 
 import Test.Codec.Borsh.Util.RandomType
+import Test.Codec.Borsh.Util.QuickCheck
 
 tests :: TestTree
 tests = testGroup "Test.Codec.Borsh.Size" [
-      testProperty "size" test_size
+      testProperty "size"    test_size
+    , testProperty "maxSize" test_maxSize
     ]
 
 test_size :: SomeBorshValue -> Property
@@ -24,5 +27,18 @@
         case borshSize (Proxy @a) of
           SizeVariable -> label "Trivial" $ True
           SizeKnown n  -> L.length (serialiseBorsh val) === fromIntegral n
+
+
+test_maxSize :: SomeBorshValue -> Property
+test_maxSize =
+    \(SomeValue typ val) -> aux typ val
+  where
+    aux :: forall a. ToBorsh a => BorshType a -> a -> Property
+    aux typ val =
+        case borshTypeMaxSize typ of
+          Nothing   -> label "Trivial" $ True
+          Just Dict ->
+                       L.length (serialiseBorsh val)
+            `assertLE` fromIntegral (borshMaxSize (Proxy @a))
 
 
diff --git a/test/Test/Codec/Borsh/Util/QuickCheck.hs b/test/Test/Codec/Borsh/Util/QuickCheck.hs
--- a/test/Test/Codec/Borsh/Util/QuickCheck.hs
+++ b/test/Test/Codec/Borsh/Util/QuickCheck.hs
@@ -5,6 +5,8 @@
     -- * Generators
   , split2
   , splitN
+    -- * Assertions
+  , assertLE
   ) where
 
 import Control.Monad
@@ -49,3 +51,13 @@
         (xs',y:ys) ->
           return $ xs' ++ (x:y):ys
 
+{-------------------------------------------------------------------------------
+  Assertions
+-------------------------------------------------------------------------------}
+
+assertLE :: (Show a, Ord a) => a -> a -> Property
+assertLE x y =
+  if x <= y then
+    property True
+  else
+    counterexample (show y ++ " > " ++ show x) $ property False
diff --git a/test/Test/Codec/Borsh/Util/RandomType.hs b/test/Test/Codec/Borsh/Util/RandomType.hs
--- a/test/Test/Codec/Borsh/Util/RandomType.hs
+++ b/test/Test/Codec/Borsh/Util/RandomType.hs
@@ -1,16 +1,15 @@
 -- 11 iterations needed for GHC 9.2.2
 {-# OPTIONS_GHC -fconstraint-solver-iterations=11 #-}
+{-# OPTIONS_GHC -Wno-redundant-constraints #-}
 
 module Test.Codec.Borsh.Util.RandomType (
     -- * Types
     BorshType(..)
   , SomeBorshType(..)
+  , borshTypeMaxSize
+
     -- * Values of those types
   , SomeBorshValue(..)
-
-  -- TODO remove these
-  , arbitraryValue
-  , arbitraryType
   ) where
 
 import Control.Monad
@@ -327,6 +326,7 @@
   BtEnum ::
        ( All2 CanTest (xs ': xss)
        , All (Compose Show (NP BorshType)) (xs ': xss)
+       , All SListI (xs ': xss)
        )
     => -- POP: For every constructor, and every argument to every constructor,
        -- what is the type of that argument?
@@ -359,6 +359,61 @@
   SomeType :: CanTest a => BorshType a -> SomeBorshType
 
 deriving instance Show SomeBorshType
+
+{-------------------------------------------------------------------------------
+  Maximum size
+-------------------------------------------------------------------------------}
+
+borshTypeMaxSize :: BorshType a -> Maybe (Dict BorshMaxSize a)
+borshTypeMaxSize = \case
+    BtSimple bt -> borshSimpleTypeMaxSize bt
+    BtArray _ bt -> do
+      Dict <- borshTypeMaxSize bt
+      return Dict
+    BtVec _ -> Nothing
+    BtOption bt -> do
+      Dict <- borshTypeMaxSize bt
+      return Dict
+    BtHashSet _ -> Nothing
+    BtHashMap _ _ -> Nothing
+    BtStruct xs -> do
+      Dict <- fmap all_NP . hsequence' $ hmap (Comp . borshTypeMaxSize) xs
+      return Dict
+    BtEnum xss -> do
+      Dict <- fmap all_POP . hsequence' $ hmap (Comp . borshTypeMaxSize) xss
+      return Dict
+    BtBTree _ -> Nothing
+    BtNTree _ -> Nothing
+    BtPolyStruct bt -> do
+      Dict <- borshTypeMaxSize bt
+      return Dict
+    BtEither bt1 bt2 -> do
+      Dict <- borshTypeMaxSize bt1
+      Dict <- borshTypeMaxSize bt2
+      return Dict
+
+borshSimpleTypeMaxSize :: BorshSimpleType a -> Maybe (Dict BorshMaxSize a)
+borshSimpleTypeMaxSize = \case
+    BtU8            -> Just Dict
+    BtU16           -> Just Dict
+    BtU32           -> Just Dict
+    BtU64           -> Just Dict
+    BtU128          -> Just Dict
+    BtI8            -> Just Dict
+    BtI16           -> Just Dict
+    BtI32           -> Just Dict
+    BtI64           -> Just Dict
+    BtI128          -> Just Dict
+    BtF32           -> Just Dict
+    BtF64           -> Just Dict
+    BtText          -> Nothing
+    BtUnit          -> Just Dict
+    BtSimpleList    -> Nothing
+    BtSimpleStruct1 -> Just Dict
+    BtSimpleStruct2 -> Just Dict
+    BtByteString    -> Nothing
+    BtChar          -> Just Dict
+    BtBool          -> Just Dict
 
 {-------------------------------------------------------------------------------
   Shrinking types
