Skip to content

feat: add 25 new XRPL transaction types, 7 ledger objects, and docs#6

Merged
Platonenkov merged 20 commits into
devfrom
feature/missing_func
May 13, 2026
Merged

feat: add 25 new XRPL transaction types, 7 ledger objects, and docs#6
Platonenkov merged 20 commits into
devfrom
feature/missing_func

Conversation

@Platonenkov
Copy link
Copy Markdown
Collaborator

@Platonenkov Platonenkov commented May 12, 2026

Summary

  • 25 new transaction types: XChain Bridge (8), Vault (6), Loan/LoanBroker (9), DelegateSet, LedgerStateFix
  • 7 new ledger objects: LOBridge, LOXChainOwnedClaimID, LOXChainOwnedCreateAccountClaimID, LODelegate, LOVault, LOLoanBroker, LOLoan
  • LoanSigningHelper: V1/V2/V3 multi-party signing patterns for LoanSet CounterpartySignature
  • NumberType: 12-byte binary codec (8-byte mantissa + 4-byte exponent) for Loan fields
  • Integration tests: 33 new tests (XChain, Vault, Loan, DelegateSet, LedgerStateFix)
  • Documentation: XChainBridge, Vault, LendingProtocol guides (EN + RU)
  • Validate audit: all 25 Validate methods verified against xrpl.org docs

Details

Category Files Count
Transaction models Xrpl/Models/Transactions/ 25
Ledger objects Xrpl/Models/Ledger/LO*.cs 7
Common models XChainBridgeModel, XChainAttestations, PermissionModel 3
Converters TransactionRequest/Response/LOConverter 3 updated
Enums TransactionType (+25), LedgerEntryType (+2) 2 updated
BinaryCodec NumberType, XChainBridgeType 2 updated
Signing LoanSigningHelper, XrplWallet 2
Integration tests TestI*.cs 8 new files
Documentation DocFx/*.md 6 new files

Test plan

  • dotnet build passes without errors
  • 738 unit tests pass (dotnet test --filter "TestU")
  • 33 integration tests pass on rippled 3.1.3 standalone (dotnet test --filter "TestI")
  • Review new transaction models for field completeness
  • Review LoanSigningHelper V1/V2/V3 patterns
  • Verify NumberType serialization edge cases

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added Cross‑Chain Bridge, Single‑Asset Vaults, Lending (LoanBroker/Loan) transactions, DelegateSet and LedgerStateFix; MPT (multi‑purpose token) asset support; loan multi‑party signing workflows.
  • Bug Fixes

    • Improved numeric serialization/normalization, bridge binary compatibility, fee/reserve calculation for certain tx types, and clearer error messages.
  • Documentation

    • Full EN/RU guides for Cross‑Chain Bridge, Vaults, and Lending; TOC updates.
  • Tests

    • Many new and updated unit & integration tests covering bridges, vaults, loans, MPTs, and number format.
  • Chores

    • CI config adjusted; local tool settings added to .gitignore.

Review Change Stack

…ation tests

Add models, converters, TxFormat, validation, and integration tests for:
- XChain Bridge (XLS-38d): 8 transaction types + 3 ledger objects
- Vault (XLS-65d): 6 transaction types + 1 ledger object
- Loan/LoanBroker (XLS-66d): 9 transaction types + 2 ledger objects
- DelegateSet (XLS-74d) + LedgerStateFix: 2 transaction types + 1 ledger object
Currency properties in XChain and Loan transaction models were missing
[JsonConverter(typeof(CurrencyConverter))] attributes, causing XRP amounts
to serialize as objects instead of strings — leading to invalidTransaction
errors. Also fix VaultClawback test to set asfAllowTrustLineClawback flag.
XChainBridge compound type was writing raw 20-byte AccountID fields
without VL length prefix, causing rippled to reject blobs with
"Invalid STAccount size". Now matches rippled's STXChainBridge::add()
serialization format.

Mark integration tests [Ignore] for amendments not available on
rippled 3.0.0 standalone: Loan, DelegateKeys, LedgerStateFix,
XChainBridge (requires sidechain), VaultCreate_WithAsset2 (Asset2
field unsupported), VaultClawback (XRP vaults not clawback-eligible).
…entation

- Fix error messages to include error_exception from rippled responses
  (RequestManager.cs, XrplErrorClassifier.cs)
- Fix nullable request fields (AccountObjects.Limit, DeletionBlockersOnly,
  AccountOffers.Strict) to prevent invalidParams errors on rippled 3.1.x
- Fix XChainBridge XRP tests: use genesis account as IssuingChainDoor
- Add IOU-IOU bridge tests with DefaultRipple, TrustLine setup, token issuance
- Fix XChainBridgeType binary size assertions after VL-encoding fix
- Update CI rippled config: unveto XChainBridge/SingleAssetVault/LendingProtocol,
  comment out removed Batch amendment
- Add [Ignore] on tests for removed/unavailable amendments (Batch, DelegateSet,
  Loan, LedgerStateFix) with explanatory messages
- Add Cross-Chain Bridge Guide documentation (EN + RU) to DocFx
…fication

- Fix DelegateSet model: field "Delegate" → "Authorize" to match rippled protocol
- Update validation to check for "Authorize" field
- Add LODelegate ledger object verification in integration tests via account_objects
- Verify Account, Delegate, Permissions fields and PermissionValue correctness
LedgerStateFix requires a special transaction cost >= owner reserve
(0.2 XRP), similar to AccountDelete and AMMCreate. Added it to
IsReserveFeeTxNeed() so Autofill handles the fee automatically.
Updated test to use fail_hard and rely on Autofill for fee.
NumberType was writing 8 bytes (raw ulong) but rippled expects 12 bytes
(8 bytes int64 mantissa + 4 bytes int32 exponent). This caused
`invalid SerialIter geti32` errors on LoanSet submission.

Key changes:
- NumberType: 12-byte serialization with mantissa normalized to [10^18, MaxValue]
- LoanSet: CounterpartySignature co-signing with fee tripling
- LoanBrokerSet/LoanSet/LoanManage/LoanPay: field corrections per XLS-66 spec
- TxFormat: corrected field requirements for Loan transaction types
- Integration tests: all 8 Loan tests passing (broker, loan, pay, delete)
Add comprehensive documentation for the XRPL Lending Protocol (XLS-66d):
- LendingProtocol-Guide.md (English) and LendingProtocol-Guide.ru.md (Russian)
- Covers: broker setup, vault funding, loan creation, CounterpartySignature
  co-signing flow, Number type encoding, common errors, best practices
- Update CHANGES.md with all new transaction types, ledger objects, and fixes
- Add Lending Protocol Guide to DocFx table of contents
Implement three signing patterns for LoanSet CounterpartySignature
analogous to Batch signing (XLS-66d):
- V1 (automatic): both keys available locally
- V2 (parallel): independent signing + CombineLoanSignatures merge
- V3 (sequential): borrower signs first via SignAsLoanCounterparty,
  broker completes via LoanSigningHelper.BrokerSign

Add SignAsLoanCounterparty() to XrplWallet for borrower partial signing.
Add V2/V3 integration tests (10 total Loan tests passing).
Update documentation with all three signing patterns.
…fill

Fee for CounterpartySignature overhead is now computed in
CalculateBaseFeeForType (baseFee * 2) alongside EscrowFinish and Batch
fee logic. Removed adjustFee parameter from PrepareForSigning.
- Replace TestVaultCreate_WithAsset2 with TestVaultCreate_WithIOU
  (Asset2 not supported in VaultCreate on rippled 3.1.3)
- Remove Asset2 from VaultCreate TxFormat
- Rewrite TestVaultClawback_Basic to use IOU vault with proper setup:
  DefaultRipple, AllowTrustLineClawback, TrustLine, Payment, then
  VaultDeposit before clawback
- All 8 Vault tests now pass
…docs

- Audit all 25 Validate methods against xrpl.org documentation
- VaultClawback: Holder field changed from Optional to Required in TxFormat
- XChainAddAccountCreateAttestation: add missing OtherChainSource field
- XChainAddClaimAttestation: add WasLockingChainSend validation
- WasLockingChainSend changed from int to byte (UInt8, 0/1 values)
- DelegateSet: rename Authorize to Delegate (PascalCase, no keyword conflict)
- LOVault: add VaultDataFormat, VaultCreateFlags, VaultLedgerFlags, DataParsed
- VaultCreate/VaultSet/VaultWithdraw: add Scale, WithdrawalPolicy, Destination fields
- LOLoan/LOLoanBroker: relax STNumber null-field assertions (rippled omits zero defaults)
- Add Vault Guide documentation (EN + RU)
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 25b611a8-a37b-41ad-bdd5-e9000e1ddac1

📥 Commits

Reviewing files that changed from the base of the PR and between 1c13941 and 7524cb4.

📒 Files selected for processing (1)
  • Xrpl/Models/Subscriptions/BookChangesStream.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • Xrpl/Models/Subscriptions/BookChangesStream.cs

📝 Walkthrough

Walkthrough

Adds cross-chain bridge, vault, lending protocol, and related support end‑to‑end: codec wire/JSON changes (Number, Issue, XChainBridge), many new ledger/transaction models and validators, client API/stream events, loan co‑signing helpers and wallet partial signing, autofill fee updates, extensive unit/integration tests, documentation (EN/RU), CI rippled feature toggles, and a .gitignore entry.

Changes

End-to-end feature surface (codec → models → client → wallet → tests → docs)

Layer / File(s) Summary
NumberType (mantissa/exponent) codec & tests
Base/Xrpl.BinaryCodec/Types/NumberType.cs, Tests/Xrpl.BinaryCodec.Test/Types/TestNumberType.cs
Replaced 8‑byte ulong number encoding with signed mantissa (long) + exponent (int) 12‑byte wire format; added parsing/normalization from decimal/scientific strings, JSON decimal-string output, and related unit tests for 12‑byte layout, rippled match, negatives, zero, and mantissa normalization.
Issue (MPT) codec, currency converter & tests
Base/Xrpl.BinaryCodec/Types/Issue.cs, Xrpl/Client/Json/Converters/CurrencyConverter.cs, Tests/Xrpl.BinaryCodec.Test/Types/TestIssueMpt.cs, Xrpl/Models/Common/Common.cs
Introduced MPT issuance id support (Hash192) with new wire/JSON forms for MPT assets; Issue read/write updated to detect/emit MPT marker and reconstruct issuance id; IssuedCurrency gains MptIssuanceId + IsMpt(); JSON converter reads/writes mpt_issuance_id; added MPT binary/unit tests.
XChainBridge wire TLV fixes & tests
Base/Xrpl.BinaryCodec/Types/XChainBridgeType.cs, Tests/Xrpl.BinaryCodec.Test/Types/TestXChainBridgeType.cs
Writes/reads explicit VL(20) prefixes before door AccountId fields, validates lengths, and parses AccountId from exact slices; unit tests adjusted expected serialized sizes (+2 bytes).
Ledger models, subscriptions & common DTOs
Xrpl/Models/Ledger/*, Xrpl/Models/Subscriptions/*, Xrpl/Models/Common/*
Added many LO models (LOBridge, LOXChainOwnedClaimID, LOXChainOwnedCreateAccountClaimID, LOVault, LOLoanBroker, LOLoan, LODelegate), Vault helpers (VaultDataFormat, VaultLedgerFlags), subscription stream models (ManifestStream, BookChangesStream, ServerStatusStream), XChain attestation DTOs, and Permission models; IssuedCurrency extended for MPT and readable name helper.
Transaction models, converters, TxFormat, Validation dispatcher
Xrpl/Models/Transactions/*, Xrpl/Client/Json/Converters/*, Xrpl/Models/Transactions/TxFormat.cs, Xrpl/Client/Json/Converters/LedgerObjectConverter.cs
Implemented XChain*, Vault*, LoanBroker*/Loan*, DelegateSet, LedgerStateFix transaction request/response DTOs with JSON mappings and per‑transaction async validators; extended TxFormat schemas and Validation switch dispatch; converters updated to recognize new TransactionType/LedgerEntryType mappings.
DelegateSet validation
Xrpl/Models/Transactions/DelegateSet.cs
New DelegateSet contract/DTOs and comprehensive Permissions validation (supports two input shapes, nested Permission wrapper, max entries, duplication and non‑delegable transaction checks).
Client API surface, connection streams & error handling
Xrpl/Client/IXrplClient.cs, Xrpl/Client/connection.cs, Xrpl/Client/RequestManager.cs, Xrpl/Client/Exceptions/XrplErrorClassifier.cs
Extended IXrplClient with stream events (OnValidationReceived, OnBookChanges, OnServerStatus), corrected OnManifestReceived signature, added RPC helpers (ChannelAuthorize/ChannelVerify/ServerDefinitions/VaultInfo/TransactionEntry); connection dispatch now deserializes/dispatches new stream types; RequestManager and error classifier prefer ErrorMessage with fallback to ErrorException when composing raw error detail.
Loan multi‑party signing flows & wallet helpers
Xrpl/Wallet/LoanSigningHelper.cs, Xrpl/Wallet/XrplWallet.cs
Added LoanSigningHelper implementing PrepareForSigning and V1/V2/V3 signing patterns (SignLoanSet, CombineLoanSignatures, BrokerSign) and XrplWallet helpers to produce counterparty partial signatures (SignAsLoanCounterparty).
Autofill fee rules
Xrpl/Sugar/Autofill.cs
Fee logic: LoanSet base fee = baseFee * 2; fee-cap bypass uses IsReserveFeeTxNeed(tx); IsReserveFeeTxNeed extended to include LedgerStateFix (alongside AccountDelete and AMMCreate).
Integration test bases & suites
Tests/Xrpl.Tests/Integration/transactions/*, Tests/Xrpl.Tests/Integration/transactions/TestILoanBase.cs, TestIVaultBase.cs, TestIXChainBridgeBase.cs
Added many integration suites and shared base helpers for XChain, Vault, Loan (including MPT scenarios and co‑signing workflows), DelegateSet, LedgerStateFix, AMM MPT rejection tests; some legacy Batch tests marked ignored.
Unit tests and binary codec tests
Tests/Xrpl.BinaryCodec.Test/*
Updated/added unit tests covering NumberType 12‑byte format and normalization, MPT Issue encoding/roundtrip, XChainBridge size changes, and LoanSet encode/decode/signing checks.
Docs, changelog, CI config, .gitignore
DocFx/*, CHANGES.md, .ci-config/rippled.cfg, .gitignore
Added Cross‑Chain Bridge, Vault, Lending Protocol guides (EN + RU) and TOC entries; updated CHANGES.md; CI rippled config: enabled XChainBridge, SingleAssetVault, LendingProtocol, commented Batch amendment (noted removal), added fixCleanup3_1_3; added .claude/settings.local.json to .gitignore.

Sequence Diagram(s)

sequenceDiagram
  participant Borrower as Borrower (Wallet)
  participant Broker as Broker (Wallet)
  participant Client as SDK Client
  participant Server as XRPL Network
  Borrower->>Client: PrepareForSigning(LoanSet)
  Borrower->>Client: Sign counterparty preimage (SignAsLoanCounterparty)
  Client-->>Broker: deliver partially-signed blob (contains CounterpartySignature)
  Broker->>Client: BrokerSign(partially-signed blob)
  Client->>Server: submit fully-signed tx blob
  Server-->>Client: tx accepted / metadata
  Client->>Broker: return TransactionSummary
Loading

Estimated code review effort
🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

"I tuned the mantissa, exponent just right,
Bridges stitched ledger to ledger by night.
Vaults hold carrots, loans sign two-by-two,
Tests hop green, docs sing in EN and RU.
A rabbit applauds the build—🥕🎉"

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/missing_func

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🟠 Major comments (24)
Base/Xrpl.BinaryCodec/Types/XChainBridgeType.cs-65-69 (1)

65-69: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate AccountID VL lengths before constructing AccountId.

FromParser reads lockingDoorLen / issuingDoorLen but never enforces the expected 20-byte AccountID length. A malformed buffer can be accepted and misparsed. Please reject non-20 lengths at parse time (Line 65 and Line 68).

🔧 Proposed fix
 public static XChainBridgeType FromParser(BinaryParser parser, int? hint = null)
 {
     int lockingDoorLen = parser.ReadVlLength();
+    if (lockingDoorLen != 20)
+        throw new FormatException($"Invalid LockingChainDoor length: {lockingDoorLen}. Expected 20.");
     AccountId lockingDoor = new AccountId(parser.Read(lockingDoorLen));
     Issue lockingIssue = Issue.FromParser(parser);
     int issuingDoorLen = parser.ReadVlLength();
+    if (issuingDoorLen != 20)
+        throw new FormatException($"Invalid IssuingChainDoor length: {issuingDoorLen}. Expected 20.");
     AccountId issuingDoor = new AccountId(parser.Read(issuingDoorLen));
     Issue issuingIssue = Issue.FromParser(parser);

     return new XChainBridgeType(lockingDoor, lockingIssue, issuingDoor, issuingIssue);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Base/Xrpl.BinaryCodec/Types/XChainBridgeType.cs` around lines 65 - 69, The
code reads variable-length AccountID fields into lockingDoorLen and
issuingDoorLen and constructs new AccountId without validating their length, so
update the parsing logic in the XChainBridgeType parsing block (where
lockingDoorLen, lockingDoor, Issue.FromParser, issuingDoorLen, issuingDoor are
handled) to check parser.ReadVlLength() returns exactly 20 bytes before calling
new AccountId(parser.Read(...)); if the length is not 20, throw a parsing/format
exception (or return an error) to reject the malformed buffer rather than
constructing an AccountId from an incorrect-length byte array.
Xrpl/Client/RequestManager.cs-352-357 (1)

352-357: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Include ErrorException in the no-status error gate.

On Line 352, responses with only ErrorException currently bypass this branch and get downgraded to generic "Response has no status".

💡 Suggested fix
-                if (response.Error is not null || response.ErrorMessage is not null)
+                if (response.Error is not null || response.ErrorMessage is not null || response.ErrorException is not null)
                 {
-                    string detail = response.ErrorMessage ?? response.ErrorException;
+                    string detail = response.ErrorMessage ?? response.ErrorException ?? "Unknown error";
                     var errMessage = response.Error is null
                         ? detail
                         : $"{response.Error} - {detail}";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Client/RequestManager.cs` around lines 352 - 357, The branch that builds
an error message currently only checks response.Error and response.ErrorMessage,
causing responses with only response.ErrorException to fall through to the "no
status" gate; update the conditional in RequestManager (where response is
evaluated) to include response.ErrorException (i.e., if response.Error is not
null || response.ErrorMessage is not null || response.ErrorException is not
null) and keep constructing detail with response.ErrorMessage ??
response.ErrorException and errMessage using response.Error when present so that
ErrorException-only failures are handled and logged like other errors.
Xrpl/Models/Transactions/LoanSet.cs-292-296 (1)

292-296: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

ValidateLoanSet is too permissive for required string fields.

At Lines 292–296, PrincipalRequested only checks null (not string/non-empty), and LoanBrokerID allows empty whitespace strings. This lets invalid tx payloads pass validation.

Suggested fix
-            if (!tx.TryGetValue("LoanBrokerID", out var brokerId) || brokerId is not string)
-                throw new ValidationException("LoanSet: missing field LoanBrokerID");
+            if (!tx.TryGetValue("LoanBrokerID", out var brokerId)
+                || brokerId is not string brokerIdText
+                || string.IsNullOrWhiteSpace(brokerIdText))
+                throw new ValidationException("LoanSet: missing or invalid field LoanBrokerID");

-            if (!tx.TryGetValue("PrincipalRequested", out var principal) || principal is null)
-                throw new ValidationException("LoanSet: missing field PrincipalRequested");
+            if (!tx.TryGetValue("PrincipalRequested", out var principal)
+                || principal is not string principalText
+                || string.IsNullOrWhiteSpace(principalText))
+                throw new ValidationException("LoanSet: missing or invalid field PrincipalRequested");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/LoanSet.cs` around lines 292 - 296, The
ValidateLoanSet validator is too permissive: update the checks for
"LoanBrokerID" and "PrincipalRequested" so they require non-empty strings (not
just presence or non-null). Specifically, in the ValidateLoanSet method, change
the LoanBrokerID check to ensure tx.TryGetValue("LoanBrokerID", out var
brokerId) && brokerId is string s && !string.IsNullOrWhiteSpace(s)
(trim/validate non-empty) and change the PrincipalRequested check to ensure
tx.TryGetValue("PrincipalRequested", out var principal) && principal is string p
&& !string.IsNullOrWhiteSpace(p); if either fails, throw the existing
ValidationException with the same field-specific message.
Tests/Xrpl.BinaryCodec.Test/Types/TestLoanSetEncode.cs-28-42 (1)

28-42: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add assertions; current test is observational-only.

Lines 28-42 only print values, so the test passes even when encoding/decoding regresses.

Suggested fix
         string encoded = XrplBinaryCodec.Encode(node);
-        Console.WriteLine("Encoded hex: " + encoded);
-        Console.WriteLine("Encoded length: " + encoded.Length / 2 + " bytes");
+        Assert.IsFalse(string.IsNullOrWhiteSpace(encoded), "Encoded hex should not be empty.");

         JsonNode decodedNode = XrplBinaryCodec.Decode(encoded);
-        string decoded = decodedNode.ToJsonString(new JsonSerializerOptions { WriteIndented = true });
-        Console.WriteLine("Decoded JSON: " + decoded);
+        Assert.IsNotNull(decodedNode, "Decoded node should not be null.");
+        Assert.AreEqual("LoanSet", decodedNode["TransactionType"]?.ToString());
+        Assert.AreEqual("10000000000000", decodedNode["PrincipalRequested"]?.ToString());

         string principalDecoded = decodedNode["PrincipalRequested"]?.ToString();
-        Console.WriteLine("PrincipalRequested decoded: " + principalDecoded);
+        Assert.AreEqual("10000000000000", principalDecoded);

-        // Also encode for signing
         string forSigning = XrplBinaryCodec.EncodeForSigning(node);
-        Console.WriteLine("ForSigning hex: " + forSigning);
+        Assert.IsFalse(string.IsNullOrWhiteSpace(forSigning), "Signing payload should not be empty.");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/Xrpl.BinaryCodec.Test/Types/TestLoanSetEncode.cs` around lines 28 - 42,
The test currently only prints values; make it assert encoding/decoding
correctness by adding assertions: use XrplBinaryCodec.Encode(node) and
Assert.NotNull/NotEmpty on the resulting string, Assert.Equal on the decoded
JSON or on specific fields to verify round-trip (e.g., compare
decodedNode["PrincipalRequested"] to the original node's PrincipalRequested),
and Assert.Equal for the byte length if you have an expected length; also assert
that XrplBinaryCodec.EncodeForSigning(node) produces a non-empty string and, if
available, matches an expected signing hex or at least differs from the regular
Encode output. Use the test framework's Assert methods (e.g., Assert.Equal,
Assert.NotNull/NotEmpty) to replace the Console.WriteLine checks around
XrplBinaryCodec.Encode, Decode, decodedNode and EncodeForSigning.
Base/Xrpl.BinaryCodec/Types/NumberType.cs-57-69 (1)

57-69: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

ToJson() will throw OverflowException for large protocol-valid exponents.

Lines 57-69 apply repeated decimal multiplication/division based on Exponent (which permits magnitudes up to ±32768), but System.Decimal has a maximum magnitude of ~7.92 × 10^28. A mantissa near long.MaxValue (≈9.22 × 10^18) multiplied by 10 even 11 times exceeds this limit and throws OverflowException at runtime. The protocol allows exponent magnitudes far beyond what decimal can handle, and there is no overflow handling.

A safer approach is string-based formatting from (Mantissa, Exponent) instead of decimal scaling, or use BigInteger for arbitrary precision arithmetic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Base/Xrpl.BinaryCodec/Types/NumberType.cs` around lines 57 - 69, ToJson
currently scales a System.Decimal by looping on Mantissa and Exponent which can
overflow for protocol-allowed exponents; replace that decimal-scaling logic in
ToJson with a string-based conversion (or BigInteger-backed) that builds the
numeric string from Mantissa and Exponent without performing repeated decimal
multiplication/division. Specifically, in ToJson use Mantissa and Exponent to
compute sign, absMantissa (as BigInteger or string), then shift the decimal
point by Exponent to produce a correctly placed integer/decimal or
scientific-style string (handling large positive/negative exponents and trimming
trailing zeros) and return that string instead of using System.Decimal; update
any references to Mantissa and Exponent in NumberType.ToJson accordingly.
Xrpl/Models/Transactions/TxFormat.cs-553-571 (1)

553-571: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

LoanSet format is missing CounterpartySignature.

Given LoanSet co-signing support, omitting this field from TxFormat can cause validation failures for otherwise valid signed LoanSet transactions.

💡 Proposed fix
                 [BinaryCodec.Types.TransactionType.LoanSet] = new TxFormat
                 {
                     [Field.LoanBrokerID] = Requirement.Required,
                     [Field.PrincipalRequested] = Requirement.Required,
                     [Field.Counterparty] = Requirement.Optional,
+                    [Field.CounterpartySignature] = Requirement.Optional,
                     [Field.InterestRate] = Requirement.Optional,
                     [Field.LateInterestRate] = Requirement.Optional,
                     [Field.CloseInterestRate] = Requirement.Optional,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/TxFormat.cs` around lines 553 - 571, The LoanSet
TxFormat (BinaryCodec.Types.TransactionType.LoanSet) is missing the
CounterpartySignature field which breaks validation for co-signed LoanSet
transactions; update the TxFormat initializer for LoanSet (the object assigned
to BinaryCodec.Types.TransactionType.LoanSet) to include
Field.CounterpartySignature with an appropriate requirement (e.g.,
Requirement.Optional) alongside the other fields so co-signed LoanSet
transactions validate correctly.
Xrpl/Sugar/Autofill.cs-324-327 (1)

324-327: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reserve-fee transactions can be incorrectly capped after adding LedgerStateFix.

Line 324 extends reserve-fee detection, but cap bypass still only checks AccountDelete (Line 209). With a low maxFeeXRP, LedgerStateFix can be autofilled with insufficient fee and fail submission.

💡 Proposed fix
-                totalFee = transactionType == "AccountDelete"
+                totalFee = IsReserveFeeTxNeed(tx)
                     ? calculatedFee
                     : BigInteger.Min(calculatedFee, maxFeeBI);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Sugar/Autofill.cs` around lines 324 - 327, The reserve-fee detection was
expanded to include LedgerStateFix but the fee-cap bypass logic still only
treats AccountDelete as exempt; update the cap-bypass check (the conditional
that currently tests for nameof(TransactionType.AccountDelete)) to also allow
nameof(TransactionType.LedgerStateFix) so LedgerStateFix transactions bypass the
maxFeeXRP cap the same way; locate the comparison that checks for AccountDelete
and extend it to include LedgerStateFix (or refactor both places to call the
same IsReserveFeeTransaction helper) to keep behavior consistent.
DocFx/LendingProtocol-Guide.md-435-448 (1)

435-448: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Resolve contradictory LoanSet fee guidance.

Line 275 says Autofill already includes CounterpartySignature overhead, but Lines 444-448 instruct users to triple the fee after autofill. These instructions conflict and can lead to systematic overpayment.

Suggested fix
-| `telINSUF_FEE_P` | Fee too low after adding CounterpartySignature | Triple the fee after autofill to account for extra signature bytes |
+| `telINSUF_FEE_P` | Fee too low after adding CounterpartySignature | Re-run Autofill on the final pre-signed payload and verify the computed fee before submit |
...
-2. **Triple the fee for LoanSet** — after autofilling, multiply the fee by 3 to account for the `CounterpartySignature` overhead:
-   ```csharp
-   ulong feeDrops = ulong.Parse(loanTx.Fee.Value);
-   loanTx.Fee = new Currency { Value = (feeDrops * 3).ToString(), CurrencyCode = "XRP" };
-   ```
+2. **Use Autofill for final fee calculation** — ensure the transaction includes the signing fields expected by your flow, then call Autofill and submit with the returned fee.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DocFx/LendingProtocol-Guide.md` around lines 435 - 448, The docs conflict:
one place states Autofill includes CounterpartySignature overhead while another
instructs developers to manually triple loanTx.Fee after Autofill; remove the
"triple the fee" snippet and replace it with guidance to rely on Autofill's
returned fee (i.e., call Autofill after you have added any expected signing
fields such as CounterpartySignature or other signing fields, then submit using
the fee value returned in loanTx.Fee), and update the LoanSet guidance to
reference using Autofill for final fee calculation instead of multiplying
loanTx.Fee by 3.
Xrpl/Wallet/XrplWallet.cs-1023-1033 (1)

1023-1033: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Require broker SigningPubKey before generating CounterpartySignature.

SignAsLoanCounterparty signs whatever preimage is present, but it doesn't enforce that SigningPubKey is already set (broker key). If missing, you produce a counterparty signature over the wrong preimage and the combined LoanSet becomes invalid.

Suggested fix
             string txType = tx["TransactionType"]?.GetValue<string>();
             if (!string.Equals(txType, "LoanSet", StringComparison.OrdinalIgnoreCase))
                 throw new ValidationException($"SignAsLoanCounterparty requires TransactionType=LoanSet, got: {txType}");
+
+            var brokerSigningPubKey = tx["SigningPubKey"]?.GetValue<string>();
+            if (string.IsNullOrWhiteSpace(brokerSigningPubKey))
+                throw new ValidationException("LoanSet must include broker SigningPubKey before counterparty signing.");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Wallet/XrplWallet.cs` around lines 1023 - 1033, The
SignAsLoanCounterparty flow currently computes a signing preimage without
ensuring the broker's SigningPubKey is present, causing invalid
CounterpartySignature; in SignAsLoanCounterparty (XrplWallet.cs) check that
tx["SigningPubKey"] (the broker's public key) exists and is non-empty before
removing existing signatures and before calling
LoanSigningHelper.GetSigningPreimage(tx), and if it's missing throw a
ValidationException with a clear message (e.g., "SignAsLoanCounterparty requires
broker SigningPubKey to be set"); this ensures you sign the correct preimage.
Xrpl/Models/Transactions/VaultWithdraw.cs-53-55 (1)

53-55: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Apply CurrencyConverter to Amount for VaultWithdraw.

Amount is a Currency field but lacks JsonConverter(typeof(CurrencyConverter)) here, unlike other transaction models with Currency payloads.

Suggested patch
+using Xrpl.Client.Json.Converters;
 using Xrpl.Models.Common;
@@
         /// <inheritdoc />
         [JsonPropertyName("Amount")]
+        [JsonConverter(typeof(CurrencyConverter))]
         public Currency Amount { get; set; }
@@
         /// <inheritdoc />
         [JsonPropertyName("Amount")]
+        [JsonConverter(typeof(CurrencyConverter))]
         public Currency Amount { get; set; }

Also applies to: 73-75

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/VaultWithdraw.cs` around lines 53 - 55, The
VaultWithdraw model's Amount property(s) are missing the JSON converter
attribute; add [JsonConverter(typeof(CurrencyConverter))] to each Currency-typed
Amount property in the VaultWithdraw class so they serialize/deserialize like
other transaction models that use CurrencyConverter (apply this to the primary
Amount property and the second Amount declaration later in the VaultWithdraw
class).
Xrpl/Models/Transactions/LedgerStateFix.cs-57-63 (1)

57-63: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add required Owner validation in LedgerStateFix validator.

Line 61 only validates LedgerFixType. Owner is part of ILedgerStateFix and currently passes through unchecked.

Suggested patch
 public static async Task ValidateLedgerStateFix(Dictionary<string, object> tx)
 {
     await Common.ValidateBaseTransaction(tx);

     if (!tx.TryGetValue("LedgerFixType", out var fixType) || fixType is null)
         throw new ValidationException("LedgerStateFix: missing field LedgerFixType");
+
+    if (!tx.TryGetValue("Owner", out var owner) || owner is not string s || string.IsNullOrWhiteSpace(s))
+        throw new ValidationException("LedgerStateFix: missing field Owner");
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/LedgerStateFix.cs` around lines 57 - 63, The
LedgerStateFix validator (ValidateLedgerStateFix) currently only checks
LedgerFixType but must also validate the required Owner field from
ILedgerStateFix: add a check that tx.TryGetValue("Owner", out var owner)
succeeds and owner is a non-null, non-empty string, and then validate the owner
value using the project account/address validator (e.g. call
Common.ValidateAccount(owner) or the equivalent helper in Common); if missing or
invalid, throw a ValidationException with a clear message (e.g. "LedgerStateFix:
missing or invalid field Owner").
Xrpl/Models/Transactions/LoanManage.cs-93-99 (1)

93-99: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Enforce LoanManage action-flag semantics in validation.

Line 97 validates only LoanID; it doesn’t enforce that exactly one of tfLoanDefault/tfLoanImpair/tfLoanUnimpair is selected.

Suggested patch
 public static async Task ValidateLoanManage(Dictionary<string, object> tx)
 {
     await Common.ValidateBaseTransaction(tx);

     if (!tx.TryGetValue("LoanID", out var id) || id is not string)
         throw new ValidationException("LoanManage: missing field LoanID");
+
+    if (!tx.TryGetValue("Flags", out var rawFlags) || rawFlags is null)
+        throw new ValidationException("LoanManage: missing field Flags");
+
+    uint flagsValue = rawFlags switch
+    {
+        uint u => u,
+        int i when i >= 0 => (uint)i,
+        long l when l is >= 0 and <= uint.MaxValue => (uint)l,
+        _ => throw new ValidationException("LoanManage: invalid field Flags")
+    };
+
+    LoanManageFlags flags = (LoanManageFlags)flagsValue;
+    int actionCount =
+        ((flags & LoanManageFlags.tfLoanDefault) != 0 ? 1 : 0) +
+        ((flags & LoanManageFlags.tfLoanImpair) != 0 ? 1 : 0) +
+        ((flags & LoanManageFlags.tfLoanUnimpair) != 0 ? 1 : 0);
+
+    if (actionCount != 1)
+        throw new ValidationException("LoanManage: exactly one action flag must be set");
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/LoanManage.cs` around lines 93 - 99,
ValidateLoanManage currently only checks LoanID; update it to also enforce that
exactly one action flag is set among tfLoanDefault, tfLoanImpair,
tfLoanUnimpair. In ValidateLoanManage, after verifying LoanID, read the three
keys from tx (e.g., using tx.TryGetValue for "tfLoanDefault", "tfLoanImpair",
"tfLoanUnimpair"), count how many evaluate as set/true (handle boolean or
numeric truthy values), and if the count != 1 throw a ValidationException with a
clear message like "LoanManage: must set exactly one of tfLoanDefault,
tfLoanImpair, tfLoanUnimpair"; keep the existing LoanID check and
Common.ValidateBaseTransaction call.
Xrpl/Models/Transactions/VaultClawback.cs-47-48 (1)

47-48: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Amount is missing CurrencyConverter, which can break XRPL amount JSON encoding.

Please align with other transaction models and explicitly apply CurrencyConverter to both request and response Amount fields.

Proposed fix
 using Xrpl.Client.Exceptions;
+using Xrpl.Client.Json.Converters;
 using Xrpl.Models.Common;
@@
         [JsonPropertyName("Amount")]
+        [JsonConverter(typeof(CurrencyConverter))]
         public Currency Amount { get; set; }
@@
         [JsonPropertyName("Amount")]
+        [JsonConverter(typeof(CurrencyConverter))]
         public Currency Amount { get; set; }

Also applies to: 63-64

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/VaultClawback.cs` around lines 47 - 48, The Amount
property in the VaultClawback model lacks the CurrencyConverter attribute and
must mirror other transaction models; add the JsonConverter attribute
referencing CurrencyConverter to the Amount properties in VaultClawback (both
occurrences — the request and response Amount fields) so JSON (de)serialization
uses CurrencyConverter for XRPL amount encoding/decoding; update the Amount
property declarations in the VaultClawback class to include
[JsonConverter(typeof(CurrencyConverter))].
Xrpl/Models/Transactions/LoanPay.cs-14-17 (1)

14-17: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Mutually exclusive LoanPay flags are documented but never enforced.

ValidateLoanPay should reject combinations of tfLoanOverpayment, tfLoanFullPayment, and tfLoanLatePayment.

Proposed fix
 using System;
 using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
 using System.Threading.Tasks;
@@
         public static async Task ValidateLoanPay(Dictionary<string, object> tx)
         {
             await Common.ValidateBaseTransaction(tx);
@@
             if (!tx.TryGetValue("Amount", out var amount) || amount is null)
                 throw new ValidationException("LoanPay: missing field Amount");
+
+            if (tx.TryGetValue("Flags", out var flagsObj) && flagsObj is not null)
+            {
+                uint rawFlags = flagsObj switch
+                {
+                    uint u => u,
+                    int i => checked((uint)i),
+                    long l => checked((uint)l),
+                    JsonElement { ValueKind: JsonValueKind.Number } je when je.TryGetUInt32(out var u) => u,
+                    _ => throw new ValidationException("LoanPay: invalid field Flags"),
+                };
+
+                uint exclusive = (uint)(LoanPayFlags.tfLoanOverpayment | LoanPayFlags.tfLoanFullPayment | LoanPayFlags.tfLoanLatePayment);
+                uint selected = rawFlags & exclusive;
+                if (selected != 0 && (selected & (selected - 1)) != 0)
+                    throw new ValidationException("LoanPay: mutually exclusive flags cannot be combined");
+            }
         }

Also applies to: 110-119

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/LoanPay.cs` around lines 14 - 17, The enum
LoanPayFlags documents mutually exclusive flags but ValidateLoanPay currently
does not enforce that only one of tfLoanOverpayment, tfLoanFullPayment, and
tfLoanLatePayment may be set; update ValidateLoanPay to read the LoanPayFlags
bitmask (use the tfLoanOverpayment, tfLoanFullPayment, tfLoanLatePayment
symbols) and fail validation if more than one of those three bits is present
(e.g., count set bits or test pairwise combinations) so the method rejects
invalid combinations and returns/throws the existing validation error type.
Xrpl/Models/Transactions/XChainAddClaimAttestation.cs-45-48 (1)

45-48: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Destination is defined as required but never validated.

ValidateXChainAddClaimAttestation should reject payloads missing Destination.

Proposed fix
             if (!tx.TryGetValue("AttestationSignerAccount", out var signerAccount) || signerAccount is not string)
                 throw new ValidationException("XChainAddClaimAttestation: missing field AttestationSignerAccount");
 
+            if (!tx.TryGetValue("Destination", out var destination) || destination is not string)
+                throw new ValidationException("XChainAddClaimAttestation: missing field Destination");
+
             if (!tx.TryGetValue("OtherChainSource", out var source) || source is not string)
                 throw new ValidationException("XChainAddClaimAttestation: missing field OtherChainSource");

Also applies to: 188-190

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/XChainAddClaimAttestation.cs` around lines 45 - 48,
ValidateXChainAddClaimAttestation currently doesn't check that the
XChainAddClaimAttestation.Destination property is set; update the
ValidateXChainAddClaimAttestation method to explicitly reject payloads where
Destination is null, empty, or whitespace (throw or return the same validation
error mechanism used elsewhere). Also add the same presence check for the other
validator/validation block that handles the XChainAddClaimAttestation payload at
the second location referenced (lines 188-190) so both validation paths enforce
Destination is provided.
Xrpl/Models/Transactions/LoanBrokerCoverClawback.cs-12-14 (1)

12-14: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

ValidateLoanBrokerCoverClawback currently allows empty transaction-specific payloads.

The method should enforce “at least one of LoanBrokerID or Amount”.

Proposed fix
         public static async Task ValidateLoanBrokerCoverClawback(Dictionary<string, object> tx)
         {
             await Common.ValidateBaseTransaction(tx);
+
+            bool hasLoanBrokerId = tx.TryGetValue("LoanBrokerID", out var loanBrokerId) && loanBrokerId is string;
+            bool hasAmount = tx.TryGetValue("Amount", out var amount) && amount is not null;
+
+            if (!hasLoanBrokerId && !hasAmount)
+                throw new ValidationException("LoanBrokerCoverClawback: at least one of LoanBrokerID or Amount is required");
         }

Also applies to: 62-65

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/LoanBrokerCoverClawback.cs` around lines 12 - 14,
The ValidateLoanBrokerCoverClawback method currently permits empty transaction
payloads; update it (and the analogous check around lines 62-65) to require that
at least one of LoanBrokerID or Amount is provided: inside
ValidateLoanBrokerCoverClawback (and the duplicate validation block), check if
both LoanBrokerID is null/empty/zero and Amount is null/zero and, if so,
return/throw the existing validation error type with a clear message (e.g.,
"either LoanBrokerID or Amount must be specified") so the transaction is
rejected when both fields are absent.
Xrpl/Wallet/LoanSigningHelper.cs-163-166 (1)

163-166: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Compatibility check drops SigningPubKey, allowing invalid signature combinations.

In V2, two blobs with different SigningPubKey values can be treated as compatible even though signatures are over different preimages.

Proposed fix
             // Verify bodies match (excluding signatures)
+            string brokerSigningPubKey = brokerTx["SigningPubKey"]?.GetValue<string>();
+            string counterpartySigningPubKey = counterpartyTx["SigningPubKey"]?.GetValue<string>();
+            if (!string.Equals(brokerSigningPubKey, counterpartySigningPubKey, StringComparison.Ordinal))
+                throw new ValidationException("Incompatible LoanSet SigningPubKey values.");
+
             JsonObject brokerCanon = Canonicalize(brokerTx);
             JsonObject counterpartyCanon = Canonicalize(counterpartyTx);
@@
             JsonObject canon = tx.DeepClone().AsObject();
             canon.Remove("TxnSignature");
-            canon.Remove("SigningPubKey");
             canon.Remove("CounterpartySignature");
             return canon;

Also applies to: 238-243

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Wallet/LoanSigningHelper.cs` around lines 163 - 166, The compatibility
check using Canonicalize(brokerTx) and Canonicalize(counterpartyTx) currently
removes SigningPubKey, allowing different SigningPubKey values to pass; update
the comparison logic in the method that does JsonObject brokerCanon =
Canonicalize(brokerTx) / counterpartyCanon = Canonicalize(counterpartyTx) (and
the analogous block at 238-243) to also require identical SigningPubKey values
(or include SigningPubKey in the canonicalization result) before accepting the
two transactions as compatible, and throw the same ValidationException if
SigningPubKey differs.
Xrpl/Wallet/LoanSigningHelper.cs-210-211 (1)

210-211: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

BrokerSign silently overwrites SigningPubKey, which can invalidate an existing CounterpartySignature.

If the partial blob was prepared with a different broker key, this should fail fast instead of returning an invalid signed blob.

Proposed fix
-            tx["SigningPubKey"] = brokerWallet.PublicKey;
+            string existingSigningPubKey = tx["SigningPubKey"]?.GetValue<string>();
+            if (!string.IsNullOrEmpty(existingSigningPubKey) &&
+                !string.Equals(existingSigningPubKey, brokerWallet.PublicKey, StringComparison.Ordinal))
+            {
+                throw new ValidationException("Partially signed blob SigningPubKey does not match broker wallet.");
+            }
+            tx["SigningPubKey"] = brokerWallet.PublicKey;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Wallet/LoanSigningHelper.cs` around lines 210 - 211, BrokerSign
currently unconditionally sets tx["SigningPubKey"] = brokerWallet.PublicKey
which can silently invalidate an existing CounterpartySignature; change
BrokerSign in LoanSigningHelper so that if tx already contains "SigningPubKey"
and its value does not equal brokerWallet.PublicKey you fail fast (throw an
InvalidOperationException or return an error) instead of overwriting, otherwise
(if absent or equal) set tx["SigningPubKey"] to brokerWallet.PublicKey;
reference the BrokerSign method and the tx["SigningPubKey"] and
CounterpartySignature fields when making this change.
Xrpl/Models/Transactions/DelegateSet.cs-65-66 (1)

65-66: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Permissions validation should enforce array shape, not just non-null.

Right now scalar/object values pass validation even though this field is defined as a list.

Proposed fix
+using System.Text.Json;
@@
             if (!tx.TryGetValue("Permissions", out var perms) || perms is null)
                 throw new ValidationException("DelegateSet: missing field Permissions");
+            if (perms is not List<object> && perms is not JsonElement { ValueKind: JsonValueKind.Array })
+                throw new ValidationException("DelegateSet: invalid field Permissions");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/DelegateSet.cs` around lines 65 - 66, The current
check in DelegateSet (the tx.TryGetValue("Permissions", out var perms) branch)
only ensures Permissions is non-null; change it to require that perms is an
array (e.g., JArray or IEnumerable) and validate the element shape/type (reject
scalar/object values) before proceeding; if perms is not an array or contains
invalid items, throw a ValidationException with a clear message. Update the
Permissions validation logic in the DelegateSet class where
tx.TryGetValue("Permissions", ...) is used and ensure each element is validated
for the expected type/structure.
Tests/Xrpl.Tests/Integration/transactions/TestILoanBase.cs-35-38 (1)

35-38: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Final-result validator should not treat terQUEUED as success

For TransactionSummary (post-submit result), accepting terQUEUED can hide non-final or failed outcomes.

Proposed fix
-        if (res is not { Meta: { TransactionResult: "tesSUCCESS" or "terQUEUED" } })
+        if (res is not { Meta: { TransactionResult: "tesSUCCESS" } })
             throw new RippleException($"Final tx result is not success: {res.Meta?.TransactionResult}");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/Xrpl.Tests/Integration/transactions/TestILoanBase.cs` around lines 35 -
38, The validator ValidateResult currently treats both "tesSUCCESS" and
"terQUEUED" as success; change it to accept only the final success result
"tesSUCCESS". In ValidateResult, update the pattern check on
TransactionSummary.Meta.TransactionResult to only match "tesSUCCESS" and throw
RippleException with the actual TransactionResult when it is not "tesSUCCESS" to
surface non-final/queued outcomes; reference the ValidateResult method,
TransactionSummary.Meta.TransactionResult and RippleException when making the
change.
Tests/Xrpl.Tests/Integration/transactions/TestILoanBase.cs-177-184 (1)

177-184: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

V2 signing is not isolated per signer

Both signatures are derived from the same mutable dictionary instance. If Sign(...) mutates input, the second signature is no longer independent.

Proposed fix
-        Dictionary<string, object> brokerDict = JsonSerializer.Deserialize<Dictionary<string, object>>(
-            prepared.ToJsonString(), XrplJsonOptions.Default);
+        string preparedJson = prepared.ToJsonString();
+        Dictionary<string, object> brokerDict = JsonSerializer.Deserialize<Dictionary<string, object>>(
+            preparedJson, XrplJsonOptions.Default);
+        Dictionary<string, object> borrowerDict = JsonSerializer.Deserialize<Dictionary<string, object>>(
+            preparedJson, XrplJsonOptions.Default);
         SignatureResult brokerSig = brokerWallet.Sign(brokerDict);

         // Device B (borrower): signs as counterparty (adds CounterpartySignature)
-        SignatureResult counterpartySig = borrowerWallet.SignAsLoanCounterparty(brokerDict);
+        SignatureResult counterpartySig = borrowerWallet.SignAsLoanCounterparty(borrowerDict);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/Xrpl.Tests/Integration/transactions/TestILoanBase.cs` around lines 177
- 184, The test currently reuses the same mutable Dictionary<string, object>
(created via JsonSerializer.Deserialize(prepared.ToJsonString(),
XrplJsonOptions.Default)) for both brokerWallet.Sign and
borrowerWallet.SignAsLoanCounterparty which can cause the second sign to operate
on a mutated object; fix by ensuring V2 signing is isolated: create a fresh
independent dictionary for each signer (e.g. deserialize prepared.ToJsonString()
twice or deep-clone the brokerDict) so that brokerWallet.Sign and
borrowerWallet.SignAsLoanCounterparty each receive their own dictionary instance
and cannot interfere with one another.
Xrpl/Models/Transactions/LoanBrokerSet.cs-137-138 (1)

137-138: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validation blocks valid LoanBrokerSet modify flow

ValidateLoanBrokerSet currently requires VaultID for all cases. Per this model’s own contract, updates can be done via LoanBrokerID without creating a new broker. This can reject valid transactions.

Proposed fix
-            if (!tx.TryGetValue("VaultID", out var vaultId) || vaultId is not string)
-                throw new ValidationException("LoanBrokerSet: missing field VaultID");
+            var hasVaultId = tx.TryGetValue("VaultID", out var vaultId);
+            var hasLoanBrokerId = tx.TryGetValue("LoanBrokerID", out var loanBrokerId);
+
+            if (!hasVaultId && !hasLoanBrokerId)
+                throw new ValidationException("LoanBrokerSet: missing required field VaultID or LoanBrokerID");
+
+            if (hasVaultId && vaultId is not string)
+                throw new ValidationException("LoanBrokerSet: invalid field VaultID");
+
+            if (hasLoanBrokerId && loanBrokerId is not string)
+                throw new ValidationException("LoanBrokerSet: invalid field LoanBrokerID");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/LoanBrokerSet.cs` around lines 137 - 138,
ValidateLoanBrokerSet currently unconditionally requires VaultID which blocks
valid update transactions that reference an existing broker by LoanBrokerID;
change the validation in ValidateLoanBrokerSet to accept either a VaultID
(string) for new broker creation or a LoanBrokerID (string/appropriate type) for
updates: use tx.TryGetValue checks for both "VaultID" and "LoanBrokerID",
validate the present one(s) for correct type, and only throw a
ValidationException if neither field is present or if the provided field has the
wrong type, updating the exception message to indicate that either VaultID or
LoanBrokerID is required.
Tests/Xrpl.Tests/Integration/transactions/TestILoanBase.cs-232-237 (1)

232-237: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Single fixed delay before tx lookup is flaky

A hardcoded 5-second wait plus one lookup can intermittently fail under slower ledger close or queue timing.

Proposed fix
-        // Wait for ledger to close and get the result via tx lookup
+        // Poll tx lookup until metadata is available (bounded retries)
         string txHash = global::Xrpl.Utils.Hashes.HashLedger.HashSignedTx(txBlob);
-        await Task.Delay(5000);
-
-        TxRequest txReq = new TxRequest(txHash);
-        TransactionResponse txResponse = await client.Tx(txReq);
+        TxRequest txReq = new TxRequest(txHash);
+        TransactionResponse txResponse = null;
+        for (int i = 0; i < 15; i++)
+        {
+            txResponse = await client.Tx(txReq);
+            if (txResponse?.Meta != null) break;
+            await Task.Delay(1000);
+        }
+        if (txResponse?.Meta == null)
+            throw new RippleException($"LoanSet tx not validated in time: {txHash}");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/Xrpl.Tests/Integration/transactions/TestILoanBase.cs` around lines 232
- 237, The single fixed Task.Delay(5000) before doing the tx lookup is flaky;
replace it with a polling loop that repeatedly calls client.Tx(new
TxRequest(txHash)) (use txHash from
global::Xrpl.Utils.Hashes.HashLedger.HashSignedTx(txBlob)) with a short await
Task.Delay(pollInterval) between attempts until a valid TransactionResponse is
returned (e.g., response != null and contains the expected tx/validated fields)
or until a configurable timeout is reached; ensure you surface a clear failure
when the timeout elapses and avoid a single one-shot lookup.
Xrpl/Models/Transactions/XChainAddAccountCreateAttestation.cs-192-220 (1)

192-220: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing type/range checks for critical attestation fields

XChainAccountCreateCount and WasLockingChainSend are only null-checked. Invalid values (non-numeric count, chain flag other than 0/1) currently pass validation.

Proposed fix
-            if (!tx.TryGetValue("XChainAccountCreateCount", out var count) || count is null)
-                throw new ValidationException("XChainAddAccountCreateAttestation: missing field XChainAccountCreateCount");
+            if (!tx.TryGetValue("XChainAccountCreateCount", out var count) ||
+                count is not string countStr ||
+                !ulong.TryParse(countStr, out _))
+                throw new ValidationException("XChainAddAccountCreateAttestation: invalid field XChainAccountCreateCount");

...
-            if (!tx.TryGetValue("WasLockingChainSend", out var wasLocking) || wasLocking is null)
-                throw new ValidationException("XChainAddAccountCreateAttestation: missing field WasLockingChainSend");
+            if (!tx.TryGetValue("WasLockingChainSend", out var wasLocking) ||
+                !byte.TryParse(wasLocking?.ToString(), out var wasLockingByte) ||
+                (wasLockingByte != 0 && wasLockingByte != 1))
+                throw new ValidationException("XChainAddAccountCreateAttestation: invalid field WasLockingChainSend");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/XChainAddAccountCreateAttestation.cs` around lines
192 - 220, The current validation in XChainAddAccountCreateAttestation only
null-checks XChainAccountCreateCount and WasLockingChainSend allowing
non-numeric or out-of-range values; update the validation in the parsing block
to (1) ensure XChainAccountCreateCount is present and can be parsed to an
integer (use int.TryParse or convert from JSON number) and enforce a sensible
range (e.g., >= 0), throwing ValidationException with the same context if it
fails, and (2) ensure WasLockingChainSend is present and is either a boolean or
a numeric 0/1 (accept boolean true/false or parse int 0/1) and normalize it to a
bool, throwing ValidationException if it cannot be interpreted; adjust any
downstream uses of the parsed values in the XChainAddAccountCreateAttestation
processing to use the validated/converted int and bool variables.
🧹 Nitpick comments (1)
Tests/Xrpl.Tests/Integration/transactions/TestILedgerStateFix.cs (1)

49-55: ⚡ Quick win

Guard amendment-disabled environments to reduce integration flakiness.

This assertion is strict for environments where LedgerStateFix is unavailable. Consider treating amendment-disabled outcomes as Assert.Inconclusive(...) so the test signals environment mismatch instead of functional failure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/Xrpl.Tests/Integration/transactions/TestILedgerStateFix.cs` around
lines 49 - 55, The test's strict assertion on result.EngineResult should be
relaxed to treat amendment-disabled environments as inconclusive: before the
Assert.IsTrue on txResult, detect amendment-disabled outcomes (either by
checking for the node-specific engine result code that indicates the
LedgerStateFix amendment is disabled or by calling an existing helper like
IsAmendmentDisabledOutcome(txResult) / an env flag) and call
Assert.Inconclusive("LedgerStateFix amendment disabled in this environment")
when detected; otherwise proceed with the existing check in TestILedgerStateFix
using the txResult variable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f904a137-bead-428c-b4ae-7a3cda192082

📥 Commits

Reviewing files that changed from the base of the PR and between b57cd9a and 7ac8ba0.

📒 Files selected for processing (73)
  • .ci-config/rippled.cfg
  • .gitignore
  • Base/Xrpl.BinaryCodec/Types/NumberType.cs
  • Base/Xrpl.BinaryCodec/Types/XChainBridgeType.cs
  • CHANGES.md
  • DocFx/LendingProtocol-Guide.md
  • DocFx/LendingProtocol-Guide.ru.md
  • DocFx/Vault-Guide.md
  • DocFx/Vault-Guide.ru.md
  • DocFx/XChainBridge-Guide.md
  • DocFx/XChainBridge-Guide.ru.md
  • DocFx/toc.yml
  • Tests/Xrpl.BinaryCodec.Test/Types/TestLoanSetEncode.cs
  • Tests/Xrpl.BinaryCodec.Test/Types/TestNumberType.cs
  • Tests/Xrpl.BinaryCodec.Test/Types/TestXChainBridgeType.cs
  • Tests/Xrpl.Tests/Integration/transactions/TestIBatch.cs
  • Tests/Xrpl.Tests/Integration/transactions/TestIDelegateSet.cs
  • Tests/Xrpl.Tests/Integration/transactions/TestILedgerStateFix.cs
  • Tests/Xrpl.Tests/Integration/transactions/TestILoan.cs
  • Tests/Xrpl.Tests/Integration/transactions/TestILoanBase.cs
  • Tests/Xrpl.Tests/Integration/transactions/TestIVault.cs
  • Tests/Xrpl.Tests/Integration/transactions/TestIVaultBase.cs
  • Tests/Xrpl.Tests/Integration/transactions/TestIXChainBridge.cs
  • Tests/Xrpl.Tests/Integration/transactions/TestIXChainBridgeBase.cs
  • Xrpl/Client/Exceptions/XrplErrorClassifier.cs
  • Xrpl/Client/Json/Converters/LedgerObjectConverter.cs
  • Xrpl/Client/Json/Converters/TransactionRequestConverter.cs
  • Xrpl/Client/Json/Converters/TransactionResponseConverter.cs
  • Xrpl/Client/RequestManager.cs
  • Xrpl/Models/Common/PermissionModel.cs
  • Xrpl/Models/Common/XChainAttestations.cs
  • Xrpl/Models/Common/XChainBridgeModel.cs
  • Xrpl/Models/Enums/LedgerEntryType.cs
  • Xrpl/Models/Enums/TransactionType.cs
  • Xrpl/Models/Ledger/LOBridge.cs
  • Xrpl/Models/Ledger/LODelegate.cs
  • Xrpl/Models/Ledger/LOLoan.cs
  • Xrpl/Models/Ledger/LOLoanBroker.cs
  • Xrpl/Models/Ledger/LOVault.cs
  • Xrpl/Models/Ledger/LOXChainOwnedClaimID.cs
  • Xrpl/Models/Ledger/LOXChainOwnedCreateAccountClaimID.cs
  • Xrpl/Models/Methods/AccountObjects.cs
  • Xrpl/Models/Methods/AccountOffers.cs
  • Xrpl/Models/Transactions/DelegateSet.cs
  • Xrpl/Models/Transactions/LedgerStateFix.cs
  • Xrpl/Models/Transactions/LoanBrokerCoverClawback.cs
  • Xrpl/Models/Transactions/LoanBrokerCoverDeposit.cs
  • Xrpl/Models/Transactions/LoanBrokerCoverWithdraw.cs
  • Xrpl/Models/Transactions/LoanBrokerDelete.cs
  • Xrpl/Models/Transactions/LoanBrokerSet.cs
  • Xrpl/Models/Transactions/LoanDelete.cs
  • Xrpl/Models/Transactions/LoanManage.cs
  • Xrpl/Models/Transactions/LoanPay.cs
  • Xrpl/Models/Transactions/LoanSet.cs
  • Xrpl/Models/Transactions/TxFormat.cs
  • Xrpl/Models/Transactions/Validation.cs
  • Xrpl/Models/Transactions/VaultClawback.cs
  • Xrpl/Models/Transactions/VaultCreate.cs
  • Xrpl/Models/Transactions/VaultDelete.cs
  • Xrpl/Models/Transactions/VaultDeposit.cs
  • Xrpl/Models/Transactions/VaultSet.cs
  • Xrpl/Models/Transactions/VaultWithdraw.cs
  • Xrpl/Models/Transactions/XChainAccountCreateCommit.cs
  • Xrpl/Models/Transactions/XChainAddAccountCreateAttestation.cs
  • Xrpl/Models/Transactions/XChainAddClaimAttestation.cs
  • Xrpl/Models/Transactions/XChainClaim.cs
  • Xrpl/Models/Transactions/XChainCommit.cs
  • Xrpl/Models/Transactions/XChainCreateBridge.cs
  • Xrpl/Models/Transactions/XChainCreateClaimID.cs
  • Xrpl/Models/Transactions/XChainModifyBridge.cs
  • Xrpl/Sugar/Autofill.cs
  • Xrpl/Wallet/LoanSigningHelper.cs
  • Xrpl/Wallet/XrplWallet.cs

@Platonenkov
Copy link
Copy Markdown
Collaborator Author

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🟠 Major comments (24)

Base/Xrpl.BinaryCodec/Types/XChainBridgeType.cs-65-69 (1)> 65-69: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate AccountID VL lengths before constructing AccountId.
FromParser reads lockingDoorLen / issuingDoorLen but never enforces the expected 20-byte AccountID length. A malformed buffer can be accepted and misparsed. Please reject non-20 lengths at parse time (Line 65 and Line 68).

🔧 Proposed fix

 public static XChainBridgeType FromParser(BinaryParser parser, int? hint = null)
 {
     int lockingDoorLen = parser.ReadVlLength();
+    if (lockingDoorLen != 20)
+        throw new FormatException($"Invalid LockingChainDoor length: {lockingDoorLen}. Expected 20.");
     AccountId lockingDoor = new AccountId(parser.Read(lockingDoorLen));
     Issue lockingIssue = Issue.FromParser(parser);
     int issuingDoorLen = parser.ReadVlLength();
+    if (issuingDoorLen != 20)
+        throw new FormatException($"Invalid IssuingChainDoor length: {issuingDoorLen}. Expected 20.");
     AccountId issuingDoor = new AccountId(parser.Read(issuingDoorLen));
     Issue issuingIssue = Issue.FromParser(parser);

     return new XChainBridgeType(lockingDoor, lockingIssue, issuingDoor, issuingIssue);
 }

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Base/Xrpl.BinaryCodec/Types/XChainBridgeType.cs` around lines 65 - 69, The
code reads variable-length AccountID fields into lockingDoorLen and
issuingDoorLen and constructs new AccountId without validating their length, so
update the parsing logic in the XChainBridgeType parsing block (where
lockingDoorLen, lockingDoor, Issue.FromParser, issuingDoorLen, issuingDoor are
handled) to check parser.ReadVlLength() returns exactly 20 bytes before calling
new AccountId(parser.Read(...)); if the length is not 20, throw a parsing/format
exception (or return an error) to reject the malformed buffer rather than
constructing an AccountId from an incorrect-length byte array.

Xrpl/Client/RequestManager.cs-352-357 (1)> 352-357: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Include ErrorException in the no-status error gate.
On Line 352, responses with only ErrorException currently bypass this branch and get downgraded to generic "Response has no status".

💡 Suggested fix

-                if (response.Error is not null || response.ErrorMessage is not null)
+                if (response.Error is not null || response.ErrorMessage is not null || response.ErrorException is not null)
                 {
-                    string detail = response.ErrorMessage ?? response.ErrorException;
+                    string detail = response.ErrorMessage ?? response.ErrorException ?? "Unknown error";
                     var errMessage = response.Error is null
                         ? detail
                         : $"{response.Error} - {detail}";

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Client/RequestManager.cs` around lines 352 - 357, The branch that builds
an error message currently only checks response.Error and response.ErrorMessage,
causing responses with only response.ErrorException to fall through to the "no
status" gate; update the conditional in RequestManager (where response is
evaluated) to include response.ErrorException (i.e., if response.Error is not
null || response.ErrorMessage is not null || response.ErrorException is not
null) and keep constructing detail with response.ErrorMessage ??
response.ErrorException and errMessage using response.Error when present so that
ErrorException-only failures are handled and logged like other errors.

Xrpl/Models/Transactions/LoanSet.cs-292-296 (1)> 292-296: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

ValidateLoanSet is too permissive for required string fields.
At Lines 292–296, PrincipalRequested only checks null (not string/non-empty), and LoanBrokerID allows empty whitespace strings. This lets invalid tx payloads pass validation.

Suggested fix

-            if (!tx.TryGetValue("LoanBrokerID", out var brokerId) || brokerId is not string)
-                throw new ValidationException("LoanSet: missing field LoanBrokerID");
+            if (!tx.TryGetValue("LoanBrokerID", out var brokerId)
+                || brokerId is not string brokerIdText
+                || string.IsNullOrWhiteSpace(brokerIdText))
+                throw new ValidationException("LoanSet: missing or invalid field LoanBrokerID");

-            if (!tx.TryGetValue("PrincipalRequested", out var principal) || principal is null)
-                throw new ValidationException("LoanSet: missing field PrincipalRequested");
+            if (!tx.TryGetValue("PrincipalRequested", out var principal)
+                || principal is not string principalText
+                || string.IsNullOrWhiteSpace(principalText))
+                throw new ValidationException("LoanSet: missing or invalid field PrincipalRequested");

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/LoanSet.cs` around lines 292 - 296, The
ValidateLoanSet validator is too permissive: update the checks for
"LoanBrokerID" and "PrincipalRequested" so they require non-empty strings (not
just presence or non-null). Specifically, in the ValidateLoanSet method, change
the LoanBrokerID check to ensure tx.TryGetValue("LoanBrokerID", out var
brokerId) && brokerId is string s && !string.IsNullOrWhiteSpace(s)
(trim/validate non-empty) and change the PrincipalRequested check to ensure
tx.TryGetValue("PrincipalRequested", out var principal) && principal is string p
&& !string.IsNullOrWhiteSpace(p); if either fails, throw the existing
ValidationException with the same field-specific message.

Tests/Xrpl.BinaryCodec.Test/Types/TestLoanSetEncode.cs-28-42 (1)> 28-42: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add assertions; current test is observational-only.
Lines 28-42 only print values, so the test passes even when encoding/decoding regresses.

Suggested fix

         string encoded = XrplBinaryCodec.Encode(node);
-        Console.WriteLine("Encoded hex: " + encoded);
-        Console.WriteLine("Encoded length: " + encoded.Length / 2 + " bytes");
+        Assert.IsFalse(string.IsNullOrWhiteSpace(encoded), "Encoded hex should not be empty.");

         JsonNode decodedNode = XrplBinaryCodec.Decode(encoded);
-        string decoded = decodedNode.ToJsonString(new JsonSerializerOptions { WriteIndented = true });
-        Console.WriteLine("Decoded JSON: " + decoded);
+        Assert.IsNotNull(decodedNode, "Decoded node should not be null.");
+        Assert.AreEqual("LoanSet", decodedNode["TransactionType"]?.ToString());
+        Assert.AreEqual("10000000000000", decodedNode["PrincipalRequested"]?.ToString());

         string principalDecoded = decodedNode["PrincipalRequested"]?.ToString();
-        Console.WriteLine("PrincipalRequested decoded: " + principalDecoded);
+        Assert.AreEqual("10000000000000", principalDecoded);

-        // Also encode for signing
         string forSigning = XrplBinaryCodec.EncodeForSigning(node);
-        Console.WriteLine("ForSigning hex: " + forSigning);
+        Assert.IsFalse(string.IsNullOrWhiteSpace(forSigning), "Signing payload should not be empty.");

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/Xrpl.BinaryCodec.Test/Types/TestLoanSetEncode.cs` around lines 28 - 42,
The test currently only prints values; make it assert encoding/decoding
correctness by adding assertions: use XrplBinaryCodec.Encode(node) and
Assert.NotNull/NotEmpty on the resulting string, Assert.Equal on the decoded
JSON or on specific fields to verify round-trip (e.g., compare
decodedNode["PrincipalRequested"] to the original node's PrincipalRequested),
and Assert.Equal for the byte length if you have an expected length; also assert
that XrplBinaryCodec.EncodeForSigning(node) produces a non-empty string and, if
available, matches an expected signing hex or at least differs from the regular
Encode output. Use the test framework's Assert methods (e.g., Assert.Equal,
Assert.NotNull/NotEmpty) to replace the Console.WriteLine checks around
XrplBinaryCodec.Encode, Decode, decodedNode and EncodeForSigning.

Base/Xrpl.BinaryCodec/Types/NumberType.cs-57-69 (1)> 57-69: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

ToJson() will throw OverflowException for large protocol-valid exponents.
Lines 57-69 apply repeated decimal multiplication/division based on Exponent (which permits magnitudes up to ±32768), but System.Decimal has a maximum magnitude of ~7.92 × 10^28. A mantissa near long.MaxValue (≈9.22 × 10^18) multiplied by 10 even 11 times exceeds this limit and throws OverflowException at runtime. The protocol allows exponent magnitudes far beyond what decimal can handle, and there is no overflow handling.
A safer approach is string-based formatting from (Mantissa, Exponent) instead of decimal scaling, or use BigInteger for arbitrary precision arithmetic.

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Base/Xrpl.BinaryCodec/Types/NumberType.cs` around lines 57 - 69, ToJson
currently scales a System.Decimal by looping on Mantissa and Exponent which can
overflow for protocol-allowed exponents; replace that decimal-scaling logic in
ToJson with a string-based conversion (or BigInteger-backed) that builds the
numeric string from Mantissa and Exponent without performing repeated decimal
multiplication/division. Specifically, in ToJson use Mantissa and Exponent to
compute sign, absMantissa (as BigInteger or string), then shift the decimal
point by Exponent to produce a correctly placed integer/decimal or
scientific-style string (handling large positive/negative exponents and trimming
trailing zeros) and return that string instead of using System.Decimal; update
any references to Mantissa and Exponent in NumberType.ToJson accordingly.

Xrpl/Models/Transactions/TxFormat.cs-553-571 (1)> 553-571: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

LoanSet format is missing CounterpartySignature.
Given LoanSet co-signing support, omitting this field from TxFormat can cause validation failures for otherwise valid signed LoanSet transactions.

💡 Proposed fix

                 [BinaryCodec.Types.TransactionType.LoanSet] = new TxFormat
                 {
                     [Field.LoanBrokerID] = Requirement.Required,
                     [Field.PrincipalRequested] = Requirement.Required,
                     [Field.Counterparty] = Requirement.Optional,
+                    [Field.CounterpartySignature] = Requirement.Optional,
                     [Field.InterestRate] = Requirement.Optional,
                     [Field.LateInterestRate] = Requirement.Optional,
                     [Field.CloseInterestRate] = Requirement.Optional,

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/TxFormat.cs` around lines 553 - 571, The LoanSet
TxFormat (BinaryCodec.Types.TransactionType.LoanSet) is missing the
CounterpartySignature field which breaks validation for co-signed LoanSet
transactions; update the TxFormat initializer for LoanSet (the object assigned
to BinaryCodec.Types.TransactionType.LoanSet) to include
Field.CounterpartySignature with an appropriate requirement (e.g.,
Requirement.Optional) alongside the other fields so co-signed LoanSet
transactions validate correctly.

Xrpl/Sugar/Autofill.cs-324-327 (1)> 324-327: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reserve-fee transactions can be incorrectly capped after adding LedgerStateFix.
Line 324 extends reserve-fee detection, but cap bypass still only checks AccountDelete (Line 209). With a low maxFeeXRP, LedgerStateFix can be autofilled with insufficient fee and fail submission.

💡 Proposed fix

-                totalFee = transactionType == "AccountDelete"
+                totalFee = IsReserveFeeTxNeed(tx)
                     ? calculatedFee
                     : BigInteger.Min(calculatedFee, maxFeeBI);

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Sugar/Autofill.cs` around lines 324 - 327, The reserve-fee detection was
expanded to include LedgerStateFix but the fee-cap bypass logic still only
treats AccountDelete as exempt; update the cap-bypass check (the conditional
that currently tests for nameof(TransactionType.AccountDelete)) to also allow
nameof(TransactionType.LedgerStateFix) so LedgerStateFix transactions bypass the
maxFeeXRP cap the same way; locate the comparison that checks for AccountDelete
and extend it to include LedgerStateFix (or refactor both places to call the
same IsReserveFeeTransaction helper) to keep behavior consistent.

DocFx/LendingProtocol-Guide.md-435-448 (1)> 435-448: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Resolve contradictory LoanSet fee guidance.
Line 275 says Autofill already includes CounterpartySignature overhead, but Lines 444-448 instruct users to triple the fee after autofill. These instructions conflict and can lead to systematic overpayment.

Suggested fix

-| `telINSUF_FEE_P` | Fee too low after adding CounterpartySignature | Triple the fee after autofill to account for extra signature bytes |
+| `telINSUF_FEE_P` | Fee too low after adding CounterpartySignature | Re-run Autofill on the final pre-signed payload and verify the computed fee before submit |
...
-2. **Triple the fee for LoanSet** — after autofilling, multiply the fee by 3 to account for the `CounterpartySignature` overhead:
-   ```csharp
-   ulong feeDrops = ulong.Parse(loanTx.Fee.Value);
-   loanTx.Fee = new Currency { Value = (feeDrops * 3).ToString(), CurrencyCode = "XRP" };
-   ```
+2. **Use Autofill for final fee calculation** — ensure the transaction includes the signing fields expected by your flow, then call Autofill and submit with the returned fee.

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DocFx/LendingProtocol-Guide.md` around lines 435 - 448, The docs conflict:
one place states Autofill includes CounterpartySignature overhead while another
instructs developers to manually triple loanTx.Fee after Autofill; remove the
"triple the fee" snippet and replace it with guidance to rely on Autofill's
returned fee (i.e., call Autofill after you have added any expected signing
fields such as CounterpartySignature or other signing fields, then submit using
the fee value returned in loanTx.Fee), and update the LoanSet guidance to
reference using Autofill for final fee calculation instead of multiplying
loanTx.Fee by 3.

Xrpl/Wallet/XrplWallet.cs-1023-1033 (1)> 1023-1033: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Require broker SigningPubKey before generating CounterpartySignature.
SignAsLoanCounterparty signs whatever preimage is present, but it doesn't enforce that SigningPubKey is already set (broker key). If missing, you produce a counterparty signature over the wrong preimage and the combined LoanSet becomes invalid.

Suggested fix

             string txType = tx["TransactionType"]?.GetValue<string>();
             if (!string.Equals(txType, "LoanSet", StringComparison.OrdinalIgnoreCase))
                 throw new ValidationException($"SignAsLoanCounterparty requires TransactionType=LoanSet, got: {txType}");
+
+            var brokerSigningPubKey = tx["SigningPubKey"]?.GetValue<string>();
+            if (string.IsNullOrWhiteSpace(brokerSigningPubKey))
+                throw new ValidationException("LoanSet must include broker SigningPubKey before counterparty signing.");

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Wallet/XrplWallet.cs` around lines 1023 - 1033, The
SignAsLoanCounterparty flow currently computes a signing preimage without
ensuring the broker's SigningPubKey is present, causing invalid
CounterpartySignature; in SignAsLoanCounterparty (XrplWallet.cs) check that
tx["SigningPubKey"] (the broker's public key) exists and is non-empty before
removing existing signatures and before calling
LoanSigningHelper.GetSigningPreimage(tx), and if it's missing throw a
ValidationException with a clear message (e.g., "SignAsLoanCounterparty requires
broker SigningPubKey to be set"); this ensures you sign the correct preimage.

Xrpl/Models/Transactions/VaultWithdraw.cs-53-55 (1)> 53-55: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Apply CurrencyConverter to Amount for VaultWithdraw.
Amount is a Currency field but lacks JsonConverter(typeof(CurrencyConverter)) here, unlike other transaction models with Currency payloads.

Suggested patch

+using Xrpl.Client.Json.Converters;
 using Xrpl.Models.Common;
@@
         /// <inheritdoc />
         [JsonPropertyName("Amount")]
+        [JsonConverter(typeof(CurrencyConverter))]
         public Currency Amount { get; set; }
@@
         /// <inheritdoc />
         [JsonPropertyName("Amount")]
+        [JsonConverter(typeof(CurrencyConverter))]
         public Currency Amount { get; set; }

Also applies to: 73-75

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/VaultWithdraw.cs` around lines 53 - 55, The
VaultWithdraw model's Amount property(s) are missing the JSON converter
attribute; add [JsonConverter(typeof(CurrencyConverter))] to each Currency-typed
Amount property in the VaultWithdraw class so they serialize/deserialize like
other transaction models that use CurrencyConverter (apply this to the primary
Amount property and the second Amount declaration later in the VaultWithdraw
class).

Xrpl/Models/Transactions/LedgerStateFix.cs-57-63 (1)> 57-63: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add required Owner validation in LedgerStateFix validator.
Line 61 only validates LedgerFixType. Owner is part of ILedgerStateFix and currently passes through unchecked.

Suggested patch

 public static async Task ValidateLedgerStateFix(Dictionary<string, object> tx)
 {
     await Common.ValidateBaseTransaction(tx);

     if (!tx.TryGetValue("LedgerFixType", out var fixType) || fixType is null)
         throw new ValidationException("LedgerStateFix: missing field LedgerFixType");
+
+    if (!tx.TryGetValue("Owner", out var owner) || owner is not string s || string.IsNullOrWhiteSpace(s))
+        throw new ValidationException("LedgerStateFix: missing field Owner");
 }

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/LedgerStateFix.cs` around lines 57 - 63, The
LedgerStateFix validator (ValidateLedgerStateFix) currently only checks
LedgerFixType but must also validate the required Owner field from
ILedgerStateFix: add a check that tx.TryGetValue("Owner", out var owner)
succeeds and owner is a non-null, non-empty string, and then validate the owner
value using the project account/address validator (e.g. call
Common.ValidateAccount(owner) or the equivalent helper in Common); if missing or
invalid, throw a ValidationException with a clear message (e.g. "LedgerStateFix:
missing or invalid field Owner").

Xrpl/Models/Transactions/LoanManage.cs-93-99 (1)> 93-99: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Enforce LoanManage action-flag semantics in validation.
Line 97 validates only LoanID; it doesn’t enforce that exactly one of tfLoanDefault/tfLoanImpair/tfLoanUnimpair is selected.

Suggested patch

 public static async Task ValidateLoanManage(Dictionary<string, object> tx)
 {
     await Common.ValidateBaseTransaction(tx);

     if (!tx.TryGetValue("LoanID", out var id) || id is not string)
         throw new ValidationException("LoanManage: missing field LoanID");
+
+    if (!tx.TryGetValue("Flags", out var rawFlags) || rawFlags is null)
+        throw new ValidationException("LoanManage: missing field Flags");
+
+    uint flagsValue = rawFlags switch
+    {
+        uint u => u,
+        int i when i >= 0 => (uint)i,
+        long l when l is >= 0 and <= uint.MaxValue => (uint)l,
+        _ => throw new ValidationException("LoanManage: invalid field Flags")
+    };
+
+    LoanManageFlags flags = (LoanManageFlags)flagsValue;
+    int actionCount =
+        ((flags & LoanManageFlags.tfLoanDefault) != 0 ? 1 : 0) +
+        ((flags & LoanManageFlags.tfLoanImpair) != 0 ? 1 : 0) +
+        ((flags & LoanManageFlags.tfLoanUnimpair) != 0 ? 1 : 0);
+
+    if (actionCount != 1)
+        throw new ValidationException("LoanManage: exactly one action flag must be set");
 }

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/LoanManage.cs` around lines 93 - 99,
ValidateLoanManage currently only checks LoanID; update it to also enforce that
exactly one action flag is set among tfLoanDefault, tfLoanImpair,
tfLoanUnimpair. In ValidateLoanManage, after verifying LoanID, read the three
keys from tx (e.g., using tx.TryGetValue for "tfLoanDefault", "tfLoanImpair",
"tfLoanUnimpair"), count how many evaluate as set/true (handle boolean or
numeric truthy values), and if the count != 1 throw a ValidationException with a
clear message like "LoanManage: must set exactly one of tfLoanDefault,
tfLoanImpair, tfLoanUnimpair"; keep the existing LoanID check and
Common.ValidateBaseTransaction call.

Xrpl/Models/Transactions/VaultClawback.cs-47-48 (1)> 47-48: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Amount is missing CurrencyConverter, which can break XRPL amount JSON encoding.
Please align with other transaction models and explicitly apply CurrencyConverter to both request and response Amount fields.

Proposed fix

 using Xrpl.Client.Exceptions;
+using Xrpl.Client.Json.Converters;
 using Xrpl.Models.Common;
@@
         [JsonPropertyName("Amount")]
+        [JsonConverter(typeof(CurrencyConverter))]
         public Currency Amount { get; set; }
@@
         [JsonPropertyName("Amount")]
+        [JsonConverter(typeof(CurrencyConverter))]
         public Currency Amount { get; set; }

Also applies to: 63-64

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/VaultClawback.cs` around lines 47 - 48, The Amount
property in the VaultClawback model lacks the CurrencyConverter attribute and
must mirror other transaction models; add the JsonConverter attribute
referencing CurrencyConverter to the Amount properties in VaultClawback (both
occurrences — the request and response Amount fields) so JSON (de)serialization
uses CurrencyConverter for XRPL amount encoding/decoding; update the Amount
property declarations in the VaultClawback class to include
[JsonConverter(typeof(CurrencyConverter))].

Xrpl/Models/Transactions/LoanPay.cs-14-17 (1)> 14-17: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Mutually exclusive LoanPay flags are documented but never enforced.
ValidateLoanPay should reject combinations of tfLoanOverpayment, tfLoanFullPayment, and tfLoanLatePayment.

Proposed fix

 using System;
 using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
 using System.Threading.Tasks;
@@
         public static async Task ValidateLoanPay(Dictionary<string, object> tx)
         {
             await Common.ValidateBaseTransaction(tx);
@@
             if (!tx.TryGetValue("Amount", out var amount) || amount is null)
                 throw new ValidationException("LoanPay: missing field Amount");
+
+            if (tx.TryGetValue("Flags", out var flagsObj) && flagsObj is not null)
+            {
+                uint rawFlags = flagsObj switch
+                {
+                    uint u => u,
+                    int i => checked((uint)i),
+                    long l => checked((uint)l),
+                    JsonElement { ValueKind: JsonValueKind.Number } je when je.TryGetUInt32(out var u) => u,
+                    _ => throw new ValidationException("LoanPay: invalid field Flags"),
+                };
+
+                uint exclusive = (uint)(LoanPayFlags.tfLoanOverpayment | LoanPayFlags.tfLoanFullPayment | LoanPayFlags.tfLoanLatePayment);
+                uint selected = rawFlags & exclusive;
+                if (selected != 0 && (selected & (selected - 1)) != 0)
+                    throw new ValidationException("LoanPay: mutually exclusive flags cannot be combined");
+            }
         }

Also applies to: 110-119

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/LoanPay.cs` around lines 14 - 17, The enum
LoanPayFlags documents mutually exclusive flags but ValidateLoanPay currently
does not enforce that only one of tfLoanOverpayment, tfLoanFullPayment, and
tfLoanLatePayment may be set; update ValidateLoanPay to read the LoanPayFlags
bitmask (use the tfLoanOverpayment, tfLoanFullPayment, tfLoanLatePayment
symbols) and fail validation if more than one of those three bits is present
(e.g., count set bits or test pairwise combinations) so the method rejects
invalid combinations and returns/throws the existing validation error type.

Xrpl/Models/Transactions/XChainAddClaimAttestation.cs-45-48 (1)> 45-48: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Destination is defined as required but never validated.
ValidateXChainAddClaimAttestation should reject payloads missing Destination.

Proposed fix

             if (!tx.TryGetValue("AttestationSignerAccount", out var signerAccount) || signerAccount is not string)
                 throw new ValidationException("XChainAddClaimAttestation: missing field AttestationSignerAccount");
 
+            if (!tx.TryGetValue("Destination", out var destination) || destination is not string)
+                throw new ValidationException("XChainAddClaimAttestation: missing field Destination");
+
             if (!tx.TryGetValue("OtherChainSource", out var source) || source is not string)
                 throw new ValidationException("XChainAddClaimAttestation: missing field OtherChainSource");

Also applies to: 188-190

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/XChainAddClaimAttestation.cs` around lines 45 - 48,
ValidateXChainAddClaimAttestation currently doesn't check that the
XChainAddClaimAttestation.Destination property is set; update the
ValidateXChainAddClaimAttestation method to explicitly reject payloads where
Destination is null, empty, or whitespace (throw or return the same validation
error mechanism used elsewhere). Also add the same presence check for the other
validator/validation block that handles the XChainAddClaimAttestation payload at
the second location referenced (lines 188-190) so both validation paths enforce
Destination is provided.

Xrpl/Models/Transactions/LoanBrokerCoverClawback.cs-12-14 (1)> 12-14: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

ValidateLoanBrokerCoverClawback currently allows empty transaction-specific payloads.
The method should enforce “at least one of LoanBrokerID or Amount”.

Proposed fix

         public static async Task ValidateLoanBrokerCoverClawback(Dictionary<string, object> tx)
         {
             await Common.ValidateBaseTransaction(tx);
+
+            bool hasLoanBrokerId = tx.TryGetValue("LoanBrokerID", out var loanBrokerId) && loanBrokerId is string;
+            bool hasAmount = tx.TryGetValue("Amount", out var amount) && amount is not null;
+
+            if (!hasLoanBrokerId && !hasAmount)
+                throw new ValidationException("LoanBrokerCoverClawback: at least one of LoanBrokerID or Amount is required");
         }

Also applies to: 62-65

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/LoanBrokerCoverClawback.cs` around lines 12 - 14,
The ValidateLoanBrokerCoverClawback method currently permits empty transaction
payloads; update it (and the analogous check around lines 62-65) to require that
at least one of LoanBrokerID or Amount is provided: inside
ValidateLoanBrokerCoverClawback (and the duplicate validation block), check if
both LoanBrokerID is null/empty/zero and Amount is null/zero and, if so,
return/throw the existing validation error type with a clear message (e.g.,
"either LoanBrokerID or Amount must be specified") so the transaction is
rejected when both fields are absent.

Xrpl/Wallet/LoanSigningHelper.cs-163-166 (1)> 163-166: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Compatibility check drops SigningPubKey, allowing invalid signature combinations.
In V2, two blobs with different SigningPubKey values can be treated as compatible even though signatures are over different preimages.

Proposed fix

             // Verify bodies match (excluding signatures)
+            string brokerSigningPubKey = brokerTx["SigningPubKey"]?.GetValue<string>();
+            string counterpartySigningPubKey = counterpartyTx["SigningPubKey"]?.GetValue<string>();
+            if (!string.Equals(brokerSigningPubKey, counterpartySigningPubKey, StringComparison.Ordinal))
+                throw new ValidationException("Incompatible LoanSet SigningPubKey values.");
+
             JsonObject brokerCanon = Canonicalize(brokerTx);
             JsonObject counterpartyCanon = Canonicalize(counterpartyTx);
@@
             JsonObject canon = tx.DeepClone().AsObject();
             canon.Remove("TxnSignature");
-            canon.Remove("SigningPubKey");
             canon.Remove("CounterpartySignature");
             return canon;

Also applies to: 238-243

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Wallet/LoanSigningHelper.cs` around lines 163 - 166, The compatibility
check using Canonicalize(brokerTx) and Canonicalize(counterpartyTx) currently
removes SigningPubKey, allowing different SigningPubKey values to pass; update
the comparison logic in the method that does JsonObject brokerCanon =
Canonicalize(brokerTx) / counterpartyCanon = Canonicalize(counterpartyTx) (and
the analogous block at 238-243) to also require identical SigningPubKey values
(or include SigningPubKey in the canonicalization result) before accepting the
two transactions as compatible, and throw the same ValidationException if
SigningPubKey differs.

Xrpl/Wallet/LoanSigningHelper.cs-210-211 (1)> 210-211: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

BrokerSign silently overwrites SigningPubKey, which can invalidate an existing CounterpartySignature.
If the partial blob was prepared with a different broker key, this should fail fast instead of returning an invalid signed blob.

Proposed fix

-            tx["SigningPubKey"] = brokerWallet.PublicKey;
+            string existingSigningPubKey = tx["SigningPubKey"]?.GetValue<string>();
+            if (!string.IsNullOrEmpty(existingSigningPubKey) &&
+                !string.Equals(existingSigningPubKey, brokerWallet.PublicKey, StringComparison.Ordinal))
+            {
+                throw new ValidationException("Partially signed blob SigningPubKey does not match broker wallet.");
+            }
+            tx["SigningPubKey"] = brokerWallet.PublicKey;

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Wallet/LoanSigningHelper.cs` around lines 210 - 211, BrokerSign
currently unconditionally sets tx["SigningPubKey"] = brokerWallet.PublicKey
which can silently invalidate an existing CounterpartySignature; change
BrokerSign in LoanSigningHelper so that if tx already contains "SigningPubKey"
and its value does not equal brokerWallet.PublicKey you fail fast (throw an
InvalidOperationException or return an error) instead of overwriting, otherwise
(if absent or equal) set tx["SigningPubKey"] to brokerWallet.PublicKey;
reference the BrokerSign method and the tx["SigningPubKey"] and
CounterpartySignature fields when making this change.

Xrpl/Models/Transactions/DelegateSet.cs-65-66 (1)> 65-66: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Permissions validation should enforce array shape, not just non-null.
Right now scalar/object values pass validation even though this field is defined as a list.

Proposed fix

+using System.Text.Json;
@@
             if (!tx.TryGetValue("Permissions", out var perms) || perms is null)
                 throw new ValidationException("DelegateSet: missing field Permissions");
+            if (perms is not List<object> && perms is not JsonElement { ValueKind: JsonValueKind.Array })
+                throw new ValidationException("DelegateSet: invalid field Permissions");

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/DelegateSet.cs` around lines 65 - 66, The current
check in DelegateSet (the tx.TryGetValue("Permissions", out var perms) branch)
only ensures Permissions is non-null; change it to require that perms is an
array (e.g., JArray or IEnumerable) and validate the element shape/type (reject
scalar/object values) before proceeding; if perms is not an array or contains
invalid items, throw a ValidationException with a clear message. Update the
Permissions validation logic in the DelegateSet class where
tx.TryGetValue("Permissions", ...) is used and ensure each element is validated
for the expected type/structure.

Tests/Xrpl.Tests/Integration/transactions/TestILoanBase.cs-35-38 (1)> 35-38: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Final-result validator should not treat terQUEUED as success
For TransactionSummary (post-submit result), accepting terQUEUED can hide non-final or failed outcomes.

Proposed fix

-        if (res is not { Meta: { TransactionResult: "tesSUCCESS" or "terQUEUED" } })
+        if (res is not { Meta: { TransactionResult: "tesSUCCESS" } })
             throw new RippleException($"Final tx result is not success: {res.Meta?.TransactionResult}");

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/Xrpl.Tests/Integration/transactions/TestILoanBase.cs` around lines 35 -
38, The validator ValidateResult currently treats both "tesSUCCESS" and
"terQUEUED" as success; change it to accept only the final success result
"tesSUCCESS". In ValidateResult, update the pattern check on
TransactionSummary.Meta.TransactionResult to only match "tesSUCCESS" and throw
RippleException with the actual TransactionResult when it is not "tesSUCCESS" to
surface non-final/queued outcomes; reference the ValidateResult method,
TransactionSummary.Meta.TransactionResult and RippleException when making the
change.

Tests/Xrpl.Tests/Integration/transactions/TestILoanBase.cs-177-184 (1)> 177-184: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

V2 signing is not isolated per signer
Both signatures are derived from the same mutable dictionary instance. If Sign(...) mutates input, the second signature is no longer independent.

Proposed fix

-        Dictionary<string, object> brokerDict = JsonSerializer.Deserialize<Dictionary<string, object>>(
-            prepared.ToJsonString(), XrplJsonOptions.Default);
+        string preparedJson = prepared.ToJsonString();
+        Dictionary<string, object> brokerDict = JsonSerializer.Deserialize<Dictionary<string, object>>(
+            preparedJson, XrplJsonOptions.Default);
+        Dictionary<string, object> borrowerDict = JsonSerializer.Deserialize<Dictionary<string, object>>(
+            preparedJson, XrplJsonOptions.Default);
         SignatureResult brokerSig = brokerWallet.Sign(brokerDict);

         // Device B (borrower): signs as counterparty (adds CounterpartySignature)
-        SignatureResult counterpartySig = borrowerWallet.SignAsLoanCounterparty(brokerDict);
+        SignatureResult counterpartySig = borrowerWallet.SignAsLoanCounterparty(borrowerDict);

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/Xrpl.Tests/Integration/transactions/TestILoanBase.cs` around lines 177
- 184, The test currently reuses the same mutable Dictionary<string, object>
(created via JsonSerializer.Deserialize(prepared.ToJsonString(),
XrplJsonOptions.Default)) for both brokerWallet.Sign and
borrowerWallet.SignAsLoanCounterparty which can cause the second sign to operate
on a mutated object; fix by ensuring V2 signing is isolated: create a fresh
independent dictionary for each signer (e.g. deserialize prepared.ToJsonString()
twice or deep-clone the brokerDict) so that brokerWallet.Sign and
borrowerWallet.SignAsLoanCounterparty each receive their own dictionary instance
and cannot interfere with one another.

Xrpl/Models/Transactions/LoanBrokerSet.cs-137-138 (1)> 137-138: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validation blocks valid LoanBrokerSet modify flow
ValidateLoanBrokerSet currently requires VaultID for all cases. Per this model’s own contract, updates can be done via LoanBrokerID without creating a new broker. This can reject valid transactions.

Proposed fix

-            if (!tx.TryGetValue("VaultID", out var vaultId) || vaultId is not string)
-                throw new ValidationException("LoanBrokerSet: missing field VaultID");
+            var hasVaultId = tx.TryGetValue("VaultID", out var vaultId);
+            var hasLoanBrokerId = tx.TryGetValue("LoanBrokerID", out var loanBrokerId);
+
+            if (!hasVaultId && !hasLoanBrokerId)
+                throw new ValidationException("LoanBrokerSet: missing required field VaultID or LoanBrokerID");
+
+            if (hasVaultId && vaultId is not string)
+                throw new ValidationException("LoanBrokerSet: invalid field VaultID");
+
+            if (hasLoanBrokerId && loanBrokerId is not string)
+                throw new ValidationException("LoanBrokerSet: invalid field LoanBrokerID");

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/LoanBrokerSet.cs` around lines 137 - 138,
ValidateLoanBrokerSet currently unconditionally requires VaultID which blocks
valid update transactions that reference an existing broker by LoanBrokerID;
change the validation in ValidateLoanBrokerSet to accept either a VaultID
(string) for new broker creation or a LoanBrokerID (string/appropriate type) for
updates: use tx.TryGetValue checks for both "VaultID" and "LoanBrokerID",
validate the present one(s) for correct type, and only throw a
ValidationException if neither field is present or if the provided field has the
wrong type, updating the exception message to indicate that either VaultID or
LoanBrokerID is required.

Tests/Xrpl.Tests/Integration/transactions/TestILoanBase.cs-232-237 (1)> 232-237: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Single fixed delay before tx lookup is flaky
A hardcoded 5-second wait plus one lookup can intermittently fail under slower ledger close or queue timing.

Proposed fix

-        // Wait for ledger to close and get the result via tx lookup
+        // Poll tx lookup until metadata is available (bounded retries)
         string txHash = global::Xrpl.Utils.Hashes.HashLedger.HashSignedTx(txBlob);
-        await Task.Delay(5000);
-
-        TxRequest txReq = new TxRequest(txHash);
-        TransactionResponse txResponse = await client.Tx(txReq);
+        TxRequest txReq = new TxRequest(txHash);
+        TransactionResponse txResponse = null;
+        for (int i = 0; i < 15; i++)
+        {
+            txResponse = await client.Tx(txReq);
+            if (txResponse?.Meta != null) break;
+            await Task.Delay(1000);
+        }
+        if (txResponse?.Meta == null)
+            throw new RippleException($"LoanSet tx not validated in time: {txHash}");

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/Xrpl.Tests/Integration/transactions/TestILoanBase.cs` around lines 232
- 237, The single fixed Task.Delay(5000) before doing the tx lookup is flaky;
replace it with a polling loop that repeatedly calls client.Tx(new
TxRequest(txHash)) (use txHash from
global::Xrpl.Utils.Hashes.HashLedger.HashSignedTx(txBlob)) with a short await
Task.Delay(pollInterval) between attempts until a valid TransactionResponse is
returned (e.g., response != null and contains the expected tx/validated fields)
or until a configurable timeout is reached; ensure you surface a clear failure
when the timeout elapses and avoid a single one-shot lookup.

Xrpl/Models/Transactions/XChainAddAccountCreateAttestation.cs-192-220 (1)> 192-220: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing type/range checks for critical attestation fields
XChainAccountCreateCount and WasLockingChainSend are only null-checked. Invalid values (non-numeric count, chain flag other than 0/1) currently pass validation.

Proposed fix

-            if (!tx.TryGetValue("XChainAccountCreateCount", out var count) || count is null)
-                throw new ValidationException("XChainAddAccountCreateAttestation: missing field XChainAccountCreateCount");
+            if (!tx.TryGetValue("XChainAccountCreateCount", out var count) ||
+                count is not string countStr ||
+                !ulong.TryParse(countStr, out _))
+                throw new ValidationException("XChainAddAccountCreateAttestation: invalid field XChainAccountCreateCount");

...
-            if (!tx.TryGetValue("WasLockingChainSend", out var wasLocking) || wasLocking is null)
-                throw new ValidationException("XChainAddAccountCreateAttestation: missing field WasLockingChainSend");
+            if (!tx.TryGetValue("WasLockingChainSend", out var wasLocking) ||
+                !byte.TryParse(wasLocking?.ToString(), out var wasLockingByte) ||
+                (wasLockingByte != 0 && wasLockingByte != 1))
+                throw new ValidationException("XChainAddAccountCreateAttestation: invalid field WasLockingChainSend");

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/XChainAddAccountCreateAttestation.cs` around lines
192 - 220, The current validation in XChainAddAccountCreateAttestation only
null-checks XChainAccountCreateCount and WasLockingChainSend allowing
non-numeric or out-of-range values; update the validation in the parsing block
to (1) ensure XChainAccountCreateCount is present and can be parsed to an
integer (use int.TryParse or convert from JSON number) and enforce a sensible
range (e.g., >= 0), throwing ValidationException with the same context if it
fails, and (2) ensure WasLockingChainSend is present and is either a boolean or
a numeric 0/1 (accept boolean true/false or parse int 0/1) and normalize it to a
bool, throwing ValidationException if it cannot be interpreted; adjust any
downstream uses of the parsed values in the XChainAddAccountCreateAttestation
processing to use the validated/converted int and bool variables.

🧹 Nitpick comments (1)

Tests/Xrpl.Tests/Integration/transactions/TestILedgerStateFix.cs (1)> 49-55: ⚡ Quick win

Guard amendment-disabled environments to reduce integration flakiness.
This assertion is strict for environments where LedgerStateFix is unavailable. Consider treating amendment-disabled outcomes as Assert.Inconclusive(...) so the test signals environment mismatch instead of functional failure.

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/Xrpl.Tests/Integration/transactions/TestILedgerStateFix.cs` around
lines 49 - 55, The test's strict assertion on result.EngineResult should be
relaxed to treat amendment-disabled environments as inconclusive: before the
Assert.IsTrue on txResult, detect amendment-disabled outcomes (either by
checking for the node-specific engine result code that indicates the
LedgerStateFix amendment is disabled or by calling an existing helper like
IsAmendmentDisabledOutcome(txResult) / an env flag) and call
Assert.Inconclusive("LedgerStateFix amendment disabled in this environment")
when detected; otherwise proceed with the existing check in TestILedgerStateFix
using the txResult variable.

ℹ️ Review info

  1. LoanSet.cs (292-296) — «PrincipalRequested should be is not string»
    Отклоняем. PrincipalRequested — это NumberType в BinaryCodec (не AccountID и не Hash256). В Dictionary<string, object> он приходит как JsonElement, а не как string. Проверка is not string сломала бы валидацию для валидных транзакций. Проверка is null корректна и соответствует паттерну валидации Amount-подобных полей (Amount, SignatureReward и т.д.).

  2. LedgerStateFix.cs (57-63) — «Add required Owner validation»
    Отклоняем. Owner — Optional поле. В TxFormat.cs оно объявлено как Requirement.Optional. Некоторые LedgerStateFix транзакции не требуют Owner (зависит от LedgerFixType). Добавление required-валидации отклонит валидные транзакции.

  3. XChainAddClaimAttestation.cs (45-48) — «Destination is required but never validated»
    Отклоняем. Destination — Optional поле. В TxFormat.cs строка 458: [Field.Destination] = Requirement.Optional. CodeRabbit ошибочно определил его как required. Валидация отсутствует корректно.

  4. TestILoanBase.cs (35-38) — «terQUEUED should not be treated as success»
    Отклоняем. В файле две функции валидации:

ValidateSubmitResult (строка 31) — проверяет результат submit, где terQUEUED валиден (транзакция поставлена в очередь)
ValidateResult (строка 35) — проверяет TransactionSummary.Meta.TransactionResult
В standalone rippled с ledger_acceptor queued-транзакции быстро финализируются. terQUEUED в Meta означает, что транзакция была queued, но включена в ledger. Удаление terQUEUED сделает интеграционные тесты нестабильными в CI.

  1. LoanBrokerSet.cs (137-138) — «VaultID blocks modify flow, should be optional»
    Отклоняем. Per rippled TxFormats.cpp, VaultID — Required поле для LoanBrokerSet. LoanBrokerID — Optional. CodeRabbit неверно предположил, что modify-flow использует только LoanBrokerID без VaultID. Текущая валидация корректна.

  2. XChainAddAccountCreateAttestation.cs (192-220) — «Type/range checks for XChainAccountCreateCount and WasLockingChainSend»
    Отклоняем. Null-check паттерн (|| count is null, || wasLocking is null) используется единообразно во всех валидаторах SDK для не-AccountID/не-string полей (Amount, XChainBridge, XChainClaimID и т.д.). Добавление type-parsing только для этих двух полей нарушит консистентность. WasLockingChainSend проверяется как byte на уровне модели (C# type system). Серверная валидация rippled делает финальную проверку range.

  3. TestILedgerStateFix (nitpick) — «Assert.Inconclusive for amendment-disabled»
    Отклоняем. Тест уже использует [TestCategory("LedgerStateFix")] для фильтрации. В CI rippled запускается с включёнными amendments. Assert.Inconclusive маскирует реальные ошибки.

- XChainBridgeType: add 20-byte AccountID length validation in FromParser
- NumberType: replace decimal with BigInteger to prevent OverflowException
- RequestManager: include ErrorException in error detection gate
- TestLoanSetEncode: replace Console.WriteLine with proper assertions
- VaultWithdraw/VaultClawback: add missing CurrencyConverter attributes
- XrplWallet: validate SigningPubKey before counterparty signing
- LoanSigningHelper: verify SigningPubKey match and guard against overwrite
- TestILoanBase: isolate mutable dicts in V2 signing, replace delay with polling
- Autofill: use IsReserveFeeTxNeed helper for fee cap bypass
- LendingProtocol docs: remove contradictory fee advice
- TxFormat: add CounterpartySignature as Optional for LoanSet
- LoanManage: add tfLoanImpair/tfLoanUnimpair mutual exclusion validation
- LoanPay: add tfLoanOverpayment/tfLoanFullPayment/tfLoanLatePayment exclusion
- DelegateSet: full Permissions validation (shape, max 10, non-delegable, uniqueness)
- LoanBrokerCoverClawback: at-least-one of LoanBrokerID/Amount validation
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Base/Xrpl.BinaryCodec/Types/NumberType.cs`:
- Around line 15-16: The XML comment on NumberType.cs describing mantissa
normalization does not match the implemented constants (the large literal
1000000000000000000 and long.MaxValue) — update the doc comments in the
NumberType class (both the header comment and the later doc block around the
other occurrence) to state that for non-zero values the mantissa is normalized
to [10^18, long.MaxValue] and that zero is represented as mantissa=0,
exponent=Int32.MinValue, so the prose matches the numeric constants in the
implementation.
- Around line 31-35: Validate mantissa/exponent invariants early in NumberType
decoding/creation: in the NumberType constructor (or FromParser) enforce a sane
exponent bound (use a constant like MAX_EXPONENT_ABS and throw on |exponent| >
MAX_EXPONENT_ABS to prevent unbounded BigInteger.Pow(10, exponent) work) and
enforce the zero-representation invariant (if Mantissa == 0 then Exponent must
be 0; if Mantissa != 0 then Exponent must not be 0), throwing a clear exception
on violation so ToJson and other code (e.g., BigInteger.Pow usage) never see
malformed or expensive states.
- Around line 124-127: NumberType.FromString currently uses decimal.Parse which
overflows for XRPL-extents (exponents ±32768); replace the decimal-based parse
with manual parsing of the string (handle sign, optional decimal point, and
scientific "e/E" exponent), convert the integer mantissa into a BigInteger and
compute the combined exponent as an int, then apply the existing
mantissa/exponent normalization logic (matching the approach used by ToJson and
BigInteger in this class) so values like "1e-32000" are accepted; ensure you
reference and reuse the same normalization code paths in NumberType
(mantissa/exponent normalization) and keep the exponent bounds consistent with
the XRPL comment in ToJson.

In `@DocFx/LendingProtocol-Guide.md`:
- Around line 26-34: The fenced diagram block in LendingProtocol-Guide.md is
missing a language tag and triggers markdownlint MD040; update the
triple-backtick that opens the ASCII diagram (the LoanSet/LoanBroker/Vault
diagram) to include a language tag such as "text" (e.g., change ``` to ```text)
so the block is explicitly marked as plain text while leaving the diagram
content unchanged.
- Around line 295-301: The current code creates one mutable dictionary
(brokerDict) and uses it for both brokerWallet.Sign(...) and
borrowerWallet.SignAsLoanCounterparty(...), risking the second signature being
derived from a mutated payload; to fix, produce isolated JSON dictionaries per
signer (e.g., call JsonSerializer.Deserialize(...) into two separate
Dictionary<string, object> instances or deep-clone brokerDict) and pass the
independent dictionary to brokerWallet.Sign(...) and
borrowerWallet.SignAsLoanCounterparty(...) respectively so each signer operates
on an unmodified payload.

In `@DocFx/LendingProtocol-Guide.ru.md`:
- Around line 332-333: В документе есть противоречие по комиссии после
использования Autofill: строка с утверждением "Комиссию необходимо увеличить
после автозаполнения" конфликтует со строкой "`Autofill` автоматически
рассчитывает корректную комиссию для LoanSet (включая overhead
CounterpartySignature)". Уберите одно из утверждений и оставьте согласованный
вариант: либо удалите строку про необходимость вручную увеличивать комиссию,
либо замените вторую строку на пояснение, что Autofill не учитывает overhead
CounterpartySignature и поэтому требуется дополнительная корректировка; при
правке сориентируйтесь по терминам "Autofill", "CounterpartySignature" и
"LoanSet" чтобы сохранить точную техническую формулировку.
- Around line 26-34: The fenced code block containing the ASCII diagram in
LendingProtocol-Guide.ru.md is missing a language tag (triggering markdownlint
MD040); add a language identifier such as "text" to the opening backticks of
that diagram block so it becomes ```text ... ```, preserving the exact diagram
content and spacing to avoid formatting changes.
- Around line 295-301: The code currently deserializes prepared.ToJsonString()
into a single Dictionary<string, object> named brokerDict and reuses it for both
brokerWallet.Sign(brokerDict) and
borrowerWallet.SignAsLoanCounterparty(brokerDict); if either signing method
mutates the dictionary the second signature will be computed against a mutated
payload. Fix by creating separate dictionaries for each signer (e.g.,
deserialize prepared.ToJsonString() twice or deep-clone brokerDict into a
distinct counterpartyDict) and pass brokerDict to brokerWallet.Sign and the
separate counterpartyDict to borrowerWallet.SignAsLoanCounterparty so each
signer operates on an independent payload.

In `@Xrpl/Models/Transactions/DelegateSet.cs`:
- Around line 116-122: The IList<object> branch in DelegateSet currently only
checks counts; update it to mirror the JsonElement branch by iterating the list
entries in the IList<object> path inside the DelegateSet validation/constructor,
verifying each entry is a Permission (or has the expected shape), ensuring
PermissionValue exists and is non-empty, rejecting any PermissionValue that
represents a non-delegable transaction type, and detecting/throwing on duplicate
PermissionValue entries; use the same ValidationException messages and the same
MaxPermissions/duplicate logic as the JsonElement handling to keep validation
consistent.

In `@Xrpl/Models/Transactions/LoanManage.cs`:
- Around line 101-109: The current check in LoanManage.cs only forbids
tfLoanImpair and tfLoanUnimpair together but does not require exactly one action
flag; update the validation after ParseFlags to build an action mask using the
LoanManageFlags values (e.g., tfLoanImpair, tfLoanUnimpair, tfLoanDefault —
include all action-related enum members), compute how many of those bits are set
on rawFlags, and throw a ValidationException from the same block if the count is
not exactly 1 (message like "LoanManage: exactly one action flag required").
Make the change in the same flags-parsing block where ParseFlags and rawFlags
are used so the validation runs for missing or multiple action flags.
- Around line 112-121: The ParseFlags method currently swallows invalid input
and returns 0, which masks malformed Flags payloads; update ParseFlags to
validate inputs and throw a descriptive exception (e.g., FormatException or
JsonException) when the input cannot be parsed instead of returning 0. Locate
the ParseFlags method and replace the final fallback case with throwing an
exception that includes the original flagsObj (or its type/value) and a clear
message; ensure callers of ParseFlags (e.g., any deserialization or constructor
logic that uses ParseFlags) handle or propagate this exception appropriately so
malformed payloads are detected rather than silently coerced.
- Around line 98-99: The current LoanManage parsing checks for presence and type
of "LoanID" but allows empty or whitespace strings; update the validation in the
LoanManage parsing logic (where tx.TryGetValue("LoanID", out var id) is used) to
also reject empty or whitespace values by verifying id is a non-empty string
(e.g., cast to string and use string.IsNullOrWhiteSpace) and throw the same
ValidationException("LoanManage: missing field LoanID") when the value is
null/empty/whitespace.

In `@Xrpl/Models/Transactions/VaultWithdraw.cs`:
- Around line 94-95: VaultID is only checked for being a CLR string in the
VaultWithdraw validation, but malformed non-Hash256 values slip through; after
the existing tx.TryGetValue("VaultID", out var vaultId) and string check in
VaultWithdraw.cs, validate that vaultId is a valid Hash256 (use the project's
Hash256 parsing/validation helper such as Hash256.TryParse/Parse or a regex that
enforces 64 hex chars), and if it fails throw a ValidationException with a clear
message (e.g., "VaultWithdraw: VaultID must be a valid Hash256"); update any
tests that assume the stricter validation if needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1adeb0b7-f1a3-4cef-8e2b-d9e08cd40142

📥 Commits

Reviewing files that changed from the base of the PR and between 7ac8ba0 and 9a8738f.

📒 Files selected for processing (17)
  • Base/Xrpl.BinaryCodec/Types/NumberType.cs
  • Base/Xrpl.BinaryCodec/Types/XChainBridgeType.cs
  • DocFx/LendingProtocol-Guide.md
  • DocFx/LendingProtocol-Guide.ru.md
  • Tests/Xrpl.BinaryCodec.Test/Types/TestLoanSetEncode.cs
  • Tests/Xrpl.Tests/Integration/transactions/TestILoanBase.cs
  • Xrpl/Client/RequestManager.cs
  • Xrpl/Models/Transactions/DelegateSet.cs
  • Xrpl/Models/Transactions/LoanBrokerCoverClawback.cs
  • Xrpl/Models/Transactions/LoanManage.cs
  • Xrpl/Models/Transactions/LoanPay.cs
  • Xrpl/Models/Transactions/TxFormat.cs
  • Xrpl/Models/Transactions/VaultClawback.cs
  • Xrpl/Models/Transactions/VaultWithdraw.cs
  • Xrpl/Sugar/Autofill.cs
  • Xrpl/Wallet/LoanSigningHelper.cs
  • Xrpl/Wallet/XrplWallet.cs
🚧 Files skipped from review as they are similar to previous changes (9)
  • Xrpl/Client/RequestManager.cs
  • Xrpl/Models/Transactions/LoanBrokerCoverClawback.cs
  • Xrpl/Sugar/Autofill.cs
  • Xrpl/Models/Transactions/VaultClawback.cs
  • Xrpl/Wallet/XrplWallet.cs
  • Xrpl/Wallet/LoanSigningHelper.cs
  • Xrpl/Models/Transactions/LoanPay.cs
  • Tests/Xrpl.Tests/Integration/transactions/TestILoanBase.cs
  • Xrpl/Models/Transactions/TxFormat.cs

Comment thread Base/Xrpl.BinaryCodec/Types/NumberType.cs Outdated
Comment thread Base/Xrpl.BinaryCodec/Types/NumberType.cs
Comment thread Base/Xrpl.BinaryCodec/Types/NumberType.cs
Comment thread DocFx/LendingProtocol-Guide.md Outdated
Comment thread DocFx/LendingProtocol-Guide.md
Comment thread Xrpl/Models/Transactions/DelegateSet.cs
Comment thread Xrpl/Models/Transactions/LoanManage.cs Outdated
Comment thread Xrpl/Models/Transactions/LoanManage.cs Outdated
Comment thread Xrpl/Models/Transactions/LoanManage.cs
Comment thread Xrpl/Models/Transactions/VaultWithdraw.cs Outdated
Rewrite Issue.cs to support MPT binary format matching rippled's
STIssue serialization: account(20) + noAccount(20) + sequence(4).
Add Hash192 type for 24-byte MPTokenIssuanceID. Add MptIssuanceId
to IssuedCurrency model and IssuedCurrencyConverter.

Integration tests:
- Vault: TestVaultClawback_MPT (MPT-backed vault lifecycle)
- Loan: TestLoanBrokerSet_MPT, TestLoanSet_MPT, TestLoanPay_MPT
- AMM: TestAMMCreate_MptXrpPool_RejectedByProtocol (XLS-62 pending)

Unit tests: TestIssueMpt (encode/decode roundtrip, binary match)
NumberType.cs:
- Fix doc comments: mantissa range is [10^18, long.MaxValue], not [10^15, 10^16-1]
- Add exponent/mantissa invariant validation in FromParser
- Replace decimal.Parse with BigInteger-based manual parsing to handle
  full XRPL exponent range (±32768) without overflow

LendingProtocol-Guide.md / .ru.md:
- Add ```text language tag to ASCII diagram blocks (MD040)
- Fix V2 signing example: use separate dicts per signer to avoid
  mutation of shared payload
- Remove contradictory fee statement in RU guide (Autofill handles it)

DelegateSet.cs:
- Add full validation in IList<object> branch matching JsonElement path
  (entry shape, PermissionValue, non-delegable types, duplicates)

LoanManage.cs:
- Require exactly one action flag (tfLoanDefault|tfLoanImpair|tfLoanUnimpair)
- ParseFlags now throws FormatException instead of silently returning 0
- Reject empty/whitespace LoanID strings

VaultWithdraw.cs:
- Validate VaultID as Hash256 (64 hex chars)
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
Xrpl/Models/Transactions/LoanManage.cs (1)

109-119: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Flags is still optional in validation despite “exactly one required”.

Line 110 only checks action bits when Flags is present. A missing Flags field currently passes, which violates the stated rule.

Proposed fix
-// Exactly one action flag is required; they are mutually exclusive
-if (tx.TryGetValue("Flags", out var flagsObj) && flagsObj is not null)
-{
-    uint rawFlags = ParseFlags(flagsObj);
-    uint actionBits = rawFlags & LoanManageActionMask;
-
-    // Count set action bits — must be exactly 1
-    int actionCount = System.Numerics.BitOperations.PopCount(actionBits);
-    if (actionCount != 1)
-        throw new ValidationException("LoanManage: exactly one action flag required (tfLoanDefault, tfLoanImpair, or tfLoanUnimpair)");
-}
+if (!tx.TryGetValue("Flags", out var flagsObj) || flagsObj is null)
+    throw new ValidationException("LoanManage: exactly one action flag required (tfLoanDefault, tfLoanImpair, or tfLoanUnimpair)");
+
+uint rawFlags = ParseFlags(flagsObj);
+uint actionBits = rawFlags & LoanManageActionMask;
+int actionCount = System.Numerics.BitOperations.PopCount(actionBits);
+if (actionCount != 1)
+    throw new ValidationException("LoanManage: exactly one action flag required (tfLoanDefault, tfLoanImpair, or tfLoanUnimpair)");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Transactions/LoanManage.cs` around lines 109 - 119, The
validation currently only enforces the "exactly one action flag" rule when Flags
exists, allowing missing Flags to pass; update the LoanManage validation to
require Flags and enforce exactly one action bit by treating absent Flags as
invalid: in the method performing this check (look for LoanManageActionMask,
ParseFlags, and the ValidationException thrown), if tx does not contain "Flags"
or Flags is null, throw a ValidationException with the same message, otherwise
ParseFlags(tx["Flags"]), compute actionBits = rawFlags & LoanManageActionMask,
and keep the PopCount check to ensure exactly one flag is set.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Base/Xrpl.BinaryCodec/Types/Issue.cs`:
- Around line 193-200: The early-return when curr.IsNative returns an Issue
after reading only 20 bytes can leave extra payload bytes unread and desync the
parser; in the method that decodes Issue (the code around curr.IsNative and the
Issue(...) constructor), check the provided hint or the remaining field length
before returning for native XRP: verify the field payload length equals 20 (or
that hint indicates native) and if not, either consume the remaining bytes for
this field or throw a descriptive parse error; ensure parser.Read/Peek APIs are
used to validate length prior to the early return so subsequent decoding remains
aligned.

In `@Xrpl/Models/Common/Common.cs`:
- Around line 64-71: The IsMpt() and ToString() logic treats an empty string
MptIssuanceId as a valid MPT; update both checks to ensure MptIssuanceId is not
null or empty/whitespace (e.g., use string.IsNullOrWhiteSpace or equivalent)
before considering it MPT. Modify IsMpt() to return true only when MptIssuanceId
contains non-whitespace content, and change the ToString() conditional to use
the same non-empty check so it emits "MPT:{MptIssuanceId}" only for valid values
and falls back to Currency.CurrencyReadableName() otherwise.

---

Duplicate comments:
In `@Xrpl/Models/Transactions/LoanManage.cs`:
- Around line 109-119: The validation currently only enforces the "exactly one
action flag" rule when Flags exists, allowing missing Flags to pass; update the
LoanManage validation to require Flags and enforce exactly one action bit by
treating absent Flags as invalid: in the method performing this check (look for
LoanManageActionMask, ParseFlags, and the ValidationException thrown), if tx
does not contain "Flags" or Flags is null, throw a ValidationException with the
same message, otherwise ParseFlags(tx["Flags"]), compute actionBits = rawFlags &
LoanManageActionMask, and keep the PopCount check to ensure exactly one flag is
set.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2e5f7efb-fb1c-490c-a124-1eed29583668

📥 Commits

Reviewing files that changed from the base of the PR and between 9a8738f and cea4f37.

📒 Files selected for processing (15)
  • .ci-config/rippled.cfg
  • Base/Xrpl.BinaryCodec/Types/Issue.cs
  • Base/Xrpl.BinaryCodec/Types/NumberType.cs
  • DocFx/LendingProtocol-Guide.md
  • DocFx/LendingProtocol-Guide.ru.md
  • Tests/Xrpl.BinaryCodec.Test/Types/TestIssueMpt.cs
  • Tests/Xrpl.Tests/Integration/transactions/TestIAMMCreateMpt.cs
  • Tests/Xrpl.Tests/Integration/transactions/TestILoan.cs
  • Tests/Xrpl.Tests/Integration/transactions/TestILoanBase.cs
  • Tests/Xrpl.Tests/Integration/transactions/TestIVault.cs
  • Xrpl/Client/Json/Converters/CurrencyConverter.cs
  • Xrpl/Models/Common/Common.cs
  • Xrpl/Models/Transactions/DelegateSet.cs
  • Xrpl/Models/Transactions/LoanManage.cs
  • Xrpl/Models/Transactions/VaultWithdraw.cs
✅ Files skipped from review due to trivial changes (2)
  • .ci-config/rippled.cfg
  • DocFx/LendingProtocol-Guide.ru.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • Xrpl/Models/Transactions/DelegateSet.cs
  • Tests/Xrpl.Tests/Integration/transactions/TestIVault.cs

Comment on lines 193 to +200
if (curr.IsNative)
{
// XRP case
// XRP: just 20 zero bytes
return new Issue(curr, null);
}
// IOU case: read 20-byte issuer
var issuer = AccountId.FromParser(parser);

// Non-XRP: read 20 more bytes (issuer for IOU, or noAccount marker for MPT)
byte[] secondBytes = parser.Read(20);
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard parser consumption with hint (or strict length checks) to prevent stream desync.

Line 193 can return as XRP after 20 bytes even when the field payload is longer, leaving unread bytes and shifting subsequent decode state on malformed inputs. Enforce expected length before early-return.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Base/Xrpl.BinaryCodec/Types/Issue.cs` around lines 193 - 200, The
early-return when curr.IsNative returns an Issue after reading only 20 bytes can
leave extra payload bytes unread and desync the parser; in the method that
decodes Issue (the code around curr.IsNative and the Issue(...) constructor),
check the provided hint or the remaining field length before returning for
native XRP: verify the field payload length equals 20 (or that hint indicates
native) and if not, either consume the remaining bytes for this field or throw a
descriptive parse error; ensure parser.Read/Peek APIs are used to validate
length prior to the early return so subsequent decoding remains aligned.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

STIssue — самоописывающийся формат: XRP = 20 нулевых байт, IOU = 40, MPT = 44. Парсер читает ровно столько байт, сколько нужно для каждого варианта. hint для Issue не передаётся — это стандартный подход (аналогично Amount). rippled никогда не отправит XRP Issue с лишними байтами.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Platonenkov, thank you for the clear explanation. You're right — STIssue is a self-describing format where the byte count itself determines the variant (XRP = 20, IOU = 40, MPT = 44), so the parser can never desync on well-formed data. The early return after 20 bytes for the native XRP case is entirely correct and consistent with how rippled serializes STIssue. My concern about unread bytes was not applicable here. I'll retract this comment.


✏️ Learnings added
Learnt from: Platonenkov
Repo: StaticBit-io/XrplCSharp PR: 6
File: Base/Xrpl.BinaryCodec/Types/Issue.cs:193-200
Timestamp: 2026-05-13T14:04:05.706Z
Learning: In `Base/Xrpl.BinaryCodec/Types/Issue.cs`, `STIssue` (the `Issue` type) uses a self-describing binary format: XRP = 20 bytes (all zeros), IOU = 40 bytes (20 currency + 20 issuer), MPT = 44 bytes (20 account + 20 noAccount marker + 4 sequence). The parser reads exactly as many bytes as required for each variant. The `hint` parameter is not passed for `Issue`, consistent with the same approach used for `Amount` in rippled. There is no parser desync risk from the early XRP return — rippled never sends an XRP Issue with extra bytes.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment thread Xrpl/Models/Common/Common.cs
- IssuedCurrency.IsMpt()/ToString(): use IsNullOrWhiteSpace instead of
  null check to reject empty/whitespace MptIssuanceId
- LoanManage validation: Flags is now mandatory (exactly one action flag
  required), not just validated when present
Fix validationReceived handler bug (was calling OnManifestReceived).
Add 3 missing streams: manifestReceived, bookChanges, serverStatus.
Add 5 RPC methods: vault_info, channel_authorize, channel_verify,
server_definitions, transaction_entry.
Remove dead code ValidationsStream.cs.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
Xrpl/Models/Subscriptions/BookChangesStream.cs (1)

47-48: ⚡ Quick win

Initialize Changes to an empty list by default.

This avoids null-handling in downstream event consumers when a payload omits changes.

♻️ Proposed fix
 [JsonPropertyName("changes")]
-public List<BookChange> Changes { get; set; }
+public List<BookChange> Changes { get; set; } = new();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Xrpl/Models/Subscriptions/BookChangesStream.cs` around lines 47 - 48, The
Changes property on the BookChangesStream class may be null if the payload omits
it; initialize it to an empty list by default to avoid downstream null checks —
update the property declaration for BookChangesStream (Changes) to instantiate a
new List<BookChange> as its default value so consumers always receive an empty
collection instead of null.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Tests/TestsClients/Test.ClonsoleApp/Program.cs`:
- Around line 420-423: InitTestData calls TryFillAccounts before the static
client field is assigned in Main, so TryFillAccounts (and methods it calls like
client.GetXrpFreeBalance) may dereference a null client; fix by ensuring client
is initialized before any call to TryFillAccounts (for example move the client
assignment in Main to occur before InitTestData is invoked or add a guard that
initializes/throws with a clear message inside TryFillAccounts), and/or change
TryFillAccounts to accept an IXrplClient parameter instead of using the static
client to avoid relying on uninitialized static state.

---

Nitpick comments:
In `@Xrpl/Models/Subscriptions/BookChangesStream.cs`:
- Around line 47-48: The Changes property on the BookChangesStream class may be
null if the payload omits it; initialize it to an empty list by default to avoid
downstream null checks — update the property declaration for BookChangesStream
(Changes) to instantiate a new List<BookChange> as its default value so
consumers always receive an empty collection instead of null.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d825d214-acef-41d9-8871-84d2d176b314

📥 Commits

Reviewing files that changed from the base of the PR and between f5bdeff and 05e800b.

📒 Files selected for processing (17)
  • Tests/TestsClients/Test.ClonsoleApp/Program.cs
  • Tests/Xrpl.Tests/Client/TestSubscribe.cs
  • Tests/Xrpl.Tests/Integration/transactions/TestIVault.cs
  • Tests/Xrpl.Tests/Sugar/TestUAutofillFees.cs
  • Xrpl/Client/IXrplClient.cs
  • Xrpl/Client/connection.cs
  • Xrpl/Models/Common/Common.cs
  • Xrpl/Models/Methods/ChannelAuthorize.cs
  • Xrpl/Models/Methods/ChannelVerify.cs
  • Xrpl/Models/Methods/ServerDefinitions.cs
  • Xrpl/Models/Methods/TransactionEntry.cs
  • Xrpl/Models/Methods/VaultInfo.cs
  • Xrpl/Models/Subscriptions/BookChangesStream.cs
  • Xrpl/Models/Subscriptions/ManifestStream.cs
  • Xrpl/Models/Subscriptions/ResponseStreamType.cs
  • Xrpl/Models/Subscriptions/ServerStatusStream.cs
  • Xrpl/Models/Subscriptions/ValidationsStream.cs
💤 Files with no reviewable changes (1)
  • Xrpl/Models/Subscriptions/ValidationsStream.cs
✅ Files skipped from review due to trivial changes (2)
  • Tests/Xrpl.Tests/Sugar/TestUAutofillFees.cs
  • Xrpl/Models/Subscriptions/ServerStatusStream.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • Xrpl/Models/Common/Common.cs

Comment thread Tests/TestsClients/Test.ClonsoleApp/Program.cs
TryFillAccounts was using static client field before it was assigned.
Initialize BookChangesStream.Changes to empty list to avoid null.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Xrpl/Models/Subscriptions/BookChangesStream.cs`:
- Around line 47-48: The Changes auto-property on BookChangesStream can be set
to null by System.Text.Json when payloads contain "changes": null; replace the
auto-property with a private backing field (e.g., _changes) and implement the
public property Changes with get => _changes; set => _changes = value ?? new
List<BookChange>(); so deserialization will coalesce null into an empty
List<BookChange> and avoid NREs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8b0d6381-2b41-45b8-a0d7-841e97480ffc

📥 Commits

Reviewing files that changed from the base of the PR and between 05e800b and 1c13941.

📒 Files selected for processing (2)
  • Tests/TestsClients/Test.ClonsoleApp/Program.cs
  • Xrpl/Models/Subscriptions/BookChangesStream.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • Tests/TestsClients/Test.ClonsoleApp/Program.cs

Comment thread Xrpl/Models/Subscriptions/BookChangesStream.cs Outdated
System.Text.Json sets null on "changes": null, overriding initializer.
@Platonenkov Platonenkov merged commit 9527a1b into dev May 13, 2026
4 checks passed
@Platonenkov Platonenkov deleted the feature/missing_func branch May 13, 2026 22:14
@coderabbitai coderabbitai Bot mentioned this pull request May 13, 2026
@coderabbitai coderabbitai Bot mentioned this pull request May 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant