diff --git a/include/xrpl/basics/base_uint.h b/include/xrpl/basics/base_uint.h
index 2bb74791444..0518ee37ea5 100644
--- a/include/xrpl/basics/base_uint.h
+++ b/include/xrpl/basics/base_uint.h
@@ -634,6 +634,7 @@ operator<<(std::ostream& out, base_uint<Bits, Tag> const& u)
 #ifndef __INTELLISENSE__
 static_assert(sizeof(uint128) == 128 / 8, "There should be no padding bytes");
 static_assert(sizeof(uint160) == 160 / 8, "There should be no padding bytes");
+static_assert(sizeof(uint192) == 192 / 8, "There should be no padding bytes");
 static_assert(sizeof(uint256) == 256 / 8, "There should be no padding bytes");
 #endif
 
diff --git a/include/xrpl/protocol/Indexes.h b/include/xrpl/protocol/Indexes.h
index 0f6e78d9cee..64328ce5f52 100644
--- a/include/xrpl/protocol/Indexes.h
+++ b/include/xrpl/protocol/Indexes.h
@@ -290,10 +290,7 @@ Keylet
 mptIssuance(AccountID const& issuer, std::uint32_t seq) noexcept;
 
 Keylet
-mptIssuance(uint192 const& mpt) noexcept;
-
-Keylet
-mptIssuance(ripple::MPT const& mpt) noexcept;
+mptIssuance(MPTID const& mpt) noexcept;
 
 inline Keylet
 mptIssuance(uint256 const& issuance)
@@ -302,10 +299,7 @@ mptIssuance(uint256 const& issuance)
 }
 
 Keylet
-mptoken(MPT const& issuanceID, AccountID const& holder) noexcept;
-
-Keylet
-mptoken(uint192 const& issuanceID, AccountID const& holder) noexcept;
+mptoken(MPTID const& issuanceID, AccountID const& holder) noexcept;
 
 inline Keylet
 mptoken(uint256 const& mptokenKey)
@@ -336,7 +330,7 @@ getTicketIndex(AccountID const& account, std::uint32_t uSequence);
 uint256
 getTicketIndex(AccountID const& account, SeqProxy ticketSeq);
 
-uint192
+MPTID
 getMptID(AccountID const& account, std::uint32_t sequence);
 
 }  // namespace ripple
diff --git a/include/xrpl/protocol/MPTIssue.h b/include/xrpl/protocol/MPTIssue.h
index 6c01baccf08..7bc34adcf53 100644
--- a/include/xrpl/protocol/MPTIssue.h
+++ b/include/xrpl/protocol/MPTIssue.h
@@ -28,25 +28,17 @@ namespace ripple {
 class MPTIssue
 {
 private:
-    MPT mpt_;
+    MPTID mptID_;
 
 public:
     MPTIssue() = default;
 
-    MPTIssue(MPT const& mpt);
+    MPTIssue(MPTID const& id);
 
-    MPTIssue(uint192 const& id);
-
-    AccountID const&
+    AccountID
     getIssuer() const;
 
-    MPT const&
-    mpt() const;
-
-    MPT&
-    mpt();
-
-    uint192
+    MPTID const&
     getMptID() const;
 
     friend constexpr bool
@@ -59,27 +51,17 @@ class MPTIssue
 constexpr bool
 operator==(MPTIssue const& lhs, MPTIssue const& rhs)
 {
-    return lhs.mpt_ == rhs.mpt_;
+    return lhs.mptID_ == rhs.mptID_;
 }
 
 constexpr bool
 operator!=(MPTIssue const& lhs, MPTIssue const& rhs)
 {
-    return !(lhs.mpt_ == rhs.mpt_);
-}
-
-MPT
-getMPT(uint192 const&);
-
-inline MPT
-badMPT()
-{
-    static MPT mpt{0, AccountID{0}};
-    return mpt;
+    return !(lhs.mptID_ == rhs.mptID_);
 }
 
 inline bool
-isXRP(uint192 const&)
+isXRP(MPTID const&)
 {
     return false;
 }
diff --git a/include/xrpl/protocol/SField.h b/include/xrpl/protocol/SField.h
index 4d977060dba..ae7d38627ec 100644
--- a/include/xrpl/protocol/SField.h
+++ b/include/xrpl/protocol/SField.h
@@ -43,6 +43,7 @@ Some fields have a different meaning for their
 // Forwards
 class STAccount;
 class STEitherAmount;
+class STAmount;
 class STIssue;
 class STBlob;
 template <int>
@@ -56,44 +57,49 @@ class STCurrency;
 #pragma push_macro("XMACRO")
 #undef XMACRO
 
-#define XMACRO(STYPE)                             \
-    /* special types */                           \
-    STYPE(STI_UNKNOWN, -2)                        \
-    STYPE(STI_NOTPRESENT, 0)                      \
-    STYPE(STI_UINT16, 1)                          \
-                                                  \
-    /* types (common) */                          \
-    STYPE(STI_UINT32, 2)                          \
-    STYPE(STI_UINT64, 3)                          \
-    STYPE(STI_UINT128, 4)                         \
-    STYPE(STI_UINT256, 5)                         \
-    STYPE(STI_AMOUNT, 6)                          \
-    STYPE(STI_EITHER_AMOUNT, 6)                   \
-    STYPE(STI_VL, 7)                              \
-    STYPE(STI_ACCOUNT, 8)                         \
-                                                  \
-    /* 9-13 are reserved */                       \
-    STYPE(STI_OBJECT, 14)                         \
-    STYPE(STI_ARRAY, 15)                          \
-                                                  \
-    /* types (uncommon) */                        \
-    STYPE(STI_UINT8, 16)                          \
-    STYPE(STI_UINT160, 17)                        \
-    STYPE(STI_PATHSET, 18)                        \
-    STYPE(STI_VECTOR256, 19)                      \
-    STYPE(STI_UINT96, 20)                         \
-    STYPE(STI_UINT192, 21)                        \
-    STYPE(STI_UINT384, 22)                        \
-    STYPE(STI_UINT512, 23)                        \
-    STYPE(STI_ISSUE, 24)                          \
-    STYPE(STI_XCHAIN_BRIDGE, 25)                  \
-    STYPE(STI_CURRENCY, 26)                       \
-                                                  \
-    /* high-level types */                        \
-    /* cannot be serialized inside other types */ \
-    STYPE(STI_TRANSACTION, 10001)                 \
-    STYPE(STI_LEDGERENTRY, 10002)                 \
-    STYPE(STI_VALIDATION, 10003)                  \
+#define XMACRO(STYPE)                              \
+    /* special types */                            \
+    STYPE(STI_UNKNOWN, -2)                         \
+    STYPE(STI_NOTPRESENT, 0)                       \
+    STYPE(STI_UINT16, 1)                           \
+                                                   \
+    /* types (common) */                           \
+    STYPE(STI_UINT32, 2)                           \
+    STYPE(STI_UINT64, 3)                           \
+    STYPE(STI_UINT128, 4)                          \
+    STYPE(STI_UINT256, 5)                          \
+    /* Need two enumerators with the same value */ \
+    /* so that SF_AMOUNT and SF_EITHER_AMOUNT */   \
+    /* map to the same serialization id. */        \
+    /* This is an artifact of */                   \
+    /* CONSTRUCT_TYPED_SFIELD */                   \
+    STYPE(STI_AMOUNT, 6)                           \
+    STYPE(STI_EITHER_AMOUNT, 6)                    \
+    STYPE(STI_VL, 7)                               \
+    STYPE(STI_ACCOUNT, 8)                          \
+                                                   \
+    /* 9-13 are reserved */                        \
+    STYPE(STI_OBJECT, 14)                          \
+    STYPE(STI_ARRAY, 15)                           \
+                                                   \
+    /* types (uncommon) */                         \
+    STYPE(STI_UINT8, 16)                           \
+    STYPE(STI_UINT160, 17)                         \
+    STYPE(STI_PATHSET, 18)                         \
+    STYPE(STI_VECTOR256, 19)                       \
+    STYPE(STI_UINT96, 20)                          \
+    STYPE(STI_UINT192, 21)                         \
+    STYPE(STI_UINT384, 22)                         \
+    STYPE(STI_UINT512, 23)                         \
+    STYPE(STI_ISSUE, 24)                           \
+    STYPE(STI_XCHAIN_BRIDGE, 25)                   \
+    STYPE(STI_CURRENCY, 26)                        \
+                                                   \
+    /* high-level types */                         \
+    /* cannot be serialized inside other types */  \
+    STYPE(STI_TRANSACTION, 10001)                  \
+    STYPE(STI_LEDGERENTRY, 10002)                  \
+    STYPE(STI_VALIDATION, 10003)                   \
     STYPE(STI_METADATA, 10004)
 
 #pragma push_macro("TO_ENUM")
@@ -302,8 +308,6 @@ class SField
     static std::map<int, SField const*> knownCodeToField;
 };
 
-enum class SFieldMPT { None, Yes, No };
-
 /** A field with a type known at compile time. */
 template <class T>
 struct TypedField : SField
@@ -325,38 +329,49 @@ struct OptionaledField
     }
 };
 
-template <class T>
-inline OptionaledField<T>
-operator~(TypedField<T> const& f)
-{
-    return OptionaledField<T>(f);
-}
-
-// Amount fields
-
-/** A field with a type known at compile time. */
-template <SFieldMPT>
-struct TypedFieldAmount : public TypedField<STEitherAmount>
+/** A field representing a variant with a type known at compile time.
+ * First template parameter is the variant type, the second
+ * template parameter is one of its alternative types. A variant field
+ * enables STObject::operator[]() overload to return the specified
+ * alternative type. For instance, STEitherAmount is a variant of STAmount
+ * and STMPTAmount. Some Amount fields, like SFee, don't support MPT
+ * and are declared as TypedVariantField<STEitherAmount, STAmount>.
+ * Conversely, sfAmount field supports MPT and is declared as
+ * TypedVariantField<STEitherAmount>. Then tx[sfFee] always returns STAmount,
+ * while tx[sfAmount] returns STEitherAmount and the caller has to get
+ * the specific type that STEitherAmount holds.
+ */
+template <class T, class H = T>
+struct TypedVariantField : TypedField<T>
 {
     template <class... Args>
-    explicit TypedFieldAmount(private_access_tag_t pat, Args&&... args);
+    explicit TypedVariantField(
+        SField::private_access_tag_t pat,
+        Args&&... args);
 };
 
-/** Indicate std::optional field semantics. */
-template <SFieldMPT M>
-struct OptionaledFieldAmount : public OptionaledField<STEitherAmount>
+/** Indicate std::optional variant field semantics. */
+template <class T, class H = T>
+struct OptionaledVariantField : OptionaledField<T>
 {
-    explicit OptionaledFieldAmount(TypedFieldAmount<M> const& f_)
-        : OptionaledField<STEitherAmount>(f_)
+    explicit OptionaledVariantField(TypedVariantField<T, H> const& f_)
+        : OptionaledField<T>(f_)
     {
     }
 };
 
-template <SFieldMPT M>
-inline OptionaledFieldAmount<M>
-operator~(TypedFieldAmount<M> const& f)
+template <class T>
+inline OptionaledField<T>
+operator~(TypedField<T> const& f)
+{
+    return OptionaledField<T>(f);
+}
+
+template <class T, class H = T>
+inline OptionaledVariantField<T, H>
+operator~(TypedVariantField<T, H> const& f)
 {
-    return OptionaledFieldAmount<M>(f);
+    return OptionaledVariantField<T, H>(f);
 }
 
 //------------------------------------------------------------------------------
@@ -376,8 +391,8 @@ using SF_UINT384 = TypedField<STBitString<384>>;
 using SF_UINT512 = TypedField<STBitString<512>>;
 
 using SF_ACCOUNT = TypedField<STAccount>;
-using SF_AMOUNT = TypedFieldAmount<SFieldMPT::No>;
-using SF_EITHER_AMOUNT = TypedFieldAmount<SFieldMPT::Yes>;
+using SF_AMOUNT = TypedVariantField<STEitherAmount, STAmount>;
+using SF_EITHER_AMOUNT = TypedVariantField<STEitherAmount>;
 using SF_ISSUE = TypedField<STIssue>;
 using SF_CURRENCY = TypedField<STCurrency>;
 using SF_VL = TypedField<STBlob>;
diff --git a/include/xrpl/protocol/SOTemplate.h b/include/xrpl/protocol/SOTemplate.h
index 56a0d471150..f6c231d1310 100644
--- a/include/xrpl/protocol/SOTemplate.h
+++ b/include/xrpl/protocol/SOTemplate.h
@@ -73,13 +73,15 @@ class SOElement
     {
         init(fieldName);
     }
-    SOElement(TypedFieldAmount<SFieldMPT::No> const& fieldName, SOEStyle style)
+    SOElement(
+        TypedVariantField<STEitherAmount, STAmount> const& fieldName,
+        SOEStyle style)
         : sField_(fieldName), style_(style), supportMpt_(soeMPTNotSupported)
     {
         init(fieldName);
     }
     SOElement(
-        TypedFieldAmount<SFieldMPT::Yes> const& fieldName,
+        TypedVariantField<STEitherAmount> const& fieldName,
         SOEStyle style,
         SOETxMPTAmount supportMpt = soeMPTNotSupported)
         : sField_(fieldName), style_(style), supportMpt_(supportMpt)
diff --git a/include/xrpl/protocol/STAmount.h b/include/xrpl/protocol/STAmount.h
index 63738966b95..6cd5cc7a557 100644
--- a/include/xrpl/protocol/STAmount.h
+++ b/include/xrpl/protocol/STAmount.h
@@ -32,10 +32,6 @@
 
 namespace ripple {
 
-struct int64_tag_t
-{
-};
-
 // Internal form:
 // 1: If amount is zero, then value is zero and offset is -100
 // 2: Otherwise:
@@ -102,8 +98,6 @@ class STAmount final
         bool native,
         bool negative);
 
-    STAmount(std::int64_t mantissa, int64_tag_t);
-
     explicit STAmount(std::uint64_t mantissa = 0, bool negative = false);
 
     STAmount(
diff --git a/include/xrpl/protocol/STEitherAmount.h b/include/xrpl/protocol/STEitherAmount.h
index c2c8172cb9b..d4b5756451f 100644
--- a/include/xrpl/protocol/STEitherAmount.h
+++ b/include/xrpl/protocol/STEitherAmount.h
@@ -29,11 +29,6 @@ template <typename TAmnt>
 concept ValidAmountType =
     std::is_same_v<TAmnt, STAmount> || std::is_same_v<TAmnt, STMPTAmount>;
 
-// Currency or MPT issuance ID
-template <typename T>
-concept ValidAssetType =
-    std::is_same_v<T, Currency> || std::is_same_v<T, uint192>;
-
 template <typename T>
 concept EitherAmountType = std::is_same_v<T, STEitherAmount> ||
     std::is_same_v<T, std::optional<STEitherAmount>>;
@@ -100,7 +95,7 @@ class STEitherAmount : public STBase, public CountedObject<STEitherAmount>
     std::variant<STAmount, STMPTAmount>&
     getValue();
 
-    AccountID const&
+    AccountID
     getIssuer() const;
 
     bool
diff --git a/include/xrpl/protocol/STMPTAmount.h b/include/xrpl/protocol/STMPTAmount.h
index 02ea3837d33..8e467b6453b 100644
--- a/include/xrpl/protocol/STMPTAmount.h
+++ b/include/xrpl/protocol/STMPTAmount.h
@@ -66,13 +66,13 @@ class STMPTAmount final : public MPTAmount
     bool
     isDefault() const;
 
-    AccountID const&
+    AccountID
     getIssuer() const;
 
     MPTIssue const&
     issue() const;
 
-    uint192
+    MPTID const&
     getCurrency() const;
 
     void
diff --git a/include/xrpl/protocol/STObject.h b/include/xrpl/protocol/STObject.h
index 26cdf5a55ea..4d2b061f88a 100644
--- a/include/xrpl/protocol/STObject.h
+++ b/include/xrpl/protocol/STObject.h
@@ -54,15 +54,12 @@ throwFieldNotFound(SField const& field)
 
 class STObject : public STBase, public CountedObject<STObject>
 {
-    template <typename T, SFieldMPT M>
-    using ValueType = std::conditional<M == SFieldMPT::No, STAmount, T>::type;
-
     // Proxy value for a STBase derived class
-    template <class T, SFieldMPT = SFieldMPT::None>
+    template <class T, class H = T>
     class Proxy;
-    template <class T, SFieldMPT = SFieldMPT::None>
+    template <class T, class H = T>
     class ValueProxy;
-    template <class T, SFieldMPT = SFieldMPT::None>
+    template <class T, class H = T>
     class OptionalProxy;
 
     struct Transform
@@ -240,11 +237,10 @@ class STObject : public STBase, public CountedObject<STObject>
     Blob
     getFieldVL(SField const& field) const;
     STEitherAmount const&
-    getFieldEitherAmount(SField const& field) const;
-    STEitherAmount const&
     getFieldAmount(SField const& field) const;
     STAmount const&
-    getFieldAmount(TypedFieldAmount<SFieldMPT::No> const& field) const;
+    getFieldAmount(
+        TypedVariantField<STEitherAmount, STAmount> const& field) const;
     STPathSet const&
     getFieldPathSet(SField const& field) const;
     const STVector256&
@@ -265,11 +261,11 @@ class STObject : public STBase, public CountedObject<STObject>
     typename T::value_type
     operator[](TypedField<T> const& f) const;
 
-    /** Overload for amount fields
+    /** Overload for amount field
      */
-    template <SFieldMPT M>
-    typename ValueType<STEitherAmount, M>::value_type
-    operator[](TypedFieldAmount<M> const& f) const;
+    template <class T, class H = T>
+    H::value_type
+    operator[](TypedVariantField<T, H> const& f) const;
 
     /** Get the value of a field as a std::optional
 
@@ -283,12 +279,11 @@ class STObject : public STBase, public CountedObject<STObject>
     std::optional<std::decay_t<typename T::value_type>>
     operator[](OptionaledField<T> const& of) const;
 
-    /** Overload for amount fields
+    /** Overload for a variant field
      */
-    template <SFieldMPT M>
-    std::optional<
-        std::decay_t<typename ValueType<STEitherAmount, M>::value_type>>
-    operator[](OptionaledFieldAmount<M> const& of) const;
+    template <class T, class H>
+    std::optional<std::decay_t<typename H::value_type>>
+    operator[](OptionaledVariantField<T, H> const& of) const;
 
     /** Get a modifiable field value.
         @param A TypedField built from an SField value representing the desired
@@ -301,11 +296,11 @@ class STObject : public STBase, public CountedObject<STObject>
     ValueProxy<T>
     operator[](TypedField<T> const& f);
 
-    /** Overload for amount fields
+    /** Overload for a variant field
      */
-    template <SFieldMPT M>
-    ValueProxy<STEitherAmount, M>
-    operator[](TypedFieldAmount<M> const& f);
+    template <class T, class H = T>
+    ValueProxy<T, H>
+    operator[](TypedVariantField<T, H> const& f);
 
     /** Return a modifiable field value as std::optional
 
@@ -320,11 +315,11 @@ class STObject : public STBase, public CountedObject<STObject>
     OptionalProxy<T>
     operator[](OptionaledField<T> const& of);
 
-    /** Overload for amount fields
+    /** Overload for a variant field
      */
-    template <SFieldMPT M>
-    OptionalProxy<STEitherAmount, M>
-    operator[](OptionaledFieldAmount<M> const& of);
+    template <class T, class H = T>
+    OptionalProxy<T, H>
+    operator[](OptionaledVariantField<T, H> const& of);
 
     /** Get the value of a field.
         @param A TypedField built from an SField value representing the desired
@@ -337,11 +332,11 @@ class STObject : public STBase, public CountedObject<STObject>
     typename T::value_type
     at(TypedField<T> const& f) const;
 
-    /** Overload for amount fields
+    /** Overload for a variant field
      */
-    template <SFieldMPT M>
-    typename ValueType<STEitherAmount, M>::value_type
-    at(TypedFieldAmount<M> const& f) const;
+    template <class T, class H = T>
+    H::value_type
+    at(TypedVariantField<T, H> const& f) const;
 
     /** Get the value of a field as std::optional
 
@@ -355,12 +350,11 @@ class STObject : public STBase, public CountedObject<STObject>
     std::optional<std::decay_t<typename T::value_type>>
     at(OptionaledField<T> const& of) const;
 
-    /** Overload for amount fields
+    /** Overload for a variant field
      */
-    template <SFieldMPT M>
-    std::optional<
-        std::decay_t<typename ValueType<STEitherAmount, M>::value_type>>
-    at(OptionaledFieldAmount<M> const& of) const;
+    template <class T, class H = T>
+    std::optional<std::decay_t<typename H::value_type>>
+    at(OptionaledVariantField<T, H> const& of) const;
 
     /** Get a modifiable field value.
         @param A TypedField built from an SField value representing the desired
@@ -373,11 +367,11 @@ class STObject : public STBase, public CountedObject<STObject>
     ValueProxy<T>
     at(TypedField<T> const& f);
 
-    /** Overload for amount fields that don't support MPT
+    /** Overload for a variant field
      */
-    template <SFieldMPT M>
-    ValueProxy<STEitherAmount, M>
-    at(TypedFieldAmount<M> const& f);
+    template <class T, class H = T>
+    ValueProxy<T, H>
+    at(TypedVariantField<T, H> const& f);
 
     /** Return a modifiable field value as std::optional
 
@@ -392,11 +386,11 @@ class STObject : public STBase, public CountedObject<STObject>
     OptionalProxy<T>
     at(OptionaledField<T> const& of);
 
-    /** Overload for amount fields that don't support MPT
+    /** Overload for a variant field
      */
-    template <SFieldMPT M>
-    OptionalProxy<STEitherAmount, M>
-    at(OptionaledFieldAmount<M> const& of);
+    template <class T, class H = T>
+    OptionalProxy<T, H>
+    at(OptionaledVariantField<T, H> const& of);
 
     /** Set a field.
         if the field already exists, it is replaced.
@@ -541,11 +535,11 @@ class STObject : public STBase, public CountedObject<STObject>
 
 //------------------------------------------------------------------------------
 
-template <class T, SFieldMPT M>
+template <class T, class H>
 class STObject::Proxy
 {
 protected:
-    using value_type = ValueType<T, M>::value_type;
+    using value_type = H::value_type;
 
     STObject* st_;
     SOEStyle style_;
@@ -566,11 +560,11 @@ class STObject::Proxy
     assign(U&& u);
 };
 
-template <class T, SFieldMPT M>
-class STObject::ValueProxy : private Proxy<T, M>
+template <class T, class H>
+class STObject::ValueProxy : private Proxy<T, H>
 {
 private:
-    using value_type = ValueType<T, M>::value_type;
+    using value_type = H::value_type;
 
 public:
     ValueProxy(ValueProxy const&) = default;
@@ -589,11 +583,11 @@ class STObject::ValueProxy : private Proxy<T, M>
     ValueProxy(STObject* st, TypedField<T> const* f);
 };
 
-template <class T, SFieldMPT M>
-class STObject::OptionalProxy : private Proxy<T, M>
+template <class T, class H>
+class STObject::OptionalProxy : private Proxy<T, H>
 {
 private:
-    using value_type = ValueType<T, M>::value_type;
+    using value_type = H::value_type;
 
     using optional_type = std::optional<typename std::decay<value_type>::type>;
 
@@ -725,8 +719,8 @@ class STObject::FieldErr : public std::runtime_error
     using std::runtime_error::runtime_error;
 };
 
-template <class T, SFieldMPT M>
-STObject::Proxy<T, M>::Proxy(STObject* st, TypedField<T> const* f)
+template <class T, class H>
+STObject::Proxy<T, H>::Proxy(STObject* st, TypedField<T> const* f)
     : st_(st), f_(f)
 {
     if (st_->mType)
@@ -743,32 +737,18 @@ STObject::Proxy<T, M>::Proxy(STObject* st, TypedField<T> const* f)
     }
 }
 
-template <class T, SFieldMPT M>
+template <class T, class H>
 auto
-STObject::Proxy<T, M>::value() const -> value_type
+STObject::Proxy<T, H>::value() const -> value_type
 {
     auto const t = find();
     if (t)
-        return t->value();
-    if (style_ == soeINVALID)
     {
-        Throw<STObject::FieldErr>("Value requested from invalid STObject.");
-    }
-    if (style_ != soeDEFAULT)
-    {
-        Throw<STObject::FieldErr>(
-            "Missing field '" + this->f_->getName() + "'");
+        if constexpr (std::is_same_v<T, H>)
+            return t->value();
+        else
+            return get<H>(t->value());
     }
-    return value_type{};
-}
-
-template <>
-inline auto
-STObject::Proxy<STEitherAmount, SFieldMPT::No>::value() const -> STAmount
-{
-    auto const t = find();
-    if (t)
-        return get<STAmount>(t->value());
     if (style_ == soeINVALID)
     {
         Throw<STObject::FieldErr>("Value requested from invalid STObject.");
@@ -778,20 +758,20 @@ STObject::Proxy<STEitherAmount, SFieldMPT::No>::value() const -> STAmount
         Throw<STObject::FieldErr>(
             "Missing field '" + this->f_->getName() + "'");
     }
-    return STAmount{};
+    return value_type{};
 }
 
-template <class T, SFieldMPT M>
+template <class T, class H>
 inline T const*
-STObject::Proxy<T, M>::find() const
+STObject::Proxy<T, H>::find() const
 {
     return dynamic_cast<T const*>(st_->peekAtPField(*f_));
 }
 
-template <class T, SFieldMPT M>
+template <class T, class H>
 template <class U>
 void
-STObject::Proxy<T, M>::assign(U&& u)
+STObject::Proxy<T, H>::assign(U&& u)
 {
     if (style_ == soeDEFAULT && u == value_type{})
     {
@@ -809,68 +789,68 @@ STObject::Proxy<T, M>::assign(U&& u)
 
 //------------------------------------------------------------------------------
 
-template <class T, SFieldMPT M>
+template <class T, class H>
 template <class U>
-std::enable_if_t<std::is_assignable_v<T, U>, STObject::ValueProxy<T, M>&>
-STObject::ValueProxy<T, M>::operator=(U&& u)
+std::enable_if_t<std::is_assignable_v<T, U>, STObject::ValueProxy<T, H>&>
+STObject::ValueProxy<T, H>::operator=(U&& u)
 {
     this->assign(std::forward<U>(u));
     return *this;
 }
 
-template <class T, SFieldMPT M>
-STObject::ValueProxy<T, M>::operator value_type() const
+template <class T, class H>
+STObject::ValueProxy<T, H>::operator value_type() const
 {
     return this->value();
 }
 
-template <class T, SFieldMPT M>
-STObject::ValueProxy<T, M>::ValueProxy(STObject* st, TypedField<T> const* f)
-    : Proxy<T, M>(st, f)
+template <class T, class H>
+STObject::ValueProxy<T, H>::ValueProxy(STObject* st, TypedField<T> const* f)
+    : Proxy<T, H>(st, f)
 {
 }
 
 //------------------------------------------------------------------------------
 
-template <class T, SFieldMPT M>
-STObject::OptionalProxy<T, M>::operator bool() const noexcept
+template <class T, class H>
+STObject::OptionalProxy<T, H>::operator bool() const noexcept
 {
     return engaged();
 }
 
-template <class T, SFieldMPT M>
+template <class T, class H>
 auto
-STObject::OptionalProxy<T, M>::operator*() const -> value_type
+STObject::OptionalProxy<T, H>::operator*() const -> value_type
 {
     return this->value();
 }
 
-template <class T, SFieldMPT M>
-STObject::OptionalProxy<T, M>::operator typename STObject::OptionalProxy<T, M>::
+template <class T, class H>
+STObject::OptionalProxy<T, H>::operator typename STObject::OptionalProxy<T, H>::
     optional_type() const
 {
     return optional_value();
 }
 
-template <class T, SFieldMPT M>
-typename STObject::OptionalProxy<T, M>::optional_type
-STObject::OptionalProxy<T, M>::operator~() const
+template <class T, class H>
+typename STObject::OptionalProxy<T, H>::optional_type
+STObject::OptionalProxy<T, H>::operator~() const
 {
     return optional_value();
 }
 
-template <class T, SFieldMPT M>
+template <class T, class H>
 auto
-STObject::OptionalProxy<T, M>::operator=(std::nullopt_t const&)
+STObject::OptionalProxy<T, H>::operator=(std::nullopt_t const&)
     -> OptionalProxy&
 {
     disengage();
     return *this;
 }
 
-template <class T, SFieldMPT M>
+template <class T, class H>
 auto
-STObject::OptionalProxy<T, M>::operator=(optional_type&& v) -> OptionalProxy&
+STObject::OptionalProxy<T, H>::operator=(optional_type&& v) -> OptionalProxy&
 {
     if (v)
         this->assign(std::move(*v));
@@ -879,9 +859,9 @@ STObject::OptionalProxy<T, M>::operator=(optional_type&& v) -> OptionalProxy&
     return *this;
 }
 
-template <class T, SFieldMPT M>
+template <class T, class H>
 auto
-STObject::OptionalProxy<T, M>::operator=(optional_type const& v)
+STObject::OptionalProxy<T, H>::operator=(optional_type const& v)
     -> OptionalProxy&
 {
     if (v)
@@ -891,33 +871,33 @@ STObject::OptionalProxy<T, M>::operator=(optional_type const& v)
     return *this;
 }
 
-template <class T, SFieldMPT M>
+template <class T, class H>
 template <class U>
-std::enable_if_t<std::is_assignable_v<T, U>, STObject::OptionalProxy<T, M>&>
-STObject::OptionalProxy<T, M>::operator=(U&& u)
+std::enable_if_t<std::is_assignable_v<T, U>, STObject::OptionalProxy<T, H>&>
+STObject::OptionalProxy<T, H>::operator=(U&& u)
 {
     this->assign(std::forward<U>(u));
     return *this;
 }
 
-template <class T, SFieldMPT M>
-STObject::OptionalProxy<T, M>::OptionalProxy(
+template <class T, class H>
+STObject::OptionalProxy<T, H>::OptionalProxy(
     STObject* st,
     TypedField<T> const* f)
-    : Proxy<T, M>(st, f)
+    : Proxy<T, H>(st, f)
 {
 }
 
-template <class T, SFieldMPT M>
+template <class T, class H>
 bool
-STObject::OptionalProxy<T, M>::engaged() const noexcept
+STObject::OptionalProxy<T, H>::engaged() const noexcept
 {
     return this->style_ == soeDEFAULT || this->find() != nullptr;
 }
 
-template <class T, SFieldMPT M>
+template <class T, class H>
 void
-STObject::OptionalProxy<T, M>::disengage()
+STObject::OptionalProxy<T, H>::disengage()
 {
     if (this->style_ == soeREQUIRED || this->style_ == soeDEFAULT)
         Throw<STObject::FieldErr>(
@@ -928,18 +908,18 @@ STObject::OptionalProxy<T, M>::disengage()
         this->st_->makeFieldAbsent(*this->f_);
 }
 
-template <class T, SFieldMPT M>
+template <class T, class H>
 auto
-STObject::OptionalProxy<T, M>::optional_value() const -> optional_type
+STObject::OptionalProxy<T, H>::optional_value() const -> optional_type
 {
     if (!engaged())
         return std::nullopt;
     return this->value();
 }
 
-template <class T, SFieldMPT M>
-typename STObject::OptionalProxy<T, M>::value_type
-STObject::OptionalProxy<T, M>::value_or(value_type val) const
+template <class T, class H>
+typename STObject::OptionalProxy<T, H>::value_type
+STObject::OptionalProxy<T, H>::value_or(value_type val) const
 {
     return engaged() ? this->value() : val;
 }
@@ -1051,9 +1031,9 @@ STObject::operator[](TypedField<T> const& f) const
     return at(f);
 }
 
-template <SFieldMPT M>
-typename STObject::ValueType<STEitherAmount, M>::value_type
-STObject::operator[](TypedFieldAmount<M> const& f) const
+template <class T, class H>
+inline H::value_type
+STObject::operator[](TypedVariantField<T, H> const& f) const
 {
     return at(f);
 }
@@ -1065,10 +1045,9 @@ STObject::operator[](OptionaledField<T> const& of) const
     return at(of);
 }
 
-template <SFieldMPT M>
-std::optional<
-    std::decay_t<typename STObject::ValueType<STEitherAmount, M>::value_type>>
-STObject::operator[](OptionaledFieldAmount<M> const& of) const
+template <class T, class H>
+inline std::optional<std::decay_t<typename H::value_type>>
+STObject::operator[](OptionaledVariantField<T, H> const& of) const
 {
     return at(of);
 }
@@ -1080,10 +1059,9 @@ STObject::operator[](TypedField<T> const& f) -> ValueProxy<T>
     return at(f);
 }
 
-template <SFieldMPT M>
+template <class T, class H>
 inline auto
-STObject::operator[](TypedFieldAmount<M> const& f)
-    -> ValueProxy<STEitherAmount, M>
+STObject::operator[](TypedVariantField<T, H> const& f) -> ValueProxy<T, H>
 {
     return at(f);
 }
@@ -1095,10 +1073,10 @@ STObject::operator[](OptionaledField<T> const& of) -> OptionalProxy<T>
     return at(of);
 }
 
-template <SFieldMPT M>
+template <class T, class H>
 inline auto
-STObject::operator[](OptionaledFieldAmount<M> const& of)
-    -> OptionalProxy<STEitherAmount, M>
+STObject::operator[](OptionaledVariantField<T, H> const& of)
+    -> OptionalProxy<T, H>
 {
     return at(of);
 }
@@ -1137,14 +1115,14 @@ STObject::at(TypedField<T> const& f) const
     return atImpl(f);
 }
 
-template <SFieldMPT M>
-typename STObject::ValueType<STEitherAmount, M>::value_type
-STObject::at(TypedFieldAmount<M> const& f) const
+template <class T, class H>
+inline H::value_type
+STObject::at(TypedVariantField<T, H> const& f) const
 {
-    if constexpr (M == SFieldMPT::Yes)
+    if constexpr (std::is_same_v<T, H>)
         return atImpl(f);
     else
-        return get<STAmount>(atImpl(f));
+        return get<H>(atImpl(f));
 }
 
 template <class T>
@@ -1174,15 +1152,14 @@ STObject::at(OptionaledField<T> const& of) const
     return atImpl(of);
 }
 
-template <SFieldMPT M>
-std::optional<
-    std::decay_t<typename STObject::ValueType<STEitherAmount, M>::value_type>>
-STObject::at(OptionaledFieldAmount<M> const& of) const
+template <class T, class H>
+inline std::optional<std::decay_t<typename H::value_type>>
+STObject::at(OptionaledVariantField<T, H> const& of) const
 {
-    if constexpr (M == SFieldMPT::Yes)
+    if constexpr (std::is_same_v<T, H>)
         return atImpl(of);
     else
-        return get<STAmount>(atImpl(of));
+        return get<H>(atImpl(of));
 }
 
 template <class T>
@@ -1192,11 +1169,11 @@ STObject::at(TypedField<T> const& f) -> ValueProxy<T>
     return ValueProxy<T>(this, &f);
 }
 
-template <SFieldMPT M>
+template <class T, class H>
 inline auto
-STObject::at(TypedFieldAmount<M> const& f) -> ValueProxy<STEitherAmount, M>
+STObject::at(TypedVariantField<T, H> const& f) -> ValueProxy<T, H>
 {
-    return ValueProxy<STEitherAmount, M>(this, &f);
+    return ValueProxy<T, H>(this, &f);
 }
 
 template <class T>
@@ -1206,12 +1183,11 @@ STObject::at(OptionaledField<T> const& of) -> OptionalProxy<T>
     return OptionalProxy<T>(this, of.f);
 }
 
-template <SFieldMPT M>
+template <class T, class H>
 inline auto
-STObject::at(OptionaledFieldAmount<M> const& of)
-    -> OptionalProxy<STEitherAmount, M>
+STObject::at(OptionaledVariantField<T, H> const& of) -> OptionalProxy<T, H>
 {
-    return OptionalProxy<STEitherAmount, M>(this, of.f);
+    return OptionalProxy<T, H>(this, of.f);
 }
 
 template <class Tag>
diff --git a/include/xrpl/protocol/UintTypes.h b/include/xrpl/protocol/UintTypes.h
index 8ccca8743ae..cf34366262f 100644
--- a/include/xrpl/protocol/UintTypes.h
+++ b/include/xrpl/protocol/UintTypes.h
@@ -58,8 +58,12 @@ using Currency = base_uint<160, detail::CurrencyTag>;
 /** NodeID is a 160-bit hash representing one node. */
 using NodeID = base_uint<160, detail::NodeIDTag>;
 
-/** MPT is a 192-bit hash representing MPTID. */
-using MPT = std::pair<std::uint32_t, AccountID>;
+/** MPT is a 192-bit hash representing MPTID.
+ * Currently MPTID is the only 192-bit field.
+ * If other 192-bit fields with different semantics
+ * are added then MPTID must change to a unique tag.
+ */
+using MPTID = base_uint<192>;
 
 /** XRP currency. */
 Currency const&
@@ -70,7 +74,7 @@ Currency const&
 noCurrency();
 
 /** A placeholder for empty MPTID. */
-MPT const&
+MPTID const&
 noMPT();
 
 /** We deliberately disallow the currency that looks like "XRP" because too
diff --git a/src/libxrpl/protocol/Indexes.cpp b/src/libxrpl/protocol/Indexes.cpp
index b402111972c..8014d8d4dcd 100644
--- a/src/libxrpl/protocol/Indexes.cpp
+++ b/src/libxrpl/protocol/Indexes.cpp
@@ -137,10 +137,10 @@ getTicketIndex(AccountID const& account, SeqProxy ticketSeq)
     return getTicketIndex(account, ticketSeq.value());
 }
 
-uint192
+MPTID
 getMptID(AccountID const& account, std::uint32_t sequence)
 {
-    uint192 u;
+    MPTID u;
     sequence = boost::endian::native_to_big(sequence);
     memcpy(u.data(), &sequence, sizeof(sequence));
     memcpy(u.data() + sizeof(sequence), account.data(), sizeof(account));
@@ -470,31 +470,18 @@ mptIssuance(AccountID const& issuer, std::uint32_t seq) noexcept
 }
 
 Keylet
-mptIssuance(ripple::MPT const& mpt) noexcept
-{
-    return mptIssuance(mpt.second, mpt.first);
-}
-
-Keylet
-mptIssuance(uint192 const& mpt) noexcept
+mptIssuance(MPTID const& id) noexcept
 {
     return {
-        ltMPTOKEN_ISSUANCE, indexHash(LedgerNameSpace::MPTOKEN_ISSUANCE, mpt)};
+        ltMPTOKEN_ISSUANCE, indexHash(LedgerNameSpace::MPTOKEN_ISSUANCE, id)};
 }
 
 Keylet
-mptoken(uint192 const& issuanceID, AccountID const& holder) noexcept
+mptoken(MPTID const& issuanceID, AccountID const& holder) noexcept
 {
     return mptoken(mptIssuance(issuanceID).key, holder);
 }
 
-Keylet
-mptoken(MPT const& mptID, AccountID const& holder) noexcept
-{
-    return mptoken(
-        mptIssuance(getMptID(mptID.second, mptID.first)).key, holder);
-}
-
 Keylet
 mptoken(uint256 const& issuanceKey, AccountID const& holder) noexcept
 {
diff --git a/src/libxrpl/protocol/MPTIssue.cpp b/src/libxrpl/protocol/MPTIssue.cpp
index 4737f46a938..629f00be2d2 100644
--- a/src/libxrpl/protocol/MPTIssue.cpp
+++ b/src/libxrpl/protocol/MPTIssue.cpp
@@ -23,49 +23,27 @@
 
 namespace ripple {
 
-MPTIssue::MPTIssue(MPT const& mpt) : mpt_(mpt)
+MPTIssue::MPTIssue(MPTID const& id) : mptID_(id)
 {
 }
 
-MPTIssue::MPTIssue(uint192 const& id)
-{
-    mpt_ = getMPT(id);
-}
-
-AccountID const&
+AccountID
 MPTIssue::getIssuer() const
 {
-    return mpt_.second;
-}
-
-MPT const&
-MPTIssue::mpt() const
-{
-    return mpt_;
-}
+    AccountID account;
 
-MPT&
-MPTIssue::mpt()
-{
-    return mpt_;
+    // copy from id skipping the sequence
+    memcpy(
+        account.data(),
+        mptID_.data() + sizeof(std::uint32_t),
+        sizeof(AccountID));
+    return account;
 }
 
-uint192
+MPTID const&
 MPTIssue::getMptID() const
 {
-    return ripple::getMptID(mpt_.second, mpt_.first);
-}
-
-MPT
-getMPT(uint192 const& id)
-{
-    std::uint32_t sequence;
-    AccountID account;
-
-    memcpy(&sequence, id.data(), sizeof(sequence));
-    sequence = boost::endian::big_to_native(sequence);
-    memcpy(account.data(), id.data() + sizeof(sequence), sizeof(AccountID));
-    return std::make_pair(sequence, account);
+    return mptID_;
 }
 
 Json::Value
diff --git a/src/libxrpl/protocol/SField.cpp b/src/libxrpl/protocol/SField.cpp
index 38c2148c980..19b55607435 100644
--- a/src/libxrpl/protocol/SField.cpp
+++ b/src/libxrpl/protocol/SField.cpp
@@ -45,10 +45,12 @@ TypedField<T>::TypedField(private_access_tag_t pat, Args&&... args)
 {
 }
 
-template <SFieldMPT M>
+template <class T, class H>
 template <class... Args>
-TypedFieldAmount<M>::TypedFieldAmount(private_access_tag_t pat, Args&&... args)
-    : TypedField<STEitherAmount>(pat, std::forward<Args>(args)...)
+TypedVariantField<T, H>::TypedVariantField(
+    SField::private_access_tag_t pat,
+    Args&&... args)
+    : TypedField<T>(pat, std::forward<Args>(args)...)
 {
 }
 
diff --git a/src/libxrpl/protocol/STAmount.cpp b/src/libxrpl/protocol/STAmount.cpp
index c2eee78d462..99eb4b829c5 100644
--- a/src/libxrpl/protocol/STAmount.cpp
+++ b/src/libxrpl/protocol/STAmount.cpp
@@ -187,12 +187,6 @@ STAmount::STAmount(
     canonicalize();
 }
 
-STAmount::STAmount(std::int64_t mantissa, int64_tag_t)
-    : mOffset(0), mIsNative(true)
-{
-    set(mantissa);
-}
-
 //------------------------------------------------------------------------------
 
 STAmount::STAmount(std::uint64_t mantissa, bool negative)
@@ -351,7 +345,12 @@ operator+(STAmount const& v1, STAmount const& v2)
     }
 
     if (v1.native())
-        return {getSNValue(v1) + getSNValue(v2), int64_tag_t{}};
+    {
+        auto const res = getSNValue(v1) + getSNValue(v2);
+        auto const negative = res < 0;
+        return STAmount{
+            static_cast<std::uint64_t>(negative ? -res : res), negative};
+    }
 
     if (getSTNumberSwitchover())
     {
diff --git a/src/libxrpl/protocol/STEitherAmount.cpp b/src/libxrpl/protocol/STEitherAmount.cpp
index 74eebcff487..2fc4d3f00de 100644
--- a/src/libxrpl/protocol/STEitherAmount.cpp
+++ b/src/libxrpl/protocol/STEitherAmount.cpp
@@ -183,7 +183,7 @@ STEitherAmount::getValue()
     return amount_;
 }
 
-AccountID const&
+AccountID
 STEitherAmount::getIssuer() const
 {
     if (isIssue())
@@ -297,7 +297,7 @@ amountFromJson(SField const& name, Json::Value const& v)
         if (isMPT)
         {
             // sequence (32 bits) + account (160 bits)
-            uint192 u;
+            MPTID u;
             if (!u.parseHex(currencyOrMPTID.asString()))
                 Throw<std::runtime_error>("invalid MPTokenIssuanceID");
             issue = u;
diff --git a/src/libxrpl/protocol/STMPTAmount.cpp b/src/libxrpl/protocol/STMPTAmount.cpp
index 554e3d7c713..c4e40e3ce36 100644
--- a/src/libxrpl/protocol/STMPTAmount.cpp
+++ b/src/libxrpl/protocol/STMPTAmount.cpp
@@ -111,16 +111,16 @@ STMPTAmount::add(Serializer& s) const
 bool
 STMPTAmount::isDefault() const
 {
-    return value_ == 0 && issue_ == badMPT();
+    return value_ == 0 && issue_ == noMPT();
 }
 
-AccountID const&
+AccountID
 STMPTAmount::getIssuer() const
 {
     return issue_.getIssuer();
 }
 
-uint192
+MPTID const&
 STMPTAmount::getCurrency() const
 {
     return issue_.getMptID();
diff --git a/src/libxrpl/protocol/STObject.cpp b/src/libxrpl/protocol/STObject.cpp
index ec23b16793d..ccd34d16d33 100644
--- a/src/libxrpl/protocol/STObject.cpp
+++ b/src/libxrpl/protocol/STObject.cpp
@@ -630,13 +630,6 @@ STObject::getFieldVL(SField const& field) const
     return Blob(b.data(), b.data() + b.size());
 }
 
-STEitherAmount const&
-STObject::getFieldEitherAmount(SField const& field) const
-{
-    static STEitherAmount const empty{};
-    return getFieldByConstRef<STEitherAmount>(field, empty);
-}
-
 STEitherAmount const&
 STObject::getFieldAmount(SField const& field) const
 {
@@ -645,7 +638,8 @@ STObject::getFieldAmount(SField const& field) const
 }
 
 STAmount const&
-STObject::getFieldAmount(TypedFieldAmount<SFieldMPT::No> const& field) const
+STObject::getFieldAmount(
+    TypedVariantField<STEitherAmount, STAmount> const& field) const
 {
     static STEitherAmount const empty{};
     return get<STAmount>(getFieldByConstRef<STEitherAmount>(field, empty));
diff --git a/src/libxrpl/protocol/UintTypes.cpp b/src/libxrpl/protocol/UintTypes.cpp
index 9487e5dcbf7..e50e52c1c30 100644
--- a/src/libxrpl/protocol/UintTypes.cpp
+++ b/src/libxrpl/protocol/UintTypes.cpp
@@ -18,6 +18,7 @@
 //==============================================================================
 
 #include <xrpl/beast/utility/Zero.h>
+#include <xrpl/protocol/Indexes.h>
 #include <xrpl/protocol/Serializer.h>
 #include <xrpl/protocol/SystemParameters.h>
 #include <xrpl/protocol/UintTypes.h>
@@ -125,11 +126,11 @@ noCurrency()
     return currency;
 }
 
-MPT const&
+MPTID const&
 noMPT()
 {
-    static MPT const mpt{0, noAccount()};
-    return mpt;
+    static MPTID const id = getMptID(noAccount(), 0);
+    return id;
 }
 
 Currency const&
diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp
index e23352cafa0..757e9e9db60 100644
--- a/src/test/app/MPToken_test.cpp
+++ b/src/test/app/MPToken_test.cpp
@@ -824,8 +824,7 @@ class MPToken_test : public beast::unit_test::suite
         {
             Env env{*this, features};
             env.fund(XRP(1'000), alice, bob);
-            STMPTAmount mpt{
-                MPTIssue{std::make_pair(1, alice.id())}, UINT64_C(100)};
+            STMPTAmount mpt{MPTIssue{getMptID(alice.id(), 1)}, UINT64_C(100)};
             Json::Value jv;
             jv[jss::secret] = alice.name();
             jv[jss::tx_json] = pay(alice, bob, mpt);
@@ -904,8 +903,7 @@ class MPToken_test : public beast::unit_test::suite
 
             env.fund(XRP(1'000), alice);
             env.fund(XRP(1'000), bob);
-            STMPTAmount mpt{
-                MPTIssue{std::make_pair(1, alice.id())}, UINT64_C(100)};
+            STMPTAmount mpt{MPTIssue{getMptID(alice.id(), 1)}, UINT64_C(100)};
 
             env(pay(alice, bob, mpt), ter(temDISABLED));
         }
@@ -919,8 +917,7 @@ class MPToken_test : public beast::unit_test::suite
 
             env.fund(XRP(1'000), alice);
             env.fund(XRP(1'000), carol);
-            STMPTAmount mpt{
-                MPTIssue{std::make_pair(1, alice.id())}, UINT64_C(100)};
+            STMPTAmount mpt{MPTIssue{getMptID(alice.id(), 1)}, UINT64_C(100)};
 
             Json::Value jv;
             jv[jss::secret] = alice.name();
@@ -1007,7 +1004,7 @@ class MPToken_test : public beast::unit_test::suite
         Account const alice("alice");
         auto const USD = alice["USD"];
         Account const carol("carol");
-        MPTIssue issue(std::make_pair(1, alice.id()));
+        MPTIssue issue(getMptID(alice.id(), 1));
         STMPTAmount mpt{issue, UINT64_C(100)};
         auto const jvb = bridge(alice, USD, alice, USD);
         for (auto const& feature : {features, features - featureMPTokensV1})
@@ -1331,7 +1328,7 @@ class MPToken_test : public beast::unit_test::suite
 
             auto const USD = alice["USD"];
             auto const mpt = ripple::test::jtx::MPT(
-                alice.name(), std::make_pair(env.seq(alice), alice.id()));
+                alice.name(), getMptID(alice.id(), env.seq(alice)));
 
             env(claw(alice, bob["USD"](5), bob), ter(temMALFORMED));
             env.close();
@@ -1354,7 +1351,7 @@ class MPToken_test : public beast::unit_test::suite
 
             auto const USD = alice["USD"];
             auto const mpt = ripple::test::jtx::MPT(
-                alice.name(), std::make_pair(env.seq(alice), alice.id()));
+                alice.name(), getMptID(alice.id(), env.seq(alice)));
 
             // clawing back IOU from a MPT holder fails
             env(claw(alice, bob["USD"](5), bob), ter(temMALFORMED));
@@ -1413,7 +1410,7 @@ class MPToken_test : public beast::unit_test::suite
             MPTTester mptAlice(env, alice, {.holders = {&bob}});
 
             auto const fakeMpt = ripple::test::jtx::MPT(
-                alice.name(), std::make_pair(env.seq(alice), alice.id()));
+                alice.name(), getMptID(alice.id(), env.seq(alice)));
 
             // issuer tries to clawback MPT where issuance doesn't exist
             env(claw(alice, fakeMpt(5), bob), ter(tecOBJECT_NOT_FOUND));
@@ -1452,7 +1449,7 @@ class MPToken_test : public beast::unit_test::suite
             env.close();
 
             auto const mpt = ripple::test::jtx::MPT(
-                alice.name(), std::make_pair(env.seq(alice), alice.id()));
+                alice.name(), getMptID(alice.id(), env.seq(alice)));
 
             Json::Value jv = claw(alice, mpt(1), bob);
             jv[jss::Amount][jss::value] = to_string(maxMPTokenAmount + 1);
diff --git a/src/test/jtx/amount.h b/src/test/jtx/amount.h
index 70a0bf51f34..fb5cef102ef 100644
--- a/src/test/jtx/amount.h
+++ b/src/test/jtx/amount.h
@@ -377,14 +377,14 @@ class MPT
 {
 public:
     std::string name;
-    ripple::MPT mptID;
+    ripple::MPTID mptID;
 
-    MPT(std::string const& n, ripple::MPT const& mptID_)
+    MPT(std::string const& n, ripple::MPTID const& mptID_)
         : name(n), mptID(mptID_)
     {
     }
 
-    ripple::MPT const&
+    ripple::MPTID const&
     mpt() const
     {
         return mptID;
@@ -395,7 +395,7 @@ class MPT
         This allows passing an MPT
         value where an Issue is expected.
     */
-    operator ripple::MPT() const
+    operator ripple::MPTID() const
     {
         return mpt();
     }
diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp
index 909154fb720..88f206796bf 100644
--- a/src/test/jtx/impl/mpt.cpp
+++ b/src/test/jtx/impl/mpt.cpp
@@ -94,8 +94,7 @@ MPTTester::create(const MPTCreate& arg)
 {
     if (issuanceKey_)
         Throw<std::runtime_error>("MPT can't be reused");
-    mpt_ = std::make_pair(env_.seq(issuer_), issuer_.id());
-    id_ = getMptID(issuer_.id(), mpt_->first);
+    id_ = getMptID(issuer_.id(), env_.seq(issuer_));
     issuanceKey_ = keylet::mptIssuance(*id_).key;
     Json::Value jv;
     jv[sfAccount.jsonName] = issuer_.human();
@@ -119,7 +118,6 @@ MPTTester::create(const MPTCreate& arg)
 
         id_.reset();
         issuanceKey_.reset();
-        mpt_.reset();
     }
     else if (arg.flags)
         env_.require(mptflags(*this, *arg.flags));
@@ -306,7 +304,7 @@ MPTTester::pay(
     std::int64_t amount,
     std::optional<TER> err)
 {
-    assert(mpt_);
+    assert(id_);
     auto const srcAmt = getAmount(src);
     auto const destAmt = getAmount(dest);
     auto const outstnAmt = getAmount(issuer_);
@@ -331,7 +329,7 @@ MPTTester::pay(
     else
     {
         auto const actual = static_cast<std::int64_t>(
-            amount * Number{transferRate(*env_.current(), *mpt_).value, -9});
+            amount * Number{transferRate(*env_.current(), *id_).value, -9});
         // Sender pays the transfer fee if any
         env_.require(mptpay(*this, src, srcAmt - actual));
         env_.require(mptpay(*this, dest, destAmt + amount));
@@ -347,7 +345,7 @@ MPTTester::claw(
     std::int64_t amount,
     std::optional<TER> err)
 {
-    assert(mpt_);
+    assert(id_);
     auto const issuerAmt = getAmount(issuer);
     auto const holderAmt = getAmount(holder);
     if (err)
@@ -368,8 +366,8 @@ MPTTester::claw(
 STMPTAmount
 MPTTester::mpt(std::int64_t amount) const
 {
-    assert(mpt_);
-    return ripple::test::jtx::MPT(issuer_.name(), *mpt_)(amount);
+    assert(id_);
+    return ripple::test::jtx::MPT(issuer_.name(), *id_)(amount);
 }
 
 std::int64_t
diff --git a/src/test/jtx/mpt.h b/src/test/jtx/mpt.h
index e3ebb69d658..e96b73f039a 100644
--- a/src/test/jtx/mpt.h
+++ b/src/test/jtx/mpt.h
@@ -111,7 +111,7 @@ struct MPTCreate
 struct MPTDestroy
 {
     AccountP issuer = nullptr;
-    std::optional<uint192> id = std::nullopt;
+    std::optional<MPTID> id = std::nullopt;
     std::optional<std::uint32_t> ownerCount = std::nullopt;
     std::optional<std::uint32_t> holderCount = std::nullopt;
     std::optional<std::uint32_t> flags = std::nullopt;
@@ -122,7 +122,7 @@ struct MPTAuthorize
 {
     AccountP account = nullptr;
     AccountP holder = nullptr;
-    std::optional<uint192> id = std::nullopt;
+    std::optional<MPTID> id = std::nullopt;
     std::optional<std::uint32_t> ownerCount = std::nullopt;
     std::optional<std::uint32_t> holderCount = std::nullopt;
     std::optional<std::uint32_t> flags = std::nullopt;
@@ -133,7 +133,7 @@ struct MPTSet
 {
     AccountP account = nullptr;
     AccountP holder = nullptr;
-    std::optional<uint192> id = std::nullopt;
+    std::optional<MPTID> id = std::nullopt;
     std::optional<std::uint32_t> ownerCount = std::nullopt;
     std::optional<std::uint32_t> holderCount = std::nullopt;
     std::optional<std::uint32_t> flags = std::nullopt;
@@ -145,9 +145,8 @@ class MPTTester
     Env& env_;
     Account const& issuer_;
     std::unordered_map<std::string, AccountP> const holders_;
-    std::optional<uint192> id_;
+    std::optional<MPTID> id_;
     std::optional<uint256> issuanceKey_;
-    std::optional<ripple::MPT> mpt_;
     bool close_;
 
 public:
@@ -206,7 +205,7 @@ class MPTTester
         return *issuanceKey_;
     }
 
-    uint192 const&
+    MPTID const&
     issuanceID() const
     {
         assert(id_);
diff --git a/src/xrpld/app/tx/detail/Clawback.cpp b/src/xrpld/app/tx/detail/Clawback.cpp
index fa8f2a3843f..a6ca307bb3d 100644
--- a/src/xrpld/app/tx/detail/Clawback.cpp
+++ b/src/xrpld/app/tx/detail/Clawback.cpp
@@ -172,7 +172,7 @@ preclaimHelper<STMPTAmount>(PreclaimContext const& ctx)
     if (sleHolder->isFieldPresent(sfAMMID))
         return tecAMM_ACCOUNT;
 
-    auto const issuanceKey = keylet::mptIssuance(clawAmount.issue().mpt());
+    auto const issuanceKey = keylet::mptIssuance(clawAmount.issue().getMptID());
     auto const sleIssuance = ctx.view.read(issuanceKey);
     if (!sleIssuance)
         return tecOBJECT_NOT_FOUND;
diff --git a/src/xrpld/app/tx/detail/Payment.cpp b/src/xrpld/app/tx/detail/Payment.cpp
index 6cc88a8107a..98ef572b548 100644
--- a/src/xrpld/app/tx/detail/Payment.cpp
+++ b/src/xrpld/app/tx/detail/Payment.cpp
@@ -268,7 +268,7 @@ preflightHelper<STMPTAmount>(PreflightContext const& ctx)
                         << "bad dst amount: " << saDstAmount.getFullText();
         return temBAD_AMOUNT;
     }
-    if (badMPT() == uDstCurrency)
+    if (noMPT() == uDstCurrency)
     {
         JLOG(j.trace()) << "Malformed transaction: "
                         << "Bad asset.";
diff --git a/src/xrpld/ledger/View.h b/src/xrpld/ledger/View.h
index 3ee99713dcc..b413a775bea 100644
--- a/src/xrpld/ledger/View.h
+++ b/src/xrpld/ledger/View.h
@@ -235,7 +235,7 @@ forEachItemAfter(
 transferRate(ReadView const& view, AccountID const& issuer);
 
 [[nodiscard]] Rate
-transferRate(ReadView const& view, MPT const& id);
+transferRate(ReadView const& view, MPTID const& id);
 
 /** Returns `true` if the directory is empty
     @param key The key of the directory
diff --git a/src/xrpld/ledger/detail/View.cpp b/src/xrpld/ledger/detail/View.cpp
index f5303c3bb7e..22a8249758e 100644
--- a/src/xrpld/ledger/detail/View.cpp
+++ b/src/xrpld/ledger/detail/View.cpp
@@ -180,7 +180,7 @@ isGlobalFrozen(ReadView const& view, AccountID const& issuer)
 bool
 isGlobalFrozen(ReadView const& view, MPTIssue const& mpt)
 {
-    if (auto const sle = view.read(keylet::mptIssuance(mpt.mpt())))
+    if (auto const sle = view.read(keylet::mptIssuance(mpt.getMptID())))
         return sle->getFlags() & lsfMPTLocked;
     return false;
 }
@@ -211,7 +211,7 @@ isIndividualFrozen(
     AccountID const& account,
     MPTIssue const& mpt)
 {
-    if (auto const sle = view.read(keylet::mptoken(mpt.mpt(), account)))
+    if (auto const sle = view.read(keylet::mptoken(mpt.getMptID(), account)))
         return sle->getFlags() & lsfMPTLocked;
     return false;
 }
@@ -316,7 +316,7 @@ accountHolds(
 {
     STMPTAmount amount;
 
-    auto const sleMpt = view.read(keylet::mptoken(issue.mpt(), account));
+    auto const sleMpt = view.read(keylet::mptoken(issue.getMptID(), account));
     if (!sleMpt)
         amount.clear(issue);
     else if (zeroIfFrozen == fhZERO_IF_FROZEN && isFrozen(view, account, issue))
@@ -564,7 +564,7 @@ transferRate(ReadView const& view, AccountID const& issuer)
 }
 
 Rate
-transferRate(ReadView const& view, MPT const& id)
+transferRate(ReadView const& view, MPTID const& id)
 {
     auto const sle = view.read(keylet::mptIssuance(id));
 
@@ -1363,11 +1363,13 @@ rippleSend(
     }
 
     // Sending 3rd party MPTs: transit.
-    if (auto const sle = view.read(keylet::mptIssuance(saAmount.issue().mpt())))
+    if (auto const sle =
+            view.read(keylet::mptIssuance(saAmount.issue().getMptID())))
     {
         saActual = (waiveFee == WaiveTransferFee::Yes)
             ? saAmount
-            : multiply(saAmount, transferRate(view, saAmount.issue().mpt()));
+            : multiply(
+                  saAmount, transferRate(view, saAmount.issue().getMptID()));
 
         JLOG(j.debug()) << "rippleSend> " << to_string(uSenderID) << " - > "
                         << to_string(uReceiverID)
@@ -1693,7 +1695,7 @@ requireAuth(ReadView const& view, Issue const& issue, AccountID const& account)
 TER
 requireAuth(ReadView const& view, MPTIssue const& mpt, AccountID const& account)
 {
-    auto const mptID = keylet::mptIssuance(mpt.mpt());
+    auto const mptID = keylet::mptIssuance(mpt.getMptID());
     if (auto const sle = view.read(mptID);
         sle && sle->getFieldU32(sfFlags) & lsfMPTRequireAuth)
     {
@@ -1713,7 +1715,7 @@ canTransfer(
     AccountID const& from,
     AccountID const& to)
 {
-    auto const mptID = keylet::mptIssuance(mpt.mpt());
+    auto const mptID = keylet::mptIssuance(mpt.getMptID());
     if (auto const sle = view.read(mptID);
         sle && !(sle->getFieldU32(sfFlags) & lsfMPTCanTransfer))
     {
@@ -1856,7 +1858,7 @@ rippleCredit(
     STMPTAmount saAmount,
     beast::Journal j)
 {
-    auto const mptID = keylet::mptIssuance(saAmount.issue().mpt());
+    auto const mptID = keylet::mptIssuance(saAmount.issue().getMptID());
     auto const issuer = saAmount.getIssuer();
     if (uSenderID == issuer)
     {
diff --git a/src/xrpld/rpc/MPTokenIssuanceID.h b/src/xrpld/rpc/MPTokenIssuanceID.h
index f7f45fded3b..4f94e61fa86 100644
--- a/src/xrpld/rpc/MPTokenIssuanceID.h
+++ b/src/xrpld/rpc/MPTokenIssuanceID.h
@@ -37,7 +37,7 @@ canHaveMPTokenIssuanceID(
     std::shared_ptr<STTx const> const& serializedTx,
     TxMeta const& transactionMeta);
 
-std::optional<uint192>
+std::optional<MPTID>
 getIDFromCreatedIssuance(TxMeta const& transactionMeta);
 
 void
diff --git a/src/xrpld/rpc/detail/MPTokenIssuanceID.cpp b/src/xrpld/rpc/detail/MPTokenIssuanceID.cpp
index 8de1e8f3344..c2aa3eea02a 100644
--- a/src/xrpld/rpc/detail/MPTokenIssuanceID.cpp
+++ b/src/xrpld/rpc/detail/MPTokenIssuanceID.cpp
@@ -47,7 +47,7 @@ canHaveMPTokenIssuanceID(
     return true;
 }
 
-std::optional<uint192>
+std::optional<MPTID>
 getIDFromCreatedIssuance(TxMeta const& transactionMeta)
 {
     for (STObject const& node : transactionMeta.getNodes())
@@ -74,7 +74,7 @@ insertMPTokenIssuanceID(
     if (!canHaveMPTokenIssuanceID(transaction, transactionMeta))
         return;
 
-    std::optional<uint192> result = getIDFromCreatedIssuance(transactionMeta);
+    std::optional<MPTID> result = getIDFromCreatedIssuance(transactionMeta);
     if (result.has_value())
         response[jss::mpt_issuance_id] = to_string(result.value());
 }
diff --git a/src/xrpld/rpc/handlers/LedgerEntry.cpp b/src/xrpld/rpc/handlers/LedgerEntry.cpp
index 30b1113cd05..b8f5ffe104a 100644
--- a/src/xrpld/rpc/handlers/LedgerEntry.cpp
+++ b/src/xrpld/rpc/handlers/LedgerEntry.cpp
@@ -651,7 +651,7 @@ doLedgerEntry(RPC::JsonContext& context)
                 context.params[jss::mpt_issuance];
             if (unparsedMPTIssuanceID.isString())
             {
-                uint192 mptIssuanceID;
+                MPTID mptIssuanceID;
                 if (!mptIssuanceID.parseHex(unparsedMPTIssuanceID.asString()))
                 {
                     uNodeIndex = beast::zero;
@@ -691,7 +691,7 @@ doLedgerEntry(RPC::JsonContext& context)
                         context.params[jss::mptoken][jss::mpt_issuance_id]
                             .asString();
 
-                    uint192 mptIssuanceID;
+                    MPTID mptIssuanceID;
                     if (!mptIssuanceID.parseHex(mptIssuanceIdStr))
                         Throw<std::runtime_error>(
                             "Cannot parse mpt_issuance_id");