Skip to content

v11.9.0

Choose a tag to compare

@github-actions github-actions released this 27 Sep 18:36
641b5af
  • XrplNumber is the value type of the XRPL Number fields (#214, part of #211). It lives in Xrpl.BinaryCodec.Numbers and holds a value the way rippled's Number class does on the large mantissa scale: a sign, a mantissa in [10^18, 10^19 - 1] and an exponent in [-32768, 32768].

    • Parse / TryParse read decimal and scientific text; ToString() writes what rippled's to_string(Number) writes, 1e13 for 10^13; Mantissa / Exponent give the wire pair of Number::mantissa() / Number::exponent()
    • equality and ordering by value; implicit conversion from int, explicit from long and decimal, explicit to decimal and TryToDecimal, which rounds to nearest (ties to even) past 28 fractional digits and fails on overflow
    • XrplNumberJsonConverter is applied to the type itself: reads a JSON string or number, writes the string
    • TestUXrplNumber carries the to_string vectors of rippled's NumberTest.to_string and the text vectors of xrpl.js st-number.test.ts; TestUNumberFields checks model JSON and the encoded bytes
  • A Number value the ledger cannot hold exactly is refused instead of rounded. NumberType.FromString rounded 9223372036854775895 to 9223372036854775900 and signed that; it now throws FormatException, as rippled's numberFromJson refuses the value with "number cannot be represented". The limit is 19 significant digits, and 18 above 9223372036854775807

  • NumberType is built on XrplNumber. NumberType.Value returns it and NumberType(XrplNumber) creates the canonical wire pair. ToJson() and ToString() now write rippled's text, so the codec decodes PrincipalRequested 10000000000000 as "1e13", not "10000000000000". FromParser accepts the wire exponent 32769 that rippled produces for a mantissa above int64 (9223372036854775810e32768), and refuses a mantissa of long.MinValue

  • Breaking: the Number fields of the vault and lending models are XrplNumber? instead of string:

    • LOVault: AssetsTotal, AssetsAvailable, AssetsMaximum, LossUnrealized
    • LOLoanBroker: DebtTotal, DebtMaximum, CoverAvailable
    • LOLoan: PrincipalOutstanding, TotalValueOutstanding, PeriodicPayment, ManagementFeeOutstanding, LoanOriginationFee, LoanServiceFee, LatePaymentFee, ClosePaymentFee
    • ILoanSet / LoanSet / LoanSetResponse: PrincipalRequested, LoanOriginationFee, LoanServiceFee, LatePaymentFee, ClosePaymentFee
    • ILoanBrokerSet / LoanBrokerSet / LoanBrokerSetResponse: DebtMaximum
    • IVaultCreate / VaultCreate / VaultCreateResponse and IVaultSet / VaultSet / VaultSetResponse: AssetsMaximum

    Migration: add using Xrpl.BinaryCodec.Numbers;. Assign an int directly (PrincipalRequested = 10000000), anything else through XrplNumber.Parse("...") or a cast from long or decimal. Read values with ToString(), which is rippled's text, compare them with == and <, and convert with TryToDecimal or (decimal). Code that parsed the string with decimal.Parse must handle the scientific form or switch to TryToDecimal

  • An unreadable Number costs one field, not the object. LenientXrplNumberConverter is applied to the Number fields of LOVault, LOLoanBroker, LOLoan and the four *Response models above: a value this version refuses reads as null and the rest of the entry, an account_objects page or a transaction still deserializes.

  • XrplNumber.Parse works in time linear in the length of the text: it accumulates at most 19 significant digits and carries leading and trailing zeros as an exponent (Parse_StaysLinearOnMegabyteInput). The written exponent may reach ±10^9; whether the value fits is decided after the fraction digits are counted, so "0e100001" is zero

  • new NumberType(0, exponent) with an exponent other than int.MinValue or 0 throws ArgumentOutOfRangeException; FromParser already refused those bytes and reports them as FormatException

  • Autofill reads LOLoan.PeriodicPayment and LoanServiceFee through TryToDecimal when it estimates the LoanPay fee

  • LendingProtocol-Guide and Vault-Guide (both languages) assign Number fields as numbers, and the lending guide describes XrplNumber

  • Previews through simulate (#215, part of #211). PreviewSugar adds PreviewLoanPay, PreviewVaultDeposit, PreviewVaultWithdraw, PreviewAMMDeposit, PreviewAMMWithdraw and PreviewBalanceChanges to IXrplClient. Each autofills the transaction, runs it through simulate and returns a TransactionPreview<TOutcome>: EngineResult, WouldSucceed, the simulated Metadata, the autofilled Transaction, and an Outcome read from the metadata.

    • LoanPaymentOutcome gives PaymentsMade, PrincipalPaid, PaidToVault, InterestToVault, PaidToBroker, TotalPaid and LoanAfter of a LoanPay. The split follows the money: the vault receives principal and interest, and the broker receives the rest
    • VaultOutcome gives AccountAssetChange, AccountShareChange, VaultAssetChange and VaultAfter of a vault deposit or withdrawal
    • AmmOutcome gives AssetChange, Asset2Change, LpTokenChange and AmmAfter of an AMM deposit or withdrawal
    • each outcome also has a FromMetadata, which reads the same figures from a validated transaction
    • for an account that issues the asset, the change sums every trust line in that currency
    • VaultOutcome.FromMetadata reads the vault named by the transaction's VaultID and refuses a transaction other than VaultDeposit, VaultWithdraw or VaultClawback
    • TestIPreviewLoanVault and TestIPreviewAmm preview a transaction, submit the previewed transaction unchanged, and compare the two outcomes field by field: an XRP LoanPay, an MPT LoanPay, a vault deposit and withdrawal, and a single-sided AMM deposit and withdrawal
  • PreviewLoanSet shows a loan's terms before the borrower signs (#217, part of #211). It simulates the LoanSet with an empty CounterpartySignature, which simulate accepts unverified, and returns a LoanSetOutcome: LoanId, the created Loan, BorrowerReceives, PaidFromVault, InterestTotal, ServiceFeesTotal and TotalToRepay. LoanSetOutcome.FromMetadata reads the same from a validated LoanSet

    • TestIPreviewLoanVault.TestPreviewLoanSet_MatchesTheLoanTheSignedTransactionCreates previews a loan unsigned, creates it with the borrower's signature, and compares the two loans field by field
  • LoanSchedule.Project projects a loan's regular payments (#217, #220, part of #211). It starts from a Loan entry - from the ledger or from PreviewLoanSet - and repeats rippled's computePaymentComponents for each payment in rippled's own arithmetic - XrplNumber in the NumberContext the amendments select, with roundToAsset computed as STAmount computes it, 16 significant digits and then the loan's scale for an issued currency: the step from the rounded state to the rounded theoretical state after it, capped at the rounded PeriodicPayment, with the final payment clearing the rest. Each LoanScheduleRow has DueDate, Principal, Interest, ManagementFee, ServiceFee, Total and the outstanding amounts after it

    • LedgerRules.FixCleanup3_2_0 selects the rounding of the target state and FixCleanup3_3_0 the Number scale; LedgerRules.FromNodeAsync reads both from the node
    • TestIPreviewLoanVault.TestLoanSchedule_MatchesEveryPaymentTheNodeTakes (XRP, 6 payments, 10% management fee) and TestLoanSchedule_Mpt_MatchesEveryPaymentTheNodeTakes (MPT, 5 payments) and TestLoanSchedule_Iou_MatchesEveryPaymentTheNodeTakes (USD: 7, 3 and 12 payments with fractional principals, management fees and service fees) project a loan, pay every payment and compare each with its row; TestULoanSchedule pins the recorded splits
  • The Blazor test client (Tests/TestsClients/Blazor-WebAssembly) has a Loan preview tab next to the connection page. It shows a new loan's terms from PreviewLoanSet and its schedule from LoanSchedule.Project, or the remaining schedule of an existing loan, and checks the terms against LoanSet's preflight limits first. On a test network its Full flow mode runs the whole cycle: it funds a broker and a borrower, creates a vault and broker, previews the terms, co-signs and submits the LoanSet, and pays each payment next to its projected row

  • The Blazor test client has an AMM preview tab (Pages/AmmPreview.razor, #225). It loads a pool with amm_info - an account's LP tokens and its trading fee, the auction slot's discount included, when an account is given - and quotes any mode AmmLiquidity covers next to the node's simulate answer, with the pool the ledger will hold afterwards

  • The Blazor test client has a Vault preview tab (Pages/VaultPreview.razor) that quotes VaultShares next to simulate, the phase of a closed-ended vault judged at the validated close time. The Loan preview tab shows LoanOrigination.Compute next to the node's LoanSet, and previews a payment on an existing loan - LoanPayments.PaymentForAmount with or without tfLoanOverpayment, LatePaymentDue, FullPaymentDue - next to simulate; its full flow can create a loan that allows overpayments. The AMM preview tab gains the effective-price and empty-pool modes, and its asset selector binds to a string

  • LendingProtocol-Guide (both languages) shows PreviewLoanSet and LoanSchedule

  • ServerFeatures.GetByName returns null for a name the node does not list; it returned an empty pair whose Value was null (GetByName_UnknownName_IsNull)

  • LoanPayments reads a Loan entry without asking the node:

    • RegularPaymentCap: PeriodicPayment rounded up to the asset, plus LoanServiceFee; on the final payment, TotalValueOutstanding plus LoanServiceFee. Each part is rounded up on the XrplNumber before it becomes a decimal, with a step finer than 10^-28 rounded up to 10^-28; a cap beyond decimal returns null
    • IsPaymentLate and IsPastGracePeriod, with an inclusive or exclusive boundary, comparing in UTC: a local time is converted, and an unspecified one is taken as UTC
    • Autofill rounds the periodic payment through the same helper when it estimates the LoanPay fee
  • LoanPayments.LatePaymentDue and LoanPayments.FullPaymentDue (#221, part of #211) compute what a late payment and an early full payment cost at a given parent close time, as rippled's computeLatePayment and computeFullPayment do, and return a LoanPaymentDue: Principal, InterestToVault, PaidToBroker and Total. Each returns null where the node refuses that payment: not yet late (tecTOO_SOON), already late (tecEXPIRED), or one payment left (tecKILLED)

    • the lending formulas moved from LoanSchedule into the internal LendingMath, which both use
    • LedgerRules.FixCleanup3_4_0 selects whether a payment exactly at NextPaymentDueDate is late; FromNodeAsync reads it
    • TestLatePaymentDue_MatchesWhatTheNodeCharges, TestFullPaymentDue_MatchesWhatTheNodeCharges and TestFullPaymentDue_Iou_MatchesWhatTheNodeCharges pay a loan late or close it early and compare the node's split with the computed one
  • LoanOrigination.Compute (#222, part of #211) computes the Loan a LoanSet would create, offline, from the transaction, the vault and the broker, as rippled's LoanSet and computeLoanProperties do: PeriodicPayment, TotalValueOutstanding, ManagementFeeOutstanding, LoanScale and the copied terms, in a LoanOriginationTerms. It repeats the checks that depend on the amounts and reports the node's result in Refusal and RefusalReason: fewer assets available than requested (tecINSUFFICIENT_FUNDS), a value the asset cannot hold, a value finer than the loan's scale, and the amortization guards (tecPRECISION_LOSS)

    • the internal AssetRounding gains STAmount's conversion, exponent, scale() and isRounded
    • the internal LendingMath gains PeriodicPayment (loanPeriodicPayment); PaymentFactor takes the rate, the payment count, fixCleanup3_2_0 and the context instead of LoanTerms; LoanTerms.Create builds terms from their parts
    • LendingProtocol-Guide (both languages) shows LoanOrigination.Compute
    • TestLoanOrigination_MatchesWhatTheNodeWouldCreate compares eleven XRP and USD terms with PreviewLoanSet: seven created loans field by field, with the schedules projected from each, and four refusals by result
  • LoanPayments.PaymentForAmount computes what a regular LoanPay does with its Amount, as rippled's makeRegularPayment does: as many regular payments as the amount covers, up to 100, then, with tfLoanOverpayment on a loan created with it, the rest as an overpayment (computeOverpaymentComponents, tryOverpayment, doOverpayment) that repays principal and re-amortizes the loan. It returns a LoanAmountPayment: the split in Due (LoanPaymentDue), PaymentsMade, IsOverpaid, the Loan as the payment leaves it in LoanAfter, or the node's result in Refusal and RefusalReason (temBAD_AMOUNT, tecNO_PERMISSION, tecKILLED, tecEXPIRED, tecINSUFFICIENT_PAYMENT)

    • LedgerRules.FixCleanup3_1_3 selects whether the amount is truncated to the loan's scale before the overpayment is taken from it; FromNodeAsync reads it
    • computeLoanProperties and checkLoanGuards moved from LoanOrigination into the internal LendingMath (ComputeLoanProperties, LoanGuards, LoanProperties), which the overpayment reuses; LendingMath.Overpay ports the overpayment
    • LendingProtocol-Guide (both languages) shows LoanPayments.PaymentForAmount
    • LendingMath.LoanGuards returns the node's result with the reason: a zero-rate loan that comes out with interest is tecINTERNAL, as in checkLoanGuards, and LoanOrigination.Compute reports it instead of tecPRECISION_LOSS
    • TestPaymentForAmount_MatchesWhatTheNodeTakes pays an XRP loan three payments and a half without the flag and two and a half with it, and previews two refusals; TestPaymentForAmount_Iou_Overpayment_MatchesWhatTheNodeTakes overpays a USD loan; both compare the split, the payment count and the loan left with the node's
  • VaultShares (#223, part of #211) computes vault deposits and withdrawals offline from the Vault entry and the share count, as rippled's VaultDeposit, VaultWithdraw and VaultHelpers.cpp do: Deposit, WithdrawAssets and RedeemShares return a VaultQuote with the Shares minted or burned and the Assets moved, or the node's result in Refusal and RefusalReason (tecPRECISION_LOSS, tecINSUFFICIENT_FUNDS, tecLIMIT_EXCEEDED, and tecPATH_DRY where the conversion overflows, a vault with assets and no shares outstanding included)

    • the conversions follow assetsToSharesDeposit, sharesToAssetsDeposit, assetsToSharesWithdraw and sharesToAssetsWithdraw, with roundToVaultScale, clampToAssetsTotalScale, the dust checks, the sole shareholder's waived unrealized loss and the final withdrawal of all available assets
    • LedgerRules (Xrpl/Sugar/LedgerRules.cs) carries the amendment flags for both the lending and the vault helpers
    • Vault-Guide (both languages) shows VaultShares
    • TestUVaultShares pins the empty vault's first deposit and the tecPATH_DRY of redeeming shares when none are outstanding
    • TestIVaultShares quotes every step of XRP, USD and MPT vaults offline, compares it with PreviewVaultDeposit or PreviewVaultWithdraw and submits it: deposits, withdrawals by assets and by shares, final withdrawals, and refusals for dust and for more than the vault or the holder has
    • TestVaultShares_WithUnrealizedLoss_MatchWhatTheNodeMoves impairs a loan so the vault carries LossUnrealized, then compares withdrawals by assets and by shares in the redemption phase with the node; TestILoanBase.BuildBrokerVaultAsync takes the investment window
    • Deposit, WithdrawAssets and RedeemShares take an optional parentCloseTime and, under LendingProtocolV1_1, repeat getVaultPhase for a closed-ended vault: a deposit outside the subscription phase is tecEXPIRED, a withdrawal in the investment phase tecTOO_SOON; LedgerRules.LendingProtocolV1_1 selects it and FromNodeAsync reads it. TestUVaultShares pins the phase boundaries; TestIVaultShares quotes every step at the validated close time and compares the investment-phase refusals with the node; Vault-Guide (both languages) describes the check
  • AmmLiquidity (#224, part of #211) computes AMM deposits and withdrawals exactly, as rippled's AMMDeposit and AMMWithdraw do under fixAMMv1_3, from an AmmPool read with AmmPool.FromAmmInfo. An AmmQuote carries Asset1, Asset2, LpTokens and PoolAfter - the balances and LP token balance the ledger will hold - or the node's result in Refusal and RefusalReason (tecAMM_INVALID_TOKENS, tecAMM_FAILED, tecAMM_BALANCE, tecPRECISION_LOSS)

    • deposits: DepositForTokens (tfLPToken), DepositBoth (tfTwoAsset), DepositSingle (tfSingleAsset), DepositSingleForTokens (tfOneAssetLPToken), DepositWithEffectivePrice (tfLimitLPToken), DepositIntoEmptyPool (tfTwoAssetIfEmpty); withdrawals: WithdrawForTokens (tfLPToken), WithdrawAll (tfWithdrawAll), WithdrawBoth (tfTwoAsset), WithdrawSingle (tfSingleAsset), WithdrawSingleForTokens (tfOneAssetLPToken), WithdrawAllOfAsset (tfOneAssetWithdrawAll), WithdrawWithEffectivePrice (tfLimitLPToken, its EPrice in LP tokens per unit of the asset); InitialLpTokens gives the LP tokens AMMCreate issues
    • AmmPool.FromAmmInfo takes the account's trading fee as getTradingFee does: the auction slot's discounted fee for its holder and authorized accounts until it expires
    • the internal AmmFormulas ports equations 3 to 8, adjustLPTokens, adjustAssetInByTokens, adjustAssetOutByTokens and checkAMMPrecisionLoss; the internal AmountMath ports STAmount's divide, multiply, addition and toSTAmount
    • LedgerRules gains LargeNumbers, FixAMMv1_1, FixAMMv1_3 and MPTokensV2
    • AmmMath points to AmmLiquidity for exact figures
    • TestIAmmLiquidity runs a token/XRP pool through every covered mode, the discounted fee, the only liquidity provider, the effective-price modes - a deposit taken whole, one capped by the price, one refused - and the withdrawal that deletes the pool: each step is quoted, compared with PreviewAMMDeposit or PreviewAMMWithdraw for acceptance or the refusal, submitted, and the pool is compared with amm_info to the last digit; TestInitialLpTokens_MatchWhatAMMCreateIssues compares InitialLpTokens and DepositIntoEmptyPool with the LP tokens of a new pool
    • DepositWithEffectivePrice and WithdrawWithEffectivePrice refuse an EPrice that is not positive and a negative Amount with temBAD_AMOUNT, as invalidAMMAmount does in preflight; TestUAmmLiquidity pins it
  • BalanceChanges.GetBalanceChanges reports MPT balances. It reports a holder's change in MPTAmount and, like a trust line issuer, the issuer's side as the opposite of the change in OutstandingAmount, as a Currency with MPTokenIssuanceID. A holder whose modified MPToken lists no previous MPTAmount is resolved against the change in the issuance's OutstandingAmount, taken as zero when the issuance node is absent. TestUBalanceChangesMpt covers a vault deposit and withdrawal recorded from a node, and both outcomes of the ambiguous case

  • Tests/Xrpl.Tests/Fixtures/Simulate holds the recorded simulate responses, and its README says where each one comes from

  • LendingProtocol-Guide and Vault-Guide (both languages) show the payment cap and the previews

  • RawJson and XrplResponse<T>.Raw document the cost of a large response (#213): why the frame is allocated per message and cannot be pooled, and that a large result read with Utf8JsonReader over RawJson.Span avoids the second message-sized copy a JsonElement result makes

  • XrplNumber arithmetic, bit-exact with rippled's Number (#218, part of #211). Add, Subtract, Multiply, Divide, Power, Root2, Truncate, ToInt64 and Abs round as rippled's Number.cpp does under a NumberContext; +, -, *, / and unary - use NumberContext.Default

    • NumberContext names the mantissa scale (NumberMantissaScale: Small, LargeLegacy, Large320, Large330) and the rounding mode (NumberRounding: ToNearest, TowardsZero, Downward, Upward); Default is Large330 rounding to nearest, ForAmendments maps SingleAssetVault / LendingProtocol, fixCleanup3_2_0 and fixCleanup3_3_0 to the scale, WithRounding changes the mode
    • the engine is NumberCore (internal), a statement-for-statement port of the parts of Number.cpp that rippled's transaction processing calls, with 128-bit intermediates
    • Root2 throws ArithmeticException on an input where rippled's Newton-Raphson loop would cycle with a period longer than two and never return
    • TestUNumberVectors replays 15,935 results printed by rippled's Number.cpp at 00606bec1 across all scales, rounding modes and operations; Tests/Xrpl.BinaryCodec.Test/Fixtures/Number holds the vectors, the generator and how to rebuild them. TestUXrplNumberArithmetic covers the public API
    • Xrpl.BinaryCodec exposes its internals to Xrpl.BinaryCodec.Test
  • Twelve Xrpl.BinaryCodec.Test classes take the TestU prefix that CI's unit-test filter matches (#218): TestUBinarySerializer, TestUEnumParity, TestUFieldDispatch, TestUAmount, TestUInt32Type, TestUInt64Type, TestUIouValueTrailingDot, TestUIssueMpt, TestULoanSetEncode, TestUNumberType, TestUXChainBridgeType and TestUXrplNumber

  • Breaking: AMMBid.AuthAccounts changes from List<AuthAccount> to List<AuthAccountWrapper> (#219). Entries in AMMBid, IAMMBid and AMMBidResponse serialize as {"AuthAccount": {"Account": ...}}

    • migration: new AuthAccount { Account = address } becomes new AuthAccountWrapper(address); read the address as entry.AuthAccount.Account
    • TestUAMMBidAuthAccounts signs an AMMBid with an authorized account and decodes the blob, and reads both shapes; TestAMMBid_AuthAccounts_TradeAtTheDiscountedFee submits the bid, finds the account in amm_info and compares its deposit at the discounted fee with the node
  • XrplErrorCodes covers rippled 3.4.0's error table (#231): 27 constants added, each mapped by XrplErrorClassifier:

    • slowDown (429) and json_rpc are TemporaryServerProblem and retryable; highFee is retryable and user-fixable
    • unlBlocked, wrongNetwork, internal and dbDeserialization are ServerState, not retryable
    • forbidden is UnsupportedRequest; delegateActNotFound and noPathRequest are NotFound; masterDisabled, badCredentials, badSecret, badSeed, badKeyType, badIssuer, badFeature, channelMalformed, channelAmtMalformed, domainMalformed and oracleMalformed are InvalidInput; alreadyMultisig, alreadySingleSig, signingMalformed, transactionSigned, badSyntax and malformedStream are BadRequest
    • an error response without status is rejected with RippledException, carrying the ErrorResponse, instead of a plain XrplException
    • TestUXrplErrorCodes checks every code of rippled 3.4.0's table has a constant, that no constant classifies as Unknown, and the status-less path
  • Breaking: VaultCreate no longer declares an Amount field (#208). The property is removed from IVaultCreate, VaultCreate and VaultCreateResponse

    • migration: create the vault without an amount and fund it with VaultDeposit
    • TestUTransactionModelFieldsConformance checks every wire property a transaction request or response model declares against the fields the vendored transactions.macro gives its type. VaultCreate.Amount was the only violation
  • Lending rates are documented in their actual unit, 1/10th of a basis point, where 100000 = 100% (#209)

    • LoanSet: InterestRate, LateInterestRate, CloseInterestRate, OverpaymentInterestRate and OverpaymentFee said 1/100th of a basis point. LoanBrokerSet: CoverRateMinimum and CoverRateLiquidation said the same
    • LOLoanBroker.ManagementFeeRate gave its range as 0-100000; it is 0-10000, up to 10%. LOLoanBroker.CoverRateLiquidation now states its unit
    • every rate comment on LoanSet, LoanBrokerSet, LOLoan and LOLoanBroker carries a worked example (100000 = 100%, 5000 = 5%)
    • LendingProtocol-Guide (both languages): the broker example labelled 15000 as 150%, 12000 as 120% and 100 as 1%. They are 15%, 12% and 0.1%. The step that presented the rates as an update to an existing broker now sets them on the creating LoanBrokerSet, since rippled refuses them on a modification with temINVALID. The LoanBroker field table listed Asset, Asset2, AssetsAvailable and AssetsTotal, which the object does not have, and now matches ledger_entries.macro
    • LendingProtocol-Guide (both languages) now creates a closed-ended vault in its walkthrough, which LoanBrokerSet requires under LendingProtocolV1_1 (tecNO_PERMISSION on an open-ended one). It states which phase admits the vault deposit (subscription) and the loan (investment), and lists tecTOO_SOON, tecEXPIRED and the new tecNO_PERMISSION causes under Common Errors
    • TestILoan.TestLoanSet_InterestRateIsInTenthBasisPoints creates a loan at InterestRate = 50000 and checks that the node charges 50% a year on it
  • Hashes.HashOfferId returns the index the node assigns to an Offer: the namespace is hashed as rippled's uint16 (00 6F), not three bytes. HashOfferId(string, uint) added; the int overload delegates to it

  • Hashes.HashTrustline returns the index the node assigns to a RippleState

    • a 3-character currency code is encoded as the 20-byte code with the ISO letters at bytes 12-14
    • the two AccountIDs are ordered as unsigned bytes, as rippled does, instead of through BigInteger.Parse
    • XRP, as the ISO code or the all-zero hex code, throws ArgumentException
  • TestUHashes covers every ledger-object helper - HashAccountRoot (re-enabled), HashOfferId, HashTrustline with ISO and hex codes, HashSignerListId, HashEscrow - with the xrpl.js vectors and two node-assigned offer indices. TestILedgerObjectIds creates an account, offer, trust line, escrow and signer list on the stand and checks each helper against the index account_info / account_objects report