diff --git a/Base/Xrpl.BinaryCodec/Enums/Field.Uint8.Generated.cs b/Base/Xrpl.BinaryCodec/Enums/Field.Uint8.Generated.cs
index 28287e34..01a4ca2f 100644
--- a/Base/Xrpl.BinaryCodec/Enums/Field.Uint8.Generated.cs
+++ b/Base/Xrpl.BinaryCodec/Enums/Field.Uint8.Generated.cs
@@ -10,6 +10,7 @@ public partial class Field
public static readonly Uint8Field Method = new Uint8Field(nameof(Method), 2);
public static readonly Uint8Field Scale = new Uint8Field(nameof(Scale), 4);
public static readonly Uint8Field AssetScale = new Uint8Field(nameof(AssetScale), 5);
+ public static readonly Uint8Field LEVersion = new Uint8Field(nameof(LEVersion), 6);
public static readonly Uint8Field TickSize = new Uint8Field(nameof(TickSize), 16);
public static readonly Uint8Field UNLModifyDisabling = new Uint8Field(nameof(UNLModifyDisabling), 17);
public static readonly Uint8Field HookResult = new Uint8Field(nameof(HookResult), 18);
diff --git a/Base/Xrpl.BinaryCodec/Enums/definitions.json b/Base/Xrpl.BinaryCodec/Enums/definitions.json
index 0943aa48..14555c74 100644
--- a/Base/Xrpl.BinaryCodec/Enums/definitions.json
+++ b/Base/Xrpl.BinaryCodec/Enums/definitions.json
@@ -3150,6 +3150,16 @@
"type": "UInt8"
}
],
+ [
+ "LEVersion",
+ {
+ "isSerialized": true,
+ "isSigningField": true,
+ "isVLEncoded": false,
+ "nth": 6,
+ "type": "UInt8"
+ }
+ ],
[
"TickSize",
{
diff --git a/Base/Xrpl.BinaryCodec/Xrpl.BinaryCodec.csproj b/Base/Xrpl.BinaryCodec/Xrpl.BinaryCodec.csproj
index 770d5a8f..d38c8777 100644
--- a/Base/Xrpl.BinaryCodec/Xrpl.BinaryCodec.csproj
+++ b/Base/Xrpl.BinaryCodec/Xrpl.BinaryCodec.csproj
@@ -13,7 +13,7 @@
Apache-2.0
https://github.com/StaticBit-io/XrplCSharp
XrplCSharp
- 10.9.0.0
+ 10.11.0.0
diff --git a/CHANGES.md b/CHANGES.md
index 4466a442..2cb96daa 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,6 +1,12 @@
# Changes
## 10.11.0.0 08/03/2026
+* **`sfLEVersion` — the Vault ledger entry's schema version** ([rippled #7817](https://github.com/XRPLF/rippled/pull/7817), merged into `develop` 07/30/2026, reported by protocol-watch). `UInt8` nth 6, `SoeDefault` on `ltVAULT`: it marks which accounting scheme a vault follows. Vaults created before cash-basis accounting was activated carry no `LEVersion` at all, and rippled resolves that absence as version 0 rather than an error — so an absent value is meaningful, not missing data:
+ * `definitions.json` + the generated `Field.Uint8` entry. **Both are required**: `definitions.json` is not read at runtime, it is the input to `Tools/GenerateEnums`, so a field added there alone travels nowhere. `TestULEVersion_BinaryRoundTrip` is what proves the round trip actually works rather than that the JSON was edited
+ * `LOVault.LEVersion` (`uint?`, matching the other UInt8 fields of that object) plus a `VaultVersion` enum naming the two values the protocol defines so far (`Legacy` = 0, `CashBasis` = 1)
+ * `TestULOVault_LEVersion_Deserialize` covers both shapes — the field present, and a legacy vault without it deserializing to `null`
+ * `Xrpl.BinaryCodec` bumped to **10.11.0.0**, aligned with `Xrpl` rather than to its own next minor (10.10.0.0): the codec ships the field, so the two move together and a consumer can read one version number off both. 10.10.x is simply skipped — the codec's last published version is 10.9.0, so no number is being reused. `Xrpl.AddressCodec` and `Xrpl.Keypairs` are untouched and keep 10.9.0.0
+
* **Ledger-object flags the protocol declares but the models never named** — an unnamed bit still arrives in the model as a number, so reading the object kept working and only the consumer's ability to test it by name was lost. That is why these went unnoticed; a field-by-field diff of rippled `LedgerFormats.h` (tag `3.3.0-rc1`) against every flag enum found four gaps:
* `MPTokenIssuanceFlags` + **`MPTCanHoldConfidentialBalance`** (0x80) — introduced by ConfidentialTransfer. The rest of the amendment was already complete (transactions 85–89, `IssuerEncryptionKey`/`AuditorEncryptionKey`, `ConfidentialOutstandingAmount`); only the flag had no name. Value confirmed against a live node: `MPTokenIssuanceSet` with `MutableFlags = tmfMPTSetCanHoldConfidentialBalance` moves the issuance from `Flags = 0` to `Flags = 128`
* `MPTokenFlags` + **`lsfMPTAMM`** (0x4) — a much older gap: the flag is present as far back as 3.2.1. `AMMCreate` sets it together with `lsfMPTAuthorized` to implicitly authorize an MPT asset for the AMM pseudo-account
diff --git a/Tests/Xrpl.Tests/Models/TestUProtocolCompleteness.cs b/Tests/Xrpl.Tests/Models/TestUProtocolCompleteness.cs
index 367382b5..1a27dbcc 100644
--- a/Tests/Xrpl.Tests/Models/TestUProtocolCompleteness.cs
+++ b/Tests/Xrpl.Tests/Models/TestUProtocolCompleteness.cs
@@ -322,5 +322,47 @@ public void TestULORippleState_SponsorFields_Deserialize()
Assert.AreEqual(Account1, state.HighSponsor);
Assert.AreEqual(Account2, state.LowSponsor);
}
+
+ [TestMethod]
+ public void TestULOVault_LEVersion_Deserialize()
+ {
+ string json = JsonSerializer.Serialize(new Dictionary
+ {
+ ["LedgerEntryType"] = "Vault",
+ ["Account"] = Account1,
+ ["Owner"] = Account2,
+ ["ShareMPTID"] = "00000001A407AF5856CCF3C42619DAA925813FC955C72983",
+ ["WithdrawalPolicy"] = 1,
+ ["Scale"] = 6,
+ ["LEVersion"] = (uint)VaultVersion.CashBasis,
+ });
+ LOVault vault = JsonSerializer.Deserialize(json, XrplJsonOptions.Default);
+ Assert.AreEqual((uint)VaultVersion.CashBasis, vault.LEVersion);
+
+ // A vault created before cash-basis accounting carries no LEVersion at all;
+ // rippled resolves that absence as VaultVersion.Legacy rather than an error
+ string legacy = JsonSerializer.Serialize(new Dictionary
+ {
+ ["LedgerEntryType"] = "Vault",
+ ["Account"] = Account1,
+ ["Owner"] = Account2,
+ });
+ Assert.IsNull(JsonSerializer.Deserialize(legacy, XrplJsonOptions.Default).LEVersion);
+ }
+
+ [TestMethod]
+ public void TestULEVersion_BinaryRoundTrip()
+ {
+ // The field only travels if definitions.json knows it — this fails with an
+ // encoding error, not an assertion, when the entry is missing.
+ // Parsed from text rather than built from int literals: that is the shape a
+ // node response arrives in, and Uint8.FromJson takes a byte, not an Int32
+ JsonObject json = JsonNode.Parse("""{"LEVersion":1,"Scale":6}""")!.AsObject();
+ string blob = XrplBinaryCodec.Encode(json);
+ JsonObject decoded = XrplBinaryCodec.Decode(blob).AsObject();
+
+ Assert.AreEqual(1u, decoded["LEVersion"]!.GetValue());
+ Assert.AreEqual(6u, decoded["Scale"]!.GetValue());
+ }
}
}
diff --git a/Xrpl/Models/Ledger/LOVault.cs b/Xrpl/Models/Ledger/LOVault.cs
index f50c08f8..ae7b15be 100644
--- a/Xrpl/Models/Ledger/LOVault.cs
+++ b/Xrpl/Models/Ledger/LOVault.cs
@@ -23,6 +23,27 @@ public enum VaultLedgerFlags : uint
lsfVaultPrivate = 0x00010000,
}
+///
+/// Values of the Vault ledger entry's LEVersion field (rippled VaultVersion).
+///
+///
+/// stays a plain uint?, matching the other UInt8
+/// fields of this object; these constants name the values the protocol defines so far.
+///
+public enum VaultVersion : uint
+{
+ ///
+ /// Accrual-basis accounting. Vaults created before cash-basis accounting was activated
+ /// carry no LEVersion at all and are treated as this version implicitly.
+ ///
+ Legacy = 0,
+
+ ///
+ /// Cash-basis accounting (rippled #7817).
+ ///
+ CashBasis = 1,
+}
+
///
/// Recommended structure for the Vault Data field.
/// The JSON is whitespace-removed and hex-encoded (max 256 bytes).
@@ -144,6 +165,14 @@ public LOVault()
[JsonPropertyName("Scale")]
public uint? Scale { get; init; }
+ ///
+ /// Schema version of this ledger entry (UInt8), see .
+ /// Absent on vaults created before cash-basis accounting was activated, which
+ /// rippled resolves as (0) rather than an error.
+ ///
+ [JsonPropertyName("LEVersion")]
+ public uint? LEVersion { get; init; }
+
///
/// Arbitrary hex-encoded data associated with the vault, limited to 256 bytes.
/// Use for a human-readable representation.