From c0f5bb23a5f8fed9ddfcffd6e28203ed458a79c9 Mon Sep 17 00:00:00 2001
From: Casey Rodarmor <casey@rodarmor.com>
Date: Mon, 25 Mar 2024 16:09:53 -0700
Subject: [PATCH] Allow supply-capped mints (#3365)

---
 src/index.rs                      |   2 +-
 src/index/entry.rs                | 212 ++++++++++++--
 src/index/testing.rs              |   2 +-
 src/index/updater.rs              |  19 +-
 src/index/updater/rune_updater.rs |  91 +++---
 src/runes.rs                      | 448 +++++++++++++++++++++---------
 src/runes/etching.rs              |   5 +-
 src/runes/mint.rs                 |   1 +
 src/runes/runestone.rs            | 186 +++++++++----
 src/runes/tag.rs                  |   2 +
 src/subcommand/runes.rs           |   7 +-
 src/subcommand/server.rs          |  14 +-
 src/subcommand/wallet/inscribe.rs |  23 ++
 src/subcommand/wallet/mint.rs     |   2 +-
 src/templates/rune.rs             |  67 +----
 src/wallet/batch/etching.rs       |   1 +
 src/wallet/batch/mint.rs          |   1 +
 src/wallet/batch/plan.rs          |   8 +-
 templates/rune.html               |  12 +-
 tests/json_api.rs                 |   4 -
 tests/lib.rs                      |  19 +-
 tests/wallet/balance.rs           |   1 +
 tests/wallet/inscribe.rs          | 153 ++++++++++
 tests/wallet/mint.rs              |   7 +
 tests/wallet/selection.rs         |   2 +
 tests/wallet/send.rs              |   1 +
 26 files changed, 951 insertions(+), 339 deletions(-)

diff --git a/src/index.rs b/src/index.rs
index c3219f70c6..b87ff832f8 100644
--- a/src/index.rs
+++ b/src/index.rs
@@ -47,7 +47,7 @@ mod updater;
 #[cfg(test)]
 pub(crate) mod testing;
 
-const SCHEMA_VERSION: u64 = 22;
+const SCHEMA_VERSION: u64 = 23;
 
 macro_rules! define_table {
   ($name:ident, $key:ty, $value:ty) => {
diff --git a/src/index/entry.rs b/src/index/entry.rs
index 47796eac54..e32d107f5b 100644
--- a/src/index/entry.rs
+++ b/src/index/entry.rs
@@ -34,11 +34,10 @@ pub struct RuneEntry {
   pub divisibility: u8,
   pub etching: Txid,
   pub mint: Option<MintEntry>,
-  pub mints: u64,
+  pub mints: u128,
   pub number: u64,
   pub premine: u128,
   pub spaced_rune: SpacedRune,
-  pub supply: u128,
   pub symbol: Option<char>,
   pub timestamp: u32,
 }
@@ -46,22 +45,40 @@ pub struct RuneEntry {
 impl RuneEntry {
   pub fn mintable(&self, block_height: Height, block_time: u32) -> Result<u128, MintError> {
     let Some(mint) = self.mint else {
-      return Err(MintError::Unmintable(self.spaced_rune.rune));
+      return Err(MintError::Unmintable);
     };
 
     if let Some(end) = mint.end {
       if block_height.0 >= end {
-        return Err(MintError::End((self.spaced_rune.rune, end)));
+        return Err(MintError::End(end));
       }
     }
 
     if let Some(deadline) = mint.deadline {
       if block_time >= deadline {
-        return Err(MintError::Deadline((self.spaced_rune.rune, deadline)));
+        return Err(MintError::Deadline(deadline));
       }
     }
 
-    Ok(mint.limit.unwrap_or(runes::MAX_LIMIT))
+    let cap = mint.cap.unwrap_or_default();
+
+    if self.mints >= cap {
+      return Err(MintError::Cap(cap));
+    }
+
+    Ok(mint.limit.unwrap_or_default())
+  }
+
+  pub fn supply(&self) -> u128 {
+    self.premine + self.mints * self.mint.and_then(|mint| mint.limit).unwrap_or_default()
+  }
+
+  pub fn pile(&self, amount: u128) -> Pile {
+    Pile {
+      amount,
+      divisibility: self.divisibility,
+      symbol: self.symbol,
+    }
   }
 }
 
@@ -70,23 +87,24 @@ pub(super) type RuneEntryValue = (
   u8,                     // divisibility
   (u128, u128),           // etching
   Option<MintEntryValue>, // mint parameters
-  u64,                    // mints
+  u128,                   // mints
   u64,                    // number
   u128,                   // premine
   (u128, u32),            // spaced rune
-  u128,                   // supply
   Option<char>,           // symbol
   u32,                    // timestamp
 );
 
 #[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize, Default)]
 pub struct MintEntry {
+  pub cap: Option<u128>,     // mint cap
   pub deadline: Option<u32>, // unix timestamp
   pub end: Option<u32>,      // block height
   pub limit: Option<u128>,   // claim amount
 }
 
 type MintEntryValue = (
+  Option<u128>, // cap
   Option<u32>,  // deadline
   Option<u32>,  // end
   Option<u128>, // limit
@@ -103,7 +121,6 @@ impl Default for RuneEntry {
       number: 0,
       premine: 0,
       spaced_rune: SpacedRune::default(),
-      supply: 0,
       symbol: None,
       timestamp: 0,
     }
@@ -123,7 +140,6 @@ impl Entry for RuneEntry {
       number,
       premine,
       (rune, spacers),
-      supply,
       symbol,
       timestamp,
     ): RuneEntryValue,
@@ -141,7 +157,8 @@ impl Entry for RuneEntry {
           high[14], high[15],
         ])
       },
-      mint: mint.map(|(deadline, end, limit)| MintEntry {
+      mint: mint.map(|(cap, deadline, end, limit)| MintEntry {
+        cap,
         deadline,
         end,
         limit,
@@ -153,7 +170,6 @@ impl Entry for RuneEntry {
         rune: Rune(rune),
         spacers,
       },
-      supply,
       symbol,
       timestamp,
     }
@@ -178,16 +194,16 @@ impl Entry for RuneEntry {
       },
       self.mint.map(
         |MintEntry {
+           cap,
            deadline,
            end,
            limit,
-         }| (deadline, end, limit),
+         }| (cap, deadline, end, limit),
       ),
       self.mints,
       self.number,
       self.premine,
       (self.spaced_rune.rune.0, self.spaced_rune.spacers),
-      self.supply,
       self.symbol,
       self.timestamp,
     )
@@ -492,6 +508,7 @@ mod tests {
         0x1E, 0x1F,
       ]),
       mint: Some(MintEntry {
+        cap: Some(1),
         deadline: Some(2),
         end: Some(4),
         limit: Some(5),
@@ -503,7 +520,6 @@ mod tests {
         rune: Rune(7),
         spacers: 8,
       },
-      supply: 9,
       symbol: Some('a'),
       timestamp: 10,
     };
@@ -515,12 +531,11 @@ mod tests {
         0x0F0E0D0C0B0A09080706050403020100,
         0x1F1E1D1C1B1A19181716151413121110,
       ),
-      Some((Some(2), Some(4), Some(5))),
+      Some((Some(1), Some(2), Some(4), Some(5))),
       11,
       6,
       12,
       (7, 8),
-      9,
       Some('a'),
       10,
     );
@@ -550,4 +565,167 @@ mod tests {
 
     assert_eq!(actual, expected);
   }
+
+  #[test]
+  fn mintable() {
+    assert_eq!(
+      RuneEntry::default().mintable(Height(0), 0),
+      Err(MintError::Unmintable)
+    );
+
+    assert_eq!(
+      RuneEntry {
+        mint: Some(MintEntry {
+          end: Some(1),
+          limit: Some(1000),
+          cap: Some(1),
+          ..default()
+        }),
+        ..default()
+      }
+      .mintable(Height(0), 0),
+      Ok(1000),
+    );
+
+    assert_eq!(
+      RuneEntry {
+        mint: Some(MintEntry {
+          end: Some(1),
+          limit: Some(1000),
+          cap: Some(1),
+          ..default()
+        }),
+        ..default()
+      }
+      .mintable(Height(1), 0),
+      Err(MintError::End(1)),
+    );
+
+    assert_eq!(
+      RuneEntry {
+        mint: Some(MintEntry {
+          deadline: Some(1),
+          limit: Some(1000),
+          cap: Some(1),
+          ..default()
+        }),
+        ..default()
+      }
+      .mintable(Height(0), 0),
+      Ok(1000),
+    );
+
+    assert_eq!(
+      RuneEntry {
+        mint: Some(MintEntry {
+          deadline: Some(1),
+          limit: Some(1000),
+          cap: Some(1),
+          ..default()
+        }),
+        ..default()
+      }
+      .mintable(Height(0), 1),
+      Err(MintError::Deadline(1)),
+    );
+
+    assert_eq!(
+      RuneEntry {
+        mint: Some(MintEntry {
+          cap: Some(1),
+          limit: Some(1000),
+          ..default()
+        }),
+        mints: 0,
+        ..default()
+      }
+      .mintable(Height(0), 0),
+      Ok(1000),
+    );
+
+    assert_eq!(
+      RuneEntry {
+        mint: Some(MintEntry {
+          cap: Some(1),
+          limit: Some(1000),
+          ..default()
+        }),
+        mints: 1,
+        ..default()
+      }
+      .mintable(Height(0), 0),
+      Err(MintError::Cap(1)),
+    );
+
+    assert_eq!(
+      RuneEntry {
+        mint: Some(MintEntry {
+          cap: None,
+          limit: Some(1000),
+          ..default()
+        }),
+        mints: 0,
+        ..default()
+      }
+      .mintable(Height(0), 0),
+      Err(MintError::Cap(0)),
+    );
+  }
+
+  #[test]
+  fn supply() {
+    assert_eq!(
+      RuneEntry {
+        mint: Some(MintEntry {
+          limit: Some(1000),
+          ..default()
+        }),
+        mints: 0,
+        ..default()
+      }
+      .supply(),
+      0
+    );
+
+    assert_eq!(
+      RuneEntry {
+        mint: Some(MintEntry {
+          limit: Some(1000),
+          ..default()
+        }),
+        mints: 1,
+        ..default()
+      }
+      .supply(),
+      1000
+    );
+
+    assert_eq!(
+      RuneEntry {
+        mint: Some(MintEntry {
+          limit: Some(1000),
+          ..default()
+        }),
+        mints: 0,
+        premine: 1,
+        ..default()
+      }
+      .supply(),
+      1
+    );
+
+    assert_eq!(
+      RuneEntry {
+        mint: Some(MintEntry {
+          limit: Some(1000),
+          ..default()
+        }),
+        mints: 1,
+        premine: 1,
+        ..default()
+      }
+      .supply(),
+      1001
+    );
+  }
 }
diff --git a/src/index/testing.rs b/src/index/testing.rs
index 0032a3e68f..dd0c5ed8ab 100644
--- a/src/index/testing.rs
+++ b/src/index/testing.rs
@@ -147,7 +147,7 @@ impl Context {
     for (id, entry) in runes {
       pretty_assert_eq!(
         outstanding.get(id).copied().unwrap_or_default(),
-        entry.supply - entry.burned
+        entry.supply() - entry.burned
       );
     }
   }
diff --git a/src/index/updater.rs b/src/index/updater.rs
index b5229d171b..5e21331cc3 100644
--- a/src/index/updater.rs
+++ b/src/index/updater.rs
@@ -591,6 +591,8 @@ impl<'index> Updater<'index> {
         .unwrap_or(0);
 
       let mut rune_updater = RuneUpdater {
+        block_time: block.header.time,
+        burned: HashMap::new(),
         client: &self.index.client,
         height: self.height,
         id_to_entry: &mut rune_id_to_rune_entry,
@@ -601,29 +603,14 @@ impl<'index> Updater<'index> {
         runes,
         sequence_number_to_rune_id: &mut sequence_number_to_rune_id,
         statistic_to_count: &mut statistic_to_count,
-        block_time: block.header.time,
         transaction_id_to_rune: &mut transaction_id_to_rune,
-        updates: HashMap::new(),
       };
 
       for (i, (tx, txid)) in block.txdata.iter().enumerate() {
         rune_updater.index_runes(u32::try_from(i).unwrap(), tx, *txid)?;
       }
 
-      for (rune_id, update) in rune_updater.updates {
-        let mut entry = RuneEntry::load(
-          rune_id_to_rune_entry
-            .get(&rune_id.store())?
-            .unwrap()
-            .value(),
-        );
-
-        entry.burned += update.burned;
-        entry.mints += update.mints;
-        entry.supply += update.supply;
-
-        rune_id_to_rune_entry.insert(&rune_id.store(), entry.store())?;
-      }
+      rune_updater.update()?;
     }
 
     height_to_block_header.insert(&self.height, &block.header.store())?;
diff --git a/src/index/updater/rune_updater.rs b/src/index/updater/rune_updater.rs
index 210039a134..5f2b6e8a57 100644
--- a/src/index/updater/rune_updater.rs
+++ b/src/index/updater/rune_updater.rs
@@ -9,22 +9,17 @@ struct Claim {
 }
 
 struct Etched {
-  balance: u128,
   divisibility: u8,
   id: RuneId,
   mint: Option<MintEntry>,
+  premine: u128,
   spaced_rune: SpacedRune,
   symbol: Option<char>,
 }
 
-#[derive(Default)]
-pub(crate) struct RuneUpdate {
-  pub(crate) burned: u128,
-  pub(crate) mints: u64,
-  pub(crate) supply: u128,
-}
-
 pub(super) struct RuneUpdater<'a, 'tx, 'client> {
+  pub(super) block_time: u32,
+  pub(super) burned: HashMap<RuneId, u128>,
   pub(super) client: &'client Client,
   pub(super) height: u32,
   pub(super) id_to_entry: &'a mut Table<'tx, RuneIdValue, RuneEntryValue>,
@@ -35,9 +30,7 @@ pub(super) struct RuneUpdater<'a, 'tx, 'client> {
   pub(super) runes: u64,
   pub(super) sequence_number_to_rune_id: &'a mut Table<'tx, u32, RuneIdValue>,
   pub(super) statistic_to_count: &'a mut Table<'tx, u64, u64>,
-  pub(super) block_time: u32,
   pub(super) transaction_id_to_rune: &'a mut Table<'tx, &'static TxidValue, u128>,
-  pub(super) updates: HashMap<RuneId, RuneUpdate>,
 }
 
 impl<'a, 'tx, 'client> RuneUpdater<'a, 'tx, 'client> {
@@ -64,15 +57,14 @@ impl<'a, 'tx, 'client> RuneUpdater<'a, 'tx, 'client> {
         .transpose()?
       {
         *unallocated.entry(claim.id).or_default() += claim.limit;
+      }
 
-        let update = self.updates.entry(claim.id).or_default();
+      let etched = self.etched(tx_index, tx, &runestone)?;
 
-        update.mints += 1;
-        update.supply += claim.limit;
+      if let Some(Etched { id, premine, .. }) = etched {
+        *unallocated.entry(id).or_default() += premine;
       }
 
-      let mut etched = self.etched(tx_index, tx, &runestone)?;
-
       if !cenotaph {
         for Edict { id, amount, output } in runestone.edicts {
           // edicts with output values greater than the number of outputs
@@ -80,21 +72,18 @@ impl<'a, 'tx, 'client> RuneUpdater<'a, 'tx, 'client> {
           let output = usize::try_from(output).unwrap();
           assert!(output <= tx.output.len());
 
-          let (balance, id) = if id == RuneId::default() {
-            // If this edict allocates new issuance runes, skip it
-            // if no issuance was present, or if the issuance was invalid.
-            // Additionally, replace ID 0 with the newly assigned ID, and
-            // get the unallocated balance of the issuance.
-            match etched.as_mut() {
-              Some(Etched { balance, id, .. }) => (balance, *id),
-              None => continue,
-            }
+          let id = if id == RuneId::default() {
+            let Some(Etched { id, .. }) = etched else {
+              continue;
+            };
+
+            id
           } else {
-            // Get the unallocated balance of the given ID
-            match unallocated.get_mut(&id) {
-              Some(balance) => (balance, id),
-              None => continue,
-            }
+            id
+          };
+
+          let Some(balance) = unallocated.get_mut(&id) else {
+            continue;
           };
 
           let mut allocate = |balance: &mut u128, amount: u128, output: usize| {
@@ -224,7 +213,17 @@ impl<'a, 'tx, 'client> RuneUpdater<'a, 'tx, 'client> {
 
     // increment entries with burned runes
     for (id, amount) in burned {
-      self.updates.entry(id).or_default().burned += amount;
+      *self.burned.entry(id).or_default() += amount;
+    }
+
+    Ok(())
+  }
+
+  pub(super) fn update(self) -> Result {
+    for (rune_id, burned) in self.burned {
+      let mut entry = RuneEntry::load(self.id_to_entry.get(&rune_id.store())?.unwrap().value());
+      entry.burned += burned;
+      self.id_to_entry.insert(&rune_id.store(), entry.store())?;
     }
 
     Ok(())
@@ -232,10 +231,10 @@ impl<'a, 'tx, 'client> RuneUpdater<'a, 'tx, 'client> {
 
   fn create_rune_entry(&mut self, txid: Txid, burn: bool, etched: Etched) -> Result {
     let Etched {
-      balance,
       divisibility,
       id,
       mint,
+      premine,
       spaced_rune,
       symbol,
     } = etched;
@@ -248,8 +247,6 @@ impl<'a, 'tx, 'client> RuneUpdater<'a, 'tx, 'client> {
     let number = self.runes;
     self.runes += 1;
 
-    let premine = u128::MAX - balance;
-
     self
       .statistic_to_count
       .insert(&Statistic::Runes.into(), self.runes)?;
@@ -265,7 +262,6 @@ impl<'a, 'tx, 'client> RuneUpdater<'a, 'tx, 'client> {
         number,
         premine,
         spaced_rune,
-        supply: premine,
         symbol,
         timestamp: self.block_time,
       }
@@ -320,36 +316,43 @@ impl<'a, 'tx, 'client> RuneUpdater<'a, 'tx, 'client> {
     };
 
     Ok(Some(Etched {
-      balance: u128::MAX,
-      divisibility: etching.divisibility,
+      divisibility: etching.divisibility.unwrap_or_default(),
       id: RuneId {
         block: self.height,
         tx: tx_index,
       },
-      spaced_rune: SpacedRune {
-        rune,
-        spacers: etching.spacers,
-      },
-      symbol: etching.symbol,
       mint: etching.mint.map(|mint| MintEntry {
+        cap: mint.cap,
         deadline: mint.deadline,
         end: mint.term.map(|term| term + self.height),
-        limit: mint.limit.map(|limit| limit.min(runes::MAX_LIMIT)),
+        limit: mint.limit,
       }),
+      premine: etching.premine.unwrap_or_default(),
+      spaced_rune: SpacedRune {
+        rune,
+        spacers: etching.spacers.unwrap_or_default(),
+      },
+      symbol: etching.symbol,
     }))
   }
 
-  fn claim(&self, id: RuneId) -> Result<Option<Claim>> {
+  fn claim(&mut self, id: RuneId) -> Result<Option<Claim>> {
     let Some(entry) = self.id_to_entry.get(&id.store())? else {
       return Ok(None);
     };
 
-    let rune_entry = RuneEntry::load(entry.value());
+    let mut rune_entry = RuneEntry::load(entry.value());
 
     let Ok(limit) = rune_entry.mintable(Height(self.height), self.block_time) else {
       return Ok(None);
     };
 
+    drop(entry);
+
+    rune_entry.mints += 1;
+
+    self.id_to_entry.insert(&id.store(), rune_entry.store())?;
+
     Ok(Some(Claim { id, limit }))
   }
 
diff --git a/src/runes.rs b/src/runes.rs
index 1ed1d22886..6d03e69b5a 100644
--- a/src/runes.rs
+++ b/src/runes.rs
@@ -9,7 +9,6 @@ pub use {
 };
 
 pub const MAX_DIVISIBILITY: u8 = 38;
-pub const MAX_LIMIT: u128 = u64::MAX as u128;
 
 const MAGIC_NUMBER: opcodes::All = opcodes::all::OP_PUSHNUM_13;
 const RESERVED: u128 = 6402364363415443603228541259936211926;
@@ -28,21 +27,21 @@ pub mod varint;
 
 type Result<T, E = Error> = std::result::Result<T, E>;
 
-#[derive(Debug)]
+#[derive(Debug, PartialEq)]
 pub enum MintError {
-  Deadline((Rune, u32)),
-  End((Rune, u32)),
-  Unmintable(Rune),
+  Cap(u128),
+  Deadline(u32),
+  End(u32),
+  Unmintable,
 }
 
 impl Display for MintError {
   fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
     match self {
-      MintError::Deadline((rune, deadline)) => {
-        write!(f, "rune {rune} mint ended at {deadline}")
-      }
-      MintError::End((rune, end)) => write!(f, "rune {rune} mint ended on block {end}"),
-      MintError::Unmintable(rune) => write!(f, "rune {rune} not mintable"),
+      MintError::Cap(cap) => write!(f, "limited to {cap} mints"),
+      MintError::Deadline(deadline) => write!(f, "mint ended at {deadline}"),
+      MintError::End(end) => write!(f, "mint ended on block {end}"),
+      MintError::Unmintable => write!(f, "not mintable"),
     }
   }
 }
@@ -143,6 +142,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -160,7 +160,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -188,6 +187,7 @@ mod tests {
           }],
           etching: Some(Etching {
             rune: Some(Rune(minimum - 1)),
+            premine: Some(u128::MAX),
             ..default()
           }),
           ..default()
@@ -213,6 +213,7 @@ mod tests {
           }],
           etching: Some(Etching {
             rune: Some(Rune(minimum)),
+            premine: Some(u128::MAX),
             ..default()
           }),
           ..default()
@@ -230,7 +231,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: id.block,
             ..default()
           },
@@ -276,6 +276,7 @@ mod tests {
           }],
           etching: Some(Etching {
             rune: Some(Rune(RESERVED - 1)),
+            premine: Some(u128::MAX),
             ..default()
           }),
           ..default()
@@ -293,7 +294,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: id.block,
             ..default()
           },
@@ -321,6 +321,7 @@ mod tests {
           }],
           etching: Some(Etching {
             rune: None,
+            premine: Some(u128::MAX),
             ..default()
           }),
           ..default()
@@ -344,7 +345,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: 2,
           ..default()
         },
@@ -370,6 +370,7 @@ mod tests {
             output: 0,
           }],
           etching: Some(Etching {
+            premine: Some(u128::MAX),
             rune: None,
             ..default()
           }),
@@ -395,7 +396,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: 2,
             ..default()
           },
@@ -409,7 +409,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: 4,
             number: 1,
             ..default()
@@ -447,8 +446,9 @@ mod tests {
           output: 0,
         }],
         etching: Some(Etching {
-          divisibility: 1,
+          divisibility: Some(1),
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -469,7 +469,6 @@ mod tests {
           etching: txid,
           divisibility: 1,
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -498,6 +497,7 @@ mod tests {
         ],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -515,7 +515,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -544,6 +543,7 @@ mod tests {
         ],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -561,7 +561,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           symbol: None,
           timestamp: id.block,
           ..default()
@@ -586,6 +585,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(100),
           ..default()
         }),
         ..default()
@@ -603,7 +603,6 @@ mod tests {
             spacers: 0,
           },
           premine: 100,
-          supply: 100,
           timestamp: id.block,
           ..default()
         },
@@ -632,6 +631,7 @@ mod tests {
         ],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(200),
           ..default()
         }),
         ..default()
@@ -650,7 +650,6 @@ mod tests {
             spacers: 0,
           },
           premine: 200,
-          supply: 200,
           timestamp: id.block,
           ..default()
         },
@@ -696,7 +695,6 @@ mod tests {
             spacers: 0,
           },
           premine: 0,
-          supply: 0,
           timestamp: id.block,
           ..default()
         },
@@ -718,6 +716,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -735,7 +734,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -777,7 +775,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -832,7 +829,7 @@ mod tests {
   }
 
   #[test]
-  fn etched_rune_open_etching_parameters_are_unset_for_cenotaph() {
+  fn etched_rune_open_mint_parameters_are_unset_for_cenotaph() {
     let context = Context::builder().arg("--index-runes").build();
 
     let (txid0, id) = context.etch(
@@ -843,15 +840,17 @@ mod tests {
           output: 0,
         }],
         etching: Some(Etching {
+          premine: Some(u128::MAX),
           rune: Some(Rune(RUNE)),
           mint: Some(Mint {
+            cap: Some(1),
             deadline: Some(1),
             limit: Some(1),
             term: Some(1),
           }),
-          divisibility: 1,
+          divisibility: Some(1),
           symbol: Some('$'),
-          spacers: 1,
+          spacers: Some(1),
         }),
         cenotaph: true,
         ..default()
@@ -863,18 +862,17 @@ mod tests {
       [(
         id,
         RuneEntry {
-          burned: 0,
+          burned: u128::MAX,
           divisibility: 1,
           etching: txid0,
           mint: None,
           mints: 0,
           number: 0,
-          premine: 0,
+          premine: u128::MAX,
           spaced_rune: SpacedRune {
             rune: Rune(RUNE),
             spacers: 1,
           },
-          supply: 0,
           symbol: Some('$'),
           timestamp: id.block,
         },
@@ -941,6 +939,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -958,7 +957,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -997,7 +995,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -1019,6 +1016,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -1036,7 +1034,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -1068,7 +1065,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -1096,6 +1092,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -1113,7 +1110,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -1146,7 +1142,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           burned: u128::MAX,
           ..default()
@@ -1169,6 +1164,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -1186,7 +1182,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -1225,7 +1220,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -1253,6 +1247,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -1270,7 +1265,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -1309,7 +1303,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           burned: u128::MAX,
           timestamp: id.block,
           ..default()
@@ -1333,6 +1326,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -1350,7 +1344,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -1382,7 +1375,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -1410,6 +1402,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -1427,7 +1420,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -1463,7 +1455,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -1485,6 +1476,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -1502,7 +1494,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id0.block,
           ..default()
         },
@@ -1525,6 +1516,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE + 1)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -1543,7 +1535,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: id0.block,
             ..default()
           },
@@ -1557,7 +1548,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: id1.block,
             number: 1,
             ..default()
@@ -1603,7 +1593,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: id0.block,
             ..default()
           },
@@ -1617,7 +1606,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: id1.block,
             number: 1,
             ..default()
@@ -1647,6 +1635,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -1664,7 +1653,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id0.block,
           ..default()
         },
@@ -1687,6 +1675,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE + 1)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -1705,7 +1694,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: id0.block,
             ..default()
           },
@@ -1719,7 +1707,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: id1.block,
             number: 1,
             ..default()
@@ -1765,7 +1752,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: id0.block,
             ..default()
           },
@@ -1779,7 +1765,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: id1.block,
             number: 1,
             ..default()
@@ -1832,7 +1817,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: id0.block,
             ..default()
           },
@@ -1846,7 +1830,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: id1.block,
             number: 1,
             ..default()
@@ -1885,6 +1868,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -1902,7 +1886,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id0.block,
           ..default()
         },
@@ -1925,6 +1908,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE + 1)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -1943,7 +1927,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: id0.block,
             ..default()
           },
@@ -1957,7 +1940,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: id1.block,
             number: 1,
             ..default()
@@ -2021,7 +2003,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: id0.block,
             ..default()
           },
@@ -2035,7 +2016,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: id1.block,
             number: 1,
             ..default()
@@ -2066,6 +2046,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -2083,7 +2064,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -2120,7 +2100,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -2130,7 +2109,7 @@ mod tests {
   }
 
   #[test]
-  fn rune_rarity_is_assigned_correctly() {
+  fn multiple_runes_may_be_etched_in_one_block() {
     let context = Context::builder().arg("--index-runes").build();
 
     let (txid0, id0) = context.etch(
@@ -2142,6 +2121,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -2158,6 +2138,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE + 1)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -2176,7 +2157,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: id0.block,
             ..default()
           },
@@ -2190,7 +2170,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: id1.block,
             number: 1,
             ..default()
@@ -2231,6 +2210,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -2248,7 +2228,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -2297,7 +2276,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -2325,6 +2303,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -2342,7 +2321,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id0.block,
           ..default()
         },
@@ -2365,6 +2343,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE + 1)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -2383,7 +2362,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: id0.block,
             ..default()
           },
@@ -2397,7 +2375,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: id1.block,
             number: 1,
             ..default()
@@ -2458,7 +2435,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: id0.block,
             ..default()
           },
@@ -2472,7 +2448,6 @@ mod tests {
               spacers: 0,
             },
             premine: u128::MAX,
-            supply: u128::MAX,
             timestamp: id1.block,
             number: 1,
             ..default()
@@ -2511,6 +2486,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX / 2),
           ..default()
         }),
         ..default()
@@ -2528,7 +2504,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX / 2,
-          supply: u128::MAX / 2,
           timestamp: id.block,
           ..default()
         },
@@ -2570,7 +2545,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX / 2,
-          supply: u128::MAX / 2,
           timestamp: id.block,
           ..default()
         },
@@ -2598,6 +2572,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -2616,7 +2591,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -2638,6 +2612,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -2655,7 +2630,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -2684,6 +2658,7 @@ mod tests {
         ],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -2701,7 +2676,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -2723,6 +2697,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -2740,7 +2715,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -2774,6 +2748,7 @@ mod tests {
         ],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -2791,7 +2766,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -2837,6 +2811,7 @@ mod tests {
         ],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -2854,7 +2829,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -2881,6 +2855,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(4000),
           ..default()
         }),
         ..default()
@@ -2898,7 +2873,6 @@ mod tests {
             spacers: 0,
           },
           premine: 4000,
-          supply: 4000,
           timestamp: id.block,
           ..default()
         },
@@ -2932,6 +2906,7 @@ mod tests {
         ],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -2949,7 +2924,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -2982,6 +2956,7 @@ mod tests {
         ],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -2999,7 +2974,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -3029,6 +3003,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -3046,7 +3021,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -3089,7 +3063,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -3126,6 +3099,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -3143,7 +3117,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -3193,7 +3166,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -3230,6 +3202,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -3247,7 +3220,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -3297,7 +3269,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -3334,6 +3305,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -3351,7 +3323,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -3394,7 +3365,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -3431,6 +3401,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -3448,7 +3419,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -3498,7 +3468,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -3535,6 +3504,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -3552,7 +3522,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -3602,7 +3571,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -3654,6 +3622,7 @@ mod tests {
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
           symbol: Some('$'),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -3671,7 +3640,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           symbol: Some('$'),
           timestamp: id.block,
           ..default()
@@ -3694,6 +3662,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -3711,7 +3680,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -3733,6 +3701,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -3750,7 +3719,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -3793,7 +3761,6 @@ mod tests {
             spacers: 0,
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         },
@@ -3808,13 +3775,6 @@ mod tests {
     );
   }
 
-  #[test]
-  fn max_limit() {
-    MAX_LIMIT
-      .checked_mul(u128::from(u16::MAX) * u128::from(RUNE_COMMIT_INTERVAL) * 365 * 1_000_000_000)
-      .unwrap();
-  }
-
   #[test]
   fn rune_can_be_minted_without_edict() {
     let context = Context::builder().arg("--index-runes").build();
@@ -3825,6 +3785,7 @@ mod tests {
           rune: Some(Rune(RUNE)),
           mint: Some(Mint {
             limit: Some(1000),
+            cap: Some(100),
             ..default()
           }),
           ..default()
@@ -3847,6 +3808,7 @@ mod tests {
           mints: 0,
           mint: Some(MintEntry {
             limit: Some(1000),
+            cap: Some(100),
             ..default()
           }),
           ..default()
@@ -3876,6 +3838,7 @@ mod tests {
           etching: txid0,
           mint: Some(MintEntry {
             limit: Some(1000),
+            cap: Some(100),
             ..default()
           }),
           mints: 1,
@@ -3884,7 +3847,6 @@ mod tests {
             spacers: 0,
           },
           premine: 0,
-          supply: 1000,
           timestamp: id.block,
           ..default()
         },
@@ -3908,6 +3870,7 @@ mod tests {
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
           mint: Some(Mint {
+            cap: Some(100),
             limit: Some(1000),
             ..default()
           }),
@@ -3929,10 +3892,10 @@ mod tests {
           },
           timestamp: id.block,
           premine: 0,
-          supply: 0,
           mints: 0,
           mint: Some(MintEntry {
             limit: Some(1000),
+            cap: Some(100),
             ..default()
           }),
           ..default()
@@ -3967,6 +3930,7 @@ mod tests {
           etching: txid0,
           mint: Some(MintEntry {
             limit: Some(1000),
+            cap: Some(100),
             ..default()
           }),
           mints: 1,
@@ -3975,7 +3939,6 @@ mod tests {
             spacers: 0,
           },
           premine: 0,
-          supply: 1000,
           timestamp: id.block,
           ..default()
         },
@@ -4016,6 +3979,7 @@ mod tests {
           etching: txid0,
           mint: Some(MintEntry {
             limit: Some(1000),
+            cap: Some(100),
             ..default()
           }),
           mints: 2,
@@ -4024,7 +3988,6 @@ mod tests {
             spacers: 0,
           },
           premine: 0,
-          supply: 2000,
           timestamp: id.block,
           ..default()
         },
@@ -4076,6 +4039,7 @@ mod tests {
           etching: txid0,
           mint: Some(MintEntry {
             limit: Some(1000),
+            cap: Some(100),
             ..default()
           }),
           mints: 3,
@@ -4084,7 +4048,6 @@ mod tests {
             spacers: 0,
           },
           premine: 0,
-          supply: 3000,
           timestamp: id.block,
           ..default()
         },
@@ -4109,7 +4072,7 @@ mod tests {
   }
 
   #[test]
-  fn open_etchings_can_be_limited_to_term() {
+  fn open_mints_can_be_limited_to_term() {
     let context = Context::builder().arg("--index-runes").build();
 
     let (txid0, id) = context.etch(
@@ -4118,6 +4081,7 @@ mod tests {
           rune: Some(Rune(RUNE)),
           mint: Some(Mint {
             limit: Some(1000),
+            cap: Some(100),
             term: Some(2),
             ..default()
           }),
@@ -4140,6 +4104,7 @@ mod tests {
           mint: Some(MintEntry {
             limit: Some(1000),
             end: Some(id.block + 2),
+            cap: Some(100),
             ..default()
           }),
           timestamp: id.block,
@@ -4180,10 +4145,10 @@ mod tests {
           mint: Some(MintEntry {
             limit: Some(1000),
             end: Some(id.block + 2),
+            cap: Some(100),
             ..default()
           }),
           premine: 0,
-          supply: 1000,
           timestamp: id.block,
           mints: 1,
           ..default()
@@ -4227,11 +4192,11 @@ mod tests {
             spacers: 0,
           },
           premine: 0,
-          supply: 1000,
           timestamp: id.block,
           mint: Some(MintEntry {
             limit: Some(1000),
             end: Some(id.block + 2),
+            cap: Some(100),
             ..default()
           }),
           mints: 1,
@@ -4249,7 +4214,7 @@ mod tests {
   }
 
   #[test]
-  fn open_etchings_with_term_zero_can_be_premined() {
+  fn open_mints_with_term_zero_can_be_premined() {
     let context = Context::builder().arg("--index-runes").build();
 
     let (txid, id) = context.etch(
@@ -4261,6 +4226,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(1111),
           mint: Some(Mint {
             limit: Some(1000),
             term: Some(0),
@@ -4289,7 +4255,6 @@ mod tests {
           }),
           timestamp: id.block,
           premine: 1111,
-          supply: 1111,
           ..default()
         },
       )],
@@ -4327,7 +4292,6 @@ mod tests {
             ..default()
           }),
           premine: 1111,
-          supply: 1111,
           ..default()
         },
       )],
@@ -4336,7 +4300,7 @@ mod tests {
   }
 
   #[test]
-  fn open_etchings_with_end_before_deadline() {
+  fn open_mints_with_end_before_deadline() {
     let context = Context::builder().arg("--index-runes").build();
 
     context.mine_blocks(1);
@@ -4346,6 +4310,7 @@ mod tests {
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
           mint: Some(Mint {
+            cap: Some(2),
             limit: Some(1000),
             deadline: Some(12),
             term: Some(2),
@@ -4368,6 +4333,7 @@ mod tests {
           },
           timestamp: 9,
           mint: Some(MintEntry {
+            cap: Some(2),
             deadline: Some(12),
             end: Some(11),
             limit: Some(1000),
@@ -4401,11 +4367,11 @@ mod tests {
             spacers: 0,
           },
           premine: 0,
-          supply: 1000,
           timestamp: 9,
           mints: 1,
           etching: txid0,
           mint: Some(MintEntry {
+            cap: Some(2),
             deadline: Some(12),
             end: Some(11),
             limit: Some(1000),
@@ -4446,9 +4412,9 @@ mod tests {
             spacers: 0,
           },
           premine: 0,
-          supply: 1000,
           timestamp: 9,
           mint: Some(MintEntry {
+            cap: Some(2),
             limit: Some(1000),
             deadline: Some(12),
             end: Some(11),
@@ -4468,7 +4434,7 @@ mod tests {
   }
 
   #[test]
-  fn open_etchings_with_deadline_before_end() {
+  fn open_mints_with_deadline_before_end() {
     let context = Context::builder().arg("--index-runes").build();
 
     context.mine_blocks(1);
@@ -4478,6 +4444,7 @@ mod tests {
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
           mint: Some(Mint {
+            cap: Some(2),
             limit: Some(1000),
             deadline: Some(11),
             term: Some(3),
@@ -4500,6 +4467,7 @@ mod tests {
           },
           timestamp: id.block,
           mint: Some(MintEntry {
+            cap: Some(2),
             deadline: Some(11),
             end: Some(12),
             limit: Some(1000),
@@ -4533,11 +4501,11 @@ mod tests {
             spacers: 0,
           },
           premine: 0,
-          supply: 1000,
           timestamp: id.block,
           mints: 1,
           etching: txid0,
           mint: Some(MintEntry {
+            cap: Some(2),
             deadline: Some(11),
             end: Some(12),
             limit: Some(1000),
@@ -4578,9 +4546,9 @@ mod tests {
             spacers: 0,
           },
           premine: 0,
-          supply: 1000,
           timestamp: id.block,
           mint: Some(MintEntry {
+            cap: Some(2),
             limit: Some(1000),
             deadline: Some(11),
             end: Some(12),
@@ -4600,7 +4568,7 @@ mod tests {
   }
 
   #[test]
-  fn open_etchings_can_be_limited_to_deadline() {
+  fn open_mints_can_be_limited_to_deadline() {
     let context = Context::builder().arg("--index-runes").build();
 
     let (txid0, id) = context.etch(
@@ -4609,6 +4577,7 @@ mod tests {
           rune: Some(Rune(RUNE)),
           mint: Some(Mint {
             limit: Some(1000),
+            cap: Some(100),
             deadline: Some(RUNE_COMMIT_INTERVAL + 4),
             ..default()
           }),
@@ -4632,6 +4601,7 @@ mod tests {
           mint: Some(MintEntry {
             deadline: Some(id.block + 2),
             limit: Some(1000),
+            cap: Some(100),
             ..default()
           }),
           ..default()
@@ -4667,13 +4637,13 @@ mod tests {
             rune: Rune(RUNE),
             spacers: 0,
           },
-          supply: 1000,
           timestamp: id.block,
           mints: 1,
           etching: txid0,
           mint: Some(MintEntry {
             deadline: Some(id.block + 2),
             limit: Some(1000),
+            cap: Some(100),
             ..default()
           }),
           ..default()
@@ -4716,14 +4686,103 @@ mod tests {
             rune: Rune(RUNE),
             spacers: 0,
           },
-          supply: 1000,
           timestamp: id.block,
           mint: Some(MintEntry {
             limit: Some(1000),
             deadline: Some(id.block + 2),
+            cap: Some(100),
+            ..default()
+          }),
+          mints: 1,
+          ..default()
+        },
+      )],
+      [(
+        OutPoint {
+          txid: txid1,
+          vout: 0,
+        },
+        vec![(id, 1000)],
+      )],
+    );
+  }
+
+  #[test]
+  fn open_mints_can_be_limited_to_cap() {
+    let context = Context::builder().arg("--index-runes").build();
+
+    let (txid0, id) = context.etch(
+      Runestone {
+        etching: Some(Etching {
+          rune: Some(Rune(RUNE)),
+          mint: Some(Mint {
+            limit: Some(1000),
+            cap: Some(2),
+            ..default()
+          }),
+          ..default()
+        }),
+        ..default()
+      },
+      1,
+    );
+
+    context.assert_runes(
+      [(
+        id,
+        RuneEntry {
+          etching: txid0,
+          spaced_rune: SpacedRune {
+            rune: Rune(RUNE),
+            spacers: 0,
+          },
+          timestamp: id.block,
+          mint: Some(MintEntry {
+            limit: Some(1000),
+            cap: Some(2),
             ..default()
           }),
+          ..default()
+        },
+      )],
+      [],
+    );
+
+    let txid1 = context.rpc_server.broadcast_tx(TransactionTemplate {
+      inputs: &[(2, 0, 0, Witness::new())],
+      op_return: Some(
+        Runestone {
+          edicts: vec![Edict {
+            id,
+            amount: 1000,
+            output: 0,
+          }],
+          claim: Some(id),
+          ..default()
+        }
+        .encipher(),
+      ),
+      ..default()
+    });
+
+    context.mine_blocks(1);
+
+    context.assert_runes(
+      [(
+        id,
+        RuneEntry {
+          spaced_rune: SpacedRune {
+            rune: Rune(RUNE),
+            spacers: 0,
+          },
+          timestamp: id.block,
           mints: 1,
+          etching: txid0,
+          mint: Some(MintEntry {
+            cap: Some(2),
+            limit: Some(1000),
+            ..default()
+          }),
           ..default()
         },
       )],
@@ -4735,10 +4794,122 @@ mod tests {
         vec![(id, 1000)],
       )],
     );
+
+    let txid2 = context.rpc_server.broadcast_tx(TransactionTemplate {
+      inputs: &[(3, 0, 0, Witness::new())],
+      op_return: Some(
+        Runestone {
+          edicts: vec![Edict {
+            id,
+            amount: 1000,
+            output: 0,
+          }],
+          claim: Some(id),
+          ..default()
+        }
+        .encipher(),
+      ),
+      ..default()
+    });
+
+    context.mine_blocks(1);
+
+    context.assert_runes(
+      [(
+        id,
+        RuneEntry {
+          etching: txid0,
+          spaced_rune: SpacedRune {
+            rune: Rune(RUNE),
+            spacers: 0,
+          },
+          timestamp: id.block,
+          mint: Some(MintEntry {
+            limit: Some(1000),
+            cap: Some(2),
+            ..default()
+          }),
+          mints: 2,
+          ..default()
+        },
+      )],
+      [
+        (
+          OutPoint {
+            txid: txid1,
+            vout: 0,
+          },
+          vec![(id, 1000)],
+        ),
+        (
+          OutPoint {
+            txid: txid2,
+            vout: 0,
+          },
+          vec![(id, 1000)],
+        ),
+      ],
+    );
+
+    context.rpc_server.broadcast_tx(TransactionTemplate {
+      inputs: &[(4, 0, 0, Witness::new())],
+      op_return: Some(
+        Runestone {
+          edicts: vec![Edict {
+            id,
+            amount: 1000,
+            output: 0,
+          }],
+          claim: Some(id),
+          ..default()
+        }
+        .encipher(),
+      ),
+      ..default()
+    });
+
+    context.mine_blocks(1);
+
+    context.assert_runes(
+      [(
+        id,
+        RuneEntry {
+          etching: txid0,
+          spaced_rune: SpacedRune {
+            rune: Rune(RUNE),
+            spacers: 0,
+          },
+          timestamp: id.block,
+          mint: Some(MintEntry {
+            limit: Some(1000),
+            cap: Some(2),
+            ..default()
+          }),
+          mints: 2,
+          ..default()
+        },
+      )],
+      [
+        (
+          OutPoint {
+            txid: txid1,
+            vout: 0,
+          },
+          vec![(id, 1000)],
+        ),
+        (
+          OutPoint {
+            txid: txid2,
+            vout: 0,
+          },
+          vec![(id, 1000)],
+        ),
+      ],
+    );
   }
 
   #[test]
-  fn open_etching_claims_can_use_split() {
+  fn open_mint_claims_can_use_split() {
     let context = Context::builder().arg("--index-runes").build();
 
     let (txid0, id) = context.etch(
@@ -4747,6 +4918,7 @@ mod tests {
           rune: Some(Rune(RUNE)),
           mint: Some(Mint {
             limit: Some(1000),
+            cap: Some(100),
             ..default()
           }),
           ..default()
@@ -4767,6 +4939,7 @@ mod tests {
           },
           mint: Some(MintEntry {
             limit: Some(1000),
+            cap: Some(100),
             ..default()
           }),
           timestamp: id.block,
@@ -4805,10 +4978,10 @@ mod tests {
             rune: Rune(RUNE),
             spacers: 0,
           },
-          supply: 1000,
           timestamp: id.block,
           mint: Some(MintEntry {
             limit: Some(1000),
+            cap: Some(100),
             ..default()
           }),
           mints: 1,
@@ -4842,6 +5015,7 @@ mod tests {
       Runestone {
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(2000),
           mint: Some(Mint {
             limit: Some(1000),
             ..default()
@@ -4873,7 +5047,6 @@ mod tests {
           }),
           timestamp: id.block,
           premine: 2000,
-          supply: 2000,
           ..default()
         },
       )],
@@ -4930,8 +5103,10 @@ mod tests {
       Runestone {
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(2000),
           mint: Some(Mint {
             limit: Some(1000),
+            cap: Some(1),
             ..default()
           }),
           ..default()
@@ -4957,11 +5132,11 @@ mod tests {
           },
           mint: Some(MintEntry {
             limit: Some(1000),
+            cap: Some(1),
             ..default()
           }),
           timestamp: rune_id.block,
           premine: 2000,
-          supply: 2000,
           mints: 0,
           ..default()
         },
@@ -4986,6 +5161,7 @@ mod tests {
           rune: Some(Rune(RUNE)),
           mint: Some(Mint {
             limit: Some(1000),
+            cap: Some(100),
             ..default()
           }),
           ..default()
@@ -5025,10 +5201,10 @@ mod tests {
           },
           mint: Some(MintEntry {
             limit: Some(1000),
+            cap: Some(100),
             ..default()
           }),
           timestamp: id.block,
-          supply: 1000,
           mints: 1,
           ..default()
         },
@@ -5044,7 +5220,7 @@ mod tests {
   }
 
   #[test]
-  fn multiple_edicts_in_one_transaction_may_claim_open_etching() {
+  fn multiple_edicts_in_one_transaction_may_claim_open_mint() {
     let context = Context::builder().arg("--index-runes").build();
 
     let (txid0, id) = context.etch(
@@ -5053,6 +5229,7 @@ mod tests {
           rune: Some(Rune(RUNE)),
           mint: Some(Mint {
             limit: Some(1000),
+            cap: Some(100),
             ..default()
           }),
           ..default()
@@ -5073,6 +5250,7 @@ mod tests {
           },
           mint: Some(MintEntry {
             limit: Some(1000),
+            cap: Some(100),
             ..default()
           }),
           timestamp: id.block,
@@ -5124,10 +5302,10 @@ mod tests {
           },
           mint: Some(MintEntry {
             limit: Some(1000),
+            cap: Some(100),
             ..default()
           }),
           timestamp: id.block,
-          supply: 1000,
           mints: 1,
           ..default()
         },
diff --git a/src/runes/etching.rs b/src/runes/etching.rs
index 0d5b9f49ec..9329388b4e 100644
--- a/src/runes/etching.rs
+++ b/src/runes/etching.rs
@@ -2,9 +2,10 @@ use super::*;
 
 #[derive(Default, Serialize, Deserialize, Debug, PartialEq, Copy, Clone, Eq)]
 pub struct Etching {
-  pub divisibility: u8,
+  pub divisibility: Option<u8>,
   pub mint: Option<Mint>,
+  pub premine: Option<u128>,
   pub rune: Option<Rune>,
-  pub spacers: u32,
+  pub spacers: Option<u32>,
   pub symbol: Option<char>,
 }
diff --git a/src/runes/mint.rs b/src/runes/mint.rs
index aaacfce349..122714e4db 100644
--- a/src/runes/mint.rs
+++ b/src/runes/mint.rs
@@ -2,6 +2,7 @@ use super::*;
 
 #[derive(Default, Serialize, Deserialize, Debug, PartialEq, Copy, Clone, Eq)]
 pub struct Mint {
+  pub cap: Option<u128>,     // mint cap
   pub deadline: Option<u32>, // unix timestamp
   pub limit: Option<u128>,   // claim amount
   pub term: Option<u32>,     // relative block height
diff --git a/src/runes/runestone.rs b/src/runes/runestone.rs
index 896a87cc92..9741ff583e 100644
--- a/src/runes/runestone.rs
+++ b/src/runes/runestone.rs
@@ -112,23 +112,23 @@ impl Runestone {
       (default_output.into_usize() < transaction.output.len()).then_some(default_output)
     });
 
-    let divisibility = Tag::Divisibility
-      .take(&mut fields, |[divisibility]| {
-        let divisibility = u8::try_from(divisibility).ok()?;
-        (divisibility <= MAX_DIVISIBILITY).then_some(divisibility)
-      })
-      .unwrap_or_default();
+    let divisibility = Tag::Divisibility.take(&mut fields, |[divisibility]| {
+      let divisibility = u8::try_from(divisibility).ok()?;
+      (divisibility <= MAX_DIVISIBILITY).then_some(divisibility)
+    });
 
-    let limit = Tag::Limit.take(&mut fields, |[limit]| (limit <= MAX_LIMIT).then_some(limit));
+    let limit = Tag::Limit.take(&mut fields, |[limit]| Some(limit));
 
     let rune = Tag::Rune.take(&mut fields, |[rune]| Some(Rune(rune)));
 
-    let spacers = Tag::Spacers
-      .take(&mut fields, |[spacers]| {
-        let spacers = u32::try_from(spacers).ok()?;
-        (spacers <= MAX_SPACERS).then_some(spacers)
-      })
-      .unwrap_or_default();
+    let cap = Tag::Cap.take(&mut fields, |[cap]| Some(cap));
+
+    let premine = Tag::Premine.take(&mut fields, |[premine]| Some(premine));
+
+    let spacers = Tag::Spacers.take(&mut fields, |[spacers]| {
+      let spacers = u32::try_from(spacers).ok()?;
+      (spacers <= MAX_SPACERS).then_some(spacers)
+    });
 
     let symbol = Tag::Symbol.take(&mut fields, |[symbol]| {
       char::from_u32(u32::try_from(symbol).ok()?)
@@ -144,24 +144,34 @@ impl Runestone {
 
     let mint = Flag::Mint.take(&mut flags);
 
+    let overflow = (|| {
+      let premine = premine.unwrap_or_default();
+      let cap = cap.unwrap_or_default();
+      let limit = limit.unwrap_or_default();
+      premine.checked_add(cap.checked_mul(limit)?)
+    })()
+    .is_none();
+
     let etching = if etch {
       Some(Etching {
         divisibility,
-        rune,
-        spacers,
-        symbol,
         mint: mint.then_some(Mint {
+          cap,
           deadline,
           limit,
           term,
         }),
+        premine,
+        rune,
+        spacers,
+        symbol,
       })
     } else {
       None
     };
 
     Ok(Some(Self {
-      cenotaph: cenotaph || flags != 0 || fields.keys().any(|tag| tag % 2 == 0),
+      cenotaph: cenotaph || overflow || flags != 0 || fields.keys().any(|tag| tag % 2 == 0),
       claim,
       default_output,
       edicts,
@@ -186,18 +196,22 @@ impl Runestone {
         Tag::Rune.encode([rune.0], &mut payload);
       }
 
-      if etching.divisibility != 0 {
-        Tag::Divisibility.encode([etching.divisibility.into()], &mut payload);
+      if let Some(divisibility) = etching.divisibility {
+        Tag::Divisibility.encode([divisibility.into()], &mut payload);
       }
 
-      if etching.spacers != 0 {
-        Tag::Spacers.encode([etching.spacers.into()], &mut payload);
+      if let Some(spacers) = etching.spacers {
+        Tag::Spacers.encode([spacers.into()], &mut payload);
       }
 
       if let Some(symbol) = etching.symbol {
         Tag::Symbol.encode([symbol.into()], &mut payload);
       }
 
+      if let Some(premine) = etching.premine {
+        Tag::Premine.encode([premine], &mut payload);
+      }
+
       if let Some(mint) = etching.mint {
         if let Some(deadline) = mint.deadline {
           Tag::Deadline.encode([deadline.into()], &mut payload);
@@ -210,6 +224,10 @@ impl Runestone {
         if let Some(term) = mint.term {
           Tag::Term.encode([term.into()], &mut payload);
         }
+
+        if let Some(cap) = mint.cap {
+          Tag::Cap.encode([cap], &mut payload);
+        }
       }
     }
 
@@ -806,7 +824,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: None,
-          divisibility: 4,
+          divisibility: Some(4),
           ..default()
         }),
         ..default()
@@ -951,7 +969,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(4)),
-          divisibility: 5,
+          divisibility: Some(5),
           ..default()
         }),
         ..default()
@@ -1050,7 +1068,7 @@ mod tests {
 
   #[test]
   fn decipher_etching_with_all_etching_tags() {
-    assert_eq!(
+    pretty_assert_eq!(
       decipher(&[
         Tag::Flags.into(),
         Flag::Etch.mask() | Flag::Mint.mask(),
@@ -1068,6 +1086,16 @@ mod tests {
         2,
         Tag::Limit.into(),
         3,
+        Tag::Premine.into(),
+        8,
+        Tag::Cap.into(),
+        9,
+        Tag::DefaultOutput.into(),
+        0,
+        Tag::Claim.into(),
+        1,
+        Tag::Claim.into(),
+        1,
         Tag::Body.into(),
         1,
         1,
@@ -1083,15 +1111,19 @@ mod tests {
         etching: Some(Etching {
           rune: Some(Rune(4)),
           mint: Some(Mint {
+            cap: Some(9),
             deadline: Some(7),
             term: Some(2),
             limit: Some(3),
           }),
-          divisibility: 1,
+          premine: Some(8),
+          divisibility: Some(1),
           symbol: Some('a'),
-          spacers: 5,
+          spacers: Some(5),
         }),
-        ..default()
+        cenotaph: false,
+        default_output: Some(0),
+        claim: Some(RuneId::new(1, 1).unwrap()),
       },
     );
   }
@@ -1153,7 +1185,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(4)),
-          divisibility: 1,
+          divisibility: Some(1),
           symbol: Some('a'),
           ..default()
         }),
@@ -1182,7 +1214,10 @@ mod tests {
           amount: 2,
           output: 0,
         }],
-        etching: Some(Etching::default()),
+        etching: Some(Etching {
+          divisibility: Some(0),
+          ..default()
+        }),
         ..default()
       },
     );
@@ -1293,7 +1328,7 @@ mod tests {
           output: 0,
         }],
         etching: Some(Etching {
-          divisibility: 5,
+          divisibility: Some(5),
           ..default()
         }),
         ..default()
@@ -1408,7 +1443,7 @@ mod tests {
     case(
       Vec::new(),
       Some(Etching {
-        divisibility: MAX_DIVISIBILITY,
+        divisibility: Some(MAX_DIVISIBILITY),
         rune: Some(Rune(0)),
         ..default()
       }),
@@ -1418,15 +1453,17 @@ mod tests {
     case(
       Vec::new(),
       Some(Etching {
-        divisibility: MAX_DIVISIBILITY,
+        divisibility: Some(MAX_DIVISIBILITY),
         mint: Some(Mint {
+          cap: None,
           deadline: Some(10000),
           limit: Some(1),
           term: Some(1),
         }),
+        premine: None,
         rune: Some(Rune(0)),
         symbol: Some('$'),
-        spacers: 1,
+        spacers: Some(1),
       }),
       20,
     );
@@ -1447,7 +1484,7 @@ mod tests {
         output: 0,
       }],
       Some(Etching {
-        divisibility: MAX_DIVISIBILITY,
+        divisibility: Some(MAX_DIVISIBILITY),
         rune: Some(Rune(u128::MAX)),
         ..default()
       }),
@@ -1461,7 +1498,7 @@ mod tests {
         output: 0,
       }],
       Some(Etching {
-        divisibility: MAX_DIVISIBILITY,
+        divisibility: Some(MAX_DIVISIBILITY),
         rune: Some(Rune(u128::MAX)),
         ..default()
       }),
@@ -1672,15 +1709,17 @@ mod tests {
     case(
       Runestone {
         etching: Some(Etching {
-          divisibility: 1,
+          premine: Some(1),
+          divisibility: Some(1),
           mint: Some(Mint {
+            cap: Some(1),
             deadline: Some(2),
             limit: Some(3),
             term: Some(5),
           }),
           symbol: Some('@'),
           rune: Some(Rune(4)),
-          spacers: 6,
+          spacers: Some(6),
         }),
         edicts: vec![
           Edict {
@@ -1709,12 +1748,16 @@ mod tests {
         6,
         Tag::Symbol.into(),
         '@'.into(),
+        Tag::Premine.into(),
+        1,
         Tag::Deadline.into(),
         2,
         Tag::Limit.into(),
         3,
         Tag::Term.into(),
         5,
+        Tag::Cap.into(),
+        1,
         Tag::Claim.into(),
         1,
         Tag::Claim.into(),
@@ -1738,11 +1781,12 @@ mod tests {
     case(
       Runestone {
         etching: Some(Etching {
-          divisibility: 0,
+          premine: None,
+          divisibility: None,
           mint: None,
           symbol: None,
           rune: Some(Rune(3)),
-          spacers: 0,
+          spacers: None,
         }),
         cenotaph: false,
         ..default()
@@ -1753,11 +1797,12 @@ mod tests {
     case(
       Runestone {
         etching: Some(Etching {
-          divisibility: 0,
+          premine: None,
+          divisibility: None,
           mint: None,
           symbol: None,
           rune: None,
-          spacers: 0,
+          spacers: None,
         }),
         cenotaph: false,
         ..default()
@@ -1853,12 +1898,6 @@ mod tests {
     assert!(!decipher(&[Tag::Divisibility.into(), u128::MAX]).cenotaph);
   }
 
-  #[test]
-  fn invalid_limit_produces_cenotaph() {
-    assert!(decipher(&[Tag::Limit.into(), u128::MAX]).cenotaph);
-    assert!(decipher(&[Tag::Limit.into(), u128::from(u64::MAX) + 1]).cenotaph);
-  }
-
   #[test]
   fn min_and_max_runes_are_not_cenotaphs() {
     assert!(!decipher(&[Tag::Rune.into(), 0]).cenotaph);
@@ -1879,4 +1918,57 @@ mod tests {
   fn invalid_term_produces_cenotaph() {
     assert!(decipher(&[Tag::Term.into(), u128::MAX]).cenotaph);
   }
+
+  #[test]
+  fn invalid_supply_produces_cenotaph() {
+    assert!(
+      !decipher(&[
+        Tag::Flags.into(),
+        Flag::Etch.mask() | Flag::Mint.mask(),
+        Tag::Cap.into(),
+        1,
+        Tag::Limit.into(),
+        u128::MAX
+      ])
+      .cenotaph
+    );
+
+    assert!(
+      decipher(&[
+        Tag::Flags.into(),
+        Flag::Etch.mask() | Flag::Mint.mask(),
+        Tag::Cap.into(),
+        2,
+        Tag::Limit.into(),
+        u128::MAX
+      ])
+      .cenotaph
+    );
+
+    assert!(
+      decipher(&[
+        Tag::Flags.into(),
+        Flag::Etch.mask() | Flag::Mint.mask(),
+        Tag::Cap.into(),
+        2,
+        Tag::Limit.into(),
+        u128::MAX / 2 + 1
+      ])
+      .cenotaph
+    );
+
+    assert!(
+      decipher(&[
+        Tag::Flags.into(),
+        Flag::Etch.mask() | Flag::Mint.mask(),
+        Tag::Premine.into(),
+        1,
+        Tag::Cap.into(),
+        1,
+        Tag::Limit.into(),
+        u128::MAX
+      ])
+      .cenotaph
+    );
+  }
 }
diff --git a/src/runes/tag.rs b/src/runes/tag.rs
index cd22fea51e..7dbfcca8f6 100644
--- a/src/runes/tag.rs
+++ b/src/runes/tag.rs
@@ -10,6 +10,8 @@ pub(super) enum Tag {
   Deadline = 10,
   DefaultOutput = 12,
   Claim = 14,
+  Cap = 16,
+  Premine = 18,
   #[allow(unused)]
   Cenotaph = 126,
 
diff --git a/src/subcommand/runes.rs b/src/subcommand/runes.rs
index a162f471d7..2617755015 100644
--- a/src/subcommand/runes.rs
+++ b/src/subcommand/runes.rs
@@ -13,7 +13,7 @@ pub struct RuneInfo {
   pub etching: Txid,
   pub id: RuneId,
   pub mint: Option<MintEntry>,
-  pub mints: u64,
+  pub mints: u128,
   pub number: u64,
   pub premine: u128,
   pub rune: SpacedRune,
@@ -40,7 +40,7 @@ pub(crate) fn run(settings: Settings) -> SubcommandResult {
       .map(
         |(
           id,
-          RuneEntry {
+          entry @ RuneEntry {
             burned,
             divisibility,
             etching,
@@ -49,7 +49,6 @@ pub(crate) fn run(settings: Settings) -> SubcommandResult {
             number,
             premine,
             spaced_rune,
-            supply,
             symbol,
             timestamp,
           },
@@ -67,7 +66,7 @@ pub(crate) fn run(settings: Settings) -> SubcommandResult {
               number,
               premine,
               rune: spaced_rune,
-              supply,
+              supply: entry.supply(),
               symbol,
               timestamp: crate::timestamp(timestamp),
               tx: id.tx,
diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs
index 071654151c..27fb5e0b99 100644
--- a/src/subcommand/server.rs
+++ b/src/subcommand/server.rs
@@ -2687,6 +2687,7 @@ mod tests {
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
           symbol: Some('%'),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -2706,7 +2707,6 @@ mod tests {
             spacers: 0
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           symbol: Some('%'),
           ..default()
@@ -2753,6 +2753,7 @@ mod tests {
         etching: Some(Etching {
           rune: Some(rune),
           symbol: Some('%'),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -2777,7 +2778,6 @@ mod tests {
           etching: txid,
           spaced_rune: SpacedRune { rune, spacers: 0 },
           premine: u128::MAX,
-          supply: u128::MAX,
           symbol: Some('%'),
           timestamp: id.block,
           ..default()
@@ -2865,7 +2865,8 @@ mod tests {
         etching: Some(Etching {
           rune: Some(rune),
           symbol: Some('%'),
-          spacers: 1,
+          spacers: Some(1),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -2890,7 +2891,6 @@ mod tests {
           etching: txid,
           spaced_rune: SpacedRune { rune, spacers: 1 },
           premine: u128::MAX,
-          supply: u128::MAX,
           symbol: Some('%'),
           timestamp: id.block,
           ..default()
@@ -2964,6 +2964,7 @@ mod tests {
         }],
         etching: Some(Etching {
           rune: Some(Rune(RUNE)),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -2983,7 +2984,6 @@ mod tests {
             spacers: 0
           },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         }
@@ -3026,8 +3026,9 @@ mod tests {
           output: 0,
         }],
         etching: Some(Etching {
-          divisibility: 1,
+          divisibility: Some(1),
           rune: Some(rune),
+          premine: Some(u128::MAX),
           ..default()
         }),
         ..default()
@@ -3045,7 +3046,6 @@ mod tests {
           etching: txid,
           spaced_rune: SpacedRune { rune, spacers: 0 },
           premine: u128::MAX,
-          supply: u128::MAX,
           timestamp: id.block,
           ..default()
         }
diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs
index a7d69053fc..91e2b3063f 100644
--- a/src/subcommand/wallet/inscribe.rs
+++ b/src/subcommand/wallet/inscribe.rs
@@ -188,6 +188,29 @@ impl Inscribe {
         "rune `{rune}` has already been etched",
       );
 
+      let premine = etching.premine.to_amount(etching.divisibility)?;
+
+      let supply = etching.supply.to_amount(etching.divisibility)?;
+
+      let mintable = etching
+        .mint
+        .map(|mint| -> Result<u128> {
+          mint
+            .cap
+            .checked_mul(mint.limit.to_amount(etching.divisibility)?)
+            .ok_or_else(|| anyhow!("`mint.count` * `mint.limit` over maximum"))
+        })
+        .transpose()?
+        .unwrap_or_default();
+
+      ensure!(
+        supply
+          == premine
+            .checked_add(mintable)
+            .ok_or_else(|| anyhow!("`premine` + `mint.count` * `mint.limit` over maximum"))?,
+        "`supply` not equal to `premine` + `mint.count` * `mint.limit`"
+      );
+
       let bitcoin_client = wallet.bitcoin_client();
 
       let count = bitcoin_client.get_block_count()?;
diff --git a/src/subcommand/wallet/mint.rs b/src/subcommand/wallet/mint.rs
index efefb0f2ad..7982457bf4 100644
--- a/src/subcommand/wallet/mint.rs
+++ b/src/subcommand/wallet/mint.rs
@@ -39,7 +39,7 @@ impl Mint {
 
     let limit = rune_entry
       .mintable(Height(block_height), block_time)
-      .map_err(|e| anyhow!(e))?;
+      .map_err(|err| anyhow!("rune {rune} {err}"))?;
 
     let destination = wallet.get_change_address()?;
 
diff --git a/src/templates/rune.rs b/src/templates/rune.rs
index 08b0fd05b7..e43c5c2b8b 100644
--- a/src/templates/rune.rs
+++ b/src/templates/rune.rs
@@ -28,6 +28,7 @@ mod tests {
           etching: Txid::all_zeros(),
           mints: 100,
           mint: Some(MintEntry {
+            cap: Some(101),
             end: Some(11),
             limit: Some(1000000001),
             deadline: Some(7),
@@ -38,7 +39,6 @@ mod tests {
             rune: Rune(u128::MAX),
             spacers: 1
           },
-          supply: 123456789123456789,
           symbol: Some('%'),
           timestamp: 0,
         },
@@ -73,12 +73,16 @@ mod tests {
       <dd>1.000000001 %</dd>
       <dt>mints</dt>
       <dd>100</dd>
+      <dt>cap</dt>
+      <dd>101</dd>
+      <dt>remaining</dt>
+      <dd>1</dd>
       <dt>mintable</dt>
       <dd>true</dd>
     </dl>
   </dd>
   <dt>supply</dt>
-  <dd>123456789.123456789\u{A0}%</dd>
+  <dd>100.123456889\u{A0}%</dd>
   <dt>premine</dt>
   <dd>0.123456789\u{A0}%</dd>
   <dt>burned</dt>
@@ -112,7 +116,6 @@ mod tests {
             rune: Rune(u128::MAX),
             spacers: 1
           },
-          supply: 123456789123456789,
           symbol: Some('%'),
           timestamp: 0,
         },
@@ -121,32 +124,10 @@ mod tests {
         parent: None,
       },
       "<h1>B•CGDENLQRQWDSLRUGSNLBTMFIJAV</h1>
-<dl>
-  <dt>number</dt>
-  <dd>25</dd>
-  <dt>timestamp</dt>
-  <dd><time>1970-01-01 00:00:00 UTC</time></dd>
-  <dt>id</dt>
-  <dd>10:9</dd>
-  <dt>etching block</dt>
-  <dd><a href=/block/10>10</a></dd>
-  <dt>etching transaction</dt>
-  <dd>9</dd>
+<dl>.*
   <dt>mint</dt>
   <dd>no</dd>
-  <dt>supply</dt>
-  <dd>123456789.123456789\u{A0}%</dd>
-  <dt>premine</dt>
-  <dd>0\u{A0}%</dd>
-  <dt>burned</dt>
-  <dd>123456789.123456789\u{A0}%</dd>
-  <dt>divisibility</dt>
-  <dd>9</dd>
-  <dt>symbol</dt>
-  <dd>%</dd>
-  <dt>etching</dt>
-  <dd><a class=monospace href=/tx/0{64}>0{64}</a></dd>
-</dl>
+.*</dl>
 "
     );
   }
@@ -158,6 +139,7 @@ mod tests {
         entry: RuneEntry {
           burned: 123456789123456789,
           mint: Some(MintEntry {
+            cap: None,
             deadline: None,
             end: None,
             limit: None,
@@ -171,7 +153,6 @@ mod tests {
             rune: Rune(u128::MAX),
             spacers: 1
           },
-          supply: 123456789123456789,
           symbol: Some('%'),
           timestamp: 0,
         },
@@ -180,17 +161,7 @@ mod tests {
         parent: None,
       },
       "<h1>B•CGDENLQRQWDSLRUGSNLBTMFIJAV</h1>
-<dl>
-  <dt>number</dt>
-  <dd>25</dd>
-  <dt>timestamp</dt>
-  <dd><time>1970-01-01 00:00:00 UTC</time></dd>
-  <dt>id</dt>
-  <dd>10:9</dd>
-  <dt>etching block</dt>
-  <dd><a href=/block/10>10</a></dd>
-  <dt>etching transaction</dt>
-  <dd>9</dd>
+<dl>.*
   <dt>mint</dt>
   <dd>
     <dl>
@@ -202,23 +173,15 @@ mod tests {
       <dd>none</dd>
       <dt>mints</dt>
       <dd>0</dd>
+      <dt>cap</dt>
+      <dd>0</dd>
+      <dt>remaining</dt>
+      <dd>0</dd>
       <dt>mintable</dt>
       <dd>false</dd>
     </dl>
   </dd>
-  <dt>supply</dt>
-  <dd>123456789.123456789\u{A0}%</dd>
-  <dt>premine</dt>
-  <dd>0\u{A0}%</dd>
-  <dt>burned</dt>
-  <dd>123456789.123456789\u{A0}%</dd>
-  <dt>divisibility</dt>
-  <dd>9</dd>
-  <dt>symbol</dt>
-  <dd>%</dd>
-  <dt>etching</dt>
-  <dd><a class=monospace href=/tx/0{64}>0{64}</a></dd>
-</dl>
+.*</dl>
 "
     );
   }
diff --git a/src/wallet/batch/etching.rs b/src/wallet/batch/etching.rs
index f7d2683a9d..9af5f4bcc2 100644
--- a/src/wallet/batch/etching.rs
+++ b/src/wallet/batch/etching.rs
@@ -7,5 +7,6 @@ pub struct Etching {
   pub mint: Option<Mint>,
   pub premine: Decimal,
   pub rune: SpacedRune,
+  pub supply: Decimal,
   pub symbol: char,
 }
diff --git a/src/wallet/batch/mint.rs b/src/wallet/batch/mint.rs
index a55c95ad9c..cced5469b7 100644
--- a/src/wallet/batch/mint.rs
+++ b/src/wallet/batch/mint.rs
@@ -5,5 +5,6 @@ use super::*;
 pub struct Mint {
   pub deadline: Option<u32>,
   pub limit: Decimal,
+  pub cap: u128,
   pub term: Option<u32>,
 }
diff --git a/src/wallet/batch/plan.rs b/src/wallet/batch/plan.rs
index 88bf8f57fc..e3b6937ca1 100644
--- a/src/wallet/batch/plan.rs
+++ b/src/wallet/batch/plan.rs
@@ -462,19 +462,21 @@ impl Plan {
         default_output: None,
         edicts,
         etching: Some(runes::Etching {
-          divisibility: etching.divisibility,
+          divisibility: (etching.divisibility > 0).then_some(etching.divisibility),
           mint: etching
             .mint
             .map(|mint| -> Result<runes::Mint> {
               Ok(runes::Mint {
+                cap: (mint.cap > 0).then_some(mint.cap),
                 deadline: mint.deadline,
-                term: mint.term,
                 limit: Some(mint.limit.to_amount(etching.divisibility)?),
+                term: mint.term,
               })
             })
             .transpose()?,
+          premine: (premine > 0).then_some(premine),
           rune: Some(etching.rune.rune),
-          spacers: etching.rune.spacers,
+          spacers: (etching.rune.spacers > 0).then_some(etching.rune.spacers),
           symbol: Some(etching.symbol),
         }),
       };
diff --git a/templates/rune.html b/templates/rune.html
index 5d61bc221a..c83430e8bd 100644
--- a/templates/rune.html
+++ b/templates/rune.html
@@ -33,12 +33,16 @@ <h1>{{ self.entry.spaced_rune }}</h1>
 %% }
       <dt>limit</dt>
 %% if let Some(limit) = mint.limit {
-      <dd>{{ Pile{ amount: limit, divisibility: self.entry.divisibility, symbol: self.entry.symbol } }}</dd>
+      <dd>{{ self.entry.pile(limit) }}</dd>
 %% } else {
       <dd>none</dd>
 %% }
       <dt>mints</dt>
       <dd>{{ self.entry.mints }}</dd>
+      <dt>cap</dt>
+      <dd>{{ mint.cap.unwrap_or_default() }}</dd>
+      <dt>remaining</dt>
+      <dd>{{ mint.cap.unwrap_or_default() - self.entry.mints }}</dd>
       <dt>mintable</dt>
       <dd>{{ self.mintable }}</dd>
     </dl>
@@ -47,11 +51,11 @@ <h1>{{ self.entry.spaced_rune }}</h1>
   <dd>no</dd>
 %% }
   <dt>supply</dt>
-  <dd>{{ Pile{ amount: self.entry.supply, divisibility: self.entry.divisibility, symbol: self.entry.symbol } }}</dd>
+  <dd>{{ self.entry.pile(self.entry.supply()) }}</dd>
   <dt>premine</dt>
-  <dd>{{ Pile{ amount: self.entry.premine, divisibility: self.entry.divisibility, symbol: self.entry.symbol } }}</dd>
+  <dd>{{ self.entry.pile(self.entry.premine) }}</dd>
   <dt>burned</dt>
-  <dd>{{ Pile{ amount: self.entry.burned, divisibility: self.entry.divisibility, symbol: self.entry.symbol } }}</dd>
+  <dd>{{ self.entry.pile(self.entry.burned) }}</dd>
   <dt>divisibility</dt>
   <dd>{{ self.entry.divisibility }}</dd>
 %% if let Some(symbol) = self.entry.symbol {
diff --git a/tests/json_api.rs b/tests/json_api.rs
index 67719f45e7..3e5cd292be 100644
--- a/tests/json_api.rs
+++ b/tests/json_api.rs
@@ -557,7 +557,6 @@ fn get_runes() {
           rune: Rune(RUNE),
           spacers: 0
         },
-        supply: 1000,
         symbol: Some('¢'),
         timestamp: 11,
       },
@@ -594,7 +593,6 @@ fn get_runes() {
               rune: Rune(RUNE),
               spacers: 0
             },
-            supply: 1000,
             symbol: Some('¢'),
             timestamp: 11,
           }
@@ -613,7 +611,6 @@ fn get_runes() {
               rune: Rune(RUNE + 1),
               spacers: 0
             },
-            supply: 1000,
             symbol: Some('¢'),
             timestamp: 19,
           }
@@ -632,7 +629,6 @@ fn get_runes() {
               rune: Rune(RUNE + 2),
               spacers: 0
             },
-            supply: 1000,
             symbol: Some('¢'),
             timestamp: 27,
           }
diff --git a/tests/lib.rs b/tests/lib.rs
index 67b040260e..ab5c6d4903 100644
--- a/tests/lib.rs
+++ b/tests/lib.rs
@@ -188,6 +188,7 @@ fn etch(
     ord_rpc_server,
     batch::File {
       etching: Some(batch::Etching {
+        supply: "1000".parse().unwrap(),
         divisibility: 0,
         mint: None,
         premine: "1000".parse().unwrap(),
@@ -250,12 +251,24 @@ fn batch(
 
   let batch::Etching {
     divisibility,
+    mint,
     premine,
     rune,
+    supply,
     symbol,
-    mint,
   } = batchfile.etching.unwrap();
 
+  {
+    let supply = supply.to_amount(divisibility).unwrap();
+    let premine = premine.to_amount(divisibility).unwrap();
+
+    let mintable = mint
+      .map(|mint| mint.cap * mint.limit.to_amount(divisibility).unwrap())
+      .unwrap_or_default();
+
+    assert_eq!(supply, premine + mintable);
+  }
+
   let mut mint_definition = Vec::<String>::new();
 
   if let Some(mint) = mint {
@@ -298,6 +311,10 @@ fn batch(
 
     mint_definition.push("<dt>mints</dt>".into());
     mint_definition.push("<dd>0</dd>".into());
+    mint_definition.push("<dt>cap</dt>".into());
+    mint_definition.push(format!("<dd>{}</dd>", mint.cap));
+    mint_definition.push("<dt>remaining</dt>".into());
+    mint_definition.push(format!("<dd>{}</dd>", mint.cap));
 
     mint_definition.push("<dt>mintable</dt>".into());
     mint_definition.push(format!("<dd>{mintable}</dd>"));
diff --git a/tests/wallet/balance.rs b/tests/wallet/balance.rs
index f3073c785e..d47f6f178e 100644
--- a/tests/wallet/balance.rs
+++ b/tests/wallet/balance.rs
@@ -108,6 +108,7 @@ fn runic_utxos_are_deducted_from_cardinal() {
         divisibility: 0,
         mint: None,
         premine: "1000".parse().unwrap(),
+        supply: "1000".parse().unwrap(),
         rune: SpacedRune { rune, spacers: 1 },
         symbol: '¢',
       }),
diff --git a/tests/wallet/inscribe.rs b/tests/wallet/inscribe.rs
index 9c1549f353..4bee89b88c 100644
--- a/tests/wallet/inscribe.rs
+++ b/tests/wallet/inscribe.rs
@@ -2640,6 +2640,7 @@ fn batch_inscribe_can_etch_rune() {
           rune: Rune(RUNE),
           spacers: 0,
         },
+        supply: "1000".parse().unwrap(),
         premine: "1000".parse().unwrap(),
         symbol: '¢',
         mint: None,
@@ -2708,6 +2709,7 @@ fn etch_existing_rune_error() {
             rune: Rune(RUNE),
             spacers: 1,
           },
+          supply: "1000".parse().unwrap(),
           premine: "1000".parse().unwrap(),
           symbol: '¢',
           mint: None,
@@ -2752,6 +2754,7 @@ fn etch_reserved_rune_error() {
             spacers: 0,
           },
           premine: "1000".parse().unwrap(),
+          supply: "1000".parse().unwrap(),
           symbol: '¢',
           mint: None,
         }),
@@ -2794,6 +2797,7 @@ fn etch_sub_minimum_rune_error() {
             rune: Rune(0),
             spacers: 0,
           },
+          supply: "1000".parse().unwrap(),
           premine: "1000".parse().unwrap(),
           symbol: '¢',
           mint: None,
@@ -2836,6 +2840,7 @@ fn etch_requires_rune_index() {
             rune: Rune(RUNE),
             spacers: 0,
           },
+          supply: "1000".parse().unwrap(),
           premine: "1000".parse().unwrap(),
           symbol: '¢',
           mint: None,
@@ -2879,6 +2884,7 @@ fn etch_divisibility_over_maximum_error() {
             rune: Rune(RUNE),
             spacers: 0,
           },
+          supply: "1000".parse().unwrap(),
           premine: "1000".parse().unwrap(),
           symbol: '¢',
           mint: None,
@@ -2897,3 +2903,150 @@ fn etch_divisibility_over_maximum_error() {
     .expected_exit_code(1)
     .run_and_extract_stdout();
 }
+
+#[test]
+fn etch_mintable_overflow_error() {
+  let bitcoin_rpc_server = test_bitcoincore_rpc::builder()
+    .network(Network::Regtest)
+    .build();
+
+  let ord_rpc_server =
+    TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--regtest", "--index-runes"], &[]);
+
+  create_wallet(&bitcoin_rpc_server, &ord_rpc_server);
+
+  bitcoin_rpc_server.mine_blocks(1);
+
+  CommandBuilder::new("--regtest --index-runes wallet inscribe --fee-rate 0 --batch batch.yaml")
+    .write("inscription.txt", "foo")
+    .write(
+      "batch.yaml",
+      serde_yaml::to_string(&batch::File {
+        etching: Some(batch::Etching {
+          divisibility: 0,
+          rune: SpacedRune {
+            rune: Rune(RUNE),
+            spacers: 0,
+          },
+          supply: default(),
+          premine: default(),
+          symbol: '¢',
+          mint: Some(batch::Mint {
+            cap: 2,
+            term: Some(2),
+            limit: "340282366920938463463374607431768211455".parse().unwrap(),
+            deadline: None,
+          }),
+        }),
+        inscriptions: vec![batch::Entry {
+          file: "inscription.txt".into(),
+          ..default()
+        }],
+        ..default()
+      })
+      .unwrap(),
+    )
+    .bitcoin_rpc_server(&bitcoin_rpc_server)
+    .ord_rpc_server(&ord_rpc_server)
+    .expected_stderr("error: `mint.count` * `mint.limit` over maximum\n")
+    .expected_exit_code(1)
+    .run_and_extract_stdout();
+}
+
+#[test]
+fn etch_mintable_plus_premine_overflow_error() {
+  let bitcoin_rpc_server = test_bitcoincore_rpc::builder()
+    .network(Network::Regtest)
+    .build();
+
+  let ord_rpc_server =
+    TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--regtest", "--index-runes"], &[]);
+
+  create_wallet(&bitcoin_rpc_server, &ord_rpc_server);
+
+  bitcoin_rpc_server.mine_blocks(1);
+
+  CommandBuilder::new("--regtest --index-runes wallet inscribe --fee-rate 0 --batch batch.yaml")
+    .write("inscription.txt", "foo")
+    .write(
+      "batch.yaml",
+      serde_yaml::to_string(&batch::File {
+        etching: Some(batch::Etching {
+          divisibility: 0,
+          rune: SpacedRune {
+            rune: Rune(RUNE),
+            spacers: 0,
+          },
+          supply: default(),
+          premine: "1".parse().unwrap(),
+          symbol: '¢',
+          mint: Some(batch::Mint {
+            cap: 1,
+            term: Some(2),
+            limit: "340282366920938463463374607431768211455".parse().unwrap(),
+            deadline: None,
+          }),
+        }),
+        inscriptions: vec![batch::Entry {
+          file: "inscription.txt".into(),
+          ..default()
+        }],
+        ..default()
+      })
+      .unwrap(),
+    )
+    .bitcoin_rpc_server(&bitcoin_rpc_server)
+    .ord_rpc_server(&ord_rpc_server)
+    .expected_stderr("error: `premine` + `mint.count` * `mint.limit` over maximum\n")
+    .expected_exit_code(1)
+    .run_and_extract_stdout();
+}
+
+#[test]
+fn incorrect_supply_error() {
+  let bitcoin_rpc_server = test_bitcoincore_rpc::builder()
+    .network(Network::Regtest)
+    .build();
+
+  let ord_rpc_server =
+    TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--regtest", "--index-runes"], &[]);
+
+  create_wallet(&bitcoin_rpc_server, &ord_rpc_server);
+
+  bitcoin_rpc_server.mine_blocks(1);
+
+  CommandBuilder::new("--regtest --index-runes wallet inscribe --fee-rate 0 --batch batch.yaml")
+    .write("inscription.txt", "foo")
+    .write(
+      "batch.yaml",
+      serde_yaml::to_string(&batch::File {
+        etching: Some(batch::Etching {
+          divisibility: 0,
+          rune: SpacedRune {
+            rune: Rune(RUNE),
+            spacers: 0,
+          },
+          supply: "1".parse().unwrap(),
+          premine: "1".parse().unwrap(),
+          symbol: '¢',
+          mint: Some(batch::Mint {
+            cap: 1,
+            term: Some(2),
+            limit: "1".parse().unwrap(),
+            deadline: None,
+          }),
+        }),
+        inscriptions: vec![batch::Entry {
+          file: "inscription.txt".into(),
+          ..default()
+        }],
+        ..default()
+      })
+      .unwrap(),
+    )
+    .bitcoin_rpc_server(&bitcoin_rpc_server)
+    .ord_rpc_server(&ord_rpc_server)
+    .expected_stderr("error: `supply` not equal to `premine` + `mint.count` * `mint.limit`\n")
+    .expected_exit_code(1)
+    .run_and_extract_stdout();
+}
diff --git a/tests/wallet/mint.rs b/tests/wallet/mint.rs
index b6024638ef..7160950ec9 100644
--- a/tests/wallet/mint.rs
+++ b/tests/wallet/mint.rs
@@ -28,7 +28,9 @@ fn minting_rune_and_fails_if_after_end() {
         },
         premine: "0".parse().unwrap(),
         symbol: '¢',
+        supply: "111.1".parse().unwrap(),
         mint: Some(batch::Mint {
+          cap: 1,
           term: Some(2),
           limit: "111.1".parse().unwrap(),
           deadline: None,
@@ -120,6 +122,7 @@ fn minting_rune_fails_if_not_mintable() {
           rune: Rune(RUNE),
           spacers: 0,
         },
+        supply: "1000".parse().unwrap(),
         premine: "1000".parse().unwrap(),
         symbol: '¢',
         mint: None,
@@ -165,8 +168,10 @@ fn minting_rune_fails_if_after_deadline() {
         divisibility: 1,
         rune: SpacedRune { rune, spacers: 0 },
         premine: "0".parse().unwrap(),
+        supply: "222.2".parse().unwrap(),
         symbol: '¢',
         mint: Some(batch::Mint {
+          cap: 2,
           term: Some(2),
           limit: "111.1".parse().unwrap(),
           deadline: Some(deadline),
@@ -246,8 +251,10 @@ fn minting_rune_and_then_sending_works() {
           spacers: 0,
         },
         premine: "111".parse().unwrap(),
+        supply: "132".parse().unwrap(),
         symbol: '¢',
         mint: Some(batch::Mint {
+          cap: 1,
           term: Some(10),
           limit: "21".parse().unwrap(),
           deadline: None,
diff --git a/tests/wallet/selection.rs b/tests/wallet/selection.rs
index 25a4bf17d0..26800030e9 100644
--- a/tests/wallet/selection.rs
+++ b/tests/wallet/selection.rs
@@ -128,9 +128,11 @@ fn mint_does_not_select_inscription() {
           spacers: 0,
         },
         premine: "1000".parse().unwrap(),
+        supply: "2000".parse().unwrap(),
         symbol: '¢',
         mint: Some(batch::Mint {
           deadline: None,
+          cap: 1,
           limit: "1000".parse().unwrap(),
           term: None,
         }),
diff --git a/tests/wallet/send.rs b/tests/wallet/send.rs
index e67e835059..c02dfa4263 100644
--- a/tests/wallet/send.rs
+++ b/tests/wallet/send.rs
@@ -886,6 +886,7 @@ fn sending_rune_with_divisibility_works() {
         divisibility: 1,
         rune: SpacedRune { rune, spacers: 0 },
         premine: "1000".parse().unwrap(),
+        supply: "1000".parse().unwrap(),
         symbol: '¢',
         mint: None,
       }),